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

1297 lines
40 KiB

  1. /*++
  2. Copyright (c) 1994 Microsoft Corporation
  3. Module Name:
  4. util.cxx
  5. Abstract:
  6. Contains the class implementation of UTILITY classes.
  7. Author:
  8. Madan Appiah (madana) 16-Nov-1994
  9. Environment:
  10. User Mode - Win32
  11. Revision History:
  12. Ahsan Kabir (akabir) 24-Nov-1997
  13. --*/
  14. #include <cache.hxx>
  15. typedef BOOL (WINAPI *PFNGETFILEATTREX)(LPCTSTR, GET_FILEEX_INFO_LEVELS, LPVOID);
  16. static char vszDot[] = ".";
  17. static char vszDotDot[] = "..";
  18. #ifdef UNIX
  19. static char vszIndexFile[] = "index.dat";
  20. #endif /* UNIX */
  21. static char vszSHClassInfo[]=".ShellClassInfo";
  22. static char vszCLSIDKey[]="CLSID";
  23. static char vszCLSID[]="{FF393560-C2A7-11CF-BFF4-444553540000}";
  24. static char vszUICLSIDKey[]="UICLSID";
  25. static char vszUICLSID[]="{7BD29E00-76C1-11CF-9DD0-00A0C9034933}";
  26. typedef HRESULT (*PFNSHFLUSHCACHE)(VOID);
  27. #ifdef UNIX
  28. extern void UnixGetValidParentPath(LPTSTR szDevice);
  29. #endif /* UNIX */
  30. /*-----------------------------------------------------------------------------
  31. DeleteOneCachedFile
  32. Deletes a file belonging to the cache.
  33. Arguments:
  34. lpszFileName: Fully qualified filename
  35. Return Value:
  36. TRUE if successful. If FALSE, GetLastError() returns the error code.
  37. Comments:
  38. ---------------------------------------------------------------------------*/
  39. BOOL
  40. DeleteOneCachedFile(
  41. LPSTR lpszFileName,
  42. DWORD dostEntry)
  43. {
  44. if (dostEntry)
  45. {
  46. DWORD dostCreate = 0;
  47. LPWORD pwCreate = (LPWORD) &dostCreate;
  48. WIN32_FILE_ATTRIBUTE_DATA FileAttrData;
  49. switch (GetFileSizeAndTimeByName(lpszFileName, &FileAttrData))
  50. {
  51. case ERROR_SUCCESS:
  52. break;
  53. case ERROR_FILE_NOT_FOUND:
  54. case ERROR_PATH_NOT_FOUND:
  55. return TRUE;
  56. default:
  57. return FALSE;
  58. }
  59. FileTimeToDosDateTime(&FileAttrData.ftCreationTime, pwCreate, pwCreate+1);
  60. if (dostCreate != dostEntry)
  61. return TRUE; // not our file, so consider it done!
  62. }
  63. if(!DeleteFile(lpszFileName))
  64. {
  65. TcpsvcsDbgPrint (( DEBUG_ERRORS, "DeleteFile failed on %s, Error=%ld\n",
  66. lpszFileName, GetLastError()));
  67. switch (GetLastError())
  68. {
  69. case ERROR_FILE_NOT_FOUND:
  70. case ERROR_PATH_NOT_FOUND:
  71. return TRUE;
  72. default:
  73. return FALSE;
  74. }
  75. }
  76. else
  77. {
  78. TcpsvcsDbgPrint(( DEBUG_ERRORS, "Deleted %s\n", lpszFileName ));
  79. return TRUE;
  80. }
  81. }
  82. /*-----------------------------------------------------------------------------
  83. DeleteCachedFilesInDir
  84. ---------------------------------------------------------------------------*/
  85. DWORD DeleteCachedFilesInDir(
  86. LPSTR lpszPath,
  87. DWORD dwLevel
  88. )
  89. {
  90. TCHAR PathFiles[MAX_PATH+1];
  91. TCHAR FullFileName[MAX_PATH+1];
  92. LPTSTR FullFileNamePtr;
  93. WIN32_FIND_DATA FindData;
  94. HANDLE FindHandle = INVALID_HANDLE_VALUE;
  95. // Since this has become a recursive call, we don't want to go more than 6 levels.
  96. if (dwLevel>5)
  97. {
  98. INET_ASSERT(FALSE);
  99. return ERROR_INVALID_PARAMETER;
  100. }
  101. DWORD Error, len, cbUsed;
  102. BOOL fFindSuccess;
  103. DWORD cb = strlen(lpszPath);
  104. memcpy(PathFiles, lpszPath, cb + 1);
  105. if(!AppendSlashIfNecessary(PathFiles, &cb))
  106. {
  107. Error = ERROR_INVALID_NAME;
  108. goto Cleanup;
  109. }
  110. memcpy(FullFileName, PathFiles, cb + 1);
  111. memcpy(PathFiles + cb, ALLFILES_WILDCARD_STRING, sizeof(ALLFILES_WILDCARD_STRING));
  112. FullFileNamePtr = FullFileName + lstrlen( (LPTSTR)FullFileName );
  113. if ( IsValidCacheSubDir( lpszPath))
  114. DisableCacheVu( lpszPath);
  115. FindHandle = FindFirstFile( (LPTSTR)PathFiles, &FindData );
  116. if( FindHandle == INVALID_HANDLE_VALUE )
  117. {
  118. Error = GetLastError();
  119. goto Cleanup;
  120. }
  121. cbUsed = (unsigned int)(FullFileNamePtr-FullFileName);
  122. FullFileName[MAX_PATH] = '\0';
  123. do
  124. {
  125. cb = strlen(FindData.cFileName);
  126. if (cb+cbUsed+1 > MAX_PATH)
  127. {
  128. // Subtracting 1 extra so that the null terminator doesn't get overwritten
  129. cb = MAX_PATH - cbUsed - 2;
  130. }
  131. memcpy(FullFileNamePtr, FindData.cFileName, cb+1);
  132. #ifndef UNIX
  133. if (!(!strnicmp(FindData.cFileName, vszDot, sizeof(vszDot)-1) ||
  134. !strnicmp(FindData.cFileName, vszDotDot, sizeof(vszDotDot)-1)))
  135. #else
  136. if (!(!strnicmp(FindData.cFileName, vszDot, sizeof(vszDot)-1) ||
  137. !strnicmp(FindData.cFileName, vszIndexFile, sizeof(vszIndexFile)-1) ||
  138. !strnicmp(FindData.cFileName, vszDotDot, sizeof(vszDotDot)-1)))
  139. #endif /* UNIX */
  140. {
  141. if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  142. {
  143. Error = DeleteCachedFilesInDir(FullFileName, dwLevel + 1);
  144. if (Error!=ERROR_SUCCESS)
  145. {
  146. goto Cleanup;
  147. }
  148. SetFileAttributes(FullFileName, FILE_ATTRIBUTE_DIRECTORY);
  149. RemoveDirectory(FullFileName);
  150. }
  151. else
  152. {
  153. DeleteOneCachedFile( (LPTSTR)FullFileName, 0);
  154. }
  155. }
  156. //
  157. // find next file.
  158. //
  159. } while (FindNextFile( FindHandle, &FindData ));
  160. Error = GetLastError();
  161. if( Error == ERROR_NO_MORE_FILES)
  162. {
  163. Error = ERROR_SUCCESS;
  164. }
  165. Cleanup:
  166. if( FindHandle != INVALID_HANDLE_VALUE )
  167. {
  168. FindClose( FindHandle );
  169. }
  170. if( Error != ERROR_SUCCESS )
  171. {
  172. TcpsvcsDbgPrint(( DEBUG_ERRORS,
  173. "DeleteCachedFilesInDir failed, %ld.\n",
  174. Error ));
  175. }
  176. return( Error );
  177. }
  178. /*-----------------------------------------------------------------------------
  179. AppendSlashIfNecessary
  180. ---------------------------------------------------------------------------*/
  181. BOOL AppendSlashIfNecessary(LPSTR szPath, DWORD* pcbPath)
  182. {
  183. if (*pcbPath > (MAX_PATH-2))
  184. return FALSE;
  185. if (szPath[*pcbPath-1] != DIR_SEPARATOR_CHAR)
  186. {
  187. szPath[*pcbPath] = DIR_SEPARATOR_CHAR;
  188. (*pcbPath)++;
  189. szPath[*pcbPath] = '\0';
  190. }
  191. return TRUE;
  192. }
  193. /*-----------------------------------------------------------------------------
  194. EnableCachevu
  195. ---------------------------------------------------------------------------*/
  196. BOOL EnableCacheVu(LPSTR szPath, DWORD dwContainer)
  197. {
  198. DWORD cbPath = strlen(szPath);
  199. CHAR szDesktopIni[MAX_PATH];
  200. DWORD dwFileAttributes;
  201. HMODULE hInstShell32 = 0;
  202. PFNSHFLUSHCACHE pfnShFlushCache = NULL;
  203. #define DESIRED_ATTR (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)
  204. // Calls with non-existant directory allowed and return false.
  205. dwFileAttributes = GetFileAttributes(szPath);
  206. if (dwFileAttributes == 0xFFFFFFFF)
  207. return FALSE;
  208. // Always be set to enable cachevu.
  209. SetFileAttributes(szPath, FILE_ATTRIBUTE_SYSTEM);
  210. // Path to DESKTOP_INI_FILENAME
  211. memcpy(szDesktopIni, szPath, cbPath + 1);
  212. AppendSlashIfNecessary(szDesktopIni, &cbPath);
  213. // Correct location for desktop.ini.
  214. memcpy(szDesktopIni + cbPath, DESKTOPINI_FILE_NAME, sizeof(DESKTOPINI_FILE_NAME));
  215. // Check for existing desktop.ini
  216. dwFileAttributes = GetFileAttributes(szDesktopIni);
  217. if (dwFileAttributes == 0xFFFFFFFF)
  218. {
  219. dwFileAttributes = 0;
  220. // Always write out the UICLSID
  221. WritePrivateProfileString(vszSHClassInfo, vszUICLSIDKey, vszUICLSID, szDesktopIni);
  222. // HISTORY requires an additional CLSID.
  223. if (dwContainer == HISTORY)
  224. WritePrivateProfileString(vszSHClassInfo, vszCLSIDKey, vszCLSID, szDesktopIni);
  225. // Flush buffer - problems on Win95 if you don't.
  226. WritePrivateProfileString(NULL, NULL, NULL, szDesktopIni);
  227. }
  228. if ((dwFileAttributes & DESIRED_ATTR) != DESIRED_ATTR)
  229. {
  230. // Should be hidden, read-only and system for cachevu to work correctly.
  231. SetFileAttributes(szDesktopIni, DESIRED_ATTR);
  232. }
  233. /*
  234. BUGBUG - taking this code out for raid # 45710.
  235. // We now need to notify the shell that a new desktop.ini has been created.
  236. hInstShell32 = GetModuleHandle("shell32.dll");
  237. if (hInstShell32)
  238. {
  239. pfnShFlushCache = (PFNSHFLUSHCACHE) GetProcAddress(hInstShell32, (LPSTR) 526);
  240. if (pfnShFlushCache)
  241. {
  242. __try
  243. {
  244. (*pfnShFlushCache)();
  245. }
  246. __except(EXCEPTION_EXECUTE_HANDLER)
  247. {
  248. }
  249. ENDEXCEPT
  250. }
  251. }
  252. */
  253. return TRUE;
  254. }
  255. /*-----------------------------------------------------------------------------
  256. IsValidCacheSubDir
  257. ---------------------------------------------------------------------------*/
  258. BOOL IsValidCacheSubDir(LPSTR szPath)
  259. {
  260. DWORD dwFileAttributes, cb, cbPath;
  261. CHAR szDesktopIni[MAX_PATH];
  262. CHAR szCLSID [MAX_PATH];
  263. CHAR szWindowsDir[MAX_PATH];
  264. CHAR szSystemDir [MAX_PATH];
  265. cbPath = strlen(szPath);
  266. // Root, Windows or System directories
  267. // are decidedly not cache subdirectories.
  268. cb = GetWindowsDirectory(szWindowsDir, MAX_PATH);
  269. if (!cb || cb>MAX_PATH)
  270. {
  271. INET_ASSERT(FALSE);
  272. return FALSE;
  273. }
  274. AppendSlashIfNecessary(szWindowsDir, &cb);
  275. cb = GetSystemDirectory(szSystemDir, MAX_PATH);
  276. AppendSlashIfNecessary(szSystemDir, &cb);
  277. if (cbPath < 4
  278. || !strnicmp(szPath, szWindowsDir, cbPath)
  279. || !strnicmp(szPath, szSystemDir, cbPath))
  280. {
  281. INET_ASSERT(FALSE);
  282. return FALSE;
  283. }
  284. // Path to DESKTOP_INI_FILENAME
  285. memcpy(szDesktopIni, szPath, cbPath + 1);
  286. AppendSlashIfNecessary(szDesktopIni, &cbPath);
  287. // Correct location for desktop.ini.
  288. memcpy(szDesktopIni + cbPath, DESKTOPINI_FILE_NAME, sizeof(DESKTOPINI_FILE_NAME));
  289. // Check for existing desktop.ini
  290. dwFileAttributes = GetFileAttributes(szDesktopIni);
  291. // No desktop.ini found or system attribute not set.
  292. if (dwFileAttributes == 0xFFFFFFFF)
  293. {
  294. return FALSE;
  295. }
  296. // Found UICLSID (CONTENT cachevu) ?
  297. if (GetPrivateProfileString(vszSHClassInfo, vszUICLSIDKey,
  298. "", szCLSID, MAX_PATH, szDesktopIni)
  299. && !strcmp(szCLSID, vszUICLSID))
  300. {
  301. return TRUE;
  302. }
  303. return FALSE;
  304. }
  305. /*-----------------------------------------------------------------------------
  306. DisableCachevu
  307. ---------------------------------------------------------------------------*/
  308. BOOL DisableCacheVu(LPSTR szPath)
  309. {
  310. DWORD cbPath = strlen(szPath);
  311. CHAR szDesktopIni[MAX_PATH];
  312. // Path to DESKTOP_INI_FILENAME
  313. memcpy(szDesktopIni, szPath, cbPath + 1);
  314. AppendSlashIfNecessary(szDesktopIni, &cbPath);
  315. // Correct location for desktop.ini.
  316. memcpy(szDesktopIni + cbPath, DESKTOPINI_FILE_NAME, sizeof(DESKTOPINI_FILE_NAME));
  317. SetFileAttributes(szDesktopIni, FILE_ATTRIBUTE_NORMAL);
  318. DeleteFile(szDesktopIni);
  319. return TRUE;
  320. }
  321. /*-----------------------------------------------------------------------------
  322. StripTrailingWhiteSpace
  323. ---------------------------------------------------------------------------*/
  324. VOID StripTrailingWhiteSpace(LPSTR szString, LPDWORD pcb)
  325. {
  326. INET_ASSERT(szString);
  327. if (*pcb == 0)
  328. return;
  329. CHAR* ptr = szString + *pcb - 1;
  330. while (*ptr == ' ')
  331. {
  332. ptr--;
  333. if (--(*pcb) == 0)
  334. break;
  335. }
  336. *(ptr+1) = '\0';
  337. }
  338. /* PerformOperationOverUrlCache-----------------------
  339. The purpose of this function is to iterate through the content cache and perform the same action (here, called
  340. an operation) on each entry in the cache.
  341. This function takes all the parameters that FindFirstUrlCacheEntryEx accepts,
  342. plus two more:
  343. op -- This is of type CACHE_OPERATOR, discussed below
  344. pOperatorData -- a pointer to an array of data that the calling process and op use to collect/maintain info
  345. */
  346. /* CACHE_OPERATOR
  347. is a pointer to function, that takes three arguments(pointer to a cache entry, cache entry size, and a pointer to
  348. state data.
  349. The operator can perform whatever operation (move/copy/data collection) it wishes on the supplied cache entry.
  350. It must return TRUE if the operation has succeeded and PerformOperationOverUrlCache can continue to iterate through
  351. the cache, FALSE otherwise.
  352. pOpData can be null, or a cast pointer to whatever structure the operator will use to maintain state information.
  353. PerformOperationOverUrlCache guarantees that each cache entry will have sufficient space for its information.
  354. */
  355. typedef BOOL (*CACHE_OPERATOR)(INTERNET_CACHE_ENTRY_INFO* pcei, PDWORD pcbcei, PVOID pOpData);
  356. // hAdjustMemory is a helper function
  357. // that ensures that the buffer used by PerformOperationOverUrlCache
  358. // is large enough to hold all of a cache entry's info
  359. BOOL hAdjustMemory(LPINTERNET_CACHE_ENTRY_INFO* pbBuffer, PDWORD pcbAvail, PDWORD pcbNeeded)
  360. {
  361. if (*pbBuffer)
  362. {
  363. FREE_MEMORY(*pbBuffer);
  364. }
  365. do
  366. {
  367. *pcbAvail += 1024;
  368. }
  369. while (*pcbAvail < *pcbNeeded);
  370. *pcbNeeded = *pcbAvail;
  371. *pbBuffer = (LPINTERNET_CACHE_ENTRY_INFO)ALLOCATE_FIXED_MEMORY(*pcbAvail);
  372. return (*pbBuffer!=NULL);
  373. }
  374. // PerformOperationOverUrlCache
  375. // described above
  376. // uses FindFirstUrlCacheEntryEx and FindNext as any other wininet client would.
  377. // and passes a complete cache entry to the operator for processing
  378. BOOL PerformOperationOverUrlCacheA(
  379. IN PCSTR pszUrlSearchPattern,
  380. IN DWORD dwFlags,
  381. IN DWORD dwFilter,
  382. IN GROUPID GroupId,
  383. OUT PVOID pReserved1,
  384. IN OUT PDWORD pdwReserved2,
  385. IN PVOID pReserved3,
  386. IN CACHE_OPERATOR op,
  387. IN OUT PVOID pOperatorData
  388. )
  389. {
  390. BOOL fResult = FALSE;
  391. DWORD cbAvail = sizeof(INTERNET_CACHE_ENTRY_INFO) + 1024;
  392. DWORD cbCEI = cbAvail;
  393. LPINTERNET_CACHE_ENTRY_INFO pCEI = (LPINTERNET_CACHE_ENTRY_INFO) ALLOCATE_FIXED_MEMORY(cbAvail);
  394. HANDLE hFind = NULL;
  395. if (pCEI == NULL)
  396. goto Cleanup;
  397. hFind = FindFirstUrlCacheEntryEx(pszUrlSearchPattern,
  398. dwFlags,
  399. dwFilter,
  400. GroupId,
  401. pCEI,
  402. &cbCEI,
  403. pReserved1,
  404. pdwReserved2,
  405. pReserved3);
  406. if (!hFind && (GetLastError()!=ERROR_INSUFFICIENT_BUFFER) && hAdjustMemory(&pCEI, &cbAvail, &cbCEI))
  407. {
  408. hFind = FindFirstUrlCacheEntryEx(pszUrlSearchPattern,
  409. dwFlags,
  410. dwFilter,
  411. GroupId,
  412. pCEI,
  413. &cbCEI,
  414. pReserved1,
  415. pdwReserved2,
  416. pReserved3);
  417. }
  418. if (hFind!=NULL)
  419. {
  420. do
  421. {
  422. fResult = op(pCEI, &cbCEI, pOperatorData);
  423. if (fResult)
  424. {
  425. cbCEI = cbAvail;
  426. fResult = FindNextUrlCacheEntryEx(hFind, pCEI, &cbCEI, NULL, NULL, NULL);
  427. if (!fResult && (GetLastError()==ERROR_INSUFFICIENT_BUFFER) && hAdjustMemory(&pCEI, &cbAvail, &cbCEI))
  428. {
  429. fResult = FindNextUrlCacheEntryEx(hFind, pCEI, &cbCEI, NULL, NULL, NULL);
  430. }
  431. }
  432. }
  433. while (fResult);
  434. FindCloseUrlCache(hFind);
  435. if (GetLastError()==ERROR_NO_MORE_ITEMS)
  436. {
  437. fResult = TRUE;
  438. }
  439. }
  440. Cleanup:
  441. if (pCEI)
  442. {
  443. FREE_MEMORY(pCEI);
  444. }
  445. return fResult;
  446. }
  447. // ------ MoveCachedFiles ---------------------------------------------------------------------------------------
  448. // Purpose: Moves as many files as possible from the current Temporary Internet Files to the new location
  449. // State information required for the move operation
  450. struct MOVE_OP_STATE
  451. {
  452. TCHAR szNewPath[MAX_PATH];
  453. TCHAR szOldPath[MAX_PATH];
  454. DWORD ccNewPath;
  455. DWORD ccOldPath;
  456. DWORDLONG dlCacheSize;
  457. DWORD dwClusterSizeMinusOne;
  458. DWORD dwClusterSizeMask;
  459. };
  460. // Helper function that,
  461. // given a string pointer,
  462. // returns the next occurrence of DIR_SEPARATOR_CHAR ('/' || '\\')
  463. PTSTR hScanPastSeparator(PTSTR pszPath)
  464. {
  465. while (*pszPath && *pszPath!=DIR_SEPARATOR_CHAR)
  466. {
  467. pszPath++;
  468. }
  469. if (*pszPath)
  470. {
  471. return pszPath+1;
  472. }
  473. return NULL;
  474. }
  475. // Helper function that,
  476. // given a path,
  477. // ensures that all the directories in the path exist
  478. BOOL hConstructSubDirs(PTSTR pszBase)
  479. {
  480. PTSTR pszLast = hScanPastSeparator(pszBase);
  481. if (NULL == pszLast)
  482. return TRUE; // returning TRUE on purpose
  483. PTSTR pszNext = pszLast;
  484. while ((pszNext=hScanPastSeparator(pszNext))!=NULL)
  485. {
  486. *(pszNext-1) = '\0';
  487. CreateDirectory(pszBase, NULL);
  488. *(pszNext-1) = DIR_SEPARATOR_CHAR;
  489. pszLast = pszNext;
  490. }
  491. return TRUE;
  492. }
  493. // MoveOperation
  494. // actually moves a cached file to the new location
  495. BOOL MoveOperation(LPINTERNET_CACHE_ENTRY_INFO pCEI, PDWORD pcbCEI, PVOID pOpData)
  496. {
  497. MOVE_OP_STATE* pmos = (MOVE_OP_STATE*)pOpData;
  498. BOOL fResult = TRUE;
  499. if (pCEI->lpszLocalFileName)
  500. {
  501. if (!strnicmp(pCEI->lpszLocalFileName, pmos->szOldPath, pmos->ccOldPath))
  502. {
  503. // Copy the file
  504. lstrcpy(pmos->szNewPath + pmos->ccNewPath, pCEI->lpszLocalFileName + pmos->ccOldPath);
  505. fResult = CopyFile(pCEI->lpszLocalFileName, pmos->szNewPath, FALSE);
  506. if (!fResult && GetLastError()==ERROR_PATH_NOT_FOUND)
  507. {
  508. if (hConstructSubDirs(pmos->szNewPath))
  509. {
  510. fResult = CopyFile(pCEI->lpszLocalFileName, pmos->szNewPath, FALSE);
  511. }
  512. }
  513. // If the move was successful, we need to adjust the size of the new cache
  514. if (fResult)
  515. {
  516. fResult = FALSE;
  517. HANDLE h1 = CreateFile(pCEI->lpszLocalFileName,
  518. GENERIC_READ,
  519. 0,
  520. NULL,
  521. OPEN_EXISTING,
  522. FILE_ATTRIBUTE_NORMAL,
  523. NULL);
  524. // If we can't open the original file, then the new file will never
  525. // get scavenged because we'll never be able to match creation times
  526. if (h1!=INVALID_HANDLE_VALUE)
  527. {
  528. HANDLE h2 = CreateFile(pmos->szNewPath,
  529. GENERIC_WRITE,
  530. 0,
  531. NULL,
  532. OPEN_EXISTING,
  533. FILE_ATTRIBUTE_NORMAL,
  534. NULL);
  535. if (h2!=INVALID_HANDLE_VALUE)
  536. {
  537. FILETIME ft;
  538. if (GetFileTime(h1, &ft, NULL, NULL))
  539. {
  540. fResult = SetFileTime(h2, &ft, NULL, NULL);
  541. }
  542. CloseHandle(h2);
  543. }
  544. CloseHandle(h1);
  545. }
  546. }
  547. // If we haven't been able to set the create time, then we've got a problem
  548. // we'd sooner not deal with.
  549. if (!fResult)
  550. {
  551. DeleteUrlCacheEntry(pCEI->lpszSourceUrlName);
  552. DeleteFile(pmos->szNewPath);
  553. }
  554. else
  555. {
  556. pmos->dlCacheSize += ((LONGLONG) (pCEI->dwSizeLow + pmos->dwClusterSizeMinusOne)
  557. & pmos->dwClusterSizeMask);
  558. }
  559. // Delete the old one
  560. DeleteFile(pCEI->lpszLocalFileName);
  561. }
  562. }
  563. return TRUE;
  564. }
  565. DWORD
  566. MoveCachedFiles(
  567. LPSTR pszOldPath,
  568. LPSTR pszNewPath
  569. )
  570. {
  571. MOVE_OP_STATE mos;
  572. INET_ASSERT(pszOldPath && pszNewPath);
  573. mos.ccNewPath = lstrlen(pszNewPath);
  574. memcpy(mos.szNewPath, pszNewPath, mos.ccNewPath*sizeof(TCHAR));
  575. AppendSlashIfNecessary(mos.szNewPath, &mos.ccNewPath);
  576. memcpy(mos.szNewPath + mos.ccNewPath, CONTENT_VERSION_SUBDIR, sizeof(CONTENT_VERSION_SUBDIR));
  577. mos.ccNewPath += sizeof(CONTENT_VERSION_SUBDIR)-1;
  578. AppendSlashIfNecessary(mos.szNewPath, &mos.ccNewPath);
  579. mos.ccOldPath = lstrlen(pszOldPath);
  580. memcpy(mos.szOldPath, pszOldPath, mos.ccOldPath*sizeof(TCHAR));
  581. AppendSlashIfNecessary(mos.szOldPath, &mos.ccOldPath);
  582. mos.dlCacheSize = 0;
  583. GetDiskInfo(mos.szNewPath, &mos.dwClusterSizeMinusOne, NULL, NULL);
  584. mos.dwClusterSizeMinusOne--;
  585. mos.dwClusterSizeMask = ~mos.dwClusterSizeMinusOne;
  586. GlobalUrlContainers->WalkLeakList(CONTENT);
  587. // We don't need to get all the information about each and every entry.
  588. PerformOperationOverUrlCacheA(
  589. NULL,
  590. FIND_FLAGS_RETRIEVE_ONLY_FIXED_AND_FILENAME,
  591. NORMAL_CACHE_ENTRY | STICKY_CACHE_ENTRY | SPARSE_CACHE_ENTRY,
  592. NULL,
  593. NULL,
  594. NULL,
  595. NULL,
  596. MoveOperation,
  597. (PVOID)&mos);
  598. GlobalUrlContainers->SetCacheSize(CONTENT, mos.dlCacheSize);
  599. // Copy desktop.ini and index.dat, since these aren't cached
  600. TCHAR szFile[MAX_PATH];
  601. DWORD ccOldPath = lstrlen(pszOldPath);
  602. memcpy(szFile, pszOldPath, ccOldPath);
  603. AppendSlashIfNecessary(szFile, &ccOldPath);
  604. memcpy(szFile + ccOldPath, MEMMAP_FILE_NAME, sizeof(MEMMAP_FILE_NAME));
  605. memcpy(mos.szNewPath + mos.ccNewPath, MEMMAP_FILE_NAME, sizeof(MEMMAP_FILE_NAME));
  606. CopyFile(szFile, mos.szNewPath, FALSE);
  607. memcpy(szFile + ccOldPath, DESKTOPINI_FILE_NAME, sizeof(DESKTOPINI_FILE_NAME));
  608. memcpy(mos.szNewPath + mos.ccNewPath, DESKTOPINI_FILE_NAME, sizeof(DESKTOPINI_FILE_NAME));
  609. CopyFile(szFile, mos.szNewPath, FALSE);
  610. return ERROR_SUCCESS;
  611. }
  612. /*-----------------------------------------------------------------------------
  613. IsCorrectUser
  614. Routine Description:
  615. checks to see from the headers whether there is any username in there and
  616. whether it matches the currently logged on user. If no one is logged on a
  617. default username string is used
  618. Arguments:
  619. lpszHeaderInfo: headers to check
  620. dwheaderSize: size of the headers buffer
  621. Return Value:
  622. BOOL
  623. ---------------------------------------------------------------------------*/
  624. BOOL
  625. IsCorrectUserPrivate(
  626. IN LPSTR lpszHeaderInfo,
  627. IN DWORD dwHeaderSize
  628. )
  629. {
  630. LPSTR lpTemp, lpTemp2;
  631. INET_ASSERT (lpszHeaderInfo);
  632. lpTemp = lpszHeaderInfo+dwHeaderSize-1;
  633. // start searching backwards
  634. while (lpTemp >= lpszHeaderInfo) {
  635. if (*lpTemp ==':') {
  636. // If this is less than the expected header:
  637. // then we know that there is no such usernameheader
  638. // <MH> i.e. it's not a peruseritem so allow access</MH>
  639. if ((DWORD)PtrDifference((lpTemp+1), lpszHeaderInfo) < (sizeof(vszUserNameHeader)-1)) {
  640. TcpsvcsDbgPrint((DEBUG_CONTAINER,
  641. "IsCorrectUser (Util.cxx): Didn't find header <lpTemp = 0x%x %s> <lpszHeaderInfo = 0x%x %s> <vszCurrentUser = %s> <PtrDifference = %d> <sizeof(vszUserNameHeader)-1) = %d>\r\n",
  642. lpTemp,
  643. lpTemp,
  644. lpszHeaderInfo,
  645. lpszHeaderInfo,
  646. vszCurrentUser,
  647. PtrDifference(lpTemp, lpszHeaderInfo),
  648. (sizeof(vszUserNameHeader)-1)
  649. ));
  650. return (TRUE); // No such header. just ay it is OK
  651. }
  652. // point this puppy to the expected header start
  653. lpTemp2 = lpTemp - (sizeof(vszUserNameHeader)-2);
  654. // if the earlier char is not a white space [0x9-0xd or 0x20]
  655. // then this is not the beginning of the header
  656. // <MH> Also need to check for the first header which would not
  657. // have whitespace preceding it. Want to first check lpTemp2 ==
  658. // lpszheaderInfo to prevent underflowing when dereferencing.</MH>
  659. if (((lpTemp2) == lpszHeaderInfo) || isspace(*(lpTemp2-1))) {
  660. // we have the beginning of a header
  661. if (!strnicmp(lpTemp2
  662. , vszUserNameHeader
  663. , sizeof(vszUserNameHeader)-1)) {
  664. // right header, let us see whether this is the right person
  665. if(!strnicmp(lpTemp+1, vszCurrentUser, vdwCurrentUserLen)) {
  666. TcpsvcsDbgPrint((DEBUG_CONTAINER,
  667. "IsCorrectUser (Util.cxx): Match!! %s header == %s current user.\r\n",
  668. lpTemp+1,
  669. vszCurrentUser
  670. ));
  671. return (TRUE); // right guy
  672. }
  673. else {
  674. TcpsvcsDbgPrint((DEBUG_CONTAINER,
  675. "IsCorrectUser (Util.cxx): No match!! %s header != %s current user.\r\n",
  676. lpTemp+1,
  677. vszCurrentUser
  678. ));
  679. }
  680. return(FALSE); // wrong guy
  681. }
  682. }
  683. }
  684. --lpTemp;
  685. }
  686. return (TRUE); // there was no UserName header, just say it is OK
  687. }
  688. BOOL
  689. IsCorrectUser(
  690. IN LPSTR lpszHeaderInfo,
  691. IN DWORD dwHeaderSize
  692. )
  693. {
  694. BOOL fRet = FALSE;
  695. __try
  696. {
  697. fRet = IsCorrectUserPrivate(lpszHeaderInfo, dwHeaderSize);
  698. } // __try
  699. __except(EXCEPTION_EXECUTE_HANDLER)
  700. {
  701. INET_ASSERT(FALSE);
  702. fRet = FALSE;
  703. }
  704. ENDEXCEPT
  705. return fRet;
  706. }
  707. #ifndef UNICODE
  708. #define SZ_GETDISKFREESPACEEX "GetDiskFreeSpaceExA"
  709. #define SZ_WNETUSECONNECTION "WNetUseConnectionA"
  710. #define SZ_WNETCANCELCONNECTION "WNetCancelConnectionA"
  711. #else
  712. #define SZ_GETDISKFREESPACEEX "GetDiskFreeSpaceExW"
  713. #define SZ_WNETUSECONNECTION "WNetUseConnectionW"
  714. #define SZ_WNETCANCELCONNECTION "WNetCancelConnectionW"
  715. #endif
  716. typedef BOOL (WINAPI *PFNGETDISKFREESPACEEX)(LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
  717. typedef BOOL (WINAPI *PFNWNETUSECONNECTION)(HWND, LPNETRESOURCE, PSTR, PSTR, DWORD, PSTR, PDWORD, PDWORD);
  718. typedef BOOL (WINAPI *PFNWNETCANCELCONNECTION)(LPCTSTR, BOOL);
  719. BOOL EstablishFunction(PTSTR pszModule, PTSTR pszFunction, PFN* pfn)
  720. {
  721. if (*pfn==(PFN)-1)
  722. {
  723. *pfn = NULL;
  724. HMODULE ModuleHandle = GetModuleHandle(pszModule);
  725. if (ModuleHandle)
  726. {
  727. *pfn = (PFN)GetProcAddress(ModuleHandle, pszFunction);
  728. }
  729. }
  730. return (*pfn!=NULL);
  731. }
  732. // GetPartitionClusterSize
  733. // GetDiskFreeSpace has the annoying habit of lying about the layout
  734. // of the drive; thus we've been ending up with bogus sizes for the cluster size.
  735. // You can't imagine how annoying it is to think you've a 200 MB cache, but it
  736. // starts scavenging at 20MB.
  737. // This function will, if given reason to doubt the veracity of GDFS, go straight
  738. // to the hardware and get the information for itself, otherwise return the passed-in
  739. // value.
  740. // The code that follows is heavily doctored from msdn sample code. Copyright violation? I think not.
  741. static PFNGETDISKFREESPACEEX pfnGetDiskFreeSpaceEx = (PFNGETDISKFREESPACEEX)-1;
  742. #define VWIN32_DIOC_DOS_DRIVEINFO 6
  743. typedef struct _DIOC_REGISTERS
  744. {
  745. DWORD reg_EBX;
  746. DWORD reg_EDX;
  747. DWORD reg_ECX;
  748. DWORD reg_EAX;
  749. DWORD reg_EDI;
  750. DWORD reg_ESI;
  751. DWORD reg_Flags;
  752. }
  753. DIOC_REGISTERS, *PDIOC_REGISTERS;
  754. // Important: All MS_DOS data structures must be packed on a
  755. // one-byte boundary.
  756. #pragma pack(1)
  757. typedef struct
  758. _DPB {
  759. BYTE dpb_drive; // Drive number (1-indexed)
  760. BYTE dpb_unit; // Unit number
  761. WORD dpb_sector_size; // Size of sector in bytes
  762. BYTE dpb_cluster_mask; // Number of sectors per cluster, minus 1
  763. BYTE dpb_cluster_shift; // The stuff after this, we don't really care about.
  764. WORD dpb_first_fat;
  765. BYTE dpb_fat_count;
  766. WORD dpb_root_entries;
  767. WORD dpb_first_sector;
  768. WORD dpb_max_cluster;
  769. WORD dpb_fat_size;
  770. WORD dpb_dir_sector;
  771. DWORD dpb_reserved2;
  772. BYTE dpb_media;
  773. BYTE dpb_first_access;
  774. DWORD dpb_reserved3;
  775. WORD dpb_next_free;
  776. WORD dpb_free_cnt;
  777. WORD extdpb_free_cnt_hi;
  778. WORD extdpb_flags;
  779. WORD extdpb_FSInfoSec;
  780. WORD extdpb_BkUpBootSec;
  781. DWORD extdpb_first_sector;
  782. DWORD extdpb_max_cluster;
  783. DWORD extdpb_fat_size;
  784. DWORD extdpb_root_clus;
  785. DWORD extdpb_next_free;
  786. }
  787. DPB, *PDPB;
  788. #pragma pack()
  789. DWORD GetPartitionClusterSize(PTSTR szDevice, DWORD dwClusterSize)
  790. {
  791. switch (GlobalPlatformType)
  792. {
  793. case PLATFORM_TYPE_WIN95:
  794. // If GetDiskFreeSpaceEx is present _and_ we're running Win9x, this implies
  795. // that we must be doing OSR2 or later. We can trust earlier versions
  796. // of the GDFS (we think; this assumption may be invalid.)
  797. // Since Win95 can't read NTFS drives, we'll freely assume we're reading a FAT drive.
  798. // Basically, we're performing an MSDOS INT21 call to get the drive partition record. Joy.
  799. if (pfnGetDiskFreeSpaceEx)
  800. {
  801. HANDLE hDevice;
  802. DIOC_REGISTERS reg;
  803. BYTE buffer[sizeof(WORD)+sizeof(DPB)];
  804. PDPB pdpb = (PDPB)(buffer + sizeof(WORD));
  805. BOOL fResult;
  806. DWORD cb;
  807. // We must always have a drive letter in this case
  808. int nDrive = *szDevice - TEXT('A') + 1; // Drive number, 1-indexed
  809. hDevice = CreateFile("\\\\.\\vwin32", 0, 0, NULL, 0, FILE_FLAG_DELETE_ON_CLOSE, NULL);
  810. if (hDevice!=INVALID_HANDLE_VALUE)
  811. {
  812. reg.reg_EDI = PtrToUlong(buffer);
  813. reg.reg_EAX = 0x7302;
  814. reg.reg_ECX = sizeof(buffer);
  815. reg.reg_EDX = (DWORD) nDrive; // drive number (1-based)
  816. reg.reg_Flags = 0x0001; // assume error (carry flag is set)
  817. fResult = DeviceIoControl(hDevice,
  818. VWIN32_DIOC_DOS_DRIVEINFO,
  819. &reg, sizeof(reg),
  820. &reg, sizeof(reg),
  821. &cb, 0);
  822. if (fResult && !(reg.reg_Flags & 0x0001))
  823. {
  824. // no error if carry flag is clear
  825. dwClusterSize = DWORD((pdpb->dpb_cluster_mask+1)*pdpb->dpb_sector_size);
  826. }
  827. CloseHandle(hDevice);
  828. }
  829. }
  830. break;
  831. default:
  832. // Do nothing. Trust the value we've been passed.
  833. // UNIX guys will have to treat this separately.
  834. // For NT, however, this might be another issue. We can't use the DOS INT21.
  835. // Questions:
  836. // NT5 (but not NT4) supports FAT32; will we get honest answers? Apparently, yes.
  837. // NT4/5: NTFS drives and other FAT drives -- do we still get honest answers? Investigation
  838. // so far says, Yes.
  839. break;
  840. }
  841. return dwClusterSize;
  842. }
  843. /* GetDiskInfo
  844. A nice way to get volume information
  845. */
  846. BOOL GetDiskInfoA(PTSTR pszPath, PDWORD pdwClusterSize, PDWORDLONG pdlAvail, PDWORDLONG pdlTotal)
  847. {
  848. static PFNWNETUSECONNECTION pfnWNetUseConnection = (PFNWNETUSECONNECTION)-1;
  849. static PFNWNETCANCELCONNECTION pfnWNetCancelConnection = (PFNWNETCANCELCONNECTION)-1;
  850. if (!pszPath)
  851. {
  852. SetLastError(ERROR_INVALID_PARAMETER);
  853. return FALSE;
  854. }
  855. INET_ASSERT(pdwClusterSize || pdlAvail || pdlTotal);
  856. // If GetDiskFreeSpaceExA is available, we can be confident we're running W95OSR2+ || NT4
  857. EstablishFunction(TEXT("KERNEL32"), SZ_GETDISKFREESPACEEX, (PFN*)&pfnGetDiskFreeSpaceEx);
  858. BOOL fRet = FALSE;
  859. TCHAR szDevice[MAX_PATH];
  860. PTSTR pszGDFSEX = NULL;
  861. if (*pszPath==DIR_SEPARATOR_CHAR)
  862. {
  863. // If we're dealing with a cache that's actually located on a network share,
  864. // that's fine so long as we have GetDiskFreeSpaceEx at our disposal.
  865. // _However_, if we need the cluster size on Win9x, we'll need to use
  866. // INT21 stuff (see above), even if we have GDFSEX available, so we need to map
  867. // the share to a local drive.
  868. if (pfnGetDiskFreeSpaceEx
  869. && !((GlobalPlatformType==PLATFORM_TYPE_WIN95) && pdwClusterSize))
  870. {
  871. DWORD cbPath = lstrlen(pszPath);
  872. cbPath -= ((pszPath[cbPath-1]==DIR_SEPARATOR_CHAR) ? 1 : 0);
  873. if (cbPath>MAX_PATH-2)
  874. {
  875. SetLastError(ERROR_INVALID_PARAMETER);
  876. return FALSE;
  877. }
  878. memcpy(szDevice, pszPath, cbPath);
  879. szDevice[cbPath] = DIR_SEPARATOR_CHAR;
  880. cbPath++;
  881. szDevice[cbPath] = '\0';
  882. pszGDFSEX = szDevice;
  883. }
  884. else
  885. {
  886. if (!(EstablishFunction(TEXT("MPR"), SZ_WNETUSECONNECTION, (PFN*)&pfnWNetUseConnection)
  887. &&
  888. EstablishFunction(TEXT("MPR"), SZ_WNETCANCELCONNECTION, (PFN*)&pfnWNetCancelConnection)))
  889. {
  890. return FALSE;
  891. }
  892. // If it's a UNC, map it to a local drive for backwards compatibility
  893. NETRESOURCE nr = { 0, RESOURCETYPE_DISK, 0, 0, szDevice, pszPath, NULL, NULL };
  894. DWORD cbLD = sizeof(szDevice);
  895. DWORD dwNull;
  896. if (pfnWNetUseConnection(NULL,
  897. &nr,
  898. NULL,
  899. NULL,
  900. CONNECT_INTERACTIVE | CONNECT_REDIRECT,
  901. szDevice,
  902. &cbLD,
  903. &dwNull)!=ERROR_SUCCESS)
  904. {
  905. SetLastError(ERROR_NO_MORE_DEVICES);
  906. return FALSE;
  907. }
  908. }
  909. }
  910. else
  911. {
  912. memcpy(szDevice, pszPath, sizeof(TEXT("?:\\")));
  913. szDevice[3] = '\0';
  914. pszGDFSEX = pszPath;
  915. }
  916. if (*szDevice!=DIR_SEPARATOR_CHAR)
  917. {
  918. *szDevice = (TCHAR)CharUpper((LPTSTR)*szDevice);
  919. }
  920. #ifdef UNIX
  921. /* On Unix, GetDiskFreeSpace and GetDiskFreeSpaceEx will work successfully
  922. * only if the path exists. So, let us pass a path that exists
  923. */
  924. UnixGetValidParentPath(szDevice);
  925. #endif /* UNIX */
  926. // I hate goto's, and this is a way to avoid them...
  927. for (;;)
  928. {
  929. DWORDLONG cbFree = 0, cbTotal = 0;
  930. if (pfnGetDiskFreeSpaceEx && (pdlTotal || pdlAvail))
  931. {
  932. ULARGE_INTEGER ulFree, ulTotal;
  933. // BUG BUG BUG Is the following problematic? Also, we'll need to add checks to make sure that
  934. // the cKBlimit fits a DWORD (in the obscene if unlikely case drive spaces grow that large)
  935. // For instance, if this is a per user system with a non-shared cache, we might want to change
  936. // the ratios.
  937. INET_ASSERT(pszGDFSEX);
  938. fRet = pfnGetDiskFreeSpaceEx(pszGDFSEX, &ulFree, &ulTotal, NULL);
  939. // HACK Some versions of GetDiskFreeSpaceEx don't accept the whole directory; they
  940. // take only the drive letter. Pfft.
  941. if (!fRet)
  942. {
  943. fRet = pfnGetDiskFreeSpaceEx(szDevice, &ulFree, &ulTotal, NULL);
  944. }
  945. if (fRet)
  946. {
  947. cbFree = ulFree.QuadPart;
  948. cbTotal = ulTotal.QuadPart;
  949. }
  950. }
  951. if ((!fRet) || pdwClusterSize)
  952. {
  953. DWORD dwSectorsPerCluster, dwBytesPerSector, dwFreeClusters, dwClusters, dwClusterSize;
  954. if (!GetDiskFreeSpace(szDevice, &dwSectorsPerCluster, &dwBytesPerSector, &dwFreeClusters, &dwClusters))
  955. {
  956. fRet = FALSE;
  957. break;
  958. }
  959. dwClusterSize = dwBytesPerSector * dwSectorsPerCluster;
  960. if (!fRet)
  961. {
  962. cbFree = (DWORDLONG)dwClusterSize * (DWORDLONG)dwFreeClusters;
  963. cbTotal = (DWORDLONG)dwClusterSize * (DWORDLONG)dwClusters;
  964. }
  965. if (pdwClusterSize)
  966. {
  967. *pdwClusterSize = GetPartitionClusterSize(szDevice, dwClusterSize);
  968. }
  969. }
  970. if (pdlTotal)
  971. {
  972. *pdlTotal = cbTotal;
  973. }
  974. if (pdlAvail)
  975. {
  976. *pdlAvail = cbFree;
  977. }
  978. fRet = TRUE;
  979. break;
  980. };
  981. // We've got the characteristics. Now delete local device connection, if any.
  982. if (*pszPath==DIR_SEPARATOR_CHAR && !pfnGetDiskFreeSpaceEx)
  983. {
  984. pfnWNetCancelConnection(szDevice, FALSE);
  985. }
  986. return fRet;
  987. }
  988. // -- ScanToLastSeparator
  989. // Given a path, and a pointer within the path, discover where the path separator prior to the path
  990. // is located and return the pointer to it. If there is none, return NULL.
  991. BOOL ScanToLastSeparator(PTSTR pszPath, PTSTR* ppszCurrent)
  992. {
  993. PTSTR pszActual = *ppszCurrent;
  994. pszActual--;
  995. while ((pszActual>(pszPath+1)) && (*pszActual!=DIR_SEPARATOR_CHAR))
  996. {
  997. pszActual--;
  998. }
  999. if ((*pszActual==DIR_SEPARATOR_CHAR) && (pszActual!=*ppszCurrent))
  1000. {
  1001. *ppszCurrent = pszActual;
  1002. return TRUE;
  1003. }
  1004. return FALSE;
  1005. }
  1006. // -- Centralised method of tracking mutexes
  1007. // class MUTEX_HOLDER
  1008. MUTEX_HOLDER::MUTEX_HOLDER()
  1009. {
  1010. _hHandle = NULL;
  1011. _dwState = WAIT_FAILED;
  1012. }
  1013. MUTEX_HOLDER::~MUTEX_HOLDER()
  1014. {
  1015. if (_hHandle)
  1016. {
  1017. INET_ASSERT(FALSE);
  1018. TcpsvcsDbgPrint((DEBUG_CONTAINER,
  1019. "ERROR: Releasing ownership of mutex %d\r\n",
  1020. _hHandle));
  1021. ReleaseMutex(_hHandle);
  1022. }
  1023. }
  1024. VOID MUTEX_HOLDER::Grab(HANDLE hHandle, DWORD dwTime)
  1025. {
  1026. INET_ASSERT(hHandle);
  1027. _hHandle = hHandle;
  1028. _dwState = WaitForSingleObject(_hHandle, dwTime);
  1029. if (_dwState==WAIT_ABANDONED)
  1030. {
  1031. TcpsvcsDbgPrint((DEBUG_CONTAINER,
  1032. "Gained ownership of abandoned mutex %d\r\n",
  1033. hHandle));
  1034. }
  1035. else if (_dwState==WAIT_OBJECT_0)
  1036. {
  1037. TcpsvcsDbgPrint((DEBUG_CONTAINER,
  1038. "Gained ownership of mutex %d\r\n",
  1039. hHandle));
  1040. }
  1041. else
  1042. {
  1043. INET_ASSERT(FALSE);
  1044. TcpsvcsDbgPrint((DEBUG_CONTAINER,
  1045. "Unable to gain ownership of mutex %d\r\n",
  1046. hHandle));
  1047. }
  1048. }
  1049. VOID MUTEX_HOLDER::Release()
  1050. {
  1051. if (_hHandle)
  1052. {
  1053. if (_dwState==WAIT_ABANDONED || _dwState==WAIT_OBJECT_0)
  1054. {
  1055. ReleaseMutex(_hHandle);
  1056. TcpsvcsDbgPrint((DEBUG_CONTAINER,
  1057. "Released ownership of mutex %d\r\n",
  1058. _hHandle));
  1059. }
  1060. else
  1061. {
  1062. TcpsvcsDbgPrint((DEBUG_CONTAINER,
  1063. "Would release ownership of mutex %d, except we don't own it\r\n",
  1064. _hHandle));
  1065. }
  1066. }
  1067. _hHandle = NULL;
  1068. }