windows-nt/Source/XPSP1/NT/shell/shell32/bitbuck.c
2020-09-26 16:20:57 +08:00

4268 lines
141 KiB
C

#include "shellprv.h"
#pragma hdrstop
#include <regstr.h> // REGSTR_PATH_POLICIES
#include "bitbuck.h"
#include "fstreex.h"
#include "copy.h"
#include "filetbl.h"
#include "propsht.h"
#include "datautil.h"
#include "cscuiext.h"
// mtpt.cpp
STDAPI_(BOOL) CMtPt_IsSecure(int iDrive);
// copy.c
void FOUndo_AddInfo(LPUNDOATOM lpua, LPTSTR pszSrc, LPTSTR pszDest, DWORD dwAttributes);
void FOUndo_FileReallyDeleted(LPTSTR pszFile);
void CALLBACK FOUndo_Release(LPUNDOATOM lpua);
void FOUndo_FileRestored(LPCTSTR pszFile);
// drivesx.c
DWORD PathGetClusterSize(LPCTSTR pszPath);
// bitbcksf.c
int DataObjToFileOpString(IDataObject * pdtobj, LPTSTR * ppszSrc, LPTSTR * ppszDest);
//
// per-process global bitbucket data
//
BOOL g_fBBInited = FALSE; // have we initialized our global data yet?
BOOL g_bIsProcessExplorer = FALSE; // are we the main explorer process? (if so, we persist the state info in the registry)
BBSYNCOBJECT *g_pBitBucket[MAX_BITBUCKETS] = {0}; // our array of bbso's that protect each bucket
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
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
HANDLE g_hgcNumDeleters= INVALID_HANDLE_VALUE; // a global counter that indicates the total # of people who are currently doing recycle bin file operations
HKEY g_hkBitBucket = NULL; // reg key that points to HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket
HKEY g_hkBitBucketPerUser = NULL; // reg key that points to HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket
//
// prototypes
//
void PersistBBDriveInfo(int idDrive);
BOOL IsFileDeletable(LPCTSTR pszFile);
BOOL CreateRecyclerDirectory(int idDrive);
void PurgeOneBitBucket(HWND hwnd, int idDrive, DWORD dwFlags);
int CountDeletedFilesOnDrive(int idDrive, LPDWORD pdwSize, int iMaxFiles);
BOOL GetBBDriveSettings(int idDrive, ULONGLONG *pcbDiskSpace);
void DeleteOldBBRegInfo(int idDrive);
BOOL IsBitBucketInited(int idDrive);
void FreeBBInfo(BBSYNCOBJECT *pbbso);
SECURITY_DESCRIPTOR* CreateRecycleBinSecurityDescriptor();
#define MAX_DELETE_ATTEMPTS 5
#define SLEEP_DELETE_ATTEMPT 1000
int DriveIDFromBBPath(LPCTSTR pszPath)
{
TCHAR szNetHomeDir[MAX_PATH];
LPCTSTR pszTempPath = pszPath;
// NOTE: If we want to make recycle bin support recycling paths under mounted volumes
// we need to modify this to sniff the path for mounted volume junction points
int idDrive = PathGetDriveNumber(pszTempPath);
if ((idDrive == -1) && GetNetHomeDir(szNetHomeDir))
{
int iLen = lstrlen(szNetHomeDir);
// NOTE: we don't want to let you recycle the nethomedir itself, so
// insure that pszPath is larger than the nethomedir path
// (neither is trailed with a backslash)
if ((iLen < lstrlen(pszTempPath)) &&
(PathCommonPrefix(szNetHomeDir, pszTempPath, NULL) == iLen))
{
// this is a subdir of the nethomedir, so we recycle it to the net home server
// which is drive 26
return SERVERDRIVE;
}
}
return idDrive;
}
void DriveIDToBBRoot(int idDrive, LPTSTR szPath)
{
ASSERT(idDrive >= 0);
if (SERVERDRIVE == idDrive)
{
// nethomedir case
if (!GetNetHomeDir(szPath))
{
ASSERT(szPath[0] == 0);
TraceMsg(TF_BITBUCKET, "BitBucket: Machine does NOT have a NETHOMEDIR");
}
else
{
// use the nethomedir
ASSERT(szPath[0] != 0);
}
}
else
{
// build up the "C:\" string
PathBuildRoot(szPath, idDrive);
}
}
void DriveIDToBBVolumeRoot(int idDrive, LPTSTR szPath)
{
DriveIDToBBRoot(idDrive, szPath);
PathStripToRoot(szPath);
PathAddBackslash(szPath);
}
void DriveIDToBBPath(int idDrive, LPTSTR pszPath)
{
DriveIDToBBRoot(idDrive, pszPath);
// NOTE: always append the SID for the SERVERDRIVE case
if ((SERVERDRIVE == idDrive) || (CMtPt_IsSecure(idDrive)))
{
// NTRAID 196426-03/16/2001-isaacs
// GetUserSid can fail and retun NULL. We should fix the
// 21 callers to DriveIDToBBPath in Blackcomb. I have
// removed the assert and we will fall into the
// "non-secured" recycle bin processing.
LPTSTR pszInmate = GetUserSid(NULL);
if (pszInmate)
{
PathAppend(pszPath, TEXT("RECYCLER"));
PathAppend(pszPath, pszInmate);
LocalFree((HLOCAL)pszInmate);
return;
}
}
PathAppend(pszPath, TEXT("Recycled"));
}
TCHAR DriveChar(int idDrive)
{
TCHAR chDrive = (SERVERDRIVE == idDrive) ? TEXT('@') : TEXT('a') + idDrive;
ASSERT(idDrive >= 0 && idDrive < MAX_BITBUCKETS);
return chDrive;
}
//
// converts "c:\recycled\whatver" to "c"
// \\nethomedir\share to "@"
//
void DriveIDToBBRegKey(int idDrive, LPTSTR pszValue)
{
pszValue[0] = DriveChar(idDrive);
pszValue[1] = 0;
}
// Finds out if the given UNC path points to a real netware server,
// since netware in the recycle bin don't play well together.
//
// NOTE: We cache the last passed value because the MyDocs almost *never* changes so
// we don't have to hit the net if the path is the same as last time.
BOOL CheckForBBOnNovellServer(LPCTSTR pszUNCPath)
{
static TCHAR s_szLastServerQueried[MAX_PATH] = {0};
static BOOL s_bLastRet;
BOOL bRet = FALSE;
if (pszUNCPath && pszUNCPath[0])
{
BOOL bIsCached;
ENTERCRITICAL;
bIsCached = (lstrcmpi(pszUNCPath, s_szLastServerQueried) == 0);
if (bIsCached)
{
// use the cached retval
bRet = s_bLastRet;
}
LEAVECRITICAL;
if (!bIsCached)
{
TCHAR szNetwareProvider[MAX_PATH];
DWORD cchNetwareProvider = ARRAYSIZE(szNetwareProvider);
ASSERT(PathIsUNC(pszUNCPath));
// is the netware provider installed?
if (WNetGetProviderName(WNNC_NET_NETWARE, szNetwareProvider, &cchNetwareProvider) == NO_ERROR)
{
NETRESOURCE nr = {0};
TCHAR szServerName[MAX_PATH];
// reduce the UNC path to \\server\share
lstrcpyn(szServerName, pszUNCPath, ARRAYSIZE(szServerName));
PathStripToRoot(szServerName);
nr.dwType = RESOURCETYPE_DISK;
nr.lpLocalName = NULL; // don't map a drive
nr.lpRemoteName = szServerName;
nr.lpProvider = szNetwareProvider; // use netware provider only
if (WNetAddConnection3(NULL, &nr, NULL, NULL, 0) == NO_ERROR)
{
bRet = TRUE;
// delete the connection (will fail if still in use)
WNetCancelConnection2(szServerName, 0, FALSE);
}
}
ENTERCRITICAL;
// update the last queried path
lstrcpyn(s_szLastServerQueried, pszUNCPath, ARRAYSIZE(s_szLastServerQueried));
// update cacehed retval
s_bLastRet = bRet;
LEAVECRITICAL;
}
}
return bRet;
}
/*
Network home drive code (from win95 days) is being used to support the recycle bin
for users with mydocs redirected to a UNC path
"Drive 26" specifies the network homedir
This can return "" = (no net home dir, unknown setup, etc.)
or a string ( global ) pointing to the homedir (lfn)
*/
BOOL GetNetHomeDir(LPTSTR pszNetHomeDir)
{
static TCHAR s_szCachedMyDocs[MAX_PATH] = {0};
static DWORD s_dwCachedTickCount = 0;
DWORD dwCurrentTickCount = GetTickCount();
DWORD dwTickDelta;
if (dwCurrentTickCount >= s_dwCachedTickCount)
{
dwTickDelta = dwCurrentTickCount - s_dwCachedTickCount;
}
else
{
// protect against 49.7 day rollover by forcing refresh
dwTickDelta = (11 * 1000);
}
// is our cache more than 10 seconds old?
if (dwTickDelta > (10 * 1000))
{
// update our cache time
s_dwCachedTickCount = dwCurrentTickCount;
if (SHGetSpecialFolderPath(NULL, pszNetHomeDir, CSIDL_PERSONAL, FALSE))
{
TCHAR szOldBBDir[MAX_PATH];
if (PathIsUNC(pszNetHomeDir))
{
// Remove the trailing backslash (if present)
// because this string will be passed to PathCommonPrefix()
PathRemoveBackslash(pszNetHomeDir);
// If mydocs is redirected to a UNC path on a Novell server, we need to return FALSE when
// IsFileDeletable is called, or the call to NtSetInformationFile with Disposition.DeleteFile=TRUE
// will delete the file instantly even though there are open handles.
if (CheckForBBOnNovellServer(pszNetHomeDir))
{
pszNetHomeDir[0] = TEXT('\0');
}
}
else
{
pszNetHomeDir[0] = TEXT('\0');
}
// check to see if the mydocs path has changed
if (g_pBitBucket[SERVERDRIVE] &&
(g_pBitBucket[SERVERDRIVE] != (BBSYNCOBJECT *)-1) &&
g_pBitBucket[SERVERDRIVE]->pidl &&
SHGetPathFromIDList(g_pBitBucket[SERVERDRIVE]->pidl, szOldBBDir))
{
// we should always find "\RECYCLER\" because this is an old recycle bin directory.
LPTSTR pszTemp = StrRStrI(szOldBBDir, NULL, TEXT("\\RECYCLER\\"));
ASSERT(pszTemp);
// cut the string off before the "\RECYCLER\<SID>" part so we can compare it to the current mydocs path
*pszTemp = TEXT('\0');
if (lstrcmpi(szOldBBDir, pszNetHomeDir) != 0)
{
if (*pszNetHomeDir)
{
TCHAR szNewBBDir[MAX_PATH];
LPITEMIDLIST pidl;
WIN32_FIND_DATA fd = {0};
// mydocs was redirected to a different UNC path, so update the bbsyncobject for the SERVERDRIVE
// copy the new mydocs location and add the "\RECYCLER\<SID>" part back on
lstrcpyn(szNewBBDir, pszNetHomeDir, ARRAYSIZE(szNewBBDir));
PathAppend(szNewBBDir, pszTemp + 1);
// create a simple pidl since "RECYCLER\<SID>" subdirectory might not exist yet
fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
lstrcpyn(fd.cFileName, szNewBBDir, ARRAYSIZE(fd.cFileName));
if (SUCCEEDED(SHSimpleIDListFromFindData(szNewBBDir, &fd, &pidl)))
{
LPITEMIDLIST pidlOld;
ULARGE_INTEGER ulFreeUser, ulTotal, ulFree;
DWORD dwClusterSize;
BOOL bUpdateSize = FALSE;
if (SHGetDiskFreeSpaceEx(pszNetHomeDir, &ulFreeUser, &ulTotal, &ulFree))
{
dwClusterSize = PathGetClusterSize(pszNetHomeDir);
bUpdateSize = TRUE;
}
ENTERCRITICAL;
// swap in the new pidl
pidlOld = g_pBitBucket[SERVERDRIVE]->pidl;
g_pBitBucket[SERVERDRIVE]->pidl = pidl;
ILFree(pidlOld);
// set the cchBBDir
g_pBitBucket[SERVERDRIVE]->cchBBDir = lstrlen(szNewBBDir);
g_pBitBucket[SERVERDRIVE]->fInited = TRUE;
// update the size fields
if (bUpdateSize)
{
ULARGE_INTEGER ulMaxSize;
g_pBitBucket[SERVERDRIVE]->dwClusterSize = dwClusterSize;
g_pBitBucket[SERVERDRIVE]->qwDiskSize = ulTotal.QuadPart;
// we limit the max size of the recycle bin to ~4 gig
ulMaxSize.QuadPart = min(((ulTotal.QuadPart / 100) * g_pBitBucket[SERVERDRIVE]->iPercent), (DWORD)-1);
ASSERT(ulMaxSize.HighPart == 0);
g_pBitBucket[SERVERDRIVE]->cbMaxSize = ulMaxSize.LowPart;
}
LEAVECRITICAL;
}
}
else
{
// mydocs was redireced back to a local path, so flag this drive as not inited so we wont do any more
// recycle bin operations on it.
ENTERCRITICAL;
g_pBitBucket[SERVERDRIVE]->fInited = FALSE;
LEAVECRITICAL;
}
}
else
{
// the mydocs previously to pointed to \\foo\bar, and the user has set it back to that path again.
// so flag the drive as inited so we can start using it again.
if (g_pBitBucket[SERVERDRIVE]->fInited == FALSE)
{
ENTERCRITICAL;
g_pBitBucket[SERVERDRIVE]->fInited = TRUE;
LEAVECRITICAL;
}
}
}
}
else
{
pszNetHomeDir[0] = TEXT('\0');
}
ENTERCRITICAL;
// update the cached value
lstrcpyn(s_szCachedMyDocs, pszNetHomeDir, ARRAYSIZE(s_szCachedMyDocs));
LEAVECRITICAL;
}
else
{
ENTERCRITICAL;
// cache is still good
lstrcpyn(pszNetHomeDir, s_szCachedMyDocs, MAX_PATH);
LEAVECRITICAL;
}
return (BOOL)pszNetHomeDir[0];
}
STDAPI_(BOOL) IsBitBucketableDrive(int idDrive)
{
BOOL bRet = FALSE;
TCHAR szBBRoot[MAX_PATH];
TCHAR szFileSystem[MAX_PATH];
TCHAR szPath[4];
DWORD dwAllowBitBuck = SHRestricted(REST_ALLOWBITBUCKDRIVES);
if ((idDrive < 0) ||
(idDrive >= MAX_BITBUCKETS) ||
(g_pBitBucket[idDrive] == (BBSYNCOBJECT *)-1))
{
// we dont support recycle bin for the general UNC case or we have
// flagged this drive as not having a recycle bin for one reason or another.
return FALSE;
}
if (IsBitBucketInited(idDrive))
{
// the struct is allready allocated and inited, so this is a bitbucketable drive
return TRUE;
}
if (idDrive == SERVERDRIVE)
{
bRet = GetNetHomeDir(szBBRoot);
}
else if ((GetDriveType(PathBuildRoot(szPath, idDrive)) == DRIVE_FIXED) ||
(dwAllowBitBuck & (1 << idDrive)))
{
bRet = TRUE;
}
if (bRet && (idDrive != SERVERDRIVE))
{
// also check to make sure that the drive isint RAW (unformatted)
DriveIDToBBRoot(idDrive, szBBRoot);
if (!GetVolumeInformation(szBBRoot, NULL, 0, NULL, NULL, NULL, szFileSystem, ARRAYSIZE(szFileSystem)) ||
lstrcmpi(szFileSystem, TEXT("RAW")) == 0)
{
bRet = FALSE;
}
else
{
// the drive better be NTFS, FAT or FAT32, else we need to know about it and handle it properly
ASSERT((lstrcmpi(szFileSystem, TEXT("NTFS")) == 0) ||
(lstrcmpi(szFileSystem, TEXT("FAT")) == 0) ||
(lstrcmpi(szFileSystem, TEXT("FAT32")) == 0));
}
}
return bRet;
}
// c:\recycled => c:\recycled\info2 (the new IE4/NT5/Win98 info file)
__inline void GetBBInfo2FileSpec(LPTSTR pszBBPath, LPTSTR pszInfo)
{
PathCombine(pszInfo, pszBBPath, c_szInfo2);
}
// c:\recycled => c:\recycled\info (the old win95/NT4 info file)
__inline void GetBBInfoFileSpec(LPTSTR pszBBPath, LPTSTR pszInfo)
{
PathCombine(pszInfo, pszBBPath, c_szInfo);
}
__inline BOOL IsBitBucketInited(int idDrive)
{
BOOL bRet;
// InitBBDriveInfo could fail and we free and set g_pBitBucket[idDrive] = -1. So there
// is a small window between when we check g_pBitBucket[idDrive] and when we deref
// g_pBitBucket[idDrive]->fInited, to protect against g_pBitBucket[idDrive] being freed
// in this window we use the crit sec.
ENTERCRITICAL;
bRet = (g_pBitBucket[idDrive] &&
(g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1) &&
g_pBitBucket[idDrive]->fInited);
LEAVECRITICAL;
return bRet;
}
BOOL RevOldBBInfoFileHeader(HANDLE hFile, BBDATAHEADER *pbbdh)
{
// Verify that this is a valid info file
if (pbbdh->cbDataEntrySize == sizeof(BBDATAENTRYW))
{
if (pbbdh->idVersion == BITBUCKET_WIN95_VERSION ||
pbbdh->idVersion == BITBUCKET_NT4_VERSION ||
pbbdh->idVersion == BITBUCKET_WIN98IE4INT_VERSION)
{
DWORD dwBytesWritten;
// now seek back to 0 and write in the new stuff
pbbdh->idVersion = BITBUCKET_FINAL_VERSION;
SetFilePointer(hFile, 0, NULL, FILE_BEGIN); // go to the beginning
WriteFile(hFile, (LPBYTE)pbbdh, sizeof(BBDATAHEADER), &dwBytesWritten, NULL);
ASSERT(dwBytesWritten == sizeof(BBDATAHEADER));
}
return (pbbdh->idVersion == BITBUCKET_FINAL_VERSION);
}
return FALSE;
}
//
// We need to update the cCurrent and cFiles in the info file header
// for compat with win98/IE4 machines.
//
BOOL UpdateBBInfoFileHeader(int idDrive)
{
BBDATAHEADER bbdh = {0, 0, 0, sizeof(BBDATAENTRYW), 0}; // defaults
HANDLE hFile;
BOOL bRet = FALSE; // assume failure;
// Pass 1 for the # of retry attempts since we are called during shutdown and if another process
// is using the recycle bin we will hang and get the "End Task" dialog (bad!).
hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 1);
if (hFile != INVALID_HANDLE_VALUE)
{
BBDATAENTRYW bbdew;
DWORD dwBytesRead;
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
bRet = ReadFile(hFile, &bbdh, sizeof(BBDATAHEADER), &dwBytesRead, NULL);
if (bRet && dwBytesRead == sizeof(BBDATAHEADER))
{
DWORD dwSize;
DWORD dwBytesWritten;
bbdh.idVersion = BITBUCKET_FINAL_VERSION;
bbdh.cCurrent = SHGlobalCounterGetValue(g_pBitBucket[idDrive]->hgcNextFileNum);
bbdh.cFiles = CountDeletedFilesOnDrive(idDrive, &dwSize, 0);
bbdh.dwSize = dwSize;
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
WriteFile(hFile, (LPBYTE)&bbdh, sizeof(BBDATAHEADER), &dwBytesWritten, NULL);
ASSERT(dwBytesWritten == sizeof(BBDATAHEADER));
bRet = TRUE;
}
ASSERT((g_pBitBucket[idDrive]->fIsUnicode && (sizeof(BBDATAENTRYW) == bbdh.cbDataEntrySize)) ||
(!g_pBitBucket[idDrive]->fIsUnicode && (sizeof(BBDATAENTRYA) == bbdh.cbDataEntrySize)));
// Since we dont flag entries that were deleted in the info file as deleted
// immeadeately, we need to go through and mark them as such now
while (ReadNextDataEntry(hFile, &bbdew, TRUE, idDrive))
{
// do nothing
}
CloseBBInfoFile(hFile, idDrive);
}
if (!bRet)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: failed to update drive %d for win98/NT4 compat!!", idDrive);
}
return bRet;
}
BOOL ResetInfoFileHeader(HANDLE hFile, BOOL fIsUnicode)
{
DWORD dwBytesWritten;
BBDATAHEADER bbdh = { BITBUCKET_FINAL_VERSION, 0, 0,
fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA), 0};
BOOL fSuccess = FALSE;
ASSERT(INVALID_HANDLE_VALUE != hFile);
if (-1 != SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
{
if (WriteFile(hFile, (LPBYTE)&bbdh, sizeof(BBDATAHEADER), &dwBytesWritten, NULL) &&
dwBytesWritten == sizeof(BBDATAHEADER))
{
if (SetEndOfFile(hFile))
{
fSuccess = TRUE;
}
}
}
return fSuccess;
}
BOOL CreateInfoFile(idDrive)
{
TCHAR szBBPath[MAX_PATH];
TCHAR szInfoFile[MAX_PATH];
HANDLE hFile;
BOOL fSuccess = FALSE;
DriveIDToBBPath(idDrive, szBBPath);
GetBBInfo2FileSpec(szBBPath, szInfoFile);
hFile = OpenBBInfoFile(idDrive, OPENBBINFO_CREATE, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
fSuccess = ResetInfoFileHeader(hFile, TRUE);
CloseHandle(hFile);
if (fSuccess)
{
// We explicitly call SHChangeNotify so that we can generate a change specifically
// for the info file. The recycle bin shell folder will then ignore any updates to
// the info file.
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, szInfoFile, NULL);
}
}
if (!fSuccess)
{
TraceMsg(TF_WARNING, "Bitbucket: faild to create file info file!!");
}
return fSuccess;
}
// GetNT4BBAcl() - Creates a ACL structure for allowing access for
// only the current user,the administrators group, or the system.
// Returns a pointer to an access control list
// structure in the local heap; it can be
// free'd with LocalFree.
//
// !! HACKHACK !! - This code was basically taken right out of NT4 so that we can
// compare against the old NT4 recycle bin ACL. The new helper function
// GetShellSecurityDescriptor puts the ACE's in a different order
// than this function, and so we memcmp the ACL against botht this
// one and the new win2k one.
PACL GetNT4BBAcl()
{
SID_IDENTIFIER_AUTHORITY authNT = SECURITY_NT_AUTHORITY;
PACL pAcl = NULL;
PTOKEN_USER pUser = NULL;
PSID psidSystem = NULL;
PSID psidAdmin = NULL;
DWORD cbAcl;
DWORD aceIndex;
ACE_HEADER * lpAceHeader;
UINT nCnt = 2; // inheritable; so two ACE's for each user
BOOL bSuccess = FALSE;
//
// Get the USER token so we can grab its SID for the DACL.
//
pUser = GetUserToken(NULL);
if (!pUser)
{
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to get user. Error = %d", GetLastError());
goto Exit;
}
//
// Get the system sid
//
if (!AllocateAndInitializeSid(&authNT, 1, SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0, &psidSystem)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to initialize system sid. Error = %d", GetLastError());
goto Exit;
}
//
// Get the Admin sid
//
if (!AllocateAndInitializeSid(&authNT, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0,
0, 0, 0, 0, &psidAdmin)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to initialize admin sid. Error = %d", GetLastError());
goto Exit;
}
//
// Allocate space for the DACL
//
cbAcl = sizeof(ACL) +
(nCnt * GetLengthSid(pUser->User.Sid)) +
(nCnt * GetLengthSid(psidSystem)) +
(nCnt * GetLengthSid(psidAdmin)) +
(nCnt * 3 * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)));
pAcl = (PACL)LocalAlloc(LPTR, cbAcl);
if (!pAcl) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to allocate acl. Error = %d", GetLastError());
goto Exit;
}
if (!InitializeAcl(pAcl, cbAcl, ACL_REVISION)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to initialize acl. Error = %d", GetLastError());
goto Exit;
}
//
// Add Aces for User, System, and Admin. Non-inheritable ACEs first
//
aceIndex = 0;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, pUser->User.Sid)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
goto Exit;
}
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, psidSystem)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
goto Exit;
}
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, FILE_ALL_ACCESS, psidAdmin)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
goto Exit;
}
//
// Now the inheritable ACEs
//
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, pUser->User.Sid)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to get ace (%d). Error = %d", aceIndex, GetLastError());
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidSystem)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to get ace (%d). Error = %d", aceIndex, GetLastError());
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
aceIndex++;
if (!AddAccessAllowedAce(pAcl, ACL_REVISION, GENERIC_ALL, psidAdmin)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to add ace (%d). Error = %d", aceIndex, GetLastError());
goto Exit;
}
if (!GetAce(pAcl, aceIndex, &lpAceHeader)) {
TraceMsg(TF_BITBUCKET, "GetNT4BBAcl: Failed to get ace (%d). Error = %d", aceIndex, GetLastError());
goto Exit;
}
lpAceHeader->AceFlags |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
bSuccess = TRUE;
Exit:
if (pUser)
LocalFree(pUser);
if (psidSystem)
FreeSid(psidSystem);
if (psidAdmin)
FreeSid(psidAdmin);
if (!bSuccess && pAcl)
{
LocalFree(pAcl);
pAcl = NULL;
}
return pAcl;
}
//
// this checks to make sure that the users recycle bin directory is properly acl'ed
//
BOOL CheckRecycleBinAcls(idDrive)
{
BOOL bIsSecure = TRUE;
TCHAR szBBPath[MAX_PATH];
PSECURITY_DESCRIPTOR psdCurrent = NULL;
PSID psidOwner;
PACL pdaclCurrent;
DWORD dwLengthNeeded = 0;
if ((idDrive == SERVERDRIVE) || !CMtPt_IsSecure(idDrive))
{
// either redirected mydocs case (assume mydocs is already secured) or it
// is not an NTFS drive, so no ACL's to check
return TRUE;
}
DriveIDToBBPath(idDrive, szBBPath);
if (!GetFileSecurity(szBBPath,
DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
NULL,
0,
&dwLengthNeeded) &&
(GetLastError() == ERROR_INSUFFICIENT_BUFFER))
{
psdCurrent = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwLengthNeeded);
}
if (psdCurrent)
{
BOOL bDefault = FALSE;
BOOL bPresent = FALSE;
if (GetFileSecurity(szBBPath,
DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
psdCurrent,
dwLengthNeeded,
&dwLengthNeeded) &&
GetSecurityDescriptorOwner(psdCurrent, &psidOwner, &bDefault) && psidOwner &&
GetSecurityDescriptorDacl(psdCurrent, &bPresent, &pdaclCurrent, &bDefault) && pdaclCurrent)
{
PTOKEN_USER pUser = GetUserToken(NULL);
if (pUser)
{
if (!EqualSid(psidOwner, pUser->User.Sid))
{
// the user is not the owner of the dir, check to see if the owner is the Administrators group or the System
// (we consider the directory to be secure if the owner is either of these two)
SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
PSID psidAdministrators = NULL;
PSID psidSystem = NULL;
if (AllocateAndInitializeSid(&sia, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdministrators) &&
AllocateAndInitializeSid(&sia, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &psidSystem))
{
if (!EqualSid(psidOwner, psidAdministrators) && !EqualSid(psidOwner, psidSystem))
{
// directory is not owned by the user, or the Administrators group or the system, we thus consider it unsecure.
TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: dir %s has possibly unsecure owner!", szBBPath);
bIsSecure = FALSE;
}
if (psidAdministrators)
FreeSid(psidAdministrators);
if (psidSystem)
FreeSid(psidSystem);
}
else
{
TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: AllocateAndInitializeSid failed, assuming %s is unsecure", szBBPath);
bIsSecure = FALSE;
}
}
if (bIsSecure)
{
// directory owner checked out ok, lets see if the acl is what we expect...
SECURITY_DESCRIPTOR* psdRecycle = CreateRecycleBinSecurityDescriptor();
if (psdRecycle)
{
// to compare acls, we do a size check and then a memcmp (aclui code does the same)
if ((psdRecycle->Dacl->AclSize != pdaclCurrent->AclSize) ||
(memcmp(psdRecycle->Dacl, pdaclCurrent, pdaclCurrent->AclSize) != 0))
{
// acl sizes were different or they didn't memcmp, so check against the old NT4 style acl
// (in NT4 we added the ACE's in a different order which causes the memcmp to fail, even
// though the ACL is equivilant)
PACL pAclNT4 = GetNT4BBAcl();
if (pAclNT4)
{
// do the same size / memcmp check
if ((pAclNT4->AclSize != pdaclCurrent->AclSize) ||
(memcmp(pAclNT4, pdaclCurrent, pdaclCurrent->AclSize) != 0))
{
// acl sizes were different or they didn't memcmp, so assume the dir is unsecure
bIsSecure = FALSE;
}
LocalFree(pAclNT4);
}
else
{
TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: GetNT4BBSecurityAttributes failed, assuming %s is unsecure", szBBPath);
bIsSecure = FALSE;
}
}
LocalFree(psdRecycle);
}
else
{
TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: CreateRecycleBinSecurityDescriptor failed, assuming %s is unsecure", szBBPath);
bIsSecure = FALSE;
}
}
LocalFree(pUser);
}
else
{
// couldnt' get the users sid, so assume the dir is unsecure
TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: failed to get the users sid, assuming %s is unsecure", szBBPath);
bIsSecure = FALSE;
}
}
else
{
// GetFileSecurity failed, assume the dir is unsecure
TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: GetFileSecurity failed, assuming %s is unsecure", szBBPath);
bIsSecure = FALSE;
}
LocalFree(psdCurrent);
}
else
{
// GetFileSecurity failed, assume the dir is unsecure
TraceMsg(TF_BITBUCKET, "CheckRecycleBinAcls: GetFileSecurity failed or memory allocation failed, assume %s is unsecure", szBBPath);
bIsSecure = FALSE;
}
if (!bIsSecure)
{
TCHAR szDriveName[MAX_PATH];
DriveIDToBBRoot(idDrive, szDriveName);
if (ShellMessageBox(HINST_THISDLL,
NULL,
MAKEINTRESOURCE(IDS_RECYCLEBININVALIDFORMAT),
MAKEINTRESOURCE(IDS_WASTEBASKET),
MB_YESNO | MB_ICONEXCLAMATION | MB_SETFOREGROUND,
szDriveName) == IDYES)
{
TCHAR szBBPathToNuke[MAX_PATH+1];
SHFILEOPSTRUCT fo = {NULL,
FO_DELETE,
szBBPathToNuke,
NULL,
FOF_NOCONFIRMATION | FOF_SILENT,
FALSE,
NULL,
NULL};
lstrcpyn(szBBPathToNuke, szBBPath, MAX_PATH);
szBBPathToNuke[lstrlen(szBBPathToNuke) + 1] = 0; // double null terminate
// try to nuke the old recycle bin for this drive
if (SHFileOperation(&fo) == ERROR_SUCCESS)
{
// now create the new secure one
bIsSecure = CreateRecyclerDirectory(idDrive);
}
}
}
return bIsSecure;
}
//
// this verifies the info file header infomation
//
BOOL VerifyBBInfoFileHeader(int idDrive)
{
BBDATAHEADER bbdh = {0, 0, 0, sizeof(BBDATAENTRYW), 0}; // defaults
HANDLE hFile;
TCHAR szBBPath[MAX_PATH];
TCHAR szInfo[MAX_PATH];
BOOL fSuccess = FALSE;
// check for the the old win95 INFO file
DriveIDToBBPath(idDrive, szBBPath);
GetBBInfoFileSpec(szBBPath, szInfo);
hFile = CreateFile(szInfo, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_RANDOM_ACCESS, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD dwBytesRead;
if (ReadFile(hFile, &bbdh, sizeof(BBDATAHEADER), &dwBytesRead, NULL) &&
(dwBytesRead == sizeof(BBDATAHEADER)))
{
TraceMsg(TF_BITBUCKET, "Bitbucket: migrating info in old database file %s", szInfo);
fSuccess = RevOldBBInfoFileHeader(hFile, &bbdh);
}
CloseHandle(hFile);
if (fSuccess)
{
// rename from INFO -> INFO2
TCHAR szInfoNew[MAX_PATH];
GetBBInfo2FileSpec(szBBPath, szInfoNew);
TraceMsg(TF_BITBUCKET, "Bitbucket: renaming %s to %s !!", szInfo, szInfoNew);
SHMoveFile(szInfo, szInfoNew, SHCNE_RENAMEITEM);
}
else
{
goto bad_info_file;
}
}
// Failed to open or rev the old info file. Next, we check for the existance of the new info2 file
// to see if the drive has a bitbucket format that is greater than what we can handle
if (!fSuccess)
{
hFile = OpenBBInfoFile(idDrive, OPENBBINFO_READ, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
BOOL bRet;
DWORD dwBytesRead;
SetFilePointer(hFile, 0, NULL, FILE_BEGIN); // go to the beginning
bRet = ReadFile(hFile, &bbdh, sizeof(BBDATAHEADER), &dwBytesRead, NULL);
CloseBBInfoFile(hFile, idDrive);
if ((bRet == 0) ||
(dwBytesRead != sizeof(BBDATAHEADER)) ||
(bbdh.idVersion > BITBUCKET_FINAL_VERSION) ||
(bbdh.cbDataEntrySize != sizeof(BBDATAENTRYA) && bbdh.cbDataEntrySize != sizeof(BBDATAENTRYW)))
{
TCHAR szDriveName[MAX_PATH];
// either we had a corrupt win95 info file, or an info2 file whose version is greater than ours
// so we just empy the recycle bin.
bad_info_file:
// since we failed to read the existing header, assume the native format
g_pBitBucket[idDrive]->fIsUnicode = TRUE;
// find out which drive it is that is corrupt
DriveIDToBBRoot(idDrive, szDriveName);
if (ShellMessageBox(HINST_THISDLL,
NULL,
MAKEINTRESOURCE(IDS_RECYCLEBININVALIDFORMAT),
MAKEINTRESOURCE(IDS_WASTEBASKET),
MB_YESNO | MB_ICONEXCLAMATION | MB_SETFOREGROUND,
szDriveName) == IDYES)
{
// nuke this bucket since it is hosed
PurgeOneBitBucket(NULL, idDrive, SHERB_NOCONFIRMATION);
return TRUE;
}
hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD dwBytesWritten;
bbdh.idVersion = BITBUCKET_FINAL_VERSION;
if (bbdh.cbDataEntrySize != sizeof(BBDATAENTRYW) &&
bbdh.cbDataEntrySize != sizeof(BBDATAENTRYA))
{
// assume the native data entry size
bbdh.cbDataEntrySize = sizeof(BBDATAENTRYW);
}
g_pBitBucket[idDrive]->fIsUnicode = (bbdh.cbDataEntrySize == sizeof(BBDATAENTRYW));
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
WriteFile(hFile, (LPBYTE)&bbdh, sizeof(BBDATAHEADER), &dwBytesWritten, NULL);
ASSERT(dwBytesWritten == sizeof(BBDATAHEADER));
CloseBBInfoFile(hFile, idDrive);
fSuccess = TRUE;
}
else
{
fSuccess = FALSE;
}
}
else if (bbdh.idVersion != BITBUCKET_FINAL_VERSION)
{
// old info2 information
fSuccess = RevOldBBInfoFileHeader(hFile, &bbdh);
}
else
{
// the header info is current
fSuccess = TRUE;
}
}
else
{
// brand spanking new drive, so go create the info file now.
fSuccess = CreateInfoFile(idDrive);
}
}
// get the only relevant thing in the header, whether it is unicode or not
g_pBitBucket[idDrive]->fIsUnicode = (bbdh.cbDataEntrySize == sizeof(BBDATAENTRYW));
return fSuccess;
}
LONG FindInitialNextFileNum(idDrive)
{
int iRet = 0;
TCHAR szBBFileSpec[MAX_PATH];
WIN32_FIND_DATA fd;
HANDLE hFind;
DriveIDToBBPath(idDrive, szBBFileSpec);
PathAppend(szBBFileSpec, TEXT("D*.*"));
hFind = FindFirstFile(szBBFileSpec, &fd);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if (!PathIsDotOrDotDot(fd.cFileName) && lstrcmpi(fd.cFileName, c_szDesktopIni))
{
int iCurrent = BBPathToIndex(fd.cFileName);
if (iCurrent > iRet)
{
iRet = iCurrent;
}
}
} while (FindNextFile(hFind, &fd));
FindClose(hFind);
}
ASSERT(iRet >= 0);
return (LONG)iRet;
}
BOOL InitBBDriveInfo(int idDrive)
{
TCHAR szName[MAX_PATH];
DWORD dwDisp;
LONG lInitialCount = 0;
// build up the string "BitBucket.<drive letter>"
lstrcpy(szName, TEXT("BitBucket."));
DriveIDToBBRegKey(idDrive, &szName[10]);
lstrcpy(&szName[11], TEXT(".DirtyCount"));
g_pBitBucket[idDrive]->hgcDirtyCount = SHGlobalCounterCreateNamed(szName, 0); // BitBucket.<drive letter>.DirtyCount
if (g_pBitBucket[idDrive]->hgcDirtyCount == INVALID_HANDLE_VALUE)
{
ASSERTMSG(FALSE, "BitBucket: failed to create hgcDirtyCount for drive %d !!", idDrive);
return FALSE;
}
// now create the subkey for this drive
DriveIDToBBRegKey(idDrive, szName);
// the per-user key is volatile since we only use this for temporary bookeeping (eg need to purge / compact).
// the exception to this rule is the SERVERDRIVE case, because this is the users "My Documents" so we let it
// and we also need to store the path under that key (it has to roam with the user)
if (RegCreateKeyEx(g_hkBitBucketPerUser, szName, 0, NULL,
(SERVERDRIVE == idDrive) ? REG_OPTION_NON_VOLATILE : REG_OPTION_VOLATILE,
KEY_SET_VALUE | KEY_QUERY_VALUE,
NULL, &g_pBitBucket[idDrive]->hkeyPerUser,
&dwDisp) != ERROR_SUCCESS)
{
ASSERTMSG(FALSE, "BitBucket: Could not create HKCU BitBucket registry key for drive %s", szName);
g_pBitBucket[idDrive]->hkeyPerUser = NULL;
return FALSE;
}
if (RegCreateKeyEx(g_hkBitBucket, szName, 0, NULL, REG_OPTION_NON_VOLATILE, MAXIMUM_ALLOWED,
NULL, &g_pBitBucket[idDrive]->hkey, &dwDisp) != ERROR_SUCCESS)
{
TraceMsg(TF_BITBUCKET, "BitBucket: Could not create HKLM BitBucket registry key for drive %s, falling back to HKLM global key! ", szName);
if (RegOpenKeyEx(g_hkBitBucket, NULL, 0, MAXIMUM_ALLOWED, &g_pBitBucket[idDrive]->hkey) != ERROR_SUCCESS)
{
ASSERTMSG(FALSE, "BitBucket: Could not duplicate HKLM Global Bitbucket key!");
return FALSE;
}
}
// load the rest of the settings (hgcNextFileNum, fIsUnicode, iPercent, cbMaxSize, dwClusterSize, and fNukeOnDelete)
return GetBBDriveSettings(idDrive, NULL);
}
BOOL AllocBBDriveInfo(int idDrive)
{
TCHAR szBBPath[MAX_PATH];
LPITEMIDLIST pidl;
BOOL bRet = FALSE; // assume failure
DriveIDToBBPath(idDrive, szBBPath);
pidl = ILCreateFromPath(szBBPath);
if (!pidl && !PathFileExists(szBBPath))
{
if (CreateRecyclerDirectory(idDrive))
{
pidl = ILCreateFromPath(szBBPath);
}
}
if (pidl)
{
BBSYNCOBJECT *pbbso = LocalAlloc(LPTR, sizeof(*pbbso));
if (pbbso)
{
if (SHInterlockedCompareExchange(&g_pBitBucket[idDrive], pbbso, NULL))
{
DWORD dwInitialTickCount = GetTickCount();
BOOL bKeepWaiting = TRUE;
// Some other thread beat us to creating this bitbucket.
// We can't return until that thread has inited the bitbucket
// since some of the members might not be valid yet.
LocalFree(pbbso);
ILFree(pidl);
do
{
if (g_pBitBucket[idDrive] == (BBSYNCOBJECT *)-1)
{
// this volume is flagged as not being recycleable for some reason...
break;
}
// Spin until the bitbucket struct is inited
Sleep(50);
bKeepWaiting = !IsBitBucketInited(idDrive);
// we should never spin more than ~15 seconds
if (((GetTickCount() - dwInitialTickCount) >= (60 * 1000)) && bKeepWaiting)
{
ASSERTMSG(FALSE, "AllocBBDriveInfo: other thread took longer that 1 minute to init a bitbucket?!?");
break;
}
} while (bKeepWaiting);
return ((g_pBitBucket[idDrive] != NULL) &&
(g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1));
}
ASSERT(g_pBitBucket[idDrive] && (g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1));
g_pBitBucket[idDrive]->pidl = pidl;
g_pBitBucket[idDrive]->cchBBDir = lstrlen(szBBPath);
if (InitBBDriveInfo(idDrive))
{
// Success!!
g_pBitBucket[idDrive]->fInited = TRUE;
bRet = TRUE;
}
else
{
// we failed for some weird reason
TraceMsg(TF_WARNING, "Bitbucket: InitBBDriveInfo() failed on drive %d", idDrive);
ILFree(pidl);
ENTERCRITICAL;
// take the critical section to protect people who call IsBitBucketInited()
FreeBBInfo(g_pBitBucket[idDrive]);
if (idDrive == SERVERDRIVE)
{
// We set it to null in the serverdrive case so we will always retry. This allows
// the user to re-direct and try to recycle on a new location.
g_pBitBucket[idDrive] = NULL;
}
else
{
// set it to -1 here so we dont try any future recycle operations on this volume
g_pBitBucket[idDrive] = (BBSYNCOBJECT *)-1;
}
LEAVECRITICAL;
}
}
else
{
ILFree(pidl);
}
}
return bRet;
}
BOOL InitBBGlobals()
{
if (!g_fBBInited)
{
// Save this now beceause at shutdown the desktop window will already be gone,
// so we need to find out if we are the main explorer process now.
if (!g_bIsProcessExplorer)
{
g_bIsProcessExplorer = IsWindowInProcess(GetShellWindow());
}
// do we have our global hkey that points to HKLM\Software\Microsoft\Windows\CurrentVersion\BitBucket yet?
if (!g_hkBitBucket)
{
g_hkBitBucket = SHGetShellKey(SHELLKEY_HKLM_EXPLORER, TEXT("BitBucket"), TRUE);
if (!g_hkBitBucket)
{
TraceMsg(TF_WARNING, "Bitbucket: Could not create g_hkBitBucket!");
return FALSE;
}
}
// do we have our global hkey that points to HKCU\Software\Microsoft\Windows\CurrentVersion\BitBucket yet?
if (!g_hkBitBucketPerUser)
{
g_hkBitBucketPerUser = SHGetShellKey(SHELLKEY_HKCU_EXPLORER, TEXT("BitBucket"), TRUE);
if (!g_hkBitBucketPerUser)
{
TraceMsg(TF_WARNING, "Bitbucket: Could not create g_hkBitBucketPerUser!");
return FALSE;
}
}
// have we initialized the global settings dirty counter yet
if (g_hgcGlobalDirtyCount == INVALID_HANDLE_VALUE)
{
g_hgcGlobalDirtyCount = SHGlobalCounterCreateNamed(TEXT("BitBucket.GlobalDirtyCount"), 0);
if (g_hgcGlobalDirtyCount == INVALID_HANDLE_VALUE)
{
TraceMsg(TF_WARNING, "Bitbucket: failed to create g_hgcGlobalDirtyCount!");
return FALSE;
}
g_lProcessDirtyCount = SHGlobalCounterGetValue(g_hgcGlobalDirtyCount);
}
// have we initialized the global # of people doing recycle bin file operations?
if (g_hgcNumDeleters == INVALID_HANDLE_VALUE)
{
g_hgcNumDeleters = SHGlobalCounterCreateNamed(TEXT("BitBucket.NumDeleters"), 0);
if (g_hgcGlobalDirtyCount == INVALID_HANDLE_VALUE)
{
TraceMsg(TF_WARNING, "Bitbucket: failed to create g_hgcGlobalDirtyCount!");
return FALSE;
}
}
// we inited everything!!
g_fBBInited = TRUE;
}
return g_fBBInited;
}
void FreeBBInfo(BBSYNCOBJECT *pbbso)
{
if (pbbso->hgcNextFileNum)
CloseHandle(pbbso->hgcNextFileNum);
if (pbbso->hgcDirtyCount)
CloseHandle(pbbso->hgcDirtyCount);
if (pbbso->hkey)
RegCloseKey(pbbso->hkey);
if (pbbso->hkeyPerUser)
RegCloseKey(pbbso->hkeyPerUser);
LocalFree(pbbso);
}
//
// This function is exported from shell32 so that explorer can call us during WM_ENDSESSION
// and we can go save a bunch of state and free all the semaphores.
STDAPI_(void) SaveRecycleBinInfo()
{
if (g_bIsProcessExplorer)
{
LONG lGlobalDirtyCount;
BOOL bGlobalUpdate = FALSE; // did global settings change?
int i;
// We are going to persist the info to the registry, so check to see if we need to
// update our info now
lGlobalDirtyCount = SHGlobalCounterGetValue(g_hgcGlobalDirtyCount);
if (g_lProcessDirtyCount < lGlobalDirtyCount)
{
g_lProcessDirtyCount = lGlobalDirtyCount;
RefreshAllBBDriveSettings();
bGlobalUpdate = TRUE;
}
for (i = 0; i < MAX_BITBUCKETS ; i++)
{
if (IsBitBucketInited(i))
{
LONG lBucketDirtyCount = SHGlobalCounterGetValue(g_pBitBucket[i]->hgcDirtyCount);
// if we didnt do a global update, check this bucket specifically to see if it is dirty
// and we need to update it
if (!bGlobalUpdate && g_pBitBucket[i]->lCurrentDirtyCount < lBucketDirtyCount)
{
g_pBitBucket[i]->lCurrentDirtyCount = lBucketDirtyCount;
RefreshBBDriveSettings(i);
}
// save all of the volume serial # and whether the drive is unicode to the registry
PersistBBDriveInfo(i);
// we also update the header for win98/IE4 compat
UpdateBBInfoFileHeader(i);
}
}
}
}
void BitBucket_Terminate()
{
int i;
// free the global recycle bin structs
for (i = 0; i < MAX_BITBUCKETS ; i++)
{
if ((g_pBitBucket[i]) && (g_pBitBucket[i] != (BBSYNCOBJECT *)-1))
{
ENTERCRITICAL;
FreeBBInfo(g_pBitBucket[i]);
g_pBitBucket[i] = NULL;
LEAVECRITICAL;
}
}
if (g_hgcGlobalDirtyCount != INVALID_HANDLE_VALUE)
CloseHandle(g_hgcGlobalDirtyCount);
if (g_hgcNumDeleters != INVALID_HANDLE_VALUE)
CloseHandle(g_hgcNumDeleters);
if (g_hkBitBucketPerUser != NULL)
RegCloseKey(g_hkBitBucketPerUser);
if (g_hkBitBucket != NULL)
RegCloseKey(g_hkBitBucket);
}
//
// refreshes g_pBitBucket with new global settings
//
BOOL RefreshAllBBDriveSettings()
{
int i;
// since global settings changes affect all the drives, update all the drives
for (i = 0; i < MAX_BITBUCKETS; i++)
{
if ((g_pBitBucket[i]) && (g_pBitBucket[i] != (BBSYNCOBJECT *)-1))
{
RefreshBBDriveSettings(i);
}
}
return TRUE;
}
BOOL ReadBBDriveSetting(HKEY hkey, LPTSTR pszValue, LPBYTE pbData, DWORD cbData)
{
DWORD dwSize;
retry:
dwSize = cbData;
if (RegQueryValueEx(hkey, pszValue, NULL, NULL, pbData, &dwSize) != ERROR_SUCCESS)
{
if (hkey == g_hkBitBucket)
{
ASSERTMSG(FALSE, "Missing global bitbucket data: run regsvr32 on shell32.dll !!");
return FALSE;
}
else
{
// we are missing the per-bitbuckt information, so fall back to the global stuff
hkey = g_hkBitBucket;
goto retry;
}
}
return TRUE;
}
//
// Same as SHGetRestriction, except you can tell the difference between
// "policy not set" and "policy set with value=0"
//
DWORD ReadPolicySetting(LPCWSTR pszBaseKey, LPCWSTR pszGroup, LPCWSTR pszRestriction, LPBYTE pbData, DWORD cbData)
{
// Make sure the string is long enough to hold longest one...
WCHAR szSubKey[MAX_PATH];
DWORD dwSize;
DWORD dwRet;
//
// This restriction hasn't been read yet.
//
if (!pszBaseKey)
{
pszBaseKey = REGSTR_PATH_POLICIES;
}
#ifndef UNIX
PathCombineW(szSubKey, pszBaseKey, pszGroup);
#else
wsprintfW(szSubKey, L"%s\\%s", pszBaseKey, pszGroup);
#endif
// Check local machine first and let it override what the
// HKCU policy has done.
dwSize = cbData;
dwRet = SHGetValueW(HKEY_LOCAL_MACHINE, szSubKey, pszRestriction, NULL, pbData, &dwSize);
if (ERROR_SUCCESS != dwRet)
{
// Check current user if we didn't find anything for the local machine.
dwSize = cbData;
dwRet = SHGetValueW(HKEY_CURRENT_USER, szSubKey, pszRestriction, NULL, pbData, &dwSize);
}
return dwRet;
}
BOOL RefreshBBDriveSettings(int idDrive)
{
HKEY hkey;
ULARGE_INTEGER ulMaxSize;
BOOL fUseGlobalSettings = TRUE;
DWORD dwSize = sizeof(fUseGlobalSettings);
ASSERT(g_pBitBucket[idDrive] && (g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1));
RegQueryValueEx(g_hkBitBucket, TEXT("UseGlobalSettings"), NULL, NULL, (LPBYTE)&fUseGlobalSettings, &dwSize);
if (fUseGlobalSettings)
{
hkey = g_hkBitBucket;
}
else
{
hkey = g_pBitBucket[idDrive]->hkey;
}
// read the iPercent value
if (ERROR_SUCCESS == ReadPolicySetting(NULL, L"Explorer", L"RecycleBinSize", (LPBYTE)&g_pBitBucket[idDrive]->iPercent, sizeof(g_pBitBucket[idDrive]->iPercent)))
{
// Make sure it's not too big or too small
g_pBitBucket[idDrive]->iPercent = max(0, min(100, g_pBitBucket[idDrive]->iPercent));
}
else if (!ReadBBDriveSetting(hkey, TEXT("Percent"), (LPBYTE)&g_pBitBucket[idDrive]->iPercent, sizeof(g_pBitBucket[idDrive]->iPercent)))
{
// default
g_pBitBucket[idDrive]->iPercent = 10;
}
// read the fNukeOnDelete value
if (SHRestricted(REST_BITBUCKNUKEONDELETE))
{
g_pBitBucket[idDrive]->fNukeOnDelete = TRUE;
}
else if (!ReadBBDriveSetting(hkey, TEXT("NukeOnDelete"), (LPBYTE)&g_pBitBucket[idDrive]->fNukeOnDelete, sizeof(g_pBitBucket[idDrive]->fNukeOnDelete)))
{
// default
g_pBitBucket[idDrive]->fNukeOnDelete = FALSE;
}
// re-calculate cbMaxSize based on the new iPercent
ulMaxSize.QuadPart = min((g_pBitBucket[idDrive]->qwDiskSize / 100) * g_pBitBucket[idDrive]->iPercent, (DWORD)-1);
ASSERT(ulMaxSize.HighPart == 0);
g_pBitBucket[idDrive]->cbMaxSize = ulMaxSize.LowPart;
// since we just refreshed the settings from the registry, we are now up to date
g_pBitBucket[idDrive]->lCurrentDirtyCount = SHGlobalCounterGetValue(g_pBitBucket[idDrive]->hgcDirtyCount);
return TRUE;
}
//
// this function is used to compact the bitbucked INFO files.
//
// we do a lazy delete (just mark the entries as deleted) and when we hit a
// certain number of bogus entries in the info file, we need to go through and clean up the
// garbage and compact the file.
//
DWORD CALLBACK CompactBBInfoFileThread(void *pData)
{
int idDrive = PtrToLong(pData);
//
// PERF (reinerf) - as an optimization, we might want to check here to see
// if someone is waiting to empty the bitbucket since if we are going to empty
// this bucket there is no point in wasting time compacting it.
//
HANDLE hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
// work in chunks of 10
BBDATAENTRYW bbdewArray[10]; // use a unicode array, but it might end up holding BBDATAENTRYA stucts
LPBBDATAENTRYW pbbdew = bbdewArray;
int iNumEntries = 0;
DWORD dwDataEntrySize = g_pBitBucket[idDrive]->fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA);
DWORD dwReadPos = 0;
DWORD dwBytesWritten;
// save off the inital write pos
DWORD dwWritePos = SetFilePointer(hFile, 0, NULL, FILE_CURRENT);
while (ReadNextDataEntry(hFile, pbbdew, TRUE, idDrive))
{
ASSERT(!IsDeletedEntry(pbbdew));
iNumEntries++;
// do we have 10 entries yet?
if (iNumEntries == ARRAYSIZE(bbdewArray))
{
iNumEntries = 0;
TraceMsg(TF_BITBUCKET, "Bitbucket: Compacting drive %d: dwRead = %d, dwWrite = %d, writing 10 entries", idDrive, dwReadPos, dwWritePos);
// save where we are for reading
dwReadPos = SetFilePointer(hFile, 0, NULL, FILE_CURRENT);
// then go to where we are for writing
SetFilePointer(hFile, dwWritePos, NULL, FILE_BEGIN);
// write it out
if (!WriteFile(hFile, (LPBYTE)bbdewArray, dwDataEntrySize * ARRAYSIZE(bbdewArray), &dwBytesWritten, NULL) || dwBytesWritten != (dwDataEntrySize * ARRAYSIZE(bbdewArray)))
{
// we're in big trouble if this happens.
// bail completely so that at worst, we only have a few bad records.
// if we keep trying to write from this point, but the write point is
// we'll nuke all the records
ASSERTMSG(FALSE, "Bitbucket: we were compacting drive %d and it is totally messed up", idDrive);
break;
}
// sucess! move our write pos to the end of were we finished writing
dwWritePos += (dwDataEntrySize * ARRAYSIZE(bbdewArray));
// go back to were we left off reading
SetFilePointer(hFile, dwReadPos, NULL, FILE_BEGIN);
// reset our lparray pointer
pbbdew = bbdewArray;
}
else
{
// dont have 10 entries yet, so keep going
pbbdew = (LPBBDATAENTRYW)((LPBYTE)pbbdew + dwDataEntrySize);
}
}
TraceMsg(TF_BITBUCKET, "Bitbucket: Compacting drive %d: dwRead = %d, dwWrite = %d, writing last %d entries", idDrive, dwReadPos, dwWritePos, iNumEntries);
// write whatever we have left over
SetFilePointer(hFile, dwWritePos, NULL, FILE_BEGIN);
WriteFile(hFile, (LPBYTE)bbdewArray, dwDataEntrySize * iNumEntries, &dwBytesWritten, NULL);
ASSERT(dwBytesWritten == (dwDataEntrySize * iNumEntries));
SetEndOfFile(hFile);
CloseBBInfoFile(hFile, idDrive);
}
return 0;
}
void CompactBBInfoFile(int idDrive)
{
HANDLE hThread;
DWORD idThread;
// try to spin up a background thread to do the work for us
hThread = CreateThread(NULL, 0, CompactBBInfoFileThread, IntToPtr(idDrive), 0, &idThread);
if (hThread)
{
// let the background thread do the work
CloseHandle(hThread);
}
else
{
TraceMsg(TF_BITBUCKET, "BBCompact - failed to create backgound thread! Doing work on this thread");
CompactBBInfoFileThread(IntToPtr(idDrive));
}
}
void GetDeletedFileNameFromParts(LPTSTR pszFileName, int idDrive, int iIndex, LPCTSTR pszOriginal)
{
wnsprintf(pszFileName, MAX_PATH, TEXT("D%c%d%s"), DriveChar(idDrive), iIndex, PathFindExtension(pszOriginal));
}
void GetDeletedFileName(LPTSTR pszFileName, const BBDATAENTRYW *pbbdew)
{
GetDeletedFileNameFromParts(pszFileName, pbbdew->idDrive, pbbdew->iIndex, pbbdew->szOriginal);
}
// get the full path to the file/folder in the recycle bin location
void GetDeletedFilePath(LPTSTR pszPath, const BBDATAENTRYW *pbbdew)
{
TCHAR szFileName[MAX_PATH];
DriveIDToBBPath(pbbdew->idDrive, pszPath);
GetDeletedFileName(szFileName, pbbdew);
PathAppend(pszPath, szFileName);
}
void UpdateIcon(BOOL fFull)
{
LONG cbData;
DWORD dwType;
HKEY hkeyCLSID = NULL;
HKEY hkeyUserCLSID = NULL;
TCHAR szTemp[MAX_PATH];
TCHAR szNewValue[MAX_PATH];
TCHAR szValue[MAX_PATH];
TraceMsg(TF_BITBUCKET, "BitBucket: UpdateIcon %s", fFull ? TEXT("Full") : TEXT("Empty"));
szValue[0] = 0;
szNewValue[0] = 0;
// get the HKCR CLSID key (HKCR\CLSID\CLSID_RecycleBin\DefaultIcon)
if (FAILED(SHRegGetCLSIDKey(&CLSID_RecycleBin, c_szDefaultIcon, FALSE, FALSE, &hkeyCLSID)))
goto error;
// get the per-user CLSID
// HKCU
// NT: Software\Microsoft\Windows\CurrentVersion\Explorer\CLSID
// 9x: Software\Classes\CLSID
if (FAILED(SHRegGetCLSIDKey(&CLSID_RecycleBin, c_szDefaultIcon, TRUE, FALSE, &hkeyUserCLSID)))
{
// it most likely failed because the reg key dosent exist, so create it now
if (FAILED(SHRegGetCLSIDKey(&CLSID_RecycleBin, c_szDefaultIcon, TRUE, TRUE, &hkeyUserCLSID)))
goto error;
// now that we created it, lets copy the stuff from HKLM there
// get the local machine default icon
cbData = sizeof(szTemp);
if (RegQueryValueEx(hkeyCLSID, NULL, 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
goto error;
// set the per-user default icon
RegSetValueEx(hkeyUserCLSID, NULL, 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
// get the local machine full icon
cbData = sizeof(szTemp);
if (RegQueryValueEx(hkeyCLSID, TEXT("Full"), 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
goto error;
// set the per-user full icon
RegSetValueEx(hkeyUserCLSID, TEXT("Full"), 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
// get the local machine empty icon
cbData = sizeof(szTemp);
if (RegQueryValueEx(hkeyCLSID, TEXT("Empty"), 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
goto error;
// set the per-user empty icon
RegSetValueEx(hkeyUserCLSID, TEXT("Empty"), 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
}
// try the per user first, if we dont find it, then copy the information from HKCR\CLSID\etc...
// to the per-user location
cbData = sizeof(szTemp);
if (RegQueryValueEx(hkeyUserCLSID, NULL, 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
{
// get the local machine default icon
cbData = sizeof(szTemp);
if (RegQueryValueEx(hkeyCLSID, NULL, 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
goto error;
// set the per-user default icon
RegSetValueEx(hkeyUserCLSID, NULL, 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
}
lstrcpy(szValue, szTemp);
cbData = sizeof(szTemp);
if (RegQueryValueEx(hkeyUserCLSID, fFull ? TEXT("Full") : TEXT("Empty"), 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
{
cbData = sizeof(szTemp);
if (RegQueryValueEx(hkeyCLSID, fFull ? TEXT("Full") : TEXT("Empty"), 0, &dwType, (LPBYTE)szTemp, &cbData) != ERROR_SUCCESS)
goto error;
// set the per-user full/empty icon
RegSetValueEx(hkeyUserCLSID, fFull ? TEXT("Full") : TEXT("Empty"), 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
}
lstrcpy(szNewValue, szTemp);
if (lstrcmpi(szNewValue, szValue) != 0)
{
TCHAR szExpandedValue[MAX_PATH];
LPTSTR szIconIndex;
cbData = sizeof(szTemp);
TW32(RegQueryValueEx(hkeyUserCLSID, fFull ? TEXT("Full") : TEXT("Empty"), 0, &dwType, (LPBYTE)szTemp, &cbData));
// we always update the per user default icon, because recycle bins are per user on NTFS
RegSetValueEx(hkeyUserCLSID, NULL, 0, dwType, (LPBYTE)szTemp, (lstrlen(szTemp) + 1) * sizeof(TCHAR));
if (SHExpandEnvironmentStrings(szValue, szExpandedValue, ARRAYSIZE(szExpandedValue)))
{
szIconIndex = StrRChr(szExpandedValue, NULL, TEXT(','));
if (szIconIndex)
{
int id;
int iNum = StrToInt(szIconIndex + 1);
*szIconIndex = 0; // end szValue after the dll name
// ..and tell anyone viewing this image index to update
id = LookupIconIndex(szExpandedValue, iNum, 0);
SHUpdateImage(szExpandedValue, iNum, 0, id);
SHChangeNotifyHandleEvents();
}
}
}
error:
if (hkeyCLSID)
RegCloseKey(hkeyCLSID);
if (hkeyUserCLSID)
RegCloseKey(hkeyUserCLSID);
}
//
// this loads the settings for this drive. it obeys the "use global" bit
//
BOOL GetBBDriveSettings(int idDrive, ULONGLONG *pcbDiskSpace)
{
TCHAR szDrive[MAX_PATH];
TCHAR szName[MAX_PATH];
TCHAR szVolume[MAX_PATH];
TCHAR szPath[MAX_PATH];
DWORD dwSizePath = ARRAYSIZE(szPath);
ULARGE_INTEGER ulFreeUser, ulTotal, ulFree;
DWORD dwSize1;
DWORD dwSerialNumber, dwSerialNumberFromRegistry;
LONG lInitialCount;
BOOL bHaveCachedRegInfo = FALSE;
BOOL bRet = TRUE;
HKEY hkey;
// Get volume root since we are going to call GetVolumeInformation()
DriveIDToBBVolumeRoot(idDrive, szVolume);
DriveIDToBBPath(idDrive, szDrive);
GetBBInfo2FileSpec(szDrive, szName);
if (idDrive == SERVERDRIVE)
{
// in the SERVERDRIVE case everything is under HKCU, so use the per-user key
hkey = g_pBitBucket[idDrive]->hkeyPerUser;
}
else
{
hkey = g_pBitBucket[idDrive]->hkey;
}
// first we need to check to see we have cached registry info for this drive, or if this
// is a new drive
dwSize1 = sizeof(dwSerialNumber);
if (PathFileExists(szName) &&
(RegQueryValueEx(hkey,
TEXT("VolumeSerialNumber"),
NULL,
NULL,
(LPBYTE)&dwSerialNumberFromRegistry,
&dwSize1) == ERROR_SUCCESS) &&
GetVolumeInformation(szVolume,
NULL,
0,
&dwSerialNumber,
NULL,
NULL,
NULL,
0) &&
(dwSerialNumber == dwSerialNumberFromRegistry))
{
// we were able to read the drive serial number and it matched the regsitry, so
// assume that the cached reg info is valid
bHaveCachedRegInfo = TRUE;
}
// do some extra checks in the SERVERDRIVE case to make sure that the path matches in addition to the volume serial number.
// (eg nethomedir could be on the same volume but a different path)
if (bHaveCachedRegInfo && (SERVERDRIVE == idDrive))
{
if ((RegQueryValueEx(hkey, TEXT("Path"), NULL, NULL, (LPBYTE) szPath, &dwSizePath) != ERROR_SUCCESS) ||
(lstrcmpi(szPath, szDrive) != 0))
{
// couldn't read the path or it didnt match, so no we can't use the cacehed info
bHaveCachedRegInfo = FALSE;
}
}
if (!bHaveCachedRegInfo)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: new drive %s detected!!!", szDrive);
// this is a new volume, so delete any old registry info we had
DeleteOldBBRegInfo(idDrive);
// And also migrate the win95 info if it exists
// NOTE: this also fills in the g_pBitBucket[idDrive]->fIsUnicode
VerifyBBInfoFileHeader(idDrive);
}
else
{
// set g_pBitBucket[idDrive]->fIsUnicode based on the registry info
dwSize1 = sizeof(g_pBitBucket[idDrive]->fIsUnicode);
if (RegQueryValueEx(hkey, TEXT("IsUnicode"), NULL, NULL, (LPBYTE)&g_pBitBucket[idDrive]->fIsUnicode, &dwSize1) != ERROR_SUCCESS)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: IsUnicode missing from registry for drive %s !!", szDrive);
// instead, try to get this out of the header
VerifyBBInfoFileHeader(idDrive);
}
}
// we need to check to make sure that the Recycle Bin folder is properly secured
if (!CheckRecycleBinAcls(idDrive))
{
// we return false if this fails (meaning we detected an unsecure directory and were unable to
// fix it or the user didnt want to fix it). This will effectively disable all recycle bin operations
// on this volume for this session.
return FALSE;
}
// calculate the next file num index
lInitialCount = FindInitialNextFileNum(idDrive);
// create the hgcNextFileNume global counter
ASSERT(lInitialCount >= 0);
lstrcpy(szName, TEXT("BitBucket."));
DriveIDToBBRegKey(idDrive, &szName[10]);
lstrcpy(&szName[11], TEXT(".NextFileNum"));
g_pBitBucket[idDrive]->hgcNextFileNum = SHGlobalCounterCreateNamed(szName, lInitialCount); // BitBucket.<drive letter>.NextFileNum
if (g_pBitBucket[idDrive]->hgcNextFileNum == INVALID_HANDLE_VALUE)
{
ASSERTMSG(FALSE, "BitBucket: failed to create hgcNextFileNum for drive %s !!", szDrive);
return FALSE;
}
// we call SHGetDiskFreeSpaceEx so we can respect quotas on NTFS
DriveIDToBBRoot(idDrive, szDrive);
if (SHGetDiskFreeSpaceEx(szDrive, &ulFreeUser, &ulTotal, &ulFree))
{
g_pBitBucket[idDrive]->dwClusterSize = PathGetClusterSize(szDrive);
g_pBitBucket[idDrive]->qwDiskSize = ulTotal.QuadPart;
}
else
{
if (idDrive == SERVERDRIVE)
{
g_pBitBucket[idDrive]->dwClusterSize = 2048;
g_pBitBucket[idDrive]->qwDiskSize = 0x7FFFFFFF;
}
else
{
ASSERTMSG(FALSE, "Bitbucket: SHGetDiskFreeSpaceEx failed on %s !!", szDrive);
g_pBitBucket[idDrive]->dwClusterSize = 0;
g_pBitBucket[idDrive]->qwDiskSize = 0;
}
}
if (pcbDiskSpace)
{
*pcbDiskSpace = g_pBitBucket[idDrive]->qwDiskSize;
}
// Read the Percent and NukeOnDelete settings, and recalculate cbMaxSize.
RefreshBBDriveSettings(idDrive);
TraceMsg(TF_BITBUCKET,
"GetBBDriveSettings: Drive %s, fIsUnicode=%d, iPercent=%d, cbMaxSize=%d, fNukeOnDelete=%d, NextFileNum=%d",
szDrive,
g_pBitBucket[idDrive]->fIsUnicode,
g_pBitBucket[idDrive]->iPercent,
g_pBitBucket[idDrive]->cbMaxSize,
g_pBitBucket[idDrive]->fNukeOnDelete,
SHGlobalCounterGetValue(g_pBitBucket[idDrive]->hgcNextFileNum));
return TRUE;
}
//
// cleans up old iPercent and fNukeOnDelete registry keys when we dectect a new drive
//
void DeleteOldBBRegInfo(idDrive)
{
RegDeleteValue(g_pBitBucket[idDrive]->hkey, TEXT("Percent"));
RegDeleteValue(g_pBitBucket[idDrive]->hkey, TEXT("NukeOnDelete"));
RegDeleteValue(g_pBitBucket[idDrive]->hkey, TEXT("IsUnicode"));
}
//
// This gets called when explorer exits to persist the volume serial # and
// whether the drive is unicode for the specified drive.
//
void PersistBBDriveInfo(int idDrive)
{
TCHAR szVolume[MAX_PATH];
DWORD dwSerialNumber;
HKEY hkey;
if (SERVERDRIVE == idDrive)
{
TCHAR szPath[MAX_PATH];
// in the SERVERDRIVE case everything is under HKCU, so use the per-user key
hkey = g_pBitBucket[idDrive]->hkeyPerUser;
DriveIDToBBPath(idDrive, szPath);
RegSetValueEx(hkey, TEXT("Path"), 0, REG_SZ, (LPBYTE) szPath, sizeof(TCHAR) * (lstrlen(szPath) + 1));
}
else
{
hkey = g_pBitBucket[idDrive]->hkey;
}
DriveIDToBBVolumeRoot(idDrive, szVolume);
// write out the volume serial # so we can detect when a new drive comes along and give it the default settings
// NOTE: we will fail to write out the volume serial # if we are a normal user and HKLM is locked down. Oh well.
if (GetVolumeInformation(szVolume, NULL, 0, &dwSerialNumber, NULL, NULL, NULL, 0))
{
RegSetValueEx(hkey, TEXT("VolumeSerialNumber"), 0, REG_DWORD, (LPBYTE)&dwSerialNumber, sizeof(dwSerialNumber));
}
// save off fIsUnicode as well
RegSetValueEx(hkey, TEXT("IsUnicode"), 0, REG_DWORD, (LPBYTE)&g_pBitBucket[idDrive]->fIsUnicode, sizeof(g_pBitBucket[idDrive]->fIsUnicode));
}
//
// This is what gets called when the user tweaks the drive settings for all the drives (the global settings)
//
BOOL PersistGlobalSettings(BOOL fUseGlobalSettings, BOOL fNukeOnDelete, int iPercent)
{
ASSERT(g_hkBitBucket);
if (RegSetValueEx(g_hkBitBucket, TEXT("Percent"), 0, REG_DWORD, (LPBYTE)&iPercent, sizeof(iPercent)) != ERROR_SUCCESS ||
RegSetValueEx(g_hkBitBucket, TEXT("NukeOnDelete"), 0, REG_DWORD, (LPBYTE)&fNukeOnDelete, sizeof(fNukeOnDelete)) != ERROR_SUCCESS ||
RegSetValueEx(g_hkBitBucket, TEXT("UseGlobalSettings"), 0, REG_DWORD, (LPBYTE)&fUseGlobalSettings, sizeof(fUseGlobalSettings)) != ERROR_SUCCESS)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: failed to update global bitbucket data in the registry!!");
return FALSE;
}
// since we just updated the global drive settings, we need to increment the dirty count and set our own
g_lProcessDirtyCount = SHGlobalCounterIncrement(g_hgcGlobalDirtyCount);
return TRUE;
}
//
// This is what gets called when the user tweaks the drive settings for a drive via the
// Recycle Bin property sheet page. The only thing we care about is the % slider and the
// "Do not move files to the recycle bin" settings.
//
BOOL PersistBBDriveSettings(int idDrive, int iPercent, BOOL fNukeOnDelete)
{
if (RegSetValueEx(g_pBitBucket[idDrive]->hkey, TEXT("Percent"), 0, REG_DWORD, (LPBYTE)&iPercent, sizeof(iPercent)) != ERROR_SUCCESS ||
RegSetValueEx(g_pBitBucket[idDrive]->hkey, TEXT("NukeOnDelete"), 0, REG_DWORD, (LPBYTE)&fNukeOnDelete, sizeof(fNukeOnDelete)) != ERROR_SUCCESS)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: unable to persist drive settings for drive %d", idDrive);
return FALSE;
}
// since we just updated the drive settings, we need to increment the dirty count for this drive
g_pBitBucket[idDrive]->lCurrentDirtyCount = SHGlobalCounterIncrement(g_pBitBucket[idDrive]->hgcDirtyCount);
return TRUE;
}
//
// walks the multi-string pszSrc and sets up the undo info
//
void BBCheckRestoredFiles(LPCTSTR pszSrc)
{
if (pszSrc && IsFileInBitBucket(pszSrc))
{
LPCTSTR pszTemp = pszSrc;
while (*pszTemp)
{
FOUndo_FileRestored(pszTemp);
pszTemp += (lstrlen(pszTemp) + 1);
}
SHUpdateRecycleBinIcon();
}
}
//
// This is the quick and efficent way to tell if the Recycle Bin is empty or not
//
STDAPI_(BOOL) IsRecycleBinEmpty()
{
int i;
for (i = 0; i < MAX_BITBUCKETS; i++)
{
if (CountDeletedFilesOnDrive(i, NULL, 1))
return FALSE;
}
return TRUE;
}
//
// Finds out how many files are deleted on this drive, and optionally the total size of those files.
// Also, stop counting if the total # of files equals iMaxFiles.
//
// NOTE: if you pass iMaxFiles = 0, then we ignore the parameter and count up all the files/sizes
//
int CountDeletedFilesOnDrive(int idDrive, LPDWORD pdwSize, int iMaxFiles)
{
int cFiles = 0;
HANDLE hFile;
WIN32_FIND_DATA wfd;
TCHAR szBBPath[MAX_PATH];
TCHAR szBBFileSpec[MAX_PATH];
if (pdwSize)
*pdwSize = 0;
if (!IsBitBucketableDrive(idDrive))
return 0;
DriveIDToBBPath(idDrive, szBBPath);
PathCombine(szBBFileSpec, szBBPath, TEXT("D*.*"));
hFile = FindFirstFile(szBBFileSpec, &wfd);
if (hFile == INVALID_HANDLE_VALUE)
{
return 0;
}
do
{
if (PathIsDotOrDotDot(wfd.cFileName) || lstrcmpi(wfd.cFileName, c_szDesktopIni) == 0)
{
continue;
}
cFiles++;
if (pdwSize)
{
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
FOLDERCONTENTSINFO fci = {0};
TCHAR szDir[MAX_PATH];
fci.bContinue = TRUE;
// PERF (reinerf) - for perf we should try to avoid
// calling FolderSize here. Perhaps we could encode the size
// as part of the extension?
lstrcpyn(szDir, szBBPath, ARRAYSIZE(szDir));
PathAppend(szDir, wfd.cFileName);
FolderSize(szDir, &fci);
*pdwSize += (DWORD)(fci.cbSize);
}
else
{
// simple file case
*pdwSize += wfd.nFileSizeLow;
}
}
if (iMaxFiles > 0 && cFiles >= iMaxFiles)
break;
} while (FindNextFile(hFile, &wfd));
FindClose(hFile);
return cFiles;
}
//
// Returns the number of files in the Recycle Bin, and optionally the drive id
// if there's only one file, and optionally the total size of all the stuff.
//
// We also stop counting if iMaxFiles is nonzero and we find that many
// files. This helps perf by having a cutoff point where we use a generic error
// message instead of the exact # of files. If iMaxFiles is zero, we give the true
// count of files.
//
// NOTE: don't use this if you just want to check to see if the recycle bin is
// empty or full!! Use IsRecycleBinEmpty() instead
//
int BBTotalCount(LPINT pidDrive, LPDWORD pdwSize, int iMaxFiles)
{
int i;
int idDrive;
int nFiles = 0;
DWORD dwSize;
if (pdwSize)
*pdwSize = 0;
for (i = 0; i < MAX_BITBUCKETS; i++)
{
int nFilesOld = nFiles;
nFiles += CountDeletedFilesOnDrive(i, pdwSize ? &dwSize : NULL, iMaxFiles > 0 ? iMaxFiles - nFilesOld : 0);
if (pdwSize)
*pdwSize += dwSize;
if (nFilesOld == 0 && nFiles == 1)
{
// if just one file, set the drive id
idDrive = i;
}
if (iMaxFiles > 0 && nFiles >= iMaxFiles)
break;
}
if (pidDrive)
*pidDrive = (nFiles == 1) ? idDrive : 0;
return nFiles;
}
//
// gets the number of files and and size of the bitbucket for the given drive
//
SHSTDAPI SHQueryRecycleBin(LPCTSTR pszRootPath, LPSHQUERYRBINFO pSHQueryInfo)
{
DWORD dwSize = 0;
DWORD dwNumItems = 0;
// since this fn is exported, we need to check to see if we need to
// init our global data first
if (!InitBBGlobals())
{
return E_OUTOFMEMORY;
}
if (!pSHQueryInfo ||
(pSHQueryInfo->cbSize < sizeof(SHQUERYRBINFO)))
{
return E_INVALIDARG;
}
if (pszRootPath && pszRootPath[0] != TEXT('\0'))
{
int idDrive = DriveIDFromBBPath(pszRootPath);
if (MakeBitBucket(idDrive))
{
dwNumItems = CountDeletedFilesOnDrive(idDrive, &dwSize, 0);
}
}
else
{
//
// NTRAID#NTBUG9-146905-2001/03/15-jeffreys
//
// This is a public API, documented to return the totals for all
// recycle bins when no path is given. This was broken in Windows
// 2000 and Millennium.
//
dwNumItems = BBTotalCount(NULL, &dwSize, 0);
}
pSHQueryInfo->i64Size = (__int64)dwSize;
pSHQueryInfo->i64NumItems = (__int64)dwNumItems;
return S_OK;
}
SHSTDAPI SHQueryRecycleBinA(LPCSTR pszRootPath, LPSHQUERYRBINFO pSHQueryRBInfo)
{
WCHAR wszPath[MAX_PATH];
SHAnsiToUnicode(pszRootPath, wszPath, ARRAYSIZE(wszPath));
return SHQueryRecycleBin(wszPath, pSHQueryRBInfo);
}
//
// Empty the given drive or all drives
//
SHSTDAPI SHEmptyRecycleBin(HWND hWnd, LPCTSTR pszRootPath, DWORD dwFlags)
{
// since this fn is exported, we need to check to see if we need to
// init our global data first
if (!InitBBGlobals())
{
// this could happen in low memory situations, we have no choice but
// to abort the empty
return E_OUTOFMEMORY;
}
if ((pszRootPath == NULL) || (*pszRootPath == 0))
{
BBPurgeAll(hWnd, dwFlags);
}
else
{
int idDrive = DriveIDFromBBPath(pszRootPath);
// note: we include MAX_DRIVES(26) which is SERVERDRIVE case!
if ((idDrive < 0) || (idDrive > MAX_DRIVES))
{
return E_INVALIDARG;
}
if (MakeBitBucket(idDrive))
{
PurgeOneBitBucket(hWnd, idDrive, dwFlags);
}
}
return S_OK;
}
SHSTDAPI SHEmptyRecycleBinA(HWND hWnd, LPCSTR pszRootPath, DWORD dwFlags)
{
WCHAR wszPath[MAX_PATH];
SHAnsiToUnicode(pszRootPath, wszPath, ARRAYSIZE(wszPath));
return SHEmptyRecycleBin(hWnd, wszPath, dwFlags);
}
void MarkBBPurgeAllTime(BOOL bStart)
{
TCHAR szText[64];
if (g_dwStopWatchMode == 0xffffffff)
g_dwStopWatchMode = StopWatchMode(); // Since the stopwatch funcs live in shdocvw, delay this call so we don't load shdocvw until we need to
if (g_dwStopWatchMode)
{
lstrcpy((LPTSTR)szText, TEXT("Shell Empty Recycle"));
if (bStart)
{
lstrcat((LPTSTR)szText, TEXT(": Start"));
StopWatch_Start(SWID_BITBUCKET, (LPCTSTR)szText, SPMODE_SHELL | SPMODE_DEBUGOUT);
}
else
{
lstrcat((LPTSTR)szText, TEXT(": Stop"));
StopWatch_Stop(SWID_BITBUCKET, (LPCTSTR)szText, SPMODE_SHELL | SPMODE_DEBUGOUT);
}
}
}
HRESULT BBPurgeAll(HWND hwndOwner, DWORD dwFlags)
{
TCHAR szPath[MAX_PATH * 2 + 3]; // null space and double null termination
int nFiles;
int idDrive;
BOOL fConfirmed;
SHFILEOPSTRUCT sFileOp ={hwndOwner,
FO_DELETE,
szPath,
NULL,
FOF_NOCONFIRMATION | FOF_SIMPLEPROGRESS,
FALSE,
NULL,
MAKEINTRESOURCE(IDS_BB_EMPTYINGWASTEBASKET)};
// check to see if we need to init our global data first
if (!InitBBGlobals())
{
// this could happen in low memory situations, we have no choice but
// to fail the empty
return E_OUTOFMEMORY;
}
if (g_dwStopWatchMode) // If the shell perf mode is enabled, time the empty operation
{
MarkBBPurgeAllTime(TRUE);
}
fConfirmed = (dwFlags & SHERB_NOCONFIRMATION);
if (!fConfirmed)
{
// find out how many files we have...
BBDATAENTRYW bbdew;
TCHAR szSrcName[MAX_PATH];
WIN32_FIND_DATA fd;
CONFIRM_DATA cd = {CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_PROGRAM_FILE | CONFIRM_MULTIPLE, 0};
nFiles = BBTotalCount(&idDrive, NULL, MAX_EMPTY_FILES);
if (!nFiles)
{
if (g_dwStopWatchMode)
{
MarkBBPurgeAllTime(FALSE);
}
return S_FALSE; // no files to delete
}
// first do the confirmation thing
fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
// We have to call IsBitBucketInited() here since we could be in BBPurgeAll as a result
// of a corrupt bitbucket. In this case, the g_pBitBucket[idDrive] has not been inited and
// therefore we can't use it yet
if (nFiles == 1 && IsBitBucketInited(idDrive))
{
HANDLE hFile = OpenBBInfoFile(idDrive, OPENBBINFO_READ, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
ReadNextDataEntry(hFile, &bbdew, TRUE, idDrive);
CloseBBInfoFile(hFile, idDrive);
StrCpyNW(szSrcName, bbdew.szOriginal, ARRAYSIZE(szSrcName));
}
else
{
if (g_dwStopWatchMode)
{
MarkBBPurgeAllTime(FALSE);
}
return S_FALSE; // no files to delete
}
}
else
{
// If we haven't inited this bucket yet or there are MAX_EMPTY_FILES or more files,
// then use the generic empty message
if (nFiles == 1 || nFiles >= MAX_EMPTY_FILES)
{
// counting up the total # of files in the bitbucket scales as
// the # of files (duh!). This can get pretty expensive, so if there
// are MAX_EMPTY_FILES or more files in the bin, we just give a generic
// error message
// set this so ConfirmFileOp knows to use the generic message
nFiles = -1;
}
szSrcName[0] = 0;
}
if (ConfirmFileOp(hwndOwner, NULL, &cd, nFiles, 0, CONFIRM_DELETE_FILE | CONFIRM_WASTEBASKET_PURGE,
szSrcName, &fd, NULL, &fd, NULL) == IDYES)
{
fConfirmed = TRUE;
}
}
if (fConfirmed)
{
DECLAREWAITCURSOR;
SetWaitCursor();
if (dwFlags & SHERB_NOPROGRESSUI)
{
sFileOp.fFlags |= FOF_SILENT;
}
for (idDrive = 0; (idDrive < MAX_BITBUCKETS) && !sFileOp.fAnyOperationsAborted; idDrive++)
{
if (MakeBitBucket(idDrive))
{
HANDLE hFile;
// nuke all the BB files (d*.*)
DriveIDToBBPath(idDrive, szPath);
PathAppend(szPath, c_szDStarDotStar);
szPath[lstrlen(szPath) + 1] = 0; // double null terminate
// turn off redraw for now.
ShellFolderView_SetRedraw(hwndOwner, FALSE);
hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
if (INVALID_HANDLE_VALUE != hFile)
{
// now do the actual delete.
if (SHFileOperation(&sFileOp) || sFileOp.fAnyOperationsAborted)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: emptying bucket on %s failed or user aborted", szPath);
// NOTE: the info file may point to some files that have been deleted,
// it will be cleaned up later
}
else
{
// reset the info file since we deleted it as part of the empty operation
ResetInfoFileHeader(hFile, g_pBitBucket[idDrive]->fIsUnicode);
}
// we always reset the desktop.ini
CreateRecyclerDirectory(idDrive);
CloseBBInfoFile(hFile, idDrive);
}
ShellFolderView_SetRedraw(hwndOwner, TRUE);
}
}
if (!(dwFlags & SHERB_NOSOUND))
{
SHPlaySound(TEXT("EmptyRecycleBin"));
}
SHUpdateRecycleBinIcon();
ResetWaitCursor();
}
if (g_dwStopWatchMode)
{
MarkBBPurgeAllTime(FALSE);
}
return S_OK;
}
BOOL BBNukeFile(LPCTSTR pszPath, DWORD dwAttribs)
{
if (Win32DeleteFile(pszPath))
{
FOUndo_FileReallyDeleted((LPTSTR)pszPath);
return TRUE;
}
return FALSE;
}
BOOL BBNukeFolder(LPCTSTR pszDir)
{
TCHAR szPath[MAX_PATH];
BOOL fRet;
if (PathCombine(szPath, pszDir, c_szStarDotStar))
{
WIN32_FIND_DATA fd;
HANDLE hfind = FindFirstFile(szPath, &fd);
if (hfind != INVALID_HANDLE_VALUE)
{
do
{
LPTSTR pszFile = fd.cAlternateFileName[0] ? fd.cAlternateFileName : fd.cFileName;
if (pszFile[0] != TEXT('.'))
{
// use the short path name so that we
// don't have to worry about max path
PathCombine(szPath, pszDir, pszFile);
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// even if this fails, we keep going.
// we want to delete as much as possible
BBNukeFolder(szPath);
}
else
{
BBNukeFile(szPath, fd.dwFileAttributes);
}
}
} while (FindNextFile(hfind, &fd));
FindClose(hfind);
}
}
fRet = Win32RemoveDirectory(pszDir);
// if everything was successful, we need to notify any undo stuff about this
if (fRet)
{
FOUndo_FileReallyDeleted((LPTSTR)szPath);
}
return fRet;
}
BOOL BBNuke(LPCTSTR pszPath)
{
BOOL fRet = FALSE;
// verify that the file exists
DWORD dwAttribs = GetFileAttributes(pszPath);
TraceMsg(TF_BITBUCKET, "Bitbucket: BBNuke called on %s ", pszPath);
if (dwAttribs != (UINT)-1)
{
// this was a directory, we need to recurse in and delete everything inside
if (dwAttribs & FILE_ATTRIBUTE_DIRECTORY)
{
fRet = BBNukeFolder(pszPath);
}
else
{
fRet = BBNukeFile(pszPath, dwAttribs);
}
}
return fRet;
}
DWORD PurgeBBFiles(int idDrive)
{
DWORD dwCurrentSize;
CountDeletedFilesOnDrive(idDrive, &dwCurrentSize, 0);
if (dwCurrentSize > g_pBitBucket[idDrive]->cbMaxSize)
{
DWORD dwDataEntrySize = g_pBitBucket[idDrive]->fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA);
HANDLE hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
BBDATAENTRYW bbdew;
TCHAR szBBPath[MAX_PATH];
DriveIDToBBPath(idDrive, szBBPath);
// while we're too big, find something to delete
while (dwCurrentSize > g_pBitBucket[idDrive]->cbMaxSize && ReadNextDataEntry(hFile, &bbdew, TRUE, idDrive))
{
TCHAR szPath[MAX_PATH], szDeletedFile[MAX_PATH];
GetDeletedFileName(szDeletedFile, &bbdew);
PathCombine(szPath, szBBPath, szDeletedFile);
BBNuke(szPath);
NukeFileInfoBeforePoint(hFile, &bbdew, dwDataEntrySize);
// subtract the size of what we just nuked
dwCurrentSize -= bbdew.dwSize;
TraceMsg(TF_BITBUCKET, "Bitbucket: purging drive %d, curent size = %d, max size = %d", idDrive, dwCurrentSize, g_pBitBucket[idDrive]->cbMaxSize);
}
CloseBBInfoFile(hFile, idDrive);
}
}
return dwCurrentSize;
}
STDAPI BBFileNameToInfo(LPCTSTR pszFileName, int *pidDrive, int *piIndex)
{
HRESULT hr = E_FAIL;
if (lstrcmpi(pszFileName, c_szInfo) &&
lstrcmpi(pszFileName, c_szInfo2) &&
lstrcmpi(pszFileName, c_szDesktopIni) &&
lstrcmpi(pszFileName, TEXT("Recycled")) &&
(StrChr(pszFileName, TEXT('\\')) == NULL)) // recycle bin dosen't support multi-level paths
{
if ((pszFileName[0] == TEXT('D')) || (pszFileName[0] == TEXT('d')))
{
if (pszFileName[1])
{
if (pidDrive)
{
hr = S_OK;
if (pszFileName[1] == TEXT('@'))
*pidDrive = SERVERDRIVE;
else if (InRange(pszFileName[1], TEXT('a'), TEXT('z')))
*pidDrive = pszFileName[1] - TEXT('a');
else if (InRange(pszFileName[1], TEXT('A'), TEXT('Z')))
*pidDrive = pszFileName[1] - TEXT('A');
else
hr = E_FAIL;
}
if (piIndex)
{
// this depends on StrToInt stoping is parsing when it hits the file extension
*piIndex = StrToInt(&pszFileName[2]);
hr = S_OK;
}
}
}
}
return hr;
}
// converts C:\RECYCLED\Dc19.foo to 19
int BBPathToIndex(LPCTSTR pszPath)
{
int iIndex;
LPTSTR pszFileName = PathFindFileName(pszPath);
if (SUCCEEDED(BBFileNameToInfo(pszFileName, NULL, &iIndex)))
{
return iIndex;
}
return -1;
}
BOOL ReadNextDataEntry(HANDLE hFile, LPBBDATAENTRYW pbbdew, BOOL fSkipDeleted, int idDrive)
{
DWORD dwBytesRead;
DWORD dwDataEntrySize = g_pBitBucket[idDrive]->fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA);
ZeroMemory(pbbdew, sizeof(*pbbdew));
TryAgain:
if (ReadFile(hFile, pbbdew, dwDataEntrySize, &dwBytesRead, NULL) &&
(dwBytesRead == dwDataEntrySize))
{
TCHAR szDeleteFileName[MAX_PATH], szOldPath[MAX_PATH];
if (fSkipDeleted && IsDeletedEntry(pbbdew))
{
goto TryAgain;
}
// for ansi entries fill out the unicode version of the original
if (!g_pBitBucket[idDrive]->fIsUnicode)
{
BBDATAENTRYA *pbbdea = (BBDATAENTRYA *)pbbdew;
SHAnsiToUnicode(pbbdea->szOriginal, pbbdew->szOriginal, ARRAYSIZE(pbbdew->szOriginal));
}
// We check for a drive that has had its letter changed since this record was added.
// In this case, we want to restore the files that were deleted on this volume to this volume.
if (pbbdew->idDrive != idDrive)
{
TCHAR szNewPath[MAX_PATH];
DriveIDToBBPath(idDrive, szOldPath);
lstrcpyn(szNewPath, szOldPath, ARRAYSIZE(szNewPath));
GetDeletedFileName(szDeleteFileName, pbbdew);
PathAppend(szOldPath, szDeleteFileName);
GetDeletedFileNameFromParts(szDeleteFileName, idDrive, pbbdew->iIndex, pbbdew->szOriginal);
PathAppend(szNewPath, szDeleteFileName);
TraceMsg(TF_BITBUCKET, "Bitbucket: found entry %s corospoinding to old drive letter, whacking it to be on drive %d !!", szOldPath, idDrive);
// we need to rename the file from d?0.txt to d<idDrive>0.txt
if (!Win32MoveFile(szOldPath, szNewPath, GetFileAttributes(szOldPath) & FILE_ATTRIBUTE_DIRECTORY))
{
TraceMsg(TF_BITBUCKET, "Bitbucket: failed to rename %s to %s, getlasterror = %d", szOldPath, szNewPath, GetLastError());
goto DeleteEntry;
}
// whack the rest of the information about this entry to match the new drive ID
pbbdew->idDrive = idDrive;
pbbdew->szShortName[0] = 'A' + (CHAR)idDrive;
if (g_pBitBucket[idDrive]->fIsUnicode)
{
// for unicode volumes we need to whack the first letter of the long name as well
pbbdew->szOriginal[0] = L'A' + (WCHAR)idDrive;
}
}
else
{
// Starting with NT5, when we delete or restore items, we dont bother updating the info file.
// So we need to make sure that the entry we have has not been restored or really nuked.
GetDeletedFilePath(szOldPath, pbbdew);
if (!PathFileExists(szOldPath))
{
DeleteEntry:
// this entry is really deleted, so mark it as such now
NukeFileInfoBeforePoint(hFile, pbbdew, dwDataEntrySize);
if (fSkipDeleted)
{
goto TryAgain;
}
}
}
return TRUE;
}
else
{
return FALSE;
}
}
//
// the file pointer is RIGHT AFTER the entry that we want to delete.
//
// back the file pointer up one record and mark it deleted
//
void NukeFileInfoBeforePoint(HANDLE hFile, LPBBDATAENTRYW pbbdew, DWORD dwDataEntrySize)
{
DWORD dwBytesWritten;
LONG lPos = SetFilePointer(hFile, 0, NULL, FILE_CURRENT);
ASSERT((DWORD)lPos >= dwDataEntrySize + sizeof(BBDATAHEADER));
if ((DWORD)lPos >= dwDataEntrySize + sizeof(BBDATAHEADER))
{
// found the entry.. back up the file pointer to the beginning
// of this record and mark it as deleted
lPos -= dwDataEntrySize;
SetFilePointer(hFile, lPos, NULL, FILE_BEGIN);
MarkEntryDeleted(pbbdew);
if (WriteFile(hFile, pbbdew, dwDataEntrySize, &dwBytesWritten, NULL))
{
ASSERT(dwDataEntrySize == dwBytesWritten);
}
else
{
TraceMsg(TF_BITBUCKET, "Bitbucket: couldn't nuke file info");
// move the file pointer back to where it was when we entered this function
SetFilePointer(hFile, lPos + dwDataEntrySize, NULL, FILE_BEGIN);
}
}
}
//
// This closes the hFile and sends out an SHCNE_UPDATEITEM for the info file on
// drive idDrive
//
void CloseBBInfoFile(HANDLE hFile, int idDrive)
{
TCHAR szInfoFile[MAX_PATH];
ASSERT(hFile != INVALID_HANDLE_VALUE);
DriveIDToBBPath(idDrive, szInfoFile);
PathAppend(szInfoFile, c_szInfo2);
CloseHandle(hFile);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szInfoFile, NULL);
}
// One half second (500 ms = 0.5 s)
#define BBINFO_OPEN_RETRY_PERIOD 500
// Retry 30 times (at least 20 s)
#define BBINFO_OPEN_MAX_RETRIES 40
//
// This opens up a handle to the bitbucket info file.
//
// NOTE: use CloseBBInfoFile so that we generate the proper
// SHChangeNotify event for the info file.
//
HANDLE OpenBBInfoFile(int idDrive, DWORD dwFlags, int iRetryCount)
{
HANDLE hFile;
TCHAR szBBPath[MAX_PATH];
TCHAR szInfo[MAX_PATH];
int nAttempts = 0;
DWORD dwLastErr;
DECLAREWAITCURSOR;
if ((iRetryCount == 0) || (iRetryCount > BBINFO_OPEN_MAX_RETRIES))
{
// zero retry count means that the caller wants the max # of retries
iRetryCount = BBINFO_OPEN_MAX_RETRIES;
}
DriveIDToBBPath(idDrive, szBBPath);
GetBBInfo2FileSpec(szBBPath, szInfo);
// If we are hitting a sharing violation, retry many times
do
{
nAttempts++;
hFile = CreateFile(szInfo,
GENERIC_READ | ((OPENBBINFO_WRITE & dwFlags) ? GENERIC_WRITE : 0),
(OPENBBINFO_WRITE & dwFlags) ? 0 : FILE_SHARE_READ,
NULL,
(OPENBBINFO_CREATE & dwFlags) ? OPEN_ALWAYS : OPEN_EXISTING,
FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_RANDOM_ACCESS,
NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
// success!
break;
}
dwLastErr = GetLastError();
if (ERROR_SHARING_VIOLATION != dwLastErr)
{
break;
}
TraceMsg(TF_BITBUCKET, "Bitbucket: sharing violation on info file (retry %d)", nAttempts - 1);
if (nAttempts < iRetryCount)
{
SetWaitCursor();
Sleep(BBINFO_OPEN_RETRY_PERIOD);
ResetWaitCursor();
}
} while (nAttempts < iRetryCount);
if (hFile == INVALID_HANDLE_VALUE)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: could not open a handle to %s - error 0x%08x!!", szInfo, dwLastErr);
return INVALID_HANDLE_VALUE;
}
// set the file pointer to just after the dataheader
SetFilePointer(hFile, sizeof(BBDATAHEADER), NULL, FILE_BEGIN);
return hFile;
}
void BBAddDeletedFileInfo(LPCTSTR pszOriginal, LPCTSTR pszShortName, int iIndex, int idDrive, DWORD dwSize, HDPA *phdpaDeletedFiles)
{
BBDATAENTRYW *pbbdew;
BOOL fSuccess = FALSE; // assume failure
// Flush the cache regularly
if (*phdpaDeletedFiles && DPA_GetPtrCount(*phdpaDeletedFiles) >= 1)
{
pbbdew = (BBDATAENTRYW *)DPA_FastGetPtr(*phdpaDeletedFiles, 0);
// Flush the cache before we start deleting files on a different drive, or
// when it's too full
if (pbbdew->idDrive != idDrive || DPA_GetPtrCount(*phdpaDeletedFiles) >= 128)
{
BBFinishDelete(*phdpaDeletedFiles);
*phdpaDeletedFiles = NULL;
}
}
pbbdew = NULL;
if (!*phdpaDeletedFiles)
{
*phdpaDeletedFiles = DPA_Create(0); // Use default growth value
}
if (*phdpaDeletedFiles)
{
pbbdew = (BBDATAENTRYW *)LocalAlloc(LPTR, sizeof(*pbbdew));
if (pbbdew)
{
SYSTEMTIME st;
if (g_pBitBucket[idDrive]->fIsUnicode)
{
// Create a BBDATAENTRYW from a unicode name
lstrcpy(pbbdew->szOriginal, pszOriginal);
if (!DoesStringRoundTrip(pszOriginal, pbbdew->szShortName, ARRAYSIZE(pbbdew->szShortName)))
SHUnicodeToAnsi(pszShortName, pbbdew->szShortName, ARRAYSIZE(pbbdew->szShortName));
}
else
{
BBDATAENTRYA *pbbdea = (BBDATAENTRYA *)pbbdew;
// Create a BBDATAENTRYA from a unicode name
if (!DoesStringRoundTrip(pszOriginal, pbbdea->szOriginal, ARRAYSIZE(pbbdea->szOriginal)))
SHUnicodeToAnsi(pszShortName, pbbdea->szOriginal, ARRAYSIZE(pbbdea->szOriginal));
}
pbbdew->iIndex = iIndex;
pbbdew->idDrive = idDrive;
pbbdew->dwSize = ROUND_TO_CLUSTER(dwSize, g_pBitBucket[idDrive]->dwClusterSize);
GetSystemTime(&st); // Get time of deletion
SystemTimeToFileTime(&st, &pbbdew->ft);
if (DPA_AppendPtr(*phdpaDeletedFiles, pbbdew) != -1)
{
fSuccess = TRUE;
}
}
}
if (!fSuccess)
{
TCHAR szBBPath[MAX_PATH];
TCHAR szFileName[MAX_PATH];
ASSERTMSG(FALSE, "BitBucket: failed to record deleted file %s , have to nuke it!!", pszOriginal);
LocalFree(pbbdew);
// get the recycled dir and tack on the file name
DriveIDToBBPath(idDrive, szBBPath);
GetDeletedFileNameFromParts(szFileName, idDrive, iIndex, pszOriginal);
PathAppend(szBBPath, szFileName);
// now delete it
BBNuke(szBBPath);
}
}
BOOL BBFinishDelete(HDPA hdpaDeletedFiles)
{
BOOL fSuccess = TRUE; // assume success
int iDeletedFiles = hdpaDeletedFiles ? DPA_GetPtrCount(hdpaDeletedFiles) : 0;
if (iDeletedFiles > 0)
{
int iCurrentFile = 0;
BBDATAENTRYW *pbbdew = (BBDATAENTRYW *)DPA_FastGetPtr(hdpaDeletedFiles, iCurrentFile);
// now write it to the file
int idDrive = pbbdew->idDrive;
HANDLE hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD dwDataEntrySize = g_pBitBucket[idDrive]->fIsUnicode ? sizeof(BBDATAENTRYW) : sizeof(BBDATAENTRYA);
SetFilePointer(hFile, 0, NULL, FILE_END);
while (iCurrentFile < iDeletedFiles)
{
DWORD dwBytesWritten;
pbbdew = (BBDATAENTRYW *)DPA_FastGetPtr(hdpaDeletedFiles, iCurrentFile);
// All deletes should be in the same drive for each batch.
ASSERT(idDrive == pbbdew->idDrive);
if (!WriteFile(hFile, pbbdew, dwDataEntrySize, &dwBytesWritten, NULL) ||
(dwDataEntrySize != dwBytesWritten))
{
fSuccess = FALSE;
break;
}
LocalFree(pbbdew);
iCurrentFile++;
}
CloseBBInfoFile(hFile, idDrive);
}
else
{
fSuccess = FALSE;
}
if (!fSuccess)
{
TCHAR szBBPath[MAX_PATH];
int iFilesToNuke;
for (iFilesToNuke = iCurrentFile; iFilesToNuke < iDeletedFiles; iFilesToNuke++)
{
pbbdew = DPA_FastGetPtr(hdpaDeletedFiles, iFilesToNuke);
GetDeletedFilePath(szBBPath, pbbdew);
// now delete it
BBNuke(szBBPath);
LocalFree(pbbdew);
}
}
if (iCurrentFile != 0)
{
BOOL bPurge = TRUE;
// since we sucessfully deleted a file, we set this so at the end of the last SHFileOperation call on this drive
// we will go back and make sure that there isint too much stuff in the bucket.
RegSetValueEx(g_pBitBucket[idDrive]->hkeyPerUser, TEXT("NeedToPurge"), 0, REG_DWORD, (LPBYTE)&bPurge, sizeof(bPurge));
}
}
if (hdpaDeletedFiles)
DPA_Destroy(hdpaDeletedFiles);
return fSuccess;
}
// creates the proper SECURITY_DESCRIPTOR for securing the recycle bin
//
// NOTE: if return value is non-null, the caller must LocalFree it
//
SECURITY_DESCRIPTOR* CreateRecycleBinSecurityDescriptor()
{
SHELL_USER_PERMISSION supLocalUser;
SHELL_USER_PERMISSION supSystem;
SHELL_USER_PERMISSION supAdministrators;
PSHELL_USER_PERMISSION aPerms[3] = {&supLocalUser, &supSystem, &supAdministrators};
// we want the current user to have full control
supLocalUser.susID = susCurrentUser;
supLocalUser.dwAccessType = ACCESS_ALLOWED_ACE_TYPE;
supLocalUser.dwAccessMask = FILE_ALL_ACCESS;
supLocalUser.fInherit = TRUE;
supLocalUser.dwInheritMask = (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
supLocalUser.dwInheritAccessMask = GENERIC_ALL;
// we want the SYSTEM to have full control
supSystem.susID = susSystem;
supSystem.dwAccessType = ACCESS_ALLOWED_ACE_TYPE;
supSystem.dwAccessMask = FILE_ALL_ACCESS;
supSystem.fInherit = TRUE;
supSystem.dwInheritMask = (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
supSystem.dwInheritAccessMask = GENERIC_ALL;
// we want the Administrators to have full control
supAdministrators.susID = susAdministrators;
supAdministrators.dwAccessType = ACCESS_ALLOWED_ACE_TYPE;
supAdministrators.dwAccessMask = FILE_ALL_ACCESS;
supAdministrators.fInherit = TRUE;
supAdministrators.dwInheritMask = (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE);
supAdministrators.dwInheritAccessMask = GENERIC_ALL;
return GetShellSecurityDescriptor(aPerms, ARRAYSIZE(aPerms));
}
//
// Creates the secure recycle bin directory (eg one with ACL's that protect it)
// for recycle bins on NTFS volumes.
//
BOOL CreateSecureRecyclerDirectory(LPCTSTR pszPath)
{
BOOL fSuccess = FALSE; // assume failure
SECURITY_DESCRIPTOR* psd = CreateRecycleBinSecurityDescriptor();
if (psd)
{
DWORD cbSA = GetSecurityDescriptorLength(psd);
SECURITY_DESCRIPTOR* psdSelfRelative;
psdSelfRelative = LocalAlloc(LPTR, cbSA);
if (psdSelfRelative)
{
if (MakeSelfRelativeSD(psd, psdSelfRelative, &cbSA))
{
SECURITY_ATTRIBUTES sa;
// Build the security attributes structure
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = psdSelfRelative;
sa.bInheritHandle = FALSE;
fSuccess = (SHCreateDirectoryEx(NULL, pszPath, &sa) == ERROR_SUCCESS);
}
LocalFree(psdSelfRelative);
}
LocalFree(psd);
}
return fSuccess;
}
BOOL CreateRecyclerDirectory(int idDrive)
{
TCHAR szPath[MAX_PATH];
TCHAR szRoot[MAX_PATH];
BOOL bResult = FALSE;
BOOL bExists;
// NOTE: we currently do not to check for FAT/FAT32 drives that have been
// upgraded to NTFS and migrate the recycle bin info over
DriveIDToBBPath(idDrive, szPath);
DriveIDToBBRoot(idDrive, szRoot);
bExists = PathIsDirectory(szPath);
if (!bExists)
{
if (CMtPt_IsSecure(idDrive))
{
bExists = CreateSecureRecyclerDirectory(szPath);
}
else
{
bExists = (SHCreateDirectoryEx(NULL, szPath, NULL) == ERROR_SUCCESS);
}
}
if (bExists)
{
PathAppend(szPath, c_szDesktopIni);
// CLSID_RecycleBin
WritePrivateProfileString(STRINI_CLASSINFO, TEXT("CLSID"), TEXT("{645FF040-5081-101B-9F08-00AA002F954E}"), szPath);
SetFileAttributes(szPath, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM); // desktop.ini
PathRemoveFileSpec(szPath);
// Hide all of the directories along the way until we hit the BB root
do
{
SetFileAttributes(szPath, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
PathRemoveFileSpec(szPath);
} while (0 != lstrcmpi(szPath, szRoot));
// everything's set. let's add it in
// try to load the and initalize
bResult = TRUE;
}
return bResult;
}
//
// this sets up the bitbucket directory and allocs the internal structures
//
BOOL MakeBitBucket(int idDrive)
{
BOOL bRet = FALSE;
if (IsBitBucketableDrive(idDrive))
{
if (IsBitBucketInited(idDrive))
{
LONG lBucketDirtyCount = SHGlobalCounterGetValue(g_pBitBucket[idDrive]->hgcDirtyCount);
LONG lGlobalDirtyCount = SHGlobalCounterGetValue(g_hgcGlobalDirtyCount);
// check to see if we need to refresh the settings for this bucket
if (lGlobalDirtyCount > g_lProcessDirtyCount)
{
// global settings change, so refresh all buckets
g_lProcessDirtyCount = lGlobalDirtyCount;
RefreshAllBBDriveSettings();
}
else if (lBucketDirtyCount > g_pBitBucket[idDrive]->lCurrentDirtyCount)
{
// just this buckets settings changed, so refresh only this one
g_pBitBucket[idDrive]->lCurrentDirtyCount = lBucketDirtyCount;
RefreshBBDriveSettings(idDrive);
}
bRet = TRUE;
}
else
{
bRet = AllocBBDriveInfo(idDrive);
}
}
return bRet;
}
// Tells if a file will *likely* be recycled...
// this could be wrong if:
//
// * disk is full
// * file is really a folder
// * file greater than the allocated size for the recycle directory
// * file is in use or no ACLS to move or delete it
//
BOOL BBWillRecycle(LPCTSTR pszFile, INT* piRet)
{
INT iRet = BBDELETE_SUCCESS;
int idDrive = DriveIDFromBBPath(pszFile);
// MakeBitBucket will ensure that the global & per-bucket settings we have are current
if (!MakeBitBucket(idDrive) || g_pBitBucket[idDrive]->fNukeOnDelete || (g_pBitBucket[idDrive]->iPercent == 0))
{
iRet = BBDELETE_FORCE_NUKE;
}
else if (SERVERDRIVE == idDrive)
{
// Check to see if the serverdrive is offline (don't recycle while offline to prevent
// synchronization conflicts when coming back online):
TCHAR szVolume[MAX_PATH];
LONG lStatus;
DriveIDToBBVolumeRoot(idDrive, szVolume);
lStatus = GetOfflineShareStatus(szVolume);
if ((CSC_SHARESTATUS_OFFLINE == lStatus) || (CSC_SHARESTATUS_SERVERBACK == lStatus))
{
iRet = BBDELETE_NUKE_OFFLINE;
}
}
if (piRet)
{
*piRet = iRet;
}
return (BBDELETE_SUCCESS == iRet);
}
//
// This is called at the end of the last pending SHFileOperation that involves deletes.
// We wait till there arent any more people deleteing before we go try to compact the info
// file or purge entries and make the bitbucket respect its cbMaxSize.
//
void CheckCompactAndPurge()
{
int i;
TCHAR szBBKey[MAX_PATH];
HKEY hkBBPerUser;
for (i = 0; i < MAX_BITBUCKETS ; i++)
{
DriveIDToBBRegKey(i, szBBKey);
// NOTE: these function needs to manually construct the key because it would like to avoid calling MakeBitBucket()
// for drives that it has yet to look at (this is a performance optimization)
if (RegOpenKeyEx(g_hkBitBucketPerUser, szBBKey, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &hkBBPerUser) == ERROR_SUCCESS)
{
BOOL bCompact = FALSE;
BOOL bPurge = FALSE;
DWORD dwSize;
dwSize = sizeof(bCompact);
if (RegQueryValueEx(hkBBPerUser, TEXT("NeedToCompact"), NULL, NULL, (LPBYTE)&bCompact, &dwSize) == ERROR_SUCCESS && bCompact == TRUE)
{
// reset this key so no one else tries to compact this bitbucket
RegDeleteValue(hkBBPerUser, TEXT("NeedToCompact"));
}
dwSize = sizeof(bPurge);
if (RegQueryValueEx(hkBBPerUser, TEXT("NeedToPurge"), NULL, NULL, (LPBYTE)&bPurge, &dwSize) == ERROR_SUCCESS && bPurge == TRUE)
{
// reset this key so no one else tries to purge this bitbucket
RegDeleteValue(hkBBPerUser, TEXT("NeedToPurge"));
}
if (MakeBitBucket(i))
{
if (bCompact)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: compacting drive %d",i);
CompactBBInfoFile(i);
}
if (bPurge)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: purging drive %d", i);
PurgeBBFiles(i);
}
}
RegCloseKey(hkBBPerUser);
}
}
SHUpdateRecycleBinIcon();
SHChangeNotify(0, SHCNF_FLUSH | SHCNF_FLUSHNOWAIT, NULL, NULL);
}
// Initialization to call prior to BBDeleteFile
BOOL BBDeleteFileInit(LPTSTR pszFile, INT* piRet)
{
// check to see if we need to init our global data first
if (!InitBBGlobals())
{
// this could happen in low memory situations, we have no choice but
// to really nuke the file
*piRet = BBDELETE_FORCE_NUKE;
return FALSE;
}
if (!BBWillRecycle(pszFile, piRet))
{
// We failed to create the recycler directory on the volume, or
// this is the case where the user has "delete files immeadately" set, or
// % max size=0, etc.
return FALSE;
}
return TRUE;
}
// return:
//
// TRUE - The file/folder was sucessfully moved to the recycle bin. We set lpiReturn = BBDELETE_SUCCESS for this case.
//
// FALSE - The file/folder could not be moved to the recycle bin
// In this case, the piRet value tells WHY the file/folder could not be recycled:
//
// BBDELETE_FORCE_NUKE - User has "delete file immeadately" set, or % max size=0, or we failed to
// create the recycler directory.
//
// BBDELETE_CANNOT_DELETE - The file/folder is non-deletable because a file under it cannot be deleted.
// This is an NT only case, and it could be caused by acls or the fact that the
// folder or a file in it is currently in use.
//
// BBDELETE_SIZE_TOO_BIG - The file/folder is bigger than the max allowable size of the recycle bin.
//
// BBDELETE_PATH_TOO_LONG - The path would be too long ( > MAX_PATH), if the file were to be moved to the
// the recycle bin directory at the root of the drive
//
// BBDELETE_UNKNOWN_ERROR - Some other error occured, GetLastError() should explain why we failed.
//
//
BOOL BBDeleteFile(LPTSTR pszFile, INT* piRet, LPUNDOATOM lpua, BOOL fIsDir, HDPA *phdpaDeletedFiles, ULARGE_INTEGER ulSize)
{
int iRet;
TCHAR szBitBucket[MAX_PATH];
TCHAR szFileName[MAX_PATH];
TCHAR szShortFileName[MAX_PATH];
DWORD dwLastError;
int iIndex;
int idDrive = DriveIDFromBBPath(pszFile);
int iAttempts = 0;
TraceMsg(TF_BITBUCKET, "BBDeleteFile (%s)", pszFile);
// Before we move the file we save off the "short" name. This is in case we have
// a unicode path and we need the ansi short path in case a win95 machine later tries to
// restore this file. We can't do this later because GetShortPathName relies on the
// file actually exising.
if (!GetShortPathName(pszFile, szShortFileName, ARRAYSIZE(szShortFileName)))
{
lstrcpyn(szShortFileName, pszFile, ARRAYSIZE(szShortFileName));
}
TryMoveAgain:
// get the target name and move it
iIndex = SHGlobalCounterIncrement(g_pBitBucket[idDrive]->hgcNextFileNum);
GetDeletedFileNameFromParts(szFileName, idDrive, iIndex, pszFile);
DriveIDToBBPath(idDrive, szBitBucket);
if (PathAppend(szBitBucket, szFileName))
{
iRet = SHMoveFile(pszFile, szBitBucket, fIsDir ? SHCNE_RMDIR : SHCNE_DELETE);
// do GetLastError here so that we don't get the last error from the PathFileExists
dwLastError = (iRet ? ERROR_SUCCESS : GetLastError());
if (!iRet)
{
TraceMsg(TF_BITBUCKET, "BBDeleteFile : Error(%x) moving file (%s)", dwLastError, pszFile);
if (ERROR_ALREADY_EXISTS == dwLastError)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: BBDeleteFile found a file of the same name (%s) - skipping", szBitBucket);
// generate a new filename and retry
goto TryMoveAgain;
}
// Since we are moving files that may be temporarily in use (e.g. for thumbnail extraction)
// we may get a transient error (sharing violation is obvious but we also can get access denied
// for some reason) so we end up trying this again after a short nap.
else if (((ERROR_ACCESS_DENIED == dwLastError) || (ERROR_SHARING_VIOLATION == dwLastError)) &&
(iAttempts < MAX_DELETE_ATTEMPTS))
{
TraceMsg(TF_BITBUCKET, "BBDeleteFile : sleeping a bit to try again");
iAttempts++;
Sleep(SLEEP_DELETE_ATTEMPT); // wait a bit
goto TryMoveAgain;
}
else
{
// is our recycled directory still there?
TCHAR szTemp[MAX_PATH];
SHGetPathFromIDList(g_pBitBucket[idDrive]->pidl, szTemp);
// if it already exists or there was some error in creating it, bail
// else try again
if (!PathIsDirectory(szTemp) && CreateRecyclerDirectory(idDrive))
{
// if we did just re-create the directory, we need to reset the info
// file or the drive will get corrupted.
VerifyBBInfoFileHeader(idDrive);
goto TryMoveAgain;
}
}
}
else
{
// success!
BBAddDeletedFileInfo(pszFile, szShortFileName, iIndex, idDrive, ulSize.LowPart, phdpaDeletedFiles);
if (lpua)
FOUndo_AddInfo(lpua, pszFile, szBitBucket, 0);
*piRet = BBDELETE_SUCCESS;
return TRUE;
}
}
else
{
// if path append fails, it probably means that the path is too long
*piRet = BBDELETE_PATH_TOO_LONG;
return FALSE;
}
// set back the correct last error
SetLastError(dwLastError);
// something bad happened, we dont know what it is
*piRet = BBDELETE_UNKNOWN_ERROR;
return FALSE;
}
// Basically it understands how we the trash is layed out which is fine
// as we are in the bitbucket code file... So we skip the first 3
// characters for the root of the name: c:\ and we truncate off the
// last part of the name and the rest should match our deathrow name...
BOOL IsFileInBitBucket(LPCTSTR pszPath)
{
TCHAR szPath[MAX_PATH];
int idDrive = DriveIDFromBBPath(pszPath);
if (IsBitBucketableDrive(idDrive))
{
DriveIDToBBPath(idDrive, szPath);
return(PathCommonPrefix(szPath, pszPath, NULL) == lstrlen(szPath));
}
return FALSE;
}
//
// This is called by the copy engine when a user selects "undo".
//
// NOTE: takes two multistrings (seperated/double null terminated file lists)
void UndoBBFileDelete(LPCTSTR pszOriginal, LPCTSTR pszDelFile)
{
SHFILEOPSTRUCT sFileOp = {NULL,
FO_MOVE,
pszDelFile,
pszOriginal,
FOF_NOCONFIRMATION | FOF_MULTIDESTFILES | FOF_SIMPLEPROGRESS};
SHFileOperation(&sFileOp);
SHUpdateRecycleBinIcon();
}
STDAPI_(void) SHUpdateRecycleBinIcon()
{
UpdateIcon(!IsRecycleBinEmpty());
}
void PurgeOneBitBucket(HWND hwnd, int idDrive, DWORD dwFlags)
{
TCHAR szPath[MAX_PATH];
HANDLE hFile;
SHFILEOPSTRUCT sFileOp = {hwnd,
FO_DELETE,
szPath,
NULL,
FOF_SIMPLEPROGRESS,
FALSE,
NULL,
MAKEINTRESOURCE(IDS_BB_EMPTYINGWASTEBASKET)};
ASSERT(g_pBitBucket[idDrive] && (g_pBitBucket[idDrive] != (BBSYNCOBJECT *)-1));
if (dwFlags & SHERB_NOCONFIRMATION)
{
sFileOp.fFlags |= FOF_NOCONFIRMATION;
}
if (dwFlags & SHERB_NOPROGRESSUI)
{
sFileOp.fFlags |= FOF_SILENT;
}
DriveIDToBBPath(idDrive, szPath);
PathAppend(szPath, c_szDStarDotStar);
szPath[lstrlen(szPath) + 1] = 0; // double null terminate
hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
if (INVALID_HANDLE_VALUE != hFile)
{
// now do the actual delete.
if (SHFileOperation(&sFileOp) || sFileOp.fAnyOperationsAborted)
{
TraceMsg(TF_BITBUCKET, "Bitbucket: emptying bucket on %s failed", szPath);
// NOTE: the info file may point to some files that have been deleted,
// it will be cleaned up later
}
else
{
// reset the info file since we just emptied this bucket.
ResetInfoFileHeader(hFile, g_pBitBucket[idDrive]->fIsUnicode);
}
// we always re-create the desktop.ini
CreateRecyclerDirectory(idDrive);
CloseBBInfoFile(hFile, idDrive);
}
SHUpdateRecycleBinIcon();
}
// This function checks to see if an local NT directory is delete-able
//
// returns:
// TRUE yes, the dir can be nuked
// FALSE for UNC dirs or dirs on network drives, or
// if the user does not have enough privlidges
// to delete the file (acls).
//
// NOTE: this code is largely stolen from the RemoveDirectoryW API (windows\base\client\dir.c). if
// you think that there is a bug in it, then diff it against the DeleteFileW and see it something
// there changed.
//
// also sets the last error to explain why
//
BOOL IsDirectoryDeletable(LPCTSTR pszDir)
{
NTSTATUS Status;
OBJECT_ATTRIBUTES Obja;
HANDLE Handle;
UNICODE_STRING FileName;
IO_STATUS_BLOCK IoStatusBlock;
FILE_DISPOSITION_INFORMATION Disposition;
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
BOOLEAN TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
void *FreeBuffer;
DWORD dwAttributes;
BOOL fChangedAttribs = FALSE;
// return false for any network drives (allow UNC)
if (IsNetDrive(PathGetDriveNumber(pszDir)))
{
return FALSE;
}
if (PathIsUNC(pszDir) && PathIsDirectoryEmpty(pszDir))
{
// HACKACK - (reinerf) the rdr will nuke the file when we call
// NtSetInformationFile to set the deleted bit on an empty directory even
// though we pass READ_CONTROL and we still have a handle to the object.
// So, to work around this, we just assume we can always delete empty
// directories (ha!)
return TRUE;
}
// check to see if the dir is readonly
dwAttributes = GetFileAttributes(pszDir);
if ((dwAttributes != -1) && (dwAttributes & FILE_ATTRIBUTE_READONLY))
{
fChangedAttribs = TRUE;
if (!SetFileAttributes(pszDir, dwAttributes & ~FILE_ATTRIBUTE_READONLY))
{
return FALSE;
}
}
TranslationStatus = RtlDosPathNameToNtPathName_U(pszDir,
&FileName,
NULL,
&RelativeName);
if (!TranslationStatus)
{
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszDir, dwAttributes);
}
SetLastError(ERROR_PATH_NOT_FOUND);
return FALSE;
}
FreeBuffer = FileName.Buffer;
if (RelativeName.RelativeName.Length)
{
FileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
}
else
{
RelativeName.ContainingDirectory = NULL;
}
InitializeObjectAttributes(&Obja,
&FileName,
OBJ_CASE_INSENSITIVE,
RelativeName.ContainingDirectory,
NULL);
//
// Open the directory for delete access.
// Inhibit the reparse behavior using FILE_OPEN_REPARSE_POINT.
//
Status = NtOpenFile(&Handle,
DELETE | SYNCHRONIZE | FILE_READ_ATTRIBUTES | READ_CONTROL,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
if (!NT_SUCCESS(Status))
{
//
// Back level file systems may not support reparse points and thus not
// support symbolic links.
// We infer this is the case when the Status is STATUS_INVALID_PARAMETER.
//
if (Status == STATUS_INVALID_PARAMETER)
{
//
// Re-open not inhibiting the reparse behavior and not needing to read the attributes.
//
Status = NtOpenFile(&Handle,
DELETE | SYNCHRONIZE | READ_CONTROL,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT);
if (!NT_SUCCESS(Status))
{
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszDir, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
}
else
{
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszDir, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
}
else
{
//
// If we found a reparse point that is not a name grafting operation,
// either a symbolic link or a mount point, we re-open without
// inhibiting the reparse behavior.
//
Status = NtQueryInformationFile(Handle,
&IoStatusBlock,
(void *) &FileTagInformation,
sizeof(FileTagInformation),
FileAttributeTagInformation);
if (!NT_SUCCESS(Status))
{
//
// Not all File Systems implement all information classes.
// The value STATUS_INVALID_PARAMETER is returned when a non-supported
// information class is requested to a back-level File System. As all the
// parameters to NtQueryInformationFile are correct, we can infer that
// we found a back-level system.
//
// If FileAttributeTagInformation is not implemented, we assume that
// the file at hand is not a reparse point.
//
if ((Status != STATUS_NOT_IMPLEMENTED) && (Status != STATUS_INVALID_PARAMETER))
{
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
NtClose(Handle);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszDir, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
}
if (NT_SUCCESS(Status) && (FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
{
if (FileTagInformation.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
//
// We want to make sure that we return FALSE for mounted volumes. This will cause BBDeleteFile
// to return BBDELETE_CANNOT_DELETE so that we will actuall delete the mountpoint and not try to
// move the mount point to the recycle bin or walk into it.
//
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
NtClose(Handle);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszDir, dwAttributes);
}
// hmmm... lets pull ERROR_NOT_A_REPARSE_POINT out of our butt and return that error code!
SetLastError(ERROR_NOT_A_REPARSE_POINT);
return FALSE;
}
}
if (NT_SUCCESS(Status) && (FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
{
//
// Re-open without inhibiting the reparse behavior and not needing to
// read the attributes.
//
NtClose(Handle);
Status = NtOpenFile(&Handle,
DELETE | SYNCHRONIZE | READ_CONTROL,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT);
if (!NT_SUCCESS(Status))
{
//
// When the FS Filter is absent, delete it any way.
//
if (Status == STATUS_IO_REPARSE_TAG_NOT_HANDLED)
{
//
// We re-open (possible 3rd open) for delete access inhibiting the reparse behavior.
//
Status = NtOpenFile(&Handle,
DELETE | SYNCHRONIZE | READ_CONTROL,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
}
if (!NT_SUCCESS(Status))
{
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszDir, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
}
}
}
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
//
// Attempt to set the delete bit
//
#undef DeleteFile
Disposition.DeleteFile = TRUE;
Status = NtSetInformationFile(Handle,
&IoStatusBlock,
&Disposition,
sizeof(Disposition),
FileDispositionInformation);
if (NT_SUCCESS(Status))
{
//
// yep, we were able to set the bit, now unset it so its not delted!
//
Disposition.DeleteFile = FALSE;
Status = NtSetInformationFile(Handle,
&IoStatusBlock,
&Disposition,
sizeof(Disposition),
FileDispositionInformation);
NtClose(Handle);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszDir, dwAttributes);
}
return TRUE;
}
else
{
//
// nope couldnt set the del bit. can't be deleted
//
TraceMsg(TF_BITBUCKET, "IsDirectoryDeletable: NtSetInformationFile failed, status=0x%08x", Status);
NtClose(Handle);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszDir, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
return TRUE;
}
// This function checks to see if a local NT file is delete-able
//
// returns:
// TRUE yes, the file can be nuked
// FALSE for UNC files or files on network drives
// FALSE if the file is in use
//
// NOTE: this code is largely stolen from the DeleteFileW API (windows\base\client\filemisc.c). if
// you think that there is a bug in it, then diff it against the DeleteFileW and see it something
// there changed.
//
// also sets the last error to explain why
//
BOOL IsFileDeletable(LPCTSTR pszFile)
{
NTSTATUS Status;
OBJECT_ATTRIBUTES Obja;
HANDLE Handle;
UNICODE_STRING FileName;
IO_STATUS_BLOCK IoStatusBlock;
FILE_DISPOSITION_INFORMATION Disposition;
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
BOOLEAN TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
void *FreeBuffer;
BOOLEAN fIsSymbolicLink = FALSE;
DWORD dwAttributes;
BOOL fChangedAttribs = FALSE;
// return false for any network drives
if (IsNetDrive(PathGetDriveNumber(pszFile)))
{
return FALSE;
}
// check to see if the file is readonly or system
dwAttributes = GetFileAttributes(pszFile);
if (dwAttributes != -1)
{
if (dwAttributes & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
{
fChangedAttribs = TRUE;
if (!SetFileAttributes(pszFile, dwAttributes & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
{
return FALSE;
}
}
}
TranslationStatus = RtlDosPathNameToNtPathName_U(pszFile,
&FileName,
NULL,
&RelativeName);
if (!TranslationStatus)
{
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszFile, dwAttributes);
}
SetLastError(ERROR_PATH_NOT_FOUND);
return FALSE;
}
FreeBuffer = FileName.Buffer;
if (RelativeName.RelativeName.Length)
{
FileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
}
else
{
RelativeName.ContainingDirectory = NULL;
}
InitializeObjectAttributes(&Obja,
&FileName,
OBJ_CASE_INSENSITIVE,
RelativeName.ContainingDirectory,
NULL);
// Open the file for delete access
Status = NtOpenFile(&Handle,
(ACCESS_MASK)DELETE | FILE_READ_ATTRIBUTES | READ_CONTROL,
&Obja,
&IoStatusBlock,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
if (!NT_SUCCESS(Status))
{
//
// Back level file systems may not support reparse points and thus not
// support symbolic links.
// We infer this is the case when the Status is STATUS_INVALID_PARAMETER.
//
if (Status == STATUS_INVALID_PARAMETER)
{
//
// Open without inhibiting the reparse behavior and not needing to
// read the attributes.
//
Status = NtOpenFile(&Handle,
(ACCESS_MASK)DELETE | READ_CONTROL,
&Obja,
&IoStatusBlock,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT);
if (!NT_SUCCESS(Status))
{
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszFile, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
}
else
{
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszFile, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
}
else
{
//
// If we found a reparse point that is not a symbolic link, we re-open
// without inhibiting the reparse behavior.
//
Status = NtQueryInformationFile(Handle,
&IoStatusBlock,
(void *) &FileTagInformation,
sizeof(FileTagInformation),
FileAttributeTagInformation);
if (!NT_SUCCESS(Status))
{
//
// Not all File Systems implement all information classes.
// The value STATUS_INVALID_PARAMETER is returned when a non-supported
// information class is requested to a back-level File System. As all the
// parameters to NtQueryInformationFile are correct, we can infer that
// we found a back-level system.
//
// If FileAttributeTagInformation is not implemented, we assume that
// the file at hand is not a reparse point.
//
if ((Status != STATUS_NOT_IMPLEMENTED) && (Status != STATUS_INVALID_PARAMETER))
{
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
NtClose(Handle);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszFile, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
}
if (NT_SUCCESS(Status) && (FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
{
if (FileTagInformation.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
fIsSymbolicLink = TRUE;
}
}
if (NT_SUCCESS(Status) &&
(FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
!fIsSymbolicLink)
{
//
// Re-open without inhibiting the reparse behavior and not needing to
// read the attributes.
//
NtClose(Handle);
Status = NtOpenFile(&Handle,
(ACCESS_MASK)DELETE | READ_CONTROL,
&Obja,
&IoStatusBlock,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT);
if (!NT_SUCCESS(Status))
{
//
// When the FS Filter is absent, delete it any way.
//
if (Status == STATUS_IO_REPARSE_TAG_NOT_HANDLED)
{
//
// We re-open (possible 3rd open) for delete access inhibiting the reparse behavior.
//
Status = NtOpenFile(&Handle,
(ACCESS_MASK)DELETE | READ_CONTROL,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
}
if (!NT_SUCCESS(Status))
{
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszFile, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
}
}
}
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
//
// Attempt to set the delete bit
//
#undef DeleteFile
Disposition.DeleteFile = TRUE;
Status = NtSetInformationFile(Handle,
&IoStatusBlock,
&Disposition,
sizeof(Disposition),
FileDispositionInformation);
if (NT_SUCCESS(Status))
{
//
// yep, we were able to set the bit, now unset it so its not delted!
//
Disposition.DeleteFile = FALSE;
Status = NtSetInformationFile(Handle,
&IoStatusBlock,
&Disposition,
sizeof(Disposition),
FileDispositionInformation);
NtClose(Handle);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszFile, dwAttributes);
}
return TRUE;
}
else
{
//
// nope couldnt set the del bit. can't be deleted
//
TraceMsg(TF_BITBUCKET, "IsFileDeletable: NtSetInformationFile failed, status=0x%08x", Status);
NtClose(Handle);
if (fChangedAttribs)
{
// set the attribs back
SetFileAttributes(pszFile, dwAttributes);
}
SetLastError(RtlNtStatusToDosError(Status));
return FALSE;
}
return TRUE;
}
BOOL BBCheckDeleteFileSize(int idDrive, ULARGE_INTEGER ulSize)
{
return (!ulSize.HighPart && g_pBitBucket[idDrive]->cbMaxSize > ulSize.LowPart);
}
int BBRecyclePathLength(int idDrive)
{
return g_pBitBucket[idDrive]->cchBBDir;
}