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.

560 lines
17 KiB

  1. // du - simple disk usage program
  2. // If UNICODE/_UNICODE is turned on, we need to link with
  3. // wsetargv.lib (not setargv.lib) and with UMENTRY=wmain
  4. #define UNICODE
  5. #define _UNICODE
  6. #include <stdio.h>
  7. #include <tchar.h>
  8. #include <wchar.h>
  9. #include <string.h>
  10. #include <process.h>
  11. #include <ctype.h>
  12. #include <malloc.h>
  13. #include <stdlib.h>
  14. #include <locale.h>
  15. #include <windows.h>
  16. typedef struct USESTAT USESTAT;
  17. typedef struct EXTSTAT EXTSTAT;
  18. typedef USESTAT *PUSESTAT;
  19. struct USESTAT {
  20. DWORDLONG cchUsed; // bytes used in all files
  21. DWORDLONG cchAlloc; // bytes allocated in all files
  22. DWORDLONG cchCompressed; // compressed bytes in all files
  23. DWORDLONG cchDeleted; // bytes in deleted files
  24. DWORDLONG cFile; // number of files
  25. };
  26. struct EXTSTAT {
  27. EXTSTAT *Next;
  28. TCHAR *Extension;
  29. USESTAT Stat;
  30. };
  31. EXTSTAT *ExtensionList = NULL;
  32. int ExtensionCount = 0;
  33. #define CLEARUSE(use) \
  34. { (use).cchUsed = (DWORDLONG)0; \
  35. (use).cchAlloc = (DWORDLONG)0; \
  36. (use).cchDeleted = (DWORDLONG)0; \
  37. (use).cchCompressed = (DWORDLONG)0; \
  38. (use).cFile = (DWORDLONG)0; \
  39. }
  40. #define ADDUSE(sum,add) \
  41. { (sum).cchUsed += (add).cchUsed; \
  42. (sum).cchAlloc += (add).cchAlloc; \
  43. (sum).cchDeleted += (add).cchDeleted; \
  44. (sum).cchCompressed += (add).cchCompressed; \
  45. (sum).cFile += (add).cFile; \
  46. }
  47. #define DWORD_SHIFT (sizeof(DWORD) * 8)
  48. #define SHIFT(c,v) {c--; v++;}
  49. DWORD gdwOutputMode;
  50. HANDLE ghStdout;
  51. int cDisp; // number of summary lines displayed
  52. BOOL fExtensionStat = FALSE; // TRUE gather statistics by extension
  53. BOOL fNodeSummary = FALSE; // TRUE => only display top-level
  54. BOOL fShowDeleted = FALSE; // TRUE => show deleted files information
  55. BOOL fThousandSeparator = TRUE; // TRUE => use thousand separator in output
  56. BOOL fShowCompressed = FALSE; // TRUE => show compressed file info
  57. BOOL fSubtreeTotal = FALSE; // TRUE => show info in subtree total form (add from bottom up)
  58. BOOL fUnc = FALSE; // Set if we're checking a UNC path.
  59. TCHAR *pszDeleted = TEXT("deleted\\*.*");
  60. long bytesPerAlloc;
  61. int bValidDrive;
  62. DWORDLONG totFree;
  63. DWORDLONG totDisk;
  64. TCHAR buf[MAX_PATH];
  65. TCHAR root[] = TEXT("?:\\");
  66. USESTAT DoDu (TCHAR *dir);
  67. void TotPrint (PUSESTAT puse, TCHAR *p);
  68. void _setenvp(){ } // Don't make a copy of the environment
  69. TCHAR ThousandSeparator[8];
  70. // HACK for MSVCRT - Later, ask BryanT if this is still necessary (IanJa)
  71. extern int _dowildcard;
  72. void __cdecl __wsetargv ( void )
  73. {
  74. _dowildcard = 1;
  75. }
  76. TCHAR *
  77. FormatFileSize(
  78. DWORDLONG FileSize,
  79. TCHAR *FormattedSize,
  80. ULONG Width
  81. )
  82. {
  83. TCHAR Buffer[ 100 ];
  84. TCHAR *s, *s1;
  85. ULONG DigitIndex, Digit;
  86. ULONG nThousandSeparator;
  87. DWORDLONG Size;
  88. nThousandSeparator = _tcslen(ThousandSeparator);
  89. s = &Buffer[ 99 ];
  90. *s = TEXT('\0');
  91. DigitIndex = 0;
  92. Size = FileSize;
  93. while (Size != 0) {
  94. Digit = (ULONG)(Size % 10);
  95. Size = Size / 10;
  96. *--s = (TCHAR)(TEXT('0') + Digit);
  97. if ((++DigitIndex % 3) == 0 && fThousandSeparator) {
  98. // If non-null Thousand separator, insert it.
  99. if (nThousandSeparator) {
  100. s -= nThousandSeparator;
  101. _tcsncpy(s, ThousandSeparator, nThousandSeparator);
  102. }
  103. }
  104. }
  105. if (DigitIndex == 0) {
  106. *--s = TEXT('0');
  107. }
  108. else
  109. if (fThousandSeparator && !_tcsncmp(s, ThousandSeparator, nThousandSeparator)) {
  110. s += nThousandSeparator;
  111. }
  112. Size = _tcslen( s );
  113. if (Width != 0 && Size < Width) {
  114. s1 = FormattedSize;
  115. while (Width > Size) {
  116. Width -= 1;
  117. *s1++ = TEXT(' ');
  118. }
  119. _tcscpy( s1, s );
  120. } else {
  121. _tcscpy( FormattedSize, s );
  122. }
  123. return FormattedSize;
  124. }
  125. #ifdef UNICODE
  126. int __cdecl wmain(int c, wchar_t **v, wchar_t **envp)
  127. #else
  128. int __cdecl main(int c, char *v[])
  129. #endif
  130. {
  131. int tenth, pct;
  132. int bValidBuf;
  133. DWORDLONG tmpTot, tmpFree;
  134. DWORD cSecsPerClus, cBytesPerSec, cFreeClus, cTotalClus;
  135. USESTAT useTot, useTmp;
  136. TCHAR Buffer[MAX_PATH];
  137. TCHAR *p;
  138. UINT Codepage;
  139. char achCodepage[6] = ".OCP";
  140. ghStdout = GetStdHandle(STD_OUTPUT_HANDLE);
  141. GetConsoleMode(ghStdout, &gdwOutputMode);
  142. gdwOutputMode &= ~ENABLE_PROCESSED_OUTPUT;
  143. /*
  144. * This is mainly here as a good example of how to set a character-mode
  145. * application's codepage.
  146. * This affects C-runtime routines such as mbtowc(), mbstowcs(), wctomb(),
  147. * wcstombs(), mblen(), _mbstrlen(), isprint(), isalpha() etc.
  148. * To make sure these C-runtimes come from msvcrt.dll, use TARGETLIBS in
  149. * the sources file, together with TARGETTYPE=PROGRAM (and not UMAPPL?)
  150. */
  151. if (Codepage = GetConsoleOutputCP()) {
  152. sprintf(achCodepage, ".%3.4d", Codepage);
  153. }
  154. setlocale(LC_ALL, achCodepage);
  155. SHIFT (c, v);
  156. if (GetLocaleInfo(GetUserDefaultLCID(),
  157. LOCALE_STHOUSAND,
  158. Buffer,
  159. sizeof(ThousandSeparator)/sizeof(TCHAR))) {
  160. #ifdef UNICODE
  161. _tcscpy(ThousandSeparator, Buffer);
  162. #else
  163. CharToOemA(Buffer, ThousandSeparator);
  164. #endif
  165. }
  166. else {
  167. _tcscpy(ThousandSeparator, TEXT(","));
  168. }
  169. while (c && (**v == TEXT('/') || **v == TEXT('-')))
  170. {
  171. if (!_tcscmp (*v + 1, TEXT("e"))) {
  172. fExtensionStat = TRUE;
  173. } else
  174. if (!_tcscmp (*v + 1, TEXT("s")))
  175. fNodeSummary = TRUE;
  176. else
  177. if (!_tcscmp (*v + 1, TEXT("d")))
  178. fShowDeleted = TRUE;
  179. else
  180. if (!_tcscmp (*v + 1, TEXT("p")))
  181. fThousandSeparator = FALSE;
  182. else
  183. if (!_tcscmp (*v + 1, TEXT("c")))
  184. fShowCompressed = TRUE;
  185. else
  186. if (!_tcscmp (*v + 1, TEXT("t")))
  187. fSubtreeTotal = TRUE;
  188. else
  189. {
  190. fprintf( stderr, "Usage: DU [/e] [/d] [/p] [/s] [/c] [/t] [dirs]\n" );
  191. fprintf( stderr, "where:\n" );
  192. fprintf( stderr, " /e - displays information by extension.\n" );
  193. fprintf( stderr, " /d - displays informations about [deleted] subdirectories.\n" );
  194. fprintf( stderr, " /p - displays numbers plainly, without thousand separators.\n" );
  195. fprintf( stderr, " /s - displays summary information only.\n" );
  196. fprintf( stderr, " /c - displays compressed file information.\n" );
  197. fprintf( stderr, " /t - displays information in subtree total form.\n" );
  198. exit (1);
  199. }
  200. SHIFT (c, v);
  201. }
  202. if (c == 0)
  203. {
  204. GetCurrentDirectory( MAX_PATH, (LPTSTR)buf );
  205. root[0] = buf[0];
  206. if( bValidDrive = GetDiskFreeSpace( root,
  207. &cSecsPerClus,
  208. &cBytesPerSec,
  209. &cFreeClus,
  210. &cTotalClus ) == TRUE )
  211. {
  212. bytesPerAlloc = cBytesPerSec * cSecsPerClus;
  213. totFree = (DWORDLONG)bytesPerAlloc * cFreeClus;
  214. totDisk = (DWORDLONG)bytesPerAlloc * cTotalClus;
  215. }
  216. useTot = DoDu (buf);
  217. if (fNodeSummary)
  218. TotPrint (&useTot, buf);
  219. }
  220. else
  221. {
  222. CLEARUSE (useTot);
  223. while (c)
  224. {
  225. LPTSTR FilePart;
  226. bValidBuf = GetFullPathName( *v, MAX_PATH, buf, &FilePart);
  227. if ( bValidBuf )
  228. {
  229. if ( buf[0] == TEXT('\\') ) {
  230. fUnc = TRUE;
  231. bValidDrive = TRUE;
  232. bytesPerAlloc = 1;
  233. } else {
  234. root[0] = buf[0];
  235. if( bValidDrive = GetDiskFreeSpace( root,
  236. &cSecsPerClus,
  237. &cBytesPerSec,
  238. &cFreeClus,
  239. &cTotalClus ) == TRUE)
  240. {
  241. bytesPerAlloc = cBytesPerSec * cSecsPerClus;
  242. totFree = (DWORDLONG)bytesPerAlloc * cFreeClus;
  243. totDisk = (DWORDLONG)bytesPerAlloc * cTotalClus;
  244. } else
  245. _tprintf (TEXT("Invalid drive or directory %s\n"), *v );
  246. }
  247. if( bValidDrive && (GetFileAttributes( buf ) & FILE_ATTRIBUTE_DIRECTORY ) != 0 )
  248. {
  249. useTmp = DoDu (buf);
  250. if (fNodeSummary)
  251. TotPrint (&useTmp, buf);
  252. ADDUSE (useTot, useTmp);
  253. }
  254. }
  255. else
  256. _tprintf (TEXT("Invalid drive or directory %s\n"), *v );
  257. SHIFT (c, v);
  258. }
  259. }
  260. if (cDisp != 0)
  261. {
  262. if (cDisp > 1)
  263. TotPrint (&useTot, TEXT("Total"));
  264. /* quick full-disk test */
  265. if ( !fUnc ) {
  266. if (totFree == 0)
  267. puts ("Disk is full");
  268. else
  269. {
  270. tmpTot = (totDisk + 1023) / 1024;
  271. tmpFree = (totFree + 1023) / 1024;
  272. pct = (DWORD)(1000 * (tmpTot - tmpFree) / tmpTot);
  273. tenth = pct % 10;
  274. pct /= 10;
  275. // Disable processing so Middle Dot won't beep
  276. // Middle Dot 0x2022 aliases to ^G when using Raster Fonts
  277. SetConsoleMode(ghStdout, gdwOutputMode);
  278. _tprintf(TEXT("%s/"), FormatFileSize( totDisk-totFree, Buffer, 0 ));
  279. _tprintf(TEXT("%s "), FormatFileSize( totDisk, Buffer, 0 ));
  280. // Re-enable processing so newline works
  281. SetConsoleMode(ghStdout, gdwOutputMode | ENABLE_PROCESSED_OUTPUT);
  282. _tprintf (TEXT("%d.%d%% of disk in use\n"), pct, tenth);
  283. }
  284. }
  285. }
  286. if (fExtensionStat) {
  287. int i;
  288. printf( "\n" );
  289. for (i = 0; i < ExtensionCount; i++) {
  290. TotPrint( &ExtensionList[i].Stat, ExtensionList[i].Extension );
  291. }
  292. }
  293. return( 0 );
  294. }
  295. int __cdecl ExtSearchCompare( const void *Key, const void *Element)
  296. {
  297. return _tcsicmp( (TCHAR *)Key, ((EXTSTAT *) Element)->Extension );
  298. }
  299. int __cdecl ExtSortCompare( const void *Element1, const void *Element2)
  300. {
  301. return _tcsicmp( ((EXTSTAT *) Element1)->Extension, ((EXTSTAT *) Element2)->Extension );
  302. }
  303. #define MYMAKEDWORDLONG(h,l) (((DWORDLONG)(h) << DWORD_SHIFT) + (DWORDLONG)(l))
  304. #define FILESIZE(wfd) MYMAKEDWORDLONG((wfd).nFileSizeHigh, (wfd).nFileSizeLow)
  305. #define ROUNDUP(m,n) ((((m) + (n) - 1) / (n)) * (n))
  306. USESTAT DoDu (TCHAR *dir)
  307. {
  308. WIN32_FIND_DATA wfd;
  309. HANDLE hFind;
  310. USESTAT use, DirUse;
  311. TCHAR pszSearchName[MAX_PATH];
  312. TCHAR *pszFilePart;
  313. DWORDLONG compressedSize;
  314. DWORD compHi, compLo;
  315. CLEARUSE(use);
  316. // Make a copy of the incoming directory name and append a trailing
  317. // slash if necessary. pszFilePart will point to the char just after
  318. // the slash, making it easy to build fully qualified filenames.
  319. _tcscpy(pszSearchName, dir);
  320. pszFilePart = pszSearchName + _tcslen(pszSearchName);
  321. if (pszFilePart > pszSearchName)
  322. {
  323. if (pszFilePart[-1] != TEXT('\\') && pszFilePart[-1] != TEXT('/'))
  324. {
  325. *pszFilePart++ = TEXT('\\');
  326. }
  327. }
  328. if (fShowDeleted) {
  329. // First count the size of all the files in the current deleted tree
  330. _tcscpy(pszFilePart, pszDeleted);
  331. hFind = FindFirstFile(pszSearchName, &wfd);
  332. if (hFind != INVALID_HANDLE_VALUE)
  333. {
  334. do
  335. {
  336. if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  337. {
  338. use.cchDeleted += ROUNDUP( FILESIZE( wfd ), bytesPerAlloc );
  339. }
  340. } while (FindNextFile(hFind, &wfd));
  341. FindClose(hFind);
  342. }
  343. }
  344. // Then count the size of all the file in the current tree.
  345. _tcscpy(pszFilePart, TEXT("*.*"));
  346. hFind = FindFirstFile(pszSearchName, &wfd);
  347. if (hFind != INVALID_HANDLE_VALUE)
  348. {
  349. do
  350. {
  351. if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  352. {
  353. use.cchUsed += FILESIZE( wfd );
  354. use.cchAlloc += ROUNDUP( FILESIZE( wfd ), bytesPerAlloc );
  355. use.cFile++;
  356. compressedSize = FILESIZE(wfd);
  357. if (fShowCompressed && (wfd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED))
  358. {
  359. _tcscpy(pszFilePart, wfd.cFileName);
  360. compLo = GetCompressedFileSize(pszSearchName, &compHi);
  361. if (compLo != (DWORD)-1 || GetLastError() == 0) {
  362. compressedSize = MYMAKEDWORDLONG(compHi, compLo);
  363. }
  364. }
  365. use.cchCompressed += compressedSize;
  366. //
  367. // Accrue statistics by extension
  368. //
  369. if (fExtensionStat) {
  370. TCHAR Ext[_MAX_EXT];
  371. EXTSTAT *ExtensionStat;
  372. _tsplitpath( wfd.cFileName, NULL, NULL, NULL, Ext );
  373. while (TRUE) {
  374. //
  375. // Find extension in list
  376. //
  377. ExtensionStat =
  378. (EXTSTAT *) bsearch( Ext, ExtensionList,
  379. ExtensionCount, sizeof( EXTSTAT ),
  380. ExtSearchCompare );
  381. if (ExtensionStat != NULL) {
  382. break;
  383. }
  384. //
  385. // Extension not found, go add one and resort
  386. //
  387. ExtensionCount++;
  388. ExtensionList =
  389. (EXTSTAT *)realloc( ExtensionList,
  390. sizeof( EXTSTAT ) * ExtensionCount);
  391. ExtensionList[ExtensionCount - 1].Extension = _tcsdup( Ext );
  392. CLEARUSE( ExtensionList[ExtensionCount - 1].Stat );
  393. qsort( ExtensionList, ExtensionCount, sizeof( EXTSTAT ), ExtSortCompare );
  394. }
  395. ExtensionStat->Stat.cchUsed += FILESIZE( wfd );
  396. ExtensionStat->Stat.cchAlloc += ROUNDUP( FILESIZE( wfd ), bytesPerAlloc );
  397. ExtensionStat->Stat.cchCompressed += compressedSize;
  398. ExtensionStat->Stat.cFile++;
  399. }
  400. }
  401. } while (FindNextFile(hFind, &wfd));
  402. FindClose(hFind);
  403. }
  404. if (!fNodeSummary && !fSubtreeTotal)
  405. TotPrint (&use, dir);
  406. // Now, do all the subdirs and return the current total.
  407. _tcscpy(pszFilePart, TEXT("*.*"));
  408. hFind = FindFirstFile(pszSearchName, &wfd);
  409. if (hFind != INVALID_HANDLE_VALUE)
  410. {
  411. do
  412. {
  413. if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
  414. _tcsicmp (wfd.cFileName, TEXT("deleted")) &&
  415. _tcscmp (wfd.cFileName, TEXT(".")) &&
  416. _tcscmp (wfd.cFileName, TEXT("..")))
  417. {
  418. _tcscpy(pszFilePart, wfd.cFileName);
  419. DirUse = DoDu(pszSearchName);
  420. ADDUSE(use, DirUse);
  421. }
  422. } while (FindNextFile(hFind, &wfd));
  423. FindClose(hFind);
  424. }
  425. if (fSubtreeTotal)
  426. TotPrint(&use, dir);
  427. return(use);
  428. }
  429. void TotPrint (PUSESTAT puse, TCHAR *p)
  430. {
  431. static BOOL fFirst = TRUE;
  432. TCHAR Buffer[MAX_PATH];
  433. TCHAR *p1;
  434. if (fFirst) {
  435. // XXX,XXX,XXX,XXX XXX,XXX,XXX,XXX xx,xxx,xxx name
  436. _tprintf( TEXT(" Used Allocated %s%s Files\n"),
  437. fShowCompressed ? TEXT(" Compressed ") : TEXT(""),
  438. // XXX,XXX,XXX,XXX
  439. fShowDeleted ? TEXT(" Deleted ") : TEXT("")
  440. // XXX,XXX,XXX,XXX
  441. );
  442. fFirst = FALSE;
  443. }
  444. // Disable processing so Middle Dot won't beep
  445. // Middle Dot 0x2022 aliases to ^G when using Raster Fonts
  446. SetConsoleMode(ghStdout, gdwOutputMode);
  447. _tprintf(TEXT("%s "), FormatFileSize( puse->cchUsed, Buffer, 15 ));
  448. _tprintf(TEXT("%s "), FormatFileSize( puse->cchAlloc, Buffer, 15 ));
  449. if (fShowCompressed) {
  450. _tprintf(TEXT("%s "), FormatFileSize( puse->cchCompressed, Buffer, 15 ));
  451. }
  452. if (fShowDeleted) {
  453. _tprintf(TEXT("%s "), FormatFileSize( puse->cchDeleted, Buffer, 15 ));
  454. }
  455. _tprintf(TEXT("%s "), FormatFileSize( puse->cFile, Buffer, 10 ));
  456. _tprintf(TEXT("%s"),p);
  457. // Re-enable processing so newline works
  458. SetConsoleMode(ghStdout, gdwOutputMode | ENABLE_PROCESSED_OUTPUT);
  459. _tprintf(TEXT("\n"));
  460. cDisp++;
  461. }