#include "priv.h" class CFileStream : public IStream { public: // IUnknown STDMETHOD(QueryInterface) (THIS_ REFIID riid, void **ppvObj); STDMETHOD_(ULONG,AddRef) (THIS); STDMETHOD_(ULONG,Release) (THIS); // IStream STDMETHOD(Read) (THIS_ void *pv, ULONG cb, ULONG *pcbRead); STDMETHOD(Write) (THIS_ void const *pv, ULONG cb, ULONG *pcbWritten); STDMETHOD(Seek) (THIS_ LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition); STDMETHOD(SetSize) (THIS_ ULARGE_INTEGER libNewSize); STDMETHOD(CopyTo) (THIS_ IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten); STDMETHOD(Commit) (THIS_ DWORD grfCommitFlags); STDMETHOD(Revert) (THIS); STDMETHOD(LockRegion) (THIS_ ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType); STDMETHOD(UnlockRegion) (THIS_ ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType); STDMETHOD(Stat) (THIS_ STATSTG *pstatstg, DWORD grfStatFlag); STDMETHOD(Clone)(THIS_ IStream **ppstm); CFileStream(HANDLE hf, DWORD grfMode, LPCWSTR pszName); private: ~CFileStream(); HRESULT InternalCommit(DWORD grfCommitFlags, BOOL fSendChange); LONG _cRef; // Reference count HANDLE _hFile; // the file. DWORD _grfMode; // The mode that we opened the file in. BOOL _fLastOpWrite; // The last operation was a write. ULONG _iBuffer; // Index in Buffer ULONG _cbBufLen; // length of buffer if reading BYTE _bBuffer[4096]; // buffer WCHAR _szName[MAX_PATH]; // file name in case someone calls Stat }; CFileStream::CFileStream(HANDLE hf, DWORD grfMode, LPCWSTR pszName) : _cRef(1), _hFile(hf), _grfMode(grfMode) { ASSERT(_cbBufLen == 0); ASSERT(_iBuffer == 0); ASSERT(_fLastOpWrite == FALSE); HRESULT hr = StringCchCopyW(_szName, ARRAYSIZE(_szName), pszName); if (FAILED(hr)) { _szName[0] = L'\0'; } } CFileStream::~CFileStream() { if (_fLastOpWrite) { InternalCommit(0, TRUE); } ASSERT(_hFile != INVALID_HANDLE_VALUE); CloseHandle(_hFile); } STDMETHODIMP CFileStream::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CFileStream, IStream), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CFileStream::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CFileStream::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } STDMETHODIMP CFileStream::Read(void *pv, ULONG cb, ULONG *pcbRead) { ULONG cbReadRequestSize = cb; ULONG cbT, cbRead; HRESULT hr = S_OK; // Have we write since our last read? if (_fLastOpWrite == TRUE) { hr = InternalCommit(0, FALSE); if (FAILED(hr)) { if (pcbRead) *pcbRead = 0; return hr; } } _fLastOpWrite = FALSE; while (cb > 0) { // Assert if we are beyond the bufferlen and Not sizeof(_bBuffer) which // would imply a seek happened... ASSERT((_iBuffer <= _cbBufLen) || (_iBuffer == sizeof(_bBuffer))); if (_iBuffer < _cbBufLen) { cbT = _cbBufLen - _iBuffer; if (cbT > cb) cbT = cb; memcpy(pv, &_bBuffer[_iBuffer], cbT); _iBuffer += cbT; cb -= cbT; if (cb == 0) break; (BYTE *&)pv += cbT; } // Buffer's empty. Handle rest of large reads directly... // if (cb > sizeof(_bBuffer)) { cbT = cb - cb % sizeof(_bBuffer); if (!ReadFile(_hFile, pv, cbT, &cbRead, NULL)) { DebugMsg(DM_TRACE, TEXT("Stream read IO error %d"), GetLastError()); hr = ResultFromLastError(); break; } cb -= cbRead; (BYTE *&)pv += cbRead; if (cbT != cbRead) break; // end of file } if (cb == 0) break; // was the last read a partial read? if so we are done // if (_cbBufLen > 0 && _cbBufLen < sizeof(_bBuffer)) { // DebugMsg(DM_TRACE, "Stream is empty"); break; } // Read an entire buffer's worth. We may try to read past EOF, // so we must only check for != 0... // if (!ReadFile(_hFile, _bBuffer, sizeof(_bBuffer), &cbRead, NULL)) { DebugMsg(DM_TRACE, TEXT("Stream read IO error 2 %d"), GetLastError()); hr = ResultFromLastError(); break; } if (cbRead == 0) break; _iBuffer = 0; _cbBufLen = cbRead; } if (pcbRead) *pcbRead = cbReadRequestSize - cb; if (cb != 0) { // DebugMsg(DM_TRACE, "CFileStream::Read() incomplete read"); hr = S_FALSE; // still success! but not completely } return hr; } STDMETHODIMP CFileStream::Write(const void *pv, ULONG cb, ULONG *pcbWritten) { ULONG cbRequestedWrite = cb; ULONG cbT; HRESULT hr = S_OK; if (!((_grfMode & STGM_WRITE) || (_grfMode & STGM_READWRITE))) { // Can't write to a stream that we didn't open for write access return STG_E_ACCESSDENIED; } // Have we read since our last write? if (_fLastOpWrite == FALSE && _iBuffer < _cbBufLen) { // Need to reset the file pointer so that this write goes to the right spot SetFilePointer(_hFile, -(int)(_cbBufLen - _iBuffer), NULL, STREAM_SEEK_CUR); _iBuffer = 0; _cbBufLen = 0; } while (cb > 0) { if (_iBuffer < sizeof(_bBuffer)) { cbT = min((ULONG)(sizeof(_bBuffer) - _iBuffer), cb); memcpy(&_bBuffer[_iBuffer], pv, cbT); _iBuffer += cbT; cb -= cbT; _fLastOpWrite = TRUE; if (cb == 0) break; (BYTE *&)pv += cbT; } hr = InternalCommit(0, FALSE); if (FAILED(hr)) break; if (cb > sizeof(_bBuffer)) { ULONG cbWrite; cbT = cb - cb % sizeof(_bBuffer); if (!WriteFile(_hFile, pv, cbT, &cbWrite, NULL)) { DebugMsg(DM_TRACE, TEXT("Stream write IO error 2, %d"), GetLastError()); hr = ResultFromLastError(); break; } cb -= cbWrite; (BYTE *&)pv += cbWrite; if (cbWrite != cbT) break; // media full, we are done } } if (pcbWritten) *pcbWritten = cbRequestedWrite - cb; if ((cb != 0) && (hr == S_OK)) { DebugMsg(DM_TRACE, TEXT("CFileStream::Write() incomplete")); hr = S_FALSE; // still success! but not completely } return hr; } STDMETHODIMP CFileStream::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) { COMPILETIME_ASSERT(FILE_BEGIN == STREAM_SEEK_SET); COMPILETIME_ASSERT(FILE_CURRENT == STREAM_SEEK_CUR); COMPILETIME_ASSERT(FILE_END == STREAM_SEEK_END); HRESULT hr = S_OK; LARGE_INTEGER liOut; // Have we written since our last read? if (_fLastOpWrite == TRUE) { hr = InternalCommit(0, FALSE); if (FAILED(hr)) { return hr; } } if (_iBuffer < _cbBufLen) { // Need to reset the file pointer to point to the right place SetFilePointer(_hFile, -(int)(_cbBufLen - _iBuffer), NULL, STREAM_SEEK_CUR); } // Invalidate the buffer because we may move the file pointer _iBuffer = 0; _cbBufLen = 0; // Say we have not read it yet. if (SetFilePointerEx(_hFile, dlibMove, &liOut, dwOrigin)) { // Some callers pass NULL for the plibNewPosition parameter // in the IStream::Seek() call. \shell32\filetbl.c, _IconCacheSave() // is an example. if (plibNewPosition) { // SetFilePointerEx takes a LARGE_INTEGER, but Seek takes a ULARGE_INTEGER, Why the difference? plibNewPosition->QuadPart = liOut.QuadPart; } } else { hr = ResultFromLastError(); } return hr; } STDMETHODIMP CFileStream::SetSize(ULARGE_INTEGER libNewSize) { HRESULT hr = E_FAIL; // First save away the pointer's position LARGE_INTEGER pos, test; LARGE_INTEGER zero = {0}; if (SetFilePointerEx(_hFile, zero, &pos, FILE_CURRENT)) { if (libNewSize.HighPart != 0) { hr = STG_E_INVALIDFUNCTION; } else { // Now set the size LARGE_INTEGER largeint; largeint.HighPart = 0; largeint.LowPart = libNewSize.LowPart; if (SetFilePointerEx(_hFile, largeint, &test, FILE_BEGIN) && SetEndOfFile(_hFile)) { // Reset the file pointer position if (SetFilePointerEx(_hFile, pos, &test, FILE_BEGIN)) { hr = S_OK; } } } } return hr; } // // REVIEW: this could use the internal buffer in the stream to avoid // extra buffer copies. // STDMETHODIMP CFileStream::CopyTo(IStream *pstmTo, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) { HRESULT hr = S_OK; if (pcbRead) pcbRead->QuadPart = 0; if (pcbWritten) pcbWritten->QuadPart = 0; // // I'd like to use a buffer size that takes about a second to copy // for the sake of cancel opportunities, but IStream doesn't give // me useful info like the stream speed. // const DWORD cbBuffer = 0x00010000; // // Alloc the buffer and begin the copy // BYTE * pBuf = (BYTE *) LocalAlloc(LPTR, cbBuffer); if (!pBuf) return E_OUTOFMEMORY; while (cb.QuadPart) { // // Cast is OK because we know sizeof(buf) fits in a ULONG // ULONG cbRead = (ULONG)min(cb.QuadPart, cbBuffer); hr = Read(pBuf, cbRead, &cbRead); if (pcbRead) pcbRead->QuadPart += cbRead; if (FAILED(hr) || (cbRead == 0)) break; cb.QuadPart -= cbRead; hr = pstmTo->Write(pBuf, cbRead, &cbRead); if (pcbWritten) pcbWritten->QuadPart += cbRead; if (FAILED(hr) || (cbRead == 0)) break; } LocalFree(pBuf); // ISSUE // // This was here when I got here, but from the SDK I don't see // why we'd accept S_FALSE as "complete success" if (S_FALSE == hr) hr = S_OK; return hr; } STDMETHODIMP CFileStream::Commit(DWORD grfCommitFlags) { return InternalCommit(grfCommitFlags, TRUE); } HRESULT CFileStream::InternalCommit(DWORD grfCommitFlags, BOOL fSendChange) { if (_fLastOpWrite) { if (_iBuffer > 0) { DWORD cbWrite; WriteFile(_hFile, _bBuffer, _iBuffer, &cbWrite, NULL); if (cbWrite != _iBuffer) { DebugMsg(DM_TRACE, TEXT("CFileStream::Commit() incomplete write %d"), GetLastError()); return STG_E_MEDIUMFULL; } _iBuffer = 0; if (fSendChange) { SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, _szName, NULL); } } // Since we committed already, we don't need to commit again until the next write, so assume read _fLastOpWrite = FALSE; } return S_OK; } STDMETHODIMP CFileStream::Revert() { return E_NOTIMPL; } STDMETHODIMP CFileStream::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) { return E_NOTIMPL; } STDMETHODIMP CFileStream::UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) { return E_NOTIMPL; } STDMETHODIMP CFileStream::Stat(STATSTG *pstatstg, DWORD grfStatFlag) { if ( !pstatstg ) return STG_E_INVALIDPOINTER; ZeroMemory(pstatstg, sizeof(STATSTG)); // per COM conventions HRESULT hr = E_FAIL; BY_HANDLE_FILE_INFORMATION bhfi; if ( GetFileInformationByHandle(_hFile, &bhfi) ) { if (grfStatFlag & STATFLAG_NONAME) hr = S_OK; else hr = SHStrDupW(PathFindFileNameW(_szName), &pstatstg->pwcsName); if (SUCCEEDED(hr)) { pstatstg->type = STGTY_STREAM; pstatstg->cbSize.HighPart = bhfi.nFileSizeHigh; pstatstg->cbSize.LowPart = bhfi.nFileSizeLow; pstatstg->mtime = bhfi.ftLastWriteTime; pstatstg->ctime = bhfi.ftCreationTime; pstatstg->atime = bhfi.ftLastAccessTime; pstatstg->grfMode = _grfMode; pstatstg->reserved = bhfi.dwFileAttributes; } } return hr; } STDMETHODIMP CFileStream::Clone(IStream **ppstm) { return E_NOTIMPL; } // create an IStream from a Win32 file name. // in: // pszFile file name to open // grfMode STGM_ flags // // We export a W version of this function // STDAPI SHCreateStreamOnFileW(LPCWSTR pszFile, DWORD grfMode, IStream **ppstm) { *ppstm = NULL; // NOTE: these interpretations of the STGM bits are not done properly // but to maintain back compat we have to allow the invalid combinations // and not enforce the share bits right. use SHCreateStreamOnFileEx() to get // proper STGM bit support if (grfMode & ~(STGM_READ | STGM_WRITE | STGM_SHARE_DENY_NONE | STGM_SHARE_DENY_READ | STGM_SHARE_DENY_WRITE | STGM_SHARE_EXCLUSIVE | STGM_READWRITE | STGM_CREATE )) { DebugMsg(DM_ERROR, TEXT("CreateSreamOnFile: Invalid STGM_ mode")); return E_INVALIDARG; } HANDLE hFile; BOOL fCreated = FALSE; if ( grfMode & STGM_CREATE) { // Need to get the file attributes of the file first, so // that CREATE_ALWAYS will succeed for HIDDEN and SYSTEM // attributes. DWORD dwAttrib = GetFileAttributesW(pszFile); if ((DWORD)-1 == dwAttrib ) { // something went wrong, so set attributes to something // normal before we try to create the file... dwAttrib = 0; fCreated = TRUE; } // STGM_CREATE hFile = CreateFileW(pszFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, dwAttrib, NULL); } else { DWORD dwDesiredAccess, dwShareMode, dwShareBits; // not STGM_CREATE if ( grfMode & STGM_WRITE ) { dwDesiredAccess = GENERIC_WRITE; } else { dwDesiredAccess = GENERIC_READ; } if ( grfMode & STGM_READWRITE ) { dwDesiredAccess |= (GENERIC_READ | GENERIC_WRITE); } dwShareBits = grfMode & (STGM_SHARE_EXCLUSIVE | STGM_SHARE_DENY_WRITE | STGM_SHARE_DENY_READ | STGM_SHARE_DENY_NONE); switch( dwShareBits ) { case STGM_SHARE_DENY_WRITE: dwShareMode = FILE_SHARE_READ; break; case STGM_SHARE_DENY_READ: dwShareMode = FILE_SHARE_WRITE; break; case STGM_SHARE_EXCLUSIVE: dwShareMode = 0; break; default: dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; break; } hFile = CreateFileW(pszFile, dwDesiredAccess, dwShareMode, NULL, OPEN_EXISTING, 0, NULL); } HRESULT hr; if (INVALID_HANDLE_VALUE != hFile) { if ((grfMode & STGM_CREATE) && fCreated) { SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, pszFile, NULL); } *ppstm = (IStream *)new CFileStream(hFile, grfMode, pszFile); if (*ppstm) { hr = S_OK; } else { CloseHandle(hFile); hr = E_OUTOFMEMORY; } } else { DebugMsg(DM_TRACE, TEXT("CreateSreamOnFile: CreateFileW() failed %s"), pszFile); hr = ResultFromLastError(); } return hr; } // We export an A version of this function STDAPI SHCreateStreamOnFileA(LPCSTR pszFile, DWORD grfMode, IStream **ppstm) { WCHAR szFile[MAX_PATH]; SHAnsiToUnicode(pszFile, szFile, ARRAYSIZE(szFile)); return SHCreateStreamOnFileW(szFile, grfMode, ppstm); } STDAPI ModeToCreateFileFlags(DWORD grfMode, BOOL fCreate, DWORD *pdwDesiredAccess, DWORD *pdwShareMode, DWORD *pdwCreationDisposition) { HRESULT hr = S_OK; *pdwDesiredAccess = *pdwShareMode = *pdwCreationDisposition = 0; switch (grfMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) { case STGM_READ: *pdwDesiredAccess |= GENERIC_READ; break; case STGM_WRITE: *pdwDesiredAccess |= GENERIC_WRITE; break; case STGM_READWRITE: *pdwDesiredAccess |= GENERIC_READ | GENERIC_WRITE; break; default: hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } if (SUCCEEDED(hr)) { switch (grfMode & (STGM_SHARE_DENY_NONE | STGM_SHARE_DENY_READ | STGM_SHARE_DENY_WRITE | STGM_SHARE_EXCLUSIVE)) { case STGM_SHARE_DENY_READ: *pdwShareMode = FILE_SHARE_WRITE | FILE_SHARE_DELETE; break; case STGM_SHARE_DENY_WRITE: *pdwShareMode = FILE_SHARE_READ; break; case STGM_SHARE_EXCLUSIVE: *pdwShareMode = 0; break; case STGM_SHARE_DENY_NONE: default: // assume STGM_SHARE_DENY_NONE as per documentation *pdwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; } if (SUCCEEDED(hr)) { switch (grfMode & (STGM_CREATE | STGM_FAILIFTHERE)) { case STGM_CREATE: *pdwCreationDisposition = CREATE_ALWAYS; break; case STGM_FAILIFTHERE: // this is a 0 flag *pdwCreationDisposition = fCreate ? CREATE_NEW : OPEN_EXISTING; break; default: hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } } } return hr; } // similar to SHCreateStreamOnFile() but // 1) properly maps STGM bits into CreateFile() params // 2) takes dwAttributes for the STGM_CREATE case so you can create the file // with known attributes // NOTE: returns WIN32 errors from GetLastError through the HRESULT, NOT STG errors. STDAPI SHCreateStreamOnFileEx(LPCWSTR pszFile, DWORD grfMode, DWORD dwAttributes, BOOL fCreate, IStream * pstmTemplate, IStream **ppstm) { *ppstm = NULL; DWORD dwDesiredAccess, dwShareMode, dwCreationDisposition; HRESULT hr = ModeToCreateFileFlags(grfMode, fCreate, &dwDesiredAccess, &dwShareMode, &dwCreationDisposition); if (SUCCEEDED(hr)) { HANDLE hFile = CreateFileW(pszFile, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwAttributes, NULL); if (INVALID_HANDLE_VALUE == hFile) { DWORD dwErr = GetLastError(); // for some reason CreateFile is dumb and doesn't perform to spec here (?) if ((dwErr == ERROR_ACCESS_DENIED) && (dwCreationDisposition == CREATE_NEW) && PathFileExistsW(pszFile)) { dwErr = ERROR_ALREADY_EXISTS; } hr = HRESULT_FROM_WIN32(dwErr); } else { if ((CREATE_NEW == dwCreationDisposition) || (CREATE_ALWAYS == dwCreationDisposition)) { SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, pszFile, NULL); } *ppstm = (IStream *)new CFileStream(hFile, grfMode, pszFile); if (*ppstm) { hr = S_OK; } else { CloseHandle(hFile); hr = E_OUTOFMEMORY; } } } return hr; } // maps win32 errors from SHCreateStreamOnFileEx into STG error codes, for // use in IStorage/IStream implementations. HRESULT MapWin32ErrorToSTG(HRESULT hrIn) { HRESULT hr = hrIn; if (FAILED(hr)) { // munge some of the failure cases back into the STG error values // that are expected. switch (hr) { case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND): case HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND): hr = STG_E_FILENOTFOUND; break; case HRESULT_FROM_WIN32(ERROR_FILE_EXISTS): case HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS): hr = STG_E_FILEALREADYEXISTS; break; case HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED): hr = STG_E_ACCESSDENIED; } } return hr; }