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

975 lines
29 KiB

#include "common.h"
#include <emptyvc.h>
#ifndef COMPCLEN_H
#include "compclen.h"
#endif
#include <regstr.h>
#include <olectl.h>
#include <tlhelp32.h>
#ifndef RESOURCE_H
#include "resource.h"
#endif
#ifdef DEBUG
#include <stdio.h>
#endif // DEBUG
BOOL g_bSettingsChange = FALSE;
const LPCTSTR g_NoCompressFiles[] =
{
TEXT("NTLDR"),
TEXT("OSLOADER.EXE"),
TEXT("PAGEFILE.SYS"),
TEXT("NTDETECT.COM"),
TEXT("EXPLORER.EXE"),
};
LPCTSTR g_NoCompressExts[] =
{
TEXT(".PAL")
};
extern HINSTANCE g_hDllModule;
extern UINT incDllObjectCount(void);
extern UINT decDllObjectCount(void);
CCompCleanerClassFactory::CCompCleanerClassFactory() : m_cRef(1)
{
incDllObjectCount();
}
CCompCleanerClassFactory::~CCompCleanerClassFactory()
{
decDllObjectCount();
}
STDMETHODIMP CCompCleanerClassFactory::QueryInterface(REFIID riid, void **ppv)
{
*ppv = NULL;
if (IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_IClassFactory))
{
*ppv = (IClassFactory *)this;
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CCompCleanerClassFactory::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CCompCleanerClassFactory::Release()
{
if (--m_cRef)
return m_cRef;
delete this;
return 0;
}
STDMETHODIMP CCompCleanerClassFactory::CreateInstance(IUnknown * pUnkOuter, REFIID riid, void **ppvObj)
{
*ppvObj = NULL;
if (pUnkOuter)
{
return CLASS_E_NOAGGREGATION;
}
HRESULT hr;
CCompCleaner *pCompCleaner = new CCompCleaner();
if (pCompCleaner)
{
hr = pCompCleaner->QueryInterface (riid, ppvObj);
pCompCleaner->Release();
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
STDMETHODIMP CCompCleanerClassFactory::LockServer(BOOL fLock)
{
if (fLock)
incDllObjectCount();
else
decDllObjectCount();
return S_OK;
}
CCompCleaner::CCompCleaner() : m_cRef(1)
{
cbSpaceUsed.QuadPart = 0;
cbSpaceFreed.QuadPart = 0;
szVolume[0] = 0;
szFolder[0] = 0;
incDllObjectCount();
}
CCompCleaner::~CCompCleaner()
{
// Free the list of directories
FreeList(head);
head = NULL;
decDllObjectCount();
}
STDMETHODIMP CCompCleaner::QueryInterface(REFIID riid, void **ppv)
{
*ppv = NULL;
if (IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_IEmptyVolumeCache) ||
IsEqualIID(riid, IID_IEmptyVolumeCache2))
{
*ppv = (IEmptyVolumeCache2*) this;
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CCompCleaner::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CCompCleaner::Release()
{
if (--m_cRef)
return m_cRef;
delete this;
return 0;
}
// Initializes the Compression Cleaner and returns flags to the cache manager
STDMETHODIMP CCompCleaner::Initialize(HKEY hRegKey,
LPCWSTR pszVolume,
LPWSTR *ppwszDisplayName,
LPWSTR *ppwszDescription,
DWORD *pdwFlags)
{
TCHAR szFileSystemName[MAX_PATH];
DWORD fFileSystemFlags;
bPurged = FALSE;
//
// Allocate memory for the DisplayName string and load the string.
// If the allocation fails, then we will return NULL which will cause
// cleanmgr.exe to read the name from the registry.
//
if (*ppwszDisplayName = (LPWSTR)CoTaskMemAlloc(DISPLAYNAME_LENGTH * sizeof(WCHAR)))
{
LoadString(g_hDllModule, IDS_COMPCLEANER_DISP, *ppwszDisplayName, DISPLAYNAME_LENGTH);
}
//
// Allocate memory for the Description string and load the string.
// If the allocation fails, then we will return NULL which will cause
// cleanmgr.exe to read the description from the registry.
//
if (*ppwszDescription = (LPWSTR)CoTaskMemAlloc(DESCRIPTION_LENGTH * sizeof(WCHAR)))
{
LoadString(g_hDllModule, IDS_COMPCLEANER_DESC, *ppwszDescription, DESCRIPTION_LENGTH);
}
//
//If you want your cleaner to run only when the machine is dangerously low on
//disk space then return S_FALSE unless the EVCF_OUTOFDISKSPACE flag is set.
//
//if (!(*pdwFlags & EVCF_OUTOFDISKSPACE))
//{
// return S_FALSE;
//}
if (*pdwFlags & EVCF_SETTINGSMODE)
{
bSettingsMode = TRUE;
}
else
{
bSettingsMode = FALSE;
}
//Tell the cache manager to disable this item by default
*pdwFlags = 0;
//Tell the Disk Cleanup Manager that we have a Settings button
*pdwFlags |= EVCF_HASSETTINGS;
// If we're in Settings mode no need to do all this other work
//
if (bSettingsMode)
return S_OK;
ftMinLastAccessTime.dwLowDateTime = 0;
ftMinLastAccessTime.dwHighDateTime = 0;
if (GetVolumeInformation(pszVolume, NULL, 0, NULL, NULL, &fFileSystemFlags, szFileSystemName, MAX_PATH) &&
(0 == lstrcmp(szFileSystemName, TEXT("NTFS"))) &&
(fFileSystemFlags & FS_FILE_COMPRESSION))
{
StringCchCopy(szFolder, ARRAYSIZE(szFolder), pszVolume);
// Calculate the last access date filetime
CalcLADFileTime();
return S_OK;
}
return S_FALSE;
}
STDMETHODIMP CCompCleaner::InitializeEx(HKEY hRegKey, LPCWSTR pcwszVolume, LPCWSTR pcwszKeyName,
LPWSTR *ppwszDisplayName, LPWSTR *ppwszDescription,
LPWSTR *ppwszBtnText, DWORD *pdwFlags)
{
// Allocate memory for the ButtonText string and load the string.
// If we can't allocate the memory, leave the pointer NULL.
if (*ppwszBtnText = (LPWSTR)CoTaskMemAlloc(BUTTONTEXT_LENGTH * sizeof(WCHAR)))
{
LoadString(g_hDllModule, IDS_COMPCLEANER_BUTTON, *ppwszBtnText, BUTTONTEXT_LENGTH);
}
//
// Now let the IEmptyVolumeCache version 1 Init function do the rest
//
return Initialize(hRegKey, pcwszVolume, ppwszDisplayName, ppwszDescription, pdwFlags);
}
// Returns the total amount of space that the compression cleaner can free.
STDMETHODIMP CCompCleaner::GetSpaceUsed(DWORDLONG *pdwSpaceUsed, IEmptyVolumeCacheCallBack *picb)
{
cbSpaceUsed.QuadPart = 0;
WalkFileSystem(picb, FALSE);
picb->ScanProgress(cbSpaceUsed.QuadPart, EVCCBF_LASTNOTIFICATION, NULL);
*pdwSpaceUsed = cbSpaceUsed.QuadPart;
return S_OK;
}
// does the compression
STDMETHODIMP CCompCleaner::Purge(DWORDLONG dwSpaceToFree, IEmptyVolumeCacheCallBack *picb)
{
bPurged = TRUE;
// Compress the files
WalkFileSystem(picb, TRUE);
// Send the last notification to the cleanup manager
picb->PurgeProgress(cbSpaceFreed.QuadPart, (cbSpaceUsed.QuadPart - cbSpaceFreed.QuadPart), EVCCBF_LASTNOTIFICATION, NULL);
return S_OK;
}
/*
** Dialog box that displays all of the files that will be compressed by this cleaner.
**
** NOTE: Per the specification for the Compression Cleaner we are not
** providing the view files functionality. However, I will leave
** the framework in place just in case we want to use it.
*/
INT_PTR CALLBACK ViewFilesDlgProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
HWND hwndList;
LV_ITEM lviItem;
CLEANFILESTRUCT *pCleanFile;
switch (Msg)
{
case WM_INITDIALOG:
hwndList = GetDlgItem(hDlg, IDC_COMP_LIST);
pCleanFile = (CLEANFILESTRUCT *)lParam;
ListView_DeleteAllItems(hwndList);
while (pCleanFile)
{
lviItem.mask = LVIF_TEXT | LVIF_IMAGE;
lviItem.iSubItem = 0;
lviItem.iItem = 0;
//
//Only show files
//
if (!pCleanFile->bDirectory)
{
lviItem.pszText = pCleanFile->file;
ListView_InsertItem(hwndList, &lviItem);
}
pCleanFile = pCleanFile->pNext;
lviItem.iItem++;
}
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
case IDCANCEL:
EndDialog(hDlg, 0);
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
// Dialog box that displays the settings for this cleaner.
INT_PTR CALLBACK SettingsDlgProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
HKEY hCompClenReg; // Handle to our registry path
DWORD dwDisposition; // stuff for the reg calls
DWORD dwByteCount; // Ditto
DWORD dwNumDays = DEFAULT_DAYS; // Number of days setting from registry
static UINT DaysIn; // Number of days initial setting
UINT DaysOut; // Number of days final setting
#ifdef DEBUG
static CLEANFILESTRUCT *pCleanFile; // Pointer to our file list
#endif // DEBUG
switch(Msg) {
case WM_INITDIALOG:
#ifdef DEBUG
pCleanFile = (CLEANFILESTRUCT *)lParam;
#endif // DEBUG
//
// Set the range for the Days spin control (1 to 500)
//
SendDlgItemMessage(hDlg, IDC_COMP_SPIN, UDM_SETRANGE, 0, (LPARAM) MAKELONG ((short) MAX_DAYS, (short) MIN_DAYS));
//
// Get the current user setting for # days and init
// the spin control edit box
//
if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, COMPCLN_REGPATH,
0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ, NULL,
&hCompClenReg, &dwDisposition))
{
dwByteCount = sizeof(dwNumDays);
if (ERROR_SUCCESS == RegQueryValueEx(hCompClenReg,
TEXT("Days"), NULL, NULL, (LPBYTE) &dwNumDays, &dwByteCount))
{
//
// Got day count from registry, make sure it's
// not too big or too small.
//
if (dwNumDays > MAX_DAYS) dwNumDays = MAX_DAYS;
if (dwNumDays < MIN_DAYS) dwNumDays = MIN_DAYS;
SetDlgItemInt(hDlg, IDC_COMP_EDIT, dwNumDays, FALSE);
}
else
{
//
// Failed to get the day count from the registry
// so just use the default.
//
SetDlgItemInt(hDlg, IDC_COMP_EDIT, DEFAULT_DAYS, FALSE);
}
}
else
{
//
// Failed to get the day count from the registry
// so just use the default.
//
SetDlgItemInt(hDlg, IDC_COMP_EDIT, DEFAULT_DAYS, FALSE);
}
RegCloseKey(hCompClenReg);
// Track the initial setting so we can figure out
// if the user has changed the setting on the way
// out.
DaysIn = GetDlgItemInt(hDlg, IDC_COMP_EDIT, NULL, FALSE);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
#ifdef DEBUG
case IDC_VIEW:
DialogBoxParam(g_hDllModule, MAKEINTRESOURCE(IDD_COMP_VIEW), hDlg, ViewFilesDlgProc, (LPARAM)pCleanFile);
break;
#endif // DEBUG
case IDOK:
//
// Get the current spin control value and write the
// setting to the registry.
//
DaysOut = GetDlgItemInt(hDlg, IDC_COMP_EDIT, NULL, FALSE);
if (DaysOut > MAX_DAYS) DaysOut = MAX_DAYS;
if (DaysOut < MIN_DAYS) DaysOut = MIN_DAYS;
if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE,
COMPCLN_REGPATH,
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
NULL,
&hCompClenReg,
&dwDisposition))
{
dwNumDays = (DWORD)DaysOut;
RegSetValueEx(hCompClenReg,
TEXT("Days"),
0,
REG_DWORD,
(LPBYTE) &dwNumDays,
sizeof(dwNumDays));
RegCloseKey(hCompClenReg);
}
// Don't care if this failed -- what can we
// do about it anyway...
// If the user has changed the setting we need
// to recalculate the list of files.
if (DaysIn != DaysOut)
{
g_bSettingsChange = TRUE;
}
// Fall thru to IDCANCEL
case IDCANCEL:
EndDialog(hDlg, 0);
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
STDMETHODIMP CCompCleaner::ShowProperties(HWND hwnd)
{
g_bSettingsChange = FALSE;
DialogBoxParam(g_hDllModule, MAKEINTRESOURCE(IDD_COMP_SETTINGS), hwnd, SettingsDlgProc, (LPARAM)head);
//
// If the settings have changed we need to recalculate the
// LAD Filetime.
//
if (g_bSettingsChange)
{
CalcLADFileTime();
return S_OK; // Tell CleanMgr that settings have changed.
}
else
{
return S_FALSE; // Tell CleanMgr no settings changed.
}
return S_OK; // Shouldn't hit this but just in case.
}
// Deactivates the cleaner...this basically does nothing.
STDMETHODIMP CCompCleaner::Deactivate(DWORD *pdwFlags)
{
*pdwFlags = 0;
return S_OK;
}
/*
** checks if the file is a specified number of days
** old. If the file has not been accessed in the
** specified number of days then it can safely be
** deleted. If the file has been accessed in that
** number of days then the file will not be deleted.
**
** Notes;
** Mod Log: Created by Jason Cobb (7/97)
** Adapted for Compression Cleaner by DSchott (6/98)
*/
BOOL CCompCleaner::LastAccessisOK(FILETIME ftFileLastAccess)
{
//Is the last access FILETIME for this file less than the current
//FILETIME minus the number of specified days?
return (CompareFileTime(&ftFileLastAccess, &ftMinLastAccessTime) == -1);
}
// This function checks if the file is in the g_NoCompressFiles file list.
// If it is, returns TRUE, else FALSE.
BOOL IsDontCompressFile(LPCTSTR lpFullPath)
{
LPCTSTR lpFile = PathFindFileName(lpFullPath);
if (lpFile)
{
for (int i = 0; i < ARRAYSIZE(g_NoCompressFiles); i++)
{
if (!lstrcmpi(lpFile, g_NoCompressFiles[i]))
{
MiDebugMsg((0, TEXT("File is in No Compress list: %s"), lpFile));
return TRUE;
}
}
LPCTSTR lpExt = PathFindExtension(lpFile);
if (lpExt)
{
for (int i = 0; i < ARRAYSIZE(g_NoCompressExts); i++)
{
if (!lstrcmpi(lpExt, g_NoCompressExts[i]))
{
MiDebugMsg((0, TEXT("File has No Compress extension: %s"), lpFile));
return TRUE;
}
}
}
}
return FALSE; // If we made it here the file must be OK to compress.
}
/*
** checks if a file is open by doing a CreateFile
** with fdwShareMode of 0. If GetLastError() retuns
** ERROR_SHARING_VIOLATION then this function retuns TRUE because
** someone has the file open. Otherwise this function retuns false.
**
** Notes;
** Mod Log: Created by Jason Cobb (7/97)
** Adapted for Compression Cleaner by DSchott (6/98)
**------------------------------------------------------------------------------
*/
BOOL IsFileOpen(LPTSTR lpFile, DWORD dwAttributes, FILETIME *lpftFileLastAccess)
{
BOOL bRet = FALSE;
#if 0
// Need to see if we can open file with WRITE access -- if we
// can't we can't compress it. Of course if the file has R/O
// attribute then we won't be able to open for WRITE. So,
// we need to remove the R/O attribute long enough to try
// opening the file then restore the original attributes.
if (dwAttributes & FILE_ATTRIBUTE_READONLY)
{
SetFileAttributes(lpFile, FILE_ATTRIBUTE_NORMAL);
}
SetLastError(0);
HANDLE hFile = CreateFile(lpFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
DWORD dwResult = GetLastError();
if ((ERROR_SHARING_VIOLATION == dwResult) || (ERROR_ACCESS_DENIED == dwResult))
{
bRet = TRUE;
}
}
else
{
SetFileTime(hFile, NULL, lpftFileLastAccess, NULL);
CloseHandle(hFile);
}
if (dwAttributes & FILE_ATTRIBUTE_READONLY)
SetFileAttributes(lpFile, dwAttributes);
#endif
return bRet;
}
/*
**
** Purpose: This function provides a common entry point for
** searching for files to compress and then compressing them
**
** Notes;
** Mod Log: Created by Bret Anderson (1/01)
**
**------------------------------------------------------------------------------
*/
void CCompCleaner::WalkFileSystem(IEmptyVolumeCacheCallBack *picb, BOOL bCompress)
{
MiDebugMsg((0, TEXT("CCompCleaner::WalkFileSystem")));
cbSpaceUsed.QuadPart = 0;
if (!bCompress)
{
//
//Walk all of the folders in the folders list scanning for disk space.
//
for (LPTSTR lpSingleFolder = szFolder; *lpSingleFolder; lpSingleFolder += lstrlen(lpSingleFolder) + 1)
WalkForUsedSpace(lpSingleFolder, picb, bCompress, 0);
}
else
{
//
// Walk through the linked list of directories compressing the necessary files
//
CLEANFILESTRUCT *pCompDir = head;
while (pCompDir)
{
WalkForUsedSpace(pCompDir->file, picb, bCompress, 0);
pCompDir = pCompDir->pNext;
}
}
return;
}
/*
** Purpose: This function gets the current last access days
** setting from the registry and calculates the magic
** filetime we're looking for when searching for files
** to compress.
**
** Notes;
** Mod Log: Created by David Schott (7/98)
*/
void CCompCleaner::CalcLADFileTime()
{
HKEY hCompClenReg = NULL; // Handle to our registry path
DWORD dwDisposition; // stuff for the reg calls
DWORD dwByteCount; // Ditto
DWORD dwDaysLastAccessed = 0; // Day count from the registry setting
MiDebugMsg((0, TEXT("CCompCleaner::CalcLADFileTime")));
//
// Get the DaysLastAccessed value from the registry.
//
dwDaysLastAccessed = DEFAULT_DAYS;
if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, COMPCLN_REGPATH,
0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ,
NULL, &hCompClenReg, &dwDisposition))
{
dwByteCount = sizeof(dwDaysLastAccessed);
RegQueryValueEx(hCompClenReg,
TEXT("Days"),
NULL,
NULL,
(LPBYTE) &dwDaysLastAccessed,
&dwByteCount);
RegCloseKey(hCompClenReg);
}
//
// Verify LD setting is within range
//
if (dwDaysLastAccessed > MAX_DAYS)
dwDaysLastAccessed = MAX_DAYS;
if (dwDaysLastAccessed < MIN_DAYS)
dwDaysLastAccessed = MIN_DAYS;
//
//Determine the LastAccessedTime
//
if (dwDaysLastAccessed != 0)
{
ULARGE_INTEGER ulTemp, ulLastAccessTime;
FILETIME ft;
//Determine the number of days in 100ns units
ulTemp.LowPart = FILETIME_HOUR_LOW;
ulTemp.HighPart = FILETIME_HOUR_HIGH;
ulTemp.QuadPart *= dwDaysLastAccessed;
//Get the current FILETIME
GetSystemTimeAsFileTime(&ft);
ulLastAccessTime.LowPart = ft.dwLowDateTime;
ulLastAccessTime.HighPart = ft.dwHighDateTime;
//Subtract the Last Access number of days (in 100ns units) from
//the current system time.
ulLastAccessTime.QuadPart -= ulTemp.QuadPart;
//Save this minimal Last Access time in the FILETIME member variable
//ftMinLastAccessTime.
ftMinLastAccessTime.dwLowDateTime = ulLastAccessTime.LowPart;
ftMinLastAccessTime.dwHighDateTime = ulLastAccessTime.HighPart;
}
}
/*
** Purpose: This function will walk the specified directory and increment
** the member variable to indicate how much disk space these files
** are taking or it will perform the action of compressing the files
** if the bCompress variable is set.
** It will look at the dwFlags member variable to determine if it
** needs to recursively walk the tree or not.
** We no longer want to store a linked list of all files to compress
** due to extreme memory usage on large filesystems. This means
** we will walk through all the files on the system twice.
** Notes;
** Mod Log: Created by Jason Cobb (2/97)
** Adapted for Compression Cleaner by DSchott (6/98)
*/
BOOL CCompCleaner::WalkForUsedSpace(LPCTSTR lpPath, IEmptyVolumeCacheCallBack *picb, BOOL bCompress, int depth)
{
BOOL bRet = TRUE;
BOOL bFind = TRUE;
WIN32_FIND_DATA wd;
ULARGE_INTEGER dwFileSize;
static DWORD dwCount = 0;
TCHAR szFindPath[MAX_PATH], szAddFile[MAX_PATH];
if (PathCombine(szFindPath, lpPath, TEXT("*.*")))
{
BOOL bFolderFound = FALSE;
bFind = TRUE;
HANDLE hFind = FindFirstFile(szFindPath, &wd);
while (hFind != INVALID_HANDLE_VALUE && bFind)
{
if (!PathCombine(szAddFile, lpPath, wd.cFileName))
{
// Failure here means the file name is too long, just ignore that file
continue;
}
if (wd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
dwFileSize.HighPart = 0;
dwFileSize.LowPart = 0;
bFolderFound = TRUE;
}
else if ((IsFileOpen(szAddFile, wd.dwFileAttributes, &wd.ftLastAccessTime) == FALSE) &&
(!(wd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)) &&
(!(wd.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED)) &&
(!(wd.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE)) &&
(LastAccessisOK(wd.ftLastAccessTime)) &&
(!IsDontCompressFile(szAddFile)))
{
dwFileSize.HighPart = wd.nFileSizeHigh;
dwFileSize.LowPart = wd.nFileSizeLow;
if (bCompress)
{
if (!CompressFile(picb, szAddFile, dwFileSize))
{
bRet = FALSE;
bFind = FALSE;
break;
}
}
else
{
cbSpaceUsed.QuadPart += (dwFileSize.QuadPart * 4 / 10);
}
}
// CallBack the cleanup Manager to update the UI
if ((dwCount++ % 10) == 0 && !bCompress)
{
if (picb && picb->ScanProgress(cbSpaceUsed.QuadPart, 0, NULL) == E_ABORT)
{
//
//User aborted
//
bFind = FALSE;
bRet = FALSE;
break;
}
}
bFind = FindNextFile(hFind, &wd);
}
FindClose(hFind);
if (bRet && bFolderFound)
{
//
//Recurse through all of the directories
//
if (PathCombine(szFindPath, lpPath, TEXT("*.*")))
{
bFind = TRUE;
HANDLE hFind = FindFirstFile(szFindPath, &wd);
while (hFind != INVALID_HANDLE_VALUE && bFind)
{
if ((wd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
(lstrcmp(wd.cFileName, TEXT(".")) != 0) &&
(lstrcmp(wd.cFileName, TEXT("..")) != 0))
{
ULARGE_INTEGER cbSpaceBefore;
cbSpaceBefore.QuadPart = cbSpaceUsed.QuadPart;
PathCombine(szAddFile, lpPath, wd.cFileName);
if (WalkForUsedSpace(szAddFile, picb, bCompress, depth + 1) == FALSE)
{
// User canceled
bFind = FALSE;
bRet = FALSE;
break;
}
// Tag this directory for compression
// We only want to tag directories that are in the root
// otherwise we'll end up with a very large data structure
if (cbSpaceBefore.QuadPart != cbSpaceUsed.QuadPart &&
depth == 0 && !bCompress)
{
AddDirToList(szAddFile);
}
}
bFind = FindNextFile(hFind, &wd);
}
FindClose(hFind);
}
}
}
return bRet;
}
// Adds a directory to the linked list of directories.
BOOL CCompCleaner::AddDirToList(LPCTSTR lpFile)
{
BOOL bRet = TRUE;
CLEANFILESTRUCT *pNew = (CLEANFILESTRUCT *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pNew));
if (pNew == NULL)
{
MiDebugMsg((0, TEXT("CCompCleaner::AddDirToList -> ERROR HeapAlloc() failed with error %d"), GetLastError()));
return FALSE;
}
StringCchCopy(pNew->file, ARRAYSIZE(pNew->file), lpFile);
if (head)
pNew->pNext = head;
else
pNew->pNext = NULL;
head = pNew;
return bRet;
}
void CCompCleaner::FreeList(CLEANFILESTRUCT *pCleanFile)
{
if (pCleanFile == NULL)
return;
if (pCleanFile->pNext)
FreeList(pCleanFile->pNext);
HeapFree(GetProcessHeap(), 0, pCleanFile);
}
// Compresses the specified file
BOOL CCompCleaner::CompressFile(IEmptyVolumeCacheCallBack *picb, LPCTSTR lpFile, ULARGE_INTEGER filesize)
{
ULARGE_INTEGER ulCompressedSize;
ulCompressedSize.QuadPart = filesize.QuadPart;
// If the file is read only, we need to remove the
// R/O attribute long enough to compress the file.
BOOL bFileWasRO = FALSE;
DWORD dwAttributes = GetFileAttributes(lpFile);
if ((0xFFFFFFFF != dwAttributes) && (dwAttributes & FILE_ATTRIBUTE_READONLY))
{
bFileWasRO = TRUE;
SetFileAttributes(lpFile, FILE_ATTRIBUTE_NORMAL);
}
HANDLE hFile = CreateFile(lpFile, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
USHORT InBuffer = COMPRESSION_FORMAT_DEFAULT;
DWORD dwBytesReturned = 0;
if (DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, &InBuffer, sizeof(InBuffer),
NULL, 0, &dwBytesReturned, NULL))
{
// Get the compressed file size so we can figure out
// how much space we gained by compressing.
ulCompressedSize.LowPart = GetCompressedFileSize(lpFile, &ulCompressedSize.HighPart);
}
CloseHandle(hFile);
}
// Restore the file attributes if needed
if (bFileWasRO)
SetFileAttributes(lpFile, dwAttributes);
// Adjust the cbSpaceFreed
cbSpaceFreed.QuadPart = cbSpaceFreed.QuadPart + (filesize.QuadPart - ulCompressedSize.QuadPart);
// Call back the cleanup manager to update the progress bar
if (picb->PurgeProgress(cbSpaceFreed.QuadPart, (cbSpaceUsed.QuadPart - cbSpaceFreed.QuadPart), 0, NULL) == E_ABORT)
{
// User aborted so stop compressing files
MiDebugMsg((0, TEXT("CCompCleaner::PurgeFiles User abort")));
return FALSE;
}
return TRUE;
}