1557 lines
40 KiB
C
1557 lines
40 KiB
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,
|
||
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,
|
||
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,
|
||
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 )) {
|
||
|
||
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, 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) {
|
||
return FALSE;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Now compress the file
|
||
//
|
||
|
||
if (!CompressFile( FileHandle, DirectorySpec, &FindData )) {
|
||
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 )) {
|
||
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) {
|
||
return FALSE;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Now compress the file
|
||
//
|
||
|
||
if (!UncompressFile( FileHandle, &FindData )) {
|
||
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 )) {
|
||
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)();
|
||
}
|