//--------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation 1991-1993 // // File: stream.c // // This file contains some of the stream support code that is used by // the shell. It also contains the shells implementation of a memory // stream that is used by the cabinet to allow views to be serialized. // // History: // 08-20-93 KurtE Added header block and memory stream. // //--------------------------------------------------------------------------- #include "priv.h" #include #include "nullstm.h" // This code was stolen from shell32. This is the BETTER_STRONGER_FASTER // version (smaller and half the allocs), added after Win95 shipped. #include "stream.h" EXTERN_C HKEY SHRegDuplicateHKey(HKEY hkey); // The Win95/NT4/IE4 code did not enforce the grfMode. Turn this on to enforce: //#define ENFORCE_GRFMODE // Note: I haven't tested compat issues with this turned on yet... [mikesh] STDMETHODIMP CMemStream::QueryInterface(REFIID riid, void **ppvObj) { if (IsEqualIID(riid, IID_IStream) || IsEqualIID(riid, IID_IUnknown)) { *ppvObj=this; this->cRef++; return S_OK; } *ppvObj = NULL; return E_NOINTERFACE; } STDMETHODIMP_(ULONG) CMemStream::AddRef() { this->cRef++; return this->cRef; } BOOL CMemStream::WriteToReg() { if (this->cbData) { return ERROR_SUCCESS == RegSetValueEx(this->hkey, this->szValue[0] ? this->szValue : NULL, 0, REG_BINARY, this->cbData ? this->pBuf : (LPBYTE)"", this->cbData); } else { DWORD dwRet = SHDeleteValue(this->hkey, NULL, this->szValue); // If the Stream is being stored in the default key, then // we should clean up the key. Otherwise, the caller // passed us the key, and they need it. It would be rude for us // to delete it. Fixes a Start Menu bug (NT#361333) where we would delete the // programs key where start menu stores it's stuff on a load, so we // never persist anything. - lamadio (6.25.99) if (this->szValue[0] == TEXT('\0')) { SHDeleteEmptyKey(this->hkey, NULL); } return ERROR_SUCCESS == dwRet; } } STDMETHODIMP_(ULONG) CMemStream::Release() { this->cRef--; if (this->cRef > 0) return this->cRef; // If this is backed up by the registry serialize the data if (this->hkey) { // Backed by the registry. // Write and cleanup. WriteToReg(); RegCloseKey(this->hkey); } // Free the data buffer that is allocated to the stream if (this->pBuf) LocalFree(this->pBuf); LocalFree((HLOCAL)this); return 0; } STDMETHODIMP CMemStream::Read(void *pv, ULONG cb, ULONG *pcbRead) { #ifdef ENFORCE_GRFMODE if ((this->grfMode & (STGM_READ|STGM_WRITE|STGM_READWRITE)) == STGM_WRITE) { if (pcbRead != NULL) *pcbRead = 0; return STG_E_ACCESSDENIED; } #endif ASSERT(pv); // I guess a null read is ok. if (!cb) { if (pcbRead != NULL) *pcbRead = 0; return S_OK; } if (this->iSeek >= this->cbData) { if (pcbRead != NULL) *pcbRead = 0; // nothing read } else { if ((this->iSeek + cb) > this->cbData) cb = this->cbData - this->iSeek; // Now Copy the memory ASSERT(this->pBuf); CopyMemory(pv, this->pBuf + this->iSeek, cb); this->iSeek += (UINT)cb; if (pcbRead != NULL) *pcbRead = cb; } return S_OK; } LPBYTE CMemStream::GrowBuffer(ULONG cbNew) { if (this->pBuf == NULL) { this->pBuf = (LPBYTE)LocalAlloc(LPTR, cbNew); } else { LPBYTE pTemp = (LPBYTE)LocalReAlloc(this->pBuf, cbNew, LMEM_MOVEABLE | LMEM_ZEROINIT); if (pTemp) { this->pBuf = pTemp; } else { TraceMsg(TF_ERROR, "Stream buffer realloc failed"); return NULL; } } if (this->pBuf) this->cbAlloc = cbNew; return this->pBuf; } #define SIZEINCR 0x1000 STDMETHODIMP CMemStream::Write(void const *pv, ULONG cb, ULONG *pcbWritten) { #ifdef ENFORCE_GRFMODE if ((this->grfMode & (STGM_READ|STGM_WRITE|STGM_READWRITE)) == STGM_READ) { if (pcbWritten != NULL) *pcbWritten = 0; return STG_E_ACCESSDENIED; } #endif // I guess a null write is ok. if (!cb) { if (pcbWritten != NULL) *pcbWritten = 0; return S_OK; } // See if the data will fit into our current buffer if ((this->iSeek + cb) > this->cbAlloc) { // enlarge the buffer // Give it a little slop to avoid a lot of reallocs. if (GrowBuffer(this->iSeek + (UINT)cb + SIZEINCR) == NULL) return STG_E_INSUFFICIENTMEMORY; } ASSERT(this->pBuf); // See if we need to fill the area between the data size and // the seek position if (this->iSeek > this->cbData) { ZeroMemory(this->pBuf + this->cbData, this->iSeek - this->cbData); } CopyMemory(this->pBuf + this->iSeek, pv, cb); // buffer grown above this->iSeek += (UINT)cb; if (this->iSeek > this->cbData) this->cbData = this->iSeek; if (pcbWritten != NULL) *pcbWritten = cb; return S_OK; } STDMETHODIMP CMemStream::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) { LONG lNewSeek; // Note: curently not testing for error conditions for number wrap... switch (dwOrigin) { case STREAM_SEEK_SET: lNewSeek = (LONG)dlibMove.LowPart; break; case STREAM_SEEK_CUR: lNewSeek = (LONG)this->iSeek + (LONG)dlibMove.LowPart; break; case STREAM_SEEK_END: lNewSeek = (LONG)this->cbData + (LONG)dlibMove.LowPart; break; default: return STG_E_INVALIDPARAMETER; } if (lNewSeek < 0) return STG_E_INVALIDFUNCTION; this->iSeek = (UINT)lNewSeek; if (plibNewPosition != NULL) { plibNewPosition->LowPart = (DWORD)lNewSeek; plibNewPosition->HighPart = 0; } return S_OK; } STDMETHODIMP CMemStream::SetSize(ULARGE_INTEGER libNewSize) { #ifdef ENFORCE_GRFMODE if ((this->grfMode & (STGM_READ|STGM_WRITE|STGM_READWRITE)) == STGM_READ) { return STG_E_ACCESSDENIED; } #endif UINT cbNew = (UINT)libNewSize.LowPart; // See if the data will fit into our current buffer if (cbNew > this->cbData) { // See if we have to Enlarge the buffer. if (cbNew > this->cbAlloc) { // enlarge the buffer - Does not check wrap... // Give it a little slop to avoid a lot of reallocs. if (GrowBuffer(cbNew) == NULL) return STG_E_INSUFFICIENTMEMORY; } // Now fill some memory ZeroMemory(this->pBuf + this->cbData, cbNew - this->cbData); } // Save away the new size. this->cbData = cbNew; return S_OK; } STDMETHODIMP CMemStream::CopyTo(IStream *pstmTo, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) { #ifdef ENFORCE_GRFMODE if ((this->grfMode & (STGM_READ|STGM_WRITE|STGM_READWRITE)) == STGM_WRITE) { if (pcbRead != NULL) ZeroMemory(pcbRead, sizeof(*pcbRead)); if (pcbWritten != NULL) ZeroMemory(pcbWritten, sizeof(*pcbWritten)); return STG_E_ACCESSDENIED; } #endif HRESULT hres = S_OK; UINT cbRead = this->cbData - this->iSeek; ULONG cbWritten = 0; if (cb.HighPart == 0 && cb.LowPart < cbRead) { cbRead = cb.LowPart; } if (cbRead > 0) { hres = pstmTo->Write(this->pBuf + this->iSeek, cbRead, &cbWritten); this->iSeek += cbRead; } if (pcbRead) { pcbRead->LowPart = cbRead; pcbRead->HighPart = 0; } if (pcbWritten) { pcbWritten->LowPart = cbWritten; pcbWritten->HighPart = 0; } return hres; } STDMETHODIMP CMemStream::Commit(DWORD grfCommitFlags) { return E_NOTIMPL; } STDMETHODIMP CMemStream::Revert() { return E_NOTIMPL; } STDMETHODIMP CMemStream::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) { return E_NOTIMPL; } STDMETHODIMP CMemStream::UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) { return E_NOTIMPL; } // Trident calls this to determine the size of the structure. // No reason to not support this one. STDMETHODIMP CMemStream::Stat(STATSTG *pstatstg, DWORD grfStatFlag) { ZeroMemory(pstatstg, sizeof(*pstatstg)); // we have no name pstatstg->type = STGTY_STREAM; pstatstg->cbSize.LowPart = this->cbData; // blow off modify, create, access times (we don't track anyway) pstatstg->grfMode = this->grfMode; // we're not transacting, so we have no lock modes // we're the null clsid already // we're not based on storage, so we have no state or storage bits return S_OK; } STDMETHODIMP CMemStream::Clone(IStream **ppstm) { *ppstm = NULL; return E_NOTIMPL; } CMemStream * CreateMemStreamEx( LPBYTE pInit, UINT cbInit, LPCTSTR pszValue) OPTIONAL { UINT cchValue = (pszValue ? lstrlen(pszValue) : 0); UINT l_cbAlloc = sizeof(CMemStream) + (cchValue * sizeof(TCHAR)); // null terminator for pszValue is taken care of by CMemStream.szValue[1] CMemStream *localthis = (CMemStream *)LocalAlloc(LPTR, l_cbAlloc); if (localthis) { new (localthis) CMemStream; localthis->cRef = 1; // See if there is some initial data we should map in here. if ((pInit != NULL) && (cbInit > 0)) { if (localthis->GrowBuffer(cbInit) == NULL) { // Could not allocate buffer! LocalFree((HLOCAL)localthis); return NULL; } localthis->cbData = cbInit; CopyMemory(localthis->pBuf, pInit, cbInit); } if (pszValue) { StringCchCopy(localthis->szValue, cchValue + 1, pszValue); } // We have no other value to set this to localthis->grfMode = STGM_READWRITE; return localthis; } return NULL; } STDAPI_(IStream *) SHCreateMemStream( LPBYTE pInit, UINT cbInit) { CMemStream *localthis = CreateMemStreamEx(pInit, cbInit, NULL); if (localthis) return localthis; return NULL; } //---------------------------------------------------------------------------- // Open a stream to the reg file given an open key. // NB pszValue can be NULL. // // Win9x exported OpenRegStream which *always* returned a stream, even for read, // even when there was no data there. IE4 shell32 delegated to shlwapi's SHOpenRegStream // which needs to support this sub-optimal behavior. See NT5 bug 190878 (shell32 fault). // STDAPI_(IStream *) SHOpenRegStreamW( HKEY hkey, LPCWSTR pszSubkey, LPCWSTR pszValue, OPTIONAL DWORD grfMode) { IStream * pstm = SHOpenRegStream2W(hkey, pszSubkey, pszValue, grfMode); #ifndef UNIX if (!pstm) pstm = SHConstNullStream(); #endif return pstm; } STDAPI_(IStream *) SHOpenRegStreamA( HKEY hkey, LPCSTR pszSubkey, LPCSTR pszValue, OPTIONAL DWORD grfMode) { IStream * pstm = SHOpenRegStream2A(hkey, pszSubkey, pszValue, grfMode); #ifndef UNIX if (!pstm) pstm = SHConstNullStream(); #endif return pstm; } // We should add STGM_CREATE support to the shlwapi streams. When saving out // streams, we currently create the stream with STGM_WRITE (but not STGM_CREATE) // so shlwapi goes to all the wasted trouble of reading the old stream data into // memory, only to throw it away when we write over it. // // STGM_CREATE means "I don't care about the old values because I'm going to // overwrite them anyway." (It really should be named STGM_TRUNCATEONOPEN.) // STDAPI_(IStream *) SHOpenRegStream2( HKEY hkey, LPCTSTR pszSubkey, LPCTSTR pszValue, OPTIONAL DWORD grfMode) { CMemStream *localthis; // In bed with class... RIPMSG(IS_VALID_HANDLE(hkey, KEY), "SHOpenRegStream2: Caller passed invalid hkey"); RIPMSG(!pszSubkey || IS_VALID_STRING_PTR(pszSubkey, -1), "SHOpenRegStream2: Caller passed invalid pszSubkey"); RIPMSG(!pszValue || IS_VALID_STRING_PTR(pszValue, -1), "SHOpenRegStream2: Caller passed invalid pszValue"); // Null keys are illegal. if (!hkey) { return NULL; } localthis = CreateMemStreamEx(NULL, 0, pszValue); if (!localthis) return NULL; // Failed to allocate space localthis->grfMode = grfMode; // Get the hkey we're going to deal with // // Did the caller pass us a subkey, and does it contain a string? if (pszSubkey && *pszSubkey) { // Yes; Then try to bind to that key. // If this stream is one the user mentioned as wanting to write to // we need to save away the regkey and value. if ((grfMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_READ) { // Store away the key. // write access required. if (RegCreateKeyEx(hkey, pszSubkey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &localthis->hkey, NULL) != ERROR_SUCCESS) { TraceMsg(TF_ERROR, "SHOpenRegStream: Unable to create key."); localthis->hkey = NULL; // be paranoid } } else if (RegOpenKeyEx(hkey, pszSubkey, 0, KEY_READ, &localthis->hkey) != ERROR_SUCCESS) { localthis->hkey = NULL; // be paranoid } } else { localthis->hkey = SHRegDuplicateHKey(hkey); } // we don't have an hkey, bail if (NULL == localthis->hkey) { localthis->Release(); return NULL; } // Now see if we need to initialize the stream. if ((grfMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_WRITE) { DWORD dwType; DWORD cbData; if ((RegQueryValueEx(localthis->hkey, pszValue, NULL, &dwType, NULL, &cbData) == ERROR_SUCCESS) && cbData) { if (localthis->GrowBuffer(cbData) != NULL) { ASSERT(localthis->cbAlloc >= cbData); // Get the data. RegQueryValueEx(localthis->hkey, pszValue, NULL, &dwType, localthis->pBuf, &cbData); localthis->cbData = cbData; } else { TraceMsg(TF_ERROR, "OpenRegStream: Unable to initialize stream to registry."); localthis->Release(); return NULL; } } } // If the stream was opened read-only, then close the key so // CMemStream::Release won't try to write the "updates" back out to the // registry. if ((grfMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_READ) { RegCloseKey(localthis->hkey); localthis->hkey = NULL; } return localthis; } #ifdef UNICODE STDAPI_(IStream *) SHOpenRegStream2A( HKEY hkey, LPCSTR pszSubkey, LPCSTR pszValue, OPTIONAL DWORD grfMode) { IStream * pstm = NULL; RIPMSG(IS_VALID_HANDLE(hkey, KEY), "SHOpenRegStream2A: Caller passed invalid hkey"); RIPMSG(!pszSubkey || IS_VALID_STRING_PTRA(pszSubkey, -1), "SHOpenRegStream2A: Caller passed invalid pszSubkey"); RIPMSG(!pszValue || IS_VALID_STRING_PTRA(pszValue, -1), "SHOpenRegStream2A: Caller passed invalid pszValue"); WCHAR wszSubkey[MAX_PATH]; if (pszSubkey) { if (!MultiByteToWideChar(CP_ACP, 0, pszSubkey, -1, wszSubkey, SIZECHARS(wszSubkey))) return NULL; pszSubkey = (LPCSTR)wszSubkey; } WCHAR wszValue[MAX_PATH]; if (pszValue) { if (!MultiByteToWideChar(CP_ACP, 0, pszValue, -1, wszValue, SIZECHARS(wszValue))) return NULL; pszValue = (LPCSTR)wszValue; } pstm = SHOpenRegStream2W(hkey, (LPCWSTR)pszSubkey, (LPCWSTR)pszValue, grfMode); return pstm; } #else STDAPI_(IStream *) SHOpenRegStream2W( HKEY hkey, LPCWSTR pszSubkey, LPCWSTR pszValue, OPTIONAL DWORD grfMode) { IStream * pstm = NULL; RIPMSG(IS_VALID_HANDLE(hkey, KEY), "SHOpenRegStream2W: Caller passed invalid hkey"); RIPMSG(!pszSubkey || IS_VALID_STRING_PTRW(pszSubkey, -1), "SHOpenRegStream2W: Caller passed invalid pszSubkey"); RIPMSG(!pszValue || IS_VALID_STRING_PTRW(pszValue, -1), "SHOpenRegStream2W: Caller passed invalid pszValue"); CHAR szSubkey[MAX_PATH]; if (pszSubkey) { if (!WideCharToMultiByte(CP_ACP, 0, pszSubkey, -1, szSubkey, SIZECHARS(szSubkey), NULL, NULL)) return NULL; pszSubkey = (LPCWSTR)szSubkey; } CHAR szValue[MAX_PATH]; if (pszValue) { if (!WideCharToMultiByte(CP_ACP, 0, pszValue, -1, szValue, SIZECHARS(szValue), NULL, NULL)) return NULL; pszValue = (LPCWSTR)szValue; } pstm = SHOpenRegStream2A(hkey, (LPCSTR)pszSubkey, (LPCSTR)pszValue, grfMode); return pstm; } #endif // UNICODE