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.

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