Source code of Windows XP (NT5)
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.

649 lines
18 KiB

  1. /*++
  2. Copyright (c) 2000 Microsoft Corporation
  3. Module Name:
  4. EmulateFindHandles.cpp
  5. Abstract:
  6. If an application calls FindFirstFile on a directory, then attempts to
  7. remove that directory without first closing the FindFirstFile handle, the
  8. directory will be in use; The RemoveDirectory call will return an
  9. ERROR_SHARING_VIOLATION error. This shim will force the FindFirstFile
  10. handle closed to ensure the directory is removed.
  11. This shim also ensures the FindFirstFile handles are valid before calling
  12. FindNext or FindClose.
  13. The FindFirstFile handle will not be forced closed unless the directory
  14. is empty.
  15. History:
  16. 04/12/2000 robkenny Created
  17. 11/13/2000 robkenny Fixed PREFIX bugs, mostly by removing the W routines.
  18. 11/20/2000 maonis Added FindNextFile and renamed from RemoveDirectoryInUse.
  19. 02/27/2001 robkenny Converted to use CString
  20. 04/26/2001 robkenny FindFileInfo now normalizes the names for comparisons
  21. Moved all AutoLockFFIV outside of the exception handlers
  22. to ensure they are deconstructed correctly.
  23. --*/
  24. #include "precomp.h"
  25. #include "CharVector.h"
  26. #include "parseDDE.h"
  27. IMPLEMENT_SHIM_BEGIN(EmulateFindHandles)
  28. #include "ShimHookMacro.h"
  29. APIHOOK_ENUM_BEGIN
  30. APIHOOK_ENUM_ENTRY(FindFirstFileA)
  31. APIHOOK_ENUM_ENTRY(FindFirstFileExA)
  32. APIHOOK_ENUM_ENTRY(FindNextFileA)
  33. APIHOOK_ENUM_ENTRY(FindClose)
  34. APIHOOK_ENUM_ENTRY(RemoveDirectoryA)
  35. APIHOOK_ENUM_ENTRY(DdeClientTransaction)
  36. APIHOOK_ENUM_END
  37. BOOL g_bHookDDE = TRUE; // default to hooking DDE
  38. //---------------------------------------------------------------------------
  39. // A class that automatically locks/unlocks the FFIV
  40. class AutoLockFFIV
  41. {
  42. public:
  43. AutoLockFFIV();
  44. ~AutoLockFFIV();
  45. };
  46. //---------------------------------------------------------------------------
  47. /*++
  48. HANDLE Vector type class.
  49. --*/
  50. class FindFileInfo
  51. {
  52. public:
  53. HANDLE m_hFindHandle;
  54. CString m_csFindName;
  55. FindFileInfo(HANDLE findHandle, LPCSTR lpFileName)
  56. {
  57. Init(findHandle, lpFileName);
  58. }
  59. // Convert csFileName into a fully qualified, long path to the *directory*
  60. // c:\Program Files\Some App\*.exe should get changed to c:\Program Files\Some App
  61. // c:\Progra~1\Some~1\*.exe should get changed to c:\Program Files\Some App
  62. // .\*.exe should get changed to c:\Program Files\Some App
  63. // *.exe should get changed to *.exe
  64. static void NormalizeName(CString & csFileName)
  65. {
  66. DWORD dwAttr = GetFileAttributesW(csFileName);
  67. if (dwAttr == -1)
  68. {
  69. CString csDirPart;
  70. csFileName.GetNotLastPathComponent(csDirPart);
  71. csFileName = csDirPart;
  72. }
  73. csFileName.GetFullPathName();
  74. csFileName.GetLongPathName();
  75. }
  76. // Init the values, we store the full path for safe compares
  77. void Init(HANDLE findHandle, LPCSTR lpFileName)
  78. {
  79. m_hFindHandle = findHandle;
  80. m_csFindName = lpFileName;
  81. NormalizeName(m_csFindName);
  82. }
  83. bool operator == (HANDLE findHandle) const
  84. {
  85. return findHandle == m_hFindHandle;
  86. }
  87. // Only compare up to the length of lpFileName
  88. bool operator == (LPCSTR lpFileName) const
  89. {
  90. // We need to convert lpFileName the same way as done in Init()
  91. CString csFileName(lpFileName);
  92. NormalizeName(csFileName);
  93. return m_csFindName.CompareNoCase(csFileName) == 0;
  94. }
  95. };
  96. class FindFileInfoVector : public VectorT<FindFileInfo *>
  97. {
  98. protected:
  99. static FindFileInfoVector * g_TheHandleVector;
  100. CRITICAL_SECTION m_Lock;
  101. public:
  102. FindFileInfoVector()
  103. {
  104. InitializeCriticalSection(&m_Lock);
  105. }
  106. void Lock()
  107. {
  108. EnterCriticalSection(&m_Lock);
  109. }
  110. void Unlock()
  111. {
  112. LeaveCriticalSection(&m_Lock);
  113. }
  114. // Search through the list of open FindFirstFile handles for a match to hMember
  115. FindFileInfo * Find(HANDLE hMember)
  116. {
  117. if (hMember != INVALID_HANDLE_VALUE)
  118. {
  119. DPF(g_szModuleName,
  120. eDbgLevelSpew,
  121. "FindFileInfoVector::Find(0x%08x)\n",
  122. hMember);
  123. for (int i = 0; i < Size(); ++i)
  124. {
  125. FindFileInfo * ffi = Get(i);
  126. if (*ffi == hMember)
  127. {
  128. DPF(g_szModuleName,
  129. eDbgLevelSpew,
  130. "FindFileInfoVector: FOUND handle 0x%08x (%S)\n",
  131. ffi->m_hFindHandle, ffi->m_csFindName.Get());
  132. return ffi;
  133. }
  134. }
  135. }
  136. return NULL;
  137. }
  138. // Search through the list of open FindFirstFile handles for a match to lpFileName
  139. FindFileInfo * Find(LPCSTR lpFileName)
  140. {
  141. if (lpFileName != NULL)
  142. {
  143. DPF(g_szModuleName,
  144. eDbgLevelSpew,
  145. "FindFileInfoVector::Find(%s)\n",
  146. lpFileName);
  147. for (int i = 0; i < Size(); ++i)
  148. {
  149. FindFileInfo * ffi = Get(i);
  150. if (*ffi == lpFileName)
  151. {
  152. DPF(g_szModuleName,
  153. eDbgLevelSpew,
  154. "FindFileInfoVector: FOUND handle 0x%08x (%S)\n",
  155. ffi->m_hFindHandle, ffi->m_csFindName.Get());
  156. return ffi;
  157. }
  158. #if 0
  159. else
  160. {
  161. DPF(g_szModuleName,
  162. eDbgLevelSpew,
  163. "FindFileInfoVector: NOT FOUND handle 0x%08x (%S)\n",
  164. ffi.m_hFindHandle, ffi.m_csFindName.Get());
  165. }
  166. #endif
  167. }
  168. }
  169. return NULL;
  170. }
  171. // Remove the FindFileInfo,
  172. // return true if the handle was actually removed.
  173. bool Remove(FindFileInfo * ffi)
  174. {
  175. for (int i = 0; i < Size(); ++i)
  176. {
  177. if (Get(i) == ffi)
  178. {
  179. DPF(g_szModuleName,
  180. eDbgLevelSpew,
  181. "FindFileInfoVector: REMOVED handle 0x%08x (%S)\n",
  182. ffi->m_hFindHandle, ffi->m_csFindName.Get());
  183. // Remove the entry by copying the last entry over this index
  184. // Only move if this is not the last entry.
  185. if (i < Size() - 1)
  186. {
  187. CopyElement(i, Get(Size() - 1));
  188. }
  189. nVectorList -= 1;
  190. }
  191. }
  192. return false;
  193. }
  194. // Return a pointer to the global FindFileInfoVector
  195. static FindFileInfoVector * GetHandleVector()
  196. {
  197. if (g_TheHandleVector == NULL)
  198. {
  199. g_TheHandleVector = new FindFileInfoVector;
  200. }
  201. return g_TheHandleVector;
  202. }
  203. };
  204. FindFileInfoVector * FindFileInfoVector::g_TheHandleVector = NULL;
  205. FindFileInfoVector * OpenFindFileHandles;
  206. AutoLockFFIV::AutoLockFFIV()
  207. {
  208. FindFileInfoVector::GetHandleVector()->Lock();
  209. }
  210. AutoLockFFIV::~AutoLockFFIV()
  211. {
  212. FindFileInfoVector::GetHandleVector()->Unlock();
  213. }
  214. //---------------------------------------------------------------------------
  215. /*++
  216. Call FindFirstFileA, if it fails because the file doesn't exist,
  217. correct the file path and try again.
  218. --*/
  219. HANDLE
  220. APIHOOK(FindFirstFileA)(
  221. LPCSTR lpFileName, // file name
  222. LPWIN32_FIND_DATAA lpFindFileData // data buffer
  223. )
  224. {
  225. HANDLE returnValue = ORIGINAL_API(FindFirstFileA)(lpFileName, lpFindFileData);
  226. if (returnValue != INVALID_HANDLE_VALUE)
  227. {
  228. DPF(g_szModuleName,
  229. eDbgLevelSpew,
  230. "FindFirstFileA: adding handle 0x%08x (%s)\n",
  231. returnValue, lpFileName);
  232. AutoLockFFIV lock;
  233. CSTRING_TRY
  234. {
  235. // Save the handle for later.
  236. FindFileInfo *ffi = new FindFileInfo(returnValue, lpFileName);
  237. FindFileInfoVector::GetHandleVector()->Append(ffi);
  238. }
  239. CSTRING_CATCH
  240. {
  241. // Do nothing
  242. }
  243. }
  244. return returnValue;
  245. }
  246. /*++
  247. Add the file handle to our list.
  248. --*/
  249. HANDLE
  250. APIHOOK(FindFirstFileExA)(
  251. LPCSTR lpFileName,
  252. FINDEX_INFO_LEVELS fInfoLevelId,
  253. LPVOID lpFindFileData,
  254. FINDEX_SEARCH_OPS fSearchOp,
  255. LPVOID lpSearchFilter,
  256. DWORD dwAdditionalFlags
  257. )
  258. {
  259. HANDLE returnValue = ORIGINAL_API(FindFirstFileExA)(
  260. lpFileName,
  261. fInfoLevelId,
  262. lpFindFileData,
  263. fSearchOp,
  264. lpSearchFilter,
  265. dwAdditionalFlags);
  266. if (returnValue != INVALID_HANDLE_VALUE)
  267. {
  268. DPF(g_szModuleName,
  269. eDbgLevelSpew,
  270. "FindFirstFileA: adding handle 0x%08x (%s)\n",
  271. returnValue, lpFileName);
  272. AutoLockFFIV lock;
  273. CSTRING_TRY
  274. {
  275. // Save the handle for later.
  276. FindFileInfo *ffi = new FindFileInfo(returnValue, lpFileName);
  277. FindFileInfoVector::GetHandleVector()->Append(ffi);
  278. }
  279. CSTRING_CATCH
  280. {
  281. // Do nothing
  282. }
  283. }
  284. return returnValue;
  285. }
  286. /*++
  287. Validates the FindFirstFile handle before calling FindNextFileA.
  288. --*/
  289. BOOL
  290. APIHOOK(FindNextFileA)(
  291. HANDLE hFindFile,
  292. LPWIN32_FIND_DATAA lpFindFileData
  293. )
  294. {
  295. BOOL returnValue = FALSE;
  296. AutoLockFFIV lock;
  297. CSTRING_TRY
  298. {
  299. // Only call FindNextFileA if the handle is actually open
  300. FindFileInfo * ffi = FindFileInfoVector::GetHandleVector()->Find(hFindFile);
  301. if (ffi)
  302. {
  303. returnValue = ORIGINAL_API(FindNextFileA)(hFindFile, lpFindFileData);
  304. DPF(g_szModuleName,
  305. eDbgLevelSpew,
  306. "FindNextFile: using handle 0x%08x (%ls)\n",
  307. hFindFile, ffi->m_csFindName.Get());
  308. }
  309. }
  310. CSTRING_CATCH
  311. {
  312. returnValue = ORIGINAL_API(FindNextFileA)(hFindFile, lpFindFileData);
  313. }
  314. return returnValue;
  315. }
  316. /*++
  317. Remove the file handle to our list.
  318. --*/
  319. BOOL
  320. APIHOOK(FindClose)(
  321. HANDLE hFindFile // file search handle
  322. )
  323. {
  324. BOOL returnValue = FALSE;
  325. AutoLockFFIV lock;
  326. CSTRING_TRY
  327. {
  328. // Only call FindClose if the handle is actually open
  329. FindFileInfo * ffi = FindFileInfoVector::GetHandleVector()->Find(hFindFile);
  330. if (ffi)
  331. {
  332. returnValue = ORIGINAL_API(FindClose)(hFindFile);
  333. DPF(g_szModuleName,
  334. eDbgLevelSpew,
  335. "FindClose: removing handle 0x%08x (%S)\n",
  336. hFindFile, ffi->m_csFindName.Get());
  337. // Remove this entry from the list of open FindFirstFile handles.
  338. FindFileInfoVector::GetHandleVector()->Remove(ffi);
  339. }
  340. }
  341. CSTRING_CATCH
  342. {
  343. returnValue = ORIGINAL_API(FindClose)(hFindFile);
  344. }
  345. return returnValue;
  346. }
  347. /*++
  348. Call RemoveDirectoryA, if it fails because the directory is in use,
  349. make sure all FindFirstFile handles are closed, then try again.
  350. --*/
  351. BOOL
  352. APIHOOK(RemoveDirectoryA)(
  353. LPCSTR lpFileName // directory name
  354. )
  355. {
  356. FindFileInfo * ffi;
  357. BOOL returnValue = ORIGINAL_API(RemoveDirectoryA)(lpFileName);
  358. if (!returnValue)
  359. {
  360. AutoLockFFIV lock;
  361. CSTRING_TRY
  362. {
  363. DWORD dwLastError = GetLastError();
  364. // NOTE:
  365. // ERROR_DIR_NOT_EMPTY error takes precedence over ERROR_SHARING_VIOLATION,
  366. // so we will not forcably to free the FindFirstFile handles unless the directory is empty.
  367. // If the directory is in use, check to see if the app left a FindFirstFileHandle open.
  368. if (dwLastError == ERROR_SHARING_VIOLATION)
  369. {
  370. // Close all FindFirstFile handles open to this directory.
  371. while(ffi = FindFileInfoVector::GetHandleVector()->Find(lpFileName))
  372. {
  373. DPF(g_szModuleName,
  374. eDbgLevelError,
  375. "[RemoveDirectoryA] Forcing closed FindFirstFile (%S).",
  376. ffi->m_csFindName.Get());
  377. // Calling FindClose here would not, typically, get hooked, so we call
  378. // our hook routine directly to ensure we close the handle and remove it from the list
  379. // If we don't remove it from the list we'll never get out of this loop :-)
  380. APIHOOK(FindClose)(ffi->m_hFindHandle);
  381. }
  382. // Last chance
  383. returnValue = ORIGINAL_API(RemoveDirectoryA)(lpFileName);
  384. }
  385. }
  386. CSTRING_CATCH
  387. {
  388. // Do nothing
  389. }
  390. }
  391. return returnValue;
  392. }
  393. // A list of DDE commands that we are interested in.
  394. const char * c_sDDECommands[] =
  395. {
  396. "DeleteGroup",
  397. NULL,
  398. } ;
  399. // Parse the DDE Command looking for DeleteGroup,
  400. // If the command is found, make sure that we do not have any open FindFirstFile handles
  401. // on that directory.
  402. // This needs to be aware of "User" vs. "All Users" syntax of DDE
  403. void CloseHandleIfDeleteGroup(LPBYTE pData)
  404. {
  405. if (pData)
  406. {
  407. // Now we need to parse the string, looking for a DeleteGroup command
  408. // Format "[DeleteGroup(GroupName, CommonGroupFlag)]"
  409. // CommonGroupFlag is optional
  410. char * pszBuf = StringDuplicateA((const char *)pData);
  411. if (!pszBuf)
  412. return;
  413. UINT * lpwCmd = GetDDECommands(pszBuf, c_sDDECommands, FALSE);
  414. if (lpwCmd)
  415. {
  416. // Store off lpwCmd so we can free the correect addr later
  417. UINT *lpwCmdTemp = lpwCmd;
  418. // Execute a command.
  419. while (*lpwCmd != (UINT)-1)
  420. {
  421. UINT wCmd = *lpwCmd++;
  422. // Subtract 1 to account for the terminating NULL
  423. if (wCmd < ARRAYSIZE(c_sDDECommands)-1)
  424. {
  425. // We found a command--it must be DeleteGroup--since there is only 1
  426. BOOL iCommonGroup = -1;
  427. // From DDE_DeleteGroup
  428. if (*lpwCmd < 1 || *lpwCmd > 3)
  429. {
  430. goto Leave;
  431. }
  432. if (*lpwCmd == 2)
  433. {
  434. //
  435. // Need to check for common group flag
  436. //
  437. if (pszBuf[*(lpwCmd + 2)] == TEXT('1')) {
  438. iCommonGroup = 1;
  439. } else {
  440. iCommonGroup = 0;
  441. }
  442. }
  443. const char * groupName = pszBuf + lpwCmd[1];
  444. // Build a path to the directory
  445. CHAR szGroupName[MAX_PATH];
  446. GetGroupPath(groupName, szGroupName, 0, iCommonGroup);
  447. AutoLockFFIV lock;
  448. CSTRING_TRY
  449. {
  450. // Attempt to delete the directory, since we are calling our hooked
  451. // routine, it will detect if the directory is in use and do the dirty work.
  452. // Close all FindFirstFile handles open to this directory.
  453. FindFileInfo * ffi;
  454. while( ffi = FindFileInfoVector::GetHandleVector()->Find(szGroupName))
  455. {
  456. DPF(g_szModuleName,
  457. eDbgLevelError,
  458. "[DdeClientTransaction] %s Forcing closed FindFirstFile (%S).",
  459. pData, ffi->m_csFindName.Get());
  460. // Calling FindClose here would not, typically, get hooked, so we call
  461. // our hook routine directly to ensure we close the handle and remove it from the list
  462. // If we don't remove it from the list we'll never get out of this loop :-)
  463. APIHOOK(FindClose)(ffi->m_hFindHandle);
  464. }
  465. }
  466. CSTRING_CATCH
  467. {
  468. // Do nothing
  469. }
  470. }
  471. // Next command.
  472. lpwCmd += *lpwCmd + 1;
  473. }
  474. Leave:
  475. // Tidyup...
  476. GlobalFree(lpwCmdTemp);
  477. }
  478. free(pszBuf);
  479. }
  480. }
  481. //==============================================================================
  482. //==============================================================================
  483. HDDEDATA
  484. APIHOOK(DdeClientTransaction)( IN LPBYTE pData, IN DWORD cbData,
  485. IN HCONV hConv, IN HSZ hszItem, IN UINT wFmt, IN UINT wType,
  486. IN DWORD dwTimeout, OUT LPDWORD pdwResult)
  487. {
  488. #if 0
  489. // Allow a longer timeout for debugging purposes.
  490. dwTimeout = 0x0fffffff;
  491. #endif
  492. CloseHandleIfDeleteGroup(pData);
  493. HDDEDATA returnValue = ORIGINAL_API(DdeClientTransaction)(
  494. pData,
  495. cbData,
  496. hConv,
  497. hszItem,
  498. wFmt,
  499. wType,
  500. dwTimeout,
  501. pdwResult);
  502. return returnValue;
  503. }
  504. /*++
  505. Parse the command line, looking for the -noDDE switch
  506. --*/
  507. void ParseCommandLine(const char * commandLine)
  508. {
  509. CString csCL(commandLine);
  510. // if (-noDDE) then g_bHookDDE = FALSE;
  511. g_bHookDDE = csCL.CompareNoCase(L"-noDDE") != 0;
  512. }
  513. BOOL
  514. NOTIFY_FUNCTION(
  515. DWORD fdwReason
  516. )
  517. {
  518. if (fdwReason == DLL_PROCESS_ATTACH) {
  519. // This forces the allocation of the array:
  520. FindFileInfoVector * ffiv = FindFileInfoVector::GetHandleVector();
  521. ParseCommandLine(COMMAND_LINE);
  522. return ffiv != NULL;
  523. }
  524. return TRUE;
  525. }
  526. HOOK_BEGIN
  527. CALL_NOTIFY_FUNCTION
  528. APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileA)
  529. APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileExA)
  530. APIHOOK_ENTRY(KERNEL32.DLL, FindNextFileA)
  531. APIHOOK_ENTRY(KERNEL32.DLL, FindClose)
  532. APIHOOK_ENTRY(KERNEL32.DLL, RemoveDirectoryA)
  533. if (g_bHookDDE)
  534. {
  535. APIHOOK_ENTRY(USER32.DLL, DdeClientTransaction)
  536. }
  537. HOOK_END
  538. IMPLEMENT_SHIM_END