/*++ Copyright (c) 1994-2001 Microsoft Corporation Module Name: Compact.c Abstract: This module implements the double stuff utility for compressed NTFS volumes. Author: Gary Kimura [garyki] 10-Jan-1994 Revision History: --*/ // // Include the standard header files. // #define UNICODE #define _UNICODE #include #include #include #include #include #include "support.h" #include "msg.h" #define lstrchr wcschr #define lstricmp _wcsicmp #define lstrnicmp _wcsnicmp // // FIRST_COLUMN_WIDTH - When compressing files, the width of the output // column which displays the file name // #define FIRST_COLUMN_WIDTH (20) // // Local procedure types // typedef BOOLEAN (*PACTION_ROUTINE) ( IN PTCHAR DirectorySpec, IN PTCHAR FileSpec ); typedef VOID (*PFINAL_ACTION_ROUTINE) ( ); // // Declare global variables to hold the command line information // BOOLEAN DoSubdirectories = FALSE; // recurse BOOLEAN IgnoreErrors = FALSE; // keep going despite errs BOOLEAN UserSpecifiedFileSpec = FALSE; BOOLEAN ForceOperation = FALSE; // compress even if already so BOOLEAN Quiet = FALSE; // be less verbose BOOLEAN DisplayAllFiles = FALSE; // dsply hidden, system? TCHAR StartingDirectory[MAX_PATH]; // parameter to "/s" ULONG AttributesNoDisplay = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN; // // Declere global variables to hold compression statistics // LARGE_INTEGER TotalDirectoryCount; LARGE_INTEGER TotalFileCount; LARGE_INTEGER TotalCompressedFileCount; LARGE_INTEGER TotalUncompressedFileCount; LARGE_INTEGER TotalFileSize; LARGE_INTEGER TotalCompressedSize; TCHAR Buf[1024]; // for displaying stuff HANDLE OpenFileForCompress( IN PTCHAR ptcFile ) /*++ Routine Description: This routine jumps through the hoops necessary to open the file for READ_DATA|WRITE_DATA even if the file has the READONLY attribute set. Arguments: ptcFile - Specifies the file that should be opened. Return Value: A handle open on the file if successfull, INVALID_HANDLE_VALUE otherwise, in which case the caller may use GetLastError() for more info. --*/ { BY_HANDLE_FILE_INFORMATION fi; HANDLE hRet; HANDLE h; INT err; hRet = CreateFile( ptcFile, FILE_READ_DATA | FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, NULL ); if (INVALID_HANDLE_VALUE != hRet) { return hRet; } if (ERROR_ACCESS_DENIED != GetLastError()) { return INVALID_HANDLE_VALUE; } err = GetLastError(); h = CreateFile( ptcFile, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, NULL ); if (INVALID_HANDLE_VALUE == h) { return INVALID_HANDLE_VALUE; } if (!GetFileInformationByHandle(h, &fi)) { CloseHandle(h); return INVALID_HANDLE_VALUE; } if ((fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) { // If we couldn't open the file for some reason other than that // the readonly attribute was set, fail. SetLastError(err); CloseHandle(h); return INVALID_HANDLE_VALUE; } fi.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY; if (!SetFileAttributes(ptcFile, fi.dwFileAttributes)) { CloseHandle(h); return INVALID_HANDLE_VALUE; } hRet = CreateFile( ptcFile, FILE_READ_DATA | FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, NULL ); CloseHandle(h); if (INVALID_HANDLE_VALUE == hRet) { return INVALID_HANDLE_VALUE; } fi.dwFileAttributes |= FILE_ATTRIBUTE_READONLY; if (!SetFileAttributes(ptcFile, fi.dwFileAttributes)) { CloseHandle(hRet); return INVALID_HANDLE_VALUE; } return hRet; } // // Now do the routines to list the compression state and size of // a file and/or directory // BOOLEAN DisplayFile ( IN PTCHAR FileSpec, IN PWIN32_FIND_DATA FindData ) { LARGE_INTEGER FileSize; LARGE_INTEGER CompressedSize; TCHAR PrintState; ULONG Percentage = 100; double Ratio = 1.0; FileSize.LowPart = FindData->nFileSizeLow; FileSize.HighPart = FindData->nFileSizeHigh; PrintState = ' '; // // Decide if the file is compressed and if so then // get the compressed file size. // CompressedSize.LowPart = GetCompressedFileSize( FileSpec, &CompressedSize.HighPart ); if (FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) { // detecting any error according to Jul 2000 MSDN GetCompressedFileSize doc if (CompressedSize.LowPart == -1 && GetLastError() != 0) { CompressedSize.QuadPart = 0; } PrintState = 'C'; TotalCompressedFileCount.QuadPart += 1; } else { // detecting any error according to Jul 2000 MSDN GetCompressedFileSize doc if ((CompressedSize.LowPart != -1 || GetLastError() == 0) && CompressedSize.QuadPart != 0 && CompressedSize.QuadPart < FileSize.QuadPart) { // File on DblSpace partition. PrintState = 'd'; TotalCompressedFileCount.QuadPart += 1; } else { CompressedSize = FileSize; TotalUncompressedFileCount.QuadPart += 1; } } // // Calculate the compression ratio for this file // if (CompressedSize.QuadPart != 0) { if (CompressedSize.QuadPart > FileSize.QuadPart) { // // The file probably grew between the time we got its size // and the time we got its compressed size. Kludge. // FileSize.QuadPart = CompressedSize.QuadPart; } Ratio = (double)FileSize.QuadPart / (double)CompressedSize.QuadPart; } // // Print out the sizes compression state and file name // if (!Quiet && (DisplayAllFiles || (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) { FormatFileSize(&FileSize, 9, Buf, FALSE); lstrcat(Buf, TEXT(" : ")); FormatFileSize(&CompressedSize, 9, &Buf[lstrlen(Buf)], FALSE); swprintf(&Buf[lstrlen(Buf)], TEXT(" = %2.1lf "), Ratio); if (_tcslen(DecimalPlace) == 1) { Buf[lstrlen(Buf)-3] = DecimalPlace[0]; } DisplayMsg(COMPACT_THROW, Buf); DisplayMsg(COMPACT_TO_ONE); swprintf(Buf, TEXT("%c %s"), PrintState, FindData->cFileName); DisplayMsg(COMPACT_THROW_NL, Buf); } // // Increment our running total // TotalFileSize.QuadPart += FileSize.QuadPart; TotalCompressedSize.QuadPart += CompressedSize.QuadPart; TotalFileCount.QuadPart += 1; return TRUE; } BOOLEAN DoListAction ( IN PTCHAR DirectorySpec, IN PTCHAR FileSpec ) { PTCHAR DirectorySpecEnd; // // So that we can keep on appending names to the directory spec // get a pointer to the end of its string // DirectorySpecEnd = DirectorySpec + lstrlen(DirectorySpec); // // List the compression attribute for the directory // { ULONG Attributes; if (!Quiet || Quiet) { Attributes = GetFileAttributes( DirectorySpec ); if (0xFFFFFFFF == Attributes) { if (!Quiet || !IgnoreErrors) { // // Refrain from displaying error only when in quiet // mode *and* we're ignoring errors. // DisplayErr(DirectorySpec, GetLastError()); } if (!IgnoreErrors) { return FALSE; } } else { if (Attributes & FILE_ATTRIBUTE_COMPRESSED) { DisplayMsg(COMPACT_LIST_CDIR, DirectorySpec); } else { DisplayMsg(COMPACT_LIST_UDIR, DirectorySpec); } } } TotalDirectoryCount.QuadPart += 1; } // // Now for every file in the directory that matches the file spec we will // will open the file and list its compression state // { HANDLE FindHandle; WIN32_FIND_DATA FindData; // // setup the template for findfirst/findnext // // // Make sure we don't try any paths that are too long for us // to deal with. // if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) < MAX_PATH) { lstrcpy( DirectorySpecEnd, FileSpec ); FindHandle = FindFirstFile( DirectorySpec, &FindData ); if (INVALID_HANDLE_VALUE != FindHandle) { do { // // append the found file to the directory spec and open the // file // if (0 == lstrcmp(FindData.cFileName, TEXT("..")) || 0 == lstrcmp(FindData.cFileName, TEXT("."))) { continue; } // // Make sure we don't try any paths that are too long for us // to deal with. // if ((DirectorySpecEnd - DirectorySpec) + lstrlen( FindData.cFileName ) >= MAX_PATH ) { continue; } lstrcpy( DirectorySpecEnd, FindData.cFileName ); // // Now print out the state of the file // DisplayFile( DirectorySpec, &FindData ); } while ( FindNextFile( FindHandle, &FindData )); FindClose( FindHandle ); } } } // // For if we are to do subdirectores then we will look for every // subdirectory and recursively call ourselves to list the subdirectory // if (DoSubdirectories) { HANDLE FindHandle; WIN32_FIND_DATA FindData; // // Setup findfirst/findnext to search the entire directory // if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) < MAX_PATH) { lstrcpy( DirectorySpecEnd, TEXT("*") ); FindHandle = FindFirstFile( DirectorySpec, &FindData ); if (INVALID_HANDLE_VALUE != FindHandle) { do { // // Now skip over the . and .. entries otherwise we'll recurse // like mad // if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) || 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) { continue; } else { // // If the entry is for a directory then we'll tack on the // subdirectory name to the directory spec and recursively // call otherselves // if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // // Make sure we don't try any paths that are too long for us // to deal with. // if ((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("\\") ) + lstrlen( FindData.cFileName ) >= MAX_PATH ) { continue; } lstrcpy( DirectorySpecEnd, FindData.cFileName ); lstrcat( DirectorySpecEnd, TEXT("\\") ); if (!DoListAction( DirectorySpec, FileSpec )) { FindClose( FindHandle ); return FALSE || IgnoreErrors; } } } } while ( FindNextFile( FindHandle, &FindData )); FindClose( FindHandle ); } } } return TRUE; } VOID DoFinalListAction ( ) { ULONG TotalPercentage = 100; double f = 1.0; TCHAR FileCount[32]; TCHAR DirectoryCount[32]; TCHAR CompressedFileCount[32]; TCHAR UncompressedFileCount[32]; TCHAR CompressedSize[32]; TCHAR FileSize[32]; TCHAR Percentage[10]; TCHAR Ratio[8]; if (TotalCompressedSize.QuadPart != 0) { f = (double)TotalFileSize.QuadPart / (double)TotalCompressedSize.QuadPart; } FormatFileSize(&TotalFileCount, 0, FileCount, FALSE); FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE); FormatFileSize(&TotalCompressedFileCount, 0, CompressedFileCount, FALSE); FormatFileSize(&TotalUncompressedFileCount, 0, UncompressedFileCount, FALSE); FormatFileSize(&TotalCompressedSize, 0, CompressedSize, TRUE); FormatFileSize(&TotalFileSize, 0, FileSize, TRUE); swprintf(Percentage, TEXT("%d"), TotalPercentage); swprintf(Ratio, TEXT("%2.1lf"), f); if (_tcslen(DecimalPlace) == 1) Ratio[lstrlen(Ratio)-2] = DecimalPlace[0]; DisplayMsg(COMPACT_LIST_SUMMARY, FileCount, DirectoryCount, CompressedFileCount, UncompressedFileCount, FileSize, CompressedSize, Ratio ); return; } BOOLEAN CompressFile ( IN HANDLE Handle, IN PTCHAR FileSpec, IN PWIN32_FIND_DATA FindData ) { USHORT State = 1; ULONG Length; ULONG i; BOOL Success; double f = 1.0; if ((FindData->dwFileAttributes & (FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_ENCRYPTED)) && !ForceOperation) { return TRUE; } Success = DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, &State, sizeof(USHORT), NULL, 0, &Length, FALSE ); if (!Success) { if (Quiet && IgnoreErrors) { return FALSE || IgnoreErrors; } swprintf(Buf, TEXT("%s "), FindData->cFileName); DisplayMsg(COMPACT_THROW, Buf); for (i = lstrlen(FindData->cFileName) + 1; i < FIRST_COLUMN_WIDTH; ++i) { swprintf(Buf, TEXT("%c"), ' '); DisplayMsg(COMPACT_THROW, Buf); } DisplayMsg(COMPACT_ERR); if (!Quiet && !IgnoreErrors) { if (ERROR_INVALID_FUNCTION == GetLastError()) { // This error is caused by doing the fsctl on a // non-compressing volume. DisplayMsg(COMPACT_WRONG_FILE_SYSTEM_OR_CLUSTER_SIZE, FindData->cFileName); } else { DisplayErr(FindData->cFileName, GetLastError()); } } return FALSE || IgnoreErrors; } if (!Quiet && (DisplayAllFiles || (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) { swprintf(Buf, TEXT("%s "), FindData->cFileName); DisplayMsg(COMPACT_THROW, Buf); for (i = lstrlen(FindData->cFileName) + 1; i < FIRST_COLUMN_WIDTH; ++i) { swprintf(Buf, TEXT("%c"), ' '); DisplayMsg(COMPACT_THROW, Buf); } } // // Gather statistics and increment our running total // { LARGE_INTEGER FileSize; LARGE_INTEGER CompressedSize; ULONG Percentage = 100; FileSize.LowPart = FindData->nFileSizeLow; FileSize.HighPart = FindData->nFileSizeHigh; CompressedSize.LowPart = GetCompressedFileSize( FileSpec, &CompressedSize.HighPart ); if (CompressedSize.LowPart == -1 && GetLastError() != 0) CompressedSize.QuadPart = 0; // // This statement to prevent confusion from the case where the // compressed file had been 0 size, but has grown since the filesize // was examined. // if (0 == FileSize.QuadPart) { CompressedSize.QuadPart = 0; } if (CompressedSize.QuadPart != 0) { f = (double)FileSize.QuadPart / (double)CompressedSize.QuadPart; } // // Print out the sizes compression state and file name // if (!Quiet && (DisplayAllFiles || (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) { FormatFileSize(&FileSize, 9, Buf, FALSE); lstrcat(Buf, TEXT(" : ")); FormatFileSize(&CompressedSize, 9, &Buf[lstrlen(Buf)], FALSE); swprintf(&Buf[lstrlen(Buf)], TEXT(" = %2.1lf "), f); if (_tcslen(DecimalPlace) == 1) Buf[lstrlen(Buf)-3] = DecimalPlace[0]; DisplayMsg(COMPACT_THROW, Buf); DisplayMsg(COMPACT_TO_ONE); DisplayMsg(COMPACT_OK); } // // Increment our running total // TotalFileSize.QuadPart += FileSize.QuadPart; TotalCompressedSize.QuadPart += CompressedSize.QuadPart; TotalFileCount.QuadPart += 1; } return TRUE; } BOOLEAN DoCompressAction ( IN PTCHAR DirectorySpec, IN PTCHAR FileSpec ) { PTCHAR DirectorySpecEnd; // // If the file spec is null then we'll set the compression bit for the // the directory spec and get out. // if (lstrlen(FileSpec) == 0) { HANDLE FileHandle; USHORT State = 1; ULONG Length; FileHandle = OpenFileForCompress(DirectorySpec); if (INVALID_HANDLE_VALUE == FileHandle) { DisplayErr(DirectorySpec, GetLastError()); return FALSE || IgnoreErrors; } DisplayMsg(COMPACT_COMPRESS_DIR, DirectorySpec); if (!DeviceIoControl(FileHandle, FSCTL_SET_COMPRESSION, &State, sizeof(USHORT), NULL, 0, &Length, FALSE )) { if (!Quiet || !IgnoreErrors) { DisplayMsg(COMPACT_ERR); } if (!Quiet && !IgnoreErrors) { if (ERROR_INVALID_FUNCTION == GetLastError()) { // This error is caused by doing the fsctl on a // non-compressing volume. DisplayMsg(COMPACT_WRONG_FILE_SYSTEM_OR_CLUSTER_SIZE, DirectorySpec); } else { DisplayErr(DirectorySpec, GetLastError()); } } CloseHandle( FileHandle ); return FALSE || IgnoreErrors; } if (!Quiet) { DisplayMsg(COMPACT_OK); } CloseHandle( FileHandle ); TotalDirectoryCount.QuadPart += 1; TotalFileCount.QuadPart += 1; return TRUE; } // // So that we can keep on appending names to the directory spec // get a pointer to the end of its string // DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec ); // // List the directory that we will be compressing within and say what its // current compress attribute is // { ULONG Attributes; if (!Quiet || Quiet) { Attributes = GetFileAttributes( DirectorySpec ); if (Attributes == 0xFFFFFFFF) { DisplayErr(DirectorySpec, GetLastError()); return FALSE || IgnoreErrors; } if (Attributes & FILE_ATTRIBUTE_COMPRESSED) { DisplayMsg(COMPACT_COMPRESS_CDIR, DirectorySpec); } else { DisplayMsg(COMPACT_COMPRESS_UDIR, DirectorySpec); } } TotalDirectoryCount.QuadPart += 1; } // // Now for every file in the directory that matches the file spec we will // will open the file and compress it // { HANDLE FindHandle; HANDLE FileHandle; WIN32_FIND_DATA FindData; // // setup the template for findfirst/findnext // if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) < MAX_PATH) { lstrcpy( DirectorySpecEnd, FileSpec ); FindHandle = FindFirstFile( DirectorySpec, &FindData ); if (INVALID_HANDLE_VALUE != FindHandle) { do { // // Now skip over the . and .. entries // if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) || 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) { continue; } else { // // Make sure we don't try any paths that are too long for us // to deal with. // if ( (DirectorySpecEnd - DirectorySpec) + lstrlen( FindData.cFileName ) >= MAX_PATH ) { continue; } // // append the found file to the directory spec and open // the file // lstrcpy( DirectorySpecEnd, FindData.cFileName ); // // Hack hack, kludge kludge. Refrain from compressing // files named "\NTDLR" to help users avoid hosing // themselves. // if (ExcludeThisFile(DirectorySpec)) { if (!Quiet) { DisplayMsg(COMPACT_SKIPPING, DirectorySpecEnd); } continue; } FileHandle = OpenFileForCompress(DirectorySpec); if (INVALID_HANDLE_VALUE == FileHandle) { if (!Quiet || !IgnoreErrors) { DisplayErr(FindData.cFileName, GetLastError()); } if (!IgnoreErrors) { FindClose(FindHandle); return FALSE; } continue; } // // Now compress the file // if (!CompressFile( FileHandle, DirectorySpec, &FindData )) { CloseHandle( FileHandle ); FindClose( FindHandle ); return FALSE || IgnoreErrors; } // // Close the file and go get the next file // CloseHandle( FileHandle ); } } while ( FindNextFile( FindHandle, &FindData )); FindClose( FindHandle ); } } } // // If we are to do subdirectores then we will look for every subdirectory // and recursively call ourselves to list the subdirectory // if (DoSubdirectories) { HANDLE FindHandle; WIN32_FIND_DATA FindData; // // Setup findfirst/findnext to search the entire directory // if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) < MAX_PATH) { lstrcpy( DirectorySpecEnd, TEXT("*") ); FindHandle = FindFirstFile( DirectorySpec, &FindData ); if (INVALID_HANDLE_VALUE != FindHandle) { do { // // Now skip over the . and .. entries otherwise we'll recurse // like mad // if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) || 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) { continue; } else { // // If the entry is for a directory then we'll tack on the // subdirectory name to the directory spec and recursively // call otherselves // if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // // Make sure we don't try any paths that are too long for us // to deal with. // if ((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("\\") ) + lstrlen( FindData.cFileName ) >= MAX_PATH ) { continue; } lstrcpy( DirectorySpecEnd, FindData.cFileName ); lstrcat( DirectorySpecEnd, TEXT("\\") ); if (!DoCompressAction( DirectorySpec, FileSpec )) { FindClose( FindHandle ); return FALSE || IgnoreErrors; } } } } while ( FindNextFile( FindHandle, &FindData )); FindClose( FindHandle ); } } } return TRUE; } VOID DoFinalCompressAction ( ) { ULONG TotalPercentage = 100; double f = 1.0; TCHAR FileCount[32]; TCHAR DirectoryCount[32]; TCHAR CompressedSize[32]; TCHAR FileSize[32]; TCHAR Percentage[32]; TCHAR Ratio[8]; if (TotalCompressedSize.QuadPart != 0) { f = (double)TotalFileSize.QuadPart / (double)TotalCompressedSize.QuadPart; } FormatFileSize(&TotalFileCount, 0, FileCount, FALSE); FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE); FormatFileSize(&TotalCompressedSize, 0, CompressedSize, TRUE); FormatFileSize(&TotalFileSize, 0, FileSize, TRUE); swprintf(Percentage, TEXT("%d"), TotalPercentage); swprintf(Ratio, TEXT("%2.1f"), f); if (_tcslen(DecimalPlace) == 1) Ratio[lstrlen(Ratio)-2] = DecimalPlace[0]; DisplayMsg(COMPACT_COMPRESS_SUMMARY, FileCount, DirectoryCount, FileSize, CompressedSize, Ratio ); } BOOLEAN UncompressFile ( IN HANDLE Handle, IN PWIN32_FIND_DATA FindData ) { USHORT State = 0; ULONG Length; if (!(FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) && !ForceOperation) { return TRUE; } if (!DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, &State, sizeof(USHORT), NULL, 0, &Length, FALSE )) { if (!Quiet || !IgnoreErrors) { swprintf(Buf, TEXT("%s "), FindData->cFileName); DisplayMsg(COMPACT_THROW, Buf); DisplayMsg(COMPACT_ERR); if (!Quiet && !IgnoreErrors) { if (ERROR_INVALID_FUNCTION == GetLastError()) { // This error is caused by doing the fsctl on a // non-compressing volume. DisplayMsg(COMPACT_WRONG_FILE_SYSTEM, FindData->cFileName); } else { DisplayErr(FindData->cFileName, GetLastError()); } } } return FALSE || IgnoreErrors; } if (!Quiet && (DisplayAllFiles || (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) { swprintf(Buf, TEXT("%s "), FindData->cFileName); DisplayMsg(COMPACT_THROW, Buf); DisplayMsg(COMPACT_OK); } // // Increment our running total // TotalFileCount.QuadPart += 1; return TRUE; } BOOLEAN DoUncompressAction ( IN PTCHAR DirectorySpec, IN PTCHAR FileSpec ) { PTCHAR DirectorySpecEnd; // // If the file spec is null then we'll clear the compression bit for the // the directory spec and get out. // if (lstrlen(FileSpec) == 0) { HANDLE FileHandle; USHORT State = 0; ULONG Length; FileHandle = OpenFileForCompress(DirectorySpec); if (INVALID_HANDLE_VALUE == FileHandle) { if (!Quiet || !IgnoreErrors) { DisplayErr(DirectorySpec, GetLastError()); } CloseHandle( FileHandle ); return FALSE || IgnoreErrors; } DisplayMsg(COMPACT_UNCOMPRESS_DIR, DirectorySpec); if (!DeviceIoControl(FileHandle, FSCTL_SET_COMPRESSION, &State, sizeof(USHORT), NULL, 0, &Length, FALSE )) { if (!Quiet || !IgnoreErrors) { DisplayMsg(COMPACT_ERR); } if (!Quiet && !IgnoreErrors) { if (ERROR_INVALID_FUNCTION == GetLastError()) { // This error is caused by doing the fsctl on a // non-compressing volume. DisplayMsg(COMPACT_WRONG_FILE_SYSTEM_OR_CLUSTER_SIZE, DirectorySpec); } else { DisplayErr(DirectorySpec, GetLastError()); } } CloseHandle( FileHandle ); return FALSE || IgnoreErrors; } if (!Quiet) { DisplayMsg(COMPACT_OK); } CloseHandle( FileHandle ); TotalDirectoryCount.QuadPart += 1; TotalFileCount.QuadPart += 1; return TRUE; } // // So that we can keep on appending names to the directory spec // get a pointer to the end of its string // DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec ); // // List the directory that we will be uncompressing within and say what its // current compress attribute is // { ULONG Attributes; if (!Quiet || Quiet) { Attributes = GetFileAttributes( DirectorySpec ); if (Attributes == 0xFFFFFFFF) { DisplayErr(DirectorySpec, GetLastError()); return FALSE || IgnoreErrors; } if (Attributes & FILE_ATTRIBUTE_COMPRESSED) { DisplayMsg(COMPACT_UNCOMPRESS_CDIR, DirectorySpec); } else { DisplayMsg(COMPACT_UNCOMPRESS_UDIR, DirectorySpec); } } TotalDirectoryCount.QuadPart += 1; } // // Now for every file in the directory that matches the file spec we will // will open the file and uncompress it // { HANDLE FindHandle; HANDLE FileHandle; WIN32_FIND_DATA FindData; // // setup the template for findfirst/findnext // if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) < MAX_PATH) { lstrcpy( DirectorySpecEnd, FileSpec ); FindHandle = FindFirstFile( DirectorySpec, &FindData ); if (INVALID_HANDLE_VALUE != FindHandle) { do { // // Now skip over the . and .. entries // if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) || 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) { continue; } else { // // Make sure we don't try any paths that are too long for us // to deal with. // if ((DirectorySpecEnd - DirectorySpec) + lstrlen( FindData.cFileName ) >= MAX_PATH ) { continue; } // // append the found file to the directory spec and open // the file // lstrcpy( DirectorySpecEnd, FindData.cFileName ); FileHandle = OpenFileForCompress(DirectorySpec); if (INVALID_HANDLE_VALUE == FileHandle) { if (!Quiet || !IgnoreErrors) { DisplayErr(DirectorySpec, GetLastError()); } if (!IgnoreErrors) { FindClose( FindHandle ); return FALSE; } continue; } // // Now compress the file // if (!UncompressFile( FileHandle, &FindData )) { CloseHandle( FileHandle ); FindClose( FindHandle ); return FALSE || IgnoreErrors; } // // Close the file and go get the next file // CloseHandle( FileHandle ); } } while ( FindNextFile( FindHandle, &FindData )); FindClose( FindHandle ); } } } // // If we are to do subdirectores then we will look for every subdirectory // and recursively call ourselves to list the subdirectory // if (DoSubdirectories) { HANDLE FindHandle; WIN32_FIND_DATA FindData; // // Setup findfirst/findnext to search the entire directory // if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) < MAX_PATH) { lstrcpy( DirectorySpecEnd, TEXT("*") ); FindHandle = FindFirstFile( DirectorySpec, &FindData ); if (INVALID_HANDLE_VALUE != FindHandle) { do { // // Now skip over the . and .. entries otherwise we'll recurse // like mad // if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) || 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) { continue; } else { // // If the entry is for a directory then we'll tack on the // subdirectory name to the directory spec and recursively // call otherselves // if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // // Make sure we don't try any paths that are too long for us // to deal with. // if ((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("\\") ) + lstrlen( FindData.cFileName ) >= MAX_PATH ) { continue; } lstrcpy( DirectorySpecEnd, FindData.cFileName ); lstrcat( DirectorySpecEnd, TEXT("\\") ); if (!DoUncompressAction( DirectorySpec, FileSpec )) { FindClose( FindHandle ); return FALSE || IgnoreErrors; } } } } while ( FindNextFile( FindHandle, &FindData )); FindClose( FindHandle ); } } } return TRUE; } VOID DoFinalUncompressAction ( ) { TCHAR FileCount[32]; TCHAR DirectoryCount[32]; FormatFileSize(&TotalFileCount, 0, FileCount, FALSE); FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE); DisplayMsg(COMPACT_UNCOMPRESS_SUMMARY, FileCount, DirectoryCount); return; } int __cdecl main() { PTCHAR *argv; ULONG argc; ULONG i; PACTION_ROUTINE ActionRoutine = NULL; PFINAL_ACTION_ROUTINE FinalActionRoutine = NULL; BOOLEAN UserSpecifiedFileSpec = FALSE; TCHAR DirectorySpec[MAX_PATH]; TCHAR FileSpec[MAX_PATH]; PTCHAR p; INT rtncode; InitializeIoStreams(); DirectorySpec[0] = '\0'; argv = CommandLineToArgvW(GetCommandLine(), &argc); if (NULL == argv) { DisplayErr(NULL, GetLastError()); return 1; } // // Scan through the arguments looking for switches // for (i = 1; i < argc; i += 1) { if (argv[i][0] == '/') { if (0 == lstricmp(argv[i], TEXT("/c"))) { if (ActionRoutine != NULL && ActionRoutine != DoCompressAction) { DisplayMsg(COMPACT_USAGE, NULL); return 1; } ActionRoutine = DoCompressAction; FinalActionRoutine = DoFinalCompressAction; } else if (0 == lstricmp(argv[i], TEXT("/u"))) { if (ActionRoutine != NULL && ActionRoutine != DoListAction) { DisplayMsg(COMPACT_USAGE, NULL); return 1; } ActionRoutine = DoUncompressAction; FinalActionRoutine = DoFinalUncompressAction; } else if (0 == lstricmp(argv[i], TEXT("/q"))) { Quiet = TRUE; } else if (0 == lstrnicmp(argv[i], TEXT("/s"), 2)) { PTCHAR pch; DoSubdirectories = TRUE; pch = lstrchr(argv[i], ':'); if (NULL != pch) { lstrcpy(StartingDirectory, pch + 1); } else if (2 == lstrlen(argv[i])) { // Starting dir is CWD GetCurrentDirectory( MAX_PATH, StartingDirectory ); } else { DisplayMsg(COMPACT_USAGE, NULL); return 1; } } else if (0 == lstricmp(argv[i], TEXT("/i"))) { IgnoreErrors = TRUE; } else if (0 == lstricmp(argv[i], TEXT("/f"))) { ForceOperation = TRUE; } else if (0 == lstricmp(argv[i], TEXT("/a"))) { DisplayAllFiles = TRUE; } else { DisplayMsg(COMPACT_USAGE, NULL); if (0 == lstricmp(argv[i], TEXT("/?"))) return 0; else return 1; } } else { UserSpecifiedFileSpec = TRUE; } } // // If the use didn't specify an action then set the default to do a listing // if (ActionRoutine == NULL) { ActionRoutine = DoListAction; FinalActionRoutine = DoFinalListAction; } // // Get our current directoy because the action routines might move us // around // if (!DoSubdirectories) { GetCurrentDirectory( MAX_PATH, StartingDirectory ); } else if (!SetCurrentDirectory( StartingDirectory )) { DisplayErr(StartingDirectory, GetLastError()); return 1; } // // If the user didn't specify a file spec then we'll do just "*" // rtncode = 0; if (!UserSpecifiedFileSpec) { (VOID)GetFullPathName( TEXT("*"), MAX_PATH, DirectorySpec, &p ); lstrcpy( FileSpec, p ); *p = '\0'; // // Also want to make "compact /c" set the bit for the current // directory. // if (ActionRoutine != DoListAction) { if (!(ActionRoutine)( DirectorySpec, TEXT("") )) rtncode = 1; } if (!(ActionRoutine)( DirectorySpec, FileSpec )) rtncode = 1; } else { // // Now scan the arguments again looking for non-switches // and this time do the action, but before calling reset // the current directory so that things work again // for (i = 1; i < argc; i += 1) { if (argv[i][0] != '/') { SetCurrentDirectory( StartingDirectory ); // // Handle a command with "." as the file argument specially, // since it doesn't make good sense and the results without // this code are surprising. // if ('.' == argv[i][0] && '\0' == argv[i][1]) { argv[i] = TEXT("*"); GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p); *p = '\0'; p = NULL; } else { PWCHAR pwch; GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p); // // We want to treat "foobie:xxx" as an invalid drive name, // rather than as a name identifying a stream. If there's // a colon, there should be only a single character before // it. // pwch = wcschr(argv[i], ':'); if (NULL != pwch && pwch - argv[i] != 1) { DisplayMsg(COMPACT_INVALID_PATH, argv[i]); rtncode = 1; break; } // // GetFullPathName strips trailing dots, but we want // to save them so that "*." will work correctly. // if ((lstrlen(argv[i]) > 0) && ('.' == argv[i][lstrlen(argv[i]) - 1])) { lstrcat(DirectorySpec, TEXT(".")); } } if (IsUncRoot(DirectorySpec)) { // // If the path is like \\server\share, we append an // additional slash to make things come out right. // lstrcat(DirectorySpec, TEXT("\\")); p = NULL; } if (p != NULL) { lstrcpy( FileSpec, p ); *p = '\0'; } else { FileSpec[0] = '\0'; } if (!(ActionRoutine)( DirectorySpec, FileSpec ) && !IgnoreErrors) { rtncode = 1; break; } } } } // // Reset our current directory back // SetCurrentDirectory( StartingDirectory ); // // And do the final action routine that will print out the final // statistics of what we've done // (FinalActionRoutine)(); return rtncode; }