Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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