Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1616 lines
42 KiB

  1. /*++
  2. Copyright (c) 1994-2001 Microsoft Corporation
  3. Module Name:
  4. Compact.c
  5. Abstract:
  6. This module implements the double stuff utility for compressed NTFS
  7. volumes.
  8. Author:
  9. Gary Kimura [garyki] 10-Jan-1994
  10. Revision History:
  11. --*/
  12. //
  13. // Include the standard header files.
  14. //
  15. #define UNICODE
  16. #define _UNICODE
  17. #include <stdio.h>
  18. #include <windows.h>
  19. #include <winioctl.h>
  20. #include <shellapi.h>
  21. #include <tchar.h>
  22. #include "support.h"
  23. #include "msg.h"
  24. #define lstrchr wcschr
  25. #define lstricmp _wcsicmp
  26. #define lstrnicmp _wcsnicmp
  27. //
  28. // FIRST_COLUMN_WIDTH - When compressing files, the width of the output
  29. // column which displays the file name
  30. //
  31. #define FIRST_COLUMN_WIDTH (20)
  32. //
  33. // Local procedure types
  34. //
  35. typedef BOOLEAN (*PACTION_ROUTINE) (
  36. IN PTCHAR DirectorySpec,
  37. IN PTCHAR FileSpec
  38. );
  39. typedef VOID (*PFINAL_ACTION_ROUTINE) (
  40. );
  41. //
  42. // Declare global variables to hold the command line information
  43. //
  44. BOOLEAN DoSubdirectories = FALSE; // recurse
  45. BOOLEAN IgnoreErrors = FALSE; // keep going despite errs
  46. BOOLEAN UserSpecifiedFileSpec = FALSE;
  47. BOOLEAN ForceOperation = FALSE; // compress even if already so
  48. BOOLEAN Quiet = FALSE; // be less verbose
  49. BOOLEAN DisplayAllFiles = FALSE; // dsply hidden, system?
  50. TCHAR StartingDirectory[MAX_PATH]; // parameter to "/s"
  51. ULONG AttributesNoDisplay = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
  52. //
  53. // Declere global variables to hold compression statistics
  54. //
  55. LARGE_INTEGER TotalDirectoryCount;
  56. LARGE_INTEGER TotalFileCount;
  57. LARGE_INTEGER TotalCompressedFileCount;
  58. LARGE_INTEGER TotalUncompressedFileCount;
  59. LARGE_INTEGER TotalFileSize;
  60. LARGE_INTEGER TotalCompressedSize;
  61. TCHAR Buf[1024]; // for displaying stuff
  62. HANDLE
  63. OpenFileForCompress(
  64. IN PTCHAR ptcFile
  65. )
  66. /*++
  67. Routine Description:
  68. This routine jumps through the hoops necessary to open the file
  69. for READ_DATA|WRITE_DATA even if the file has the READONLY
  70. attribute set.
  71. Arguments:
  72. ptcFile - Specifies the file that should be opened.
  73. Return Value:
  74. A handle open on the file if successfull, INVALID_HANDLE_VALUE
  75. otherwise, in which case the caller may use GetLastError() for more
  76. info.
  77. --*/
  78. {
  79. BY_HANDLE_FILE_INFORMATION fi;
  80. HANDLE hRet;
  81. HANDLE h;
  82. INT err;
  83. hRet = CreateFile(
  84. ptcFile,
  85. FILE_READ_DATA | FILE_WRITE_DATA,
  86. FILE_SHARE_READ | FILE_SHARE_WRITE,
  87. NULL,
  88. OPEN_EXISTING,
  89. FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
  90. NULL
  91. );
  92. if (INVALID_HANDLE_VALUE != hRet) {
  93. return hRet;
  94. }
  95. if (ERROR_ACCESS_DENIED != GetLastError()) {
  96. return INVALID_HANDLE_VALUE;
  97. }
  98. err = GetLastError();
  99. h = CreateFile(
  100. ptcFile,
  101. FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
  102. FILE_SHARE_READ | FILE_SHARE_WRITE,
  103. NULL,
  104. OPEN_EXISTING,
  105. FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
  106. NULL
  107. );
  108. if (INVALID_HANDLE_VALUE == h) {
  109. return INVALID_HANDLE_VALUE;
  110. }
  111. if (!GetFileInformationByHandle(h, &fi)) {
  112. CloseHandle(h);
  113. return INVALID_HANDLE_VALUE;
  114. }
  115. if ((fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) {
  116. // If we couldn't open the file for some reason other than that
  117. // the readonly attribute was set, fail.
  118. SetLastError(err);
  119. CloseHandle(h);
  120. return INVALID_HANDLE_VALUE;
  121. }
  122. fi.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
  123. if (!SetFileAttributes(ptcFile, fi.dwFileAttributes)) {
  124. CloseHandle(h);
  125. return INVALID_HANDLE_VALUE;
  126. }
  127. hRet = CreateFile(
  128. ptcFile,
  129. FILE_READ_DATA | FILE_WRITE_DATA,
  130. FILE_SHARE_READ | FILE_SHARE_WRITE,
  131. NULL,
  132. OPEN_EXISTING,
  133. FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
  134. NULL
  135. );
  136. CloseHandle(h);
  137. if (INVALID_HANDLE_VALUE == hRet) {
  138. return INVALID_HANDLE_VALUE;
  139. }
  140. fi.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
  141. if (!SetFileAttributes(ptcFile, fi.dwFileAttributes)) {
  142. CloseHandle(hRet);
  143. return INVALID_HANDLE_VALUE;
  144. }
  145. return hRet;
  146. }
  147. //
  148. // Now do the routines to list the compression state and size of
  149. // a file and/or directory
  150. //
  151. BOOLEAN
  152. DisplayFile (
  153. IN PTCHAR FileSpec,
  154. IN PWIN32_FIND_DATA FindData
  155. )
  156. {
  157. LARGE_INTEGER FileSize;
  158. LARGE_INTEGER CompressedSize;
  159. TCHAR PrintState;
  160. ULONG Percentage = 100;
  161. double Ratio = 1.0;
  162. FileSize.LowPart = FindData->nFileSizeLow;
  163. FileSize.HighPart = FindData->nFileSizeHigh;
  164. PrintState = ' ';
  165. //
  166. // Decide if the file is compressed and if so then
  167. // get the compressed file size.
  168. //
  169. CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
  170. &CompressedSize.HighPart );
  171. if (FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
  172. // detecting any error according to Jul 2000 MSDN GetCompressedFileSize doc
  173. if (CompressedSize.LowPart == -1 && GetLastError() != 0) {
  174. CompressedSize.QuadPart = 0;
  175. }
  176. PrintState = 'C';
  177. TotalCompressedFileCount.QuadPart += 1;
  178. } else {
  179. // detecting any error according to Jul 2000 MSDN GetCompressedFileSize doc
  180. if ((CompressedSize.LowPart != -1 || GetLastError() == 0) &&
  181. CompressedSize.QuadPart != 0 &&
  182. CompressedSize.QuadPart < FileSize.QuadPart) {
  183. // File on DblSpace partition.
  184. PrintState = 'd';
  185. TotalCompressedFileCount.QuadPart += 1;
  186. } else {
  187. CompressedSize = FileSize;
  188. TotalUncompressedFileCount.QuadPart += 1;
  189. }
  190. }
  191. //
  192. // Calculate the compression ratio for this file
  193. //
  194. if (CompressedSize.QuadPart != 0) {
  195. if (CompressedSize.QuadPart > FileSize.QuadPart) {
  196. //
  197. // The file probably grew between the time we got its size
  198. // and the time we got its compressed size. Kludge.
  199. //
  200. FileSize.QuadPart = CompressedSize.QuadPart;
  201. }
  202. Ratio = (double)FileSize.QuadPart / (double)CompressedSize.QuadPart;
  203. }
  204. //
  205. // Print out the sizes compression state and file name
  206. //
  207. if (!Quiet &&
  208. (DisplayAllFiles ||
  209. (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
  210. FormatFileSize(&FileSize, 9, Buf, FALSE);
  211. lstrcat(Buf, TEXT(" : "));
  212. FormatFileSize(&CompressedSize, 9, &Buf[lstrlen(Buf)], FALSE);
  213. swprintf(&Buf[lstrlen(Buf)], TEXT(" = %2.1lf "), Ratio);
  214. if (_tcslen(DecimalPlace) == 1) {
  215. Buf[lstrlen(Buf)-3] = DecimalPlace[0];
  216. }
  217. DisplayMsg(COMPACT_THROW, Buf);
  218. DisplayMsg(COMPACT_TO_ONE);
  219. swprintf(Buf, TEXT("%c %s",), PrintState, FindData->cFileName);
  220. DisplayMsg(COMPACT_THROW_NL, Buf);
  221. }
  222. //
  223. // Increment our running total
  224. //
  225. TotalFileSize.QuadPart += FileSize.QuadPart;
  226. TotalCompressedSize.QuadPart += CompressedSize.QuadPart;
  227. TotalFileCount.QuadPart += 1;
  228. return TRUE;
  229. }
  230. BOOLEAN
  231. DoListAction (
  232. IN PTCHAR DirectorySpec,
  233. IN PTCHAR FileSpec
  234. )
  235. {
  236. PTCHAR DirectorySpecEnd;
  237. //
  238. // So that we can keep on appending names to the directory spec
  239. // get a pointer to the end of its string
  240. //
  241. DirectorySpecEnd = DirectorySpec + lstrlen(DirectorySpec);
  242. //
  243. // List the compression attribute for the directory
  244. //
  245. {
  246. ULONG Attributes;
  247. if (!Quiet || Quiet) {
  248. Attributes = GetFileAttributes( DirectorySpec );
  249. if (0xFFFFFFFF == Attributes) {
  250. if (!Quiet || !IgnoreErrors) {
  251. //
  252. // Refrain from displaying error only when in quiet
  253. // mode *and* we're ignoring errors.
  254. //
  255. DisplayErr(DirectorySpec, GetLastError());
  256. }
  257. if (!IgnoreErrors) {
  258. return FALSE;
  259. }
  260. } else {
  261. if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {
  262. DisplayMsg(COMPACT_LIST_CDIR, DirectorySpec);
  263. } else {
  264. DisplayMsg(COMPACT_LIST_UDIR, DirectorySpec);
  265. }
  266. }
  267. }
  268. TotalDirectoryCount.QuadPart += 1;
  269. }
  270. //
  271. // Now for every file in the directory that matches the file spec we will
  272. // will open the file and list its compression state
  273. //
  274. {
  275. HANDLE FindHandle;
  276. WIN32_FIND_DATA FindData;
  277. //
  278. // setup the template for findfirst/findnext
  279. //
  280. //
  281. // Make sure we don't try any paths that are too long for us
  282. // to deal with.
  283. //
  284. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
  285. MAX_PATH) {
  286. lstrcpy( DirectorySpecEnd, FileSpec );
  287. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  288. if (INVALID_HANDLE_VALUE != FindHandle) {
  289. do {
  290. //
  291. // append the found file to the directory spec and open the
  292. // file
  293. //
  294. if (0 == lstrcmp(FindData.cFileName, TEXT("..")) ||
  295. 0 == lstrcmp(FindData.cFileName, TEXT("."))) {
  296. continue;
  297. }
  298. //
  299. // Make sure we don't try any paths that are too long for us
  300. // to deal with.
  301. //
  302. if ((DirectorySpecEnd - DirectorySpec) +
  303. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  304. continue;
  305. }
  306. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  307. //
  308. // Now print out the state of the file
  309. //
  310. DisplayFile( DirectorySpec, &FindData );
  311. } while ( FindNextFile( FindHandle, &FindData ));
  312. FindClose( FindHandle );
  313. }
  314. }
  315. }
  316. //
  317. // For if we are to do subdirectores then we will look for every
  318. // subdirectory and recursively call ourselves to list the subdirectory
  319. //
  320. if (DoSubdirectories) {
  321. HANDLE FindHandle;
  322. WIN32_FIND_DATA FindData;
  323. //
  324. // Setup findfirst/findnext to search the entire directory
  325. //
  326. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
  327. MAX_PATH) {
  328. lstrcpy( DirectorySpecEnd, TEXT("*") );
  329. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  330. if (INVALID_HANDLE_VALUE != FindHandle) {
  331. do {
  332. //
  333. // Now skip over the . and .. entries otherwise we'll recurse
  334. // like mad
  335. //
  336. if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  337. 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  338. continue;
  339. } else {
  340. //
  341. // If the entry is for a directory then we'll tack on the
  342. // subdirectory name to the directory spec and recursively
  343. // call otherselves
  344. //
  345. if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  346. //
  347. // Make sure we don't try any paths that are too long for us
  348. // to deal with.
  349. //
  350. if ((DirectorySpecEnd - DirectorySpec) +
  351. lstrlen( TEXT("\\") ) +
  352. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  353. continue;
  354. }
  355. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  356. lstrcat( DirectorySpecEnd, TEXT("\\") );
  357. if (!DoListAction( DirectorySpec, FileSpec )) {
  358. FindClose( FindHandle );
  359. return FALSE || IgnoreErrors;
  360. }
  361. }
  362. }
  363. } while ( FindNextFile( FindHandle, &FindData ));
  364. FindClose( FindHandle );
  365. }
  366. }
  367. }
  368. return TRUE;
  369. }
  370. VOID
  371. DoFinalListAction (
  372. )
  373. {
  374. ULONG TotalPercentage = 100;
  375. double f = 1.0;
  376. TCHAR FileCount[32];
  377. TCHAR DirectoryCount[32];
  378. TCHAR CompressedFileCount[32];
  379. TCHAR UncompressedFileCount[32];
  380. TCHAR CompressedSize[32];
  381. TCHAR FileSize[32];
  382. TCHAR Percentage[10];
  383. TCHAR Ratio[8];
  384. if (TotalCompressedSize.QuadPart != 0) {
  385. f = (double)TotalFileSize.QuadPart /
  386. (double)TotalCompressedSize.QuadPart;
  387. }
  388. FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
  389. FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
  390. FormatFileSize(&TotalCompressedFileCount, 0, CompressedFileCount, FALSE);
  391. FormatFileSize(&TotalUncompressedFileCount, 0, UncompressedFileCount, FALSE);
  392. FormatFileSize(&TotalCompressedSize, 0, CompressedSize, TRUE);
  393. FormatFileSize(&TotalFileSize, 0, FileSize, TRUE);
  394. swprintf(Percentage, TEXT("%d"), TotalPercentage);
  395. swprintf(Ratio, TEXT("%2.1lf"), f);
  396. if (_tcslen(DecimalPlace) == 1)
  397. Ratio[lstrlen(Ratio)-2] = DecimalPlace[0];
  398. DisplayMsg(COMPACT_LIST_SUMMARY, FileCount, DirectoryCount,
  399. CompressedFileCount, UncompressedFileCount,
  400. FileSize, CompressedSize,
  401. Ratio );
  402. return;
  403. }
  404. BOOLEAN
  405. CompressFile (
  406. IN HANDLE Handle,
  407. IN PTCHAR FileSpec,
  408. IN PWIN32_FIND_DATA FindData
  409. )
  410. {
  411. USHORT State = 1;
  412. ULONG Length;
  413. ULONG i;
  414. BOOL Success;
  415. double f = 1.0;
  416. if ((FindData->dwFileAttributes &
  417. (FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_ENCRYPTED)) &&
  418. !ForceOperation) {
  419. return TRUE;
  420. }
  421. Success = DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, &State,
  422. sizeof(USHORT), NULL, 0, &Length, FALSE );
  423. if (!Success) {
  424. if (Quiet && IgnoreErrors) {
  425. return FALSE || IgnoreErrors;
  426. }
  427. swprintf(Buf, TEXT("%s "), FindData->cFileName);
  428. DisplayMsg(COMPACT_THROW, Buf);
  429. for (i = lstrlen(FindData->cFileName) + 1; i < FIRST_COLUMN_WIDTH; ++i) {
  430. swprintf(Buf, TEXT("%c"), ' ');
  431. DisplayMsg(COMPACT_THROW, Buf);
  432. }
  433. DisplayMsg(COMPACT_ERR);
  434. if (!Quiet && !IgnoreErrors) {
  435. if (ERROR_INVALID_FUNCTION == GetLastError()) {
  436. // This error is caused by doing the fsctl on a
  437. // non-compressing volume.
  438. DisplayMsg(COMPACT_WRONG_FILE_SYSTEM_OR_CLUSTER_SIZE, FindData->cFileName);
  439. } else {
  440. DisplayErr(FindData->cFileName, GetLastError());
  441. }
  442. }
  443. return FALSE || IgnoreErrors;
  444. }
  445. if (!Quiet &&
  446. (DisplayAllFiles ||
  447. (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
  448. swprintf(Buf, TEXT("%s "), FindData->cFileName);
  449. DisplayMsg(COMPACT_THROW, Buf);
  450. for (i = lstrlen(FindData->cFileName) + 1; i < FIRST_COLUMN_WIDTH; ++i) {
  451. swprintf(Buf, TEXT("%c"), ' ');
  452. DisplayMsg(COMPACT_THROW, Buf);
  453. }
  454. }
  455. //
  456. // Gather statistics and increment our running total
  457. //
  458. {
  459. LARGE_INTEGER FileSize;
  460. LARGE_INTEGER CompressedSize;
  461. ULONG Percentage = 100;
  462. FileSize.LowPart = FindData->nFileSizeLow;
  463. FileSize.HighPart = FindData->nFileSizeHigh;
  464. CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
  465. &CompressedSize.HighPart );
  466. if (CompressedSize.LowPart == -1 && GetLastError() != 0)
  467. CompressedSize.QuadPart = 0;
  468. //
  469. // This statement to prevent confusion from the case where the
  470. // compressed file had been 0 size, but has grown since the filesize
  471. // was examined.
  472. //
  473. if (0 == FileSize.QuadPart) {
  474. CompressedSize.QuadPart = 0;
  475. }
  476. if (CompressedSize.QuadPart != 0) {
  477. f = (double)FileSize.QuadPart / (double)CompressedSize.QuadPart;
  478. }
  479. //
  480. // Print out the sizes compression state and file name
  481. //
  482. if (!Quiet &&
  483. (DisplayAllFiles ||
  484. (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
  485. FormatFileSize(&FileSize, 9, Buf, FALSE);
  486. lstrcat(Buf, TEXT(" : "));
  487. FormatFileSize(&CompressedSize, 9, &Buf[lstrlen(Buf)], FALSE);
  488. swprintf(&Buf[lstrlen(Buf)], TEXT(" = %2.1lf "), f);
  489. if (_tcslen(DecimalPlace) == 1)
  490. Buf[lstrlen(Buf)-3] = DecimalPlace[0];
  491. DisplayMsg(COMPACT_THROW, Buf);
  492. DisplayMsg(COMPACT_TO_ONE);
  493. DisplayMsg(COMPACT_OK);
  494. }
  495. //
  496. // Increment our running total
  497. //
  498. TotalFileSize.QuadPart += FileSize.QuadPart;
  499. TotalCompressedSize.QuadPart += CompressedSize.QuadPart;
  500. TotalFileCount.QuadPart += 1;
  501. }
  502. return TRUE;
  503. }
  504. BOOLEAN
  505. DoCompressAction (
  506. IN PTCHAR DirectorySpec,
  507. IN PTCHAR FileSpec
  508. )
  509. {
  510. PTCHAR DirectorySpecEnd;
  511. //
  512. // If the file spec is null then we'll set the compression bit for the
  513. // the directory spec and get out.
  514. //
  515. if (lstrlen(FileSpec) == 0) {
  516. HANDLE FileHandle;
  517. USHORT State = 1;
  518. ULONG Length;
  519. FileHandle = OpenFileForCompress(DirectorySpec);
  520. if (INVALID_HANDLE_VALUE == FileHandle) {
  521. DisplayErr(DirectorySpec, GetLastError());
  522. return FALSE || IgnoreErrors;
  523. }
  524. DisplayMsg(COMPACT_COMPRESS_DIR, DirectorySpec);
  525. if (!DeviceIoControl(FileHandle, FSCTL_SET_COMPRESSION, &State,
  526. sizeof(USHORT), NULL, 0, &Length, FALSE )) {
  527. if (!Quiet || !IgnoreErrors) {
  528. DisplayMsg(COMPACT_ERR);
  529. }
  530. if (!Quiet && !IgnoreErrors) {
  531. DisplayErr(DirectorySpec, GetLastError());
  532. }
  533. CloseHandle( FileHandle );
  534. return FALSE || IgnoreErrors;
  535. }
  536. if (!Quiet) {
  537. DisplayMsg(COMPACT_OK);
  538. }
  539. CloseHandle( FileHandle );
  540. TotalDirectoryCount.QuadPart += 1;
  541. TotalFileCount.QuadPart += 1;
  542. return TRUE;
  543. }
  544. //
  545. // So that we can keep on appending names to the directory spec
  546. // get a pointer to the end of its string
  547. //
  548. DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec );
  549. //
  550. // List the directory that we will be compressing within and say what its
  551. // current compress attribute is
  552. //
  553. {
  554. ULONG Attributes;
  555. if (!Quiet || Quiet) {
  556. Attributes = GetFileAttributes( DirectorySpec );
  557. if (Attributes == 0xFFFFFFFF) {
  558. DisplayErr(DirectorySpec, GetLastError());
  559. return FALSE || IgnoreErrors;
  560. }
  561. if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {
  562. DisplayMsg(COMPACT_COMPRESS_CDIR, DirectorySpec);
  563. } else {
  564. DisplayMsg(COMPACT_COMPRESS_UDIR, DirectorySpec);
  565. }
  566. }
  567. TotalDirectoryCount.QuadPart += 1;
  568. }
  569. //
  570. // Now for every file in the directory that matches the file spec we will
  571. // will open the file and compress it
  572. //
  573. {
  574. HANDLE FindHandle;
  575. HANDLE FileHandle;
  576. WIN32_FIND_DATA FindData;
  577. //
  578. // setup the template for findfirst/findnext
  579. //
  580. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
  581. MAX_PATH) {
  582. lstrcpy( DirectorySpecEnd, FileSpec );
  583. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  584. if (INVALID_HANDLE_VALUE != FindHandle) {
  585. do {
  586. //
  587. // Now skip over the . and .. entries
  588. //
  589. if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  590. 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  591. continue;
  592. } else {
  593. //
  594. // Make sure we don't try any paths that are too long for us
  595. // to deal with.
  596. //
  597. if ( (DirectorySpecEnd - DirectorySpec) +
  598. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  599. continue;
  600. }
  601. //
  602. // append the found file to the directory spec and open
  603. // the file
  604. //
  605. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  606. //
  607. // Hack hack, kludge kludge. Refrain from compressing
  608. // files named "\NTDLR" to help users avoid hosing
  609. // themselves.
  610. //
  611. if (ExcludeThisFile(DirectorySpec)) {
  612. if (!Quiet) {
  613. DisplayMsg(COMPACT_SKIPPING, DirectorySpecEnd);
  614. }
  615. continue;
  616. }
  617. FileHandle = OpenFileForCompress(DirectorySpec);
  618. if (INVALID_HANDLE_VALUE == FileHandle) {
  619. if (!Quiet || !IgnoreErrors) {
  620. DisplayErr(FindData.cFileName, GetLastError());
  621. }
  622. if (!IgnoreErrors) {
  623. FindClose(FindHandle);
  624. return FALSE;
  625. }
  626. continue;
  627. }
  628. //
  629. // Now compress the file
  630. //
  631. if (!CompressFile( FileHandle, DirectorySpec, &FindData )) {
  632. CloseHandle( FileHandle );
  633. FindClose( FindHandle );
  634. return FALSE || IgnoreErrors;
  635. }
  636. //
  637. // Close the file and go get the next file
  638. //
  639. CloseHandle( FileHandle );
  640. }
  641. } while ( FindNextFile( FindHandle, &FindData ));
  642. FindClose( FindHandle );
  643. }
  644. }
  645. }
  646. //
  647. // If we are to do subdirectores then we will look for every subdirectory
  648. // and recursively call ourselves to list the subdirectory
  649. //
  650. if (DoSubdirectories) {
  651. HANDLE FindHandle;
  652. WIN32_FIND_DATA FindData;
  653. //
  654. // Setup findfirst/findnext to search the entire directory
  655. //
  656. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
  657. MAX_PATH) {
  658. lstrcpy( DirectorySpecEnd, TEXT("*") );
  659. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  660. if (INVALID_HANDLE_VALUE != FindHandle) {
  661. do {
  662. //
  663. // Now skip over the . and .. entries otherwise we'll recurse
  664. // like mad
  665. //
  666. if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  667. 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  668. continue;
  669. } else {
  670. //
  671. // If the entry is for a directory then we'll tack on the
  672. // subdirectory name to the directory spec and recursively
  673. // call otherselves
  674. //
  675. if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  676. //
  677. // Make sure we don't try any paths that are too long for us
  678. // to deal with.
  679. //
  680. if ((DirectorySpecEnd - DirectorySpec) +
  681. lstrlen( TEXT("\\") ) +
  682. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  683. continue;
  684. }
  685. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  686. lstrcat( DirectorySpecEnd, TEXT("\\") );
  687. if (!DoCompressAction( DirectorySpec, FileSpec )) {
  688. FindClose( FindHandle );
  689. return FALSE || IgnoreErrors;
  690. }
  691. }
  692. }
  693. } while ( FindNextFile( FindHandle, &FindData ));
  694. FindClose( FindHandle );
  695. }
  696. }
  697. }
  698. return TRUE;
  699. }
  700. VOID
  701. DoFinalCompressAction (
  702. )
  703. {
  704. ULONG TotalPercentage = 100;
  705. double f = 1.0;
  706. TCHAR FileCount[32];
  707. TCHAR DirectoryCount[32];
  708. TCHAR CompressedSize[32];
  709. TCHAR FileSize[32];
  710. TCHAR Percentage[32];
  711. TCHAR Ratio[8];
  712. if (TotalCompressedSize.QuadPart != 0) {
  713. f = (double)TotalFileSize.QuadPart /
  714. (double)TotalCompressedSize.QuadPart;
  715. }
  716. FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
  717. FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
  718. FormatFileSize(&TotalCompressedSize, 0, CompressedSize, TRUE);
  719. FormatFileSize(&TotalFileSize, 0, FileSize, TRUE);
  720. swprintf(Percentage, TEXT("%d"), TotalPercentage);
  721. swprintf(Ratio, TEXT("%2.1f"), f);
  722. if (_tcslen(DecimalPlace) == 1)
  723. Ratio[lstrlen(Ratio)-2] = DecimalPlace[0];
  724. DisplayMsg(COMPACT_COMPRESS_SUMMARY, FileCount, DirectoryCount,
  725. FileSize, CompressedSize, Ratio );
  726. }
  727. BOOLEAN
  728. UncompressFile (
  729. IN HANDLE Handle,
  730. IN PWIN32_FIND_DATA FindData
  731. )
  732. {
  733. USHORT State = 0;
  734. ULONG Length;
  735. if (!(FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) &&
  736. !ForceOperation) {
  737. return TRUE;
  738. }
  739. if (!DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, &State,
  740. sizeof(USHORT), NULL, 0, &Length, FALSE )) {
  741. if (!Quiet || !IgnoreErrors) {
  742. swprintf(Buf, TEXT("%s "), FindData->cFileName);
  743. DisplayMsg(COMPACT_THROW, Buf);
  744. DisplayMsg(COMPACT_ERR);
  745. if (!Quiet && !IgnoreErrors) {
  746. if (ERROR_INVALID_FUNCTION == GetLastError()) {
  747. // This error is caused by doing the fsctl on a
  748. // non-compressing volume.
  749. DisplayMsg(COMPACT_WRONG_FILE_SYSTEM, FindData->cFileName);
  750. } else {
  751. DisplayErr(FindData->cFileName, GetLastError());
  752. }
  753. }
  754. }
  755. return FALSE || IgnoreErrors;
  756. }
  757. if (!Quiet &&
  758. (DisplayAllFiles ||
  759. (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
  760. swprintf(Buf, TEXT("%s "), FindData->cFileName);
  761. DisplayMsg(COMPACT_THROW, Buf);
  762. DisplayMsg(COMPACT_OK);
  763. }
  764. //
  765. // Increment our running total
  766. //
  767. TotalFileCount.QuadPart += 1;
  768. return TRUE;
  769. }
  770. BOOLEAN
  771. DoUncompressAction (
  772. IN PTCHAR DirectorySpec,
  773. IN PTCHAR FileSpec
  774. )
  775. {
  776. PTCHAR DirectorySpecEnd;
  777. //
  778. // If the file spec is null then we'll clear the compression bit for the
  779. // the directory spec and get out.
  780. //
  781. if (lstrlen(FileSpec) == 0) {
  782. HANDLE FileHandle;
  783. USHORT State = 0;
  784. ULONG Length;
  785. FileHandle = OpenFileForCompress(DirectorySpec);
  786. if (INVALID_HANDLE_VALUE == FileHandle) {
  787. if (!Quiet || !IgnoreErrors) {
  788. DisplayErr(DirectorySpec, GetLastError());
  789. }
  790. CloseHandle( FileHandle );
  791. return FALSE || IgnoreErrors;
  792. }
  793. DisplayMsg(COMPACT_UNCOMPRESS_DIR, DirectorySpec);
  794. if (!DeviceIoControl(FileHandle, FSCTL_SET_COMPRESSION, &State,
  795. sizeof(USHORT), NULL, 0, &Length, FALSE )) {
  796. if (!Quiet || !IgnoreErrors) {
  797. DisplayMsg(COMPACT_ERR);
  798. }
  799. if (!Quiet && !IgnoreErrors) {
  800. DisplayErr(DirectorySpec, GetLastError());
  801. }
  802. CloseHandle( FileHandle );
  803. return FALSE || IgnoreErrors;
  804. }
  805. if (!Quiet) {
  806. DisplayMsg(COMPACT_OK);
  807. }
  808. CloseHandle( FileHandle );
  809. TotalDirectoryCount.QuadPart += 1;
  810. TotalFileCount.QuadPart += 1;
  811. return TRUE;
  812. }
  813. //
  814. // So that we can keep on appending names to the directory spec
  815. // get a pointer to the end of its string
  816. //
  817. DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec );
  818. //
  819. // List the directory that we will be uncompressing within and say what its
  820. // current compress attribute is
  821. //
  822. {
  823. ULONG Attributes;
  824. if (!Quiet || Quiet) {
  825. Attributes = GetFileAttributes( DirectorySpec );
  826. if (Attributes == 0xFFFFFFFF) {
  827. DisplayErr(DirectorySpec, GetLastError());
  828. return FALSE || IgnoreErrors;
  829. }
  830. if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {
  831. DisplayMsg(COMPACT_UNCOMPRESS_CDIR, DirectorySpec);
  832. } else {
  833. DisplayMsg(COMPACT_UNCOMPRESS_UDIR, DirectorySpec);
  834. }
  835. }
  836. TotalDirectoryCount.QuadPart += 1;
  837. }
  838. //
  839. // Now for every file in the directory that matches the file spec we will
  840. // will open the file and uncompress it
  841. //
  842. {
  843. HANDLE FindHandle;
  844. HANDLE FileHandle;
  845. WIN32_FIND_DATA FindData;
  846. //
  847. // setup the template for findfirst/findnext
  848. //
  849. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
  850. MAX_PATH) {
  851. lstrcpy( DirectorySpecEnd, FileSpec );
  852. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  853. if (INVALID_HANDLE_VALUE != FindHandle) {
  854. do {
  855. //
  856. // Now skip over the . and .. entries
  857. //
  858. if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  859. 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  860. continue;
  861. } else {
  862. //
  863. // Make sure we don't try any paths that are too long for us
  864. // to deal with.
  865. //
  866. if ((DirectorySpecEnd - DirectorySpec) +
  867. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  868. continue;
  869. }
  870. //
  871. // append the found file to the directory spec and open
  872. // the file
  873. //
  874. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  875. FileHandle = OpenFileForCompress(DirectorySpec);
  876. if (INVALID_HANDLE_VALUE == FileHandle) {
  877. if (!Quiet || !IgnoreErrors) {
  878. DisplayErr(DirectorySpec, GetLastError());
  879. }
  880. if (!IgnoreErrors) {
  881. FindClose( FindHandle );
  882. return FALSE;
  883. }
  884. continue;
  885. }
  886. //
  887. // Now compress the file
  888. //
  889. if (!UncompressFile( FileHandle, &FindData )) {
  890. CloseHandle( FileHandle );
  891. FindClose( FindHandle );
  892. return FALSE || IgnoreErrors;
  893. }
  894. //
  895. // Close the file and go get the next file
  896. //
  897. CloseHandle( FileHandle );
  898. }
  899. } while ( FindNextFile( FindHandle, &FindData ));
  900. FindClose( FindHandle );
  901. }
  902. }
  903. }
  904. //
  905. // If we are to do subdirectores then we will look for every subdirectory
  906. // and recursively call ourselves to list the subdirectory
  907. //
  908. if (DoSubdirectories) {
  909. HANDLE FindHandle;
  910. WIN32_FIND_DATA FindData;
  911. //
  912. // Setup findfirst/findnext to search the entire directory
  913. //
  914. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
  915. MAX_PATH) {
  916. lstrcpy( DirectorySpecEnd, TEXT("*") );
  917. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  918. if (INVALID_HANDLE_VALUE != FindHandle) {
  919. do {
  920. //
  921. // Now skip over the . and .. entries otherwise we'll recurse
  922. // like mad
  923. //
  924. if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  925. 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  926. continue;
  927. } else {
  928. //
  929. // If the entry is for a directory then we'll tack on the
  930. // subdirectory name to the directory spec and recursively
  931. // call otherselves
  932. //
  933. if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  934. //
  935. // Make sure we don't try any paths that are too long for us
  936. // to deal with.
  937. //
  938. if ((DirectorySpecEnd - DirectorySpec) +
  939. lstrlen( TEXT("\\") ) +
  940. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  941. continue;
  942. }
  943. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  944. lstrcat( DirectorySpecEnd, TEXT("\\") );
  945. if (!DoUncompressAction( DirectorySpec, FileSpec )) {
  946. FindClose( FindHandle );
  947. return FALSE || IgnoreErrors;
  948. }
  949. }
  950. }
  951. } while ( FindNextFile( FindHandle, &FindData ));
  952. FindClose( FindHandle );
  953. }
  954. }
  955. }
  956. return TRUE;
  957. }
  958. VOID
  959. DoFinalUncompressAction (
  960. )
  961. {
  962. TCHAR FileCount[32];
  963. TCHAR DirectoryCount[32];
  964. FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
  965. FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
  966. DisplayMsg(COMPACT_UNCOMPRESS_SUMMARY, FileCount, DirectoryCount);
  967. return;
  968. }
  969. int
  970. __cdecl
  971. main()
  972. {
  973. PTCHAR *argv;
  974. ULONG argc;
  975. ULONG i;
  976. PACTION_ROUTINE ActionRoutine = NULL;
  977. PFINAL_ACTION_ROUTINE FinalActionRoutine = NULL;
  978. BOOLEAN UserSpecifiedFileSpec = FALSE;
  979. TCHAR DirectorySpec[MAX_PATH];
  980. TCHAR FileSpec[MAX_PATH];
  981. PTCHAR p;
  982. INT rtncode;
  983. InitializeIoStreams();
  984. DirectorySpec[0] = '\0';
  985. argv = CommandLineToArgvW(GetCommandLine(), &argc);
  986. if (NULL == argv) {
  987. DisplayErr(NULL, GetLastError());
  988. return 1;
  989. }
  990. //
  991. // Scan through the arguments looking for switches
  992. //
  993. for (i = 1; i < argc; i += 1) {
  994. if (argv[i][0] == '/') {
  995. if (0 == lstricmp(argv[i], TEXT("/c"))) {
  996. if (ActionRoutine != NULL &&
  997. ActionRoutine != DoCompressAction) {
  998. DisplayMsg(COMPACT_USAGE, NULL);
  999. return 1;
  1000. }
  1001. ActionRoutine = DoCompressAction;
  1002. FinalActionRoutine = DoFinalCompressAction;
  1003. } else if (0 == lstricmp(argv[i], TEXT("/u"))) {
  1004. if (ActionRoutine != NULL && ActionRoutine != DoListAction) {
  1005. DisplayMsg(COMPACT_USAGE, NULL);
  1006. return 1;
  1007. }
  1008. ActionRoutine = DoUncompressAction;
  1009. FinalActionRoutine = DoFinalUncompressAction;
  1010. } else if (0 == lstricmp(argv[i], TEXT("/q"))) {
  1011. Quiet = TRUE;
  1012. } else if (0 == lstrnicmp(argv[i], TEXT("/s"), 2)) {
  1013. PTCHAR pch;
  1014. DoSubdirectories = TRUE;
  1015. pch = lstrchr(argv[i], ':');
  1016. if (NULL != pch) {
  1017. lstrcpy(StartingDirectory, pch + 1);
  1018. } else if (2 == lstrlen(argv[i])) {
  1019. // Starting dir is CWD
  1020. GetCurrentDirectory( MAX_PATH, StartingDirectory );
  1021. } else {
  1022. DisplayMsg(COMPACT_USAGE, NULL);
  1023. return 1;
  1024. }
  1025. } else if (0 == lstricmp(argv[i], TEXT("/i"))) {
  1026. IgnoreErrors = TRUE;
  1027. } else if (0 == lstricmp(argv[i], TEXT("/f"))) {
  1028. ForceOperation = TRUE;
  1029. } else if (0 == lstricmp(argv[i], TEXT("/a"))) {
  1030. DisplayAllFiles = TRUE;
  1031. } else {
  1032. DisplayMsg(COMPACT_USAGE, NULL);
  1033. if (0 == lstricmp(argv[i], TEXT("/?")))
  1034. return 0;
  1035. else
  1036. return 1;
  1037. }
  1038. } else {
  1039. UserSpecifiedFileSpec = TRUE;
  1040. }
  1041. }
  1042. //
  1043. // If the use didn't specify an action then set the default to do a listing
  1044. //
  1045. if (ActionRoutine == NULL) {
  1046. ActionRoutine = DoListAction;
  1047. FinalActionRoutine = DoFinalListAction;
  1048. }
  1049. //
  1050. // Get our current directoy because the action routines might move us
  1051. // around
  1052. //
  1053. if (!DoSubdirectories) {
  1054. GetCurrentDirectory( MAX_PATH, StartingDirectory );
  1055. } else if (!SetCurrentDirectory( StartingDirectory )) {
  1056. DisplayErr(StartingDirectory, GetLastError());
  1057. return 1;
  1058. }
  1059. //
  1060. // If the user didn't specify a file spec then we'll do just "*"
  1061. //
  1062. rtncode = 0;
  1063. if (!UserSpecifiedFileSpec) {
  1064. (VOID)GetFullPathName( TEXT("*"), MAX_PATH, DirectorySpec, &p );
  1065. lstrcpy( FileSpec, p ); *p = '\0';
  1066. //
  1067. // Also want to make "compact /c" set the bit for the current
  1068. // directory.
  1069. //
  1070. if (ActionRoutine != DoListAction) {
  1071. if (!(ActionRoutine)( DirectorySpec, TEXT("") ))
  1072. rtncode = 1;
  1073. }
  1074. if (!(ActionRoutine)( DirectorySpec, FileSpec ))
  1075. rtncode = 1;
  1076. } else {
  1077. //
  1078. // Now scan the arguments again looking for non-switches
  1079. // and this time do the action, but before calling reset
  1080. // the current directory so that things work again
  1081. //
  1082. for (i = 1; i < argc; i += 1) {
  1083. if (argv[i][0] != '/') {
  1084. SetCurrentDirectory( StartingDirectory );
  1085. //
  1086. // Handle a command with "." as the file argument specially,
  1087. // since it doesn't make good sense and the results without
  1088. // this code are surprising.
  1089. //
  1090. if ('.' == argv[i][0] && '\0' == argv[i][1]) {
  1091. argv[i] = TEXT("*");
  1092. GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p);
  1093. *p = '\0';
  1094. p = NULL;
  1095. } else {
  1096. PWCHAR pwch;
  1097. GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p);
  1098. //
  1099. // We want to treat "foobie:xxx" as an invalid drive name,
  1100. // rather than as a name identifying a stream. If there's
  1101. // a colon, there should be only a single character before
  1102. // it.
  1103. //
  1104. pwch = wcschr(argv[i], ':');
  1105. if (NULL != pwch && pwch - argv[i] != 1) {
  1106. DisplayMsg(COMPACT_INVALID_PATH, argv[i]);
  1107. rtncode = 1;
  1108. break;
  1109. }
  1110. //
  1111. // GetFullPathName strips trailing dots, but we want
  1112. // to save them so that "*." will work correctly.
  1113. //
  1114. if ((lstrlen(argv[i]) > 0) &&
  1115. ('.' == argv[i][lstrlen(argv[i]) - 1])) {
  1116. lstrcat(DirectorySpec, TEXT("."));
  1117. }
  1118. }
  1119. if (IsUncRoot(DirectorySpec)) {
  1120. //
  1121. // If the path is like \\server\share, we append an
  1122. // additional slash to make things come out right.
  1123. //
  1124. lstrcat(DirectorySpec, TEXT("\\"));
  1125. p = NULL;
  1126. }
  1127. if (p != NULL) {
  1128. lstrcpy( FileSpec, p ); *p = '\0';
  1129. } else {
  1130. FileSpec[0] = '\0';
  1131. }
  1132. if (!(ActionRoutine)( DirectorySpec, FileSpec ) &&
  1133. !IgnoreErrors) {
  1134. rtncode = 1;
  1135. break;
  1136. }
  1137. }
  1138. }
  1139. }
  1140. //
  1141. // Reset our current directory back
  1142. //
  1143. SetCurrentDirectory( StartingDirectory );
  1144. //
  1145. // And do the final action routine that will print out the final
  1146. // statistics of what we've done
  1147. //
  1148. (FinalActionRoutine)();
  1149. return rtncode;
  1150. }