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
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);
|
|
}
|
|
|
|
|