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.

4424 lines
152 KiB

  1. #include "shellprv.h"
  2. #pragma hdrstop
  3. #include <regstr.h> // REGSTR_PATH_POLICIES
  4. #include "bitbuck.h"
  5. #include "fstreex.h"
  6. #include "copy.h"
  7. #include "filetbl.h"
  8. #include "propsht.h"
  9. #include "datautil.h"
  10. #include "cscuiext.h"
  11. // mtpt.cpp
  12. STDAPI_(BOOL) CMtPt_IsSecure(int iDrive);
  13. // copy.c
  14. void FOUndo_AddInfo(LPUNDOATOM lpua, LPTSTR pszSrc, LPTSTR pszDest, DWORD dwAttributes);
  15. void FOUndo_FileReallyDeleted(LPTSTR pszFile);
  16. void CALLBACK FOUndo_Release(LPUNDOATOM lpua);
  17. void FOUndo_FileRestored(LPCTSTR pszFile);
  18. // drivesx.c
  19. DWORD PathGetClusterSize(LPCTSTR pszPath);
  20. // bitbcksf.c
  21. int DataObjToFileOpString(IDataObject * pdtobj, LPTSTR * ppszSrc, LPTSTR * ppszDest);
  22. //
  23. // per-process global bitbucket data
  24. //
  25. BOOL g_fBBInited = FALSE; // have we initialized our global data yet?
  26. BOOL g_bIsProcessExplorer = FALSE; // are we the main explorer process? (if so, we persist the state info in the registry)
  27. BBSYNCOBJECT *g_pBitBucket[MAX_BITBUCKETS] = {0}; // our array of bbso's that protect each bucket
  28. HANDLE g_hgcGlobalDirtyCount = INVALID_HANDLE_VALUE;// a global counter to tell us if the global settings have changed and we need to re-read them
  29. LONG g_lProcessDirtyCount = 0; // out current dirty count; we compare this to hgcDirtyCount to see if we need to update the settings from the registry
  30. HANDLE g_hgcNumDeleters= INVALID_HANDLE_VALUE; // a global counter that indicates the total # of people who are currently doing recycle bin file operations
  31. HKEY g_hkBitBucket = NULL; // reg key that points to HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket
  32. HKEY g_hkBitBucketPerUser = NULL; // reg key that points to HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket
  33. //
  34. // prototypes
  35. //
  36. void PersistBBDriveInfo(int idDrive);
  37. BOOL IsFileDeletable(LPCTSTR pszFile);
  38. BOOL CreateRecyclerDirectory(int idDrive);
  39. void PurgeOneBitBucket(HWND hwnd, int idDrive, DWORD dwFlags);
  40. int CountDeletedFilesOnDrive(int idDrive, ULARGE_INTEGER *puliSize, int iMaxFiles);
  41. BOOL GetBBDriveSettings(int idDrive, ULONGLONG *pcbDiskSpace);
  42. void DeleteOldBBRegInfo(int idDrive);
  43. BOOL IsBitBucketInited(int idDrive);
  44. void FreeBBInfo(BBSYNCOBJECT *pbbso);
  45. SECURITY_DESCRIPTOR* CreateRecycleBinSecurityDescriptor();
  46. #define MAX_DELETE_ATTEMPTS 5
  47. #define SLEEP_DELETE_ATTEMPT 1000
  48. int DriveIDFromBBPath(LPCTSTR pszPath)
  49. {
  50. TCHAR szNetHomeDir[MAX_PATH];
  51. LPCTSTR pszTempPath = pszPath;
  52. // NOTE: If we want to make recycle bin support recycling paths under mounted volumes
  53. // we need to modify this to sniff the path for mounted volume junction points
  54. int idDrive = PathGetDriveNumber(pszTempPath);
  55. if ((idDrive == -1) && GetNetHomeDir(szNetHomeDir))
  56. {
  57. int iLen = lstrlen(szNetHomeDir);
  58. // NOTE: we don't want to let you recycle the nethomedir itself, so
  59. // insure that pszPath is larger than the nethomedir path
  60. // (neither is trailed with a backslash)
  61. if ((iLen < lstrlen(pszTempPath)) &&
  62. (PathCommonPrefix(szNetHomeDir, pszTempPath, NULL) == iLen))
  63. {
  64. // this is a subdir of the nethomedir, so we recycle it to the net home server
  65. // which is drive 26
  66. return SERVERDRIVE;
  67. }
  68. }
  69. return idDrive;
  70. }
  71. BOOL DriveIDToBBRoot(int idDrive, LPTSTR pszPath)
  72. {
  73. *pszPath = TEXT('\0');
  74. ASSERT(idDrive >= 0);
  75. if (SERVERDRIVE == idDrive)
  76. {
  77. // nethomedir case
  78. if (!GetNetHomeDir(pszPath))
  79. {
  80. ASSERT(*pszPath == TEXT('\0'));
  81. TraceMsg(TF_BITBUCKET, "BitBucket: Machine does NOT have a NETHOMEDIR");
  82. }
  83. else
  84. {
  85. // use the nethomedir
  86. ASSERT(*pszPath != TEXT('\0'));
  87. }
  88. }
  89. else
  90. {
  91. // build up the "C:\" string
  92. PathBuildRoot(pszPath, idDrive);
  93. }
  94. return (*pszPath != TEXT('\0'));
  95. }
  96. BOOL DriveIDToBBVolumeRoot(int idDrive, LPTSTR szPath)
  97. {
  98. BOOL bRet = FALSE;
  99. if (DriveIDToBBRoot(idDrive, szPath))
  100. {
  101. PathStripToRoot(szPath);
  102. if (PathAddBackslash(szPath))
  103. {
  104. bRet = TRUE;
  105. }
  106. }
  107. return bRet;
  108. }
  109. BOOL DriveIDToBBPath(int idDrive, LPTSTR pszPath)
  110. {
  111. BOOL bRet = FALSE;
  112. if (DriveIDToBBRoot(idDrive, pszPath))
  113. {
  114. // NOTE: always append the SID for the SERVERDRIVE case
  115. if ((SERVERDRIVE == idDrive) || (CMtPt_IsSecure(idDrive)))
  116. {
  117. LPTSTR pszInmate = GetUserSid(NULL);
  118. if (pszInmate)
  119. {
  120. if (PathAppend(pszPath, TEXT("RECYCLER")) &&
  121. PathAppend(pszPath, pszInmate))
  122. {
  123. bRet = TRUE;
  124. }
  125. LocalFree((HLOCAL)pszInmate);
  126. }
  127. }
  128. else
  129. {
  130. if (PathAppend(pszPath, TEXT("Recycled")))
  131. {
  132. bRet = TRUE;
  133. }
  134. }
  135. }
  136. return bRet;
  137. }
  138. TCHAR DriveChar(int idDrive)
  139. {
  140. TCHAR chDrive = (SERVERDRIVE == idDrive) ? TEXT('@') : TEXT('a') + idDrive;
  141. ASSERT(idDrive >= 0 && idDrive < MAX_BITBUCKETS);
  142. return chDrive;
  143. }
  144. //
  145. // converts "c:\recycled\whatver" to "c"
  146. // \\nethomedir\share to "@"
  147. //
  148. BOOL DriveIDToBBRegKey(int idDrive, LPTSTR pszValue)
  149. {
  150. pszValue[0] = DriveChar(idDrive);
  151. pszValue[1] = TEXT('\0');
  152. return TRUE;
  153. }
  154. // Finds out if the given UNC path points to a real netware server,
  155. // since netware in the recycle bin don't play well together.
  156. //
  157. // NOTE: We cache the last passed server\share because the MyDocs almost *never* changes so
  158. // we don't have to hit the net if the path is the same as last time.
  159. BOOL CheckForBBOnNovellServer(LPCTSTR pszUNCPath)
  160. {
  161. static TCHAR s_szLastServerQueried[MAX_PATH] = {0};
  162. static BOOL s_bLastRet;
  163. BOOL bRet = FALSE;
  164. if (pszUNCPath && pszUNCPath[0])
  165. {
  166. BOOL bIsCached;
  167. TCHAR szServerName[MAX_PATH];
  168. // reduce the UNC path to \\server\share
  169. StringCchCopy(szServerName, ARRAYSIZE(szServerName), pszUNCPath);
  170. PathStripToRoot(szServerName);
  171. ENTERCRITICAL;
  172. bIsCached = (lstrcmpi(szServerName, s_szLastServerQueried) == 0);
  173. if (bIsCached)
  174. {
  175. // use the cached retval
  176. bRet = s_bLastRet;
  177. }
  178. LEAVECRITICAL;
  179. if (!bIsCached)
  180. {
  181. TCHAR szNetwareProvider[MAX_PATH];
  182. DWORD cchNetwareProvider = ARRAYSIZE(szNetwareProvider);
  183. ASSERT(PathIsUNC(pszUNCPath));
  184. // is the netware provider installed?
  185. if (WNetGetProviderName(WNNC_NET_NETWARE, szNetwareProvider, &cchNetwareProvider) == NO_ERROR)
  186. {
  187. NETRESOURCE nr = {0};
  188. nr.dwType = RESOURCETYPE_DISK;
  189. nr.lpLocalName = NULL; // don't map a drive
  190. nr.lpRemoteName = szServerName;
  191. nr.lpProvider = szNetwareProvider; // use netware provider only
  192. if (WNetAddConnection3(NULL, &nr, NULL, NULL, 0) == NO_ERROR)
  193. {
  194. bRet = TRUE;
  195. // delete the connection (will fail if still in use)
  196. WNetCancelConnection2(szServerName, 0, FALSE);
  197. }
  198. }
  199. ENTERCRITICAL;
  200. // update the last queried path
  201. StringCchCopy(s_szLastServerQueried, ARRAYSIZE(s_szLastServerQueried), szServerName);
  202. // update cacehed retval
  203. s_bLastRet = bRet;
  204. LEAVECRITICAL;
  205. }
  206. }
  207. return bRet;
  208. }
  209. /*
  210. Network home drive code (from win95 days) is being used to support the recycle bin
  211. for users with mydocs redirected to a UNC path
  212. "Drive 26" specifies the network homedir
  213. This can return "" = (no net home dir, unknown setup, etc.)
  214. or a string ( global ) pointing to the homedir (lfn)
  215. */
  216. BOOL GetNetHomeDir(LPTSTR pszNetHomeDir)
  217. {
  218. static TCHAR s_szCachedMyDocs[MAX_PATH] = {0};
  219. static DWORD s_dwCachedTickCount = 0;
  220. DWORD dwCurrentTickCount = GetTickCount();
  221. DWORD dwTickDelta;
  222. if (dwCurrentTickCount >= s_dwCachedTickCount)
  223. {
  224. dwTickDelta = dwCurrentTickCount - s_dwCachedTickCount;
  225. }
  226. else
  227. {
  228. // protect against 49.7 day rollover by forcing refresh
  229. dwTickDelta = (11 * 1000);
  230. }
  231. // is our cache more than 10 seconds old?
  232. if (dwTickDelta > (10 * 1000))
  233. {
  234. // update our cache time
  235. s_dwCachedTickCount = dwCurrentTickCount;
  236. if (SHGetSpecialFolderPath(NULL, pszNetHomeDir, CSIDL_PERSONAL, FALSE))
  237. {
  238. TCHAR szOldBBDir[MAX_PATH];
  239. if (PathIsUNC(pszNetHomeDir))
  240. {
  241. // Remove the trailing backslash (if present)
  242. // because this string will be passed to PathCommonPrefix()
  243. PathRemoveBackslash(pszNetHomeDir);
  244. // If mydocs is redirected to a UNC path on a Novell server, we need to return FALSE when
  245. // IsFileDeletable is called, or the call to NtSetInformationFile with Disposition.DeleteFile=TRUE
  246. // will delete the file instantly even though there are open handles.
  247. if (CheckForBBOnNovellServer(pszNetHomeDir))
  248. {
  249. pszNetHomeDir[0] = TEXT('\0');
  250. }
  251. }
  252. else
  253. {
  254. pszNetHomeDir[0] = TEXT('\0');
  255. }
  256. // check to see if the mydocs path has changed
  257. if (g_pBitBucket[SERVERDRIVE] &&
  258. (g_pBitBucket[SERVERDRIVE] != (BBSYNCOBJECT *)-1) &&
  259. g_pBitBucket[SERVERDRIVE]->pidl &&
  260. SHGetPathFromIDList(g_pBitBucket[SERVERDRIVE]->pidl, szOldBBDir))
  261. {
  262. // we should always find "\RECYCLER\" because this is an old recycle bin directory.
  263. LPTSTR pszTemp = StrRStrI(szOldBBDir, NULL, TEXT("\\RECYCLER\\"));
  264. ASSERT(pszTemp);
  265. // cut the string off before the "\RECYCLER\<SID>" part so we can compare it to the current mydocs path
  266. *pszTemp = TEXT('\0');
  267. if (lstrcmpi(szOldBBDir, pszNetHomeDir) != 0)
  268. {
  269. if (*pszNetHomeDir)
  270. {
  271. TCHAR szNewBBDir[MAX_PATH];
  272. LPITEMIDLIST pidl = NULL;
  273. // mydocs was redirected to a different UNC path, so update the bbsyncobject for the SERVERDRIVE
  274. // copy the new mydocs location and add the "\RECYCLER\<SID>" part back on
  275. if (SUCCEEDED(StringCchCopy(szNewBBDir, ARRAYSIZE(szNewBBDir), pszNetHomeDir)) &&
  276. PathAppend(szNewBBDir, pszTemp + 1))
  277. {
  278. WIN32_FIND_DATA fd = {0};
  279. // create a simple pidl since "RECYCLER\<SID>" subdirectory might not exist yet
  280. fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
  281. StringCchCopy(fd.cFileName, ARRAYSIZE(fd.cFileName), szNewBBDir);
  282. SHSimpleIDListFromFindData(szNewBBDir, &fd, &pidl);
  283. }
  284. if (pidl)
  285. {
  286. LPITEMIDLIST pidlOld;
  287. ULARGE_INTEGER ulFreeUser, ulTotal, ulFree;
  288. DWORD dwClusterSize;
  289. BOOL bUpdateSize = FALSE;
  290. if (SHGetDiskFreeSpaceEx(pszNetHomeDir, &ulFreeUser, &ulTotal, &ulFree))
  291. {
  292. dwClusterSize = PathGetClusterSize(pszNetHomeDir);
  293. bUpdateSize = TRUE;
  294. }
  295. ENTERCRITICAL;
  296. // swap in the new pidl
  297. pidlOld = g_pBitBucket[SERVERDRIVE]->pidl;
  298. g_pBitBucket[SERVERDRIVE]->pidl = pidl;
  299. ILFree(pidlOld);
  300. // set the cchBBDir
  301. g_pBitBucket[SERVERDRIVE]->cchBBDir = lstrlen(szNewBBDir);
  302. g_pBitBucket[SERVERDRIVE]->fInited = TRUE;
  303. // update the size fields
  304. if (bUpdateSize)
  305. {
  306. ULARGE_INTEGER ulMaxSize;
  307. g_pBitBucket[SERVERDRIVE]->dwClusterSize = dwClusterSize;
  308. g_pBitBucket[SERVERDRIVE]->qwDiskSize = ulTotal.QuadPart;
  309. // we limit the max size of the recycle bin to ~4 gig
  310. ulMaxSize.QuadPart = min(((ulTotal.QuadPart / 100) * g_pBitBucket[SERVERDRIVE]->iPercent), (DWORD)-1);
  311. ASSERT(ulMaxSize.HighPart == 0);
  312. g_pBitBucket[SERVERDRIVE]->cbMaxSize = ulMaxSize.LowPart;
  313. }
  314. LEAVECRITICAL;
  315. }
  316. }
  317. else
  318. {
  319. // mydocs was redireced back to a local path, so flag this drive as not inited so we wont do any more
  320. // recycle bin operations on it.
  321. ENTERCRITICAL;
  322. g_pBitBucket[SERVERDRIVE]->fInited = FALSE;
  323. LEAVECRITICAL;
  324. }
  325. }
  326. else
  327. {
  328. // the mydocs previously to pointed to \\foo\bar, and the user has set it back to that path again.
  329. // so flag the drive as inited so we can start using it again.
  330. if (g_pBitBucket[SERVERDRIVE]->fInited == FALSE)
  331. {
  332. ENTERCRITICAL;
  333. g_pBitBucket[SERVERDRIVE]->fInited = TRUE;
  334. LEAVECRITICAL;
  335. }
  336. }
  337. }
  338. }
  339. else
  340. {
  341. pszNetHomeDir[0] = TEXT('\0');
  342. }
  343. ENTERCRITICAL;
  344. // update the cached value
  345. StringCchCopy(s_szCachedMyDocs, ARRAYSIZE(s_szCachedMyDocs), pszNetHomeDir);
  346. LEAVECRITICAL;
  347. }
  348. else
  349. {
  350. ENTERCRITICAL;
  351. // cache is still good
  352. StringCchCopy(pszNetHomeDir, MAX_PATH, s_szCachedMyDocs);
  353. LEAVECRITICAL;
  354. }
  355. return (BOOL)pszNetHomeDir[0];
  356. }
  357. STDAPI_(BOOL) IsBitBucketableDrive(int idDrive)
  358. {
  359. BOOL bRet = FALSE;
  360. TCHAR szBBRoot[MAX_PATH];
  361. TCHAR szFileSystem[MAX_PATH];
  362. TCHAR szPath[4];
  363. DWORD dwAllowBitBuck = SHRestricted(REST_ALLOWBITBUCKDRIVES);
  364. if ((idDrive < 0) ||
  365. (idDrive >= MAX_BITBUCKETS) ||
  366. (g_pBitBucket[idDrive] == (BBSYNCOBJECT *)-1))
  367. {
  368. // we dont support recycle bin for the general UNC case or we have
  369. // flagged this drive as not having a recycle bin for one reason or another.
  370. return FALSE;
  371. }
  372. if (IsBitBucketInited(idDrive))
  373. {
  374. // the struct is allready allocated and inited, so this is a bitbucketable drive
  375. return TRUE;
  376. }
  377. if (idDrive == SERVERDRIVE)
  378. {
  379. bRet = GetNetHomeDir(szBBRoot);
  380. }
  381. else if ((GetDriveType(PathBuildRoot(szPath, idDrive)) == DRIVE_FIXED) ||
  382. (dwAllowBitBuck & (1 << idDrive)))
  383. {
  384. bRet = TRUE;
  385. }
  386. if (bRet && (idDrive != SERVERDRIVE))
  387. {
  388. // also check to make sure that the drive isint RAW (unformatted)
  389. if (DriveIDToBBRoot(idDrive, szBBRoot))
  390. {
  391. if(!GetVolumeInformation(szBBRoot, NULL, 0, NULL, NULL, NULL, szFileSystem, ARRAYSIZE(szFileSystem)) ||
  392. lstrcmpi(szFileSystem, TEXT("RAW")) == 0)
  393. {
  394. bRet = FALSE;
  395. }
  396. else
  397. {
  398. // the drive better be NTFS, FAT or FAT32, else we need to know about it and handle it properly
  399. ASSERT((lstrcmpi(szFileSystem, TEXT("NTFS")) == 0) ||
  400. (lstrcmpi(szFileSystem, TEXT("FAT")) == 0) ||
  401. (lstrcmpi(szFileSystem, TEXT("FAT32")) == 0));
  402. }
  403. }
  404. else
  405. {
  406. // path must be too long
  407. bRet = FALSE;
  408. }
  409. }
  410. return bRet;
  411. }
  412. // c:\recycled => c:\recycled\info2 (the new IE4/NT5/Win98 info file)
  413. __inline BOOL GetBBInfo2FileSpec(LPTSTR pszBBPath, LPTSTR pszInfo)
  414. {
  415. return PathCombine(pszInfo, pszBBPath, c_szInfo2) ? TRUE : FALSE;
  416. }
  417. // c:\recycled => c:\recycled\info (the old win95/NT4 info file)
  418. __inline BOOL GetBBInfoFileSpec(LPTSTR pszBBPath, LPTSTR pszInfo)
  419. {
  420. return PathCombine(pszInfo, pszBBPath, c_szInfo) ? TRUE : FALSE;
  421. }
  422. __inline BOOL IsBitBucketInited(int idDrive)
  423. {
  424. BOOL bRet;
  425. // InitBBDriveInfo could fail and we free and set g_pBitBucket[idDrive] = -1. So there
  426. // is a small window between when we check g_pBitBucket[idDrive] and when we deref
  427. // g_pBitBucket[idDrive]->fInited, to protect against g_pBitBucket[idDrive] being freed
  428. // in this window we use the crit sec.
  429. ENTERCRITICAL;
  430. bRet = (g_pBitBucket[idDrive] &&
  431. (g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1) &&
  432. g_pBitBucket[idDrive]->fInited);
  433. LEAVECRITICAL;
  434. return bRet;
  435. }
  436. BOOL RevOldBBInfoFileHeader(HANDLE hFile, BBDATAHEADER *pbbdh)
  437. {
  438. // Verify that this is a valid info file
  439. if (pbbdh->cbDataEntrySize == sizeof(BBDATAENTRYW))
  440. {
  441. if (pbbdh->idVersion == BITBUCKET_WIN95_VERSION ||
  442. pbbdh->idVersion == BITBUCKET_NT4_VERSION ||
  443. pbbdh->idVersion == BITBUCKET_WIN98IE4INT_VERSION)
  444. {
  445. DWORD dwBytesWritten;
  446. // now seek back to 0 and write in the new stuff
  447. pbbdh->idVersion = BITBUCKET_FINAL_VERSION;
  448. SetFilePointer(hFile, 0, NULL, FILE_BEGIN); // go to the beginning
  449. WriteFile(hFile, (LPBYTE)pbbdh, sizeof(BBDATAHEADER), &dwBytesWritten, NULL);
  450. ASSERT(dwBytesWritten == sizeof(BBDATAHEADER));
  451. }
  452. return (pbbdh->idVersion == BITBUCKET_FINAL_VERSION);
  453. }
  454. return FALSE;
  455. }
  456. //
  457. // We need to update the cCurrent and cFiles in the info file header
  458. // for compat with win98/IE4 machines.
  459. //
  460. BOOL UpdateBBInfoFileHeader(int idDrive)
  461. {
  462. BBDATAHEADER bbdh = {0, 0, 0, sizeof(BBDATAENTRYW), 0}; // defaults
  463. HANDLE hFile;
  464. BOOL bRet = FALSE; // assume failure;
  465. // Pass 1 for the # of retry attempts since we are called during shutdown and if another process
  466. // is using the recycle bin we will hang and get the "End Task" dialog (bad!).
  467. hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 1);
  468. if (hFile != INVALID_HANDLE_VALUE)
  469. {
  470. BBDATAENTRYW bbdew;
  471. DWORD dwBytesRead;
  472. SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
  473. bRet = ReadFile(hFile, &bbdh, sizeof(BBDATAHEADER), &dwBytesRead, NULL);
  474. if (bRet && dwBytesRead == sizeof(BBDATAHEADER))
  475. {
  476. ULARGE_INTEGER uliSize;
  477. DWORD dwBytesWritten;
  478. bbdh.idVersion = BITBUCKET_FINAL_VERSION;
  479. bbdh.cCurrent = SHGlobalCounterGetValue(g_pBitBucket[idDrive]->hgcNextFileNum);
  480. bbdh.cFiles = CountDeletedFilesOnDrive(idDrive, &uliSize, 0);
  481. bbdh.dwSize = uliSize.LowPart;
  482. SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
  483. WriteFile(hFile, (LPBYTE)&bbdh, sizeof(BBDATAHEADER), &dwBytesWritten, NULL);
  484. ASSERT(dwBytesWritten == sizeof(BBDATAHEADER));
  485. bRet = TRUE;
  486. }
  487. ASSERT((g_pBitBucket[idDrive]->fIsUnicode && (sizeof(BBDATAENTRYW) == bbdh.cbDataEntrySize)) ||
  488. (!g_pBitBucket[idDrive]->fIsUnicode && (sizeof(BBDATAENTRYA) == bbdh.cbDataEntrySize)));
  489. // Since we dont flag entries that were deleted in the info file as deleted
  490. // immeadeately, we need to go through and mark them as such now
  491. while (ReadNextDataEntry(hFile, &bbdew, TRUE, idDrive))
  492. {
  493. // do nothing
  494. }
  495. CloseBBInfoFile(hFile, idDrive);
  496. }
  497. if (!bRet)
  498. {
  499. TraceMsg(TF_BITBUCKET, "Bitbucket: failed to update drive %d for win98/NT4 compat!!", idDrive);
  500. }
  501. return bRet;
  502. }
  503. BOOL ResetInfoFileHeader(HANDLE hFile, BOOL fIsUnicode)
  504. {
  505. DWORD dwBytesWritten;
  506. BBDATAHEADER bbdh = { BITBUCKET_FINAL_VERSION, 0, 0,
  507. fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA), 0};
  508. BOOL fSuccess = FALSE;
  509. ASSERT(INVALID_HANDLE_VALUE != hFile);
  510. if (-1 != SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
  511. {
  512. if (WriteFile(hFile, (LPBYTE)&bbdh, sizeof(BBDATAHEADER), &dwBytesWritten, NULL) &&
  513. dwBytesWritten == sizeof(BBDATAHEADER))
  514. {
  515. if (SetEndOfFile(hFile))
  516. {
  517. fSuccess = TRUE;
  518. }
  519. }
  520. }
  521. return fSuccess;
  522. }
  523. BOOL CreateInfoFile(idDrive)
  524. {
  525. TCHAR szBBPath[MAX_PATH];
  526. TCHAR szInfoFile[MAX_PATH];
  527. HANDLE hFile;
  528. BOOL fSuccess = FALSE;
  529. if (DriveIDToBBPath(idDrive, szBBPath) &&
  530. GetBBInfo2FileSpec(szBBPath, szInfoFile))
  531. {
  532. hFile = OpenBBInfoFile(idDrive, OPENBBINFO_CREATE, 0);
  533. if (hFile != INVALID_HANDLE_VALUE)
  534. {
  535. fSuccess = ResetInfoFileHeader(hFile, TRUE);
  536. CloseHandle(hFile);
  537. if (fSuccess)
  538. {
  539. // We explicitly call SHChangeNotify so that we can generate a change specifically
  540. // for the info file. The recycle bin shell folder will then ignore any updates to
  541. // the info file.
  542. SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, szInfoFile, NULL);
  543. }
  544. }
  545. }
  546. if (!fSuccess)
  547. {
  548. TraceMsg(TF_WARNING, "Bitbucket: faild to create file info file!!");
  549. }
  550. return fSuccess;
  551. }
  552. // GetNT4BBAcl() - Creates a ACL structure for allowing access for
  553. // only the current user,the administrators group, or the system.
  554. // Returns a pointer to an access control list
  555. // structure in the local heap; it can be
  556. // free'd with LocalFree.
  557. //
  558. // !! HACKHACK !! - This code was basically taken right out of NT4 so that we can
  559. // compare against the old NT4 recycle bin ACL. The new helper function
  560. // GetShellSecurityDescriptor puts the ACE's in a different order
  561. // than this function, and so we memcmp the ACL against botht this
  562. // one and the new win2k one.
  563. PACL GetNT4BBAcl()
  564. {
  565. SID_IDENTIFIER_AUTHORITY authNT = SECURITY_NT_AUTHORITY;
  566. PACL pAcl = NULL;
  567. PTOKEN_USER pUser = NULL;
  568. PSID psidSystem = NULL;
  569. PSID psidAdmin = NULL;
  570. DWORD cbAcl;
  571. DWORD aceIndex;
  572. ACE_HEADER * lpAceHeader;
  573. UINT nCnt = 2; // inheritable; so two ACE's for each user
  574. BOOL bSuccess = FALSE;
  575. //
  576. // Get the USER token so we can grab its SID for the DACL.
  577. //
  578. pUser = GetUserToken(NULL);
  579. if (!pUser)
  580. {
  581. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to get user. Error = %d", GetLastError());
  582. goto Exit;
  583. }
  584. //
  585. // Get the system sid
  586. //
  587. if (!AllocateAndInitializeSid(&authNT, 1, SECURITY_LOCAL_SYSTEM_RID,
  588. 0, 0, 0, 0, 0, 0, 0, &psidSystem)) {
  589. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to initialize system sid. Error = %d", GetLastError());
  590. goto Exit;
  591. }
  592. //
  593. // Get the Admin sid
  594. //
  595. if (!AllocateAndInitializeSid(&authNT, 2, SECURITY_BUILTIN_DOMAIN_RID,
  596. DOMAIN_ALIAS_RID_ADMINS, 0, 0,
  597. 0, 0, 0, 0, &psidAdmin)) {
  598. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to initialize admin sid. Error = %d", GetLastError());
  599. goto Exit;
  600. }
  601. //
  602. // Allocate space for the DACL
  603. //
  604. cbAcl = sizeof(ACL) +
  605. (nCnt * GetLengthSid(pUser->User.Sid)) +
  606. (nCnt * GetLengthSid(psidSystem)) +
  607. (nCnt * GetLengthSid(psidAdmin)) +
  608. (nCnt * 3 * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)));
  609. pAcl = (PACL)LocalAlloc(LPTR, cbAcl);
  610. if (!pAcl) {
  611. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to allocate acl. Error = %d", GetLastError());
  612. goto Exit;
  613. }
  614. if (!InitializeAcl(pAcl, cbAcl, ACL_REVISION)) {
  615. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to initialize acl. Error = %d", GetLastError());
  616. goto Exit;
  617. }
  618. //
  619. // Add Aces for User, System, and Admin. Non-inheritable ACEs first
  620. //
  621. aceIndex = 0;
  622. if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, pUser->User.Sid)) {
  623. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
  624. goto Exit;
  625. }
  626. aceIndex++;
  627. if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, psidSystem)) {
  628. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
  629. goto Exit;
  630. }
  631. aceIndex++;
  632. if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, psidAdmin)) {
  633. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
  634. goto Exit;
  635. }
  636. //
  637. // Now the inheritable ACEs
  638. //
  639. aceIndex++;
  640. if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, pUser->User.Sid)) {
  641. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
  642. goto Exit;
  643. }
  644. if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
  645. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to get ace (%d). Error = %d", aceIndex, GetLastError());
  646. goto Exit;
  647. }
  648. lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
  649. aceIndex++;
  650. if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidSystem)) {
  651. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
  652. goto Exit;
  653. }
  654. if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
  655. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to get ace (%d). Error = %d", aceIndex, GetLastError());
  656. goto Exit;
  657. }
  658. lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
  659. aceIndex++;
  660. if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidAdmin)) {
  661. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
  662. goto Exit;
  663. }
  664. if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
  665. TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to get ace (%d). Error = %d", aceIndex, GetLastError());
  666. goto Exit;
  667. }
  668. lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
  669. bSuccess = TRUE;
  670. Exit:
  671. if (pUser)
  672. LocalFree(pUser);
  673. if (psidSystem)
  674. FreeSid(psidSystem);
  675. if (psidAdmin)
  676. FreeSid(psidAdmin);
  677. if (!bSuccess && pAcl)
  678. {
  679. LocalFree(pAcl);
  680. pAcl = NULL;
  681. }
  682. return pAcl;
  683. }
  684. //
  685. // this checks to make sure that the users recycle bin directory is properly acl'ed
  686. //
  687. BOOL CheckRecycleBinAcls(idDrive)
  688. {
  689. BOOL bIsSecure = TRUE;
  690. TCHAR szBBPath[MAX_PATH];
  691. PSECURITY_DESCRIPTOR psdCurrent = NULL;
  692. PSID psidOwner;
  693. PACL pdaclCurrent;
  694. DWORD dwLengthNeeded = 0;
  695. if ((idDrive == SERVERDRIVE) || !CMtPt_IsSecure(idDrive))
  696. {
  697. // either redirected mydocs case (assume mydocs is already secured) or it
  698. // is not an NTFS drive, so no ACL's to check
  699. return TRUE;
  700. }
  701. if (!DriveIDToBBPath(idDrive, szBBPath))
  702. {
  703. return FALSE;
  704. }
  705. if (!GetFileSecurity(szBBPath,
  706. DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
  707. NULL,
  708. 0,
  709. &dwLengthNeeded) &&
  710. (GetLastError() == ERROR_INSUFFICIENT_BUFFER))
  711. {
  712. psdCurrent = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwLengthNeeded);
  713. }
  714. if (psdCurrent)
  715. {
  716. BOOL bDefault = FALSE;
  717. BOOL bPresent = FALSE;
  718. if (GetFileSecurity(szBBPath,
  719. DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
  720. psdCurrent,
  721. dwLengthNeeded,
  722. &dwLengthNeeded) &&
  723. GetSecurityDescriptorOwner(psdCurrent, &psidOwner, &bDefault) && psidOwner &&
  724. GetSecurityDescriptorDacl(psdCurrent, &bPresent, &pdaclCurrent, &bDefault) && pdaclCurrent)
  725. {
  726. PTOKEN_USER pUser = GetUserToken(NULL);
  727. if (pUser)
  728. {
  729. if (!EqualSid(psidOwner, pUser->User.Sid))
  730. {
  731. // the user is not the owner of the dir, check to see if the owner is the Administrators group or the System
  732. // (we consider the directory to be secure if the owner is either of these two)
  733. SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
  734. PSID psidAdministrators = NULL;
  735. PSID psidSystem = NULL;
  736. if (AllocateAndInitializeSid(&sia, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdministrators) &&
  737. AllocateAndInitializeSid(&sia, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &psidSystem))
  738. {
  739. if (!EqualSid(psidOwner, psidAdministrators) && !EqualSid(psidOwner, psidSystem))
  740. {
  741. // directory is not owned by the user, or the Administrators group or the system, we thus consider it unsecure.
  742. TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: dir %s has possibly unsecure owner!", szBBPath);
  743. bIsSecure = FALSE;
  744. }
  745. if (psidAdministrators)
  746. FreeSid(psidAdministrators);
  747. if (psidSystem)
  748. FreeSid(psidSystem);
  749. }
  750. else
  751. {
  752. TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: AllocateAndInitializeSid failed, assuming %s is unsecure", szBBPath);
  753. bIsSecure = FALSE;
  754. }
  755. }
  756. if (bIsSecure)
  757. {
  758. // directory owner checked out ok, lets see if the acl is what we expect...
  759. SECURITY_DESCRIPTOR* psdRecycle = CreateRecycleBinSecurityDescriptor();
  760. if (psdRecycle)
  761. {
  762. // to compare acls, we do a size check and then a memcmp (aclui code does the same)
  763. if ((psdRecycle->Dacl->AclSize != pdaclCurrent->AclSize) ||
  764. (memcmp(psdRecycle->Dacl, pdaclCurrent, pdaclCurrent->AclSize) != 0))
  765. {
  766. // acl sizes were different or they didn't memcmp, so check against the old NT4 style acl
  767. // (in NT4 we added the ACE's in a different order which causes the memcmp to fail, even
  768. // though the ACL is equivilant)
  769. PACL pAclNT4 = GetNT4BBAcl();
  770. if (pAclNT4)
  771. {
  772. // do the same size / memcmp check
  773. if ((pAclNT4->AclSize != pdaclCurrent->AclSize) ||
  774. (memcmp(pAclNT4, pdaclCurrent, pdaclCurrent->AclSize) != 0))
  775. {
  776. // acl sizes were different or they didn't memcmp, so assume the dir is unsecure
  777. bIsSecure = FALSE;
  778. }
  779. LocalFree(pAclNT4);
  780. }
  781. else
  782. {
  783. TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: GetNT4BBSecurityAttributes failed, assuming %s is unsecure", szBBPath);
  784. bIsSecure = FALSE;
  785. }
  786. }
  787. LocalFree(psdRecycle);
  788. }
  789. else
  790. {
  791. TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: CreateRecycleBinSecurityDescriptor failed, assuming %s is unsecure", szBBPath);
  792. bIsSecure = FALSE;
  793. }
  794. }
  795. LocalFree(pUser);
  796. }
  797. else
  798. {
  799. // couldnt' get the users sid, so assume the dir is unsecure
  800. TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: failed to get the users sid, assuming %s is unsecure", szBBPath);
  801. bIsSecure = FALSE;
  802. }
  803. }
  804. else
  805. {
  806. // GetFileSecurity failed, assume the dir is unsecure
  807. TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: GetFileSecurity failed, assuming %s is unsecure", szBBPath);
  808. bIsSecure = FALSE;
  809. }
  810. LocalFree(psdCurrent);
  811. }
  812. else
  813. {
  814. // GetFileSecurity failed, assume the dir is unsecure
  815. TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: GetFileSecurity failed or memory allocation failed, assume %s is unsecure", szBBPath);
  816. bIsSecure = FALSE;
  817. }
  818. if (!bIsSecure)
  819. {
  820. TCHAR szDriveName[MAX_PATH];
  821. if (DriveIDToBBRoot(idDrive, szDriveName))
  822. {
  823. if (ShellMessageBox(HINST_THISDLL,
  824. NULL,
  825. MAKEINTRESOURCE(IDS_RECYCLEBININVALIDFORMAT),
  826. MAKEINTRESOURCE(IDS_WASTEBASKET),
  827. MB_YESNO | MB_ICONEXCLAMATION | MB_SETFOREGROUND,
  828. szDriveName) == IDYES)
  829. {
  830. TCHAR szBBPathToNuke[MAX_PATH + 1]; // +1 for double null
  831. SHFILEOPSTRUCT fo = {NULL,
  832. FO_DELETE,
  833. szBBPathToNuke,
  834. NULL,
  835. FOF_NOCONFIRMATION | FOF_SILENT,
  836. FALSE,
  837. NULL,
  838. NULL};
  839. if (SUCCEEDED(StringCchCopy(szBBPathToNuke, ARRAYSIZE(szBBPathToNuke) - 1, szBBPath)))
  840. {
  841. szBBPathToNuke[lstrlen(szBBPathToNuke) + 1] = TEXT('\0'); // double null terminate
  842. // try to nuke the old recycle bin for this drive
  843. if (SHFileOperation(&fo) == ERROR_SUCCESS)
  844. {
  845. // now create the new secure one
  846. bIsSecure = CreateRecyclerDirectory(idDrive);
  847. }
  848. }
  849. }
  850. }
  851. }
  852. return bIsSecure;
  853. }
  854. //
  855. // this verifies the info file header infomation
  856. //
  857. BOOL VerifyBBInfoFileHeader(int idDrive)
  858. {
  859. BBDATAHEADER bbdh = {0, 0, 0, sizeof(BBDATAENTRYW), 0}; // defaults
  860. HANDLE hFile;
  861. TCHAR szBBPath[MAX_PATH];
  862. TCHAR szInfo[MAX_PATH];
  863. BOOL fSuccess = FALSE;
  864. // check for the the old win95 INFO file
  865. if (!DriveIDToBBPath(idDrive, szBBPath) ||
  866. !GetBBInfoFileSpec(szBBPath, szInfo))
  867. {
  868. return FALSE;
  869. }
  870. hFile = CreateFile(szInfo, GENERIC_READ | GENERIC_WRITE,
  871. FILE_SHARE_READ | FILE_SHARE_WRITE,
  872. NULL, OPEN_EXISTING, FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_RANDOM_ACCESS, NULL);
  873. if (hFile != INVALID_HANDLE_VALUE)
  874. {
  875. DWORD dwBytesRead;
  876. if (ReadFile(hFile, &bbdh, sizeof(BBDATAHEADER), &dwBytesRead, NULL) &&
  877. (dwBytesRead == sizeof(BBDATAHEADER)))
  878. {
  879. TraceMsg(TF_BITBUCKET, "Bitbucket: migrating info in old database file %s", szInfo);
  880. fSuccess = RevOldBBInfoFileHeader(hFile, &bbdh);
  881. }
  882. CloseHandle(hFile);
  883. if (fSuccess)
  884. {
  885. // rename from INFO -> INFO2
  886. TCHAR szInfoNew[MAX_PATH];
  887. GetBBInfo2FileSpec(szBBPath, szInfoNew);
  888. TraceMsg(TF_BITBUCKET, "Bitbucket: renaming %s to %s !!", szInfo, szInfoNew);
  889. SHMoveFile(szInfo, szInfoNew, SHCNE_RENAMEITEM);
  890. }
  891. else
  892. {
  893. goto bad_info_file;
  894. }
  895. }
  896. // Failed to open or rev the old info file. Next, we check for the existance of the new info2 file
  897. // to see if the drive has a bitbucket format that is greater than what we can handle
  898. if (!fSuccess)
  899. {
  900. hFile = OpenBBInfoFile(idDrive, OPENBBINFO_READ, 0);
  901. if (hFile != INVALID_HANDLE_VALUE)
  902. {
  903. BOOL bRet;
  904. DWORD dwBytesRead;
  905. SetFilePointer(hFile, 0, NULL, FILE_BEGIN); // go to the beginning
  906. bRet = ReadFile(hFile, &bbdh, sizeof(BBDATAHEADER), &dwBytesRead, NULL);
  907. CloseBBInfoFile(hFile, idDrive);
  908. if ((bRet == 0) ||
  909. (dwBytesRead != sizeof(BBDATAHEADER)) ||
  910. (bbdh.idVersion > BITBUCKET_FINAL_VERSION) ||
  911. (bbdh.cbDataEntrySize != sizeof(BBDATAENTRYA) && bbdh.cbDataEntrySize != sizeof(BBDATAENTRYW)))
  912. {
  913. TCHAR szDriveName[MAX_PATH];
  914. // either we had a corrupt win95 info file, or an info2 file whose version is greater than ours
  915. // so we just empy the recycle bin.
  916. bad_info_file:
  917. // since we failed to read the existing header, assume the native format
  918. g_pBitBucket[idDrive]->fIsUnicode = TRUE;
  919. // find out which drive it is that is corrupt
  920. if (!DriveIDToBBRoot(idDrive, szDriveName) ||
  921. (ShellMessageBox(HINST_THISDLL,
  922. NULL,
  923. MAKEINTRESOURCE(IDS_RECYCLEBININVALIDFORMAT),
  924. MAKEINTRESOURCE(IDS_WASTEBASKET),
  925. MB_YESNO | MB_ICONEXCLAMATION | MB_SETFOREGROUND,
  926. szDriveName) == IDYES))
  927. {
  928. // nuke this bucket since it is hosed
  929. PurgeOneBitBucket(NULL, idDrive, SHERB_NOCONFIRMATION);
  930. return TRUE;
  931. }
  932. hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
  933. if (hFile != INVALID_HANDLE_VALUE)
  934. {
  935. DWORD dwBytesWritten;
  936. bbdh.idVersion = BITBUCKET_FINAL_VERSION;
  937. if (bbdh.cbDataEntrySize != sizeof(BBDATAENTRYW) &&
  938. bbdh.cbDataEntrySize != sizeof(BBDATAENTRYA))
  939. {
  940. // assume the native data entry size
  941. bbdh.cbDataEntrySize = sizeof(BBDATAENTRYW);
  942. }
  943. g_pBitBucket[idDrive]->fIsUnicode = (bbdh.cbDataEntrySize == sizeof(BBDATAENTRYW));
  944. SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
  945. WriteFile(hFile, (LPBYTE)&bbdh, sizeof(BBDATAHEADER), &dwBytesWritten, NULL);
  946. ASSERT(dwBytesWritten == sizeof(BBDATAHEADER));
  947. CloseBBInfoFile(hFile, idDrive);
  948. fSuccess = TRUE;
  949. }
  950. else
  951. {
  952. fSuccess = FALSE;
  953. }
  954. }
  955. else if (bbdh.idVersion != BITBUCKET_FINAL_VERSION)
  956. {
  957. // old info2 information
  958. fSuccess = RevOldBBInfoFileHeader(hFile, &bbdh);
  959. }
  960. else
  961. {
  962. // the header info is current
  963. fSuccess = TRUE;
  964. }
  965. }
  966. else
  967. {
  968. // brand spanking new drive, so go create the info file now.
  969. fSuccess = CreateInfoFile(idDrive);
  970. }
  971. }
  972. // get the only relevant thing in the header, whether it is unicode or not
  973. g_pBitBucket[idDrive]->fIsUnicode = (bbdh.cbDataEntrySize == sizeof(BBDATAENTRYW));
  974. return fSuccess;
  975. }
  976. LONG FindInitialNextFileNum(idDrive)
  977. {
  978. int iRet = 0;
  979. TCHAR szBBFileSpec[MAX_PATH];
  980. WIN32_FIND_DATA fd;
  981. HANDLE hFind;
  982. if (DriveIDToBBPath(idDrive, szBBFileSpec) &&
  983. PathAppend(szBBFileSpec, TEXT("D*.*")))
  984. {
  985. hFind = FindFirstFile(szBBFileSpec, &fd);
  986. if (hFind != INVALID_HANDLE_VALUE)
  987. {
  988. do
  989. {
  990. if (!PathIsDotOrDotDot(fd.cFileName) && lstrcmpi(fd.cFileName, c_szDesktopIni))
  991. {
  992. int iCurrent = BBPathToIndex(fd.cFileName);
  993. if (iCurrent > iRet)
  994. {
  995. iRet = iCurrent;
  996. }
  997. }
  998. } while (FindNextFile(hFind, &fd));
  999. FindClose(hFind);
  1000. }
  1001. }
  1002. ASSERT(iRet >= 0);
  1003. return (LONG)iRet;
  1004. }
  1005. BOOL InitBBDriveInfo(int idDrive)
  1006. {
  1007. BOOL bRet = FALSE;
  1008. TCHAR szName[MAX_PATH];
  1009. DWORD dwDisp;
  1010. LONG lInitialCount = 0;
  1011. // build up the string "BitBucket.<drive letter>"
  1012. if (SUCCEEDED(StringCchCopy(szName, ARRAYSIZE(szName), TEXT("BitBucket."))) &&
  1013. DriveIDToBBRegKey(idDrive, &szName[10]) && // 10 for length of "BitBucket."
  1014. SUCCEEDED(StringCchCat(szName, ARRAYSIZE(szName), TEXT(".DirtyCount"))))
  1015. {
  1016. g_pBitBucket[idDrive]->hgcDirtyCount = SHGlobalCounterCreateNamed(szName, 0); // BitBucket.<drive letter>.DirtyCount
  1017. if (g_pBitBucket[idDrive]->hgcDirtyCount == INVALID_HANDLE_VALUE)
  1018. {
  1019. ASSERTMSG(FALSE, "BitBucket: failed to create hgcDirtyCount for drive %d !!", idDrive);
  1020. return FALSE;
  1021. }
  1022. // now create the subkey for this drive
  1023. DriveIDToBBRegKey(idDrive, szName);
  1024. // the per-user key is volatile since we only use this for temporary bookeeping (eg need to purge / compact).
  1025. // the exception to this rule is the SERVERDRIVE case, because this is the users "My Documents" so we let it
  1026. // and we also need to store the path under that key (it has to roam with the user)
  1027. if (RegCreateKeyEx(g_hkBitBucketPerUser,
  1028. szName,
  1029. 0,
  1030. NULL,
  1031. (SERVERDRIVE == idDrive) ? REG_OPTION_NON_VOLATILE : REG_OPTION_VOLATILE,
  1032. KEY_SET_VALUE | KEY_QUERY_VALUE,
  1033. NULL,
  1034. &g_pBitBucket[idDrive]->hkeyPerUser,
  1035. &dwDisp) != ERROR_SUCCESS)
  1036. {
  1037. ASSERTMSG(FALSE, "BitBucket: Could not create HKCU BitBucket registry key for drive %s", szName);
  1038. g_pBitBucket[idDrive]->hkeyPerUser = NULL;
  1039. return FALSE;
  1040. }
  1041. if (RegCreateKeyEx(g_hkBitBucket,
  1042. szName,
  1043. 0,
  1044. NULL,
  1045. REG_OPTION_NON_VOLATILE,
  1046. MAXIMUM_ALLOWED, // user may or may not have permissions to change global bb settings
  1047. NULL,
  1048. &g_pBitBucket[idDrive]->hkey,
  1049. &dwDisp) != ERROR_SUCCESS)
  1050. {
  1051. TraceMsg(TF_BITBUCKET, "BitBucket: Could not create HKLM BitBucket registry key for drive %s, falling back to HKLM global key! ", szName);
  1052. if (RegOpenKeyEx(g_hkBitBucket,
  1053. NULL,
  1054. 0,
  1055. KEY_QUERY_VALUE, // use KEY_QUERY_VALUE so when we read the settings for this drive we will read the global values (but not try and overwrite them!)
  1056. &g_pBitBucket[idDrive]->hkey) != ERROR_SUCCESS)
  1057. {
  1058. ASSERTMSG(FALSE, "BitBucket: Could not duplicate HKLM Global Bitbucket key!");
  1059. return FALSE;
  1060. }
  1061. }
  1062. // load the rest of the settings (hgcNextFileNum, fIsUnicode, iPercent, cbMaxSize, dwClusterSize, and fNukeOnDelete)
  1063. bRet = GetBBDriveSettings(idDrive, NULL);
  1064. }
  1065. return bRet;
  1066. }
  1067. BOOL AllocBBDriveInfo(int idDrive)
  1068. {
  1069. TCHAR szBBPath[MAX_PATH];
  1070. LPITEMIDLIST pidl = NULL;
  1071. BOOL bRet = FALSE; // assume failure
  1072. if (DriveIDToBBPath(idDrive, szBBPath))
  1073. {
  1074. pidl = ILCreateFromPath(szBBPath);
  1075. if (!pidl && !PathFileExists(szBBPath))
  1076. {
  1077. if (CreateRecyclerDirectory(idDrive))
  1078. {
  1079. pidl = ILCreateFromPath(szBBPath);
  1080. }
  1081. }
  1082. }
  1083. if (pidl)
  1084. {
  1085. BBSYNCOBJECT *pbbso = (BBSYNCOBJECT *)LocalAlloc(LPTR, sizeof(*pbbso));
  1086. if (pbbso)
  1087. {
  1088. if (SHInterlockedCompareExchange(&g_pBitBucket[idDrive], pbbso, NULL))
  1089. {
  1090. DWORD dwInitialTickCount = GetTickCount();
  1091. BOOL bKeepWaiting = TRUE;
  1092. // Some other thread beat us to creating this bitbucket.
  1093. // We can't return until that thread has inited the bitbucket
  1094. // since some of the members might not be valid yet.
  1095. LocalFree(pbbso);
  1096. ILFree(pidl);
  1097. do
  1098. {
  1099. if (g_pBitBucket[idDrive] == (BBSYNCOBJECT *)-1)
  1100. {
  1101. // this volume is flagged as not being recycleable for some reason...
  1102. break;
  1103. }
  1104. // Spin until the bitbucket struct is inited
  1105. Sleep(50);
  1106. bKeepWaiting = !IsBitBucketInited(idDrive);
  1107. // we should never spin more than ~15 seconds
  1108. if (((GetTickCount() - dwInitialTickCount) >= (60 * 1000)) && bKeepWaiting)
  1109. {
  1110. ASSERTMSG(FALSE, "AllocBBDriveInfo: other thread took longer that 1 minute to init a bitbucket?!?");
  1111. break;
  1112. }
  1113. } while (bKeepWaiting);
  1114. return ((g_pBitBucket[idDrive] != NULL) &&
  1115. (g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1));
  1116. }
  1117. ASSERT(g_pBitBucket[idDrive] && (g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1));
  1118. g_pBitBucket[idDrive]->pidl = pidl;
  1119. g_pBitBucket[idDrive]->cchBBDir = lstrlen(szBBPath);
  1120. if (InitBBDriveInfo(idDrive))
  1121. {
  1122. // Success!!
  1123. g_pBitBucket[idDrive]->fInited = TRUE;
  1124. bRet = TRUE;
  1125. }
  1126. else
  1127. {
  1128. // we failed for some weird reason
  1129. TraceMsg(TF_WARNING, "Bitbucket: InitBBDriveInfo() failed on drive %d", idDrive);
  1130. ILFree(pidl);
  1131. ENTERCRITICAL;
  1132. // take the critical section to protect people who call IsBitBucketInited()
  1133. FreeBBInfo(g_pBitBucket[idDrive]);
  1134. if (idDrive == SERVERDRIVE)
  1135. {
  1136. // We set it to null in the serverdrive case so we will always retry. This allows
  1137. // the user to re-direct and try to recycle on a new location.
  1138. g_pBitBucket[idDrive] = NULL;
  1139. }
  1140. else
  1141. {
  1142. // set it to -1 here so we dont try any future recycle operations on this volume
  1143. g_pBitBucket[idDrive] = (BBSYNCOBJECT *)-1;
  1144. }
  1145. LEAVECRITICAL;
  1146. }
  1147. }
  1148. else
  1149. {
  1150. ILFree(pidl);
  1151. }
  1152. }
  1153. return bRet;
  1154. }
  1155. BOOL InitBBGlobals()
  1156. {
  1157. if (!g_fBBInited)
  1158. {
  1159. // Save this now beceause at shutdown the desktop window will already be gone,
  1160. // so we need to find out if we are the main explorer process now.
  1161. if (!g_bIsProcessExplorer)
  1162. {
  1163. g_bIsProcessExplorer = IsWindowInProcess(GetShellWindow());
  1164. }
  1165. // do we have our global hkey that points to HKLM\Software\Microsoft\Windows\CurrentVersion\BitBucket yet?
  1166. if (!g_hkBitBucket)
  1167. {
  1168. g_hkBitBucket = SHGetShellKey(SHELLKEY_HKLM_EXPLORER, TEXT("BitBucket"), TRUE);
  1169. if (!g_hkBitBucket)
  1170. {
  1171. TraceMsg(TF_WARNING, "Bitbucket: Could not create g_hkBitBucket!");
  1172. return FALSE;
  1173. }
  1174. }
  1175. // do we have our global hkey that points to HKCU\Software\Microsoft\Windows\CurrentVersion\BitBucket yet?
  1176. if (!g_hkBitBucketPerUser)
  1177. {
  1178. g_hkBitBucketPerUser = SHGetShellKey(SHELLKEY_HKCU_EXPLORER, TEXT("BitBucket"), TRUE);
  1179. if (!g_hkBitBucketPerUser)
  1180. {
  1181. TraceMsg(TF_WARNING, "Bitbucket: Could not create g_hkBitBucketPerUser!");
  1182. return FALSE;
  1183. }
  1184. }
  1185. // have we initialized the global settings dirty counter yet
  1186. if (g_hgcGlobalDirtyCount == INVALID_HANDLE_VALUE)
  1187. {
  1188. g_hgcGlobalDirtyCount = SHGlobalCounterCreateNamed(TEXT("BitBucket.GlobalDirtyCount"), 0);
  1189. if (g_hgcGlobalDirtyCount == INVALID_HANDLE_VALUE)
  1190. {
  1191. TraceMsg(TF_WARNING, "Bitbucket: failed to create g_hgcGlobalDirtyCount!");
  1192. return FALSE;
  1193. }
  1194. g_lProcessDirtyCount = SHGlobalCounterGetValue(g_hgcGlobalDirtyCount);
  1195. }
  1196. // have we initialized the global # of people doing recycle bin file operations?
  1197. if (g_hgcNumDeleters == INVALID_HANDLE_VALUE)
  1198. {
  1199. g_hgcNumDeleters = SHGlobalCounterCreateNamed(TEXT("BitBucket.NumDeleters"), 0);
  1200. if (g_hgcGlobalDirtyCount == INVALID_HANDLE_VALUE)
  1201. {
  1202. TraceMsg(TF_WARNING, "Bitbucket: failed to create g_hgcGlobalDirtyCount!");
  1203. return FALSE;
  1204. }
  1205. }
  1206. // we inited everything!!
  1207. g_fBBInited = TRUE;
  1208. }
  1209. return g_fBBInited;
  1210. }
  1211. void FreeBBInfo(BBSYNCOBJECT *pbbso)
  1212. {
  1213. if (pbbso->hgcNextFileNum)
  1214. CloseHandle(pbbso->hgcNextFileNum);
  1215. if (pbbso->hgcDirtyCount)
  1216. CloseHandle(pbbso->hgcDirtyCount);
  1217. if (pbbso->hkey)
  1218. RegCloseKey(pbbso->hkey);
  1219. if (pbbso->hkeyPerUser)
  1220. RegCloseKey(pbbso->hkeyPerUser);
  1221. LocalFree(pbbso);
  1222. }
  1223. //
  1224. // This function is exported from shell32 so that explorer can call us during WM_ENDSESSION
  1225. // and we can go save a bunch of state and free all the semaphores.
  1226. STDAPI_(void) SaveRecycleBinInfo()
  1227. {
  1228. if (g_bIsProcessExplorer)
  1229. {
  1230. LONG lGlobalDirtyCount;
  1231. BOOL bGlobalUpdate = FALSE; // did global settings change?
  1232. int i;
  1233. // We are going to persist the info to the registry, so check to see if we need to
  1234. // update our info now
  1235. lGlobalDirtyCount = SHGlobalCounterGetValue(g_hgcGlobalDirtyCount);
  1236. if (g_lProcessDirtyCount < lGlobalDirtyCount)
  1237. {
  1238. g_lProcessDirtyCount = lGlobalDirtyCount;
  1239. RefreshAllBBDriveSettings();
  1240. bGlobalUpdate = TRUE;
  1241. }
  1242. for (i = 0; i < MAX_BITBUCKETS ; i++)
  1243. {
  1244. if (IsBitBucketInited(i))
  1245. {
  1246. LONG lBucketDirtyCount = SHGlobalCounterGetValue(g_pBitBucket[i]->hgcDirtyCount);
  1247. // if we didnt do a global update, check this bucket specifically to see if it is dirty
  1248. // and we need to update it
  1249. if (!bGlobalUpdate && g_pBitBucket[i]->lCurrentDirtyCount < lBucketDirtyCount)
  1250. {
  1251. g_pBitBucket[i]->lCurrentDirtyCount = lBucketDirtyCount;
  1252. RefreshBBDriveSettings(i);
  1253. }
  1254. // save all of the volume serial # and whether the drive is unicode to the registry
  1255. PersistBBDriveInfo(i);
  1256. // we also update the header for win98/IE4 compat
  1257. UpdateBBInfoFileHeader(i);
  1258. }
  1259. }
  1260. }
  1261. }
  1262. void BitBucket_Terminate()
  1263. {
  1264. int i;
  1265. // free the global recycle bin structs
  1266. for (i = 0; i < MAX_BITBUCKETS ; i++)
  1267. {
  1268. if ((g_pBitBucket[i]) && (g_pBitBucket[i] != (BBSYNCOBJECT *)-1))
  1269. {
  1270. ENTERCRITICAL;
  1271. FreeBBInfo(g_pBitBucket[i]);
  1272. g_pBitBucket[i] = NULL;
  1273. LEAVECRITICAL;
  1274. }
  1275. }
  1276. if (g_hgcGlobalDirtyCount != INVALID_HANDLE_VALUE)
  1277. CloseHandle(g_hgcGlobalDirtyCount);
  1278. if (g_hgcNumDeleters != INVALID_HANDLE_VALUE)
  1279. CloseHandle(g_hgcNumDeleters);
  1280. if (g_hkBitBucketPerUser != NULL)
  1281. RegCloseKey(g_hkBitBucketPerUser);
  1282. if (g_hkBitBucket != NULL)
  1283. RegCloseKey(g_hkBitBucket);
  1284. }
  1285. //
  1286. // refreshes g_pBitBucket with new global settings
  1287. //
  1288. BOOL RefreshAllBBDriveSettings()
  1289. {
  1290. int i;
  1291. // since global settings changes affect all the drives, update all the drives
  1292. for (i = 0; i < MAX_BITBUCKETS; i++)
  1293. {
  1294. if ((g_pBitBucket[i]) && (g_pBitBucket[i] != (BBSYNCOBJECT *)-1))
  1295. {
  1296. RefreshBBDriveSettings(i);
  1297. }
  1298. }
  1299. return TRUE;
  1300. }
  1301. BOOL ReadBBDriveSetting(HKEY hkey, LPTSTR pszValue, LPBYTE pbData, DWORD cbData)
  1302. {
  1303. DWORD dwSize;
  1304. retry:
  1305. dwSize = cbData;
  1306. if (RegQueryValueEx(hkey, pszValue, NULL, NULL, pbData, &dwSize) != ERROR_SUCCESS)
  1307. {
  1308. if (hkey == g_hkBitBucket)
  1309. {
  1310. ASSERTMSG(FALSE, "Missing global bitbucket data: run regsvr32 on shell32.dll !!");
  1311. return FALSE;
  1312. }
  1313. else
  1314. {
  1315. // we are missing the per-bitbuckt information, so fall back to the global stuff
  1316. hkey = g_hkBitBucket;
  1317. goto retry;
  1318. }
  1319. }
  1320. return TRUE;
  1321. }
  1322. //
  1323. // Same as SHGetRestriction, except you can tell the difference between
  1324. // "policy not set" and "policy set with value=0"
  1325. //
  1326. DWORD ReadPolicySetting(LPCWSTR pszBaseKey, LPCWSTR pszGroup, LPCWSTR pszRestriction, LPBYTE pbData, DWORD cbData)
  1327. {
  1328. // Make sure the string is long enough to hold longest one...
  1329. WCHAR szSubKey[MAX_PATH];
  1330. DWORD dwSize;
  1331. DWORD dwRet;
  1332. //
  1333. // This restriction hasn't been read yet.
  1334. //
  1335. if (!pszBaseKey)
  1336. {
  1337. pszBaseKey = REGSTR_PATH_POLICIES;
  1338. }
  1339. if (PathCombineW(szSubKey, pszBaseKey, pszGroup))
  1340. {
  1341. // Check local machine first and let it override what the
  1342. // HKCU policy has done.
  1343. dwSize = cbData;
  1344. dwRet = SHGetValueW(HKEY_LOCAL_MACHINE, szSubKey, pszRestriction, NULL, pbData, &dwSize);
  1345. if (ERROR_SUCCESS != dwRet)
  1346. {
  1347. // Check current user if we didn't find anything for the local machine.
  1348. dwSize = cbData;
  1349. dwRet = SHGetValueW(HKEY_CURRENT_USER, szSubKey, pszRestriction, NULL, pbData, &dwSize);
  1350. }
  1351. }
  1352. else
  1353. {
  1354. dwRet = ERROR_FILE_NOT_FOUND;
  1355. }
  1356. return dwRet;
  1357. }
  1358. BOOL RefreshBBDriveSettings(int idDrive)
  1359. {
  1360. HKEY hkey;
  1361. ULARGE_INTEGER ulMaxSize;
  1362. BOOL fUseGlobalSettings = TRUE;
  1363. DWORD dwSize;
  1364. ASSERT(g_pBitBucket[idDrive] && (g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1));
  1365. dwSize = sizeof(fUseGlobalSettings);
  1366. RegQueryValueEx(g_hkBitBucket, TEXT("UseGlobalSettings"), NULL, NULL, (LPBYTE)&fUseGlobalSettings, &dwSize);
  1367. if (fUseGlobalSettings)
  1368. {
  1369. hkey = g_hkBitBucket;
  1370. }
  1371. else
  1372. {
  1373. hkey = g_pBitBucket[idDrive]->hkey;
  1374. }
  1375. // read the iPercent value
  1376. if (ERROR_SUCCESS == ReadPolicySetting(NULL, L"Explorer", L"RecycleBinSize", (LPBYTE)&g_pBitBucket[idDrive]->iPercent, sizeof(g_pBitBucket[idDrive]->iPercent)))
  1377. {
  1378. // Make sure it's not too big or too small
  1379. g_pBitBucket[idDrive]->iPercent = max(0, min(100, g_pBitBucket[idDrive]->iPercent));
  1380. }
  1381. else if (!ReadBBDriveSetting(hkey, TEXT("Percent"), (LPBYTE)&g_pBitBucket[idDrive]->iPercent, sizeof(g_pBitBucket[idDrive]->iPercent)))
  1382. {
  1383. // default
  1384. g_pBitBucket[idDrive]->iPercent = 10;
  1385. }
  1386. // read the fNukeOnDelete value
  1387. if (SHRestricted(REST_BITBUCKNUKEONDELETE))
  1388. {
  1389. g_pBitBucket[idDrive]->fNukeOnDelete = TRUE;
  1390. }
  1391. else if (!ReadBBDriveSetting(hkey, TEXT("NukeOnDelete"), (LPBYTE)&g_pBitBucket[idDrive]->fNukeOnDelete, sizeof(g_pBitBucket[idDrive]->fNukeOnDelete)))
  1392. {
  1393. // default
  1394. g_pBitBucket[idDrive]->fNukeOnDelete = FALSE;
  1395. }
  1396. // re-calculate cbMaxSize based on the new iPercent
  1397. ulMaxSize.QuadPart = min((g_pBitBucket[idDrive]->qwDiskSize / 100) * g_pBitBucket[idDrive]->iPercent, (DWORD)-1);
  1398. ASSERT(ulMaxSize.HighPart == 0);
  1399. g_pBitBucket[idDrive]->cbMaxSize = ulMaxSize.LowPart;
  1400. // since we just refreshed the settings from the registry, we are now up to date
  1401. g_pBitBucket[idDrive]->lCurrentDirtyCount = SHGlobalCounterGetValue(g_pBitBucket[idDrive]->hgcDirtyCount);
  1402. return TRUE;
  1403. }
  1404. //
  1405. // this function is used to compact the bitbucked INFO files.
  1406. //
  1407. // we do a lazy delete (just mark the entries as deleted) and when we hit a
  1408. // certain number of bogus entries in the info file, we need to go through and clean up the
  1409. // garbage and compact the file.
  1410. //
  1411. DWORD CALLBACK CompactBBInfoFileThread(void *pData)
  1412. {
  1413. int idDrive = PtrToLong(pData);
  1414. //
  1415. // PERF (reinerf) - as an optimization, we might want to check here to see
  1416. // if someone is waiting to empty the bitbucket since if we are going to empty
  1417. // this bucket there is no point in wasting time compacting it.
  1418. //
  1419. HANDLE hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
  1420. if (hFile != INVALID_HANDLE_VALUE)
  1421. {
  1422. // work in chunks of 10
  1423. BBDATAENTRYW bbdewArray[10]; // use a unicode array, but it might end up holding BBDATAENTRYA stucts
  1424. LPBBDATAENTRYW pbbdew = bbdewArray;
  1425. int iNumEntries = 0;
  1426. DWORD dwDataEntrySize = g_pBitBucket[idDrive]->fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA);
  1427. DWORD dwReadPos = 0;
  1428. DWORD dwBytesWritten;
  1429. // save off the inital write pos
  1430. DWORD dwWritePos = SetFilePointer(hFile, 0, NULL, FILE_CURRENT);
  1431. while (ReadNextDataEntry(hFile, pbbdew, TRUE, idDrive))
  1432. {
  1433. ASSERT(!IsDeletedEntry(pbbdew));
  1434. iNumEntries++;
  1435. // do we have 10 entries yet?
  1436. if (iNumEntries == ARRAYSIZE(bbdewArray))
  1437. {
  1438. iNumEntries = 0;
  1439. TraceMsg(TF_BITBUCKET, "Bitbucket: Compacting drive %d: dwRead = %d, dwWrite = %d, writing 10 entries", idDrive, dwReadPos, dwWritePos);
  1440. // save where we are for reading
  1441. dwReadPos = SetFilePointer(hFile, 0, NULL, FILE_CURRENT);
  1442. // then go to where we are for writing
  1443. SetFilePointer(hFile, dwWritePos, NULL, FILE_BEGIN);
  1444. // write it out
  1445. if (!WriteFile(hFile, (LPBYTE)bbdewArray, dwDataEntrySize * ARRAYSIZE(bbdewArray), &dwBytesWritten, NULL) || dwBytesWritten != (dwDataEntrySize * ARRAYSIZE(bbdewArray)))
  1446. {
  1447. // we're in big trouble if this happens.
  1448. // bail completely so that at worst, we only have a few bad records.
  1449. // if we keep trying to write from this point, but the write point is
  1450. // we'll nuke all the records
  1451. ASSERTMSG(FALSE, "Bitbucket: we were compacting drive %d and it is totally messed up", idDrive);
  1452. break;
  1453. }
  1454. // sucess! move our write pos to the end of were we finished writing
  1455. dwWritePos += (dwDataEntrySize * ARRAYSIZE(bbdewArray));
  1456. // go back to were we left off reading
  1457. SetFilePointer(hFile, dwReadPos, NULL, FILE_BEGIN);
  1458. // reset our lparray pointer
  1459. pbbdew = bbdewArray;
  1460. }
  1461. else
  1462. {
  1463. // dont have 10 entries yet, so keep going
  1464. pbbdew = (LPBBDATAENTRYW)((LPBYTE)pbbdew + dwDataEntrySize);
  1465. }
  1466. }
  1467. TraceMsg(TF_BITBUCKET, "Bitbucket: Compacting drive %d: dwRead = %d, dwWrite = %d, writing last %d entries", idDrive, dwReadPos, dwWritePos, iNumEntries);
  1468. // write whatever we have left over
  1469. SetFilePointer(hFile, dwWritePos, NULL, FILE_BEGIN);
  1470. WriteFile(hFile, (LPBYTE)bbdewArray, dwDataEntrySize * iNumEntries, &dwBytesWritten, NULL);
  1471. ASSERT(dwBytesWritten == (dwDataEntrySize * iNumEntries));
  1472. SetEndOfFile(hFile);
  1473. CloseBBInfoFile(hFile, idDrive);
  1474. }
  1475. return 0;
  1476. }
  1477. void CompactBBInfoFile(int idDrive)
  1478. {
  1479. HANDLE hThread;
  1480. DWORD idThread;
  1481. // try to spin up a background thread to do the work for us
  1482. hThread = CreateThread(NULL, 0, CompactBBInfoFileThread, IntToPtr(idDrive), 0, &idThread);
  1483. if (hThread)
  1484. {
  1485. // let the background thread do the work
  1486. CloseHandle(hThread);
  1487. }
  1488. else
  1489. {
  1490. TraceMsg(TF_BITBUCKET, "BBCompact - failed to create backgound thread! Doing work on this thread");
  1491. CompactBBInfoFileThread(IntToPtr(idDrive));
  1492. }
  1493. }
  1494. BOOL GetDeletedFileNameFromParts(LPTSTR pszFileName, size_t cchFileName, int idDrive, int iIndex, LPCTSTR pszOriginal)
  1495. {
  1496. return SUCCEEDED(StringCchPrintf(pszFileName,
  1497. cchFileName,
  1498. TEXT("D%c%d%s"),
  1499. DriveChar(idDrive),
  1500. iIndex,
  1501. PathFindExtension(pszOriginal)));
  1502. }
  1503. BOOL GetDeletedFileName(LPTSTR pszFileName, size_t cchFileName, const BBDATAENTRYW* pbbdew)
  1504. {
  1505. return GetDeletedFileNameFromParts(pszFileName, cchFileName, pbbdew->idDrive, pbbdew->iIndex, pbbdew->szOriginal);
  1506. }
  1507. // get the full path to the file/folder in the recycle bin location
  1508. BOOL GetDeletedFilePath(LPTSTR pszPath, const BBDATAENTRYW* pbbdew)
  1509. {
  1510. BOOL bRet = FALSE;
  1511. TCHAR szFileName[MAX_PATH];
  1512. if (DriveIDToBBPath(pbbdew->idDrive, pszPath) &&
  1513. GetDeletedFileName(szFileName, ARRAYSIZE(szFileName), pbbdew) &&
  1514. PathAppend(pszPath, szFileName))
  1515. {
  1516. bRet = TRUE;
  1517. }
  1518. return bRet;
  1519. }
  1520. void UpdateIcon(BOOL fFull)
  1521. {
  1522. LONG cbData;
  1523. DWORD dwType;
  1524. HKEY hkeyCLSID = NULL;
  1525. HKEY hkeyUserCLSID = NULL;
  1526. TCHAR szTemp[MAX_PATH];
  1527. TCHAR szNewValue[MAX_PATH];
  1528. TCHAR szValue[MAX_PATH];
  1529. TraceMsg(TF_BITBUCKET, "BitBucket: UpdateIcon %s", fFull ? TEXT("Full") : TEXT("Empty"));
  1530. szValue[0] = 0;
  1531. szNewValue[0] = 0;
  1532. // get the HKCR CLSID key (HKCR\CLSID\CLSID_RecycleBin\DefaultIcon)
  1533. if (FAILED(SHRegGetCLSIDKey(&CLSID_RecycleBin, c_szDefaultIcon, FALSE, FALSE, &hkeyCLSID)))
  1534. goto error;
  1535. // get the per-user CLSID
  1536. // HKCU
  1537. // NT: Software\Microsoft\Windows\CurrentVersion\Explorer\CLSID
  1538. // 9x: Software\Classes\CLSID
  1539. if (FAILED(SHRegGetCLSIDKey(&CLSID_RecycleBin, c_szDefaultIcon, TRUE, FALSE, &hkeyUserCLSID)))
  1540. {
  1541. // it most likely failed because the reg key dosent exist, so create it now
  1542. if (FAILED(SHRegGetCLSIDKey(&CLSID_RecycleBin, c_szDefaultIcon, TRUE, TRUE, &hkeyUserCLSID)))
  1543. goto error;
  1544. // now that we created it, lets copy the stuff from HKLM there
  1545. // get the local machine default icon
  1546. cbData = sizeof(szTemp);
  1547. if (RegQueryValueEx(hkeyCLSID, NULL, 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
  1548. goto error;
  1549. // set the per-user default icon
  1550. RegSetValueEx(hkeyUserCLSID, NULL, 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
  1551. // get the local machine full icon
  1552. cbData = sizeof(szTemp);
  1553. if (RegQueryValueEx(hkeyCLSID, TEXT("Full"), 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
  1554. goto error;
  1555. // set the per-user full icon
  1556. RegSetValueEx(hkeyUserCLSID, TEXT("Full"), 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
  1557. // get the local machine empty icon
  1558. cbData = sizeof(szTemp);
  1559. if (RegQueryValueEx(hkeyCLSID, TEXT("Empty"), 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
  1560. goto error;
  1561. // set the per-user empty icon
  1562. RegSetValueEx(hkeyUserCLSID, TEXT("Empty"), 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
  1563. }
  1564. // try the per user first, if we dont find it, then copy the information from HKCR\CLSID\etc...
  1565. // to the per-user location
  1566. cbData = sizeof(szTemp);
  1567. if (RegQueryValueEx(hkeyUserCLSID, NULL, 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
  1568. {
  1569. // get the local machine default icon
  1570. cbData = sizeof(szTemp);
  1571. if (RegQueryValueEx(hkeyCLSID, NULL, 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
  1572. goto error;
  1573. // set the per-user default icon
  1574. RegSetValueEx(hkeyUserCLSID, NULL, 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
  1575. }
  1576. StringCchCopy(szValue, ARRAYSIZE(szValue), szTemp);
  1577. cbData = sizeof(szTemp);
  1578. if (RegQueryValueEx(hkeyUserCLSID, fFull ? TEXT("Full") : TEXT("Empty"), 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
  1579. {
  1580. cbData = sizeof(szTemp);
  1581. if (RegQueryValueEx(hkeyCLSID, fFull ? TEXT("Full") : TEXT("Empty"), 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
  1582. goto error;
  1583. // set the per-user full/empty icon
  1584. RegSetValueEx(hkeyUserCLSID, fFull ? TEXT("Full") : TEXT("Empty"), 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
  1585. }
  1586. StringCchCopy(szNewValue, ARRAYSIZE(szNewValue), szTemp);
  1587. if (lstrcmpi(szNewValue, szValue) != 0)
  1588. {
  1589. TCHAR szExpandedValue[MAX_PATH];
  1590. LPTSTR szIconIndex;
  1591. cbData = sizeof(szTemp);
  1592. if (RegQueryValueEx(hkeyUserCLSID, fFull ? TEXT("Full") : TEXT("Empty"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS)
  1593. {
  1594. // we always update the per user default icon, because recycle bins are per user on NTFS
  1595. RegSetValueEx(hkeyUserCLSID, NULL, 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
  1596. }
  1597. if (SHExpandEnvironmentStrings(szValue, szExpandedValue, ARRAYSIZE(szExpandedValue)))
  1598. {
  1599. szIconIndex = StrRChr(szExpandedValue, NULL, TEXT(','));
  1600. if (szIconIndex)
  1601. {
  1602. int id;
  1603. int iNum = StrToInt(szIconIndex + 1);
  1604. *szIconIndex = TEXT('\0'); // end szValue after the dll name
  1605. // ..and tell anyone viewing this image index to update
  1606. id = LookupIconIndex(szExpandedValue, iNum, 0);
  1607. SHUpdateImage(szExpandedValue, iNum, 0, id);
  1608. SHChangeNotifyHandleEvents();
  1609. }
  1610. }
  1611. }
  1612. error:
  1613. if (hkeyCLSID)
  1614. RegCloseKey(hkeyCLSID);
  1615. if (hkeyUserCLSID)
  1616. RegCloseKey(hkeyUserCLSID);
  1617. }
  1618. //
  1619. // this loads the settings for this drive. it obeys the "use global" bit
  1620. //
  1621. BOOL GetBBDriveSettings(int idDrive, ULONGLONG *pcbDiskSpace)
  1622. {
  1623. TCHAR szDrive[MAX_PATH];
  1624. TCHAR szName[MAX_PATH];
  1625. TCHAR szVolume[MAX_PATH];
  1626. TCHAR szPath[MAX_PATH];
  1627. ULARGE_INTEGER ulFreeUser, ulTotal, ulFree;
  1628. DWORD dwSize1;
  1629. DWORD dwSerialNumber, dwSerialNumberFromRegistry;
  1630. LONG lInitialCount;
  1631. BOOL bHaveCachedRegInfo = FALSE;
  1632. BOOL bRet = TRUE;
  1633. HKEY hkey;
  1634. // Get volume root since we are going to call GetVolumeInformation()
  1635. if (!DriveIDToBBVolumeRoot(idDrive, szVolume) ||
  1636. !DriveIDToBBPath(idDrive, szDrive) ||
  1637. !GetBBInfo2FileSpec(szDrive, szName))
  1638. {
  1639. return FALSE;
  1640. }
  1641. if (idDrive == SERVERDRIVE)
  1642. {
  1643. // in the SERVERDRIVE case everything is under HKCU, so use the per-user key
  1644. hkey = g_pBitBucket[idDrive]->hkeyPerUser;
  1645. }
  1646. else
  1647. {
  1648. hkey = g_pBitBucket[idDrive]->hkey;
  1649. }
  1650. // first we need to check to see we have cached registry info for this drive, or if this
  1651. // is a new drive
  1652. dwSize1 = sizeof(dwSerialNumberFromRegistry);
  1653. if (PathFileExists(szName) &&
  1654. (RegQueryValueEx(hkey,
  1655. TEXT("VolumeSerialNumber"),
  1656. NULL,
  1657. NULL,
  1658. (LPBYTE)&dwSerialNumberFromRegistry,
  1659. &dwSize1) == ERROR_SUCCESS) &&
  1660. GetVolumeInformation(szVolume,
  1661. NULL,
  1662. 0,
  1663. &dwSerialNumber,
  1664. NULL,
  1665. NULL,
  1666. NULL,
  1667. 0) &&
  1668. (dwSerialNumber == dwSerialNumberFromRegistry))
  1669. {
  1670. // we were able to read the drive serial number and it matched the regsitry, so
  1671. // assume that the cached reg info is valid
  1672. bHaveCachedRegInfo = TRUE;
  1673. }
  1674. // do some extra checks in the SERVERDRIVE case to make sure that the path matches in addition to the volume serial number.
  1675. // (eg nethomedir could be on the same volume but a different path)
  1676. if (bHaveCachedRegInfo && (SERVERDRIVE == idDrive))
  1677. {
  1678. DWORD cbPath = sizeof(szPath);
  1679. if ((RegQueryValueEx(hkey, TEXT("Path"), NULL, NULL, (LPBYTE) szPath, &cbPath) != ERROR_SUCCESS) ||
  1680. (lstrcmpi(szPath, szDrive) != 0))
  1681. {
  1682. // couldn't read the path or it didnt match, so no we can't use the cached info
  1683. bHaveCachedRegInfo = FALSE;
  1684. }
  1685. }
  1686. if (!bHaveCachedRegInfo)
  1687. {
  1688. TraceMsg(TF_BITBUCKET, "Bitbucket: new drive %s detected!!!", szDrive);
  1689. // this is a new volume, so delete any old registry info we had
  1690. DeleteOldBBRegInfo(idDrive);
  1691. // And also migrate the win95 info if it exists
  1692. // NOTE: this also fills in the g_pBitBucket[idDrive]->fIsUnicode
  1693. VerifyBBInfoFileHeader(idDrive);
  1694. }
  1695. else
  1696. {
  1697. // set g_pBitBucket[idDrive]->fIsUnicode based on the registry info
  1698. dwSize1 = sizeof(g_pBitBucket[idDrive]->fIsUnicode);
  1699. if (RegQueryValueEx(hkey, TEXT("IsUnicode"), NULL, NULL, (LPBYTE)&g_pBitBucket[idDrive]->fIsUnicode, &dwSize1) != ERROR_SUCCESS)
  1700. {
  1701. TraceMsg(TF_BITBUCKET, "Bitbucket: IsUnicode missing from registry for drive %s !!", szDrive);
  1702. // instead, try to get this out of the header
  1703. VerifyBBInfoFileHeader(idDrive);
  1704. }
  1705. }
  1706. // we need to check to make sure that the Recycle Bin folder is properly secured
  1707. if (!CheckRecycleBinAcls(idDrive))
  1708. {
  1709. // we return false if this fails (meaning we detected an unsecure directory and were unable to
  1710. // fix it or the user didnt want to fix it). This will effectively disable all recycle bin operations
  1711. // on this volume for this session.
  1712. return FALSE;
  1713. }
  1714. // calculate the next file num index
  1715. lInitialCount = FindInitialNextFileNum(idDrive);
  1716. // create the hgcNextFileNume global counter
  1717. ASSERT(lInitialCount >= 0);
  1718. if (SUCCEEDED(StringCchCopy(szName, ARRAYSIZE(szName), TEXT("BitBucket."))) &&
  1719. DriveIDToBBRegKey(idDrive, &szName[10]) && // 10 = lstrlen("BitBucket.")
  1720. SUCCEEDED(StringCchCat(szName, ARRAYSIZE(szName), TEXT(".NextFileNum"))))
  1721. {
  1722. // BitBucket.<drive letter>.NextFileNum
  1723. g_pBitBucket[idDrive]->hgcNextFileNum = SHGlobalCounterCreateNamed(szName, lInitialCount);
  1724. }
  1725. else
  1726. {
  1727. g_pBitBucket[idDrive]->hgcNextFileNum = INVALID_HANDLE_VALUE;
  1728. }
  1729. if (g_pBitBucket[idDrive]->hgcNextFileNum == INVALID_HANDLE_VALUE)
  1730. {
  1731. ASSERTMSG(FALSE, "BitBucket: failed to create hgcNextFileNum for drive %s !!", szDrive);
  1732. return FALSE;
  1733. }
  1734. // we call SHGetDiskFreeSpaceEx so we can respect quotas on NTFS
  1735. if (DriveIDToBBRoot(idDrive, szDrive) &&
  1736. SHGetDiskFreeSpaceEx(szDrive, &ulFreeUser, &ulTotal, &ulFree))
  1737. {
  1738. g_pBitBucket[idDrive]->dwClusterSize = PathGetClusterSize(szDrive);
  1739. g_pBitBucket[idDrive]->qwDiskSize = ulTotal.QuadPart;
  1740. }
  1741. else
  1742. {
  1743. if (idDrive == SERVERDRIVE)
  1744. {
  1745. g_pBitBucket[idDrive]->dwClusterSize = 2048;
  1746. g_pBitBucket[idDrive]->qwDiskSize = 0x7FFFFFFF;
  1747. }
  1748. else
  1749. {
  1750. ASSERTMSG(FALSE, "Bitbucket: SHGetDiskFreeSpaceEx failed on %s !!", szDrive);
  1751. g_pBitBucket[idDrive]->dwClusterSize = 0;
  1752. g_pBitBucket[idDrive]->qwDiskSize = 0;
  1753. }
  1754. }
  1755. if (pcbDiskSpace)
  1756. {
  1757. *pcbDiskSpace = g_pBitBucket[idDrive]->qwDiskSize;
  1758. }
  1759. // Read the Percent and NukeOnDelete settings, and recalculate cbMaxSize.
  1760. RefreshBBDriveSettings(idDrive);
  1761. TraceMsg(TF_BITBUCKET,
  1762. "GetBBDriveSettings: Drive %s, fIsUnicode=%d, iPercent=%d, cbMaxSize=%d, fNukeOnDelete=%d, NextFileNum=%d",
  1763. szDrive,
  1764. g_pBitBucket[idDrive]->fIsUnicode,
  1765. g_pBitBucket[idDrive]->iPercent,
  1766. g_pBitBucket[idDrive]->cbMaxSize,
  1767. g_pBitBucket[idDrive]->fNukeOnDelete,
  1768. SHGlobalCounterGetValue(g_pBitBucket[idDrive]->hgcNextFileNum));
  1769. return TRUE;
  1770. }
  1771. //
  1772. // cleans up old iPercent and fNukeOnDelete registry keys when we dectect a new drive
  1773. //
  1774. void DeleteOldBBRegInfo(idDrive)
  1775. {
  1776. RegDeleteValue(g_pBitBucket[idDrive]->hkey, TEXT("Percent"));
  1777. RegDeleteValue(g_pBitBucket[idDrive]->hkey, TEXT("NukeOnDelete"));
  1778. RegDeleteValue(g_pBitBucket[idDrive]->hkey, TEXT("IsUnicode"));
  1779. }
  1780. //
  1781. // This gets called when explorer exits to persist the volume serial # and
  1782. // whether the drive is unicode for the specified drive.
  1783. //
  1784. void PersistBBDriveInfo(int idDrive)
  1785. {
  1786. TCHAR szVolume[MAX_PATH];
  1787. DWORD dwSerialNumber;
  1788. HKEY hkey;
  1789. if (SERVERDRIVE == idDrive)
  1790. {
  1791. TCHAR szPath[MAX_PATH];
  1792. // in the SERVERDRIVE case everything is under HKCU, so use the per-user key
  1793. hkey = g_pBitBucket[idDrive]->hkeyPerUser;
  1794. if (DriveIDToBBPath(idDrive, szPath))
  1795. {
  1796. RegSetValueEx(hkey, TEXT("Path"), 0, REG_SZ, (LPBYTE) szPath, sizeof(TCHAR) * (lstrlen(szPath) + 1));
  1797. }
  1798. }
  1799. else
  1800. {
  1801. hkey = g_pBitBucket[idDrive]->hkey;
  1802. }
  1803. if (DriveIDToBBVolumeRoot(idDrive, szVolume))
  1804. {
  1805. // write out the volume serial # so we can detect when a new drive comes along and give it the default settings
  1806. // NOTE: we will fail to write out the volume serial # if we are a normal user and HKLM is locked down. Oh well.
  1807. if (GetVolumeInformation(szVolume, NULL, 0, &dwSerialNumber, NULL, NULL, NULL, 0))
  1808. {
  1809. RegSetValueEx(hkey, TEXT("VolumeSerialNumber"), 0, REG_DWORD, (LPBYTE)&dwSerialNumber, sizeof(dwSerialNumber));
  1810. }
  1811. }
  1812. // save off fIsUnicode as well
  1813. RegSetValueEx(hkey, TEXT("IsUnicode"), 0, REG_DWORD, (LPBYTE)&g_pBitBucket[idDrive]->fIsUnicode, sizeof(g_pBitBucket[idDrive]->fIsUnicode));
  1814. }
  1815. //
  1816. // This is what gets called when the user tweaks the drive settings for all the drives (the global settings)
  1817. //
  1818. BOOL PersistGlobalSettings(BOOL fUseGlobalSettings, BOOL fNukeOnDelete, int iPercent)
  1819. {
  1820. ASSERT(g_hkBitBucket);
  1821. if (RegSetValueEx(g_hkBitBucket, TEXT("Percent"), 0, REG_DWORD, (LPBYTE)&iPercent, sizeof(iPercent)) != ERROR_SUCCESS ||
  1822. RegSetValueEx(g_hkBitBucket, TEXT("NukeOnDelete"), 0, REG_DWORD, (LPBYTE)&fNukeOnDelete, sizeof(fNukeOnDelete)) != ERROR_SUCCESS ||
  1823. RegSetValueEx(g_hkBitBucket, TEXT("UseGlobalSettings"), 0, REG_DWORD, (LPBYTE)&fUseGlobalSettings, sizeof(fUseGlobalSettings)) != ERROR_SUCCESS)
  1824. {
  1825. TraceMsg(TF_BITBUCKET, "Bitbucket: failed to update global bitbucket data in the registry!!");
  1826. return FALSE;
  1827. }
  1828. // since we just updated the global drive settings, we need to increment the dirty count and set our own
  1829. g_lProcessDirtyCount = SHGlobalCounterIncrement(g_hgcGlobalDirtyCount);
  1830. return TRUE;
  1831. }
  1832. //
  1833. // This is what gets called when the user tweaks the drive settings for a drive via the
  1834. // Recycle Bin property sheet page. The only thing we care about is the % slider and the
  1835. // "Do not move files to the recycle bin" settings.
  1836. //
  1837. BOOL PersistBBDriveSettings(int idDrive, int iPercent, BOOL fNukeOnDelete)
  1838. {
  1839. if (RegSetValueEx(g_pBitBucket[idDrive]->hkey, TEXT("Percent"), 0, REG_DWORD, (LPBYTE)&iPercent, sizeof(iPercent)) != ERROR_SUCCESS ||
  1840. RegSetValueEx(g_pBitBucket[idDrive]->hkey, TEXT("NukeOnDelete"), 0, REG_DWORD, (LPBYTE)&fNukeOnDelete, sizeof(fNukeOnDelete)) != ERROR_SUCCESS)
  1841. {
  1842. TraceMsg(TF_BITBUCKET, "Bitbucket: unable to persist drive settings for drive %d", idDrive);
  1843. return FALSE;
  1844. }
  1845. // since we just updated the drive settings, we need to increment the dirty count for this drive
  1846. g_pBitBucket[idDrive]->lCurrentDirtyCount = SHGlobalCounterIncrement(g_pBitBucket[idDrive]->hgcDirtyCount);
  1847. return TRUE;
  1848. }
  1849. //
  1850. // walks the multi-string pszSrc and sets up the undo info
  1851. //
  1852. void BBCheckRestoredFiles(LPCTSTR pszSrc)
  1853. {
  1854. if (pszSrc && IsFileInBitBucket(pszSrc))
  1855. {
  1856. LPCTSTR pszTemp = pszSrc;
  1857. while (*pszTemp)
  1858. {
  1859. FOUndo_FileRestored(pszTemp);
  1860. pszTemp += (lstrlen(pszTemp) + 1);
  1861. }
  1862. SHUpdateRecycleBinIcon();
  1863. }
  1864. }
  1865. //
  1866. // This is the quick and efficent way to tell if the Recycle Bin is empty or not
  1867. //
  1868. STDAPI_(BOOL) IsRecycleBinEmpty()
  1869. {
  1870. int i;
  1871. for (i = 0; i < MAX_BITBUCKETS; i++)
  1872. {
  1873. if (CountDeletedFilesOnDrive(i, NULL, 1))
  1874. return FALSE;
  1875. }
  1876. return TRUE;
  1877. }
  1878. //
  1879. // Finds out how many files are deleted on this drive, and optionally the total size of those files.
  1880. // Also, stop counting if the total # of files equals iMaxFiles.
  1881. //
  1882. // NOTE: if you pass iMaxFiles = 0, then we ignore the parameter and count up all the files/sizes
  1883. //
  1884. int CountDeletedFilesOnDrive(int idDrive, ULARGE_INTEGER *puliSize, int iMaxFiles)
  1885. {
  1886. int cFiles = 0;
  1887. HANDLE hFile;
  1888. WIN32_FIND_DATA wfd;
  1889. TCHAR szBBPath[MAX_PATH];
  1890. TCHAR szBBFileSpec[MAX_PATH];
  1891. if (puliSize)
  1892. {
  1893. puliSize->QuadPart = 0;
  1894. }
  1895. if (!IsBitBucketableDrive(idDrive) ||
  1896. !DriveIDToBBPath(idDrive, szBBPath) ||
  1897. !PathCombine(szBBFileSpec, szBBPath, TEXT("D*.*")))
  1898. {
  1899. return 0;
  1900. }
  1901. hFile = FindFirstFile(szBBFileSpec, &wfd);
  1902. if (hFile == INVALID_HANDLE_VALUE)
  1903. {
  1904. return 0;
  1905. }
  1906. do
  1907. {
  1908. if (PathIsDotOrDotDot(wfd.cFileName) || lstrcmpi(wfd.cFileName, c_szDesktopIni) == 0)
  1909. {
  1910. continue;
  1911. }
  1912. cFiles++;
  1913. if (puliSize)
  1914. {
  1915. if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  1916. {
  1917. FOLDERCONTENTSINFO fci = {0};
  1918. TCHAR szDir[MAX_PATH];
  1919. fci.bContinue = TRUE;
  1920. // PERF (reinerf) - for perf we should try to avoid
  1921. // calling FolderSize here. Perhaps we could encode the size
  1922. // as part of the extension?
  1923. if (SUCCEEDED(StringCchCopy(szDir, ARRAYSIZE(szDir), szBBPath)) &&
  1924. PathAppend(szDir, wfd.cFileName))
  1925. {
  1926. FolderSize(szDir, &fci);
  1927. puliSize->QuadPart += fci.cbSize;
  1928. }
  1929. }
  1930. else
  1931. {
  1932. // simple file case
  1933. ULARGE_INTEGER uliTemp;
  1934. uliTemp.LowPart = wfd.nFileSizeLow;
  1935. uliTemp.HighPart = wfd.nFileSizeHigh;
  1936. puliSize->QuadPart += uliTemp.QuadPart;
  1937. }
  1938. }
  1939. if ((iMaxFiles > 0) && (cFiles >= iMaxFiles))
  1940. {
  1941. break;
  1942. }
  1943. } while (FindNextFile(hFile, &wfd));
  1944. FindClose(hFile);
  1945. return cFiles;
  1946. }
  1947. //
  1948. // Returns the number of files in the Recycle Bin, and optionally the drive id
  1949. // if there's only one file, and optionally the total size of all the stuff.
  1950. //
  1951. // We also stop counting if iMaxFiles is nonzero and we find that many
  1952. // files. This helps perf by having a cutoff point where we use a generic error
  1953. // message instead of the exact # of files. If iMaxFiles is zero, we give the true
  1954. // count of files.
  1955. //
  1956. // NOTE: don't use this if you just want to check to see if the recycle bin is
  1957. // empty or full!! Use IsRecycleBinEmpty() instead
  1958. //
  1959. int BBTotalCount(LPINT pidDrive, ULARGE_INTEGER *puliSize, int iMaxFiles)
  1960. {
  1961. int i;
  1962. int idDrive;
  1963. int nFiles = 0;
  1964. if (puliSize)
  1965. {
  1966. puliSize->QuadPart = 0;
  1967. }
  1968. for (i = 0; i < MAX_BITBUCKETS; i++)
  1969. {
  1970. int nFilesOld = nFiles;
  1971. ULARGE_INTEGER uliSize;
  1972. nFiles += CountDeletedFilesOnDrive(i, puliSize ? &uliSize : NULL, iMaxFiles - nFilesOld);
  1973. if (puliSize)
  1974. {
  1975. puliSize->QuadPart += uliSize.QuadPart;
  1976. }
  1977. if (nFilesOld == 0 && nFiles == 1)
  1978. {
  1979. // if just one file, set the drive id
  1980. idDrive = i;
  1981. }
  1982. if (iMaxFiles > 0 && nFiles >= iMaxFiles)
  1983. break;
  1984. }
  1985. if (pidDrive)
  1986. *pidDrive = (nFiles == 1) ? idDrive : 0;
  1987. return nFiles;
  1988. }
  1989. //
  1990. // gets the number of files and and size of the bitbucket for the given drive
  1991. //
  1992. SHSTDAPI SHQueryRecycleBin(LPCTSTR pszRootPath, LPSHQUERYRBINFO pSHQueryInfo)
  1993. {
  1994. ULARGE_INTEGER uliSize;
  1995. DWORD dwNumItems = 0;
  1996. uliSize.QuadPart = 0;
  1997. // since this fn is exported, we need to check to see if we need to
  1998. // init our global data first
  1999. if (!InitBBGlobals())
  2000. {
  2001. return E_OUTOFMEMORY;
  2002. }
  2003. if (!pSHQueryInfo ||
  2004. (pSHQueryInfo->cbSize < sizeof(SHQUERYRBINFO)))
  2005. {
  2006. return E_INVALIDARG;
  2007. }
  2008. if (pszRootPath && pszRootPath[0] != TEXT('\0'))
  2009. {
  2010. int idDrive = DriveIDFromBBPath(pszRootPath);
  2011. if (MakeBitBucket(idDrive))
  2012. {
  2013. dwNumItems = CountDeletedFilesOnDrive(idDrive, &uliSize, 0);
  2014. }
  2015. }
  2016. else
  2017. {
  2018. //
  2019. // NTRAID#NTBUG9-146905-2001/03/15-jeffreys
  2020. //
  2021. // This is a public API, documented to return the totals for all
  2022. // recycle bins when no path is given. This was broken in Windows
  2023. // 2000 and Millennium.
  2024. //
  2025. dwNumItems = BBTotalCount(NULL, &uliSize, 0);
  2026. }
  2027. pSHQueryInfo->i64Size = uliSize.QuadPart;
  2028. pSHQueryInfo->i64NumItems = (__int64)dwNumItems;
  2029. return S_OK;
  2030. }
  2031. SHSTDAPI SHQueryRecycleBinA(LPCSTR pszRootPath, LPSHQUERYRBINFO pSHQueryRBInfo)
  2032. {
  2033. WCHAR wszPath[MAX_PATH];
  2034. SHAnsiToUnicode(pszRootPath, wszPath, ARRAYSIZE(wszPath));
  2035. return SHQueryRecycleBin(wszPath, pSHQueryRBInfo);
  2036. }
  2037. //
  2038. // Empty the given drive or all drives
  2039. //
  2040. SHSTDAPI SHEmptyRecycleBin(HWND hWnd, LPCTSTR pszRootPath, DWORD dwFlags)
  2041. {
  2042. // since this fn is exported, we need to check to see if we need to
  2043. // init our global data first
  2044. if (!InitBBGlobals())
  2045. {
  2046. // this could happen in low memory situations, we have no choice but
  2047. // to abort the empty
  2048. return E_OUTOFMEMORY;
  2049. }
  2050. if ((pszRootPath == NULL) || (*pszRootPath == 0))
  2051. {
  2052. BBPurgeAll(hWnd, dwFlags);
  2053. }
  2054. else
  2055. {
  2056. int idDrive = DriveIDFromBBPath(pszRootPath);
  2057. // note: we include MAX_DRIVES(26) which is SERVERDRIVE case!
  2058. if ((idDrive < 0) || (idDrive > MAX_DRIVES))
  2059. {
  2060. return E_INVALIDARG;
  2061. }
  2062. if (MakeBitBucket(idDrive))
  2063. {
  2064. PurgeOneBitBucket(hWnd, idDrive, dwFlags);
  2065. }
  2066. }
  2067. return S_OK;
  2068. }
  2069. SHSTDAPI SHEmptyRecycleBinA(HWND hWnd, LPCSTR pszRootPath, DWORD dwFlags)
  2070. {
  2071. WCHAR wszPath[MAX_PATH];
  2072. SHAnsiToUnicode(pszRootPath, wszPath, ARRAYSIZE(wszPath));
  2073. return SHEmptyRecycleBin(hWnd, wszPath, dwFlags);
  2074. }
  2075. void MarkBBPurgeAllTime(BOOL bStart)
  2076. {
  2077. TCHAR szText[64];
  2078. if (g_dwStopWatchMode == 0xffffffff)
  2079. g_dwStopWatchMode = StopWatchMode(); // Since the stopwatch funcs live in shdocvw, delay this call so we don't load shdocvw until we need to
  2080. if (g_dwStopWatchMode)
  2081. {
  2082. StringCchCopy(szText, ARRAYSIZE(szText), TEXT("Shell Empty Recycle"));
  2083. if (bStart)
  2084. {
  2085. StringCchCat(szText, ARRAYSIZE(szText), TEXT(": Start"));
  2086. StopWatch_Start(SWID_BITBUCKET, (LPCTSTR)szText, SPMODE_SHELL | SPMODE_DEBUGOUT);
  2087. }
  2088. else
  2089. {
  2090. StringCchCat(szText, ARRAYSIZE(szText), TEXT(": Stop"));
  2091. StopWatch_Stop(SWID_BITBUCKET, (LPCTSTR)szText, SPMODE_SHELL | SPMODE_DEBUGOUT);
  2092. }
  2093. }
  2094. }
  2095. HRESULT BBPurgeAll(HWND hwndOwner, DWORD dwFlags)
  2096. {
  2097. TCHAR szPath[MAX_PATH * 2 + 3]; // null space and double null termination
  2098. int nFiles;
  2099. int idDrive;
  2100. BOOL fConfirmed;
  2101. SHFILEOPSTRUCT sFileOp ={hwndOwner,
  2102. FO_DELETE,
  2103. szPath,
  2104. NULL,
  2105. FOF_NOCONFIRMATION | FOF_SIMPLEPROGRESS,
  2106. FALSE,
  2107. NULL,
  2108. MAKEINTRESOURCE(IDS_BB_EMPTYINGWASTEBASKET)};
  2109. // check to see if we need to init our global data first
  2110. if (!InitBBGlobals())
  2111. {
  2112. // this could happen in low memory situations, we have no choice but
  2113. // to fail the empty
  2114. return E_OUTOFMEMORY;
  2115. }
  2116. if (g_dwStopWatchMode) // If the shell perf mode is enabled, time the empty operation
  2117. {
  2118. MarkBBPurgeAllTime(TRUE);
  2119. }
  2120. fConfirmed = (dwFlags & SHERB_NOCONFIRMATION);
  2121. if (!fConfirmed)
  2122. {
  2123. // find out how many files we have...
  2124. BBDATAENTRYW bbdew;
  2125. TCHAR szSrcName[MAX_PATH];
  2126. WIN32_FIND_DATA fd;
  2127. CONFIRM_DATA cd = {CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_PROGRAM_FILE | CONFIRM_MULTIPLE, 0};
  2128. nFiles = BBTotalCount(&idDrive, NULL, MAX_EMPTY_FILES);
  2129. if (!nFiles)
  2130. {
  2131. if (g_dwStopWatchMode)
  2132. {
  2133. MarkBBPurgeAllTime(FALSE);
  2134. }
  2135. return S_FALSE; // no files to delete
  2136. }
  2137. // first do the confirmation thing
  2138. fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
  2139. // We have to call IsBitBucketInited() here since we could be in BBPurgeAll as a result
  2140. // of a corrupt bitbucket. In this case, the g_pBitBucket[idDrive] has not been inited and
  2141. // therefore we can't use it yet
  2142. if (nFiles == 1 && IsBitBucketInited(idDrive))
  2143. {
  2144. HANDLE hFile = OpenBBInfoFile(idDrive, OPENBBINFO_READ, 0);
  2145. if (hFile != INVALID_HANDLE_VALUE)
  2146. {
  2147. ReadNextDataEntry(hFile, &bbdew, TRUE, idDrive);
  2148. CloseBBInfoFile(hFile, idDrive);
  2149. StringCchCopy(szSrcName, ARRAYSIZE(szSrcName), bbdew.szOriginal);
  2150. }
  2151. else
  2152. {
  2153. if (g_dwStopWatchMode)
  2154. {
  2155. MarkBBPurgeAllTime(FALSE);
  2156. }
  2157. return S_FALSE; // no files to delete
  2158. }
  2159. }
  2160. else
  2161. {
  2162. // If we haven't inited this bucket yet or there are MAX_EMPTY_FILES or more files,
  2163. // then use the generic empty message
  2164. if (nFiles == 1 || nFiles >= MAX_EMPTY_FILES)
  2165. {
  2166. // counting up the total # of files in the bitbucket scales as
  2167. // the # of files (duh!). This can get pretty expensive, so if there
  2168. // are MAX_EMPTY_FILES or more files in the bin, we just give a generic
  2169. // error message
  2170. // set this so ConfirmFileOp knows to use the generic message
  2171. nFiles = -1;
  2172. }
  2173. szSrcName[0] = 0;
  2174. }
  2175. if (ConfirmFileOp(hwndOwner, NULL, &cd, nFiles, 0, CONFIRM_DELETE_FILE | CONFIRM_WASTEBASKET_PURGE,
  2176. szSrcName, &fd, NULL, &fd, NULL) == IDYES)
  2177. {
  2178. fConfirmed = TRUE;
  2179. }
  2180. }
  2181. if (fConfirmed)
  2182. {
  2183. DECLAREWAITCURSOR;
  2184. SetWaitCursor();
  2185. if (dwFlags & SHERB_NOPROGRESSUI)
  2186. {
  2187. sFileOp.fFlags |= FOF_SILENT;
  2188. }
  2189. for (idDrive = 0; (idDrive < MAX_BITBUCKETS) && !sFileOp.fAnyOperationsAborted; idDrive++)
  2190. {
  2191. if (MakeBitBucket(idDrive))
  2192. {
  2193. HANDLE hFile;
  2194. // nuke all the BB files (d*.*)
  2195. if (DriveIDToBBPath(idDrive, szPath) &&
  2196. PathAppend(szPath, c_szDStarDotStar))
  2197. {
  2198. szPath[lstrlen(szPath) + 1] = 0; // double null terminate
  2199. // turn off redraw for now.
  2200. ShellFolderView_SetRedraw(hwndOwner, FALSE);
  2201. hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
  2202. if (INVALID_HANDLE_VALUE != hFile)
  2203. {
  2204. // now do the actual delete.
  2205. if (SHFileOperation(&sFileOp) || sFileOp.fAnyOperationsAborted)
  2206. {
  2207. TraceMsg(TF_BITBUCKET, "Bitbucket: emptying bucket on %s failed or user aborted", szPath);
  2208. // NOTE: the info file may point to some files that have been deleted,
  2209. // it will be cleaned up later
  2210. }
  2211. else
  2212. {
  2213. // reset the info file since we deleted it as part of the empty operation
  2214. ResetInfoFileHeader(hFile, g_pBitBucket[idDrive]->fIsUnicode);
  2215. }
  2216. // we always reset the desktop.ini
  2217. CreateRecyclerDirectory(idDrive);
  2218. CloseBBInfoFile(hFile, idDrive);
  2219. }
  2220. ShellFolderView_SetRedraw(hwndOwner, TRUE);
  2221. }
  2222. }
  2223. }
  2224. if (!(dwFlags & SHERB_NOSOUND))
  2225. {
  2226. SHPlaySound(TEXT("EmptyRecycleBin"));
  2227. }
  2228. SHUpdateRecycleBinIcon();
  2229. ResetWaitCursor();
  2230. }
  2231. if (g_dwStopWatchMode)
  2232. {
  2233. MarkBBPurgeAllTime(FALSE);
  2234. }
  2235. return S_OK;
  2236. }
  2237. BOOL BBNukeFile(LPCTSTR pszPath, DWORD dwAttribs)
  2238. {
  2239. if (Win32DeleteFile(pszPath))
  2240. {
  2241. FOUndo_FileReallyDeleted((LPTSTR)pszPath);
  2242. return TRUE;
  2243. }
  2244. return FALSE;
  2245. }
  2246. BOOL BBNukeFolder(LPCTSTR pszDir)
  2247. {
  2248. TCHAR szPath[MAX_PATH];
  2249. BOOL fRet;
  2250. if (PathCombine(szPath, pszDir, c_szStarDotStar))
  2251. {
  2252. WIN32_FIND_DATA fd;
  2253. HANDLE hfind = FindFirstFile(szPath, &fd);
  2254. if (hfind != INVALID_HANDLE_VALUE)
  2255. {
  2256. do
  2257. {
  2258. LPTSTR pszFile = fd.cAlternateFileName[0] ? fd.cAlternateFileName : fd.cFileName;
  2259. if (pszFile[0] != TEXT('.'))
  2260. {
  2261. // use the short path name so that we avoid hitting MAX_PATH too soon
  2262. if (PathCombine(szPath, pszDir, pszFile))
  2263. {
  2264. if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  2265. {
  2266. // even if this fails, we keep going.
  2267. // we want to delete as much as possible
  2268. BBNukeFolder(szPath);
  2269. }
  2270. else
  2271. {
  2272. BBNukeFile(szPath, fd.dwFileAttributes);
  2273. }
  2274. }
  2275. }
  2276. } while (FindNextFile(hfind, &fd));
  2277. FindClose(hfind);
  2278. }
  2279. }
  2280. fRet = Win32RemoveDirectory(pszDir);
  2281. // if everything was successful, we need to notify any undo stuff about this
  2282. if (fRet)
  2283. {
  2284. FOUndo_FileReallyDeleted((LPTSTR)szPath);
  2285. }
  2286. return fRet;
  2287. }
  2288. BOOL BBNuke(LPCTSTR pszPath)
  2289. {
  2290. BOOL fRet = FALSE;
  2291. // verify that the file exists
  2292. DWORD dwAttribs = GetFileAttributes(pszPath);
  2293. TraceMsg(TF_BITBUCKET, "Bitbucket: BBNuke called on %s ", pszPath);
  2294. if (dwAttribs != (UINT)-1)
  2295. {
  2296. // this was a directory, we need to recurse in and delete everything inside
  2297. if (dwAttribs & FILE_ATTRIBUTE_DIRECTORY)
  2298. {
  2299. fRet = BBNukeFolder(pszPath);
  2300. }
  2301. else
  2302. {
  2303. fRet = BBNukeFile(pszPath, dwAttribs);
  2304. }
  2305. }
  2306. return fRet;
  2307. }
  2308. DWORD PurgeBBFiles(int idDrive)
  2309. {
  2310. ULARGE_INTEGER uliCurrentSize;
  2311. CountDeletedFilesOnDrive(idDrive, &uliCurrentSize, 0);
  2312. if (uliCurrentSize.HighPart || uliCurrentSize.LowPart > g_pBitBucket[idDrive]->cbMaxSize)
  2313. {
  2314. DWORD dwDataEntrySize = g_pBitBucket[idDrive]->fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA);
  2315. HANDLE hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
  2316. if (hFile != INVALID_HANDLE_VALUE)
  2317. {
  2318. BBDATAENTRYW bbdew;
  2319. TCHAR szBBPath[MAX_PATH];
  2320. if (DriveIDToBBPath(idDrive, szBBPath))
  2321. {
  2322. // while we're too big, find something to delete
  2323. while ((uliCurrentSize.HighPart || uliCurrentSize.LowPart > g_pBitBucket[idDrive]->cbMaxSize)
  2324. && ReadNextDataEntry(hFile, &bbdew, TRUE, idDrive))
  2325. {
  2326. TCHAR szPath[MAX_PATH];
  2327. TCHAR szDeletedFile[MAX_PATH];
  2328. if (GetDeletedFileName(szDeletedFile, ARRAYSIZE(szDeletedFile), &bbdew) &&
  2329. PathCombine(szPath, szBBPath, szDeletedFile))
  2330. {
  2331. BBNuke(szPath);
  2332. NukeFileInfoBeforePoint(hFile, &bbdew, dwDataEntrySize);
  2333. // subtract the size of what we just nuked
  2334. uliCurrentSize.QuadPart -= bbdew.dwSize;
  2335. }
  2336. TraceMsg(TF_BITBUCKET, "Bitbucket: purging drive %d, curent size = %I64u, max size = %d", idDrive, uliCurrentSize.QuadPart, g_pBitBucket[idDrive]->cbMaxSize);
  2337. }
  2338. }
  2339. CloseBBInfoFile(hFile, idDrive);
  2340. }
  2341. }
  2342. return uliCurrentSize.LowPart;
  2343. }
  2344. STDAPI BBFileNameToInfo(LPCTSTR pszFileName, int *pidDrive, int *piIndex)
  2345. {
  2346. HRESULT hr = E_FAIL;
  2347. if (lstrcmpi(pszFileName, c_szInfo) &&
  2348. lstrcmpi(pszFileName, c_szInfo2) &&
  2349. lstrcmpi(pszFileName, c_szDesktopIni) &&
  2350. lstrcmpi(pszFileName, TEXT("Recycled")) &&
  2351. (StrChr(pszFileName, TEXT('\\')) == NULL)) // recycle bin dosen't support multi-level paths
  2352. {
  2353. if ((pszFileName[0] == TEXT('D')) || (pszFileName[0] == TEXT('d')))
  2354. {
  2355. if (pszFileName[1])
  2356. {
  2357. if (pidDrive)
  2358. {
  2359. hr = S_OK;
  2360. if (pszFileName[1] == TEXT('@'))
  2361. *pidDrive = SERVERDRIVE;
  2362. else if (InRange(pszFileName[1], TEXT('a'), TEXT('z')))
  2363. *pidDrive = pszFileName[1] - TEXT('a');
  2364. else if (InRange(pszFileName[1], TEXT('A'), TEXT('Z')))
  2365. *pidDrive = pszFileName[1] - TEXT('A');
  2366. else
  2367. hr = E_FAIL;
  2368. }
  2369. if (piIndex)
  2370. {
  2371. // this depends on StrToInt stoping is parsing when it hits the file extension
  2372. *piIndex = StrToInt(&pszFileName[2]);
  2373. hr = S_OK;
  2374. }
  2375. }
  2376. }
  2377. }
  2378. return hr;
  2379. }
  2380. // converts C:\RECYCLED\Dc19.foo to 19
  2381. int BBPathToIndex(LPCTSTR pszPath)
  2382. {
  2383. int iIndex;
  2384. LPTSTR pszFileName = PathFindFileName(pszPath);
  2385. if (SUCCEEDED(BBFileNameToInfo(pszFileName, NULL, &iIndex)))
  2386. {
  2387. return iIndex;
  2388. }
  2389. return -1;
  2390. }
  2391. BOOL ReadNextDataEntry(HANDLE hFile, LPBBDATAENTRYW pbbdew, BOOL fSkipDeleted, int idDrive)
  2392. {
  2393. DWORD dwBytesRead;
  2394. DWORD dwDataEntrySize = g_pBitBucket[idDrive]->fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA);
  2395. ZeroMemory(pbbdew, sizeof(*pbbdew));
  2396. TryAgain:
  2397. if (ReadFile(hFile, pbbdew, dwDataEntrySize, &dwBytesRead, NULL) &&
  2398. (dwBytesRead == dwDataEntrySize))
  2399. {
  2400. TCHAR szDeleteFileName[MAX_PATH];
  2401. TCHAR szOldPath[MAX_PATH];
  2402. if (fSkipDeleted && IsDeletedEntry(pbbdew))
  2403. {
  2404. goto TryAgain;
  2405. }
  2406. // for ansi entries fill out the unicode version of the original
  2407. if (!g_pBitBucket[idDrive]->fIsUnicode)
  2408. {
  2409. BBDATAENTRYA *pbbdea = (BBDATAENTRYA *)pbbdew;
  2410. SHAnsiToUnicode(pbbdea->szOriginal, pbbdew->szOriginal, ARRAYSIZE(pbbdew->szOriginal));
  2411. }
  2412. // We check for a drive that has had its letter changed since this record was added.
  2413. // In this case, we want to restore the files that were deleted on this volume to this volume.
  2414. if (pbbdew->idDrive != idDrive)
  2415. {
  2416. TCHAR szNewPath[MAX_PATH];
  2417. if (DriveIDToBBPath(idDrive, szOldPath) &&
  2418. SUCCEEDED(StringCchCopy(szNewPath, ARRAYSIZE(szNewPath), szOldPath)))
  2419. {
  2420. if (GetDeletedFileName(szDeleteFileName, ARRAYSIZE(szDeleteFileName), pbbdew) &&
  2421. PathAppend(szOldPath, szDeleteFileName))
  2422. {
  2423. if (GetDeletedFileNameFromParts(szDeleteFileName, ARRAYSIZE(szDeleteFileName), idDrive, pbbdew->iIndex, pbbdew->szOriginal) &&
  2424. PathAppend(szNewPath, szDeleteFileName))
  2425. {
  2426. TraceMsg(TF_BITBUCKET, "Bitbucket: found entry %s corospoinding to old drive letter, whacking it to be on drive %d !!", szOldPath, idDrive);
  2427. // we need to rename the file from d?0.txt to d<idDrive>0.txt
  2428. if (!Win32MoveFile(szOldPath, szNewPath, GetFileAttributes(szOldPath) & FILE_ATTRIBUTE_DIRECTORY))
  2429. {
  2430. TraceMsg(TF_BITBUCKET, "Bitbucket: failed to rename %s to %s, getlasterror = %d", szOldPath, szNewPath, GetLastError());
  2431. goto DeleteEntry;
  2432. }
  2433. // whack the rest of the information about this entry to match the new drive ID
  2434. pbbdew->idDrive = idDrive;
  2435. pbbdew->szShortName[0] = 'A' + (CHAR)idDrive;
  2436. if (g_pBitBucket[idDrive]->fIsUnicode)
  2437. {
  2438. // for unicode volumes we need to whack the first letter of the long name as well
  2439. pbbdew->szOriginal[0] = L'A' + (WCHAR)idDrive;
  2440. }
  2441. }
  2442. }
  2443. }
  2444. }
  2445. else
  2446. {
  2447. // Starting with NT5, when we delete or restore items, we dont bother updating the info file.
  2448. // So we need to make sure that the entry we have has not been restored or really nuked.
  2449. if (GetDeletedFilePath(szOldPath, pbbdew) &&
  2450. !PathFileExists(szOldPath))
  2451. {
  2452. DeleteEntry:
  2453. // this entry is really deleted, so mark it as such now
  2454. NukeFileInfoBeforePoint(hFile, pbbdew, dwDataEntrySize);
  2455. if (fSkipDeleted)
  2456. {
  2457. goto TryAgain;
  2458. }
  2459. }
  2460. }
  2461. return TRUE;
  2462. }
  2463. else
  2464. {
  2465. return FALSE;
  2466. }
  2467. }
  2468. //
  2469. // the file pointer is RIGHT AFTER the entry that we want to delete.
  2470. //
  2471. // back the file pointer up one record and mark it deleted
  2472. //
  2473. void NukeFileInfoBeforePoint(HANDLE hFile, LPBBDATAENTRYW pbbdew, DWORD dwDataEntrySize)
  2474. {
  2475. DWORD dwBytesWritten;
  2476. LONG lPos = SetFilePointer(hFile, 0, NULL, FILE_CURRENT);
  2477. ASSERT((DWORD)lPos >= dwDataEntrySize + sizeof(BBDATAHEADER));
  2478. if ((DWORD)lPos >= dwDataEntrySize + sizeof(BBDATAHEADER))
  2479. {
  2480. // found the entry.. back up the file pointer to the beginning
  2481. // of this record and mark it as deleted
  2482. lPos -= dwDataEntrySize;
  2483. SetFilePointer(hFile, lPos, NULL, FILE_BEGIN);
  2484. MarkEntryDeleted(pbbdew);
  2485. if (WriteFile(hFile, pbbdew, dwDataEntrySize, &dwBytesWritten, NULL))
  2486. {
  2487. ASSERT(dwDataEntrySize == dwBytesWritten);
  2488. }
  2489. else
  2490. {
  2491. TraceMsg(TF_BITBUCKET, "Bitbucket: couldn't nuke file info");
  2492. // move the file pointer back to where it was when we entered this function
  2493. SetFilePointer(hFile, lPos + dwDataEntrySize, NULL, FILE_BEGIN);
  2494. }
  2495. }
  2496. }
  2497. //
  2498. // This closes the hFile and sends out an SHCNE_UPDATEITEM for the info file on
  2499. // drive idDrive
  2500. //
  2501. void CloseBBInfoFile(HANDLE hFile, int idDrive)
  2502. {
  2503. TCHAR szInfoFile[MAX_PATH];
  2504. ASSERT(hFile != INVALID_HANDLE_VALUE);
  2505. CloseHandle(hFile);
  2506. if (DriveIDToBBPath(idDrive, szInfoFile) &&
  2507. PathAppend(szInfoFile, c_szInfo2))
  2508. {
  2509. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szInfoFile, NULL);
  2510. }
  2511. }
  2512. // One half second (500 ms = 0.5 s)
  2513. #define BBINFO_OPEN_RETRY_PERIOD 500
  2514. // Retry 30 times (at least 20 s)
  2515. #define BBINFO_OPEN_MAX_RETRIES 40
  2516. //
  2517. // This opens up a handle to the bitbucket info file.
  2518. //
  2519. // NOTE: use CloseBBInfoFile so that we generate the proper
  2520. // SHChangeNotify event for the info file.
  2521. //
  2522. HANDLE OpenBBInfoFile(int idDrive, DWORD dwFlags, int iRetryCount)
  2523. {
  2524. HANDLE hFile = INVALID_HANDLE_VALUE;
  2525. TCHAR szBBPath[MAX_PATH];
  2526. TCHAR szInfo[MAX_PATH];
  2527. int nAttempts = 0;
  2528. DWORD dwLastErr;
  2529. DECLAREWAITCURSOR;
  2530. if ((iRetryCount == 0) || (iRetryCount > BBINFO_OPEN_MAX_RETRIES))
  2531. {
  2532. // zero retry count means that the caller wants the max # of retries
  2533. iRetryCount = BBINFO_OPEN_MAX_RETRIES;
  2534. }
  2535. if (DriveIDToBBPath(idDrive, szBBPath) &&
  2536. GetBBInfo2FileSpec(szBBPath, szInfo))
  2537. {
  2538. // If we are hitting a sharing violation, retry many times
  2539. do
  2540. {
  2541. nAttempts++;
  2542. hFile = CreateFile(szInfo,
  2543. GENERIC_READ | ((OPENBBINFO_WRITE & dwFlags) ? GENERIC_WRITE : 0),
  2544. (OPENBBINFO_WRITE & dwFlags) ? 0 : FILE_SHARE_READ,
  2545. NULL,
  2546. (OPENBBINFO_CREATE & dwFlags) ? OPEN_ALWAYS : OPEN_EXISTING,
  2547. FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_RANDOM_ACCESS,
  2548. NULL);
  2549. if (INVALID_HANDLE_VALUE != hFile)
  2550. {
  2551. // success!
  2552. break;
  2553. }
  2554. dwLastErr = GetLastError();
  2555. if (ERROR_SHARING_VIOLATION != dwLastErr)
  2556. {
  2557. break;
  2558. }
  2559. TraceMsg(TF_BITBUCKET, "Bitbucket: sharing violation on info file (retry %d)", nAttempts - 1);
  2560. if (nAttempts < iRetryCount)
  2561. {
  2562. SetWaitCursor();
  2563. Sleep(BBINFO_OPEN_RETRY_PERIOD);
  2564. ResetWaitCursor();
  2565. }
  2566. } while (nAttempts < iRetryCount);
  2567. }
  2568. if (hFile == INVALID_HANDLE_VALUE)
  2569. {
  2570. TraceMsg(TF_BITBUCKET, "Bitbucket: could not open a handle to %s - error 0x%08x!!", szInfo, dwLastErr); hFile;
  2571. }
  2572. else
  2573. {
  2574. // set the file pointer to just after the dataheader
  2575. SetFilePointer(hFile, sizeof(BBDATAHEADER), NULL, FILE_BEGIN);
  2576. }
  2577. return hFile;
  2578. }
  2579. void BBAddDeletedFileInfo(LPCTSTR pszOriginal, LPCTSTR pszShortName, int iIndex, int idDrive, DWORD dwSize, HDPA *phdpaDeletedFiles)
  2580. {
  2581. BBDATAENTRYW *pbbdew;
  2582. BOOL fSuccess = FALSE; // assume failure
  2583. // Flush the cache regularly
  2584. if (*phdpaDeletedFiles && DPA_GetPtrCount(*phdpaDeletedFiles) >= 1)
  2585. {
  2586. pbbdew = (BBDATAENTRYW *)DPA_FastGetPtr(*phdpaDeletedFiles, 0);
  2587. // Flush the cache before we start deleting files on a different drive, or
  2588. // when it's too full
  2589. if (pbbdew->idDrive != idDrive || DPA_GetPtrCount(*phdpaDeletedFiles) >= 128)
  2590. {
  2591. BBFinishDelete(*phdpaDeletedFiles);
  2592. *phdpaDeletedFiles = NULL;
  2593. }
  2594. }
  2595. pbbdew = NULL;
  2596. if (!*phdpaDeletedFiles)
  2597. {
  2598. *phdpaDeletedFiles = DPA_Create(0); // Use default growth value
  2599. }
  2600. if (*phdpaDeletedFiles)
  2601. {
  2602. pbbdew = (BBDATAENTRYW*)LocalAlloc(LPTR, sizeof(*pbbdew));
  2603. if (pbbdew)
  2604. {
  2605. SYSTEMTIME st;
  2606. if (g_pBitBucket[idDrive]->fIsUnicode)
  2607. {
  2608. // Create a BBDATAENTRYW from a unicode name
  2609. StringCchCopy(pbbdew->szOriginal, ARRAYSIZE(pbbdew->szOriginal), pszOriginal);
  2610. if (!DoesStringRoundTrip(pszOriginal, pbbdew->szShortName, ARRAYSIZE(pbbdew->szShortName)))
  2611. {
  2612. SHUnicodeToAnsi(pszShortName, pbbdew->szShortName, ARRAYSIZE(pbbdew->szShortName));
  2613. }
  2614. }
  2615. else
  2616. {
  2617. BBDATAENTRYA *pbbdea = (BBDATAENTRYA *)pbbdew;
  2618. // Create a BBDATAENTRYA from a unicode name
  2619. if (!DoesStringRoundTrip(pszOriginal, pbbdea->szOriginal, ARRAYSIZE(pbbdea->szOriginal)))
  2620. {
  2621. SHUnicodeToAnsi(pszShortName, pbbdea->szOriginal, ARRAYSIZE(pbbdea->szOriginal));
  2622. }
  2623. }
  2624. pbbdew->iIndex = iIndex;
  2625. pbbdew->idDrive = idDrive;
  2626. pbbdew->dwSize = ROUND_TO_CLUSTER(dwSize, g_pBitBucket[idDrive]->dwClusterSize);
  2627. GetSystemTime(&st); // Get time of deletion
  2628. SystemTimeToFileTime(&st, &pbbdew->ft);
  2629. if (DPA_AppendPtr(*phdpaDeletedFiles, pbbdew) != -1)
  2630. {
  2631. fSuccess = TRUE;
  2632. }
  2633. }
  2634. }
  2635. if (!fSuccess)
  2636. {
  2637. TCHAR szBBPath[MAX_PATH];
  2638. TCHAR szFileName[MAX_PATH];
  2639. ASSERTMSG(FALSE, "BitBucket: failed to record deleted file %s , have to nuke it!!", pszOriginal);
  2640. LocalFree(pbbdew);
  2641. // get the recycled dir and tack on the file name
  2642. if (DriveIDToBBPath(idDrive, szBBPath) &&
  2643. GetDeletedFileNameFromParts(szFileName,
  2644. ARRAYSIZE(szFileName),
  2645. idDrive,
  2646. iIndex,
  2647. pszOriginal) &&
  2648. PathAppend(szBBPath, szFileName))
  2649. {
  2650. // now delete it
  2651. BBNuke(szBBPath);
  2652. }
  2653. }
  2654. }
  2655. BOOL BBFinishDelete(HDPA hdpaDeletedFiles)
  2656. {
  2657. BOOL fSuccess = TRUE; // assume success
  2658. int iDeletedFiles = hdpaDeletedFiles ? DPA_GetPtrCount(hdpaDeletedFiles) : 0;
  2659. if (iDeletedFiles > 0)
  2660. {
  2661. int iCurrentFile = 0;
  2662. BBDATAENTRYW *pbbdew = (BBDATAENTRYW *)DPA_FastGetPtr(hdpaDeletedFiles, iCurrentFile);
  2663. // now write it to the file
  2664. int idDrive = pbbdew->idDrive;
  2665. HANDLE hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
  2666. if (hFile != INVALID_HANDLE_VALUE)
  2667. {
  2668. DWORD dwDataEntrySize = g_pBitBucket[idDrive]->fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA);
  2669. SetFilePointer(hFile, 0, NULL, FILE_END);
  2670. while (iCurrentFile < iDeletedFiles)
  2671. {
  2672. DWORD dwBytesWritten;
  2673. pbbdew = (BBDATAENTRYW *)DPA_FastGetPtr(hdpaDeletedFiles, iCurrentFile);
  2674. // All deletes should be in the same drive for each batch.
  2675. ASSERT(idDrive == pbbdew->idDrive);
  2676. if (!WriteFile(hFile, pbbdew, dwDataEntrySize, &dwBytesWritten, NULL) ||
  2677. (dwDataEntrySize != dwBytesWritten))
  2678. {
  2679. fSuccess = FALSE;
  2680. break;
  2681. }
  2682. LocalFree(pbbdew);
  2683. iCurrentFile++;
  2684. }
  2685. CloseBBInfoFile(hFile, idDrive);
  2686. }
  2687. else
  2688. {
  2689. fSuccess = FALSE;
  2690. }
  2691. if (!fSuccess)
  2692. {
  2693. TCHAR szBBPath[MAX_PATH];
  2694. int iFilesToNuke;
  2695. for (iFilesToNuke = iCurrentFile; iFilesToNuke < iDeletedFiles; iFilesToNuke++)
  2696. {
  2697. pbbdew = DPA_FastGetPtr(hdpaDeletedFiles, iFilesToNuke);
  2698. if (GetDeletedFilePath(szBBPath, pbbdew))
  2699. {
  2700. // now delete it
  2701. BBNuke(szBBPath);
  2702. }
  2703. LocalFree(pbbdew);
  2704. }
  2705. }
  2706. if (iCurrentFile != 0)
  2707. {
  2708. BOOL bPurge = TRUE;
  2709. // since we sucessfully deleted a file, we set this so at the end of the last SHFileOperation call on this drive
  2710. // we will go back and make sure that there isint too much stuff in the bucket.
  2711. RegSetValueEx(g_pBitBucket[idDrive]->hkeyPerUser, TEXT("NeedToPurge"), 0, REG_DWORD, (LPBYTE)&bPurge, sizeof(bPurge));
  2712. }
  2713. }
  2714. if (hdpaDeletedFiles)
  2715. DPA_Destroy(hdpaDeletedFiles);
  2716. return fSuccess;
  2717. }
  2718. // creates the proper SECURITY_DESCRIPTOR for securing the recycle bin
  2719. //
  2720. // NOTE: if return value is non-null, the caller must LocalFree it
  2721. //
  2722. SECURITY_DESCRIPTOR* CreateRecycleBinSecurityDescriptor()
  2723. {
  2724. SHELL_USER_PERMISSION supLocalUser;
  2725. SHELL_USER_PERMISSION supSystem;
  2726. SHELL_USER_PERMISSION supAdministrators;
  2727. PSHELL_USER_PERMISSION aPerms[3] = {&supLocalUser, &supSystem, &supAdministrators};
  2728. // we want the current user to have full control
  2729. supLocalUser.susID = susCurrentUser;
  2730. supLocalUser.dwAccessType = ACCESS_ALLOWED_ACE_TYPE;
  2731. supLocalUser.dwAccessMask = FILE_ALL_ACCESS;
  2732. supLocalUser.fInherit = TRUE;
  2733. supLocalUser.dwInheritMask = (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
  2734. supLocalUser.dwInheritAccessMask = GENERIC_ALL;
  2735. // we want the SYSTEM to have full control
  2736. supSystem.susID = susSystem;
  2737. supSystem.dwAccessType = ACCESS_ALLOWED_ACE_TYPE;
  2738. supSystem.dwAccessMask = FILE_ALL_ACCESS;
  2739. supSystem.fInherit = TRUE;
  2740. supSystem.dwInheritMask = (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
  2741. supSystem.dwInheritAccessMask = GENERIC_ALL;
  2742. // we want the Administrators to have full control
  2743. supAdministrators.susID = susAdministrators;
  2744. supAdministrators.dwAccessType = ACCESS_ALLOWED_ACE_TYPE;
  2745. supAdministrators.dwAccessMask = FILE_ALL_ACCESS;
  2746. supAdministrators.fInherit = TRUE;
  2747. supAdministrators.dwInheritMask = (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
  2748. supAdministrators.dwInheritAccessMask = GENERIC_ALL;
  2749. return GetShellSecurityDescriptor(aPerms, ARRAYSIZE(aPerms));
  2750. }
  2751. //
  2752. // Creates the secure recycle bin directory (eg one with ACL's that protect it)
  2753. // for recycle bins on NTFS volumes.
  2754. //
  2755. BOOL CreateSecureRecyclerDirectory(LPCTSTR pszPath)
  2756. {
  2757. BOOL fSuccess = FALSE; // assume failure
  2758. SECURITY_DESCRIPTOR* psd = CreateRecycleBinSecurityDescriptor();
  2759. if (psd)
  2760. {
  2761. DWORD cbSA = GetSecurityDescriptorLength(psd);
  2762. SECURITY_DESCRIPTOR* psdSelfRelative;
  2763. psdSelfRelative = (SECURITY_DESCRIPTOR*)LocalAlloc(LPTR, cbSA);
  2764. if (psdSelfRelative)
  2765. {
  2766. if (MakeSelfRelativeSD(psd, psdSelfRelative, &cbSA))
  2767. {
  2768. SECURITY_ATTRIBUTES sa;
  2769. // Build the security attributes structure
  2770. sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  2771. sa.lpSecurityDescriptor = psdSelfRelative;
  2772. sa.bInheritHandle = FALSE;
  2773. fSuccess = (SHCreateDirectoryEx(NULL, pszPath, &sa) == ERROR_SUCCESS);
  2774. }
  2775. LocalFree(psdSelfRelative);
  2776. }
  2777. LocalFree(psd);
  2778. }
  2779. return fSuccess;
  2780. }
  2781. BOOL CreateRecyclerDirectory(int idDrive)
  2782. {
  2783. TCHAR szPath[MAX_PATH];
  2784. TCHAR szRoot[MAX_PATH];
  2785. BOOL bResult = FALSE;
  2786. // NOTE: we currently do not to check for FAT/FAT32 drives that have been
  2787. // upgraded to NTFS and migrate the recycle bin info over
  2788. if (DriveIDToBBPath(idDrive, szPath) &&
  2789. DriveIDToBBRoot(idDrive, szRoot))
  2790. {
  2791. BOOL bExists = PathIsDirectory(szPath);
  2792. if (!bExists)
  2793. {
  2794. if (CMtPt_IsSecure(idDrive))
  2795. {
  2796. bExists = CreateSecureRecyclerDirectory(szPath);
  2797. }
  2798. else
  2799. {
  2800. bExists = (SHCreateDirectoryEx(NULL, szPath, NULL) == ERROR_SUCCESS);
  2801. }
  2802. }
  2803. if (bExists && PathAppend(szPath, c_szDesktopIni))
  2804. {
  2805. // CLSID_RecycleBin
  2806. WritePrivateProfileString(STRINI_CLASSINFO, TEXT("CLSID"), TEXT("{645FF040-5081-101B-9F08-00AA002F954E}"), szPath);
  2807. SetFileAttributes(szPath, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM); // desktop.ini
  2808. PathRemoveFileSpec(szPath);
  2809. // Hide all of the directories along the way until we hit the BB root
  2810. do
  2811. {
  2812. SetFileAttributes(szPath, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
  2813. PathRemoveFileSpec(szPath);
  2814. } while (0 != lstrcmpi(szPath, szRoot));
  2815. // everything's set. let's add it in
  2816. // try to load the and initalize
  2817. bResult = TRUE;
  2818. }
  2819. }
  2820. return bResult;
  2821. }
  2822. //
  2823. // this sets up the bitbucket directory and allocs the internal structures
  2824. //
  2825. BOOL MakeBitBucket(int idDrive)
  2826. {
  2827. BOOL bRet = FALSE;
  2828. if (IsBitBucketableDrive(idDrive))
  2829. {
  2830. if (IsBitBucketInited(idDrive))
  2831. {
  2832. LONG lBucketDirtyCount = SHGlobalCounterGetValue(g_pBitBucket[idDrive]->hgcDirtyCount);
  2833. LONG lGlobalDirtyCount = SHGlobalCounterGetValue(g_hgcGlobalDirtyCount);
  2834. // check to see if we need to refresh the settings for this bucket
  2835. if (lGlobalDirtyCount > g_lProcessDirtyCount)
  2836. {
  2837. // global settings change, so refresh all buckets
  2838. g_lProcessDirtyCount = lGlobalDirtyCount;
  2839. RefreshAllBBDriveSettings();
  2840. }
  2841. else if (lBucketDirtyCount > g_pBitBucket[idDrive]->lCurrentDirtyCount)
  2842. {
  2843. // just this buckets settings changed, so refresh only this one
  2844. g_pBitBucket[idDrive]->lCurrentDirtyCount = lBucketDirtyCount;
  2845. RefreshBBDriveSettings(idDrive);
  2846. }
  2847. bRet = TRUE;
  2848. }
  2849. else
  2850. {
  2851. bRet = AllocBBDriveInfo(idDrive);
  2852. }
  2853. }
  2854. return bRet;
  2855. }
  2856. // Tells if a file will *likely* be recycled...
  2857. // this could be wrong if:
  2858. //
  2859. // * disk is full
  2860. // * file is really a folder
  2861. // * file greater than the allocated size for the recycle directory
  2862. // * file is in use or no ACLS to move or delete it
  2863. //
  2864. BOOL BBWillRecycle(LPCTSTR pszFile, INT* piRet)
  2865. {
  2866. INT iRet = BBDELETE_SUCCESS;
  2867. int idDrive = DriveIDFromBBPath(pszFile);
  2868. // MakeBitBucket will ensure that the global & per-bucket settings we have are current
  2869. if (!MakeBitBucket(idDrive) || g_pBitBucket[idDrive]->fNukeOnDelete || (g_pBitBucket[idDrive]->iPercent == 0))
  2870. {
  2871. iRet = BBDELETE_FORCE_NUKE;
  2872. }
  2873. else if (SERVERDRIVE == idDrive)
  2874. {
  2875. // Check to see if the serverdrive is offline (don't recycle while offline to prevent
  2876. // synchronization conflicts when coming back online):
  2877. TCHAR szVolume[MAX_PATH];
  2878. LONG lStatus;
  2879. if (DriveIDToBBVolumeRoot(idDrive, szVolume))
  2880. {
  2881. lStatus = GetOfflineShareStatus(szVolume);
  2882. if ((CSC_SHARESTATUS_OFFLINE == lStatus) || (CSC_SHARESTATUS_SERVERBACK == lStatus))
  2883. {
  2884. iRet = BBDELETE_NUKE_OFFLINE;
  2885. }
  2886. }
  2887. else
  2888. {
  2889. iRet = BBDELETE_NUKE_OFFLINE;
  2890. }
  2891. }
  2892. if (piRet)
  2893. {
  2894. *piRet = iRet;
  2895. }
  2896. return (BBDELETE_SUCCESS == iRet);
  2897. }
  2898. //
  2899. // This is called at the end of the last pending SHFileOperation that involves deletes.
  2900. // We wait till there arent any more people deleteing before we go try to compact the info
  2901. // file or purge entries and make the bitbucket respect its cbMaxSize.
  2902. //
  2903. void CheckCompactAndPurge()
  2904. {
  2905. int i;
  2906. TCHAR szBBKey[MAX_PATH];
  2907. HKEY hkBBPerUser;
  2908. for (i = 0; i < MAX_BITBUCKETS ; i++)
  2909. {
  2910. DriveIDToBBRegKey(i, szBBKey);
  2911. // NOTE: these function needs to manually construct the key because it would like to avoid calling MakeBitBucket()
  2912. // for drives that it has yet to look at (this is a performance optimization)
  2913. if (RegOpenKeyEx(g_hkBitBucketPerUser, szBBKey, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &hkBBPerUser) == ERROR_SUCCESS)
  2914. {
  2915. BOOL bCompact = FALSE;
  2916. BOOL bPurge = FALSE;
  2917. DWORD dwSize;
  2918. dwSize = sizeof(bCompact);
  2919. if (RegQueryValueEx(hkBBPerUser, TEXT("NeedToCompact"), NULL, NULL, (LPBYTE)&bCompact, &dwSize) == ERROR_SUCCESS && bCompact == TRUE)
  2920. {
  2921. // reset this key so no one else tries to compact this bitbucket
  2922. RegDeleteValue(hkBBPerUser, TEXT("NeedToCompact"));
  2923. }
  2924. dwSize = sizeof(bPurge);
  2925. if (RegQueryValueEx(hkBBPerUser, TEXT("NeedToPurge"), NULL, NULL, (LPBYTE)&bPurge, &dwSize) == ERROR_SUCCESS && bPurge == TRUE)
  2926. {
  2927. // reset this key so no one else tries to purge this bitbucket
  2928. RegDeleteValue(hkBBPerUser, TEXT("NeedToPurge"));
  2929. }
  2930. if (MakeBitBucket(i))
  2931. {
  2932. if (bCompact)
  2933. {
  2934. TraceMsg(TF_BITBUCKET, "Bitbucket: compacting drive %d",i);
  2935. CompactBBInfoFile(i);
  2936. }
  2937. if (bPurge)
  2938. {
  2939. TraceMsg(TF_BITBUCKET, "Bitbucket: purging drive %d", i);
  2940. PurgeBBFiles(i);
  2941. }
  2942. }
  2943. RegCloseKey(hkBBPerUser);
  2944. }
  2945. }
  2946. SHUpdateRecycleBinIcon();
  2947. SHChangeNotify(0, SHCNF_FLUSH | SHCNF_FLUSHNOWAIT, NULL, NULL);
  2948. }
  2949. // Initialization to call prior to BBDeleteFile
  2950. BOOL BBDeleteFileInit(LPTSTR pszFile, INT* piRet)
  2951. {
  2952. // check to see if we need to init our global data first
  2953. if (!InitBBGlobals())
  2954. {
  2955. // this could happen in low memory situations, we have no choice but
  2956. // to really nuke the file
  2957. *piRet = BBDELETE_FORCE_NUKE;
  2958. return FALSE;
  2959. }
  2960. if (!BBWillRecycle(pszFile, piRet))
  2961. {
  2962. // We failed to create the recycler directory on the volume, or
  2963. // this is the case where the user has "delete files immeadately" set, or
  2964. // % max size=0, etc.
  2965. return FALSE;
  2966. }
  2967. return TRUE;
  2968. }
  2969. // return:
  2970. //
  2971. // TRUE - The file/folder was sucessfully moved to the recycle bin. We set lpiReturn = BBDELETE_SUCCESS for this case.
  2972. //
  2973. // FALSE - The file/folder could not be moved to the recycle bin
  2974. // In this case, the piRet value tells WHY the file/folder could not be recycled:
  2975. //
  2976. // BBDELETE_FORCE_NUKE - User has "delete file immeadately" set, or % max size=0, or we failed to
  2977. // create the recycler directory.
  2978. //
  2979. // BBDELETE_CANNOT_DELETE - The file/folder is non-deletable because a file under it cannot be deleted.
  2980. // This is an NT only case, and it could be caused by acls or the fact that the
  2981. // folder or a file in it is currently in use.
  2982. //
  2983. // BBDELETE_SIZE_TOO_BIG - The file/folder is bigger than the max allowable size of the recycle bin.
  2984. //
  2985. // BBDELETE_PATH_TOO_LONG - The path would be too long ( > MAX_PATH), if the file were to be moved to the
  2986. // the recycle bin directory at the root of the drive
  2987. //
  2988. // BBDELETE_UNKNOWN_ERROR - Some other error occured, GetLastError() should explain why we failed.
  2989. //
  2990. //
  2991. BOOL BBDeleteFile(LPTSTR pszFile, INT* piRet, LPUNDOATOM lpua, BOOL fIsDir, HDPA *phdpaDeletedFiles, ULARGE_INTEGER ulSize)
  2992. {
  2993. int iRet;
  2994. TCHAR szBitBucket[MAX_PATH];
  2995. TCHAR szFileName[MAX_PATH];
  2996. TCHAR szShortFileName[MAX_PATH];
  2997. DWORD dwLastError;
  2998. int iIndex;
  2999. int idDrive = DriveIDFromBBPath(pszFile);
  3000. int iAttempts = 0;
  3001. TraceMsg(TF_BITBUCKET, "BBDeleteFile (%s)", pszFile);
  3002. // Before we move the file we save off the "short" name. This is in case we have
  3003. // a unicode path and we need the ansi short path in case a win95 machine later tries to
  3004. // restore this file. We can't do this later because GetShortPathName relies on the
  3005. // file actually exising.
  3006. if (!GetShortPathName(pszFile, szShortFileName, ARRAYSIZE(szShortFileName)))
  3007. {
  3008. StringCchCopy(szShortFileName, ARRAYSIZE(szShortFileName), pszFile);
  3009. }
  3010. TryMoveAgain:
  3011. // get the target name and move it
  3012. iIndex = SHGlobalCounterIncrement(g_pBitBucket[idDrive]->hgcNextFileNum);
  3013. if (GetDeletedFileNameFromParts(szFileName, ARRAYSIZE(szFileName), idDrive, iIndex, pszFile) &&
  3014. DriveIDToBBPath(idDrive, szBitBucket) &&
  3015. PathAppend(szBitBucket, szFileName))
  3016. {
  3017. iRet = SHMoveFile(pszFile, szBitBucket, fIsDir ? SHCNE_RMDIR : SHCNE_DELETE);
  3018. // do GetLastError here so that we don't get the last error from the PathFileExists
  3019. dwLastError = (iRet ? ERROR_SUCCESS : GetLastError());
  3020. if (!iRet)
  3021. {
  3022. TraceMsg(TF_BITBUCKET, "BBDeleteFile : Error(%x) moving file (%s)", dwLastError, pszFile);
  3023. if (ERROR_ALREADY_EXISTS == dwLastError)
  3024. {
  3025. TraceMsg(TF_BITBUCKET, "Bitbucket: BBDeleteFile found a file of the same name (%s) - skipping", szBitBucket);
  3026. // generate a new filename and retry
  3027. goto TryMoveAgain;
  3028. }
  3029. // Since we are moving files that may be temporarily in use (e.g. for thumbnail extraction)
  3030. // we may get a transient error (sharing violation is obvious but we also can get access denied
  3031. // for some reason) so we end up trying this again after a short nap.
  3032. else if (((ERROR_ACCESS_DENIED == dwLastError) || (ERROR_SHARING_VIOLATION == dwLastError)) &&
  3033. (iAttempts < MAX_DELETE_ATTEMPTS))
  3034. {
  3035. TraceMsg(TF_BITBUCKET, "BBDeleteFile : sleeping a bit to try again");
  3036. iAttempts++;
  3037. Sleep(SLEEP_DELETE_ATTEMPT); // wait a bit
  3038. goto TryMoveAgain;
  3039. }
  3040. else
  3041. {
  3042. // is our recycled directory still there?
  3043. TCHAR szTemp[MAX_PATH];
  3044. SHGetPathFromIDList(g_pBitBucket[idDrive]->pidl, szTemp);
  3045. // if it already exists or there was some error in creating it, bail
  3046. // else try again
  3047. if (!PathIsDirectory(szTemp) && CreateRecyclerDirectory(idDrive))
  3048. {
  3049. // if we did just re-create the directory, we need to reset the info
  3050. // file or the drive will get corrupted.
  3051. VerifyBBInfoFileHeader(idDrive);
  3052. goto TryMoveAgain;
  3053. }
  3054. }
  3055. }
  3056. else
  3057. {
  3058. // success!
  3059. BBAddDeletedFileInfo(pszFile, szShortFileName, iIndex, idDrive, ulSize.LowPart, phdpaDeletedFiles);
  3060. if (lpua)
  3061. FOUndo_AddInfo(lpua, pszFile, szBitBucket, 0);
  3062. *piRet = BBDELETE_SUCCESS;
  3063. return TRUE;
  3064. }
  3065. }
  3066. else
  3067. {
  3068. // probably means that the path is too long
  3069. *piRet = BBDELETE_PATH_TOO_LONG;
  3070. return FALSE;
  3071. }
  3072. // set back the correct last error
  3073. SetLastError(dwLastError);
  3074. // something bad happened, we dont know what it is
  3075. *piRet = BBDELETE_UNKNOWN_ERROR;
  3076. return FALSE;
  3077. }
  3078. // Basically it understands how we the trash is layed out which is fine
  3079. // as we are in the bitbucket code file... So we skip the first 3
  3080. // characters for the root of the name: c:\ and we truncate off the
  3081. // last part of the name and the rest should match our deathrow name...
  3082. BOOL IsFileInBitBucket(LPCTSTR pszPath)
  3083. {
  3084. TCHAR szPath[MAX_PATH];
  3085. int idDrive = DriveIDFromBBPath(pszPath);
  3086. if (IsBitBucketableDrive(idDrive) &&
  3087. DriveIDToBBPath(idDrive, szPath))
  3088. {
  3089. return (PathCommonPrefix(szPath, pszPath, NULL) == lstrlen(szPath));
  3090. }
  3091. return FALSE;
  3092. }
  3093. //
  3094. // This is called by the copy engine when a user selects "undo".
  3095. //
  3096. // NOTE: takes two multistrings (seperated/double null terminated file lists)
  3097. void UndoBBFileDelete(LPCTSTR pszOriginal, LPCTSTR pszDelFile)
  3098. {
  3099. SHFILEOPSTRUCT sFileOp = {NULL,
  3100. FO_MOVE,
  3101. pszDelFile,
  3102. pszOriginal,
  3103. FOF_NOCONFIRMATION | FOF_MULTIDESTFILES | FOF_SIMPLEPROGRESS};
  3104. SHFileOperation(&sFileOp);
  3105. SHUpdateRecycleBinIcon();
  3106. }
  3107. STDAPI_(void) SHUpdateRecycleBinIcon()
  3108. {
  3109. UpdateIcon(!IsRecycleBinEmpty());
  3110. }
  3111. void PurgeOneBitBucket(HWND hwnd, int idDrive, DWORD dwFlags)
  3112. {
  3113. TCHAR szPath[MAX_PATH + 1]; // +1 for the double-null
  3114. HANDLE hFile;
  3115. SHFILEOPSTRUCT sFileOp = {hwnd,
  3116. FO_DELETE,
  3117. szPath,
  3118. NULL,
  3119. FOF_SIMPLEPROGRESS,
  3120. FALSE,
  3121. NULL,
  3122. MAKEINTRESOURCE(IDS_BB_EMPTYINGWASTEBASKET)};
  3123. ASSERT(g_pBitBucket[idDrive] && (g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1));
  3124. if (dwFlags & SHERB_NOCONFIRMATION)
  3125. {
  3126. sFileOp.fFlags |= FOF_NOCONFIRMATION;
  3127. }
  3128. if (dwFlags & SHERB_NOPROGRESSUI)
  3129. {
  3130. sFileOp.fFlags |= FOF_SILENT;
  3131. }
  3132. if (DriveIDToBBPath(idDrive, szPath) &&
  3133. PathAppend(szPath, c_szDStarDotStar))
  3134. {
  3135. szPath[lstrlen(szPath) + 1] = 0; // double null terminate
  3136. hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
  3137. if (INVALID_HANDLE_VALUE != hFile)
  3138. {
  3139. // now do the actual delete.
  3140. if (SHFileOperation(&sFileOp) || sFileOp.fAnyOperationsAborted)
  3141. {
  3142. TraceMsg(TF_BITBUCKET, "Bitbucket: emptying bucket on %s failed", szPath);
  3143. // NOTE: the info file may point to some files that have been deleted,
  3144. // it will be cleaned up later
  3145. }
  3146. else
  3147. {
  3148. // reset the info file since we just emptied this bucket.
  3149. ResetInfoFileHeader(hFile, g_pBitBucket[idDrive]->fIsUnicode);
  3150. }
  3151. // we always re-create the desktop.ini
  3152. CreateRecyclerDirectory(idDrive);
  3153. CloseBBInfoFile(hFile, idDrive);
  3154. }
  3155. }
  3156. SHUpdateRecycleBinIcon();
  3157. }
  3158. // This function checks to see if an local NT directory is delete-able
  3159. //
  3160. // returns:
  3161. // TRUE yes, the dir can be nuked
  3162. // FALSE for UNC dirs or dirs on network drives, or
  3163. // if the user does not have enough privlidges
  3164. // to delete the file (acls).
  3165. //
  3166. // NOTE: this code is largely stolen from the RemoveDirectoryW API (windows\base\client\dir.c). if
  3167. // you think that there is a bug in it, then diff it against the DeleteFileW and see it something
  3168. // there changed.
  3169. //
  3170. // also sets the last error to explain why
  3171. //
  3172. BOOL IsDirectoryDeletable(LPCTSTR pszDir)
  3173. {
  3174. NTSTATUS Status;
  3175. OBJECT_ATTRIBUTES Obja;
  3176. HANDLE Handle;
  3177. UNICODE_STRING FileName;
  3178. IO_STATUS_BLOCK IoStatusBlock;
  3179. FILE_DISPOSITION_INFORMATION Disposition;
  3180. FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
  3181. BOOLEAN TranslationStatus;
  3182. RTL_RELATIVE_NAME_U RelativeName;
  3183. void *FreeBuffer;
  3184. DWORD dwAttributes;
  3185. BOOL fChangedAttribs = FALSE;
  3186. // return false for any network drives (allow UNC)
  3187. if (IsNetDrive(PathGetDriveNumber(pszDir)))
  3188. {
  3189. return FALSE;
  3190. }
  3191. if (PathIsUNC(pszDir) && PathIsDirectoryEmpty(pszDir))
  3192. {
  3193. // HACKACK - (reinerf) the rdr will nuke the file when we call
  3194. // NtSetInformationFile to set the deleted bit on an empty directory even
  3195. // though we pass READ_CONTROL and we still have a handle to the object.
  3196. // So, to work around this, we just assume we can always delete empty
  3197. // directories (ha!)
  3198. return TRUE;
  3199. }
  3200. // check to see if the dir is readonly
  3201. dwAttributes = GetFileAttributes(pszDir);
  3202. if ((dwAttributes != -1) && (dwAttributes & FILE_ATTRIBUTE_READONLY))
  3203. {
  3204. fChangedAttribs = TRUE;
  3205. if (!SetFileAttributes(pszDir, dwAttributes & ~FILE_ATTRIBUTE_READONLY))
  3206. {
  3207. return FALSE;
  3208. }
  3209. }
  3210. TranslationStatus = RtlDosPathNameToRelativeNtPathName_U(pszDir,
  3211. &FileName,
  3212. NULL,
  3213. &RelativeName);
  3214. if (!TranslationStatus)
  3215. {
  3216. if (fChangedAttribs)
  3217. {
  3218. // set the attribs back
  3219. SetFileAttributes(pszDir, dwAttributes);
  3220. }
  3221. SetLastError(ERROR_PATH_NOT_FOUND);
  3222. return FALSE;
  3223. }
  3224. FreeBuffer = FileName.Buffer;
  3225. if (RelativeName.RelativeName.Length)
  3226. {
  3227. FileName = RelativeName.RelativeName;
  3228. }
  3229. else
  3230. {
  3231. RelativeName.ContainingDirectory = NULL;
  3232. }
  3233. InitializeObjectAttributes(&Obja,
  3234. &FileName,
  3235. OBJ_CASE_INSENSITIVE,
  3236. RelativeName.ContainingDirectory,
  3237. NULL);
  3238. //
  3239. // Open the directory for delete access.
  3240. // Inhibit the reparse behavior using FILE_OPEN_REPARSE_POINT.
  3241. //
  3242. Status = NtOpenFile(&Handle,
  3243. DELETE | SYNCHRONIZE | FILE_READ_ATTRIBUTES | READ_CONTROL,
  3244. &Obja,
  3245. &IoStatusBlock,
  3246. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  3247. FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
  3248. if (!NT_SUCCESS(Status))
  3249. {
  3250. //
  3251. // Back level file systems may not support reparse points and thus not
  3252. // support symbolic links.
  3253. // We infer this is the case when the Status is STATUS_INVALID_PARAMETER.
  3254. //
  3255. if (Status == STATUS_INVALID_PARAMETER)
  3256. {
  3257. //
  3258. // Re-open not inhibiting the reparse behavior and not needing to read the attributes.
  3259. //
  3260. Status = NtOpenFile(&Handle,
  3261. DELETE | SYNCHRONIZE | READ_CONTROL,
  3262. &Obja,
  3263. &IoStatusBlock,
  3264. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  3265. FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT);
  3266. if (!NT_SUCCESS(Status))
  3267. {
  3268. RtlReleaseRelativeName(&RelativeName);
  3269. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3270. if (fChangedAttribs)
  3271. {
  3272. // set the attribs back
  3273. SetFileAttributes(pszDir, dwAttributes);
  3274. }
  3275. SetLastError(RtlNtStatusToDosError(Status));
  3276. return FALSE;
  3277. }
  3278. }
  3279. else
  3280. {
  3281. RtlReleaseRelativeName(&RelativeName);
  3282. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3283. if (fChangedAttribs)
  3284. {
  3285. // set the attribs back
  3286. SetFileAttributes(pszDir, dwAttributes);
  3287. }
  3288. SetLastError(RtlNtStatusToDosError(Status));
  3289. return FALSE;
  3290. }
  3291. }
  3292. else
  3293. {
  3294. //
  3295. // If we found a reparse point that is not a name grafting operation,
  3296. // either a symbolic link or a mount point, we re-open without
  3297. // inhibiting the reparse behavior.
  3298. //
  3299. Status = NtQueryInformationFile(Handle,
  3300. &IoStatusBlock,
  3301. (void *) &FileTagInformation,
  3302. sizeof(FileTagInformation),
  3303. FileAttributeTagInformation);
  3304. if (!NT_SUCCESS(Status))
  3305. {
  3306. //
  3307. // Not all File Systems implement all information classes.
  3308. // The value STATUS_INVALID_PARAMETER is returned when a non-supported
  3309. // information class is requested to a back-level File System. As all the
  3310. // parameters to NtQueryInformationFile are correct, we can infer that
  3311. // we found a back-level system.
  3312. //
  3313. // If FileAttributeTagInformation is not implemented, we assume that
  3314. // the file at hand is not a reparse point.
  3315. //
  3316. if ((Status != STATUS_NOT_IMPLEMENTED) && (Status != STATUS_INVALID_PARAMETER))
  3317. {
  3318. RtlReleaseRelativeName(&RelativeName);
  3319. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3320. NtClose(Handle);
  3321. if (fChangedAttribs)
  3322. {
  3323. // set the attribs back
  3324. SetFileAttributes(pszDir, dwAttributes);
  3325. }
  3326. SetLastError(RtlNtStatusToDosError(Status));
  3327. return FALSE;
  3328. }
  3329. }
  3330. if (NT_SUCCESS(Status) && (FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
  3331. {
  3332. if (FileTagInformation.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
  3333. {
  3334. //
  3335. // We want to make sure that we return FALSE for mounted volumes. This will cause BBDeleteFile
  3336. // to return BBDELETE_CANNOT_DELETE so that we will actuall delete the mountpoint and not try to
  3337. // move the mount point to the recycle bin or walk into it.
  3338. //
  3339. RtlReleaseRelativeName(&RelativeName);
  3340. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3341. NtClose(Handle);
  3342. if (fChangedAttribs)
  3343. {
  3344. // set the attribs back
  3345. SetFileAttributes(pszDir, dwAttributes);
  3346. }
  3347. // hmmm... lets pull ERROR_NOT_A_REPARSE_POINT out of our butt and return that error code!
  3348. SetLastError(ERROR_NOT_A_REPARSE_POINT);
  3349. return FALSE;
  3350. }
  3351. }
  3352. if (NT_SUCCESS(Status) && (FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
  3353. {
  3354. //
  3355. // Re-open without inhibiting the reparse behavior and not needing to
  3356. // read the attributes.
  3357. //
  3358. NtClose(Handle);
  3359. Status = NtOpenFile(&Handle,
  3360. DELETE | SYNCHRONIZE | READ_CONTROL,
  3361. &Obja,
  3362. &IoStatusBlock,
  3363. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  3364. FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT);
  3365. if (!NT_SUCCESS(Status))
  3366. {
  3367. //
  3368. // When the FS Filter is absent, delete it any way.
  3369. //
  3370. if (Status == STATUS_IO_REPARSE_TAG_NOT_HANDLED)
  3371. {
  3372. //
  3373. // We re-open (possible 3rd open) for delete access inhibiting the reparse behavior.
  3374. //
  3375. Status = NtOpenFile(&Handle,
  3376. DELETE | SYNCHRONIZE | READ_CONTROL,
  3377. &Obja,
  3378. &IoStatusBlock,
  3379. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  3380. FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
  3381. }
  3382. if (!NT_SUCCESS(Status))
  3383. {
  3384. RtlReleaseRelativeName(&RelativeName);
  3385. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3386. if (fChangedAttribs)
  3387. {
  3388. // set the attribs back
  3389. SetFileAttributes(pszDir, dwAttributes);
  3390. }
  3391. SetLastError(RtlNtStatusToDosError(Status));
  3392. return FALSE;
  3393. }
  3394. }
  3395. }
  3396. }
  3397. RtlReleaseRelativeName(&RelativeName);
  3398. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3399. //
  3400. // Attempt to set the delete bit
  3401. //
  3402. #undef DeleteFile
  3403. Disposition.DeleteFile = TRUE;
  3404. Status = NtSetInformationFile(Handle,
  3405. &IoStatusBlock,
  3406. &Disposition,
  3407. sizeof(Disposition),
  3408. FileDispositionInformation);
  3409. if (NT_SUCCESS(Status))
  3410. {
  3411. //
  3412. // yep, we were able to set the bit, now unset it so its not delted!
  3413. //
  3414. Disposition.DeleteFile = FALSE;
  3415. Status = NtSetInformationFile(Handle,
  3416. &IoStatusBlock,
  3417. &Disposition,
  3418. sizeof(Disposition),
  3419. FileDispositionInformation);
  3420. NtClose(Handle);
  3421. if (fChangedAttribs)
  3422. {
  3423. // set the attribs back
  3424. SetFileAttributes(pszDir, dwAttributes);
  3425. }
  3426. return TRUE;
  3427. }
  3428. else
  3429. {
  3430. //
  3431. // nope couldnt set the del bit. can't be deleted
  3432. //
  3433. TraceMsg(TF_BITBUCKET, "IsDirectoryDeletable: NtSetInformationFile failed, status=0x%08x", Status);
  3434. NtClose(Handle);
  3435. if (fChangedAttribs)
  3436. {
  3437. // set the attribs back
  3438. SetFileAttributes(pszDir, dwAttributes);
  3439. }
  3440. SetLastError(RtlNtStatusToDosError(Status));
  3441. return FALSE;
  3442. }
  3443. return TRUE;
  3444. }
  3445. // This function checks to see if a local NT file is delete-able
  3446. //
  3447. // returns:
  3448. // TRUE yes, the file can be nuked
  3449. // FALSE for UNC files or files on network drives
  3450. // FALSE if the file is in use
  3451. //
  3452. // NOTE: this code is largely stolen from the DeleteFileW API (windows\base\client\filemisc.c). if
  3453. // you think that there is a bug in it, then diff it against the DeleteFileW and see it something
  3454. // there changed.
  3455. //
  3456. // also sets the last error to explain why
  3457. //
  3458. BOOL IsFileDeletable(LPCTSTR pszFile)
  3459. {
  3460. NTSTATUS Status;
  3461. OBJECT_ATTRIBUTES Obja;
  3462. HANDLE Handle;
  3463. UNICODE_STRING FileName;
  3464. IO_STATUS_BLOCK IoStatusBlock;
  3465. FILE_DISPOSITION_INFORMATION Disposition;
  3466. FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
  3467. BOOLEAN TranslationStatus;
  3468. RTL_RELATIVE_NAME_U RelativeName;
  3469. void *FreeBuffer;
  3470. BOOLEAN fIsSymbolicLink = FALSE;
  3471. DWORD dwAttributes;
  3472. BOOL fChangedAttribs = FALSE;
  3473. // return false for any network drives
  3474. if (IsNetDrive(PathGetDriveNumber(pszFile)))
  3475. {
  3476. return FALSE;
  3477. }
  3478. // check to see if the file is readonly or system
  3479. dwAttributes = GetFileAttributes(pszFile);
  3480. if (dwAttributes != -1)
  3481. {
  3482. if (dwAttributes & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
  3483. {
  3484. fChangedAttribs = TRUE;
  3485. if (!SetFileAttributes(pszFile, dwAttributes & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
  3486. {
  3487. return FALSE;
  3488. }
  3489. }
  3490. }
  3491. TranslationStatus = RtlDosPathNameToRelativeNtPathName_U(pszFile,
  3492. &FileName,
  3493. NULL,
  3494. &RelativeName);
  3495. if (!TranslationStatus)
  3496. {
  3497. if (fChangedAttribs)
  3498. {
  3499. // set the attribs back
  3500. SetFileAttributes(pszFile, dwAttributes);
  3501. }
  3502. SetLastError(ERROR_PATH_NOT_FOUND);
  3503. return FALSE;
  3504. }
  3505. FreeBuffer = FileName.Buffer;
  3506. if (RelativeName.RelativeName.Length)
  3507. {
  3508. FileName = RelativeName.RelativeName;
  3509. }
  3510. else
  3511. {
  3512. RelativeName.ContainingDirectory = NULL;
  3513. }
  3514. InitializeObjectAttributes(&Obja,
  3515. &FileName,
  3516. OBJ_CASE_INSENSITIVE,
  3517. RelativeName.ContainingDirectory,
  3518. NULL);
  3519. // Open the file for delete access
  3520. Status = NtOpenFile(&Handle,
  3521. (ACCESS_MASK)DELETE | FILE_READ_ATTRIBUTES | READ_CONTROL,
  3522. &Obja,
  3523. &IoStatusBlock,
  3524. FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
  3525. FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
  3526. if (!NT_SUCCESS(Status))
  3527. {
  3528. //
  3529. // Back level file systems may not support reparse points and thus not
  3530. // support symbolic links.
  3531. // We infer this is the case when the Status is STATUS_INVALID_PARAMETER.
  3532. //
  3533. if (Status == STATUS_INVALID_PARAMETER)
  3534. {
  3535. //
  3536. // Open without inhibiting the reparse behavior and not needing to
  3537. // read the attributes.
  3538. //
  3539. Status = NtOpenFile(&Handle,
  3540. (ACCESS_MASK)DELETE | READ_CONTROL,
  3541. &Obja,
  3542. &IoStatusBlock,
  3543. FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
  3544. FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT);
  3545. if (!NT_SUCCESS(Status))
  3546. {
  3547. RtlReleaseRelativeName(&RelativeName);
  3548. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3549. if (fChangedAttribs)
  3550. {
  3551. // set the attribs back
  3552. SetFileAttributes(pszFile, dwAttributes);
  3553. }
  3554. SetLastError(RtlNtStatusToDosError(Status));
  3555. return FALSE;
  3556. }
  3557. }
  3558. else
  3559. {
  3560. RtlReleaseRelativeName(&RelativeName);
  3561. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3562. if (fChangedAttribs)
  3563. {
  3564. // set the attribs back
  3565. SetFileAttributes(pszFile, dwAttributes);
  3566. }
  3567. SetLastError(RtlNtStatusToDosError(Status));
  3568. return FALSE;
  3569. }
  3570. }
  3571. else
  3572. {
  3573. //
  3574. // If we found a reparse point that is not a symbolic link, we re-open
  3575. // without inhibiting the reparse behavior.
  3576. //
  3577. Status = NtQueryInformationFile(Handle,
  3578. &IoStatusBlock,
  3579. (void *) &FileTagInformation,
  3580. sizeof(FileTagInformation),
  3581. FileAttributeTagInformation);
  3582. if (!NT_SUCCESS(Status))
  3583. {
  3584. //
  3585. // Not all File Systems implement all information classes.
  3586. // The value STATUS_INVALID_PARAMETER is returned when a non-supported
  3587. // information class is requested to a back-level File System. As all the
  3588. // parameters to NtQueryInformationFile are correct, we can infer that
  3589. // we found a back-level system.
  3590. //
  3591. // If FileAttributeTagInformation is not implemented, we assume that
  3592. // the file at hand is not a reparse point.
  3593. //
  3594. if ((Status != STATUS_NOT_IMPLEMENTED) && (Status != STATUS_INVALID_PARAMETER))
  3595. {
  3596. RtlReleaseRelativeName(&RelativeName);
  3597. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3598. NtClose(Handle);
  3599. if (fChangedAttribs)
  3600. {
  3601. // set the attribs back
  3602. SetFileAttributes(pszFile, dwAttributes);
  3603. }
  3604. SetLastError(RtlNtStatusToDosError(Status));
  3605. return FALSE;
  3606. }
  3607. }
  3608. if (NT_SUCCESS(Status) && (FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
  3609. {
  3610. if (FileTagInformation.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
  3611. {
  3612. fIsSymbolicLink = TRUE;
  3613. }
  3614. }
  3615. if (NT_SUCCESS(Status) &&
  3616. (FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
  3617. !fIsSymbolicLink)
  3618. {
  3619. //
  3620. // Re-open without inhibiting the reparse behavior and not needing to
  3621. // read the attributes.
  3622. //
  3623. NtClose(Handle);
  3624. Status = NtOpenFile(&Handle,
  3625. (ACCESS_MASK)DELETE | READ_CONTROL,
  3626. &Obja,
  3627. &IoStatusBlock,
  3628. FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
  3629. FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT);
  3630. if (!NT_SUCCESS(Status))
  3631. {
  3632. //
  3633. // When the FS Filter is absent, delete it any way.
  3634. //
  3635. if (Status == STATUS_IO_REPARSE_TAG_NOT_HANDLED)
  3636. {
  3637. //
  3638. // We re-open (possible 3rd open) for delete access inhibiting the reparse behavior.
  3639. //
  3640. Status = NtOpenFile(&Handle,
  3641. (ACCESS_MASK)DELETE | READ_CONTROL,
  3642. &Obja,
  3643. &IoStatusBlock,
  3644. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  3645. FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
  3646. }
  3647. if (!NT_SUCCESS(Status))
  3648. {
  3649. RtlReleaseRelativeName(&RelativeName);
  3650. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3651. if (fChangedAttribs)
  3652. {
  3653. // set the attribs back
  3654. SetFileAttributes(pszFile, dwAttributes);
  3655. }
  3656. SetLastError(RtlNtStatusToDosError(Status));
  3657. return FALSE;
  3658. }
  3659. }
  3660. }
  3661. }
  3662. RtlReleaseRelativeName(&RelativeName);
  3663. RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
  3664. //
  3665. // Attempt to set the delete bit
  3666. //
  3667. #undef DeleteFile
  3668. Disposition.DeleteFile = TRUE;
  3669. Status = NtSetInformationFile(Handle,
  3670. &IoStatusBlock,
  3671. &Disposition,
  3672. sizeof(Disposition),
  3673. FileDispositionInformation);
  3674. if (NT_SUCCESS(Status))
  3675. {
  3676. //
  3677. // yep, we were able to set the bit, now unset it so its not delted!
  3678. //
  3679. Disposition.DeleteFile = FALSE;
  3680. Status = NtSetInformationFile(Handle,
  3681. &IoStatusBlock,
  3682. &Disposition,
  3683. sizeof(Disposition),
  3684. FileDispositionInformation);
  3685. NtClose(Handle);
  3686. if (fChangedAttribs)
  3687. {
  3688. // set the attribs back
  3689. SetFileAttributes(pszFile, dwAttributes);
  3690. }
  3691. return TRUE;
  3692. }
  3693. else
  3694. {
  3695. //
  3696. // nope couldnt set the del bit. can't be deleted
  3697. //
  3698. TraceMsg(TF_BITBUCKET, "IsFileDeletable: NtSetInformationFile failed, status=0x%08x", Status);
  3699. NtClose(Handle);
  3700. if (fChangedAttribs)
  3701. {
  3702. // set the attribs back
  3703. SetFileAttributes(pszFile, dwAttributes);
  3704. }
  3705. SetLastError(RtlNtStatusToDosError(Status));
  3706. return FALSE;
  3707. }
  3708. return TRUE;
  3709. }
  3710. BOOL BBCheckDeleteFileSize(int idDrive, ULARGE_INTEGER ulSize)
  3711. {
  3712. return (!ulSize.HighPart && g_pBitBucket[idDrive]->cbMaxSize > ulSize.LowPart);
  3713. }
  3714. int BBRecyclePathLength(int idDrive)
  3715. {
  3716. return g_pBitBucket[idDrive]->cchBBDir;
  3717. }