home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / sdktools / winnt / compact / compact.c next >
C/C++ Source or Header  |  1997-04-08  |  43KB  |  1,566 lines

  1. /*++
  2.  
  3. Copyright (c) 1994-1995  Microsoft Corporation
  4.  
  5. Module Name:
  6.  
  7.     Compact.c
  8.  
  9. Abstract:
  10.  
  11.     This module implements the double stuff utility for compressed NTFS
  12.     volumes.
  13.  
  14. Author:
  15.  
  16.     Gary Kimura     [garyki]        10-Jan-1994
  17.  
  18. Revision History:
  19.  
  20.  
  21. --*/
  22.  
  23. //
  24. // Include the standard header files.
  25. //
  26.  
  27. #define UNICODE
  28. #define _UNICODE
  29.  
  30. #include <stdio.h>
  31. #include <windows.h>
  32. #include <winioctl.h>
  33. #include <shellapi.h>
  34.  
  35. #include "support.h"
  36. #include "msg.h"
  37.  
  38. #define lstrchr wcschr
  39. #define lstricmp _wcsicmp
  40. #define lstrnicmp _wcsnicmp
  41.  
  42. //
  43. //  FIRST_COLUMN_WIDTH - When compressing files, the width of the output
  44. //  column which displays the file name
  45. //
  46.  
  47. #define FIRST_COLUMN_WIDTH  (20)
  48.  
  49. //
  50. //  Local procedure types
  51. //
  52.  
  53. typedef BOOLEAN (*PACTION_ROUTINE) (
  54.     IN PTCHAR DirectorySpec,
  55.     IN PTCHAR FileSpec
  56.     );
  57.  
  58. typedef VOID (*PFINAL_ACTION_ROUTINE) (
  59.     );
  60.  
  61. //
  62. //  Declare global variables to hold the command line information
  63. //
  64.  
  65. BOOLEAN DoSubdirectories      = FALSE;      // recurse
  66. BOOLEAN IgnoreErrors          = FALSE;      // keep going despite errs
  67. BOOLEAN UserSpecifiedFileSpec = FALSE;
  68. BOOLEAN ForceOperation        = FALSE;      // compress even if already so
  69. BOOLEAN Quiet                 = FALSE;      // be less verbose
  70. BOOLEAN DisplayAllFiles       = FALSE;      // dsply hidden, system?
  71. TCHAR   StartingDirectory[MAX_PATH];        // parameter to "/s"
  72. ULONG   AttributesNoDisplay = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
  73.  
  74. //
  75. //  Declere global variables to hold compression statistics
  76. //
  77.  
  78. LARGE_INTEGER TotalDirectoryCount;
  79. LARGE_INTEGER TotalFileCount;
  80. LARGE_INTEGER TotalCompressedFileCount;
  81. LARGE_INTEGER TotalUncompressedFileCount;
  82.  
  83. LARGE_INTEGER TotalFileSize;
  84. LARGE_INTEGER TotalCompressedSize;
  85.  
  86. TCHAR Buf[1024];                            // for displaying stuff
  87.  
  88.  
  89. HANDLE
  90. OpenFileForCompress(
  91.     IN      PTCHAR      ptcFile
  92.     )
  93. /*++
  94.  
  95. Routine Description:
  96.  
  97.     This routine jumps through the hoops necessary to open the file
  98.     for READ_DATA|WRITE_DATA even if the file has the READONLY
  99.     attribute set.
  100.  
  101. Arguments:
  102.  
  103.     ptcFile     - Specifies the file that should be opened.
  104.  
  105. Return Value:
  106.  
  107.     A handle open on the file if successfull, INVALID_HANDLE_VALUE
  108.     otherwise, in which case the caller may use GetLastError() for more
  109.     info.
  110.  
  111. --*/
  112. {
  113.     BY_HANDLE_FILE_INFORMATION fi;
  114.     HANDLE hRet;
  115.     HANDLE h;
  116.     INT err;
  117.  
  118.     hRet = CreateFile(
  119.                 ptcFile,
  120.                 FILE_READ_DATA | FILE_WRITE_DATA,
  121.                 FILE_SHARE_READ | FILE_SHARE_WRITE,
  122.                 NULL,
  123.                 OPEN_EXISTING,
  124.                 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
  125.                 NULL
  126.                 );
  127.  
  128.     if (INVALID_HANDLE_VALUE != hRet) {
  129.         return hRet;
  130.     }
  131.  
  132.     if (ERROR_ACCESS_DENIED != GetLastError()) {
  133.         return INVALID_HANDLE_VALUE;
  134.     }
  135.  
  136.     err = GetLastError();
  137.  
  138.     h = CreateFile(
  139.             ptcFile,
  140.             FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
  141.             FILE_SHARE_READ | FILE_SHARE_WRITE,
  142.             NULL,
  143.             OPEN_EXISTING,
  144.             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
  145.             NULL
  146.             );
  147.  
  148.     if (INVALID_HANDLE_VALUE == h) {
  149.         return INVALID_HANDLE_VALUE;
  150.     }
  151.  
  152.     if (!GetFileInformationByHandle(h, &fi)) {
  153.         CloseHandle(h);
  154.         return INVALID_HANDLE_VALUE;
  155.     }
  156.  
  157.     if ((fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) {
  158.  
  159.         // If we couldn't open the file for some reason other than that
  160.         // the readonly attribute was set, fail.
  161.  
  162.         SetLastError(err);
  163.         CloseHandle(h);
  164.         return INVALID_HANDLE_VALUE;
  165.     }
  166.  
  167.     fi.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
  168.  
  169.     if (!SetFileAttributes(ptcFile, fi.dwFileAttributes)) {
  170.         CloseHandle(h);
  171.         return INVALID_HANDLE_VALUE;
  172.     }
  173.  
  174.     hRet = CreateFile(
  175.             ptcFile,
  176.             FILE_READ_DATA | FILE_WRITE_DATA,
  177.             FILE_SHARE_READ | FILE_SHARE_WRITE,
  178.             NULL,
  179.             OPEN_EXISTING,
  180.             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
  181.             NULL
  182.             );
  183.  
  184.     CloseHandle(h);
  185.  
  186.     if (INVALID_HANDLE_VALUE == hRet) {
  187.         return INVALID_HANDLE_VALUE;
  188.     }
  189.  
  190.     fi.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
  191.  
  192.     if (!SetFileAttributes(ptcFile, fi.dwFileAttributes)) {
  193.         CloseHandle(hRet);
  194.         return INVALID_HANDLE_VALUE;
  195.     }
  196.  
  197.     return hRet;
  198. }
  199.  
  200. //
  201. //  Now do the routines to list the compression state and size of
  202. //  a file and/or directory
  203. //
  204.  
  205. BOOLEAN
  206. DisplayFile (
  207.     IN PTCHAR FileSpec,
  208.     IN PWIN32_FIND_DATA FindData
  209.     )
  210. {
  211.     LARGE_INTEGER FileSize;
  212.     LARGE_INTEGER CompressedSize;
  213.     TCHAR PrintState;
  214.  
  215.     ULONG Percentage = 100;
  216.     double Ratio = 1.0;
  217.  
  218.     FileSize.LowPart = FindData->nFileSizeLow;
  219.     FileSize.HighPart = FindData->nFileSizeHigh;
  220.     PrintState = ' ';
  221.  
  222.     //
  223.     //  Decide if the file is compressed and if so then
  224.     //  get the compressed file size.
  225.     //
  226.  
  227.     if (FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
  228.  
  229.         CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
  230.             &CompressedSize.HighPart );
  231.         PrintState = 'C';
  232.         TotalCompressedFileCount.QuadPart += 1;
  233.  
  234.     } else {
  235.  
  236.         CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
  237.             &CompressedSize.HighPart );
  238.  
  239.         if (GetLastError() == 0 &&
  240.             CompressedSize.QuadPart != 0 &&
  241.             CompressedSize.QuadPart < FileSize.QuadPart) {
  242.  
  243.             // File on DblSpace partition.
  244.  
  245.             PrintState = 'd';
  246.             TotalCompressedFileCount.QuadPart += 1;
  247.  
  248.         } else {
  249.  
  250.             CompressedSize = FileSize;
  251.             TotalUncompressedFileCount.QuadPart += 1;
  252.         }
  253.     }
  254.  
  255.  
  256.     //
  257.     //  Calculate the compression ratio for this file
  258.     //
  259.  
  260.     if (CompressedSize.QuadPart != 0) {
  261.  
  262.         if (CompressedSize.QuadPart > FileSize.QuadPart) {
  263.  
  264.             //
  265.             // The file probably grew between the time we got its size
  266.             // and the time we got its compressed size.  Kludge.
  267.             //
  268.  
  269.             FileSize.QuadPart = CompressedSize.QuadPart;
  270.         }
  271.  
  272.         Ratio = (double)FileSize.QuadPart / (double)CompressedSize.QuadPart;
  273.     }
  274.  
  275.     //
  276.     //  Print out the sizes compression state and file name
  277.     //
  278.  
  279.     if (!Quiet &&
  280.         (DisplayAllFiles ||
  281.             (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
  282.  
  283.         FormatFileSize(&FileSize, 9, Buf, FALSE);
  284.         lstrcat(Buf, TEXT(" : "));
  285.         FormatFileSize(&CompressedSize, 9, &Buf[lstrlen(Buf)], FALSE);
  286.  
  287.         swprintf(&Buf[lstrlen(Buf)], TEXT(" = %2.1lf "), Ratio);
  288.         DisplayMsg(COMPACT_THROW, Buf);
  289.  
  290.         DisplayMsg(COMPACT_TO_ONE);
  291.  
  292.         swprintf(Buf, TEXT("%c %s",), PrintState, FindData->cFileName);
  293.         DisplayMsg(COMPACT_THROW_NL, Buf);
  294.     }
  295.  
  296.     //
  297.     //  Increment our running total
  298.     //
  299.  
  300.     TotalFileSize.QuadPart += FileSize.QuadPart;
  301.     TotalCompressedSize.QuadPart += CompressedSize.QuadPart;
  302.     TotalFileCount.QuadPart += 1;
  303.  
  304.     return TRUE;
  305. }
  306.  
  307.  
  308. BOOLEAN
  309. DoListAction (
  310.     IN PTCHAR DirectorySpec,
  311.     IN PTCHAR FileSpec
  312.     )
  313.  
  314. {
  315.     PTCHAR DirectorySpecEnd;
  316.  
  317.     //
  318.     //  So that we can keep on appending names to the directory spec
  319.     //  get a pointer to the end of its string
  320.     //
  321.  
  322.     DirectorySpecEnd = DirectorySpec + lstrlen(DirectorySpec);
  323.  
  324.     //
  325.     //  List the compression attribute for the directory
  326.     //
  327.  
  328.     {
  329.         ULONG Attributes;
  330.  
  331.         if (!Quiet || Quiet) {
  332.  
  333.             Attributes = GetFileAttributes( DirectorySpec );
  334.  
  335.             if (0xFFFFFFFF == Attributes) {
  336.  
  337.                 if (!Quiet || !IgnoreErrors) {
  338.  
  339.                     //
  340.                     // Refrain from displaying error only when in quiet
  341.                     // mode *and* we're ignoring errors.
  342.                     //
  343.  
  344.                     DisplayErr(DirectorySpec, GetLastError());
  345.                 }
  346.  
  347.                 if (!IgnoreErrors) {
  348.                     return FALSE;
  349.                 }
  350.             } else {
  351.  
  352.                 if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {
  353.                     DisplayMsg(COMPACT_LIST_CDIR, DirectorySpec);
  354.                 } else {
  355.                     DisplayMsg(COMPACT_LIST_UDIR, DirectorySpec);
  356.                 }
  357.             }
  358.         }
  359.  
  360.         TotalDirectoryCount.QuadPart += 1;
  361.     }
  362.  
  363.     //
  364.     //  Now for every file in the directory that matches the file spec we will
  365.     //  will open the file and list its compression state
  366.     //
  367.  
  368.     {
  369.         HANDLE FindHandle;
  370.         WIN32_FIND_DATA FindData;
  371.  
  372.         //
  373.         //  setup the template for findfirst/findnext
  374.         //
  375.  
  376.         //
  377.         //  Make sure we don't try any paths that are too long for us
  378.         //  to deal with.
  379.         //
  380.  
  381.         if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
  382.             MAX_PATH) {
  383.  
  384.             lstrcpy( DirectorySpecEnd, FileSpec );
  385.  
  386.             FindHandle = FindFirstFile( DirectorySpec, &FindData );
  387.  
  388.             if (INVALID_HANDLE_VALUE != FindHandle) {
  389.  
  390.                do {
  391.  
  392.                    //
  393.                    //  append the found file to the directory spec and open the
  394.                    //  file
  395.                    //
  396.  
  397.                    if (0 == lstrcmp(FindData.cFileName, TEXT("..")) ||
  398.                        0 == lstrcmp(FindData.cFileName, TEXT("."))) {
  399.                        continue;
  400.                    }
  401.  
  402.                    //
  403.                    //  Make sure we don't try any paths that are too long for us
  404.                    //  to deal with.
  405.                    //
  406.  
  407.                    if ((DirectorySpecEnd - DirectorySpec) +
  408.                        lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  409.  
  410.                        continue;
  411.                    }
  412.  
  413.                    lstrcpy( DirectorySpecEnd, FindData.cFileName );
  414.  
  415.                    //
  416.                    //  Now print out the state of the file
  417.                    //
  418.  
  419.                    DisplayFile( DirectorySpec, &FindData );
  420.  
  421.                } while ( FindNextFile( FindHandle, &FindData ));
  422.  
  423.                FindClose( FindHandle );
  424.            }
  425.         }
  426.     }
  427.  
  428.     //
  429.     //  For if we are to do subdirectores then we will look for every
  430.     //  subdirectory and recursively call ourselves to list the subdirectory
  431.     //
  432.  
  433.     if (DoSubdirectories) {
  434.  
  435.         HANDLE FindHandle;
  436.  
  437.         WIN32_FIND_DATA FindData;
  438.  
  439.         //
  440.         //  Setup findfirst/findnext to search the entire directory
  441.         //
  442.  
  443.         if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
  444.             MAX_PATH) {
  445.  
  446.            lstrcpy( DirectorySpecEnd, TEXT("*") );
  447.  
  448.            FindHandle = FindFirstFile( DirectorySpec, &FindData );
  449.  
  450.            if (INVALID_HANDLE_VALUE != FindHandle) {
  451.  
  452.                do {
  453.  
  454.                    //
  455.                    //  Now skip over the . and .. entries otherwise we'll recurse
  456.                    //  like mad
  457.                    //
  458.  
  459.                    if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  460.                        0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  461.  
  462.                        continue;
  463.  
  464.                    } else {
  465.  
  466.                        //
  467.                        //  If the entry is for a directory then we'll tack on the
  468.                        //  subdirectory name to the directory spec and recursively
  469.                        //  call otherselves
  470.                        //
  471.  
  472.                        if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  473.  
  474.                            //
  475.                            //  Make sure we don't try any paths that are too long for us
  476.                            //  to deal with.
  477.                            //
  478.  
  479.                            if ((DirectorySpecEnd - DirectorySpec) +
  480.                                lstrlen( TEXT("\\") ) +
  481.                                lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  482.  
  483.                                continue;
  484.                            }
  485.  
  486.                            lstrcpy( DirectorySpecEnd, FindData.cFileName );
  487.                            lstrcat( DirectorySpecEnd, TEXT("\\") );
  488.  
  489.                            if (!DoListAction( DirectorySpec, FileSpec )) {
  490.  
  491.                                FindClose( FindHandle );
  492.                                return FALSE || IgnoreErrors;
  493.                            }
  494.                        }
  495.                    }
  496.  
  497.                } while ( FindNextFile( FindHandle, &FindData ));
  498.  
  499.                FindClose( FindHandle );
  500.            }
  501.         }
  502.     }
  503.  
  504.     return TRUE;
  505. }
  506.  
  507. VOID
  508. DoFinalListAction (
  509.     )
  510. {
  511.     ULONG TotalPercentage = 100;
  512.     double f = 1.0;
  513.  
  514.     TCHAR FileCount[32];
  515.     TCHAR DirectoryCount[32];
  516.     TCHAR CompressedFileCount[32];
  517.     TCHAR UncompressedFileCount[32];
  518.     TCHAR CompressedSize[32];
  519.     TCHAR FileSize[32];
  520.     TCHAR Percentage[10];
  521.     TCHAR Ratio[8];
  522.  
  523.     if (TotalCompressedSize.QuadPart != 0) {
  524.         f = (double)TotalFileSize.QuadPart /
  525.             (double)TotalCompressedSize.QuadPart;
  526.     }
  527.  
  528.     FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
  529.     FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
  530.     FormatFileSize(&TotalCompressedFileCount, 0, CompressedFileCount, FALSE);
  531.     FormatFileSize(&TotalUncompressedFileCount, 0, UncompressedFileCount, FALSE);
  532.     FormatFileSize(&TotalCompressedSize, 0, CompressedSize, TRUE);
  533.     FormatFileSize(&TotalFileSize, 0, FileSize, TRUE);
  534.  
  535.     swprintf(Percentage, TEXT("%d"), TotalPercentage);
  536.     swprintf(Ratio, TEXT("%2.1lf"), f);
  537.  
  538.     DisplayMsg(COMPACT_LIST_SUMMARY, FileCount, DirectoryCount,
  539.                CompressedFileCount, UncompressedFileCount,
  540.                FileSize, CompressedSize,
  541.                Ratio );
  542.  
  543.     return;
  544. }
  545.  
  546.  
  547. BOOLEAN
  548. CompressFile (
  549.     IN HANDLE Handle,
  550.     IN PTCHAR FileSpec,
  551.     IN PWIN32_FIND_DATA FindData
  552.     )
  553.  
  554. {
  555.     USHORT State = 1;
  556.     ULONG Length;
  557.     ULONG i;
  558.     BOOLEAN Success;
  559.     double f = 1.0;
  560.  
  561.     if ((FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) &&
  562.         !ForceOperation) {
  563.  
  564.         return TRUE;
  565.     }
  566.  
  567.     Success = DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, &State,
  568.         sizeof(USHORT), NULL, 0, &Length, FALSE );
  569.  
  570.     if (!Success) {
  571.  
  572.         if (Quiet && IgnoreErrors) {
  573.             return FALSE || IgnoreErrors;
  574.         }
  575.  
  576.         swprintf(Buf, TEXT("%s "), FindData->cFileName);
  577.         DisplayMsg(COMPACT_THROW, Buf);
  578.  
  579.         for (i = lstrlen(FindData->cFileName) + 1; i < FIRST_COLUMN_WIDTH; ++i) {
  580.             swprintf(Buf, TEXT("%c"), ' ');
  581.             DisplayMsg(COMPACT_THROW, Buf);
  582.         }
  583.  
  584.         DisplayMsg(COMPACT_ERR);
  585.  
  586.         if (!Quiet && !IgnoreErrors) {
  587.             if (ERROR_INVALID_FUNCTION == GetLastError()) {
  588.  
  589.                 // This error is caused by doing the fsctl on a
  590.                 // non-compressing volume.
  591.  
  592.                 DisplayMsg(COMPACT_WRONG_FILE_SYSTEM_OR_CLUSTER_SIZE, FindData->cFileName);
  593.  
  594.             } else {
  595.                 DisplayErr(FindData->cFileName, GetLastError());
  596.             }
  597.         }
  598.  
  599.         return FALSE || IgnoreErrors;
  600.     }
  601.  
  602.     if (!Quiet &&
  603.         (DisplayAllFiles ||
  604.             (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
  605.         swprintf(Buf, TEXT("%s "), FindData->cFileName);
  606.         DisplayMsg(COMPACT_THROW, Buf);
  607.  
  608.         for (i = lstrlen(FindData->cFileName) + 1; i < FIRST_COLUMN_WIDTH; ++i) {
  609.             swprintf(Buf, TEXT("%c"), ' ');
  610.             DisplayMsg(COMPACT_THROW, Buf);
  611.         }
  612.     }
  613.  
  614.  
  615.     //
  616.     //  Gather statistics and increment our running total
  617.     //
  618.  
  619.     {
  620.         LARGE_INTEGER FileSize;
  621.         LARGE_INTEGER CompressedSize;
  622.         ULONG Percentage = 100;
  623.  
  624.         FileSize.LowPart = FindData->nFileSizeLow;
  625.         FileSize.HighPart = FindData->nFileSizeHigh;
  626.  
  627.         CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
  628.             &CompressedSize.HighPart );
  629.  
  630.         //
  631.         // This statement to prevent confusion from the case where the
  632.         // compressed file had been 0 size, but has grown since the filesize
  633.         // was examined.
  634.         //
  635.  
  636.         if (0 == FileSize.QuadPart) {
  637.             CompressedSize.QuadPart = 0;
  638.         }
  639.  
  640.         if (CompressedSize.QuadPart != 0) {
  641.  
  642.             f = (double)FileSize.QuadPart / (double)CompressedSize.QuadPart;
  643.         }
  644.  
  645.         //
  646.         //  Print out the sizes compression state and file name
  647.         //
  648.  
  649.         if (!Quiet &&
  650.             (DisplayAllFiles ||
  651.                 (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
  652.  
  653.             FormatFileSize(&FileSize, 9, Buf, FALSE);
  654.             lstrcat(Buf, TEXT(" : "));
  655.             FormatFileSize(&CompressedSize, 9, &Buf[lstrlen(Buf)], FALSE);
  656.  
  657.             swprintf(&Buf[lstrlen(Buf)], TEXT(" = %2.1lf "), f);
  658.  
  659.             DisplayMsg(COMPACT_THROW, Buf);
  660.  
  661.             DisplayMsg(COMPACT_TO_ONE);
  662.             DisplayMsg(COMPACT_OK);
  663.         }
  664.  
  665.         //
  666.         //  Increment our running total
  667.         //
  668.  
  669.         TotalFileSize.QuadPart += FileSize.QuadPart;
  670.         TotalCompressedSize.QuadPart += CompressedSize.QuadPart;
  671.         TotalFileCount.QuadPart += 1;
  672.     }
  673.  
  674.     return TRUE;
  675. }
  676.  
  677. BOOLEAN
  678. DoCompressAction (
  679.     IN PTCHAR DirectorySpec,
  680.     IN PTCHAR FileSpec
  681.     )
  682.  
  683. {
  684.     PTCHAR DirectorySpecEnd;
  685.  
  686.     //
  687.     //  If the file spec is null then we'll set the compression bit for the
  688.     //  the directory spec and get out.
  689.     //
  690.  
  691.     if (lstrlen(FileSpec) == 0) {
  692.  
  693.         HANDLE FileHandle;
  694.         USHORT State = 1;
  695.         ULONG Length;
  696.  
  697.         FileHandle = OpenFileForCompress(DirectorySpec);
  698.  
  699.         if (INVALID_HANDLE_VALUE == FileHandle) {
  700.  
  701.             DisplayErr(DirectorySpec, GetLastError());
  702.             return FALSE || IgnoreErrors;
  703.         }
  704.  
  705.         DisplayMsg(COMPACT_COMPRESS_DIR, DirectorySpec);
  706.  
  707.         if (!DeviceIoControl(FileHandle, FSCTL_SET_COMPRESSION, &State,
  708.             sizeof(USHORT), NULL, 0, &Length, FALSE )) {
  709.  
  710.             if (!Quiet || !IgnoreErrors) {
  711.                 DisplayMsg(COMPACT_ERR);
  712.             }
  713.             if (!Quiet && !IgnoreErrors) {
  714.                 DisplayErr(DirectorySpec, GetLastError());
  715.             }
  716.             CloseHandle( FileHandle );
  717.             return FALSE || IgnoreErrors;
  718.         }
  719.  
  720.         if (!Quiet) {
  721.             DisplayMsg(COMPACT_OK);
  722.         }
  723.  
  724.         CloseHandle( FileHandle );
  725.  
  726.         TotalDirectoryCount.QuadPart += 1;
  727.         TotalFileCount.QuadPart += 1;
  728.  
  729.         return TRUE;
  730.     }
  731.  
  732.     //
  733.     //  So that we can keep on appending names to the directory spec
  734.     //  get a pointer to the end of its string
  735.     //
  736.  
  737.     DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec );
  738.  
  739.     //
  740.     //  List the directory that we will be compressing within and say what its
  741.     //  current compress attribute is
  742.     //
  743.  
  744.     {
  745.         ULONG Attributes;
  746.  
  747.         if (!Quiet || Quiet) {
  748.  
  749.             Attributes = GetFileAttributes( DirectorySpec );
  750.  
  751.             if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {
  752.  
  753.                 DisplayMsg(COMPACT_COMPRESS_CDIR, DirectorySpec);
  754.  
  755.             } else {
  756.  
  757.                 DisplayMsg(COMPACT_COMPRESS_UDIR, DirectorySpec);
  758.  
  759.             }
  760.         }
  761.  
  762.         TotalDirectoryCount.QuadPart += 1;
  763.     }
  764.  
  765.     //
  766.     //  Now for every file in the directory that matches the file spec we will
  767.     //  will open the file and compress it
  768.     //
  769.  
  770.     {
  771.         HANDLE FindHandle;
  772.         HANDLE FileHandle;
  773.  
  774.         WIN32_FIND_DATA FindData;
  775.  
  776.         //
  777.         //  setup the template for findfirst/findnext
  778.         //
  779.  
  780.         if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
  781.             MAX_PATH) {
  782.  
  783.            lstrcpy( DirectorySpecEnd, FileSpec );
  784.  
  785.            FindHandle = FindFirstFile( DirectorySpec, &FindData );
  786.  
  787.            if (INVALID_HANDLE_VALUE != FindHandle) {
  788.  
  789.                do {
  790.  
  791.                    //
  792.                    //  Now skip over the . and .. entries
  793.                    //
  794.  
  795.                    if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  796.                        0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  797.  
  798.                        continue;
  799.  
  800.                    } else {
  801.  
  802.                        //
  803.                        //  Make sure we don't try any paths that are too long for us
  804.                        //  to deal with.
  805.                        //
  806.  
  807.                        if ( (DirectorySpecEnd - DirectorySpec) +
  808.                            lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  809.  
  810.                            continue;
  811.                        }
  812.  
  813.                        //
  814.                        //  append the found file to the directory spec and open
  815.                        //  the file
  816.                        //
  817.  
  818.  
  819.                        lstrcpy( DirectorySpecEnd, FindData.cFileName );
  820.  
  821.                        //
  822.                        //  Hack hack, kludge kludge.  Refrain from compressing
  823.                        //  files named "\NTDLR" to help users avoid hosing
  824.                        //  themselves.
  825.                        //
  826.  
  827.                        if (IsNtldr(DirectorySpec)) {
  828.  
  829.                            if (!Quiet) {
  830.                                DisplayMsg(COMPACT_SKIPPING, DirectorySpecEnd);
  831.                            }
  832.  
  833.                            continue;
  834.                        }
  835.  
  836.                        FileHandle = OpenFileForCompress(DirectorySpec);
  837.  
  838.                        if (INVALID_HANDLE_VALUE == FileHandle) {
  839.  
  840.                            if (!Quiet || !IgnoreErrors) {
  841.                                DisplayErr(FindData.cFileName, GetLastError());
  842.                            }
  843.  
  844.                            if (!IgnoreErrors) {
  845.                                FindClose(FindHandle);
  846.                                return FALSE;
  847.                            }
  848.                            continue;
  849.                        }
  850.  
  851.                        //
  852.                        //  Now compress the file
  853.                        //
  854.  
  855.                        if (!CompressFile( FileHandle, DirectorySpec, &FindData )) {
  856.                            CloseHandle( FileHandle );
  857.                            FindClose( FindHandle );
  858.                            return FALSE || IgnoreErrors;
  859.                        }
  860.  
  861.                        //
  862.                        //  Close the file and go get the next file
  863.                        //
  864.  
  865.                        CloseHandle( FileHandle );
  866.                    }
  867.  
  868.                } while ( FindNextFile( FindHandle, &FindData ));
  869.  
  870.                FindClose( FindHandle );
  871.            }
  872.         }
  873.     }
  874.  
  875.     //
  876.     //  If we are to do subdirectores then we will look for every subdirectory
  877.     //  and recursively call ourselves to list the subdirectory
  878.     //
  879.  
  880.     if (DoSubdirectories) {
  881.  
  882.         HANDLE FindHandle;
  883.  
  884.         WIN32_FIND_DATA FindData;
  885.  
  886.         //
  887.         //  Setup findfirst/findnext to search the entire directory
  888.         //
  889.  
  890.         if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
  891.             MAX_PATH) {
  892.  
  893.            lstrcpy( DirectorySpecEnd, TEXT("*") );
  894.  
  895.            FindHandle = FindFirstFile( DirectorySpec, &FindData );
  896.  
  897.            if (INVALID_HANDLE_VALUE != FindHandle) {
  898.  
  899.                do {
  900.  
  901.                    //
  902.                    //  Now skip over the . and .. entries otherwise we'll recurse
  903.                    //  like mad
  904.                    //
  905.  
  906.                    if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  907.                        0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  908.  
  909.                        continue;
  910.  
  911.                    } else {
  912.  
  913.                        //
  914.                        //  If the entry is for a directory then we'll tack on the
  915.                        //  subdirectory name to the directory spec and recursively
  916.                        //  call otherselves
  917.                        //
  918.  
  919.                        if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  920.  
  921.                            //
  922.                            //  Make sure we don't try any paths that are too long for us
  923.                            //  to deal with.
  924.                            //
  925.  
  926.                            if ((DirectorySpecEnd - DirectorySpec) +
  927.                                lstrlen( TEXT("\\") ) +
  928.                                lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  929.  
  930.                                continue;
  931.                            }
  932.  
  933.                            lstrcpy( DirectorySpecEnd, FindData.cFileName );
  934.                            lstrcat( DirectorySpecEnd, TEXT("\\") );
  935.  
  936.                            if (!DoCompressAction( DirectorySpec, FileSpec )) {
  937.                                FindClose( FindHandle );
  938.                                return FALSE || IgnoreErrors;
  939.                            }
  940.                        }
  941.                    }
  942.  
  943.                } while ( FindNextFile( FindHandle, &FindData ));
  944.  
  945.                FindClose( FindHandle );
  946.            }
  947.         }
  948.     }
  949.  
  950.     return TRUE;
  951. }
  952.  
  953. VOID
  954. DoFinalCompressAction (
  955.     )
  956. {
  957.     ULONG TotalPercentage = 100;
  958.     double f = 1.0;
  959.  
  960.     TCHAR FileCount[32];
  961.     TCHAR DirectoryCount[32];
  962.     TCHAR CompressedSize[32];
  963.     TCHAR FileSize[32];
  964.     TCHAR Percentage[32];
  965.     TCHAR Ratio[8];
  966.  
  967.     if (TotalCompressedSize.QuadPart != 0) {
  968.         f = (double)TotalFileSize.QuadPart /
  969.             (double)TotalCompressedSize.QuadPart;
  970.     }
  971.  
  972.     FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
  973.     FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
  974.     FormatFileSize(&TotalCompressedSize, 0, CompressedSize, TRUE);
  975.     FormatFileSize(&TotalFileSize, 0, FileSize, TRUE);
  976.  
  977.     swprintf(Percentage, TEXT("%d"), TotalPercentage);
  978.     swprintf(Ratio, TEXT("%2.1f"), f);
  979.  
  980.     DisplayMsg(COMPACT_COMPRESS_SUMMARY, FileCount, DirectoryCount,
  981.                 FileSize, CompressedSize, Ratio );
  982.  
  983. }
  984.  
  985.  
  986. BOOLEAN
  987. UncompressFile (
  988.     IN HANDLE Handle,
  989.     IN PWIN32_FIND_DATA FindData
  990.     )
  991. {
  992.     USHORT State = 0;
  993.     ULONG Length;
  994.  
  995.     if (!(FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) &&
  996.         !ForceOperation) {
  997.  
  998.         return TRUE;
  999.     }
  1000.  
  1001.     if (!DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, &State,
  1002.         sizeof(USHORT), NULL, 0, &Length, FALSE )) {
  1003.  
  1004.         if (!Quiet || !IgnoreErrors) {
  1005.  
  1006.             swprintf(Buf, TEXT("%s "), FindData->cFileName);
  1007.             DisplayMsg(COMPACT_THROW, Buf);
  1008.  
  1009.             DisplayMsg(COMPACT_ERR);
  1010.  
  1011.             if (!Quiet && !IgnoreErrors) {
  1012.  
  1013.                 if (ERROR_INVALID_FUNCTION == GetLastError()) {
  1014.  
  1015.                     // This error is caused by doing the fsctl on a
  1016.                     // non-compressing volume.
  1017.  
  1018.                     DisplayMsg(COMPACT_WRONG_FILE_SYSTEM, FindData->cFileName);
  1019.  
  1020.                 } else {
  1021.                     DisplayErr(FindData->cFileName, GetLastError());
  1022.                 }
  1023.             }
  1024.         }
  1025.         return FALSE || IgnoreErrors;
  1026.     }
  1027.  
  1028.     if (!Quiet &&
  1029.         (DisplayAllFiles ||
  1030.             (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
  1031.         swprintf(Buf, TEXT("%s "), FindData->cFileName);
  1032.         DisplayMsg(COMPACT_THROW, Buf);
  1033.  
  1034.         DisplayMsg(COMPACT_OK);
  1035.     }
  1036.  
  1037.     //
  1038.     //  Increment our running total
  1039.     //
  1040.  
  1041.     TotalFileCount.QuadPart += 1;
  1042.  
  1043.     return TRUE;
  1044. }
  1045.  
  1046. BOOLEAN
  1047. DoUncompressAction (
  1048.     IN PTCHAR DirectorySpec,
  1049.     IN PTCHAR FileSpec
  1050.     )
  1051.  
  1052. {
  1053.     PTCHAR DirectorySpecEnd;
  1054.  
  1055.     //
  1056.     //  If the file spec is null then we'll clear the compression bit for the
  1057.     //  the directory spec and get out.
  1058.     //
  1059.  
  1060.     if (lstrlen(FileSpec) == 0) {
  1061.  
  1062.         HANDLE FileHandle;
  1063.         USHORT State = 0;
  1064.         ULONG Length;
  1065.  
  1066.         FileHandle = OpenFileForCompress(DirectorySpec);
  1067.  
  1068.         if (INVALID_HANDLE_VALUE == FileHandle) {
  1069.  
  1070.             if (!Quiet || !IgnoreErrors) {
  1071.                 DisplayErr(DirectorySpec, GetLastError());
  1072.             }
  1073.             return FALSE || IgnoreErrors;
  1074.         }
  1075.  
  1076.         DisplayMsg(COMPACT_UNCOMPRESS_DIR, DirectorySpec);
  1077.  
  1078.         if (!DeviceIoControl(FileHandle, FSCTL_SET_COMPRESSION, &State,
  1079.             sizeof(USHORT), NULL, 0, &Length, FALSE )) {
  1080.  
  1081.             if (!Quiet || !IgnoreErrors) {
  1082.                 DisplayMsg(COMPACT_ERR);
  1083.  
  1084.             }
  1085.             if (!Quiet && !IgnoreErrors) {
  1086.                 DisplayErr(DirectorySpec, GetLastError());
  1087.             }
  1088.  
  1089.             return FALSE || IgnoreErrors;
  1090.         }
  1091.  
  1092.         if (!Quiet) {
  1093.             DisplayMsg(COMPACT_OK);
  1094.         }
  1095.  
  1096.         CloseHandle( FileHandle );
  1097.  
  1098.         TotalDirectoryCount.QuadPart += 1;
  1099.         TotalFileCount.QuadPart += 1;
  1100.  
  1101.         return TRUE;
  1102.     }
  1103.  
  1104.     //
  1105.     //  So that we can keep on appending names to the directory spec
  1106.     //  get a pointer to the end of its string
  1107.     //
  1108.  
  1109.     DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec );
  1110.  
  1111.     //
  1112.     //  List the directory that we will be uncompressing within and say what its
  1113.     //  current compress attribute is
  1114.     //
  1115.  
  1116.     {
  1117.         ULONG Attributes;
  1118.  
  1119.         if (!Quiet || Quiet) {
  1120.  
  1121.             Attributes = GetFileAttributes( DirectorySpec );
  1122.  
  1123.             if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {
  1124.  
  1125.                 DisplayMsg(COMPACT_UNCOMPRESS_CDIR, DirectorySpec);
  1126.  
  1127.             } else {
  1128.  
  1129.                 DisplayMsg(COMPACT_UNCOMPRESS_UDIR, DirectorySpec);
  1130.             }
  1131.         }
  1132.  
  1133.         TotalDirectoryCount.QuadPart += 1;
  1134.     }
  1135.  
  1136.     //
  1137.     //  Now for every file in the directory that matches the file spec we will
  1138.     //  will open the file and uncompress it
  1139.     //
  1140.  
  1141.     {
  1142.         HANDLE FindHandle;
  1143.         HANDLE FileHandle;
  1144.  
  1145.         WIN32_FIND_DATA FindData;
  1146.  
  1147.         //
  1148.         //  setup the template for findfirst/findnext
  1149.         //
  1150.  
  1151.         if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
  1152.             MAX_PATH) {
  1153.  
  1154.            lstrcpy( DirectorySpecEnd, FileSpec );
  1155.  
  1156.            FindHandle = FindFirstFile( DirectorySpec, &FindData );
  1157.  
  1158.            if (INVALID_HANDLE_VALUE != FindHandle) {
  1159.  
  1160.                do {
  1161.  
  1162.                    //
  1163.                    //  Now skip over the . and .. entries
  1164.                    //
  1165.  
  1166.                    if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  1167.                        0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  1168.  
  1169.                        continue;
  1170.  
  1171.                    } else {
  1172.  
  1173.                        //
  1174.                        //  Make sure we don't try any paths that are too long for us
  1175.                        //  to deal with.
  1176.                        //
  1177.  
  1178.                        if ((DirectorySpecEnd - DirectorySpec) +
  1179.                            lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  1180.  
  1181.                            continue;
  1182.                        }
  1183.  
  1184.                        //
  1185.                        //  append the found file to the directory spec and open
  1186.                        //  the file
  1187.                        //
  1188.  
  1189.                        lstrcpy( DirectorySpecEnd, FindData.cFileName );
  1190.  
  1191.                        FileHandle = OpenFileForCompress(DirectorySpec);
  1192.  
  1193.                        if (INVALID_HANDLE_VALUE == FileHandle) {
  1194.  
  1195.                            if (!Quiet || !IgnoreErrors) {
  1196.                                DisplayErr(DirectorySpec, GetLastError());
  1197.                            }
  1198.  
  1199.                            if (!IgnoreErrors) {
  1200.                                FindClose( FindHandle );
  1201.                                return FALSE;
  1202.                            }
  1203.                            continue;
  1204.                        }
  1205.  
  1206.                        //
  1207.                        //  Now compress the file
  1208.                        //
  1209.  
  1210.                        if (!UncompressFile( FileHandle, &FindData )) {
  1211.                            CloseHandle( FileHandle );
  1212.                            FindClose( FindHandle );
  1213.                            return FALSE || IgnoreErrors;
  1214.                        }
  1215.  
  1216.                        //
  1217.                        //  Close the file and go get the next file
  1218.                        //
  1219.  
  1220.                        CloseHandle( FileHandle );
  1221.                    }
  1222.  
  1223.                } while ( FindNextFile( FindHandle, &FindData ));
  1224.  
  1225.                FindClose( FindHandle );
  1226.            }
  1227.         }
  1228.     }
  1229.  
  1230.     //
  1231.     //  If we are to do subdirectores then we will look for every subdirectory
  1232.     //  and recursively call ourselves to list the subdirectory
  1233.     //
  1234.  
  1235.     if (DoSubdirectories) {
  1236.  
  1237.         HANDLE FindHandle;
  1238.  
  1239.         WIN32_FIND_DATA FindData;
  1240.  
  1241.         //
  1242.         //  Setup findfirst/findnext to search the entire directory
  1243.         //
  1244.  
  1245.         if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
  1246.             MAX_PATH) {
  1247.  
  1248.            lstrcpy( DirectorySpecEnd, TEXT("*") );
  1249.  
  1250.            FindHandle = FindFirstFile( DirectorySpec, &FindData );
  1251.            if (INVALID_HANDLE_VALUE != FindHandle) {
  1252.  
  1253.                do {
  1254.  
  1255.                    //
  1256.                    //  Now skip over the . and .. entries otherwise we'll recurse
  1257.                    //  like mad
  1258.                    //
  1259.  
  1260.                    if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  1261.                        0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  1262.  
  1263.                        continue;
  1264.  
  1265.                    } else {
  1266.  
  1267.                        //
  1268.                        //  If the entry is for a directory then we'll tack on the
  1269.                        //  subdirectory name to the directory spec and recursively
  1270.                        //  call otherselves
  1271.                        //
  1272.  
  1273.                        if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  1274.  
  1275.                            //
  1276.                            //  Make sure we don't try any paths that are too long for us
  1277.                            //  to deal with.
  1278.                            //
  1279.  
  1280.                            if ((DirectorySpecEnd - DirectorySpec) +
  1281.                                lstrlen( TEXT("\\") ) +
  1282.                                lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  1283.  
  1284.                                continue;
  1285.                            }
  1286.  
  1287.                            lstrcpy( DirectorySpecEnd, FindData.cFileName );
  1288.                            lstrcat( DirectorySpecEnd, TEXT("\\") );
  1289.  
  1290.                            if (!DoUncompressAction( DirectorySpec, FileSpec )) {
  1291.                                FindClose( FindHandle );
  1292.                                return FALSE || IgnoreErrors;
  1293.                            }
  1294.                        }
  1295.                    }
  1296.  
  1297.                } while ( FindNextFile( FindHandle, &FindData ));
  1298.  
  1299.                FindClose( FindHandle );
  1300.            }
  1301.         }
  1302.     }
  1303.  
  1304.     return TRUE;
  1305. }
  1306.  
  1307. VOID
  1308. DoFinalUncompressAction (
  1309.     )
  1310.  
  1311. {
  1312.     TCHAR FileCount[32];
  1313.     TCHAR DirectoryCount[32];
  1314.  
  1315.     FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
  1316.     FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
  1317.  
  1318.     DisplayMsg(COMPACT_UNCOMPRESS_SUMMARY, FileCount, DirectoryCount);
  1319.  
  1320.     return;
  1321. }
  1322.  
  1323.  
  1324. VOID
  1325. __cdecl
  1326. main()
  1327. {
  1328.     PTCHAR *argv;
  1329.     ULONG argc;
  1330.  
  1331.     ULONG i;
  1332.  
  1333.     PACTION_ROUTINE ActionRoutine = NULL;
  1334.     PFINAL_ACTION_ROUTINE FinalActionRoutine = NULL;
  1335.  
  1336.     BOOLEAN UserSpecifiedFileSpec = FALSE;
  1337.  
  1338.     TCHAR DirectorySpec[MAX_PATH];
  1339.     TCHAR FileSpec[MAX_PATH];
  1340.     PTCHAR p;
  1341.  
  1342.     InitializeIoStreams();
  1343.  
  1344.     argv = CommandLineToArgvW(GetCommandLine(), &argc);
  1345.     if (NULL == argv) {
  1346.         DisplayErr(NULL, GetLastError());
  1347.         return;
  1348.     }
  1349.  
  1350.     //
  1351.     //  Scan through the arguments looking for switches
  1352.     //
  1353.  
  1354.     for (i = 1; i < argc; i += 1) {
  1355.  
  1356.         if (argv[i][0] == '/') {
  1357.  
  1358.             if (0 == lstricmp(argv[i], TEXT("/c"))) {
  1359.  
  1360.                 if (ActionRoutine != NULL &&
  1361.                     ActionRoutine != DoCompressAction) {
  1362.  
  1363.                     DisplayMsg(COMPACT_USAGE, NULL);
  1364.                     return;
  1365.                 }
  1366.  
  1367.                 ActionRoutine = DoCompressAction;
  1368.                 FinalActionRoutine = DoFinalCompressAction;
  1369.  
  1370.             } else if (0 == lstricmp(argv[i], TEXT("/u"))) {
  1371.  
  1372.                 if (ActionRoutine != NULL && ActionRoutine != DoListAction) {
  1373.  
  1374.                     DisplayMsg(COMPACT_USAGE, NULL);
  1375.                     return;
  1376.                 }
  1377.  
  1378.                 ActionRoutine = DoUncompressAction;
  1379.                 FinalActionRoutine = DoFinalUncompressAction;
  1380.  
  1381.             } else if (0 == lstricmp(argv[i], TEXT("/q"))) {
  1382.  
  1383.                 Quiet = TRUE;
  1384.  
  1385.             } else if (0 == lstrnicmp(argv[i], TEXT("/s"), 2)) {
  1386.  
  1387.                 PTCHAR pch;
  1388.  
  1389.                 DoSubdirectories = TRUE;
  1390.  
  1391.                 pch = lstrchr(argv[i], ':');
  1392.                 if (NULL != pch) {
  1393.                     lstrcpy(StartingDirectory, pch + 1);
  1394.                 } else if (2 == lstrlen(argv[i])) {
  1395.  
  1396.                     // Starting dir is CWD
  1397.  
  1398.                     GetCurrentDirectory( MAX_PATH, StartingDirectory );
  1399.  
  1400.                 } else {
  1401.                     DisplayMsg(COMPACT_USAGE, NULL);
  1402.                     return;
  1403.                 }
  1404.  
  1405.             } else if (0 == lstricmp(argv[i], TEXT("/i"))) {
  1406.  
  1407.                 IgnoreErrors = TRUE;
  1408.  
  1409.             } else if (0 == lstricmp(argv[i], TEXT("/f"))) {
  1410.  
  1411.                 ForceOperation = TRUE;
  1412.  
  1413.             } else if (0 == lstricmp(argv[i], TEXT("/a"))) {
  1414.  
  1415.                 DisplayAllFiles = TRUE;
  1416.  
  1417.             } else {
  1418.  
  1419.                 DisplayMsg(COMPACT_USAGE, NULL);
  1420.                 return;
  1421.             }
  1422.  
  1423.         } else {
  1424.  
  1425.             UserSpecifiedFileSpec = TRUE;
  1426.         }
  1427.     }
  1428.  
  1429.     //
  1430.     //  If the use didn't specify an action then set the default to do a listing
  1431.     //
  1432.  
  1433.     if (ActionRoutine == NULL) {
  1434.  
  1435.         ActionRoutine = DoListAction;
  1436.         FinalActionRoutine = DoFinalListAction;
  1437.     }
  1438.  
  1439.     //
  1440.     //  Get our current directoy because the action routines might move us
  1441.     //  around
  1442.     //
  1443.  
  1444.     if (!DoSubdirectories) {
  1445.         GetCurrentDirectory( MAX_PATH, StartingDirectory );
  1446.     } else if (!SetCurrentDirectory( StartingDirectory )) {
  1447.         DisplayErr(StartingDirectory, GetLastError());
  1448.         return;
  1449.     }
  1450.  
  1451.     //
  1452.     //  If the user didn't specify a file spec then we'll do just "*"
  1453.     //
  1454.  
  1455.     if (!UserSpecifiedFileSpec) {
  1456.  
  1457.         (VOID)GetFullPathName( TEXT("*"), MAX_PATH, DirectorySpec, &p );
  1458.  
  1459.         lstrcpy( FileSpec, p ); *p = '\0';
  1460.  
  1461.         //
  1462.         // Also want to make "compact /c" set the bit for the current
  1463.         // directory.
  1464.         //
  1465.  
  1466.         if (ActionRoutine != DoListAction) {
  1467.  
  1468.             (VOID)(ActionRoutine)( DirectorySpec, TEXT("") );
  1469.         }
  1470.  
  1471.         (VOID)(ActionRoutine)( DirectorySpec, FileSpec );
  1472.  
  1473.     } else {
  1474.  
  1475.         //
  1476.         //  Now scan the arguments again looking for non-switches
  1477.         //  and this time do the action, but before calling reset
  1478.         //  the current directory so that things work again
  1479.         //
  1480.  
  1481.         for (i = 1; i < argc; i += 1) {
  1482.  
  1483.             if (argv[i][0] != '/') {
  1484.  
  1485.                 SetCurrentDirectory( StartingDirectory );
  1486.  
  1487.                 //
  1488.                 // Handle a command with "." as the file argument specially,
  1489.                 // since it doesn't make good sense and the results without
  1490.                 // this code are surprising.
  1491.                 //
  1492.  
  1493.                 if ('.' == argv[i][0] && '\0' == argv[i][1]) {
  1494.                     argv[i] = TEXT("*");
  1495.                     GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p);
  1496.                     *p = '\0';
  1497.                     p = NULL;
  1498.                 } else {
  1499.  
  1500.                     PWCHAR pwch;
  1501.  
  1502.                     GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p);
  1503.  
  1504.                     //
  1505.                     // We want to treat "foobie:xxx" as an invalid drive name,
  1506.                     // rather than as a name identifying a stream.  If there's
  1507.                     // a colon, there should be only a single character before
  1508.                     // it.
  1509.                     //
  1510.  
  1511.                     pwch = wcschr(argv[i], ':');
  1512.                     if (NULL != pwch && pwch - argv[i] != 1) {
  1513.                         DisplayMsg(COMPACT_INVALID_PATH, argv[i]);
  1514.                         break;
  1515.                     }
  1516.  
  1517.                     //
  1518.                     // GetFullPathName strips trailing dots, but we want
  1519.                     // to save them so that "*." will work correctly.
  1520.                     //
  1521.  
  1522.                     if ('.' == argv[i][lstrlen(argv[i]) - 1]) {
  1523.                         lstrcat(DirectorySpec, TEXT("."));
  1524.                     }
  1525.                 }
  1526.  
  1527.                 if (IsUncRoot(DirectorySpec)) {
  1528.  
  1529.                     //
  1530.                     // If the path is like \\server\share, we append an
  1531.                     // additional slash to make things come out right.
  1532.                     //
  1533.  
  1534.                     lstrcat(DirectorySpec, TEXT("\\"));
  1535.                     p = NULL;
  1536.                 }
  1537.  
  1538.  
  1539.                 if (p != NULL) {
  1540.                     lstrcpy( FileSpec, p ); *p = '\0';
  1541.                 } else {
  1542.                     FileSpec[0] = '\0';
  1543.                 }
  1544.  
  1545.                 if (!(ActionRoutine)( DirectorySpec, FileSpec ) &&
  1546.                     !IgnoreErrors) {
  1547.                     break;
  1548.                 }
  1549.             }
  1550.         }
  1551.     }
  1552.  
  1553.     //
  1554.     //  Reset our current directory back
  1555.     //
  1556.  
  1557.     SetCurrentDirectory( StartingDirectory );
  1558.  
  1559.     //
  1560.     //  And do the final action routine that will print out the final
  1561.     //  statistics of what we've done
  1562.     //
  1563.  
  1564.     (FinalActionRoutine)();
  1565. }
  1566.