Windows2003-3790/windows/appcompat/shims/layer/emulatefindhandles.cpp
2020-09-30 16:53:55 +02:00

666 lines
19 KiB
C++

/*++
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