Leaked source code of windows server 2003
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.

1634 lines
44 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. if (ERROR_INVALID_FUNCTION == GetLastError()) {
  532. // This error is caused by doing the fsctl on a
  533. // non-compressing volume.
  534. DisplayMsg(COMPACT_WRONG_FILE_SYSTEM_OR_CLUSTER_SIZE, DirectorySpec);
  535. } else {
  536. DisplayErr(DirectorySpec, GetLastError());
  537. }
  538. }
  539. CloseHandle( FileHandle );
  540. return FALSE || IgnoreErrors;
  541. }
  542. if (!Quiet) {
  543. DisplayMsg(COMPACT_OK);
  544. }
  545. CloseHandle( FileHandle );
  546. TotalDirectoryCount.QuadPart += 1;
  547. TotalFileCount.QuadPart += 1;
  548. return TRUE;
  549. }
  550. //
  551. // So that we can keep on appending names to the directory spec
  552. // get a pointer to the end of its string
  553. //
  554. DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec );
  555. //
  556. // List the directory that we will be compressing within and say what its
  557. // current compress attribute is
  558. //
  559. {
  560. ULONG Attributes;
  561. if (!Quiet || Quiet) {
  562. Attributes = GetFileAttributes( DirectorySpec );
  563. if (Attributes == 0xFFFFFFFF) {
  564. DisplayErr(DirectorySpec, GetLastError());
  565. return FALSE || IgnoreErrors;
  566. }
  567. if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {
  568. DisplayMsg(COMPACT_COMPRESS_CDIR, DirectorySpec);
  569. } else {
  570. DisplayMsg(COMPACT_COMPRESS_UDIR, DirectorySpec);
  571. }
  572. }
  573. TotalDirectoryCount.QuadPart += 1;
  574. }
  575. //
  576. // Now for every file in the directory that matches the file spec we will
  577. // will open the file and compress it
  578. //
  579. {
  580. HANDLE FindHandle;
  581. HANDLE FileHandle;
  582. WIN32_FIND_DATA FindData;
  583. //
  584. // setup the template for findfirst/findnext
  585. //
  586. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
  587. MAX_PATH) {
  588. lstrcpy( DirectorySpecEnd, FileSpec );
  589. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  590. if (INVALID_HANDLE_VALUE != FindHandle) {
  591. do {
  592. //
  593. // Now skip over the . and .. entries
  594. //
  595. if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  596. 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  597. continue;
  598. } else {
  599. //
  600. // Make sure we don't try any paths that are too long for us
  601. // to deal with.
  602. //
  603. if ( (DirectorySpecEnd - DirectorySpec) +
  604. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  605. continue;
  606. }
  607. //
  608. // append the found file to the directory spec and open
  609. // the file
  610. //
  611. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  612. //
  613. // Hack hack, kludge kludge. Refrain from compressing
  614. // files named "\NTDLR" to help users avoid hosing
  615. // themselves.
  616. //
  617. if (ExcludeThisFile(DirectorySpec)) {
  618. if (!Quiet) {
  619. DisplayMsg(COMPACT_SKIPPING, DirectorySpecEnd);
  620. }
  621. continue;
  622. }
  623. FileHandle = OpenFileForCompress(DirectorySpec);
  624. if (INVALID_HANDLE_VALUE == FileHandle) {
  625. if (!Quiet || !IgnoreErrors) {
  626. DisplayErr(FindData.cFileName, GetLastError());
  627. }
  628. if (!IgnoreErrors) {
  629. FindClose(FindHandle);
  630. return FALSE;
  631. }
  632. continue;
  633. }
  634. //
  635. // Now compress the file
  636. //
  637. if (!CompressFile( FileHandle, DirectorySpec, &FindData )) {
  638. CloseHandle( FileHandle );
  639. FindClose( FindHandle );
  640. return FALSE || IgnoreErrors;
  641. }
  642. //
  643. // Close the file and go get the next file
  644. //
  645. CloseHandle( FileHandle );
  646. }
  647. } while ( FindNextFile( FindHandle, &FindData ));
  648. FindClose( FindHandle );
  649. }
  650. }
  651. }
  652. //
  653. // If we are to do subdirectores then we will look for every subdirectory
  654. // and recursively call ourselves to list the subdirectory
  655. //
  656. if (DoSubdirectories) {
  657. HANDLE FindHandle;
  658. WIN32_FIND_DATA FindData;
  659. //
  660. // Setup findfirst/findnext to search the entire directory
  661. //
  662. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
  663. MAX_PATH) {
  664. lstrcpy( DirectorySpecEnd, TEXT("*") );
  665. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  666. if (INVALID_HANDLE_VALUE != FindHandle) {
  667. do {
  668. //
  669. // Now skip over the . and .. entries otherwise we'll recurse
  670. // like mad
  671. //
  672. if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  673. 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  674. continue;
  675. } else {
  676. //
  677. // If the entry is for a directory then we'll tack on the
  678. // subdirectory name to the directory spec and recursively
  679. // call otherselves
  680. //
  681. if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  682. //
  683. // Make sure we don't try any paths that are too long for us
  684. // to deal with.
  685. //
  686. if ((DirectorySpecEnd - DirectorySpec) +
  687. lstrlen( TEXT("\\") ) +
  688. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  689. continue;
  690. }
  691. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  692. lstrcat( DirectorySpecEnd, TEXT("\\") );
  693. if (!DoCompressAction( DirectorySpec, FileSpec )) {
  694. FindClose( FindHandle );
  695. return FALSE || IgnoreErrors;
  696. }
  697. }
  698. }
  699. } while ( FindNextFile( FindHandle, &FindData ));
  700. FindClose( FindHandle );
  701. }
  702. }
  703. }
  704. return TRUE;
  705. }
  706. VOID
  707. DoFinalCompressAction (
  708. )
  709. {
  710. ULONG TotalPercentage = 100;
  711. double f = 1.0;
  712. TCHAR FileCount[32];
  713. TCHAR DirectoryCount[32];
  714. TCHAR CompressedSize[32];
  715. TCHAR FileSize[32];
  716. TCHAR Percentage[32];
  717. TCHAR Ratio[8];
  718. if (TotalCompressedSize.QuadPart != 0) {
  719. f = (double)TotalFileSize.QuadPart /
  720. (double)TotalCompressedSize.QuadPart;
  721. }
  722. FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
  723. FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
  724. FormatFileSize(&TotalCompressedSize, 0, CompressedSize, TRUE);
  725. FormatFileSize(&TotalFileSize, 0, FileSize, TRUE);
  726. swprintf(Percentage, TEXT("%d"), TotalPercentage);
  727. swprintf(Ratio, TEXT("%2.1f"), f);
  728. if (_tcslen(DecimalPlace) == 1)
  729. Ratio[lstrlen(Ratio)-2] = DecimalPlace[0];
  730. DisplayMsg(COMPACT_COMPRESS_SUMMARY, FileCount, DirectoryCount,
  731. FileSize, CompressedSize, Ratio );
  732. }
  733. BOOLEAN
  734. UncompressFile (
  735. IN HANDLE Handle,
  736. IN PWIN32_FIND_DATA FindData
  737. )
  738. {
  739. USHORT State = 0;
  740. ULONG Length;
  741. if (!(FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) &&
  742. !ForceOperation) {
  743. return TRUE;
  744. }
  745. if (!DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, &State,
  746. sizeof(USHORT), NULL, 0, &Length, FALSE )) {
  747. if (!Quiet || !IgnoreErrors) {
  748. swprintf(Buf, TEXT("%s "), FindData->cFileName);
  749. DisplayMsg(COMPACT_THROW, Buf);
  750. DisplayMsg(COMPACT_ERR);
  751. if (!Quiet && !IgnoreErrors) {
  752. if (ERROR_INVALID_FUNCTION == GetLastError()) {
  753. // This error is caused by doing the fsctl on a
  754. // non-compressing volume.
  755. DisplayMsg(COMPACT_WRONG_FILE_SYSTEM, FindData->cFileName);
  756. } else {
  757. DisplayErr(FindData->cFileName, GetLastError());
  758. }
  759. }
  760. }
  761. return FALSE || IgnoreErrors;
  762. }
  763. if (!Quiet &&
  764. (DisplayAllFiles ||
  765. (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
  766. swprintf(Buf, TEXT("%s "), FindData->cFileName);
  767. DisplayMsg(COMPACT_THROW, Buf);
  768. DisplayMsg(COMPACT_OK);
  769. }
  770. //
  771. // Increment our running total
  772. //
  773. TotalFileCount.QuadPart += 1;
  774. return TRUE;
  775. }
  776. BOOLEAN
  777. DoUncompressAction (
  778. IN PTCHAR DirectorySpec,
  779. IN PTCHAR FileSpec
  780. )
  781. {
  782. PTCHAR DirectorySpecEnd;
  783. //
  784. // If the file spec is null then we'll clear the compression bit for the
  785. // the directory spec and get out.
  786. //
  787. if (lstrlen(FileSpec) == 0) {
  788. HANDLE FileHandle;
  789. USHORT State = 0;
  790. ULONG Length;
  791. FileHandle = OpenFileForCompress(DirectorySpec);
  792. if (INVALID_HANDLE_VALUE == FileHandle) {
  793. if (!Quiet || !IgnoreErrors) {
  794. DisplayErr(DirectorySpec, GetLastError());
  795. }
  796. CloseHandle( FileHandle );
  797. return FALSE || IgnoreErrors;
  798. }
  799. DisplayMsg(COMPACT_UNCOMPRESS_DIR, DirectorySpec);
  800. if (!DeviceIoControl(FileHandle, FSCTL_SET_COMPRESSION, &State,
  801. sizeof(USHORT), NULL, 0, &Length, FALSE )) {
  802. if (!Quiet || !IgnoreErrors) {
  803. DisplayMsg(COMPACT_ERR);
  804. }
  805. if (!Quiet && !IgnoreErrors) {
  806. if (ERROR_INVALID_FUNCTION == GetLastError()) {
  807. // This error is caused by doing the fsctl on a
  808. // non-compressing volume.
  809. DisplayMsg(COMPACT_WRONG_FILE_SYSTEM_OR_CLUSTER_SIZE, DirectorySpec);
  810. } else {
  811. DisplayErr(DirectorySpec, GetLastError());
  812. }
  813. }
  814. CloseHandle( FileHandle );
  815. return FALSE || IgnoreErrors;
  816. }
  817. if (!Quiet) {
  818. DisplayMsg(COMPACT_OK);
  819. }
  820. CloseHandle( FileHandle );
  821. TotalDirectoryCount.QuadPart += 1;
  822. TotalFileCount.QuadPart += 1;
  823. return TRUE;
  824. }
  825. //
  826. // So that we can keep on appending names to the directory spec
  827. // get a pointer to the end of its string
  828. //
  829. DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec );
  830. //
  831. // List the directory that we will be uncompressing within and say what its
  832. // current compress attribute is
  833. //
  834. {
  835. ULONG Attributes;
  836. if (!Quiet || Quiet) {
  837. Attributes = GetFileAttributes( DirectorySpec );
  838. if (Attributes == 0xFFFFFFFF) {
  839. DisplayErr(DirectorySpec, GetLastError());
  840. return FALSE || IgnoreErrors;
  841. }
  842. if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {
  843. DisplayMsg(COMPACT_UNCOMPRESS_CDIR, DirectorySpec);
  844. } else {
  845. DisplayMsg(COMPACT_UNCOMPRESS_UDIR, DirectorySpec);
  846. }
  847. }
  848. TotalDirectoryCount.QuadPart += 1;
  849. }
  850. //
  851. // Now for every file in the directory that matches the file spec we will
  852. // will open the file and uncompress it
  853. //
  854. {
  855. HANDLE FindHandle;
  856. HANDLE FileHandle;
  857. WIN32_FIND_DATA FindData;
  858. //
  859. // setup the template for findfirst/findnext
  860. //
  861. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
  862. MAX_PATH) {
  863. lstrcpy( DirectorySpecEnd, FileSpec );
  864. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  865. if (INVALID_HANDLE_VALUE != FindHandle) {
  866. do {
  867. //
  868. // Now skip over the . and .. entries
  869. //
  870. if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  871. 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  872. continue;
  873. } else {
  874. //
  875. // Make sure we don't try any paths that are too long for us
  876. // to deal with.
  877. //
  878. if ((DirectorySpecEnd - DirectorySpec) +
  879. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  880. continue;
  881. }
  882. //
  883. // append the found file to the directory spec and open
  884. // the file
  885. //
  886. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  887. FileHandle = OpenFileForCompress(DirectorySpec);
  888. if (INVALID_HANDLE_VALUE == FileHandle) {
  889. if (!Quiet || !IgnoreErrors) {
  890. DisplayErr(DirectorySpec, GetLastError());
  891. }
  892. if (!IgnoreErrors) {
  893. FindClose( FindHandle );
  894. return FALSE;
  895. }
  896. continue;
  897. }
  898. //
  899. // Now compress the file
  900. //
  901. if (!UncompressFile( FileHandle, &FindData )) {
  902. CloseHandle( FileHandle );
  903. FindClose( FindHandle );
  904. return FALSE || IgnoreErrors;
  905. }
  906. //
  907. // Close the file and go get the next file
  908. //
  909. CloseHandle( FileHandle );
  910. }
  911. } while ( FindNextFile( FindHandle, &FindData ));
  912. FindClose( FindHandle );
  913. }
  914. }
  915. }
  916. //
  917. // If we are to do subdirectores then we will look for every subdirectory
  918. // and recursively call ourselves to list the subdirectory
  919. //
  920. if (DoSubdirectories) {
  921. HANDLE FindHandle;
  922. WIN32_FIND_DATA FindData;
  923. //
  924. // Setup findfirst/findnext to search the entire directory
  925. //
  926. if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
  927. MAX_PATH) {
  928. lstrcpy( DirectorySpecEnd, TEXT("*") );
  929. FindHandle = FindFirstFile( DirectorySpec, &FindData );
  930. if (INVALID_HANDLE_VALUE != FindHandle) {
  931. do {
  932. //
  933. // Now skip over the . and .. entries otherwise we'll recurse
  934. // like mad
  935. //
  936. if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
  937. 0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {
  938. continue;
  939. } else {
  940. //
  941. // If the entry is for a directory then we'll tack on the
  942. // subdirectory name to the directory spec and recursively
  943. // call otherselves
  944. //
  945. if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  946. //
  947. // Make sure we don't try any paths that are too long for us
  948. // to deal with.
  949. //
  950. if ((DirectorySpecEnd - DirectorySpec) +
  951. lstrlen( TEXT("\\") ) +
  952. lstrlen( FindData.cFileName ) >= MAX_PATH ) {
  953. continue;
  954. }
  955. lstrcpy( DirectorySpecEnd, FindData.cFileName );
  956. lstrcat( DirectorySpecEnd, TEXT("\\") );
  957. if (!DoUncompressAction( DirectorySpec, FileSpec )) {
  958. FindClose( FindHandle );
  959. return FALSE || IgnoreErrors;
  960. }
  961. }
  962. }
  963. } while ( FindNextFile( FindHandle, &FindData ));
  964. FindClose( FindHandle );
  965. }
  966. }
  967. }
  968. return TRUE;
  969. }
  970. VOID
  971. DoFinalUncompressAction (
  972. )
  973. {
  974. TCHAR FileCount[32];
  975. TCHAR DirectoryCount[32];
  976. FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
  977. FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
  978. DisplayMsg(COMPACT_UNCOMPRESS_SUMMARY, FileCount, DirectoryCount);
  979. return;
  980. }
  981. int
  982. __cdecl
  983. main()
  984. {
  985. PTCHAR *argv;
  986. ULONG argc;
  987. ULONG i;
  988. PACTION_ROUTINE ActionRoutine = NULL;
  989. PFINAL_ACTION_ROUTINE FinalActionRoutine = NULL;
  990. BOOLEAN UserSpecifiedFileSpec = FALSE;
  991. TCHAR DirectorySpec[MAX_PATH];
  992. TCHAR FileSpec[MAX_PATH];
  993. PTCHAR p;
  994. INT rtncode;
  995. InitializeIoStreams();
  996. DirectorySpec[0] = '\0';
  997. argv = CommandLineToArgvW(GetCommandLine(), &argc);
  998. if (NULL == argv) {
  999. DisplayErr(NULL, GetLastError());
  1000. return 1;
  1001. }
  1002. //
  1003. // Scan through the arguments looking for switches
  1004. //
  1005. for (i = 1; i < argc; i += 1) {
  1006. if (argv[i][0] == '/') {
  1007. if (0 == lstricmp(argv[i], TEXT("/c"))) {
  1008. if (ActionRoutine != NULL &&
  1009. ActionRoutine != DoCompressAction) {
  1010. DisplayMsg(COMPACT_USAGE, NULL);
  1011. return 1;
  1012. }
  1013. ActionRoutine = DoCompressAction;
  1014. FinalActionRoutine = DoFinalCompressAction;
  1015. } else if (0 == lstricmp(argv[i], TEXT("/u"))) {
  1016. if (ActionRoutine != NULL && ActionRoutine != DoListAction) {
  1017. DisplayMsg(COMPACT_USAGE, NULL);
  1018. return 1;
  1019. }
  1020. ActionRoutine = DoUncompressAction;
  1021. FinalActionRoutine = DoFinalUncompressAction;
  1022. } else if (0 == lstricmp(argv[i], TEXT("/q"))) {
  1023. Quiet = TRUE;
  1024. } else if (0 == lstrnicmp(argv[i], TEXT("/s"), 2)) {
  1025. PTCHAR pch;
  1026. DoSubdirectories = TRUE;
  1027. pch = lstrchr(argv[i], ':');
  1028. if (NULL != pch) {
  1029. lstrcpy(StartingDirectory, pch + 1);
  1030. } else if (2 == lstrlen(argv[i])) {
  1031. // Starting dir is CWD
  1032. GetCurrentDirectory( MAX_PATH, StartingDirectory );
  1033. } else {
  1034. DisplayMsg(COMPACT_USAGE, NULL);
  1035. return 1;
  1036. }
  1037. } else if (0 == lstricmp(argv[i], TEXT("/i"))) {
  1038. IgnoreErrors = TRUE;
  1039. } else if (0 == lstricmp(argv[i], TEXT("/f"))) {
  1040. ForceOperation = TRUE;
  1041. } else if (0 == lstricmp(argv[i], TEXT("/a"))) {
  1042. DisplayAllFiles = TRUE;
  1043. } else {
  1044. DisplayMsg(COMPACT_USAGE, NULL);
  1045. if (0 == lstricmp(argv[i], TEXT("/?")))
  1046. return 0;
  1047. else
  1048. return 1;
  1049. }
  1050. } else {
  1051. UserSpecifiedFileSpec = TRUE;
  1052. }
  1053. }
  1054. //
  1055. // If the use didn't specify an action then set the default to do a listing
  1056. //
  1057. if (ActionRoutine == NULL) {
  1058. ActionRoutine = DoListAction;
  1059. FinalActionRoutine = DoFinalListAction;
  1060. }
  1061. //
  1062. // Get our current directoy because the action routines might move us
  1063. // around
  1064. //
  1065. if (!DoSubdirectories) {
  1066. GetCurrentDirectory( MAX_PATH, StartingDirectory );
  1067. } else if (!SetCurrentDirectory( StartingDirectory )) {
  1068. DisplayErr(StartingDirectory, GetLastError());
  1069. return 1;
  1070. }
  1071. //
  1072. // If the user didn't specify a file spec then we'll do just "*"
  1073. //
  1074. rtncode = 0;
  1075. if (!UserSpecifiedFileSpec) {
  1076. (VOID)GetFullPathName( TEXT("*"), MAX_PATH, DirectorySpec, &p );
  1077. lstrcpy( FileSpec, p ); *p = '\0';
  1078. //
  1079. // Also want to make "compact /c" set the bit for the current
  1080. // directory.
  1081. //
  1082. if (ActionRoutine != DoListAction) {
  1083. if (!(ActionRoutine)( DirectorySpec, TEXT("") ))
  1084. rtncode = 1;
  1085. }
  1086. if (!(ActionRoutine)( DirectorySpec, FileSpec ))
  1087. rtncode = 1;
  1088. } else {
  1089. //
  1090. // Now scan the arguments again looking for non-switches
  1091. // and this time do the action, but before calling reset
  1092. // the current directory so that things work again
  1093. //
  1094. for (i = 1; i < argc; i += 1) {
  1095. if (argv[i][0] != '/') {
  1096. SetCurrentDirectory( StartingDirectory );
  1097. //
  1098. // Handle a command with "." as the file argument specially,
  1099. // since it doesn't make good sense and the results without
  1100. // this code are surprising.
  1101. //
  1102. if ('.' == argv[i][0] && '\0' == argv[i][1]) {
  1103. argv[i] = TEXT("*");
  1104. GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p);
  1105. *p = '\0';
  1106. p = NULL;
  1107. } else {
  1108. PWCHAR pwch;
  1109. GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p);
  1110. //
  1111. // We want to treat "foobie:xxx" as an invalid drive name,
  1112. // rather than as a name identifying a stream. If there's
  1113. // a colon, there should be only a single character before
  1114. // it.
  1115. //
  1116. pwch = wcschr(argv[i], ':');
  1117. if (NULL != pwch && pwch - argv[i] != 1) {
  1118. DisplayMsg(COMPACT_INVALID_PATH, argv[i]);
  1119. rtncode = 1;
  1120. break;
  1121. }
  1122. //
  1123. // GetFullPathName strips trailing dots, but we want
  1124. // to save them so that "*." will work correctly.
  1125. //
  1126. if ((lstrlen(argv[i]) > 0) &&
  1127. ('.' == argv[i][lstrlen(argv[i]) - 1])) {
  1128. lstrcat(DirectorySpec, TEXT("."));
  1129. }
  1130. }
  1131. if (IsUncRoot(DirectorySpec)) {
  1132. //
  1133. // If the path is like \\server\share, we append an
  1134. // additional slash to make things come out right.
  1135. //
  1136. lstrcat(DirectorySpec, TEXT("\\"));
  1137. p = NULL;
  1138. }
  1139. if (p != NULL) {
  1140. lstrcpy( FileSpec, p ); *p = '\0';
  1141. } else {
  1142. FileSpec[0] = '\0';
  1143. }
  1144. if (!(ActionRoutine)( DirectorySpec, FileSpec ) &&
  1145. !IgnoreErrors) {
  1146. rtncode = 1;
  1147. break;
  1148. }
  1149. }
  1150. }
  1151. }
  1152. //
  1153. // Reset our current directory back
  1154. //
  1155. SetCurrentDirectory( StartingDirectory );
  1156. //
  1157. // And do the final action routine that will print out the final
  1158. // statistics of what we've done
  1159. //
  1160. (FinalActionRoutine)();
  1161. return rtncode;
  1162. }