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.

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