Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2208 lines
72 KiB

//+----------------------------------------------------------------------------
//
// 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, "<unknown>");
}
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);
}