1288 lines
35 KiB
C++
1288 lines
35 KiB
C++
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1994.
|
|
//
|
|
// File: simpstm.cxx
|
|
//
|
|
// Contents: CStdStream implementation
|
|
//
|
|
// Classes:
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "simphead.cxx"
|
|
#pragma hdrstop
|
|
|
|
#include <ole.hxx>
|
|
#include <logfile.hxx>
|
|
#include <expparam.hxx>
|
|
|
|
|
|
#if DBG == 1 && defined(SECURE_SIMPLE_MODE)
|
|
void CSimpStream::CheckSeekPointer(void)
|
|
{
|
|
LONG lHighChk;
|
|
ULONG ulLowChk;
|
|
lHighChk = 0;
|
|
ulLowChk = SetFilePointer(_hFile, 0, &lHighChk, FILE_CURRENT);
|
|
if (ulLowChk == 0xFFFFFFFF)
|
|
{
|
|
//An error occurred while checking.
|
|
simpDebugOut((DEB_ERROR, "SetFilePointer call failed with %lu\n",
|
|
GetLastError()));
|
|
}
|
|
else if ((ulLowChk != _ulSeekPos) || (lHighChk != 0))
|
|
{
|
|
simpDebugOut((DEB_ERROR, "Seek pointer mismatch."
|
|
" Cached = %lu, Real = %lu, High = %lu\n",
|
|
_ulSeekPos, ulLowChk, lHighChk));
|
|
simpAssert((ulLowChk == _ulSeekPos) && (lHighChk == 0));
|
|
}
|
|
}
|
|
#define CheckSeek() CheckSeekPointer()
|
|
#else
|
|
#define CheckSeek()
|
|
#endif // DBG == 1 && defined(SECURE_SIMPLE_MODE)
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::Init, public
|
|
//
|
|
// Synopsis: Initialize stream object
|
|
//
|
|
// Arguments: [pstgParent] -- Pointer to parent
|
|
// [hFile] -- File handle for writes
|
|
// [ulSeekStart] -- Beginning seek pointer
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies:
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
SCODE CSimpStream::Init(
|
|
CSimpStorage *pstgParent,
|
|
HANDLE hFile,
|
|
ULONG ulSeekStart)
|
|
{
|
|
simpDebugOut((DEB_ITRACE, "In CSimpStream::Init:%p()\n", this));
|
|
_ulSeekStart = ulSeekStart;
|
|
_hFile = hFile;
|
|
_pstgParent = pstgParent;
|
|
_cReferences = 1;
|
|
|
|
#ifdef SECURE_SIMPLE_MODE
|
|
_ulHighWater = ulSeekStart;
|
|
#endif
|
|
_ulSeekPos = ulSeekStart;
|
|
|
|
if (SetFilePointer(_hFile, ulSeekStart, NULL, FILE_BEGIN) == 0xFFFFFFFF)
|
|
{
|
|
return STG_SCODE(GetLastError());
|
|
}
|
|
|
|
CheckSeek();
|
|
|
|
if (!SetEndOfFile(_hFile))
|
|
{
|
|
return STG_SCODE(GetLastError());
|
|
}
|
|
|
|
simpDebugOut((DEB_ITRACE, "Out CSimpStream::Init\n"));
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::Read, public
|
|
//
|
|
// Synopsis: Read from a stream
|
|
//
|
|
// Arguments: [pb] - Buffer
|
|
// [cb] - Count of bytes to read
|
|
// [pcbRead] - Return number of bytes read
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [pcbRead]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::Read(VOID HUGEP *pb, ULONG cb, ULONG *pcbRead)
|
|
{
|
|
ULONG cbRead;
|
|
ULONG *pcb;
|
|
SCODE sc;
|
|
|
|
olLog(("%p::In CSimpStream::Read(%p, %lu, %p)\n",
|
|
this, pb, cb, pcbRead));
|
|
|
|
SIMP_VALIDATE(Read(pb, cb, pcbRead));
|
|
|
|
pcb = (pcbRead != NULL) ? pcbRead : &cbRead;
|
|
|
|
#ifdef SECURE_SIMPLE_MODE
|
|
if (_ulSeekPos + cb > _ulHighWater)
|
|
{
|
|
ULONG cbTotalSize;
|
|
cbTotalSize = GetFileSize(_hFile, NULL);
|
|
|
|
if (_ulSeekPos + cb > cbTotalSize)
|
|
{
|
|
//Truncate.
|
|
cb = (_ulSeekPos > cbTotalSize) ? 0 : cbTotalSize - _ulSeekPos;
|
|
}
|
|
|
|
//Part of this read would come from uninitialized space, so
|
|
// we need to return zeroes instead.
|
|
if (_ulSeekPos > _ulHighWater)
|
|
{
|
|
if (SetFilePointer(_hFile,
|
|
_ulSeekPos + cb,
|
|
NULL,
|
|
FILE_BEGIN) == 0xFFFFFFFF)
|
|
{
|
|
//We can't get the seek pointer where it will need to
|
|
// end up, so return zero bytes and be done with it.
|
|
*pcb = 0;
|
|
return S_OK;
|
|
}
|
|
|
|
//Actually, the whole thing is coming from uninitialized
|
|
// space. Why someone would do this is a mystery, but
|
|
// let's return zeroes anyway.
|
|
memset(pb, SECURECHAR, cb);
|
|
*pcb = cb;
|
|
|
|
_ulSeekPos += cb;
|
|
}
|
|
else
|
|
{
|
|
ULONG cbBytesToRead = _ulHighWater - _ulSeekPos;
|
|
|
|
if (FAILED(sc = Read(pb, cbBytesToRead, pcb)))
|
|
{
|
|
CheckSeek();
|
|
return sc;
|
|
}
|
|
|
|
cb -= *pcb;
|
|
|
|
if ((*pcb != cbBytesToRead) ||
|
|
(SetFilePointer(_hFile,
|
|
_ulSeekPos + cb,
|
|
NULL,
|
|
FILE_BEGIN) == 0xFFFFFFFF))
|
|
{
|
|
//Either the Read call returned a weird number of bytes,
|
|
// Or
|
|
//We can't actually get the seek pointer where we need
|
|
// it, so return fewer bytes than we normally would,
|
|
// with a success code.
|
|
CheckSeek();
|
|
return S_OK;
|
|
}
|
|
|
|
//Zero the rest of the buffer.
|
|
memset((BYTE *)pb + *pcb, SECURECHAR, cb);
|
|
*pcb += cb;
|
|
_ulSeekPos += cb;
|
|
}
|
|
CheckSeek();
|
|
return S_OK;
|
|
}
|
|
#endif
|
|
|
|
//Maps directly to ReadFile call
|
|
BOOL f = ReadFile(_hFile,
|
|
pb,
|
|
cb,
|
|
pcb,
|
|
NULL);
|
|
|
|
_ulSeekPos += *pcb;
|
|
|
|
CheckSeek();
|
|
|
|
if (!f)
|
|
return STG_SCODE(GetLastError());
|
|
|
|
olLog(("%p::Out CSimpStream::Read(). *pcbRead == %lu, ret = %lx\n",
|
|
this, *pcb, S_OK));
|
|
return S_OK;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::Write, public
|
|
//
|
|
// Synopsis: Write to a stream
|
|
//
|
|
// Arguments: [pb] - Buffer
|
|
// [cb] - Count of bytes to write
|
|
// [pcbWritten] - Return of bytes written
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [pcbWritten]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::Write(
|
|
VOID const HUGEP *pb,
|
|
ULONG cb,
|
|
ULONG *pcbWritten)
|
|
{
|
|
ULONG cbWritten;
|
|
ULONG *pcb;
|
|
BOOL f = TRUE;
|
|
SCODE sc = S_OK;
|
|
|
|
olLog(("%p::In CSimpStream::Write(%p, %lu, %p)\n",
|
|
this, pb, cb, pcbWritten));
|
|
|
|
SIMP_VALIDATE(Write(pb, cb, pcbWritten));
|
|
|
|
pcb = (pcbWritten != NULL) ? pcbWritten : &cbWritten;
|
|
|
|
if (_ulSeekPos + cb >= OLOCKREGIONBEGIN)
|
|
return STG_E_DOCFILETOOLARGE;
|
|
|
|
#ifdef SECURE_SIMPLE_MODE
|
|
if (_ulSeekPos > _ulHighWater)
|
|
{
|
|
//We're leaving a gap in the file, so we need to fill in that
|
|
// gap. Sad but true.
|
|
ULONG cbBytesToWrite = _ulSeekPos - _ulHighWater;
|
|
|
|
ULONG cbWrittenSecure;
|
|
|
|
if (SetFilePointer(_hFile,
|
|
_ulHighWater,
|
|
NULL,
|
|
FILE_BEGIN) != 0xFFFFFFFF)
|
|
{
|
|
while (cbBytesToWrite > 0)
|
|
{
|
|
if (!(f = WriteFile(_hFile,
|
|
s_bufSecure,
|
|
min(MINISTREAMSIZE, cbBytesToWrite),
|
|
&cbWrittenSecure,
|
|
NULL)))
|
|
{
|
|
break;
|
|
}
|
|
cbBytesToWrite -= cbWrittenSecure;
|
|
}
|
|
if ((!f) && (SetFilePointer(_hFile,
|
|
_ulSeekPos,
|
|
NULL,
|
|
FILE_BEGIN) == 0xFFFFFFFF))
|
|
{
|
|
return STG_SCODE(GetLastError());
|
|
}
|
|
}
|
|
CheckSeek();
|
|
}
|
|
#endif
|
|
//Maps directly to WriteFile call
|
|
f = WriteFile(_hFile,
|
|
pb,
|
|
cb,
|
|
pcb,
|
|
NULL);
|
|
|
|
_ulSeekPos += *pcb;
|
|
#ifdef SECURE_SIMPLE_MODE
|
|
if (_ulSeekPos > _ulHighWater)
|
|
_ulHighWater = _ulSeekPos;
|
|
#endif
|
|
|
|
if (!f)
|
|
{
|
|
sc = STG_SCODE(GetLastError());
|
|
}
|
|
|
|
CheckSeek();
|
|
olLog(("%p::Out CSimpStream::Write(). "
|
|
"*pcbWritten == %lu, ret = %lx\n",
|
|
this, *pcb, sc));
|
|
|
|
return sc;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::Seek, public
|
|
//
|
|
// Synopsis: Seek to a point in a stream
|
|
//
|
|
// Arguments: [dlibMove] - Offset to move by
|
|
// [dwOrigin] - SEEK_SET, SEEK_CUR, SEEK_END
|
|
// [plibNewPosition] - Return of new offset
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [plibNewPosition]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::Seek(LARGE_INTEGER dlibMove,
|
|
DWORD dwOrigin,
|
|
ULARGE_INTEGER *plibNewPosition)
|
|
{
|
|
SCODE sc = S_OK;
|
|
LONG lMove;
|
|
ULONG ulPos;
|
|
|
|
simpAssert((dwOrigin == STREAM_SEEK_SET) || (dwOrigin == STREAM_SEEK_CUR) ||
|
|
(dwOrigin == STREAM_SEEK_END));
|
|
|
|
olLog(("%p::In CSimpStream::Seek(%ld, %lu, %p)\n",
|
|
this, LIGetLow(dlibMove), dwOrigin, plibNewPosition));
|
|
|
|
SIMP_VALIDATE(Seek(dlibMove, dwOrigin, plibNewPosition));
|
|
|
|
// Truncate dlibMove to 32 bits
|
|
if (dwOrigin == STREAM_SEEK_SET)
|
|
{
|
|
// Make sure we don't seek too far
|
|
if (LIGetHigh(dlibMove) != 0)
|
|
LISet32(dlibMove, 0xffffffff);
|
|
}
|
|
else
|
|
{
|
|
// High dword must be zero for positive values or -1 for
|
|
// negative values
|
|
// Additionally, for negative values, the low dword can't
|
|
// exceed -0x80000000 because the 32nd bit is the sign
|
|
// bit
|
|
if (LIGetHigh(dlibMove) > 0 ||
|
|
(LIGetHigh(dlibMove) == 0 &&
|
|
LIGetLow(dlibMove) >= 0x80000000))
|
|
LISet32(dlibMove, 0x7fffffff);
|
|
else if (LIGetHigh(dlibMove) < -1 ||
|
|
(LIGetHigh(dlibMove) == -1 &&
|
|
LIGetLow(dlibMove) <= 0x7fffffff))
|
|
LISet32(dlibMove, 0x80000000);
|
|
}
|
|
|
|
lMove = (LONG)LIGetLow(dlibMove);
|
|
|
|
switch(dwOrigin)
|
|
{
|
|
case STREAM_SEEK_SET:
|
|
ulPos = _ulSeekStart + lMove;
|
|
break;
|
|
|
|
case STREAM_SEEK_END:
|
|
ULONG cbSize;
|
|
cbSize = GetFileSize(_hFile, NULL);
|
|
|
|
if (lMove < 0)
|
|
{
|
|
if ((ULONG)(-lMove) > (cbSize - _ulSeekStart))
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
ulPos = cbSize+lMove;
|
|
break;
|
|
|
|
case STREAM_SEEK_CUR:
|
|
ulPos = SetFilePointer(_hFile, 0, NULL, FILE_CURRENT);
|
|
|
|
if (lMove < 0)
|
|
{
|
|
if ((ULONG)(-lMove) > (ulPos - _ulSeekStart))
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
ulPos += lMove;
|
|
break;
|
|
}
|
|
|
|
ulPos = SetFilePointer(_hFile,
|
|
ulPos,
|
|
NULL,
|
|
FILE_BEGIN);
|
|
|
|
if (plibNewPosition != NULL)
|
|
{
|
|
ULISet32(*plibNewPosition, ulPos - _ulSeekStart);
|
|
}
|
|
|
|
_ulSeekPos = ulPos;
|
|
|
|
CheckSeek();
|
|
|
|
olLog(("%p::Out CSimpStream::Seek(). ulPos == %lu, ret == %lx\n",
|
|
this, ulPos, sc));
|
|
return sc;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::SetSize, public
|
|
//
|
|
// Synopsis: Sets the size of a stream
|
|
//
|
|
// Arguments: [ulNewSize] - New size
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::SetSize(ULARGE_INTEGER ulNewSize)
|
|
{
|
|
ULONG ulCurrentPos;
|
|
SCODE sc;
|
|
|
|
olLog(("%p::In CSimpStream::SetSize(%lu)\n",
|
|
this, ULIGetLow(ulNewSize)));
|
|
|
|
SIMP_VALIDATE(SetSize(ulNewSize));
|
|
|
|
ulCurrentPos = SetFilePointer(_hFile, 0, NULL, FILE_CURRENT);
|
|
|
|
if (ulCurrentPos == 0xFFFFFFFF)
|
|
{
|
|
return STG_SCODE(GetLastError());
|
|
}
|
|
|
|
if (ULIGetHigh(ulNewSize) != 0 ||
|
|
ulCurrentPos + ULIGetLow(ulNewSize) >= OLOCKREGIONBEGIN)
|
|
return STG_E_DOCFILETOOLARGE;
|
|
|
|
if (SetFilePointer(_hFile,
|
|
ULIGetLow(ulNewSize) + _ulSeekStart,
|
|
NULL,
|
|
FILE_BEGIN) == 0xFFFFFFFF)
|
|
{
|
|
CheckSeek();
|
|
return STG_SCODE(GetLastError());
|
|
}
|
|
|
|
if (!SetEndOfFile(_hFile))
|
|
{
|
|
SetFilePointer(_hFile, ulCurrentPos, NULL, FILE_BEGIN);
|
|
|
|
CheckSeek();
|
|
return STG_SCODE(GetLastError());
|
|
}
|
|
|
|
#ifdef SECURE_SIMPLE_MODE
|
|
// if we are shrinking the stream below the highwater mark, reset it
|
|
if (ULIGetLow(ulNewSize) + _ulSeekStart < _ulHighWater)
|
|
{
|
|
_ulHighWater = ULIGetLow(ulNewSize) + _ulSeekStart;
|
|
}
|
|
#endif
|
|
|
|
if (SetFilePointer(_hFile, ulCurrentPos, NULL, FILE_BEGIN) == 0xFFFFFFFF)
|
|
{
|
|
_ulSeekPos = ULIGetLow(ulNewSize) + _ulSeekStart;
|
|
CheckSeek();
|
|
return STG_SCODE(GetLastError());
|
|
}
|
|
|
|
CheckSeek();
|
|
olLog(("%p::Out CSimpStream::SetSize(). ret == %lx\n", this, S_OK));
|
|
return S_OK;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::CopyTo, public
|
|
//
|
|
// Synopsis: Copies information from one stream to another
|
|
//
|
|
// Arguments: [pstm] - Destination
|
|
// [cb] - Number of bytes to copy
|
|
// [pcbRead] - Return number of bytes read
|
|
// [pcbWritten] - Return number of bytes written
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [pcbRead]
|
|
// [pcbWritten]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::CopyTo(IStream *pstm,
|
|
ULARGE_INTEGER cb,
|
|
ULARGE_INTEGER *pcbRead,
|
|
ULARGE_INTEGER *pcbWritten)
|
|
{
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::CopyTo("
|
|
"%p, %lu, %p, %p)\n", pstm, ULIGetLow(cb),
|
|
pcbRead, pcbWritten));
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::CopyTo\n"));
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::Release, public
|
|
//
|
|
// Synopsis: Releases a stream
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP_(ULONG) CSimpStream::Release(void)
|
|
{
|
|
LONG lRet;
|
|
|
|
olLog(("%p::In CSimpStream::Release()\n", this));
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::Release()\n"));
|
|
|
|
simpAssert(_cReferences > 0);
|
|
lRet = AtomicDec(&_cReferences);
|
|
if (lRet == 0)
|
|
{
|
|
#ifdef SECURE_SIMPLE_MODE
|
|
_pstgParent->ReleaseCurrentStream(_ulHighWater);
|
|
#else
|
|
_pstgParent->ReleaseCurrentStream();
|
|
#endif
|
|
|
|
delete this;
|
|
}
|
|
else if (lRet < 0)
|
|
lRet = 0;
|
|
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::Release\n"));
|
|
olLog(("%p::Out CSimpStream::Release(). ret == %lu\n", this, lRet));
|
|
FreeLogFile();
|
|
return lRet;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::Stat, public
|
|
//
|
|
// Synopsis: Fills in a buffer of information about this object
|
|
//
|
|
// Arguments: [pstatstg] - Buffer
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [pstatstg]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::Stat(STATSTGW *pstatstg, DWORD grfStatFlag)
|
|
{
|
|
SCODE sc = S_OK;
|
|
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::Stat(%p, %lu)\n",
|
|
pstatstg, grfStatFlag));
|
|
|
|
SIMP_VALIDATE(Stat(pstatstg, grfStatFlag));
|
|
|
|
memset (pstatstg, 0, sizeof(STATSTG));
|
|
|
|
if (!(grfStatFlag & STATFLAG_NONAME))
|
|
{
|
|
return STG_E_INVALIDFLAG;
|
|
//pstatstg->pwcsName = (WCHAR *) CoTaskMemAlloc (
|
|
// _pdfl->GetName()->GetLength()+sizeof(WCHAR));
|
|
//if (pstatstg->pwcsName)
|
|
//{
|
|
// memcpy (pstatstg->pwcsName, _pdfl->GetName()->GetBuffer(),
|
|
// _pdfl->GetName()->GetLength());
|
|
// pstatstg->pwcsName[_pdfl->GetName()->GetLength()/sizeof(WCHAR)]
|
|
// = L'\0';
|
|
//}
|
|
//else sc = STG_E_INSUFFICIENTMEMORY;
|
|
}
|
|
|
|
pstatstg->cbSize.LowPart = _ulSeekPos - _ulSeekStart;
|
|
pstatstg->cbSize.HighPart = 0;
|
|
pstatstg->type = STGTY_STREAM;
|
|
pstatstg->grfMode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE;
|
|
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::Stat\n"));
|
|
return sc;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::Clone, public
|
|
//
|
|
// Synopsis: Clones a stream
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::Clone(IStream **ppstm)
|
|
{
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::Clone(%p)\n",
|
|
ppstm));
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::Clone\n"));
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::AddRef, public
|
|
//
|
|
// Synopsis: Increments the ref count
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP_(ULONG) CSimpStream::AddRef(void)
|
|
{
|
|
ULONG ulRet;
|
|
|
|
olLog(("%p::In CSimpStream::AddRef()\n", this));
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::AddRef()\n"));
|
|
|
|
AtomicInc(&_cReferences);
|
|
ulRet = _cReferences;
|
|
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::AddRef\n"));
|
|
olLog(("%p::Out CSimpStream::AddRef(). ret == %lu\n", this, ulRet));
|
|
return ulRet;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::LockRegion, public
|
|
//
|
|
// Synopsis: Nonfunctional
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::LockRegion(ULARGE_INTEGER libOffset,
|
|
ULARGE_INTEGER cb,
|
|
DWORD dwLockType)
|
|
{
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::LockRegion("
|
|
"%lu, %lu\n", ULIGetLow(cb), dwLockType));
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::LockRegion\n"));
|
|
return ResultFromScode(STG_E_INVALIDFUNCTION);
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::UnlockRegion, public
|
|
//
|
|
// Synopsis: Nonfunctional
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::UnlockRegion(ULARGE_INTEGER libOffset,
|
|
ULARGE_INTEGER cb,
|
|
DWORD dwLockType)
|
|
{
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::UnlockRegion(%lu, %lu)\n",
|
|
ULIGetLow(cb), dwLockType));
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::UnlockRegion\n"));
|
|
return ResultFromScode(STG_E_INVALIDFUNCTION);
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::Commit, public
|
|
//
|
|
// Synopsis: No-op in current implementation
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::Commit(DWORD grfCommitFlags)
|
|
{
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::Commit(%lu)\n",
|
|
grfCommitFlags));
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::Commit\n"));
|
|
return STG_E_UNIMPLEMENTEDFUNCTION;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::Revert, public
|
|
//
|
|
// Synopsis: No-op in current implementation
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::Revert(void)
|
|
{
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::Revert()\n"));
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::Revert\n"));
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::QueryInterface, public
|
|
//
|
|
// Synopsis: Returns an object for the requested interface
|
|
//
|
|
// Arguments: [iid] - Interface ID
|
|
// [ppvObj] - Object return
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [ppvObj]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::QueryInterface(REFIID iid, void **ppvObj)
|
|
{
|
|
SCODE sc;
|
|
|
|
olLog(("%p::In CSimpStream::QueryInterface(?, %p)\n",
|
|
this, ppvObj));
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream::QueryInterface(?, %p)\n",
|
|
ppvObj));
|
|
|
|
SIMP_VALIDATE(QueryInterface(iid, ppvObj));
|
|
|
|
sc = S_OK;
|
|
if (IsEqualIID(iid, IID_IStream) || IsEqualIID(iid, IID_IUnknown))
|
|
{
|
|
*ppvObj = (IStream *)this;
|
|
CSimpStream::AddRef();
|
|
}
|
|
else if (IsEqualIID(iid, IID_IMarshal))
|
|
{
|
|
*ppvObj = (IMarshal *)this;
|
|
CSimpStream::AddRef();
|
|
}
|
|
else
|
|
sc = E_NOINTERFACE;
|
|
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStream::QueryInterface => %p\n",
|
|
ppvObj));
|
|
olLog(("%p::Out CSimpStream::QueryInterface(). "
|
|
"*ppvObj == %p, ret == %lx\n", this, *ppvObj, sc));
|
|
return ResultFromScode(sc);
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::GetUnmarshalClass, public
|
|
//
|
|
// Synopsis: Returns the class ID
|
|
//
|
|
// Arguments: [riid] - IID of object
|
|
// [pv] - Unreferenced
|
|
// [dwDestContext] - Unreferenced
|
|
// [pvDestContext] - Unreferenced
|
|
// [mshlflags] - Unreferenced
|
|
// [pcid] - CLSID return
|
|
//
|
|
// Returns: Invalid function.
|
|
//
|
|
// Modifies: [pcid]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::GetUnmarshalClass(REFIID riid,
|
|
void *pv,
|
|
DWORD dwDestContext,
|
|
LPVOID pvDestContext,
|
|
DWORD mshlflags,
|
|
LPCLSID pcid)
|
|
{
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::GetMarshalSizeMax, public
|
|
//
|
|
// Synopsis: Returns the size needed for the marshal buffer
|
|
//
|
|
// Arguments: [riid] - IID of object being marshaled
|
|
// [pv] - Unreferenced
|
|
// [dwDestContext] - Unreferenced
|
|
// [pvDestContext] - Unreferenced
|
|
// [mshlflags] - Unreferenced
|
|
// [pcbSize] - Size return
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [pcbSize]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::GetMarshalSizeMax(REFIID riid,
|
|
void *pv,
|
|
DWORD dwDestContext,
|
|
LPVOID pvDestContext,
|
|
DWORD mshlflags,
|
|
LPDWORD pcbSize)
|
|
{
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::MarshalInterface, public
|
|
//
|
|
// Synopsis: Marshals a given object
|
|
//
|
|
// Arguments: [pstStm] - Stream to write marshal data into
|
|
// [riid] - Interface to marshal
|
|
// [pv] - Unreferenced
|
|
// [dwDestContext] - Unreferenced
|
|
// [pvDestContext] - Unreferenced
|
|
// [mshlflags] - Unreferenced
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::MarshalInterface(IStream *pstStm,
|
|
REFIID riid,
|
|
void *pv,
|
|
DWORD dwDestContext,
|
|
LPVOID pvDestContext,
|
|
DWORD mshlflags)
|
|
{
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::UnmarshalInterface, public
|
|
//
|
|
// Synopsis: Non-functional
|
|
//
|
|
// Arguments: [pstStm] -
|
|
// [riid] -
|
|
// [ppvObj] -
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [ppvObj]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::UnmarshalInterface(IStream *pstStm,
|
|
REFIID riid,
|
|
void **ppvObj)
|
|
{
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::ReleaseMarshalData, public
|
|
//
|
|
// Synopsis: Non-functional
|
|
//
|
|
// Arguments: [pstStm] -
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 18-Sep-92 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::ReleaseMarshalData(IStream *pstStm)
|
|
{
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStream::DisconnectObject, public
|
|
//
|
|
// Synopsis: Non-functional
|
|
//
|
|
// Arguments: [dwRevserved] -
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 18-Sep-92 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStream::DisconnectObject(DWORD dwReserved)
|
|
{
|
|
return STG_E_INVALIDFUNCTION;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStreamOpen::Init, public
|
|
//
|
|
// Synopsis: Initialize stream object for simple mode read
|
|
//
|
|
// Arguments: [pstgParent] -- Pointer to parent
|
|
// [hFile] -- File handle for writes
|
|
// [ulSeekStart] -- Beginning seek pointer
|
|
// [grfMode] -- open mode of the stream
|
|
// [pdfl] -- CDfNameList entry for this stream
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Jun-96 HenryLee Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
SCODE CSimpStreamOpen::Init(CSimpStorageOpen *pstgParent, HANDLE hFile,
|
|
ULONG ulSeekStart, DWORD grfMode, CDfNameList *pdfl)
|
|
{
|
|
simpDebugOut((DEB_ITRACE, "In CSimpStreamOpen::Init:%p()\n", this));
|
|
simpAssert (pdfl != NULL);
|
|
|
|
_ulSeekStart = ulSeekStart;
|
|
_pdfl = pdfl;
|
|
_hFile = hFile;
|
|
_pstgParent = pstgParent;
|
|
_cReferences = 1;
|
|
_grfMode = grfMode;
|
|
|
|
if (SetFilePointer(_hFile, ulSeekStart, NULL, FILE_BEGIN) == 0xFFFFFFFF)
|
|
{
|
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
#ifdef SECURE_SIMPLE_MODE
|
|
_ulHighWater = ulSeekStart + pdfl->GetSize();
|
|
#endif
|
|
_ulSeekPos = ulSeekStart;
|
|
|
|
simpDebugOut((DEB_ITRACE, "Out CSimpStreamOpen::Init\n"));
|
|
return S_OK;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStreamOpen::Read, public
|
|
//
|
|
// Synopsis: Read from a stream for simple mode open
|
|
//
|
|
// Arguments: [pb] - Buffer
|
|
// [cb] - Count of bytes to read
|
|
// [pcbRead] - Return of bytes written
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [pcbRead]
|
|
//
|
|
// History: 04-Aug-96 HenryLee Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStreamOpen::Read ( VOID *pb, ULONG cb, ULONG *pcbRead)
|
|
{
|
|
SCODE sc = S_OK;
|
|
simpAssert (_pdfl != NULL);
|
|
|
|
// cannot read past end of stream
|
|
if (_ulSeekPos + cb > _ulSeekStart + _pdfl->GetSize())
|
|
cb = _ulSeekStart + _pdfl->GetSize() - _ulSeekPos;
|
|
|
|
sc = CSimpStream::Read (pb, cb, pcbRead);
|
|
|
|
return sc;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStreamOpen::Write, public
|
|
//
|
|
// Synopsis: Write to a stream for simple mode open
|
|
//
|
|
// Arguments: [pb] - Buffer
|
|
// [cb] - Count of bytes to write
|
|
// [pcbWritten] - Return of bytes written
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [pcbWritten]
|
|
//
|
|
// History: 04-Jun-96 HenryLee Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStreamOpen::Write(VOID const *pb, ULONG cb, ULONG *pcbWritten)
|
|
{
|
|
SCODE sc = S_OK;
|
|
simpAssert (_pdfl != NULL);
|
|
|
|
if ((_grfMode & (STGM_READ|STGM_WRITE|STGM_READWRITE)) == STGM_READ)
|
|
return STG_E_ACCESSDENIED;
|
|
|
|
// cannot write past end of stream
|
|
if (_ulSeekPos + cb > _ulSeekStart + _pdfl->GetSize())
|
|
return STG_E_WRITEFAULT;
|
|
|
|
sc = CSimpStream::Write (pb, cb, pcbWritten);
|
|
|
|
return sc;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStreamOpen::SetSize, public
|
|
//
|
|
// Synopsis: Sets the size of a stream for simple mode read
|
|
//
|
|
// Arguments: [ulNewSize] - New size
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Jun-96 HenryLee Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStreamOpen::SetSize(ULARGE_INTEGER ulNewSize)
|
|
{
|
|
simpDebugOut((DEB_TRACE, "In CSimpStream2::SetSize()\n"));
|
|
|
|
simpAssert (_pdfl != NULL);
|
|
|
|
return STG_E_INVALIDFUNCTION;
|
|
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStreamOpen::SetSize\n"));
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStreamOpen::Release, public
|
|
//
|
|
// Synopsis: Releases a stream
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// History: 04-Jun-96 HenryLee Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP_(ULONG) CSimpStreamOpen::Release()
|
|
{
|
|
simpDebugOut((DEB_TRACE, "In CSimpStreamOpen::Release()\n"));
|
|
|
|
simpAssert(_cReferences > 0);
|
|
LONG lRet = AtomicDec(&_cReferences);
|
|
if (lRet == 0)
|
|
{
|
|
((CSimpStorageOpen *)_pstgParent)->ReleaseCurrentStream();
|
|
delete this;
|
|
}
|
|
else if (lRet < 0)
|
|
lRet = 0;
|
|
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStreamOpen::Release\n"));
|
|
return lRet;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStreamOpen::Seek, public
|
|
//
|
|
// Synopsis: Seek to a point in a stream
|
|
//
|
|
// Arguments: [dlibMove] - Offset to move by
|
|
// [dwOrigin] - SEEK_SET, SEEK_CUR, SEEK_END
|
|
// [plibNewPosition] - Return of new offset
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [plibNewPosition]
|
|
//
|
|
// History: 04-Sep-96 HenryLee Created
|
|
//
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStreamOpen::Seek(LARGE_INTEGER dlibMove,
|
|
DWORD dwOrigin,
|
|
ULARGE_INTEGER *plibNewPosition)
|
|
{
|
|
SCODE sc = S_OK;
|
|
LONG lMove;
|
|
ULONG ulPos;
|
|
|
|
simpDebugOut((DEB_TRACE, "%p::In CSimpStreamOpen::Seek(%ld, %lu, %p)\n",
|
|
this, LIGetLow(dlibMove), dwOrigin, plibNewPosition));
|
|
|
|
SIMP_VALIDATE(Seek(dlibMove, dwOrigin, plibNewPosition));
|
|
|
|
// Truncate dlibMove to 32 bits
|
|
if (dwOrigin == STREAM_SEEK_SET)
|
|
{
|
|
if (LIGetHigh(dlibMove) != 0) // Make sure we don't seek too far
|
|
LISet32(dlibMove, 0xffffffff);
|
|
}
|
|
else
|
|
{
|
|
// High dword must be zero for positive values or -1 for
|
|
// negative values
|
|
// Additionally, for negative values, the low dword can't
|
|
// exceed -0x80000000 because the 32nd bit is the sign
|
|
// bit
|
|
if (LIGetHigh(dlibMove) > 0 ||
|
|
(LIGetHigh(dlibMove) == 0 && LIGetLow(dlibMove) >= 0x80000000))
|
|
LISet32(dlibMove, 0x7fffffff);
|
|
else if (LIGetHigh(dlibMove) < -1 ||
|
|
(LIGetHigh(dlibMove) == -1 && LIGetLow(dlibMove) <= 0x7fffffff))
|
|
LISet32(dlibMove, 0x80000000);
|
|
}
|
|
|
|
lMove = (LONG)LIGetLow(dlibMove);
|
|
|
|
switch(dwOrigin)
|
|
{
|
|
case STREAM_SEEK_SET:
|
|
if (lMove < 0 || (ULONG) lMove > _pdfl->GetSize())
|
|
return STG_E_INVALIDFUNCTION;
|
|
|
|
ulPos = _ulSeekStart + lMove;
|
|
break;
|
|
|
|
case STREAM_SEEK_END:
|
|
if (lMove > 0 || (lMove < 0 && (ULONG)(-lMove) > _pdfl->GetSize()))
|
|
return STG_E_INVALIDFUNCTION;
|
|
|
|
ulPos = _ulSeekStart + _pdfl->GetSize() + lMove;
|
|
break;
|
|
|
|
case STREAM_SEEK_CUR:
|
|
ulPos = SetFilePointer(_hFile, 0, NULL, FILE_CURRENT);
|
|
|
|
if ((ULONG) (ulPos + lMove) > _ulSeekStart + _pdfl->GetSize() ||
|
|
(LONG) (ulPos + lMove) < _ulSeekStart)
|
|
return STG_E_INVALIDFUNCTION;
|
|
|
|
ulPos += lMove;
|
|
break;
|
|
}
|
|
|
|
ulPos = SetFilePointer(_hFile,
|
|
ulPos,
|
|
NULL,
|
|
FILE_BEGIN);
|
|
|
|
if (plibNewPosition != NULL)
|
|
{
|
|
ULISet32(*plibNewPosition, ulPos - _ulSeekStart);
|
|
}
|
|
|
|
_ulSeekPos = ulPos;
|
|
|
|
simpDebugOut((DEB_TRACE, "%p::Out CSimpStreamOpen::Seek(). ulPos==%lu,"
|
|
" ret==%lx\n", this, ulPos, sc));
|
|
return sc;
|
|
}
|
|
|
|
//+--------------------------------------------------------------
|
|
//
|
|
// Member: CSimpStreamOpen::Stat, public
|
|
//
|
|
// Synopsis: Fills in a buffer of information about this object
|
|
//
|
|
// Arguments: [pstatstg] - Buffer
|
|
//
|
|
// Returns: Appropriate status code
|
|
//
|
|
// Modifies: [pstatstg]
|
|
//
|
|
// History: 04-Aug-94 PhilipLa Created
|
|
//
|
|
//---------------------------------------------------------------
|
|
|
|
STDMETHODIMP CSimpStreamOpen::Stat(STATSTGW *pstatstg, DWORD grfStatFlag)
|
|
{
|
|
SCODE sc = S_OK;
|
|
simpDebugOut((DEB_TRACE, "In CSimpStreamOpen::Stat(%p, %lu)\n",
|
|
pstatstg, grfStatFlag));
|
|
|
|
SIMP_VALIDATE(Stat(pstatstg, grfStatFlag));
|
|
|
|
simpAssert (_pdfl != NULL);
|
|
|
|
memset (pstatstg, 0, sizeof(STATSTG));
|
|
|
|
if (!(grfStatFlag & STATFLAG_NONAME))
|
|
{
|
|
pstatstg->pwcsName = (WCHAR *) CoTaskMemAlloc (
|
|
_pdfl->GetName()->GetLength()+sizeof(WCHAR));
|
|
|
|
if (pstatstg->pwcsName)
|
|
{
|
|
memcpy (pstatstg->pwcsName, _pdfl->GetName()->GetBuffer(),
|
|
_pdfl->GetName()->GetLength());
|
|
pstatstg->pwcsName[_pdfl->GetName()->GetLength()/sizeof(WCHAR)] =
|
|
L'\0';
|
|
}
|
|
else sc = STG_E_INSUFFICIENTMEMORY;
|
|
}
|
|
|
|
pstatstg->cbSize.LowPart = _pdfl->GetSize();
|
|
pstatstg->cbSize.HighPart = 0;
|
|
pstatstg->type = STGTY_STREAM;
|
|
pstatstg->grfMode = _grfMode;
|
|
|
|
simpDebugOut((DEB_TRACE, "Out CSimpStreamOpen::Stat\n"));
|
|
return sc;
|
|
}
|
|
|