//+---------------------------------------------------------------------------- // // Ntfs.cpp : Implementation of NTFS Store driver classes // // Copyright (C) 1998, Microsoft Corporation // // File: ntfs.cpp // // Contents: Implementation of NTFS Store driver classes. // // Classes: CNtfsStoreDriver, CNtfsPropertyStream // // Functions: // // History: 3/31/98 KeithLau Created // //----------------------------------------------------------------------------- #include "stdafx.h" #include "filehc.h" #include "mailmsg.h" #include "mailmsgi.h" #include "mailmsgprops.h" #include "seo.h" #include "seo_i.c" #include "Ntfs.h" #include "smtpmsg.h" HANDLE g_hTransHeap = NULL; // // Instantiate the CLSIDs // #ifdef __cplusplus extern "C"{ #endif const CLSID CLSID_NtfsStoreDriver = {0x609b7e3a,0xc918,0x11d1,{0xaa,0x5e,0x00,0xc0,0x4f,0xa3,0x5b,0x82}}; const CLSID CLSID_NtfsEnumMessages = {0xbbddbdec,0xc947,0x11d1,{0xaa,0x5e,0x00,0xc0,0x4f,0xa3,0x5b,0x82}}; const CLSID CLSID_NtfsPropertyStream = {0x6d7572ac,0xc939,0x11d1,{0xaa,0x5e,0x00,0xc0,0x4f,0xa3,0x5b,0x82}}; #ifdef __cplusplus } #endif // // Define the store file prefix and extension // #define NTFS_STORE_FILE_PREFIX _T("\\NTFS_") #define NTFS_STORE_FILE_WILDCARD _T("*") #define NTFS_STORE_FILE_EXTENSION _T(".EML") #define NTFS_FAT_STREAM_FILE_EXTENSION_1ST _T(".STM") #define NTFS_FAT_STREAM_FILE_EXTENSION_LIVE _T(".STL") #define NTFS_STORE_FILE_PROPERTY_STREAM_1ST _T(":PROPERTIES") #define NTFS_STORE_FILE_PROPERTY_STREAM_LIVE _T(":PROPERTIES-LIVE") #define NTFS_STORE_BACKSLASH _T("\\") #define NTFS_QUEUE_DIRECTORY_SUFFIX _T("\\Queue") #define NTFS_DROP_DIRECTORY_SUFFIX _T("\\Drop") #define SMTP_MD_ID_BEGIN_RESERVED 0x00009000 #define MD_MAIL_QUEUE_DIR (SMTP_MD_ID_BEGIN_RESERVED+11 ) #define MD_MAIL_DROP_DIR (SMTP_MD_ID_BEGIN_RESERVED+18 ) ///////////////////////////////////////////////////////////////////////////// // CDriverUtils // // Define the registry path location in the registry // #define NTFS_STORE_DIRECTORY_REG_PATH _T("Software\\Microsoft\\Exchange\\StoreDriver\\Ntfs\\%u") #define NTFS_STORE_DIRECTORY_REG_NAME _T("StoreDir") // // Instantiate static // DWORD CDriverUtils::s_dwCounter = 0; CEventLogWrapper *CNtfsStoreDriver::g_pEventLog = NULL; CDriverUtils::CDriverUtils() { } CDriverUtils::~CDriverUtils() { } HRESULT CDriverUtils::LoadStoreDirectory( DWORD dwInstanceId, LPTSTR szStoreDirectory, DWORD *pdwLength ) { HKEY hKey = NULL; DWORD dwRes; DWORD dwType; TCHAR szStoreDirPath[MAX_PATH]; HRESULT hrRes = S_OK; _ASSERT(szStoreDirectory); _ASSERT(pdwLength); TraceFunctEnter("CDriverUtils::LoadStoreDirectory"); // Build up the registry path given instance ID wsprintf(szStoreDirPath, NTFS_STORE_DIRECTORY_REG_PATH, dwInstanceId); // Open the registry key dwRes = (DWORD)RegOpenKeyEx( HKEY_LOCAL_MACHINE, szStoreDirPath, 0, KEY_ALL_ACCESS, &hKey); if (dwRes != ERROR_SUCCESS) { hrRes = HRESULT_FROM_WIN32(dwRes); goto Cleanup; } // Adjust the buffer size for character type ... (*pdwLength) *= sizeof(TCHAR); dwRes = (DWORD)RegQueryValueEx( hKey, NTFS_STORE_DIRECTORY_REG_NAME, NULL, &dwType, (LPBYTE)szStoreDirectory, pdwLength); if (dwRes != ERROR_SUCCESS) { hrRes = HRESULT_FROM_WIN32(dwRes); ErrorTrace((LPARAM)0, "Failed to load store driver directory %u", dwRes); } else { hrRes = S_OK; DebugTrace((LPARAM)0, "Store directory is %s", szStoreDirectory); } Cleanup: if (hKey) RegCloseKey(hKey); TraceFunctLeave(); return(hrRes); } HRESULT CDriverUtils::GetStoreFileName( LPTSTR szStoreDirectory, LPTSTR szStoreFilename, DWORD *pdwLength ) { _ASSERT(szStoreDirectory); _ASSERT(szStoreFilename); _ASSERT(pdwLength); DWORD dwLength = *pdwLength; DWORD dwStrLen; FILETIME ftTime; dwStrLen = lstrlen(szStoreDirectory); if (dwLength <= dwStrLen) return(HRESULT_FROM_WIN32(ERROR_MORE_DATA)); lstrcpy(szStoreFilename, szStoreDirectory); dwLength -= dwStrLen; szStoreFilename += dwStrLen; *pdwLength = dwStrLen; GetSystemTimeAsFileTime(&ftTime); dwStrLen = lstrlen(NTFS_STORE_FILE_PREFIX) + lstrlen(NTFS_STORE_FILE_EXTENSION) + 26; if (dwLength <= dwStrLen) return(HRESULT_FROM_WIN32(ERROR_MORE_DATA)); wsprintf(szStoreFilename, "%s%08x%08x%08x%s", NTFS_STORE_FILE_PREFIX, ftTime.dwLowDateTime, ftTime.dwHighDateTime, InterlockedIncrement((PLONG)&s_dwCounter), NTFS_STORE_FILE_EXTENSION); *pdwLength += (dwStrLen + 1); return(S_OK); } HRESULT CDriverUtils::GetStoreFileFromPath( LPTSTR szStoreFilename, IMailMsgPropertyStream **ppStream, PFIO_CONTEXT *ppFIOContentFile, BOOL fCreate, BOOL fIsFAT, IMailMsgProperties *pMsg, GUID guidInstance ) { // OK, got a file, get the content handle and property stream HRESULT hrRes = S_OK; HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hStream = INVALID_HANDLE_VALUE; TCHAR szPropertyStream[MAX_PATH << 1]; BOOL fDeleteOnCleanup = FALSE; _ASSERT(fCreate || (guidInstance == GUID_NULL)); IMailMsgPropertyStream *pIStream = NULL; TraceFunctEnter("CDriverUtils::GetStoreFileFromPath"); if (ppFIOContentFile) { // Open the content ... hFile = CreateFile( szStoreFilename, GENERIC_READ | GENERIC_WRITE, // Read / Write FILE_SHARE_READ, // Shared read NULL, // Default security (fCreate)?CREATE_NEW:OPEN_EXISTING, // Create new or open existing file FILE_FLAG_OVERLAPPED | // Overlapped access FILE_FLAG_SEQUENTIAL_SCAN, // Seq scan //FILE_FLAG_WRITE_THROUGH, // Write through cache NULL); // No template if (hFile == INVALID_HANDLE_VALUE) goto Cleanup; } DebugTrace(0, "--- start ---"); if (ppStream) { DebugTrace((LPARAM)0, "Handling stream in %s", fIsFAT?"FAT":"NTFS"); BOOL fTryLiveStream = !fCreate; BOOL fNoLiveStream = FALSE; BOOL fLiveWasCorrupt = FALSE; do { // Open the alternate file stream lstrcpy(szPropertyStream, szStoreFilename); if (fTryLiveStream) { DebugTrace((LPARAM) 0, "TryingLive"); lstrcat(szPropertyStream, fIsFAT?NTFS_FAT_STREAM_FILE_EXTENSION_LIVE: NTFS_STORE_FILE_PROPERTY_STREAM_LIVE); } else { DebugTrace((LPARAM) 0, "Trying1st"); lstrcat(szPropertyStream, fIsFAT?NTFS_FAT_STREAM_FILE_EXTENSION_1ST: NTFS_STORE_FILE_PROPERTY_STREAM_1ST); } DebugTrace((LPARAM) 0, "File: %s", szPropertyStream); hStream = CreateFile( szPropertyStream, GENERIC_READ | GENERIC_WRITE, // Read / Write FILE_SHARE_READ, // No sharing NULL, // Default security (fCreate)? CREATE_NEW: // Create new or OPEN_EXISTING, // Open existing file FILE_FLAG_SEQUENTIAL_SCAN, // Seq scan //FILE_FLAG_WRITE_THROUGH, // Write through cache NULL); // No template if (hStream == INVALID_HANDLE_VALUE) { DebugTrace((LPARAM) 0, "Got INVALID_HANDLE_VALUE\n"); if (fTryLiveStream && GetLastError() == ERROR_FILE_NOT_FOUND) { DebugTrace((LPARAM) 0, "livestream and FILE_NOT_FOUND\n"); hrRes = S_INVALIDSTREAM; fNoLiveStream = TRUE; } else if (GetLastError() == ERROR_FILE_NOT_FOUND) { DebugTrace((LPARAM) 0, "no primary stream either\n"); hrRes = S_NO_FIRST_COMMIT; } else { DebugTrace((LPARAM) 0, "Returning CreateFile error %lu\n", GetLastError()); hrRes = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; } } else { hrRes = CoCreateInstance( CLSID_NtfsPropertyStream, NULL, CLSCTX_INPROC_SERVER, IID_IMailMsgPropertyStream, (LPVOID *)&pIStream); if (FAILED(hrRes)) goto Cleanup; hrRes = ((CNtfsPropertyStream *)pIStream)->SetHandle(hStream, guidInstance, fTryLiveStream, pMsg); if (FAILED(hrRes)) { if (fCreate) fDeleteOnCleanup = TRUE; goto Cleanup; } } if (hrRes == S_INVALIDSTREAM || hrRes == S_NO_FIRST_COMMIT) { if (hrRes == S_INVALIDSTREAM) { DebugTrace((LPARAM) 0, "SetHandle returned S_INVALIDSTREAM\n"); } else { DebugTrace((LPARAM) 0, "SetHandle returned S_NO_FIRST_COMMIT\n"); } if (fTryLiveStream) { // if we were working with the live stream then retry with // the 1st commited stream fTryLiveStream = FALSE; fLiveWasCorrupt = !fNoLiveStream; DebugTrace((LPARAM) 0, "Trying regular stream\n"); if (pIStream) { pIStream->Release(); pIStream = NULL; } if (hrRes == S_NO_FIRST_COMMIT) hrRes = S_INVALIDSTREAM; } else { // the 1st committed stream was invalid. this can // only occur when the message was not acked. // // if the live stream existed and this one is invalid // then something is weird, so we go down the eventlog // path (returning S_OK). S_OK works because currently // pStream->m_fStreamHasHeader is set to 0. Either // the mailmsg signature check will fail or mailmsg // won't be able to read the entire master header. // either way will cause the message to be ignored and // eventlog'd // // CEnumNtfsMessages::Next will delete the message // for us if (hrRes == S_INVALIDSTREAM) { if (fLiveWasCorrupt && !fNoLiveStream) { hrRes = S_OK; DebugTrace((LPARAM) 0, "Returning S_OK because there was no live stream\n"); } else { hrRes = S_NO_FIRST_COMMIT; DebugTrace((LPARAM) 0, "Returning S_NO_FIRST_COMMIT\n"); } } else { DebugTrace((LPARAM) 0, "Returning S_NO_FIRST_COMMIT\n"); } } } else { DebugTrace((LPARAM) 0, "SetHandle returned other error %x\n", hrRes); } _ASSERT(SUCCEEDED(hrRes)); if (FAILED(hrRes)) goto Cleanup; } while (hrRes == S_INVALIDSTREAM); } // Fill in the return values if (ppStream) { *ppStream = pIStream; } if (ppFIOContentFile) { *ppFIOContentFile = AssociateFile(hFile); if (*ppFIOContentFile == NULL) { goto Cleanup; } } TraceFunctLeave(); return(hrRes); Cleanup: if (hrRes == S_OK) hrRes = HRESULT_FROM_WIN32(GetLastError()); if (SUCCEEDED(hrRes)) hrRes = E_FAIL; if (hStream != INVALID_HANDLE_VALUE) { CloseHandle(hStream); } if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); } if (fDeleteOnCleanup) { // this only happens at file creation time. There is no // live file to worry about. The below code is too simplistic // if we do have to delete a live stream. _ASSERT(fCreate); DeleteFile(szStoreFilename); DeleteFile(szPropertyStream); } TraceFunctLeave(); return(hrRes); } HRESULT CDriverUtils::SetMessageContext( IMailMsgProperties *pMsg, LPBYTE pbContext, DWORD dwLength ) { HRESULT hrRes = S_OK; BYTE pbData[(MAX_PATH * 2) + sizeof(CLSID)]; _ASSERT(pMsg); if (dwLength > (MAX_PATH * 2)) return(E_INVALIDARG); MoveMemory(pbData, &CLSID_NtfsStoreDriver, sizeof(CLSID)); MoveMemory(pbData + sizeof(CLSID), pbContext, dwLength); dwLength += sizeof(CLSID); hrRes = pMsg->PutProperty( IMMPID_MPV_STORE_DRIVER_HANDLE, dwLength, pbData); // make S_FALSE return S_OK if (SUCCEEDED(hrRes)) hrRes = S_OK; return(hrRes); } HRESULT CDriverUtils::GetMessageContext( IMailMsgProperties *pMsg, LPBYTE pbContext, DWORD *pdwLength ) { HRESULT hrRes = S_OK; DWORD dwLength; _ASSERT(pMsg); _ASSERT(pbContext); _ASSERT(pdwLength); dwLength = *pdwLength; hrRes = pMsg->GetProperty( IMMPID_MPV_STORE_DRIVER_HANDLE, dwLength, pdwLength, pbContext); if (SUCCEEDED(hrRes)) { dwLength = *pdwLength; // Verify length and CLSID if ((dwLength < sizeof(CLSID)) || (*(CLSID *)pbContext != CLSID_NtfsStoreDriver)) hrRes = NTE_BAD_SIGNATURE; else { // Copy the context info dwLength -= sizeof(CLSID); MoveMemory(pbContext, pbContext + sizeof(CLSID), dwLength); *pdwLength = dwLength; } } return(hrRes); } HRESULT CDriverUtils::IsStoreDirectoryFat( LPTSTR szStoreDirectory, BOOL *pfIsFAT ) { HRESULT hrRes = S_OK; TCHAR szDisk[MAX_PATH]; TCHAR szFileSystem[MAX_PATH]; DWORD lSerial, lMaxLen, lFlags; DWORD dwLength; UINT uiErrorMode; _ASSERT(szStoreDirectory); _ASSERT(pfIsFAT); TraceFunctEnter("CDriverUtils::IsStoreDirectoryFat"); // OK, find the root drive, make sure we handle UNC names dwLength = lstrlen(szStoreDirectory); if (dwLength < 2) return(E_INVALIDARG); szDisk[0] = szStoreDirectory[0]; szDisk[1] = szStoreDirectory[1]; if ((szDisk[0] == _T('\\')) && (szDisk[1] == _T('\\'))) { DWORD dwCount = 0; LPTSTR pTemp = szDisk + 2; DebugTrace((LPARAM)0, "UNC Name: %s", szStoreDirectory); // Handle UNC szStoreDirectory += 2; while (*szStoreDirectory) if (*pTemp = *szStoreDirectory++) if (*pTemp++ == _T('\\')) { dwCount++; if (dwCount == 2) break; } if (dwCount == 2) *pTemp = _T('\0'); else if (dwCount == 1) { *pTemp++ = _T('\\'); *pTemp = _T('\0'); } else return(E_INVALIDARG); } else { DebugTrace((LPARAM)0, "Local drive: %s", szStoreDirectory); // Local path if (!_istalpha(szDisk[0]) || (szDisk[1] != _T(':'))) return(E_INVALIDARG); szDisk[2] = _T('\\'); szDisk[3] = _T('\0'); } // Call the system to determine what file system we have here, // we set the error mode here to avoid unsightly pop-ups. uiErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); if (GetVolumeInformation( szDisk, NULL, 0, &lSerial, &lMaxLen, &lFlags, szFileSystem, MAX_PATH)) { DebugTrace((LPARAM)0, "File system is: %s", szFileSystem); if (!lstrcmpi(szFileSystem, _T("NTFS"))) *pfIsFAT = FALSE; else if (!lstrcmpi(szFileSystem, _T("FAT"))) *pfIsFAT = TRUE; else if (!lstrcmpi(szFileSystem, _T("FAT32"))) *pfIsFAT = TRUE; else hrRes = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); } else hrRes = HRESULT_FROM_WIN32(GetLastError()); SetErrorMode(uiErrorMode); TraceFunctLeave(); return(hrRes); } ///////////////////////////////////////////////////////////////////////////// // CNtfsStoreDriver // // // Instantiate static // DWORD CNtfsStoreDriver::sm_cCurrentInstances = 0; CRITICAL_SECTION CNtfsStoreDriver::sm_csLockInstList; LIST_ENTRY CNtfsStoreDriver::sm_ListHead; CNtfsStoreDriver::CNtfsStoreDriver() { m_fInitialized = FALSE; m_fIsShuttingDown = FALSE; *m_szQueueDirectory = _T('\0'); m_pSMTPServer = NULL; m_lRefCount = 0; m_fIsFAT = TRUE; // Assume we ARE on a fat partition until we discover otherwise UuidCreate(&m_guidInstance); m_ppoi = NULL; m_InstLEntry.Flink = NULL; m_InstLEntry.Blink = NULL; } CNtfsStoreDriver::~CNtfsStoreDriver() { CNtfsStoreDriver::LockList(); if (m_InstLEntry.Flink != NULL) { _ASSERT(m_InstLEntry.Blink != NULL); HRESULT hr = CNtfsStoreDriver::RemoveSinkInstance( (IUnknown *)(ISMTPStoreDriver *)this); _ASSERT(SUCCEEDED(hr)); } _ASSERT(m_InstLEntry.Flink == NULL); _ASSERT(m_InstLEntry.Blink == NULL); CNtfsStoreDriver::UnLockList(); } DECLARE_STD_IUNKNOWN_METHODS(NtfsStoreDriver, IMailMsgStoreDriver) HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::AllocMessage( IMailMsgProperties *pMsg, DWORD dwFlags, IMailMsgPropertyStream **ppStream, PFIO_CONTEXT *phContentFile, IMailMsgNotify *pNotify ) { HRESULT hrRes = S_OK; TCHAR szStoreFileName[MAX_PATH << 1]; DWORD dwLength; _ASSERT(pMsg); _ASSERT(ppStream); _ASSERT(phContentFile); TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::AllocMessage"); if (!m_fInitialized) return(E_FAIL); if (m_fIsShuttingDown) { DebugTrace((LPARAM)this, "Failing because shutting down"); return(HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); } if (!pMsg || !ppStream || !phContentFile) return(E_POINTER); do { // Get a file name dwLength = sizeof(szStoreFileName); hrRes = CDriverUtils::GetStoreFileName( m_szQueueDirectory, szStoreFileName, &dwLength); if (FAILED(hrRes)) return(hrRes); // Create the file hrRes = CDriverUtils::GetStoreFileFromPath( szStoreFileName, ppStream, phContentFile, TRUE, m_fIsFAT, pMsg, m_guidInstance ); } while (hrRes == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS)); // // GetStoreFileFromPath can return S_NO_FIRST_COMMIT and no handle // treat this as an error. // if (S_NO_FIRST_COMMIT == hrRes) hrRes = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); if (FAILED(hrRes)) return(hrRes); ((CNtfsPropertyStream *)*ppStream)->SetInfo(this); // OK, save the file name as a store driver context hrRes = CDriverUtils::SetMessageContext( pMsg, (LPBYTE)szStoreFileName, dwLength * sizeof(TCHAR)); if (FAILED(hrRes)) { // Release all file resources ReleaseContext(*phContentFile); DecCtr(m_ppoi, NTFSDRV_MSG_BODIES_OPEN); _VERIFY((*ppStream)->Release() == 0); } else { // Update counters IncCtr(m_ppoi, NTFSDRV_QUEUE_LENGTH); IncCtr(m_ppoi, NTFSDRV_NUM_ALLOCS); IncCtr(m_ppoi, NTFSDRV_MSG_BODIES_OPEN); } TraceFunctLeave(); return(hrRes); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::EnumMessages( IMailMsgEnumMessages **ppEnum ) { HRESULT hrRes = S_OK; TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::EnumMessages"); if (!m_fInitialized) return(E_FAIL); if (m_fIsShuttingDown) { DebugTrace((LPARAM)this, "Failing because shutting down"); return(HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); } if (!ppEnum) return E_POINTER; hrRes = CoCreateInstance( CLSID_NtfsEnumMessages, NULL, CLSCTX_INPROC_SERVER, IID_IMailMsgEnumMessages, (LPVOID *)ppEnum); if (SUCCEEDED(hrRes)) { ((CNtfsEnumMessages *)(*ppEnum))->SetInfo(this); hrRes = ((CNtfsEnumMessages *)(*ppEnum))->SetStoreDirectory( m_szQueueDirectory, m_fIsFAT); } TraceFunctLeave(); return(hrRes); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::ReOpen( IMailMsgProperties *pMsg, IMailMsgPropertyStream **ppStream, PFIO_CONTEXT *phContentFile, IMailMsgNotify *pNotify ) { HRESULT hrRes = S_OK; TCHAR szStoreFileName[MAX_PATH * 2]; DWORD dwLength = MAX_PATH * 2; TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::ReOpen"); if (!m_fInitialized) return(E_FAIL); if (!pMsg) return E_POINTER; if (m_fIsShuttingDown) { // We allow reopen to occur when we are pending shutdown. // This gives a chance to reopen the streams and commit any // unchanged data DebugTrace((LPARAM)this, "ReOpening while shutting down ..."); } // Now we have to load the file name from the context dwLength *= sizeof(TCHAR); hrRes = CDriverUtils::GetMessageContext( pMsg, (LPBYTE)szStoreFileName, &dwLength); if (FAILED(hrRes)) return(hrRes); // Got the file name, just open the files hrRes = CDriverUtils::GetStoreFileFromPath( szStoreFileName, ppStream, phContentFile, FALSE, m_fIsFAT, pMsg); // // GetStoreFileFromPath can return S_NO_FIRST_COMMIT and no handle // treat this as an error. // if (S_NO_FIRST_COMMIT == hrRes) hrRes = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); if (SUCCEEDED(hrRes) && ppStream) { ((CNtfsPropertyStream *)*ppStream)->SetInfo(this); } if (SUCCEEDED(hrRes)) { if (phContentFile) IncCtr(m_ppoi, NTFSDRV_MSG_BODIES_OPEN); } TraceFunctLeave(); return(hrRes); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::ReAllocMessage( IMailMsgProperties *pOriginalMsg, IMailMsgProperties *pNewMsg, IMailMsgPropertyStream **ppStream, PFIO_CONTEXT *phContentFile, IMailMsgNotify *pNotify ) { HRESULT hrRes = S_OK; TCHAR szStoreFileName[MAX_PATH * 2]; DWORD dwLength = MAX_PATH * 2; IMailMsgPropertyStream *pStream; PFIO_CONTEXT hContentFile; _ASSERT(pOriginalMsg); _ASSERT(pNewMsg); _ASSERT(ppStream); _ASSERT(phContentFile); TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::ReAllocMessage"); if (!m_fInitialized) return(E_FAIL); if (m_fIsShuttingDown) { DebugTrace((LPARAM)this, "Failing because shutting down"); return(HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); } // Now we have to load the file name from the context dwLength *= sizeof(TCHAR); hrRes = CDriverUtils::GetMessageContext( pOriginalMsg, (LPBYTE)szStoreFileName, &dwLength); if (FAILED(hrRes)) return(hrRes); // Allocate a new message hrRes = AllocMessage( pNewMsg, 0, &pStream, &hContentFile, NULL); if (FAILED(hrRes)) return(hrRes); // Copy the content from original message to new message hrRes = pOriginalMsg->CopyContentToFile( hContentFile, NULL); if (SUCCEEDED(hrRes)) { *ppStream = pStream; *phContentFile = hContentFile; } else { HRESULT myRes; // Delete on failure pStream->Release(); ReleaseContext(hContentFile); DecCtr(m_ppoi, NTFSDRV_MSG_BODIES_OPEN); myRes = Delete(pNewMsg, NULL); _ASSERT(myRes); } TraceFunctLeave(); return(hrRes); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::Delete( IMailMsgProperties *pMsg, IMailMsgNotify *pNotify ) { HRESULT hrRes = S_OK; TCHAR szStoreFileName[MAX_PATH * 2]; TCHAR szStoreFileNameStl[MAX_PATH * 2]; DWORD dwLength = MAX_PATH * 2; _ASSERT(pMsg); TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::Delete"); if (!m_fInitialized) return(E_FAIL); if (!pMsg) return E_POINTER; if (m_fIsShuttingDown) { // We would allow deletes during shutdown DebugTrace((LPARAM)this, "Deleteing while shutting down ..."); } // Now we have to load the file name from the context dwLength *= sizeof(TCHAR); hrRes = CDriverUtils::GetMessageContext( pMsg, (LPBYTE)szStoreFileName, &dwLength); if (FAILED(hrRes)) return(hrRes); // Got the file name, delete the file // For FAT, we know we can force delete the stream, but we are not // so sure about the content file. So we always try to delete the // content file first, if it succeeds, we delete the stream file. // If it fails, we will keep the stream intact so we can at least // use the stream to debug what's going on. if (!DeleteFile(szStoreFileName)) { DWORD cRetries = 0; hrRes = HRESULT_FROM_WIN32(GetLastError()); // in hotmail we've found that delete sometimes fails with // a sharing violation even though we've closed all handles. // in this case we try again for (cRetries = 0; hrRes == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION) && cRetries < 5; cRetries++) { Sleep(0); if (DeleteFile(szStoreFileName)) { hrRes = S_OK; } else { hrRes = HRESULT_FROM_WIN32(GetLastError()); } } _ASSERT(SUCCEEDED(hrRes)); ErrorTrace((LPARAM) this, "DeleteFile(%s) failed with %lu, cRetries=%lu, hrRes=%x", szStoreFileName, GetLastError(), cRetries, hrRes); } else if (m_fIsFAT) { // Wiped the content, now wipe the stream DWORD cRetries = 0; lstrcpy(szStoreFileNameStl, szStoreFileName); lstrcat(szStoreFileName, NTFS_FAT_STREAM_FILE_EXTENSION_1ST); if (!DeleteFile(szStoreFileName)) { hrRes = HRESULT_FROM_WIN32(GetLastError()); for (cRetries = 0; hrRes == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION) && cRetries < 5; cRetries++) { Sleep(0); if (DeleteFile(szStoreFileName)) { hrRes = S_OK; } else { hrRes = HRESULT_FROM_WIN32(GetLastError()); } } _ASSERT(SUCCEEDED(hrRes)); ErrorTrace((LPARAM) this, "DeleteFile(%s) failed with %lu, cRetries=%lu, hrRes=%x", szStoreFileName, GetLastError(), cRetries, hrRes); } lstrcat(szStoreFileNameStl, NTFS_FAT_STREAM_FILE_EXTENSION_LIVE); // this can fail, since we don't always have a live stream DeleteFile(szStoreFileNameStl); } if (SUCCEEDED(hrRes)) { DecCtr(m_ppoi, NTFSDRV_QUEUE_LENGTH); IncCtr(m_ppoi, NTFSDRV_NUM_DELETES); } TraceFunctLeave(); return(hrRes); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::CloseContentFile( IMailMsgProperties *pMsg, PFIO_CONTEXT hContentFile ) { HRESULT hrRes = S_OK; _ASSERT(pMsg); _ASSERT(hContentFile!=NULL); TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::CloseContentFile"); if (!m_fInitialized) return (E_FAIL); if (m_fIsShuttingDown) { // We would allow content files to be closed during shutdown DebugTrace((LPARAM)this, "Closing content file while shutting down ..."); } #ifdef DEBUG TCHAR szStoreFileName[MAX_PATH * 2]; DWORD dwLength = MAX_PATH * 2; dwLength *= sizeof(TCHAR); _ASSERT(SUCCEEDED(CDriverUtils::GetMessageContext(pMsg,(LPBYTE)szStoreFileName,&dwLength))); #endif ReleaseContext(hContentFile); DecCtr(m_ppoi, NTFSDRV_MSG_BODIES_OPEN); TraceFunctLeave(); return (hrRes); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::Init( DWORD dwInstance, IUnknown *pBinding, IUnknown *pServer, DWORD dwReason, IUnknown **ppStoreDriver ) { HRESULT hrRes = S_OK; DWORD dwLength = sizeof(m_szQueueDirectory); REFIID iidStoreDriverBinding = GUID_NULL; IUnknown * pTempStoreDriver = NULL; TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::Init"); // We will treat all dwReasons as equal ... //NK** : We need to treat binding change differently in order to set the correct //enumeration status - we do it before returning from here if (m_fInitialized) return(HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED)); if (m_fIsShuttingDown) return(HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); // Try to load the store directory DebugTrace((LPARAM)this, "Initializing instance %u", dwInstance); //Grab a lock for the duration of this function // CNtfsStoreDriver::LockList(); pTempStoreDriver = CNtfsStoreDriver::LookupSinkInstance(dwInstance, iidStoreDriverBinding); if(pTempStoreDriver) { //Found a valid store driver pTempStoreDriver->AddRef(); *ppStoreDriver = (IUnknown *)(ISMTPStoreDriver *)pTempStoreDriver; CNtfsStoreDriver::UnLockList(); return S_OK; } DWORD BuffSize = sizeof(m_szQueueDirectory); // Get the SMTP server interface m_pSMTPServer = NULL; if (pServer && !SUCCEEDED(pServer->QueryInterface(IID_ISMTPServer, (LPVOID *)&m_pSMTPServer))) m_pSMTPServer = NULL; // Read the metabase if we have a server, otherwise read from the registry if(m_pSMTPServer) { hrRes = m_pSMTPServer->ReadMetabaseString(MD_MAIL_QUEUE_DIR, (unsigned char *) m_szQueueDirectory, &BuffSize, FALSE); if (FAILED(hrRes)) { //retry once, then fall through ErrorTrace((LPARAM)this, "failed to read queue directory from metabase -%x", hrRes); BuffSize = sizeof(m_szQueueDirectory); hrRes = m_pSMTPServer->ReadMetabaseString(MD_MAIL_QUEUE_DIR, (unsigned char *) m_szQueueDirectory, &BuffSize, FALSE); } } else { DebugTrace((LPARAM)this, "NTFSDRV Getting config from registry"); hrRes = CDriverUtils::LoadStoreDirectory( dwInstance, m_szQueueDirectory, &dwLength); if (SUCCEEDED(hrRes)) { // Deduce the queue directory lstrcat(m_szQueueDirectory, NTFS_QUEUE_DIRECTORY_SUFFIX); } } // return failure code if we failed to get a queue directory, to avoid message loss. if (FAILED(hrRes)) { ErrorTrace((LPARAM)this, "CNtfsStoreDriver::Init failed -%x", hrRes); CNtfsStoreDriver::UnLockList(); return hrRes; } // Detect the file system hrRes = CDriverUtils::IsStoreDirectoryFat( m_szQueueDirectory, &m_fIsFAT); m_fInitialized = TRUE; m_fIsShuttingDown = FALSE; m_dwInstance = dwInstance; m_lRefCount = 0; //NK** MAke binding GUID a member and start storing it DebugTrace((LPARAM)this, "Queue directory: %s", m_szQueueDirectory); // Return a store driver only if we succeeded initialization if (ppStoreDriver) { *ppStoreDriver = (IUnknown *)(ISMTPStoreDriver *)this; AddRef(); // if we are the first instance then initialize perfmon if (IsListEmpty(&sm_ListHead)) { InitializePerformanceStatistics(); } CNtfsStoreDriver::InsertSinkInstance(&m_InstLEntry); } WCHAR wszPerfInstanceName[MAX_INSTANCE_NAME]; _snwprintf(wszPerfInstanceName, MAX_INSTANCE_NAME, L"SMTP #%u", dwInstance); wszPerfInstanceName[MAX_INSTANCE_NAME-1] = L'\0'; m_ppoi = CreatePerfObjInstance(wszPerfInstanceName); TraceFunctLeaveEx((LPARAM)this); // Always return S_OK CNtfsStoreDriver::UnLockList(); return(S_OK); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::PrepareForShutdown( DWORD dwReason ) { TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::PrepareForShutdown"); m_fIsShuttingDown = TRUE; TraceFunctLeaveEx((LPARAM)this); return(S_OK); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::Shutdown( DWORD dwReason ) { DWORD dwWaitTime = 0; HRESULT hr = S_OK; TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::Shutdown"); m_fIsShuttingDown = TRUE; _ASSERT(m_lRefCount == 0); #if 0 // BUG - 80960 // Now wait for all our references to come back while (m_lRefCount) { _ASSERT(m_lRefCount >= 0); Sleep(100); dwWaitTime += 100; DebugTrace((LPARAM)this, "[%u ms] Waiting for objects to be released (%u outstanding)", dwWaitTime, m_lRefCount); } #endif if(m_pSMTPServer) { m_pSMTPServer->Release(); m_pSMTPServer = NULL; } if (m_ppoi) { delete m_ppoi; m_ppoi = NULL; } CNtfsStoreDriver::LockList(); hr = CNtfsStoreDriver::RemoveSinkInstance((IUnknown *)(ISMTPStoreDriver *)this); // if we are the last instance then shutdown perfmon if (IsListEmpty(&sm_ListHead)) { ShutdownPerformanceStatistics(); } CNtfsStoreDriver::UnLockList(); if(FAILED(hr)) { //We failed to remove this sink from the global list _ASSERT(0); } m_fInitialized = FALSE; m_fIsShuttingDown = FALSE; TraceFunctLeaveEx((LPARAM)this); return(S_OK); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::LocalDelivery( IMailMsgProperties *pMsg, DWORD dwRecipCount, DWORD *pdwRecipIndexes, IMailMsgNotify *pNotify ) { HRESULT hrRes = S_OK; TCHAR szStoreFileName[MAX_PATH * 2]; TCHAR szCopyFileName[MAX_PATH * 2]; LPTSTR pszFileName; DWORD dwLength = MAX_PATH * 2; _ASSERT(pMsg); TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::LocalDelivery"); TraceFunctLeaveEx((LPARAM)this); return (hrRes); } static void LogEventCorruptMessage(CEventLogWrapper *pEventLog, IMailMsgProperties *pMsg, char *pszQueueDirectory, HRESULT hrLog) { HRESULT hr; char szMessageFile[MAX_PATH]; DWORD dwLength = sizeof(szMessageFile); const char *rgszSubstrings[] = { szMessageFile, pszQueueDirectory }; hr = CDriverUtils::GetMessageContext(pMsg, (LPBYTE) szMessageFile, &dwLength); if (FAILED(hr)) { strcpy(szMessageFile, ""); } pEventLog->LogEvent(NTFSDRV_INVALID_FILE_IN_QUEUE, 2, rgszSubstrings, EVENTLOG_WARNING_TYPE, hrLog, LOGEVENT_DEBUGLEVEL_MEDIUM, szMessageFile, LOGEVENT_FLAG_ALWAYS); } static void DeleteNeverAckdMessage(CEventLogWrapper *pEventLog, IMailMsgProperties *pMsg, char *pszQueueDirectory, HRESULT hrLog, BOOL fIsFAT) { HRESULT hr; char szMessageFile[MAX_PATH+50]; char szMessageFileSTL[MAX_PATH+50]; DWORD dwLength = MAX_PATH; TraceFunctEnter("DeleteNeverAckdMessage"); hr = CDriverUtils::GetMessageContext(pMsg, (LPBYTE) szMessageFile, &dwLength); if (FAILED(hr)) { _ASSERT(FALSE && "GetMessageContext failed"); return; } DebugTrace((LPARAM) 0, "Deleting: %s\n", szMessageFile); DeleteFile(szMessageFile); if (fIsFAT) { // Wiped the content, now wipe the stream lstrcpy(szMessageFileSTL, szMessageFile); lstrcat(szMessageFile, NTFS_FAT_STREAM_FILE_EXTENSION_1ST); DeleteFile(szMessageFile); lstrcat(szMessageFileSTL, NTFS_FAT_STREAM_FILE_EXTENSION_LIVE); // this can fail, since we don't always have a live stream DeleteFile(szMessageFileSTL); } } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::EnumerateAndSubmitMessages( IMailMsgNotify *pNotify ) { HRESULT hrRes = S_OK; IMailMsgEnumMessages *pEnum = NULL; TraceFunctEnterEx((LPARAM)this, "CNtfsStoreDriver::EnumerateAndSubmitMessages"); if (!m_fInitialized) return (E_FAIL); if (m_fIsShuttingDown) goto Shutdown; // Assert we got all the pieces ... if (!m_pSMTPServer) return S_FALSE; // Now, get an enumerator from our peer IMailMsgStoreDriver and // start enumerating away ... hrRes = EnumMessages(&pEnum); if (SUCCEEDED(hrRes)) { IMailMsgProperties *pMsg = NULL; IMailMsgPropertyStream *pStream = NULL; PFIO_CONTEXT hContentFile = NULL; do { // Check for shut down if (m_fIsShuttingDown) goto Shutdown; // Create an instance of the message object, note // we reuse messages from a failed attempt if (!pMsg) { hrRes = CoCreateInstance( CLSID_MsgImp, NULL, CLSCTX_INPROC_SERVER, IID_IMailMsgProperties, (LPVOID *)&pMsg); // Next, check if we are over the inbound cutoff limit. If so, we will release the message // and not proceed. if (SUCCEEDED(hrRes)) { DWORD dwCreationFlags; hrRes = pMsg->GetDWORD( IMMPID_MPV_MESSAGE_CREATION_FLAGS, &dwCreationFlags); if (FAILED(hrRes) || (dwCreationFlags & MPV_INBOUND_CUTOFF_EXCEEDED)) { // If we fail to get this property of if the inbound cutoff // exceeded flag is set, discard the message and return failure if (SUCCEEDED(hrRes)) { DebugTrace((LPARAM)this, "Failing because inbound cutoff reached"); hrRes = E_OUTOFMEMORY; } pMsg->Release(); pMsg = NULL; } } // Now if we are out of memory, we would probably // keep failing, so lets just return and get on with // delivery if (!SUCCEEDED(hrRes)) { break; } } // Get the next message hrRes = pEnum->Next( pMsg, &pStream, &hContentFile, NULL); // Next() cleans up its own mess if it fails if (SUCCEEDED(hrRes)) { DWORD dwStreamSize = 0; IncCtr(m_ppoi, NTFSDRV_MSG_BODIES_OPEN); DebugTrace((LPARAM) this, "Next returned success\n"); // We delete streams which are too short to contain // a master header hrRes = pStream->GetSize(pMsg, &dwStreamSize, NULL); DebugTrace((LPARAM) this, "GetSize returned %x, %x\n", dwStreamSize, hrRes); if (!SUCCEEDED(hrRes) || dwStreamSize < 1024) { pStream->Release(); ReleaseContext(hContentFile); DeleteNeverAckdMessage(g_pEventLog, pMsg, m_szQueueDirectory, hrRes, m_fIsFAT); DecCtr(m_ppoi, NTFSDRV_MSG_BODIES_OPEN); continue; } DebugTrace((LPARAM) this, "Submitting to mailmsg\n"); // Submit the message, this call will actually do the // bind to the store driver if (m_fIsShuttingDown) hrRes = E_FAIL; else { IMailMsgBind *pBind = NULL; // Bind and submit hrRes = pMsg->QueryInterface( IID_IMailMsgBind, (LPVOID *)&pBind); if (SUCCEEDED(hrRes)) { hrRes = pBind->BindToStore( pStream, (IMailMsgStoreDriver *)this, hContentFile); pBind->Release(); if (SUCCEEDED(hrRes)) { // Relinquish the extra refcount added by bind(2 -> 1) pStream->Release(); hrRes = m_pSMTPServer->SubmitMessage( pMsg); if (!SUCCEEDED(hrRes)) { // Relinquish the usage count added by bind (1 -> 0) IMailMsgQueueMgmt *pMgmt = NULL; hrRes = pMsg->QueryInterface( IID_IMailMsgQueueMgmt, (LPVOID *)&pMgmt); if (SUCCEEDED(hrRes)) { pMgmt->ReleaseUsage(); pMgmt->Release(); } else { _ASSERT(hrRes == S_OK); } } else { // update counter IncCtr(m_ppoi, NTFSDRV_QUEUE_LENGTH); IncCtr(m_ppoi, NTFSDRV_NUM_ENUMERATED); } // Whether or not the message is submitted, release our // refcount pMsg->Release(); pMsg = NULL; } } else { _ASSERT(hrRes == S_OK); } } if (!SUCCEEDED(hrRes)) { // Clean up the mess ... pStream->Release(); ReleaseContext(hContentFile); DecCtr(m_ppoi, NTFSDRV_MSG_BODIES_OPEN); if (m_fIsShuttingDown) goto Shutdown; // // log an event about the message being corrupt // LogEventCorruptMessage(g_pEventLog, pMsg, m_szQueueDirectory, hrRes); // We might want to discard this message and go on // with other messages. We will re-use this message // object upstream. hrRes = S_OK; } else { // Make sure we will not accidentally delete or // reuse the message pMsg = NULL; } } } while (SUCCEEDED(hrRes)); // We distinguish the successful end of enumeration if (hrRes == HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES)) hrRes = S_OK; // Release the enumerator, of course ... pEnum->Release(); // Release any residual messages if (pMsg) pMsg->Release(); } TraceFunctLeaveEx((LPARAM)this); return (S_OK); Shutdown: // Release the enumerator, of course ... if(pEnum) pEnum->Release(); TraceFunctLeaveEx((LPARAM)this); return (HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::IsCacheable() { // signal that only one instance of the sink should be created return (S_OK); } HRESULT STDMETHODCALLTYPE CNtfsStoreDriver::ValidateMessageContext( BYTE *pbContext, DWORD cbContext) { return E_NOTIMPL; } ///////////////////////////////////////////////////////////////////////////// // CMailMsgEnumMessages // CNtfsEnumMessages::CNtfsEnumMessages() { *m_szEnumPath = _T('\0'); m_hEnum = INVALID_HANDLE_VALUE; m_pDriver = NULL; m_fIsFAT = TRUE; // Assume we are FAT until we discover otherwise } CNtfsEnumMessages::~CNtfsEnumMessages() { *m_szEnumPath = _T('\0'); if (m_hEnum != INVALID_HANDLE_VALUE) { if (!FindClose(m_hEnum)) { _ASSERT(FALSE); } m_hEnum = INVALID_HANDLE_VALUE; } if (m_pDriver) m_pDriver->ReleaseUsage(); } HRESULT CNtfsEnumMessages::SetStoreDirectory( LPTSTR szStoreDirectory, BOOL fIsFAT ) { if (!szStoreDirectory) return(E_FAIL); // Mark the file system m_fIsFAT = fIsFAT; if (lstrlen(szStoreDirectory) >= MAX_PATH) { _ASSERT(FALSE); return(E_FAIL); } lstrcpy(m_szEnumPath, szStoreDirectory); lstrcat(m_szEnumPath, NTFS_STORE_FILE_PREFIX); lstrcat(m_szEnumPath, NTFS_STORE_FILE_WILDCARD); lstrcat(m_szEnumPath, NTFS_STORE_FILE_EXTENSION); lstrcpy(m_szStorePath, szStoreDirectory); lstrcat(m_szStorePath, NTFS_STORE_BACKSLASH); return(S_OK); } DECLARE_STD_IUNKNOWN_METHODS(NtfsEnumMessages, IMailMsgEnumMessages) HRESULT STDMETHODCALLTYPE CNtfsEnumMessages::Next( IMailMsgProperties *pMsg, IMailMsgPropertyStream **ppStream, PFIO_CONTEXT *phContentFile, IMailMsgNotify *pNotify ) { HRESULT hrRes = S_OK; TCHAR szFQPN[MAX_PATH * 2]; if (!pMsg || !ppStream || !phContentFile) return E_POINTER; BOOL fFoundFile = FALSE; TraceFunctEnter("CNtfsEnumMessages::Next"); while (!fFoundFile) { _ASSERT(m_pDriver); if (m_pDriver->IsShuttingDown()) return(HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); if (m_hEnum == INVALID_HANDLE_VALUE) { m_hEnum = FindFirstFile(m_szEnumPath, &m_Data); if (m_hEnum == INVALID_HANDLE_VALUE) { return(HRESULT_FROM_WIN32(GetLastError())); } } else { if (!FindNextFile(m_hEnum, &m_Data)) { return(HRESULT_FROM_WIN32(GetLastError())); } } // Digest the data ... while (m_Data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // Make sure it's not a directory if (!FindNextFile(m_hEnum, &m_Data)) { return(HRESULT_FROM_WIN32(GetLastError())); } } // OK, got a file, get the content handle and property stream lstrcpy(szFQPN, m_szStorePath); lstrcat(szFQPN, m_Data.cFileName); hrRes = CDriverUtils::GetStoreFileFromPath( szFQPN, ppStream, phContentFile, FALSE, m_fIsFAT, pMsg); if (hrRes == S_NO_FIRST_COMMIT) { DebugTrace((LPARAM) this, "Got no first commit, doing a delete\n"); // this means that we never ACK'd the message. silently delete it if (*ppStream) (*ppStream)->Release(); ReleaseContext(*phContentFile); DeleteFile(szFQPN); if (m_fIsFAT) { TCHAR szFileName[MAX_PATH * 2]; lstrcpy(szFileName, szFQPN); lstrcat(szFileName, NTFS_FAT_STREAM_FILE_EXTENSION_1ST); DeleteFile(szFileName); lstrcpy(szFileName, szFQPN); lstrcat(szFileName, NTFS_FAT_STREAM_FILE_EXTENSION_LIVE); DeleteFile(szFileName); } } else if (FAILED(hrRes)) { // couldn't open the file. try the next one DebugTrace((LPARAM) this, "GetStoreFileFromPath returned %x\n", hrRes); } else { CNtfsPropertyStream *pNtfsStream = (CNtfsPropertyStream *) (*ppStream); // skip over items made with this instance of the ntfs store driver if (pNtfsStream->GetInstanceGuid() == m_pDriver->GetInstanceGuid()) { (*ppStream)->Release(); ReleaseContext(*phContentFile); } else { fFoundFile = TRUE; } } } // We got the handles successfully opened, now write the filename // as the store driver context hrRes = CDriverUtils::SetMessageContext( pMsg, (LPBYTE)szFQPN, (lstrlen(szFQPN) + 1) * sizeof(TCHAR)); if (FAILED(hrRes)) { // Release all file resources ReleaseContext(*phContentFile); _VERIFY((*ppStream)->Release() == 0); } else { ((CNtfsPropertyStream *)(*ppStream))->SetInfo(m_pDriver); } return(hrRes); } ///////////////////////////////////////////////////////////////////////////// // CNtfsPropertyStream // CNtfsPropertyStream::CNtfsPropertyStream() { m_hStream = INVALID_HANDLE_VALUE; m_pDriver = NULL; m_fValidation = FALSE; // this will make us fail writeblocks if they don't call startwriteblocks // first m_hrStartWriteBlocks = E_FAIL; } CNtfsPropertyStream::~CNtfsPropertyStream() { if (m_hStream != INVALID_HANDLE_VALUE) { CloseHandle(m_hStream); m_hStream = INVALID_HANDLE_VALUE; } if (m_pDriver) { DecCtr((m_pDriver->m_ppoi), NTFSDRV_MSG_STREAMS_OPEN); m_pDriver->ReleaseUsage(); } } DECLARE_STD_IUNKNOWN_METHODS(NtfsPropertyStream, IMailMsgPropertyStream) // // IMailMsgPropertyStream // HRESULT STDMETHODCALLTYPE CNtfsPropertyStream::GetSize( IMailMsgProperties *pMsg, DWORD *pdwSize, IMailMsgNotify *pNotify ) { DWORD dwHigh, dwLow; DWORD cStreamOffset = m_fStreamHasHeader ? STREAM_OFFSET : 0; _ASSERT(m_pDriver || m_fValidation); if (!m_fValidation && (!m_pDriver || m_pDriver->IsShuttingDown())) return(HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS)); if (m_hStream == INVALID_HANDLE_VALUE) return(E_FAIL); if (!pdwSize) return E_POINTER; dwLow = GetFileSize(m_hStream, &dwHigh); if (dwHigh) return(HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW)); *pdwSize = dwLow - cStreamOffset; return(S_OK); } HRESULT STDMETHODCALLTYPE CNtfsPropertyStream::ReadBlocks( IMailMsgProperties *pMsg, DWORD dwCount, DWORD *pdwOffset, DWORD *pdwLength, BYTE **ppbBlock, IMailMsgNotify *pNotify ) { DWORD dwSizeRead; DWORD dwStreamSize; DWORD dwOffsetToRead; DWORD dwLengthToRead; HRESULT hrRes = S_OK; DWORD cStreamOffset = m_fStreamHasHeader ? STREAM_OFFSET : 0; TraceFunctEnterEx((LPARAM)this, "CNtfsPropertyStream::ReadBlocks"); if (m_hStream == INVALID_HANDLE_VALUE) return(E_FAIL); if (!pdwOffset || !pdwLength || !ppbBlock) { return E_POINTER; } if (!m_pDriver && !m_fValidation) { return E_UNEXPECTED; } _ASSERT(m_pDriver || m_fValidation); if (m_pDriver && m_pDriver->IsShuttingDown()) { DebugTrace((LPARAM)this, "Reading while shutting down ..."); } // Need to get the file size to determine if there is enough bytes // to read for each block. Note that WriteBlocks are to be serialzed so // ReadBlocks and WriteBlocks should not be overlapped. dwStreamSize = GetFileSize(m_hStream, NULL); if (dwStreamSize == 0xffffffff) { hrRes = HRESULT_FROM_WIN32(GetLastError()); if (hrRes == S_OK) hrRes = STG_E_READFAULT; ErrorTrace((LPARAM)this, "Failed to get size of stream (%08x)", hrRes); return(hrRes); } for (DWORD i = 0; i < dwCount; i++, pdwOffset++, pdwLength++, ppbBlock++) { // For each block, check beforehand that we are not reading past // the end of the file. Make sure to be weary about overflow cases dwOffsetToRead = (*pdwOffset) + cStreamOffset; dwLengthToRead = *pdwLength; if ((dwOffsetToRead > dwStreamSize) || (dwOffsetToRead > (dwOffsetToRead + dwLengthToRead)) || ((dwOffsetToRead + dwLengthToRead) > dwStreamSize)) { // Insufficient bytes, abort immediately ErrorTrace((LPARAM)this, "Insufficient bytes: Read(%u, %u); Size = %u", dwOffsetToRead, dwLengthToRead, dwStreamSize); hrRes = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF); break; } if (SetFilePointer( m_hStream, dwOffsetToRead, NULL, FILE_BEGIN) == 0xffffffff) { hrRes = HRESULT_FROM_WIN32(GetLastError()); break; } if (!ReadFile( m_hStream, *ppbBlock, dwLengthToRead, &dwSizeRead, NULL)) { hrRes = HRESULT_FROM_WIN32(GetLastError()); break; } else if (dwSizeRead != dwLengthToRead) { hrRes = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF); break; } } TraceFunctLeaveEx((LPARAM)this); return(hrRes); } HRESULT CNtfsPropertyStream::SetHandle(HANDLE hStream, GUID guidInstance, BOOL fLiveStream, IMailMsgProperties *pMsg) { TraceFunctEnter("CNtfsPropertyStream::SetHandle"); if (hStream == INVALID_HANDLE_VALUE) return(E_FAIL); m_hStream = hStream; DWORD dw; NTFS_STREAM_HEADER header; // // if guidInstance is non-NULL then we are dealing with a fresh // stream and need to write the header block // if (guidInstance != GUID_NULL) { DebugTrace((LPARAM) this, "writing NTFSDRV header"); header.dwSignature = STREAM_SIGNATURE_PRECOMMIT; header.dwVersion = 1; header.guidInstance = guidInstance; if (!WriteFile(m_hStream, &header, sizeof(header), &dw, NULL)) { return HRESULT_FROM_WIN32(GetLastError()); } m_fStreamHasHeader = TRUE; m_guidInstance = guidInstance; m_cCommits = 0; } else { DebugTrace((LPARAM) this, "reading NTFSDRV header, fLiveStream = %lu", fLiveStream); // if we are working with :PROPERTIES then we want to set the // commit count to 1 so that the next set of writes will go to // :PROPERTIES-LIVE m_cCommits = (fLiveStream) ? 2 : 1; // read the header. if we can't read it, or there aren't enough // bytes to read it, then assume that the header wasn't fully // written out. if (!ReadFile(m_hStream, &header, sizeof(header), &dw, NULL) || dw != sizeof(header)) { header.dwSignature = STREAM_SIGNATURE_PRECOMMIT; } // act according to what we find in the signature switch (header.dwSignature) { case STREAM_SIGNATURE: { DebugTrace((LPARAM) this, "signature is valid"); // the signature (and thus the stream) is valid m_fStreamHasHeader = TRUE; m_guidInstance = header.guidInstance; break; } case STREAM_SIGNATURE_PRECOMMIT: { DebugTrace((LPARAM) this, "signature is STREAM_SIGNATURE_PRECOMMIT"); // a commit was never completed return S_NO_FIRST_COMMIT; break; } case STREAM_SIGNATURE_INVALID: { DebugTrace((LPARAM) this, "signature is STREAM_SIGNATURE_INVALID"); // the valid-stream signature was never written IMailMsgValidate *pValidate = NULL; HRESULT hr; // assume that the stream is valid, and go through a full // check m_fStreamHasHeader = TRUE; m_guidInstance = header.guidInstance; // this flag allows the read stream operations to take place // before the stream is fully setup m_fValidation = TRUE; // validate stream can only be trusted on the first // property stream. this is because it can detect // truncated streams, but not streams with corrupted // properties. the first stream (:PROPERTIES) can be // truncated, but not corrupted. // // if we see the invalid signature on a :PROPERTIES-LIVE // stream then we will always assume that it is corrupted // and fall back to the initial stream. // // call into mailmsg to see if the stream is valid. if // it isn't then we won't allow it to be loaded DebugTrace((LPARAM) this, "Calling ValidateStream\n"); if (fLiveStream || FAILED(pMsg->QueryInterface(IID_IMailMsgValidate, (void **) &pValidate)) || FAILED(pValidate->ValidateStream(this))) { DebugTrace((LPARAM) this, "Stream contains invalid data"); m_fStreamHasHeader = FALSE; m_guidInstance = GUID_NULL; if (pValidate) pValidate->Release(); return S_INVALIDSTREAM; } // we are done with the validation routines if (pValidate) pValidate->Release(); m_fValidation = FALSE; DebugTrace((LPARAM) this, "Stream contains valid data"); break; } default: { // if it is anything else then it could be a file with // no header (older builds generated these) or it could be // invalid data. mailmsg will figure it out. m_fStreamHasHeader = FALSE; m_guidInstance = GUID_NULL; DebugTrace((LPARAM) this, "Unknown signature %x on stream", header.dwSignature); } } } return(S_OK); } HRESULT STDMETHODCALLTYPE CNtfsPropertyStream::StartWriteBlocks( IMailMsgProperties *pMsg, DWORD cBlocksToWrite, DWORD cBytesToWrite) { TraceFunctEnter("CNtfsPropertyStream::StartWriteBlocks"); NTFS_STREAM_HEADER header; DWORD dw; m_hrStartWriteBlocks = S_OK; // if we have seen one full commit, then fork the stream and start // writing to the live stream if (m_cCommits == 1) { char szLiveStreamFilename[MAX_PATH * 2]; BOOL fIsFAT = m_pDriver->IsFAT(); char szFatLiveStreamExtension[] = NTFS_FAT_STREAM_FILE_EXTENSION_LIVE; char szNtfsLiveStreamExtension[] = NTFS_STORE_FILE_PROPERTY_STREAM_LIVE; const DWORD cCopySize = 64 * 1024; // get the filename of the message from the mailmsg object // // we need to save space in szLiveStreamFilename for the largest // extension that we might tack on DWORD dwLength = sizeof(char) * ((MAX_PATH * 2) - max(sizeof(szNtfsLiveStreamExtension), sizeof(szFatLiveStreamExtension))); m_hrStartWriteBlocks = CDriverUtils::GetMessageContext(pMsg, (LPBYTE) szLiveStreamFilename, &dwLength); if (FAILED(m_hrStartWriteBlocks)) { ErrorTrace((LPARAM) this, "GetMessageContext failed with %x", m_hrStartWriteBlocks); TraceFunctLeave(); return m_hrStartWriteBlocks; } // allocate memory up front that will be used for copying the // streams BYTE *lpb = new BYTE[cCopySize]; if (lpb == NULL) { m_hrStartWriteBlocks = E_OUTOFMEMORY; ErrorTrace((LPARAM) this, "pvMalloc failed to allocate 64k"); TraceFunctLeave(); return m_hrStartWriteBlocks; } // we know that we have enough space for the strcats because // we saved space for it in the GetMessageContext call above strcat(szLiveStreamFilename, (m_pDriver->IsFAT()) ? szFatLiveStreamExtension : szNtfsLiveStreamExtension); // open the new stream HANDLE hLiveStream = CreateFile(szLiveStreamFilename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (hLiveStream == INVALID_HANDLE_VALUE) { delete[] (lpb); ErrorTrace((LPARAM) this, "CreateFile(%s) failed with %lu", szLiveStreamFilename, GetLastError()); m_hrStartWriteBlocks = HRESULT_FROM_WIN32(GetLastError()); TraceFunctLeave(); return m_hrStartWriteBlocks; } // copy the data between the two streams BOOL fCopyFailed = FALSE; DWORD i = 0, cRead = cCopySize, cWritten; SetFilePointer(m_hStream, 0, NULL, FILE_BEGIN); while (!fCopyFailed && cRead == cCopySize) { if (ReadFile(m_hStream, lpb, cCopySize, &cRead, NULL)) { // if this is the first block then we will touch the // signature to mark it as invalid. it will get // rewritten as valid once this commit is complete if (i == 0) { DWORD *pdwSignature = (DWORD *) lpb; if (*pdwSignature == STREAM_SIGNATURE) { *pdwSignature = STREAM_SIGNATURE_PRECOMMIT; } } if (WriteFile(hLiveStream, lpb, cRead, &cWritten, NULL)) { _ASSERT(cWritten == cRead); fCopyFailed = (cWritten != cRead); if (fCopyFailed) { SetLastError(ERROR_WRITE_FAULT); ErrorTrace((LPARAM) this, "WriteFile didn't write enough bytes" "cWritten = %lu, cRead = %lu", cWritten, cRead); } } else { fCopyFailed = TRUE; ErrorTrace((LPARAM) this, "WriteFile failed with %lu", GetLastError()); } } else { ErrorTrace((LPARAM) this, "ReadFile failed with %lu", GetLastError()); fCopyFailed = TRUE; } i++; } delete[] (lpb); if (fCopyFailed) { // there isn't any way to delete the incomplete stream here. // however we gave it an invalid signature above, so it won't // be loaded during enumeration CloseHandle(hLiveStream); m_hrStartWriteBlocks = HRESULT_FROM_WIN32(GetLastError()); TraceFunctLeave(); return m_hrStartWriteBlocks; } // close the handle to the current stream and point the stream handle // to the new one CloseHandle(m_hStream); m_hStream = hLiveStream; } else { header.dwSignature = STREAM_SIGNATURE_INVALID; if (m_fStreamHasHeader) { if (SetFilePointer(m_hStream, 0, NULL, FILE_BEGIN) == 0) { if (!WriteFile(m_hStream, &header, sizeof(header.dwSignature), &dw, NULL)) { m_hrStartWriteBlocks = HRESULT_FROM_WIN32(GetLastError()); } } else { m_hrStartWriteBlocks = HRESULT_FROM_WIN32(GetLastError()); } } } TraceFunctLeave(); return m_hrStartWriteBlocks; } HRESULT STDMETHODCALLTYPE CNtfsPropertyStream::EndWriteBlocks( IMailMsgProperties *pMsg) { HRESULT hr = S_OK; DWORD dw; NTFS_STREAM_HEADER header; _ASSERT(SUCCEEDED(m_hrStartWriteBlocks)); if (FAILED(m_hrStartWriteBlocks)) { return m_hrStartWriteBlocks; } header.dwSignature = STREAM_SIGNATURE; if (m_fStreamHasHeader) { if (SetFilePointer(m_hStream, 0, NULL, FILE_BEGIN) == 0) { if (!WriteFile(m_hStream, &header, sizeof(header.dwSignature), &dw, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); } } else { hr = HRESULT_FROM_WIN32(GetLastError()); } } if (hr == S_OK) m_cCommits++; return hr; } HRESULT STDMETHODCALLTYPE CNtfsPropertyStream::CancelWriteBlocks( IMailMsgProperties *pMsg) { return S_OK; } HRESULT STDMETHODCALLTYPE CNtfsPropertyStream::WriteBlocks( IMailMsgProperties *pMsg, DWORD dwCount, DWORD *pdwOffset, DWORD *pdwLength, BYTE **ppbBlock, IMailMsgNotify *pNotify ) { DWORD dwSizeWritten; HRESULT hrRes = S_OK; DWORD cStreamOffset = m_fStreamHasHeader ? STREAM_OFFSET : 0; TraceFunctEnterEx((LPARAM)this, "CNtfsPropertyStream::WriteBlocks"); if (!pdwOffset || !pdwLength || !ppbBlock) { return E_POINTER; } if (!m_pDriver) { return E_UNEXPECTED; } _ASSERT(m_pDriver); if (m_pDriver->IsShuttingDown()) { DebugTrace((LPARAM)this, "Writing while shutting down ..."); } if (m_hStream == INVALID_HANDLE_VALUE) return(E_FAIL); if (FAILED(m_hrStartWriteBlocks)) return m_hrStartWriteBlocks; for (DWORD i = 0; i < dwCount; i++, pdwOffset++, pdwLength++, ppbBlock++) { if (SetFilePointer( m_hStream, (*pdwOffset) + cStreamOffset, NULL, FILE_BEGIN) == 0xffffffff) { hrRes = HRESULT_FROM_WIN32(GetLastError()); break; } if (!WriteFile( m_hStream, *ppbBlock, *pdwLength, &dwSizeWritten, NULL) || (dwSizeWritten != *pdwLength)) { hrRes = HRESULT_FROM_WIN32(GetLastError()); break; } } if (SUCCEEDED(hrRes)) { if (!FlushFileBuffers(m_hStream)) hrRes = HRESULT_FROM_WIN32(GetLastError()); } TraceFunctLeaveEx((LPARAM)this); return(hrRes); }