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.
665 lines
19 KiB
665 lines
19 KiB
/*++
|
|
|
|
Copyright (c) 2000-2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
EmulateFindHandles.cpp
|
|
|
|
Abstract:
|
|
|
|
If an application calls FindFirstFile on a directory, then attempts to
|
|
remove that directory without first closing the FindFirstFile handle, the
|
|
directory will be in use; The RemoveDirectory call will return an
|
|
ERROR_SHARING_VIOLATION error. This shim will force the FindFirstFile
|
|
handle closed to ensure the directory is removed.
|
|
|
|
This shim also ensures the FindFirstFile handles are valid before calling
|
|
FindNext or FindClose.
|
|
|
|
The FindFirstFile handle will not be forced closed unless the directory
|
|
is empty.
|
|
|
|
History:
|
|
|
|
04/12/2000 robkenny Created
|
|
11/13/2000 robkenny Fixed PREFIX bugs, mostly by removing the W routines.
|
|
11/20/2000 maonis Added FindNextFile and renamed from RemoveDirectoryInUse.
|
|
02/27/2001 robkenny Converted to use CString
|
|
04/26/2001 robkenny FindFileInfo now normalizes the names for comparisons
|
|
Moved all AutoLockFFIV outside of the exception handlers
|
|
to ensure they are deconstructed correctly.
|
|
02/14/2002 mnikkel Changed GetHandleVector to correct Prefix errors.
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "CharVector.h"
|
|
#include "parseDDE.h"
|
|
|
|
IMPLEMENT_SHIM_BEGIN(EmulateFindHandles)
|
|
#include "ShimHookMacro.h"
|
|
|
|
APIHOOK_ENUM_BEGIN
|
|
APIHOOK_ENUM_ENTRY(FindFirstFileA)
|
|
APIHOOK_ENUM_ENTRY(FindFirstFileExA)
|
|
APIHOOK_ENUM_ENTRY(FindNextFileA)
|
|
APIHOOK_ENUM_ENTRY(FindClose)
|
|
APIHOOK_ENUM_ENTRY(RemoveDirectoryA)
|
|
APIHOOK_ENUM_ENTRY(DdeClientTransaction)
|
|
APIHOOK_ENUM_END
|
|
|
|
BOOL g_bHookDDE = TRUE; // default to hooking DDE
|
|
|
|
//---------------------------------------------------------------------------
|
|
// A class that automatically locks/unlocks the FFIV
|
|
class AutoLockFFIV
|
|
{
|
|
public:
|
|
AutoLockFFIV();
|
|
~AutoLockFFIV();
|
|
};
|
|
|
|
//---------------------------------------------------------------------------
|
|
/*++
|
|
|
|
HANDLE Vector type class.
|
|
|
|
--*/
|
|
class FindFileInfo
|
|
{
|
|
public:
|
|
HANDLE m_hFindHandle;
|
|
CString m_csFindName;
|
|
|
|
FindFileInfo(HANDLE findHandle, LPCSTR lpFileName)
|
|
{
|
|
Init(findHandle, lpFileName);
|
|
}
|
|
|
|
// Convert csFileName into a fully qualified, long path to the *directory*
|
|
// c:\Program Files\Some App\*.exe should get changed to c:\Program Files\Some App
|
|
// c:\Progra~1\Some~1\*.exe should get changed to c:\Program Files\Some App
|
|
// .\*.exe should get changed to c:\Program Files\Some App
|
|
// *.exe should get changed to *.exe
|
|
static void NormalizeName(CString & csFileName)
|
|
{
|
|
DWORD dwAttr = GetFileAttributesW(csFileName);
|
|
if (dwAttr == INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
CString csDirPart;
|
|
csFileName.GetNotLastPathComponent(csDirPart);
|
|
csFileName = csDirPart;
|
|
}
|
|
|
|
csFileName.GetFullPathName();
|
|
csFileName.GetLongPathName();
|
|
}
|
|
// Init the values, we store the full path for safe compares
|
|
void Init(HANDLE findHandle, LPCSTR lpFileName)
|
|
{
|
|
m_hFindHandle = findHandle;
|
|
m_csFindName = lpFileName;
|
|
|
|
NormalizeName(m_csFindName);
|
|
}
|
|
|
|
bool operator == (HANDLE findHandle) const
|
|
{
|
|
return findHandle == m_hFindHandle;
|
|
}
|
|
|
|
bool operator == (LPCSTR lpFileName) const
|
|
{
|
|
// We need to convert lpFileName the same way as done in Init()
|
|
CString csFileName(lpFileName);
|
|
NormalizeName(csFileName);
|
|
|
|
return m_csFindName.CompareNoCase(csFileName) == 0;
|
|
}
|
|
};
|
|
|
|
class FindFileInfoVector : public VectorT<FindFileInfo *>
|
|
{
|
|
protected:
|
|
static FindFileInfoVector * g_TheHandleVector;
|
|
CRITICAL_SECTION m_Lock;
|
|
|
|
public:
|
|
FindFileInfoVector()
|
|
{
|
|
}
|
|
|
|
void Lock()
|
|
{
|
|
EnterCriticalSection(&m_Lock);
|
|
}
|
|
|
|
void Unlock()
|
|
{
|
|
LeaveCriticalSection(&m_Lock);
|
|
}
|
|
|
|
// Search through the list of open FindFirstFile handles for a match to hMember
|
|
FindFileInfo * Find(HANDLE hMember)
|
|
{
|
|
if (hMember != INVALID_HANDLE_VALUE)
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindFileInfoVector::Find(0x%08x)\n",
|
|
hMember);
|
|
for (int i = 0; i < Size(); ++i)
|
|
{
|
|
FindFileInfo * ffi = Get(i);
|
|
if (*ffi == hMember)
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindFileInfoVector: FOUND handle 0x%08x (%S)\n",
|
|
ffi->m_hFindHandle, ffi->m_csFindName.Get());
|
|
return ffi;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Search through the list of open FindFirstFile handles for a match to lpFileName
|
|
FindFileInfo * Find(LPCSTR lpFileName)
|
|
{
|
|
if (lpFileName != NULL)
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindFileInfoVector::Find(%s)\n",
|
|
lpFileName);
|
|
for (int i = 0; i < Size(); ++i)
|
|
{
|
|
FindFileInfo * ffi = Get(i);
|
|
if (*ffi == lpFileName)
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindFileInfoVector: FOUND handle 0x%08x (%S)\n",
|
|
ffi->m_hFindHandle, ffi->m_csFindName.Get());
|
|
return ffi;
|
|
}
|
|
#if 0
|
|
else
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindFileInfoVector: NOT FOUND handle 0x%08x (%S)\n",
|
|
ffi.m_hFindHandle, ffi.m_csFindName.Get());
|
|
}
|
|
|
|
#endif
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Remove the FindFileInfo,
|
|
// return true if the handle was actually removed.
|
|
bool Remove(FindFileInfo * ffi)
|
|
{
|
|
for (int i = 0; i < Size(); ++i)
|
|
{
|
|
if (Get(i) == ffi)
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindFileInfoVector: REMOVED handle 0x%08x (%S)\n",
|
|
ffi->m_hFindHandle, ffi->m_csFindName.Get());
|
|
|
|
// Remove the entry by copying the last entry over this index
|
|
|
|
// Only move if this is not the last entry.
|
|
if (i < Size() - 1)
|
|
{
|
|
CopyElement(i, Get(Size() - 1));
|
|
}
|
|
nVectorList -= 1;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Initialize the global FindFileInfoVector
|
|
static BOOL InitializeHandleVector()
|
|
{
|
|
g_TheHandleVector = new FindFileInfoVector;
|
|
|
|
if (g_TheHandleVector)
|
|
{
|
|
return InitializeCriticalSectionAndSpinCount(&(g_TheHandleVector->m_Lock),0x80000000);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
// Return a pointer to the global FindFileInfoVector
|
|
static FindFileInfoVector * GetHandleVector()
|
|
{
|
|
return g_TheHandleVector;
|
|
}
|
|
};
|
|
|
|
FindFileInfoVector * FindFileInfoVector::g_TheHandleVector = NULL;
|
|
FindFileInfoVector * OpenFindFileHandles;
|
|
|
|
AutoLockFFIV::AutoLockFFIV()
|
|
{
|
|
FindFileInfoVector::GetHandleVector()->Lock();
|
|
}
|
|
AutoLockFFIV::~AutoLockFFIV()
|
|
{
|
|
FindFileInfoVector::GetHandleVector()->Unlock();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*++
|
|
Call FindFirstFileA, if it fails because the file doesn't exist,
|
|
correct the file path and try again.
|
|
--*/
|
|
HANDLE
|
|
APIHOOK(FindFirstFileA)(
|
|
LPCSTR lpFileName, // file name
|
|
LPWIN32_FIND_DATAA lpFindFileData // data buffer
|
|
)
|
|
{
|
|
HANDLE returnValue = ORIGINAL_API(FindFirstFileA)(lpFileName, lpFindFileData);
|
|
|
|
if (returnValue != INVALID_HANDLE_VALUE)
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindFirstFileA: adding handle 0x%08x (%s)\n",
|
|
returnValue, lpFileName);
|
|
|
|
AutoLockFFIV lock;
|
|
CSTRING_TRY
|
|
{
|
|
// Save the handle for later.
|
|
FindFileInfo *ffi = new FindFileInfo(returnValue, lpFileName);
|
|
FindFileInfoVector::GetHandleVector()->Append(ffi);
|
|
}
|
|
CSTRING_CATCH
|
|
{
|
|
// Do nothing
|
|
}
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
/*++
|
|
|
|
Add the file handle to our list.
|
|
|
|
--*/
|
|
|
|
HANDLE
|
|
APIHOOK(FindFirstFileExA)(
|
|
LPCSTR lpFileName,
|
|
FINDEX_INFO_LEVELS fInfoLevelId,
|
|
LPVOID lpFindFileData,
|
|
FINDEX_SEARCH_OPS fSearchOp,
|
|
LPVOID lpSearchFilter,
|
|
DWORD dwAdditionalFlags
|
|
)
|
|
{
|
|
HANDLE returnValue = ORIGINAL_API(FindFirstFileExA)(
|
|
lpFileName,
|
|
fInfoLevelId,
|
|
lpFindFileData,
|
|
fSearchOp,
|
|
lpSearchFilter,
|
|
dwAdditionalFlags);
|
|
|
|
if (returnValue != INVALID_HANDLE_VALUE)
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindFirstFileA: adding handle 0x%08x (%s)\n",
|
|
returnValue, lpFileName);
|
|
|
|
AutoLockFFIV lock;
|
|
CSTRING_TRY
|
|
{
|
|
// Save the handle for later.
|
|
FindFileInfo *ffi = new FindFileInfo(returnValue, lpFileName);
|
|
FindFileInfoVector::GetHandleVector()->Append(ffi);
|
|
}
|
|
CSTRING_CATCH
|
|
{
|
|
// Do nothing
|
|
}
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
/*++
|
|
|
|
Validates the FindFirstFile handle before calling FindNextFileA.
|
|
|
|
--*/
|
|
BOOL
|
|
APIHOOK(FindNextFileA)(
|
|
HANDLE hFindFile,
|
|
LPWIN32_FIND_DATAA lpFindFileData
|
|
)
|
|
{
|
|
BOOL returnValue = FALSE;
|
|
|
|
AutoLockFFIV lock;
|
|
CSTRING_TRY
|
|
{
|
|
// Only call FindNextFileA if the handle is actually open
|
|
FindFileInfo * ffi = FindFileInfoVector::GetHandleVector()->Find(hFindFile);
|
|
if (ffi)
|
|
{
|
|
returnValue = ORIGINAL_API(FindNextFileA)(hFindFile, lpFindFileData);
|
|
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindNextFile: using handle 0x%08x (%ls)\n",
|
|
hFindFile, ffi->m_csFindName.Get());
|
|
}
|
|
}
|
|
CSTRING_CATCH
|
|
{
|
|
returnValue = ORIGINAL_API(FindNextFileA)(hFindFile, lpFindFileData);
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
/*++
|
|
|
|
Remove the file handle to our list.
|
|
|
|
--*/
|
|
BOOL
|
|
APIHOOK(FindClose)(
|
|
HANDLE hFindFile // file search handle
|
|
)
|
|
{
|
|
BOOL returnValue = FALSE;
|
|
|
|
AutoLockFFIV lock;
|
|
CSTRING_TRY
|
|
{
|
|
// Only call FindClose if the handle is actually open
|
|
FindFileInfo * ffi = FindFileInfoVector::GetHandleVector()->Find(hFindFile);
|
|
if (ffi)
|
|
{
|
|
returnValue = ORIGINAL_API(FindClose)(hFindFile);
|
|
|
|
DPF(g_szModuleName,
|
|
eDbgLevelSpew,
|
|
"FindClose: removing handle 0x%08x (%S)\n",
|
|
hFindFile, ffi->m_csFindName.Get());
|
|
|
|
// Remove this entry from the list of open FindFirstFile handles.
|
|
FindFileInfoVector::GetHandleVector()->Remove(ffi);
|
|
}
|
|
}
|
|
CSTRING_CATCH
|
|
{
|
|
returnValue = ORIGINAL_API(FindClose)(hFindFile);
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
Call RemoveDirectoryA, if it fails because the directory is in use,
|
|
make sure all FindFirstFile handles are closed, then try again.
|
|
--*/
|
|
|
|
BOOL
|
|
APIHOOK(RemoveDirectoryA)(
|
|
LPCSTR lpFileName // directory name
|
|
)
|
|
{
|
|
FindFileInfo * ffi;
|
|
|
|
BOOL returnValue = ORIGINAL_API(RemoveDirectoryA)(lpFileName);
|
|
if (!returnValue)
|
|
{
|
|
AutoLockFFIV lock;
|
|
|
|
CSTRING_TRY
|
|
{
|
|
DWORD dwLastError = GetLastError();
|
|
|
|
// NOTE:
|
|
// ERROR_DIR_NOT_EMPTY error takes precedence over ERROR_SHARING_VIOLATION,
|
|
// so we will not forcably to free the FindFirstFile handles unless the directory is empty.
|
|
|
|
// If the directory is in use, check to see if the app left a FindFirstFileHandle open.
|
|
if (dwLastError == ERROR_SHARING_VIOLATION)
|
|
{
|
|
// Close all FindFirstFile handles open to this directory.
|
|
ffi = FindFileInfoVector::GetHandleVector()->Find(lpFileName);
|
|
while(ffi)
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelError,
|
|
"[RemoveDirectoryA] Forcing closed FindFirstFile (%S).",
|
|
ffi->m_csFindName.Get());
|
|
|
|
// Calling FindClose here would not, typically, get hooked, so we call
|
|
// our hook routine directly to ensure we close the handle and remove it from the list
|
|
// If we don't remove it from the list we'll never get out of this loop :-)
|
|
APIHOOK(FindClose)(ffi->m_hFindHandle);
|
|
ffi = FindFileInfoVector::GetHandleVector()->Find(lpFileName);
|
|
}
|
|
|
|
// Last chance
|
|
returnValue = ORIGINAL_API(RemoveDirectoryA)(lpFileName);
|
|
}
|
|
}
|
|
CSTRING_CATCH
|
|
{
|
|
// Do nothing
|
|
}
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
// A list of DDE commands that we are interested in.
|
|
const char * c_sDDECommands[] =
|
|
{
|
|
"DeleteGroup",
|
|
NULL,
|
|
} ;
|
|
|
|
// Parse the DDE Command looking for DeleteGroup,
|
|
// If the command is found, make sure that we do not have any open FindFirstFile handles
|
|
// on that directory.
|
|
// This needs to be aware of "User" vs. "All Users" syntax of DDE
|
|
void CloseHandleIfDeleteGroup(LPBYTE pData)
|
|
{
|
|
if (pData)
|
|
{
|
|
// Now we need to parse the string, looking for a DeleteGroup command
|
|
// Format "[DeleteGroup(GroupName, CommonGroupFlag)]"
|
|
// CommonGroupFlag is optional
|
|
|
|
char * pszBuf = StringDuplicateA((const char *)pData);
|
|
if (!pszBuf)
|
|
return;
|
|
|
|
UINT * lpwCmd = GetDDECommands(pszBuf, c_sDDECommands, FALSE);
|
|
if (lpwCmd)
|
|
{
|
|
// Store off lpwCmd so we can free the correect addr later
|
|
UINT *lpwCmdTemp = lpwCmd;
|
|
|
|
// Execute a command.
|
|
while (*lpwCmd != (UINT)-1)
|
|
{
|
|
UINT wCmd = *lpwCmd++;
|
|
// Subtract 1 to account for the terminating NULL
|
|
if (wCmd < ARRAYSIZE(c_sDDECommands)-1)
|
|
{
|
|
|
|
// We found a command--it must be DeleteGroup--since there is only 1
|
|
|
|
BOOL iCommonGroup = -1;
|
|
|
|
// From DDE_DeleteGroup
|
|
if (*lpwCmd < 1 || *lpwCmd > 3)
|
|
{
|
|
goto Leave;
|
|
}
|
|
if (*lpwCmd == 2)
|
|
{
|
|
//
|
|
// Need to check for common group flag
|
|
//
|
|
if (pszBuf[*(lpwCmd + 2)] == TEXT('1')) {
|
|
iCommonGroup = 1;
|
|
} else {
|
|
iCommonGroup = 0;
|
|
}
|
|
}
|
|
const char * groupName = pszBuf + lpwCmd[1];
|
|
|
|
// Build a path to the directory
|
|
AutoLockFFIV lock;
|
|
|
|
CSTRING_TRY
|
|
{
|
|
// Build a path to the directory
|
|
CString csGroupName;
|
|
GetGroupPath(groupName, csGroupName, 0, iCommonGroup);
|
|
|
|
// Attempt to delete the directory, since we are calling our hooked
|
|
// routine, it will detect if the directory is in use and do the dirty work.
|
|
|
|
// Close all FindFirstFile handles open to this directory.
|
|
|
|
const char * szGroupName = csGroupName.GetAnsi();
|
|
|
|
for (FindFileInfo * ffi = FindFileInfoVector::GetHandleVector()->Find(szGroupName);
|
|
ffi != NULL;
|
|
ffi = FindFileInfoVector::GetHandleVector()->Find(szGroupName))
|
|
{
|
|
DPF(g_szModuleName,
|
|
eDbgLevelError,
|
|
"[DdeClientTransaction] %s Forcing closed FindFirstFile (%S).",
|
|
pData, ffi->m_csFindName.Get());
|
|
// Calling FindClose here would not, typically, get hooked, so we call
|
|
// our hook routine directly to ensure we close the handle and remove it from the list
|
|
// If we don't remove it from the list we'll never get out of this loop :-)
|
|
APIHOOK(FindClose)(ffi->m_hFindHandle);
|
|
}
|
|
}
|
|
CSTRING_CATCH
|
|
{
|
|
// Do nothing
|
|
}
|
|
}
|
|
|
|
// Next command.
|
|
lpwCmd += *lpwCmd + 1;
|
|
}
|
|
|
|
Leave:
|
|
// Tidyup...
|
|
GlobalFree(lpwCmdTemp);
|
|
}
|
|
|
|
free(pszBuf);
|
|
}
|
|
}
|
|
//==============================================================================
|
|
//==============================================================================
|
|
|
|
HDDEDATA
|
|
APIHOOK(DdeClientTransaction)( IN LPBYTE pData, IN DWORD cbData,
|
|
IN HCONV hConv, IN HSZ hszItem, IN UINT wFmt, IN UINT wType,
|
|
IN DWORD dwTimeout, OUT LPDWORD pdwResult)
|
|
{
|
|
#if 0
|
|
// Allow a longer timeout for debugging purposes.
|
|
dwTimeout = 0x0fffffff;
|
|
#endif
|
|
|
|
CloseHandleIfDeleteGroup(pData);
|
|
|
|
HDDEDATA returnValue = ORIGINAL_API(DdeClientTransaction)(
|
|
pData,
|
|
cbData,
|
|
hConv,
|
|
hszItem,
|
|
wFmt,
|
|
wType,
|
|
dwTimeout,
|
|
pdwResult);
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
/*++
|
|
|
|
Parse the command line, looking for the -noDDE switch
|
|
|
|
--*/
|
|
void ParseCommandLine(const char * commandLine)
|
|
{
|
|
CString csCL(commandLine);
|
|
|
|
// if (-noDDE) then g_bHookDDE = FALSE;
|
|
g_bHookDDE = csCL.CompareNoCase(L"-noDDE") != 0;
|
|
}
|
|
|
|
|
|
BOOL
|
|
NOTIFY_FUNCTION(
|
|
DWORD fdwReason
|
|
)
|
|
{
|
|
if (fdwReason == DLL_PROCESS_ATTACH) {
|
|
|
|
// This forces the allocation of the array:
|
|
if (FindFileInfoVector::InitializeHandleVector()) {
|
|
ParseCommandLine(COMMAND_LINE);
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HOOK_BEGIN
|
|
|
|
CALL_NOTIFY_FUNCTION
|
|
|
|
APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileA)
|
|
APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileExA)
|
|
APIHOOK_ENTRY(KERNEL32.DLL, FindNextFileA)
|
|
APIHOOK_ENTRY(KERNEL32.DLL, FindClose)
|
|
APIHOOK_ENTRY(KERNEL32.DLL, RemoveDirectoryA)
|
|
if (g_bHookDDE)
|
|
{
|
|
APIHOOK_ENTRY(USER32.DLL, DdeClientTransaction)
|
|
}
|
|
|
|
HOOK_END
|
|
|
|
|
|
IMPLEMENT_SHIM_END
|
|
|