Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
14f8f9d5
Commit
14f8f9d5
authored
Jul 17, 2010
by
Vincent Povirk
Committed by
Alexandre Julliard
Jul 19, 2010
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ole32: Remove the BigBlockFile abstraction and always use an ILockBytes.
parent
41714994
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
144 additions
and
221 deletions
+144
-221
Makefile.in
dlls/ole32/Makefile.in
+1
-1
filelockbytes.c
dlls/ole32/filelockbytes.c
+121
-177
storage32.c
dlls/ole32/storage32.c
+19
-14
storage32.h
dlls/ole32/storage32.h
+3
-29
No files found.
dlls/ole32/Makefile.in
View file @
14f8f9d5
...
...
@@ -21,6 +21,7 @@ C_SRCS = \
dictionary.c
\
enumx.c
\
errorinfo.c
\
filelockbytes.c
\
filemoniker.c
\
ftmarshal.c
\
git.c
\
...
...
@@ -39,7 +40,6 @@ C_SRCS = \
pointermoniker.c
\
regsvr.c
\
rpc.c
\
stg_bigblockfile.c
\
stg_prop.c
\
stg_stream.c
\
storage32.c
\
...
...
dlls/ole32/
stg_bigblockfile
.c
→
dlls/ole32/
filelockbytes
.c
View file @
14f8f9d5
/******************************************************************************
*
* BigBlockFile
*
* This is the implementation of a file that consists of blocks of
* a predetermined size.
* This class is used in the Compound File implementation of the
* IStorage and IStream interfaces. It provides the functionality
* to read and write any blocks in the file as well as setting and
* obtaining the size of the file.
* The blocks are indexed sequentially from the start of the file
* starting with -1.
*
* TODO:
* - Support for a transacted mode
* File-based ILockBytes implementation
*
* Copyright 1999 Thuy Nguyen
* Copyright 2010 Vincent Povirk for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
...
...
@@ -55,19 +44,16 @@
WINE_DEFAULT_DEBUG_CHANNEL
(
storage
);
/***********************************************************
* Data structures used internally by the BigBlockFile
* class.
*/
struct
BigBlockFile
typedef
struct
FileLockBytesImpl
{
BOOL
fileBased
;
const
ILockBytesVtbl
*
lpVtbl
;
LONG
ref
;
ULARGE_INTEGER
filesize
;
HANDLE
hfile
;
DWORD
flProtect
;
ILockBytes
*
pLkbyt
;
};
}
FileLockBytesImpl
;
static
const
ILockBytesVtbl
FileLockBytesImpl_Vtbl
;
/***********************************************************
* Prototypes for private methods
...
...
@@ -77,64 +63,93 @@ struct BigBlockFile
* pass expressions with side effects. */
#define ROUND_UP(a, b) ((((a) + (b) - 1)/(b))*(b))
/****************************************************************************
* GetProtectMode
*
* This function will return a protection mode flag for a file-mapping object
* from the open flags of a file.
*/
static
DWORD
GetProtectMode
(
DWORD
openFlags
)
{
switch
(
STGM_ACCESS_MODE
(
openFlags
))
{
case
STGM_WRITE
:
case
STGM_READWRITE
:
return
PAGE_READWRITE
;
}
return
PAGE_READONLY
;
}
/******************************************************************************
*
BIGBLOCKFILE_FileIni
t
*
FileLockBytesImpl_Construc
t
*
* Initialize a big block object supported by a file.
*/
static
BOOL
BIGBLOCKFILE_FileInit
(
LPBIGBLOCKFILE
This
,
HANDLE
hFile
)
HRESULT
FileLockBytesImpl_Construct
(
HANDLE
hFile
,
DWORD
openFlags
,
ILockBytes
**
pLockBytes
)
{
This
->
pLkbyt
=
NULL
;
This
->
hfile
=
hFile
;
FileLockBytesImpl
*
This
;
if
(
This
->
hf
ile
==
INVALID_HANDLE_VALUE
)
return
FALSE
;
if
(
hF
ile
==
INVALID_HANDLE_VALUE
)
return
E_FAIL
;
This
=
HeapAlloc
(
GetProcessHeap
(),
0
,
sizeof
(
FileLockBytesImpl
));
if
(
!
This
)
return
E_OUTOFMEMORY
;
This
->
lpVtbl
=
&
FileLockBytesImpl_Vtbl
;
This
->
ref
=
1
;
This
->
hfile
=
hFile
;
This
->
filesize
.
u
.
LowPart
=
GetFileSize
(
This
->
hfile
,
&
This
->
filesize
.
u
.
HighPart
);
This
->
flProtect
=
GetProtectMode
(
openFlags
);
TRACE
(
"file len %u
\n
"
,
This
->
filesize
.
u
.
LowPart
);
return
TRUE
;
*
pLockBytes
=
(
ILockBytes
*
)
This
;
return
S_OK
;
}
/******************************************************************************
* BIGBLOCKFILE_LockBytesInit
*
* Initialize a big block object supported by an ILockBytes.
*/
static
BOOL
BIGBLOCKFILE_LockBytesInit
(
LPBIGBLOCKFILE
This
,
ILockBytes
*
plkbyt
)
/* ILockByte Interfaces */
static
HRESULT
WINAPI
FileLockBytesImpl_QueryInterface
(
ILockBytes
*
iface
,
REFIID
riid
,
void
**
ppvObject
)
{
This
->
hfile
=
0
;
This
->
pLkbyt
=
plkbyt
;
ILockBytes_AddRef
(
This
->
pLkbyt
);
if
(
IsEqualIID
(
riid
,
&
IID_ILockBytes
)
||
IsEqualIID
(
riid
,
&
IID_ILockBytes
))
*
ppvObject
=
iface
;
else
{
*
ppvObject
=
NULL
;
return
E_NOINTERFACE
;
}
/* We'll get the size directly with ILockBytes_Stat */
This
->
filesize
.
QuadPart
=
0
;
IUnknown_AddRef
((
IUnknown
*
)
*
ppvObject
);
TRACE
(
"ILockBytes %p
\n
"
,
This
->
pLkbyt
);
return
TRUE
;
return
S_OK
;
}
/****************************************************************************
* BIGBLOCKFILE_GetProtectMode
*
* This function will return a protection mode flag for a file-mapping object
* from the open flags of a file.
*/
static
DWORD
BIGBLOCKFILE_GetProtectMode
(
DWORD
openFlags
)
static
ULONG
WINAPI
FileLockBytesImpl_AddRef
(
ILockBytes
*
iface
)
{
switch
(
STGM_ACCESS_MODE
(
openFlags
))
{
case
STGM_WRITE
:
case
STGM_READWRITE
:
return
PAGE_READWRITE
;
}
return
PAGE_READONLY
;
FileLockBytesImpl
*
This
=
(
FileLockBytesImpl
*
)
iface
;
return
InterlockedIncrement
(
&
This
->
ref
);
}
static
ULONG
WINAPI
FileLockBytesImpl_Release
(
ILockBytes
*
iface
)
{
FileLockBytesImpl
*
This
=
(
FileLockBytesImpl
*
)
iface
;
ULONG
ref
;
/* ILockByte Interfaces */
ref
=
InterlockedDecrement
(
&
This
->
ref
);
if
(
ref
==
0
)
{
CloseHandle
(
This
->
hfile
);
HeapFree
(
GetProcessHeap
(),
0
,
This
);
}
return
ref
;
}
/******************************************************************************
* This method is part of the ILockBytes interface.
...
...
@@ -144,13 +159,14 @@ static DWORD BIGBLOCKFILE_GetProtectMode(DWORD openFlags)
*
* See the documentation of ILockBytes for more info.
*/
static
HRESULT
ImplBIGBLOCKFILE
_ReadAt
(
BigBlockFile
*
const
This
,
static
HRESULT
WINAPI
FileLockBytesImpl
_ReadAt
(
ILockBytes
*
iface
,
ULARGE_INTEGER
ulOffset
,
/* [in] */
void
*
pv
,
/* [length_is][size_is][out] */
ULONG
cb
,
/* [in] */
ULONG
*
pcbRead
)
/* [out] */
{
FileLockBytesImpl
*
This
=
(
FileLockBytesImpl
*
)
iface
;
ULONG
bytes_left
=
cb
;
LPBYTE
readPtr
=
pv
;
BOOL
ret
;
...
...
@@ -198,13 +214,14 @@ static HRESULT ImplBIGBLOCKFILE_ReadAt(
*
* See the documentation of ILockBytes for more info.
*/
static
HRESULT
ImplBIGBLOCKFILE
_WriteAt
(
BigBlockFile
*
const
This
,
static
HRESULT
WINAPI
FileLockBytesImpl
_WriteAt
(
ILockBytes
*
iface
,
ULARGE_INTEGER
ulOffset
,
/* [in] */
const
void
*
pv
,
/* [size_is][in] */
ULONG
cb
,
/* [in] */
ULONG
*
pcbWritten
)
/* [out] */
{
FileLockBytesImpl
*
This
=
(
FileLockBytesImpl
*
)
iface
;
ULONG
size_needed
=
ulOffset
.
u
.
LowPart
+
cb
;
ULONG
bytes_left
=
cb
;
const
BYTE
*
writePtr
=
pv
;
...
...
@@ -228,7 +245,7 @@ static HRESULT ImplBIGBLOCKFILE_WriteAt(
ULARGE_INTEGER
newSize
;
newSize
.
u
.
HighPart
=
0
;
newSize
.
u
.
LowPart
=
size_needed
;
BIGBLOCKFILE_SetSize
(
This
,
newSize
);
ILockBytes_SetSize
(
iface
,
newSize
);
}
offset
.
QuadPart
=
ulOffset
.
QuadPart
;
...
...
@@ -256,103 +273,23 @@ static HRESULT ImplBIGBLOCKFILE_WriteAt(
return
S_OK
;
}
/******************************************************************************
* BIGBLOCKFILE_Construct
*
* Construct a big block file. Create the file mapping object.
* Create the read only mapped pages list, the writable mapped page list
* and the blocks in use list.
*/
BigBlockFile
*
BIGBLOCKFILE_Construct
(
HANDLE
hFile
,
ILockBytes
*
pLkByt
,
DWORD
openFlags
,
BOOL
fileBased
)
{
BigBlockFile
*
This
;
This
=
HeapAlloc
(
GetProcessHeap
(),
0
,
sizeof
(
BigBlockFile
));
if
(
This
==
NULL
)
return
NULL
;
This
->
fileBased
=
fileBased
;
This
->
flProtect
=
BIGBLOCKFILE_GetProtectMode
(
openFlags
);
if
(
This
->
fileBased
)
{
if
(
!
BIGBLOCKFILE_FileInit
(
This
,
hFile
))
{
HeapFree
(
GetProcessHeap
(),
0
,
This
);
return
NULL
;
}
}
else
{
if
(
!
BIGBLOCKFILE_LockBytesInit
(
This
,
pLkByt
))
{
HeapFree
(
GetProcessHeap
(),
0
,
This
);
return
NULL
;
}
}
return
This
;
}
/******************************************************************************
* BIGBLOCKFILE_Destructor
*
* Destructor. Clean up, free memory.
*/
void
BIGBLOCKFILE_Destructor
(
BigBlockFile
*
This
)
{
if
(
This
->
fileBased
)
{
CloseHandle
(
This
->
hfile
);
}
else
{
ILockBytes_Release
(
This
->
pLkbyt
);
}
HeapFree
(
GetProcessHeap
(),
0
,
This
);
}
/******************************************************************************
* BIGBLOCKFILE_ReadAt
*/
HRESULT
BIGBLOCKFILE_ReadAt
(
BigBlockFile
*
This
,
ULARGE_INTEGER
offset
,
void
*
buffer
,
ULONG
size
,
ULONG
*
bytesRead
)
static
HRESULT
WINAPI
FileLockBytesImpl_Flush
(
ILockBytes
*
iface
)
{
if
(
This
->
fileBased
)
return
ImplBIGBLOCKFILE_ReadAt
(
This
,
offset
,
buffer
,
size
,
bytesRead
);
else
return
ILockBytes_ReadAt
(
This
->
pLkbyt
,
offset
,
buffer
,
size
,
bytesRead
);
}
/******************************************************************************
* BIGBLOCKFILE_WriteAt
*/
HRESULT
BIGBLOCKFILE_WriteAt
(
BigBlockFile
*
This
,
ULARGE_INTEGER
offset
,
const
void
*
buffer
,
ULONG
size
,
ULONG
*
bytesRead
)
{
if
(
This
->
fileBased
)
return
ImplBIGBLOCKFILE_WriteAt
(
This
,
offset
,
buffer
,
size
,
bytesRead
);
else
return
ILockBytes_WriteAt
(
This
->
pLkbyt
,
offset
,
buffer
,
size
,
bytesRead
);
return
S_OK
;
}
/******************************************************************************
*
BIGBLOCKFILE
_SetSize
*
ILockBytes
_SetSize
*
* Sets the size of the file.
*
*/
HRESULT
BIGBLOCKFILE_SetSize
(
BigBlockFile
*
This
,
ULARGE_INTEGER
newSize
)
static
HRESULT
WINAPI
FileLockBytesImpl_SetSize
(
ILockBytes
*
iface
,
ULARGE_INTEGER
newSize
)
{
FileLockBytesImpl
*
This
=
(
FileLockBytesImpl
*
)
iface
;
HRESULT
hr
=
S_OK
;
LARGE_INTEGER
newpos
;
if
(
!
This
->
fileBased
)
return
ILockBytes_SetSize
(
This
->
pLkbyt
,
newSize
);
if
(
This
->
filesize
.
u
.
LowPart
==
newSize
.
u
.
LowPart
)
return
hr
;
...
...
@@ -368,40 +305,47 @@ HRESULT BIGBLOCKFILE_SetSize(BigBlockFile *This, ULARGE_INTEGER newSize)
return
hr
;
}
/******************************************************************************
* BIGBLOCKFILE_GetSize
*
* Gets the size of the file.
*
*/
static
HRESULT
BIGBLOCKFILE_GetSize
(
BigBlockFile
*
This
,
ULARGE_INTEGER
*
size
)
static
HRESULT
WINAPI
FileLockBytesImpl_LockRegion
(
ILockBytes
*
iface
,
ULARGE_INTEGER
libOffset
,
ULARGE_INTEGER
cb
,
DWORD
dwLockType
)
{
HRESULT
hr
=
S_OK
;
if
(
This
->
fileBased
)
*
size
=
This
->
filesize
;
else
{
STATSTG
stat
;
hr
=
ILockBytes_Stat
(
This
->
pLkbyt
,
&
stat
,
STATFLAG_NONAME
);
if
(
SUCCEEDED
(
hr
))
*
size
=
stat
.
cbSize
;
}
return
hr
;
FIXME
(
"stub
\n
"
);
return
E_NOTIMPL
;
}
/******************************************************************************
* BIGBLOCKFILE_Expand
*
* Grows the file to the specified size if necessary.
*/
HRESULT
BIGBLOCKFILE_Expand
(
BigBlockFile
*
This
,
ULARGE_INTEGER
newSize
)
static
HRESULT
WINAPI
FileLockBytesImpl_UnlockRegion
(
ILockBytes
*
iface
,
ULARGE_INTEGER
libOffset
,
ULARGE_INTEGER
cb
,
DWORD
dwLockType
)
{
ULARGE_INTEGER
size
;
HRESULT
hr
;
FIXME
(
"stub
\n
"
);
return
E_NOTIMPL
;
}
hr
=
BIGBLOCKFILE_GetSize
(
This
,
&
size
);
if
(
FAILED
(
hr
))
return
hr
;
static
HRESULT
WINAPI
FileLockBytesImpl_Stat
(
ILockBytes
*
iface
,
STATSTG
*
pstatstg
,
DWORD
grfStatFlag
)
{
FileLockBytesImpl
*
This
=
(
FileLockBytesImpl
*
)
iface
;
if
(
newSize
.
QuadPart
>
size
.
QuadPart
)
hr
=
BIGBLOCKFILE_SetSize
(
This
,
newSize
);
return
hr
;
if
(
!
(
STATFLAG_NONAME
&
grfStatFlag
))
{
FIXME
(
"reading filename not supported
\n
"
);
}
pstatstg
->
pwcsName
=
NULL
;
pstatstg
->
type
=
STGTY_LOCKBYTES
;
pstatstg
->
cbSize
=
This
->
filesize
;
/* FIXME: If the implementation is exported, we'll need to set other fields. */
return
S_OK
;
}
static
const
ILockBytesVtbl
FileLockBytesImpl_Vtbl
=
{
FileLockBytesImpl_QueryInterface
,
FileLockBytesImpl_AddRef
,
FileLockBytesImpl_Release
,
FileLockBytesImpl_ReadAt
,
FileLockBytesImpl_WriteAt
,
FileLockBytesImpl_Flush
,
FileLockBytesImpl_SetSize
,
FileLockBytesImpl_LockRegion
,
FileLockBytesImpl_UnlockRegion
,
FileLockBytesImpl_Stat
};
dlls/ole32/storage32.c
View file @
14f8f9d5
...
...
@@ -327,7 +327,7 @@ static HRESULT StorageImpl_ReadAt(StorageImpl* This,
ULONG
size
,
ULONG
*
bytesRead
)
{
return
BIGBLOCKFILE_ReadAt
(
This
->
bigBlockFile
,
offset
,
buffer
,
size
,
bytesRead
);
return
ILockBytes_ReadAt
(
This
->
lockBytes
,
offset
,
buffer
,
size
,
bytesRead
);
}
static
HRESULT
StorageImpl_WriteAt
(
StorageImpl
*
This
,
...
...
@@ -336,7 +336,7 @@ static HRESULT StorageImpl_WriteAt(StorageImpl* This,
const
ULONG
size
,
ULONG
*
bytesWritten
)
{
return
BIGBLOCKFILE_WriteAt
(
This
->
bigBlockFile
,
offset
,
buffer
,
size
,
bytesWritten
);
return
ILockBytes_WriteAt
(
This
->
lockBytes
,
offset
,
buffer
,
size
,
bytesWritten
);
}
/************************************************************************
...
...
@@ -2683,17 +2683,17 @@ static HRESULT StorageImpl_Construct(
*/
This
->
bigBlockSize
=
sector_size
;
This
->
smallBlockSize
=
DEF_SMALL_BLOCK_SIZE
;
This
->
bigBlockFile
=
BIGBLOCKFILE_Construct
(
hFile
,
pLkbyt
,
openFlags
,
fileBased
);
if
(
This
->
bigBlockFile
==
0
)
if
(
hFile
)
hr
=
FileLockBytesImpl_Construct
(
hFile
,
openFlags
,
&
This
->
lockBytes
);
else
{
hr
=
E_FAIL
;
goto
end
;
This
->
lockBytes
=
pLkbyt
;
ILockBytes_AddRef
(
pLkbyt
)
;
}
if
(
FAILED
(
hr
))
goto
end
;
if
(
create
)
{
ULARGE_INTEGER
size
;
...
...
@@ -2729,7 +2729,7 @@ static HRESULT StorageImpl_Construct(
*/
size
.
u
.
HighPart
=
0
;
size
.
u
.
LowPart
=
This
->
bigBlockSize
*
3
;
BIGBLOCKFILE_SetSize
(
This
->
bigBlockFile
,
size
);
ILockBytes_SetSize
(
This
->
lockBytes
,
size
);
/*
* Initialize the big block depot
...
...
@@ -2884,8 +2884,8 @@ static void StorageImpl_Destroy(StorageBaseImpl* iface)
for
(
i
=
0
;
i
<
BLOCKCHAIN_CACHE_SIZE
;
i
++
)
BlockChainStream_Destroy
(
This
->
blockChainCache
[
i
]);
if
(
This
->
bigBlockFile
)
BIGBLOCKFILE_Destructor
(
This
->
bigBlockFile
);
if
(
This
->
lockBytes
)
ILockBytes_Release
(
This
->
lockBytes
);
HeapFree
(
GetProcessHeap
(),
0
,
This
);
}
...
...
@@ -2908,6 +2908,7 @@ static ULONG StorageImpl_GetNextFreeBigBlock(
int
depotIndex
=
0
;
ULONG
freeBlock
=
BLOCK_UNUSED
;
ULARGE_INTEGER
neededSize
;
STATSTG
statstg
;
depotIndex
=
This
->
prevFreeBlock
/
blocksPerDepot
;
depotBlockOffset
=
(
This
->
prevFreeBlock
%
blocksPerDepot
)
*
sizeof
(
ULONG
);
...
...
@@ -3022,7 +3023,11 @@ static ULONG StorageImpl_GetNextFreeBigBlock(
* make sure that the block physically exists before using it
*/
neededSize
.
QuadPart
=
StorageImpl_GetBigBlockOffset
(
This
,
freeBlock
)
+
This
->
bigBlockSize
;
BIGBLOCKFILE_Expand
(
This
->
bigBlockFile
,
neededSize
);
ILockBytes_Stat
(
This
->
lockBytes
,
&
statstg
,
STATFLAG_NONAME
);
if
(
neededSize
.
QuadPart
>
statstg
.
cbSize
.
QuadPart
)
ILockBytes_SetSize
(
This
->
lockBytes
,
neededSize
);
This
->
prevFreeBlock
=
freeBlock
;
...
...
dlls/ole32/storage32.h
View file @
14f8f9d5
...
...
@@ -11,6 +11,7 @@
*
* Copyright 1998,1999 Francis Beaudet
* Copyright 1998,1999 Thuy Nguyen
* Copyright 2010 Vincent Povirk for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
...
...
@@ -153,31 +154,7 @@ struct DirEntry
ULARGE_INTEGER
size
;
};
/*************************************************************************
* Big Block File support
*
* The big block file is an abstraction of a flat file separated in
* same sized blocks. The implementation for the methods described in
* this section appear in stg_bigblockfile.c
*/
typedef
struct
BigBlockFile
BigBlockFile
,
*
LPBIGBLOCKFILE
;
/*
* Declaration of the functions used to manipulate the BigBlockFile
* data structure.
*/
BigBlockFile
*
BIGBLOCKFILE_Construct
(
HANDLE
hFile
,
ILockBytes
*
pLkByt
,
DWORD
openFlags
,
BOOL
fileBased
);
void
BIGBLOCKFILE_Destructor
(
LPBIGBLOCKFILE
This
);
HRESULT
BIGBLOCKFILE_Expand
(
LPBIGBLOCKFILE
This
,
ULARGE_INTEGER
newSize
);
HRESULT
BIGBLOCKFILE_SetSize
(
LPBIGBLOCKFILE
This
,
ULARGE_INTEGER
newSize
);
HRESULT
BIGBLOCKFILE_ReadAt
(
LPBIGBLOCKFILE
This
,
ULARGE_INTEGER
offset
,
void
*
buffer
,
ULONG
size
,
ULONG
*
bytesRead
);
HRESULT
BIGBLOCKFILE_WriteAt
(
LPBIGBLOCKFILE
This
,
ULARGE_INTEGER
offset
,
const
void
*
buffer
,
ULONG
size
,
ULONG
*
bytesRead
);
HRESULT
FileLockBytesImpl_Construct
(
HANDLE
hFile
,
DWORD
openFlags
,
ILockBytes
**
pLockBytes
);
/*************************************************************************
* Ole Convert support
...
...
@@ -395,10 +372,7 @@ struct StorageImpl
BlockChainStream
*
blockChainCache
[
BLOCKCHAIN_CACHE_SIZE
];
UINT
blockChainToEvict
;
/*
* Pointer to the big block file abstraction
*/
BigBlockFile
*
bigBlockFile
;
ILockBytes
*
lockBytes
;
};
HRESULT
StorageImpl_ReadRawDirEntry
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment