COMPACT.C

/*++ 

Copyright (c) 1994-1995 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 <stdio.h>
#include <windows.h>
#include <winioctl.h>
#include <shellapi.h>

#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.
//

if (FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {

CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
&CompressedSize.HighPart );
PrintState = 'C';
TotalCompressedFileCount.QuadPart += 1;

} else {

CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
&CompressedSize.HighPart );

if (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);
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);

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;
BOOLEAN Success;
double f = 1.0;

if ((FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) &&
!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 );

//
// 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);

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) {
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 & 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 (IsNtldr(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);

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());
}
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) {
DisplayErr(DirectorySpec, GetLastError());
}

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 & 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;
}


VOID
__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;

InitializeIoStreams();

argv = CommandLineToArgvW(GetCommandLine(), &argc);
if (NULL == argv) {
DisplayErr(NULL, GetLastError());
return;
}

//
// 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;
}

ActionRoutine = DoCompressAction;
FinalActionRoutine = DoFinalCompressAction;

} else if (0 == lstricmp(argv[i], TEXT("/u"))) {

if (ActionRoutine != NULL && ActionRoutine != DoListAction) {

DisplayMsg(COMPACT_USAGE, NULL);
return;
}

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;
}

} 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);
return;
}

} 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;
}

//
// If the user didn't specify a file spec then we'll do just "*"
//

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) {

(VOID)(ActionRoutine)( DirectorySpec, TEXT("") );
}

(VOID)(ActionRoutine)( DirectorySpec, FileSpec );

} 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]);
break;
}

//
// GetFullPathName strips trailing dots, but we want
// to save them so that "*." will work correctly.
//

if ('.' == 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) {
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)();
}