migrate substitution keywords to SVN
[reactos.git] / reactos / subsys / system / cmd / dir.c
1 /* $Id$
2 *
3 * DIR.C - dir internal command.
4 *
5 *
6 * History:
7 *
8 * 01/29/97 (Tim Norman)
9 * started.
10 *
11 * 06/13/97 (Tim Norman)
12 * Fixed code.
13 *
14 * 07/12/97 (Tim Norman)
15 * Fixed bug that caused the root directory to be unlistable
16 *
17 * 07/12/97 (Marc Desrochers)
18 * Changed to use maxx, maxy instead of findxy()
19 *
20 * 06/08/98 (Rob Lake)
21 * Added compatibility for /w in dir
22 *
23 * 06/09/98 (Rob Lake)
24 * Compatibility for dir/s started
25 * Tested that program finds directories off root fine
26 *
27 * 06/10/98 (Rob Lake)
28 * do_recurse saves the cwd and also stores it in Root
29 * build_tree adds the cwd to the beginning of its' entries
30 * Program runs fine, added print_tree -- works fine.. as EXE,
31 * program won't work properly as COM.
32 *
33 * 06/11/98 (Rob Lake)
34 * Found problem that caused COM not to work
35 *
36 * 06/12/98 (Rob Lake)
37 * debugged...
38 * added free mem routine
39 *
40 * 06/13/98 (Rob Lake)
41 * debugged the free mem routine
42 * debugged whole thing some more
43 * Notes:
44 * ReadDir stores Root name and _Read_Dir does the hard work
45 * PrintDir prints Root and _Print_Dir does the hard work
46 * KillDir kills Root _after_ _Kill_Dir does the hard work
47 * Integrated program into DIR.C(this file) and made some same
48 * changes throughout
49 *
50 * 06/14/98 (Rob Lake)
51 * Cleaned up code a bit, added comments
52 *
53 * 06/16/98 (Rob Lake)
54 * Added error checking to my previously added routines
55 *
56 * 06/17/98 (Rob Lake)
57 * Rewrote recursive functions, again! Most other recursive
58 * functions are now obsolete -- ReadDir, PrintDir, _Print_Dir,
59 * KillDir and _Kill_Dir. do_recurse does what PrintDir did
60 * and _Read_Dir did what it did before along with what _Print_Dir
61 * did. Makes /s a lot faster!
62 * Reports 2 more files/dirs that MS-DOS actually reports
63 * when used in root directory(is this because dir defaults
64 * to look for read only files?)
65 * Added support for /b, /a and /l
66 * Made error message similar to DOS error messages
67 * Added help screen
68 *
69 * 06/20/98 (Rob Lake)
70 * Added check for /-(switch) to turn off previously defined
71 * switches.
72 * Added ability to check for DIRCMD in environment and
73 * process it
74 *
75 * 06/21/98 (Rob Lake)
76 * Fixed up /B
77 * Now can dir *.ext/X, no spaces!
78 *
79 * 06/29/98 (Rob Lake)
80 * error message now found in command.h
81 *
82 * 07/08/1998 (John P. Price)
83 * removed extra returns; closer to MSDOS
84 * fixed wide display so that an extra return is not displayed
85 * when there is five filenames in the last line.
86 *
87 * 07/12/98 (Rob Lake)
88 * Changed error messages
89 *
90 * 27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
91 * added config.h include
92 *
93 *
94 * 04-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
95 * Converted source code to Win32, except recursive dir ("dir /s").
96 *
97 * 10-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
98 * Fixed recursive dir ("dir /s").
99 *
100 * 14-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
101 * Converted to Win32 directory functions and
102 * fixed some output bugs. There are still some more ;)
103 *
104 * 10-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
105 * Added "/N" and "/4" options, "/O" is a dummy.
106 * Added locale support.
107 *
108 * 20-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
109 * Redirection safe!
110 *
111 * 01-Mar-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
112 * Replaced all runtime io functions by their Win32 counterparts.
113 *
114 * 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
115 * dir /s now works in deeper trees
116 *
117 * 28-Jan-2004 (Michael Fritscher <michael@fritscher.net>)
118 * Fix for /p, so it is working under Windows in GUI-mode, too.
119 *
120 * 30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
121 * Fix /w to print long names.
122 */
123
124 #include "precomp.h"
125
126 #ifdef INCLUDE_CMD_DIR
127
128
129 /* flag definitions */
130 enum
131 {
132 DIR_RECURSE = 0x0001,
133 DIR_PAGE = 0x0002,
134 DIR_WIDE = 0x0004, /* Rob Lake */
135 DIR_BARE = 0x0008, /* Rob Lake */
136 DIR_ALL = 0x0010, /* Rob Lake */
137 DIR_LWR = 0x0020, /* Rob Lake */
138 DIR_SORT = 0x0040, /* /O sort */
139 DIR_NEW = 0x0080, /* /N new style */
140 DIR_FOUR = 0x0100 /* /4 four digit year */
141 };
142
143
144 typedef BOOL STDCALL
145 (*PGETFREEDISKSPACEEX)(LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
146
147
148 /* Globally save the # of dirs, files and bytes,
149 * probabaly later pass them to functions. Rob Lake */
150 static ULONG recurse_dir_cnt;
151 static ULONG recurse_file_cnt;
152 static ULARGE_INTEGER recurse_bytes;
153
154
155 /*
156 * help
157 *
158 * displays help screen for dir
159 * Rob Lake
160 */
161 static VOID Help (VOID)
162 {
163 ConOutPuts(_T("Displays a list of files and subdirectories in a directory.\n"
164 "\n"
165 "DIR [drive:][path][filename] [/A] [/B] [/L] [/N] [/S] [/P] [/W] [/4]\n"
166 "\n"
167 " [drive:][path][filename]\n"
168 " Specifies drive, directory, and/or files to list.\n"
169 "\n"
170 " /A Displays files with HIDDEN SYSTEM attributes\n"
171 " default is ARCHIVE and READ ONLY\n"
172 " /B Uses bare format (no heading information or summary).\n"
173 " /L Uses lowercase.\n"
174 " /N New long list format where filenames are on the far right.\n"
175 " /S Displays files in specified directory and all subdirectories\n"
176 " /P Pauses after each screen full\n"
177 " /W Prints in wide format\n"
178 " /4 Display four digit years.\n"
179 "\n"
180 "Switches may be present in the DIRCMD environment variable. Use\n"
181 "of the - (hyphen) can turn off defined swtiches. Ex. /-W would\n"
182 "turn off printing in wide format.\n"
183 ));
184 }
185
186
187 /*
188 * DirReadParam
189 *
190 * read the parameters from the command line
191 */
192 static BOOL
193 DirReadParam (LPTSTR line, LPTSTR *param, LPDWORD lpFlags)
194 {
195 INT slash = 0;
196
197 if (!line)
198 return TRUE;
199
200 *param = NULL;
201
202 /* scan the command line, processing switches */
203 while (*line)
204 {
205 /* process switch */
206 if (*line == _T('/') || slash)
207 {
208 if (!slash)
209 line++;
210 slash = 0;
211 if (*line == _T('-'))
212 {
213 line++;
214 if (_totupper (*line) == _T('S'))
215 *lpFlags &= ~DIR_RECURSE;
216 else if (_totupper (*line) == _T('P'))
217 *lpFlags &= ~DIR_PAGE;
218 else if (_totupper (*line) == _T('W'))
219 *lpFlags &= ~DIR_WIDE;
220 else if (_totupper (*line) == _T('B'))
221 *lpFlags &= ~DIR_BARE;
222 else if (_totupper (*line) == _T('A'))
223 *lpFlags &= ~DIR_ALL;
224 else if (_totupper (*line) == _T('L'))
225 *lpFlags &= ~DIR_LWR;
226 else if (_totupper (*line) == _T('N'))
227 *lpFlags &= ~DIR_NEW;
228 else if (_totupper (*line) == _T('O'))
229 *lpFlags &= ~DIR_SORT;
230 else if (_totupper (*line) == _T('4'))
231 *lpFlags &= ~DIR_FOUR;
232 else
233 {
234 error_invalid_switch ((TCHAR)_totupper (*line));
235 return FALSE;
236 }
237 line++;
238 continue;
239 }
240 else
241 {
242 if (_totupper (*line) == _T('S'))
243 *lpFlags |= DIR_RECURSE;
244 else if (_totupper (*line) == _T('P'))
245 *lpFlags |= DIR_PAGE;
246 else if (_totupper (*line) == _T('W'))
247 *lpFlags |= DIR_WIDE;
248 else if (_totupper (*line) == _T('B'))
249 *lpFlags |= DIR_BARE;
250 else if (_totupper (*line) == _T('A'))
251 *lpFlags |= DIR_ALL;
252 else if (_totupper (*line) == _T('L'))
253 *lpFlags |= DIR_LWR;
254 else if (_totupper (*line) == _T('N'))
255 *lpFlags |= DIR_NEW;
256 else if (_totupper (*line) == _T('O'))
257 *lpFlags |= DIR_SORT;
258 else if (_totupper (*line) == _T('4'))
259 *lpFlags |= DIR_FOUR;
260 else if (*line == _T('?'))
261 {
262 Help();
263 return FALSE;
264 }
265 else
266 {
267 error_invalid_switch ((TCHAR)_totupper (*line));
268 return FALSE;
269 }
270 line++;
271 continue;
272 }
273 }
274
275 /* process parameter */
276 if (!_istspace (*line))
277 {
278 if (*param)
279 {
280 error_too_many_parameters (line);
281 return FALSE;
282 }
283
284 *param = line;
285
286 /* skip to end of line or next whitespace or next / */
287 if (*line != _T('\"'))
288 {
289 while (*line && !_istspace (*line) && *line != _T('/'))
290 line++;
291
292 /* if end of line, return */
293 if (!*line)
294 return TRUE;
295 }
296 else
297 {
298 /* skip over the initial quote */
299 (*param)++;
300 line++;
301
302 while (*line && *line != _T('"'))
303 line++;
304
305 if (*line == _T('"'))
306 *line = 0;
307 else
308 return TRUE;
309 }
310
311 /* if parameter, remember to process it later */
312 if (*line == _T('/'))
313 slash = 1;
314
315 *line++ = 0;
316 continue;
317 }
318
319 line++;
320 }
321
322 if (slash)
323 {
324 error_invalid_switch ((TCHAR)_totupper (*line));
325 return FALSE;
326 }
327
328 return TRUE;
329 }
330
331
332 /*
333 * ExtendFilespec
334 *
335 * extend the filespec, possibly adding wildcards
336 */
337 static VOID
338 ExtendFilespec (LPTSTR file)
339 {
340 INT len = 0;
341
342 if (!file)
343 return;
344
345 /* if no file spec, change to "*.*" */
346 if (*file == _T('\0'))
347 {
348 _tcscpy (file, _T("*.*"));
349 return;
350 }
351
352 /* if starts with . add * in front */
353 if (*file == _T('.'))
354 {
355 memmove (&file[1], &file[0], (_tcslen (file) + 1) * sizeof(TCHAR));
356 file[0] = _T('*');
357 }
358
359 /* if no . add .* */
360 if (!_tcschr (file, _T('.')))
361 {
362 _tcscat (file, _T(".*"));
363 return;
364 }
365
366 /* if last character is '.' add '*' */
367 len = _tcslen (file);
368 if (file[len - 1] == _T('.'))
369 {
370 _tcscat (file, _T("*"));
371 return;
372 }
373 }
374
375
376 /*
377 * dir_parse_pathspec
378 *
379 * split the pathspec into drive, directory, and filespec
380 */
381 static INT
382 DirParsePathspec (LPTSTR szPathspec, LPTSTR szPath, LPTSTR szFilespec)
383 {
384 TCHAR szOrigPath[MAX_PATH];
385 LPTSTR start;
386 LPTSTR tmp;
387 INT i;
388 BOOL bWildcards = FALSE;
389
390 GetCurrentDirectory (MAX_PATH, szOrigPath);
391
392 /* get the drive and change to it */
393 if (szPathspec[1] == _T(':'))
394 {
395 TCHAR szRootPath[] = _T("A:");
396
397 szRootPath[0] = szPathspec[0];
398 start = szPathspec + 2;
399 if (!SetCurrentDirectory (szRootPath))
400 {
401 ErrorMessage (GetLastError(), NULL);
402 return 1;
403 }
404 }
405 else
406 {
407 start = szPathspec;
408 }
409
410
411 /* check for wildcards */
412 for (i = 0; szPathspec[i]; i++)
413 {
414 if (szPathspec[i] == _T('*') || szPathspec[i] == _T('?'))
415 bWildcards = TRUE;
416 }
417
418 /* check if this spec is a directory */
419 if (!bWildcards)
420 {
421 if (SetCurrentDirectory (szPathspec))
422 {
423 _tcscpy (szFilespec, _T("*.*"));
424
425 if (!GetCurrentDirectory (MAX_PATH, szPath))
426 {
427 szFilespec[0] = _T('\0');
428 SetCurrentDirectory (szOrigPath);
429 error_out_of_memory();
430 return 1;
431 }
432
433 SetCurrentDirectory (szOrigPath);
434 return 0;
435 }
436 }
437
438 /* find the file spec */
439 tmp = _tcsrchr (start, _T('\\'));
440
441 /* if no path is specified */
442 if (!tmp)
443 {
444 _tcscpy (szFilespec, start);
445 ExtendFilespec (szFilespec);
446 if (!GetCurrentDirectory (MAX_PATH, szPath))
447 {
448 szFilespec[0] = _T('\0');
449 SetCurrentDirectory (szOrigPath);
450 error_out_of_memory();
451 return 1;
452 }
453
454 SetCurrentDirectory (szOrigPath);
455 return 0;
456 }
457
458 /* get the filename */
459 _tcscpy (szFilespec, tmp+1);
460 ExtendFilespec (szFilespec);
461
462 if (tmp == start)
463 {
464 /* change to the root directory */
465 if (!SetCurrentDirectory (_T("\\")))
466 {
467 szFilespec[0] = _T('\0');
468 SetCurrentDirectory (szOrigPath);
469 error_path_not_found ();
470 return 1;
471 }
472 }
473 else
474 {
475
476 *tmp = _T('\0');
477
478 /* change to this directory */
479 if (!SetCurrentDirectory (start))
480 {
481 *tmp = _T('\\');
482 szFilespec[0] = _T('\0');
483 SetCurrentDirectory (szOrigPath);
484 error_path_not_found ();
485 return 1;
486 }
487 }
488 /* get the full name of the directory */
489 if (!GetCurrentDirectory (MAX_PATH, szPath))
490 {
491 *tmp = _T('\\');
492 szFilespec[0] = _T('\0');
493 SetCurrentDirectory (szOrigPath);
494 error_out_of_memory ();
495 return 1;
496 }
497
498 *tmp = _T('\\');
499
500 SetCurrentDirectory (szOrigPath);
501
502 return 0;
503 }
504
505
506 /*
507 * incline
508 *
509 * increment our line if paginating, display message at end of screen
510 */
511 static BOOL
512 IncLine (LPINT pLine, DWORD dwFlags)
513 {
514 BOOL error;
515 CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo;
516 LONG WindowHeight;
517 error = GetConsoleScreenBufferInfo(hConsole, &lpConsoleScreenBufferInfo);
518
519 WindowHeight= lpConsoleScreenBufferInfo.srWindow.Bottom - lpConsoleScreenBufferInfo.srWindow.Top;
520
521 if (!WindowHeight) //That prevents bad behave if WindowHeight couln't calc
522 {
523 WindowHeight= 1000000;
524 }
525 if (!(dwFlags & DIR_PAGE))
526 return FALSE;
527
528 (*pLine)++;
529
530 if (*pLine >= (int)maxy - 2 || *pLine >= WindowHeight) //Because I don't know if WindowsHeight work under all cases, perhaps then maxy is the right value
531 {
532 *pLine = 0;
533 return (PagePrompt () == PROMPT_BREAK);
534 }
535
536 return FALSE;
537 }
538
539
540 /*
541 * PrintDirectoryHeader
542 *
543 * print the header for the dir command
544 */
545 static BOOL
546 PrintDirectoryHeader (LPTSTR szPath, LPINT pLine, DWORD dwFlags)
547 {
548 TCHAR szRootName[MAX_PATH];
549 TCHAR szVolName[80];
550 DWORD dwSerialNr;
551 LPTSTR p;
552
553 if (dwFlags & DIR_BARE)
554 return(TRUE);
555
556 /* build usable root path */
557 if (szPath[1] == _T(':') && szPath[2] == _T('\\'))
558 {
559 /* normal path */
560 szRootName[0] = szPath[0];
561 szRootName[1] = _T(':');
562 szRootName[2] = _T('\\');
563 szRootName[3] = 0;
564 }
565 else if (szPath[0] == _T('\\') && szPath[1] == _T('\\'))
566 {
567 /* UNC path */
568 p = _tcschr(&szPath[2], _T('\\'));
569 if (p == NULL)
570 {
571 error_invalid_drive();
572 return(FALSE);
573 }
574 p = _tcschr(p+1, _T('\\'));
575 if (p == NULL)
576 {
577 _tcscpy(szRootName, szPath);
578 _tcscat(szRootName, _T("\\"));
579 }
580 else
581 {
582 *p = 0;
583 _tcscpy(szRootName, szPath);
584 _tcscat(szRootName, _T("\\"));
585 *p = _T('\\');
586 }
587 }
588 else
589 {
590 error_invalid_drive();
591 return(FALSE);
592 }
593
594 /* get the media ID of the drive */
595 if (!GetVolumeInformation(szRootName, szVolName, 80, &dwSerialNr,
596 NULL, NULL, NULL, 0))
597 {
598 error_invalid_drive();
599 return(FALSE);
600 }
601
602 /* print drive info */
603 ConOutPrintf(_T(" Volume in drive %c"), szRootName[0]);
604
605 if (szVolName[0] != _T('\0'))
606 ConOutPrintf(_T(" is %s\n"), szVolName);
607 else
608 ConOutPrintf(_T(" has no label\n"));
609
610 if (IncLine(pLine, dwFlags))
611 return(FALSE);
612
613 /* print the volume serial number if the return was successful */
614 ConOutPrintf(_T(" Volume Serial Number is %04X-%04X\n"),
615 HIWORD(dwSerialNr),
616 LOWORD(dwSerialNr));
617 if (IncLine(pLine, dwFlags))
618 return(FALSE);
619
620 return(TRUE);
621 }
622
623
624 /*
625 * convert
626 *
627 * insert commas into a number
628 */
629 static INT
630 ConvertULong (ULONG num, LPTSTR des, INT len)
631 {
632 TCHAR temp[32];
633 INT c = 0;
634 INT n = 0;
635
636 if (num == 0)
637 {
638 des[0] = _T('0');
639 des[1] = _T('\0');
640 n = 1;
641 }
642 else
643 {
644 temp[31] = 0;
645 while (num > 0)
646 {
647 if (((c + 1) % (nNumberGroups + 1)) == 0)
648 temp[30 - c++] = cThousandSeparator;
649 temp[30 - c++] = (TCHAR)(num % 10) + _T('0');
650 num /= 10;
651 }
652
653 for (n = 0; n <= c; n++)
654 des[n] = temp[31 - c + n];
655 }
656
657 return n;
658 }
659
660
661 static INT
662 ConvertULargeInteger (ULARGE_INTEGER num, LPTSTR des, INT len)
663 {
664 TCHAR temp[32];
665 INT c = 0;
666 INT n = 0;
667
668 if (num.QuadPart == 0)
669 {
670 des[0] = _T('0');
671 des[1] = _T('\0');
672 n = 1;
673 }
674 else
675 {
676 temp[31] = 0;
677 while (num.QuadPart > 0)
678 {
679 if (((c + 1) % (nNumberGroups + 1)) == 0)
680 temp[30 - c++] = cThousandSeparator;
681 temp[30 - c++] = (TCHAR)(num.QuadPart % 10) + _T('0');
682 num.QuadPart /= 10;
683 }
684
685 for (n = 0; n <= c; n++)
686 des[n] = temp[31 - c + n];
687 }
688
689 return n;
690 }
691
692
693 static VOID
694 PrintFileDateTime (LPSYSTEMTIME dt, DWORD dwFlags)
695 {
696 WORD wYear = (dwFlags & DIR_FOUR) ? dt->wYear : dt->wYear%100;
697
698 switch (nDateFormat)
699 {
700 case 0: /* mmddyy */
701 default:
702 ConOutPrintf (_T("%.2d%c%.2d%c%d"),
703 dt->wMonth, cDateSeparator, dt->wDay, cDateSeparator, wYear);
704 break;
705
706 case 1: /* ddmmyy */
707 ConOutPrintf (_T("%.2d%c%.2d%c%d"),
708 dt->wDay, cDateSeparator, dt->wMonth, cDateSeparator, wYear);
709 break;
710
711 case 2: /* yymmdd */
712 ConOutPrintf (_T("%d%c%.2d%c%.2d"),
713 wYear, cDateSeparator, dt->wMonth, cDateSeparator, dt->wDay);
714 break;
715 }
716
717 switch (nTimeFormat)
718 {
719 case 0: /* 12 hour format */
720 default:
721 ConOutPrintf (_T(" %2d%c%.2u%c"),
722 (dt->wHour == 0 ? 12 : (dt->wHour <= 12 ? dt->wHour : dt->wHour - 12)),
723 cTimeSeparator,
724 dt->wMinute, (dt->wHour <= 11 ? 'a' : 'p'));
725 break;
726
727 case 1: /* 24 hour format */
728 ConOutPrintf (_T(" %2d%c%.2u"),
729 dt->wHour, cTimeSeparator, dt->wMinute);
730 break;
731 }
732 }
733
734
735 static VOID
736 GetUserDiskFreeSpace(LPCTSTR lpRoot,
737 PULARGE_INTEGER lpFreeSpace)
738 {
739 PGETFREEDISKSPACEEX pGetFreeDiskSpaceEx;
740 HINSTANCE hInstance;
741 DWORD dwSecPerCl;
742 DWORD dwBytPerSec;
743 DWORD dwFreeCl;
744 DWORD dwTotCl;
745 ULARGE_INTEGER TotalNumberOfBytes, TotalNumberOfFreeBytes;
746
747 lpFreeSpace->QuadPart = 0;
748
749 hInstance = LoadLibrary(_T("KERNEL32"));
750 if (hInstance != NULL)
751 {
752 pGetFreeDiskSpaceEx = (PGETFREEDISKSPACEEX)GetProcAddress(hInstance,
753 #ifdef _UNICODE
754 "GetDiskFreeSpaceExW");
755 #else
756 "GetDiskFreeSpaceExA");
757 #endif
758 if (pGetFreeDiskSpaceEx != NULL)
759 {
760 if (pGetFreeDiskSpaceEx(lpRoot, lpFreeSpace, &TotalNumberOfBytes, &TotalNumberOfFreeBytes) == TRUE)
761 return;
762 }
763 FreeLibrary(hInstance);
764 }
765
766 GetDiskFreeSpace(lpRoot,
767 &dwSecPerCl,
768 &dwBytPerSec,
769 &dwFreeCl,
770 &dwTotCl);
771
772 lpFreeSpace->QuadPart = dwSecPerCl * dwBytPerSec * dwFreeCl;
773 }
774
775
776 /*
777 * print_summary: prints dir summary
778 * Added by Rob Lake 06/17/98 to compact code
779 * Just copied Tim's Code and patched it a bit
780 *
781 */
782 static INT
783 PrintSummary(LPTSTR szPath,
784 ULONG ulFiles,
785 ULONG ulDirs,
786 ULARGE_INTEGER bytes,
787 LPINT pLine,
788 DWORD dwFlags)
789 {
790 TCHAR buffer[64];
791 ULARGE_INTEGER uliFree;
792 TCHAR szRoot[] = _T("A:\\");
793
794 if (dwFlags & DIR_BARE)
795 return(0);
796
797 /* Print number of files and bytes */
798 ConvertULong (ulFiles, buffer, sizeof(buffer));
799 ConOutPrintf (_T(" %6s File%c"),
800 buffer, ulFiles == 1 ? _T(' ') : _T('s'));
801
802 ConvertULargeInteger (bytes, buffer, sizeof(buffer));
803 ConOutPrintf (_T(" %15s byte%c\n"),
804 buffer, bytes.QuadPart == 1 ? _T(' ') : _T('s'));
805
806 if (IncLine (pLine, dwFlags))
807 return 1;
808
809 /* Print number of dirs and bytes free */
810 ConvertULong (ulDirs, buffer, sizeof(buffer));
811 ConOutPrintf (_T(" %6s Dir%c"),
812 buffer, ulDirs == 1 ? _T(' ') : _T('s'));
813
814 if (!(dwFlags & DIR_RECURSE))
815 {
816 szRoot[0] = szPath[0];
817 GetUserDiskFreeSpace(szRoot, &uliFree);
818 ConvertULargeInteger (uliFree, buffer, sizeof(buffer));
819 ConOutPrintf (_T(" %15s bytes free\n"), buffer);
820 if (IncLine (pLine, dwFlags))
821 return 1;
822 }
823 else
824 {
825 if ((dwFlags & DIR_BARE) == 0)
826 {
827 ConOutPrintf (_T("\n"));
828 if (IncLine (pLine, dwFlags))
829 return 1;
830 ConOutPrintf (_T("\n"));
831 }
832 if (IncLine (pLine, dwFlags))
833 return 1;
834 }
835
836 return 0;
837 }
838
839
840 /*
841 * dir_list
842 *
843 * list the files in the directory
844 */
845 static INT
846 DirList (LPTSTR szPath, LPTSTR szFilespec, LPINT pLine, DWORD dwFlags)
847 {
848 TCHAR szFullPath[MAX_PATH];
849 WIN32_FIND_DATA file;
850 ULARGE_INTEGER bytecount;
851 FILETIME ft;
852 SYSTEMTIME dt;
853 HANDLE hFile;
854 TCHAR buffer[32];
855 ULONG filecount = 0;
856 ULONG dircount = 0;
857 INT count = 0;
858 SHORT screenwidth;
859 INT longestfname = 0;
860
861 bytecount.QuadPart = 0;
862
863 _tcscpy (szFullPath, szPath);
864 if (szFullPath[_tcslen(szFullPath) - 1] != _T('\\'))
865 _tcscat (szFullPath, _T("\\"));
866 _tcscat (szFullPath, szFilespec);
867
868 hFile = FindFirstFile (szFullPath, &file);
869 if (hFile == INVALID_HANDLE_VALUE)
870 {
871 /* Don't want to print anything if scanning recursively
872 * for a file. RL
873 */
874 if ((dwFlags & DIR_RECURSE) == 0)
875 {
876 FindClose (hFile);
877 error_file_not_found ();
878 if (IncLine (pLine, dwFlags))
879 return 0;
880 return 1;
881 }
882 FindClose (hFile);
883 return 0;
884 }
885
886 /* Get the size of longest filename for wide listing. FN */
887 if (dwFlags & DIR_WIDE && (dwFlags & DIR_BARE) == 0)
888 {
889 do
890 {
891 if (_tcslen(file.cFileName) > longestfname)
892 {
893 longestfname = _tcslen(file.cFileName);
894 /* Directories get extra brackets around them. */
895 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
896 longestfname += 2;
897 }
898 }
899 while (FindNextFile (hFile, &file));
900 FindClose (hFile);
901
902 hFile = FindFirstFile (szFullPath, &file);
903
904 /* Count the highest number of columns */
905 GetScreenSize(&screenwidth, 0);
906
907 /* For counting columns of output */
908 count = 0;
909
910 /* Increase by the number of spaces behind file name */
911 longestfname += 3;
912 }
913
914 /* moved down here because if we are recursively searching and
915 * don't find any files, we don't want just to print
916 * Directory of C:\SOMEDIR
917 * with nothing else
918 * Rob Lake 06/13/98
919 */
920 if ((dwFlags & DIR_BARE) == 0)
921 {
922 ConOutPrintf (_T(" Directory of %s\n"), szPath);
923 if (IncLine (pLine, dwFlags))
924 return 1;
925 ConOutPrintf (_T("\n"));
926 if (IncLine (pLine, dwFlags))
927 return 1;
928 }
929
930 do
931 {
932 /* next file, if user doesn't want all files */
933 if (!(dwFlags & DIR_ALL) &&
934 ((file.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ||
935 (file.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)))
936 continue;
937
938 if (dwFlags & DIR_LWR)
939 {
940 _tcslwr (file.cAlternateFileName);
941 }
942
943 if (dwFlags & DIR_WIDE && (dwFlags & DIR_BARE) == 0)
944 {
945 ULARGE_INTEGER uliSize;
946
947 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
948 {
949 _stprintf (buffer, _T("[%s]"), file.cFileName);
950 dircount++;
951 }
952 else
953 {
954 _stprintf (buffer, _T("%s"), file.cFileName);
955 filecount++;
956 }
957
958 ConOutPrintf (_T("%*s"), - longestfname, buffer);
959 count++;
960 /* output as much columns as fits on the screen */
961 if (count >= (screenwidth / longestfname))
962 {
963 /* print the new line only if we aren't on the
964 * last column, in this case it wraps anyway */
965 if (count * longestfname != screenwidth)
966 ConOutPrintf (_T("\n"));
967 if (IncLine (pLine, dwFlags))
968 return 1;
969 count = 0;
970 }
971
972 uliSize.LowPart = file.nFileSizeLow;
973 uliSize.HighPart = file.nFileSizeHigh;
974 bytecount.QuadPart += uliSize.QuadPart;
975 }
976 else if (dwFlags & DIR_BARE)
977 {
978 ULARGE_INTEGER uliSize;
979
980 if (_tcscmp (file.cFileName, _T(".")) == 0 ||
981 _tcscmp (file.cFileName, _T("..")) == 0)
982 continue;
983
984 if (dwFlags & DIR_RECURSE)
985 {
986 TCHAR dir[MAX_PATH];
987
988 _tcscpy (dir, szPath);
989 _tcscat (dir, _T("\\"));
990 if (dwFlags & DIR_LWR)
991 _tcslwr (dir);
992 ConOutPrintf (dir);
993 }
994
995 ConOutPrintf (_T("%-13s\n"), file.cFileName);
996 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
997 dircount++;
998 else
999 filecount++;
1000 if (IncLine (pLine, dwFlags))
1001 return 1;
1002
1003 uliSize.LowPart = file.nFileSizeLow;
1004 uliSize.HighPart = file.nFileSizeHigh;
1005 bytecount.QuadPart += uliSize.QuadPart;
1006 }
1007 else
1008 {
1009 if (dwFlags & DIR_NEW)
1010 {
1011 /* print file date and time */
1012 if (FileTimeToLocalFileTime (&file.ftLastWriteTime, &ft))
1013 {
1014 FileTimeToSystemTime (&ft, &dt);
1015 PrintFileDateTime (&dt, dwFlags);
1016 }
1017
1018 /* print file size */
1019 if (file.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
1020 {
1021 ConOutPrintf (_T(" <JUNCTION> "));
1022 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1023 dircount++;
1024 }
1025 else if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1026 {
1027 ConOutPrintf (_T(" <DIR> "));
1028 dircount++;
1029 }
1030 else
1031 {
1032 ULARGE_INTEGER uliSize;
1033
1034 uliSize.LowPart = file.nFileSizeLow;
1035 uliSize.HighPart = file.nFileSizeHigh;
1036
1037 ConvertULargeInteger (uliSize, buffer, sizeof(buffer));
1038 ConOutPrintf (_T(" %20s"), buffer);
1039
1040 bytecount.QuadPart += uliSize.QuadPart;
1041 filecount++;
1042 }
1043
1044 /* print long filename */
1045 ConOutPrintf (_T(" %s\n"), file.cFileName);
1046 }
1047 else
1048 {
1049 if (file.cFileName[0] == _T('.'))
1050 ConOutPrintf (_T("%-13s "), file.cFileName);
1051 else if (file.cAlternateFileName[0] == _T('\0'))
1052 {
1053 TCHAR szShortName[13];
1054 LPTSTR ext;
1055
1056 _tcsncpy (szShortName, file.cFileName, 13);
1057 ext = _tcschr (szShortName, _T('.'));
1058 if (!ext)
1059 ext = _T("");
1060 else
1061 *ext++ = _T('\0');
1062 ConOutPrintf (_T("%-8s %-3s "), szShortName, ext);
1063 }
1064 else
1065 {
1066 LPTSTR ext;
1067
1068 ext = _tcschr (file.cAlternateFileName, _T('.'));
1069 if (!ext)
1070 ext = _T("");
1071 else
1072 *ext++ = _T('\0');
1073 ConOutPrintf (_T("%-8s %-3s "), file.cAlternateFileName, ext);
1074 }
1075
1076 /* print file size */
1077 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1078 {
1079 ConOutPrintf (_T("%-14s"), _T("<DIR>"));
1080 dircount++;
1081 }
1082 else
1083 {
1084 ULARGE_INTEGER uliSize;
1085
1086 uliSize.LowPart = file.nFileSizeLow;
1087 uliSize.HighPart = file.nFileSizeHigh;
1088 ConvertULargeInteger (uliSize, buffer, sizeof(buffer));
1089 ConOutPrintf (_T(" %10s "), buffer);
1090 bytecount.QuadPart += uliSize.QuadPart;
1091 filecount++;
1092 }
1093
1094 /* print file date and time */
1095 if (FileTimeToLocalFileTime (&file.ftLastWriteTime, &ft))
1096 {
1097 FileTimeToSystemTime (&ft, &dt);
1098 PrintFileDateTime (&dt, dwFlags);
1099 }
1100
1101 /* print long filename */
1102 ConOutPrintf (_T(" %s\n"), file.cFileName);
1103 }
1104
1105 if (IncLine (pLine, dwFlags))
1106 return 1;
1107 }
1108 }
1109 while (FindNextFile (hFile, &file));
1110 FindClose (hFile);
1111
1112 /* Rob Lake, need to make clean output */
1113 /* JPP 07/08/1998 added check for count != 0 */
1114 if ((dwFlags & DIR_WIDE) && (count != 0))
1115 {
1116 ConOutPrintf (_T("\n"));
1117 if (IncLine (pLine, dwFlags))
1118 return 1;
1119 }
1120
1121 if (filecount || dircount)
1122 {
1123 recurse_dir_cnt += dircount;
1124 recurse_file_cnt += filecount;
1125 recurse_bytes.QuadPart += bytecount.QuadPart;
1126
1127 /* print_summary */
1128 if (PrintSummary (szPath, filecount, dircount, bytecount, pLine, dwFlags))
1129 return 1;
1130 }
1131 else
1132 {
1133 error_file_not_found ();
1134 return 1;
1135 }
1136
1137 return 0;
1138 }
1139
1140
1141 /*
1142 * _Read_Dir: Actual function that does recursive listing
1143 */
1144 static INT
1145 DirRead (LPTSTR szPath, LPTSTR szFilespec, LPINT pLine, DWORD dwFlags)
1146 {
1147 TCHAR szFullPath[MAX_PATH];
1148 WIN32_FIND_DATA file;
1149 HANDLE hFile;
1150
1151 _tcscpy (szFullPath, szPath);
1152 if (szFullPath[_tcslen (szFullPath) - 1] != _T('\\'))
1153 _tcscat (szFullPath, _T("\\"));
1154 _tcscat (szFullPath, _T("*"));
1155
1156 hFile = FindFirstFile (szFullPath, &file);
1157 if (hFile == INVALID_HANDLE_VALUE)
1158 return 1;
1159
1160 do
1161 {
1162 /* don't list "." and ".." */
1163 if (_tcscmp (file.cFileName, _T(".")) == 0 ||
1164 _tcscmp (file.cFileName, _T("..")) == 0)
1165 continue;
1166
1167 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1168 {
1169 _tcscpy (szFullPath, szPath);
1170 if (szFullPath[_tcslen (szFullPath) - 1] != _T('\\'))
1171 _tcscat (szFullPath, _T("\\"));
1172 _tcscat (szFullPath, file.cFileName);
1173
1174 if (DirList (szFullPath, szFilespec, pLine, dwFlags))
1175 {
1176 FindClose (hFile);
1177 return 1;
1178 }
1179 if (DirRead (szFullPath, szFilespec, pLine, dwFlags) == 1)
1180 {
1181 FindClose (hFile);
1182 return 1;
1183 }
1184 }
1185 }
1186 while (FindNextFile (hFile, &file));
1187
1188 if (!FindClose (hFile))
1189 return 1;
1190
1191 return 0;
1192 }
1193
1194
1195 /*
1196 * do_recurse: Sets up for recursive directory listing
1197 */
1198 static INT
1199 DirRecurse (LPTSTR szPath, LPTSTR szSpec, LPINT pLine, DWORD dwFlags)
1200 {
1201 if (!PrintDirectoryHeader (szPath, pLine, dwFlags))
1202 return 1;
1203
1204 if (DirList (szPath, szSpec, pLine, dwFlags))
1205 return 1;
1206
1207 if ((dwFlags & DIR_BARE) == 0)
1208 {
1209 ConOutPrintf (_T("\n"));
1210 if (IncLine (pLine, dwFlags))
1211 return 1;
1212 }
1213
1214 if (DirRead (szPath, szSpec, pLine, dwFlags))
1215 return 1;
1216
1217 if ((dwFlags & DIR_BARE) == 0)
1218 ConOutPrintf (_T("Total files listed:\n"));
1219
1220 dwFlags &= ~DIR_RECURSE;
1221
1222 if (PrintSummary (szPath, recurse_file_cnt,
1223 recurse_dir_cnt, recurse_bytes, pLine, dwFlags))
1224 return 1;
1225
1226 if ((dwFlags & DIR_BARE) == 0)
1227 {
1228 ConOutPrintf (_T("\n"));
1229 if (IncLine (pLine, dwFlags))
1230 return 1;
1231 }
1232
1233 return 0;
1234 }
1235
1236
1237 /*
1238 * dir
1239 *
1240 * internal dir command
1241 */
1242 INT CommandDir (LPTSTR first, LPTSTR rest)
1243 {
1244 DWORD dwFlags = DIR_NEW | DIR_FOUR;
1245 TCHAR dircmd[256];
1246 TCHAR szPath[MAX_PATH];
1247 TCHAR szFilespec[MAX_PATH];
1248 LPTSTR param;
1249 INT nLine = 0;
1250
1251
1252 recurse_dir_cnt = 0L;
1253 recurse_file_cnt = 0L;
1254 recurse_bytes.QuadPart = 0;
1255
1256 /* read the parameters from the DIRCMD environment variable */
1257 if (GetEnvironmentVariable (_T("DIRCMD"), dircmd, 256))
1258 {
1259 if (!DirReadParam (dircmd, &param, &dwFlags))
1260 return 1;
1261 }
1262
1263 /* read the parameters */
1264 if (!DirReadParam (rest, &param, &dwFlags))
1265 return 1;
1266
1267 /* default to current directory */
1268 if (!param)
1269 param = _T(".");
1270
1271 /* parse the directory info */
1272 if (DirParsePathspec (param, szPath, szFilespec))
1273 return 1;
1274
1275 if (dwFlags & DIR_RECURSE)
1276 {
1277 if (IncLine (&nLine, dwFlags))
1278 return 0;
1279 if (DirRecurse (szPath, szFilespec, &nLine, dwFlags))
1280 return 1;
1281 return 0;
1282 }
1283
1284 /* print the header */
1285 if (!PrintDirectoryHeader (szPath, &nLine, dwFlags))
1286 return 1;
1287
1288 if (DirList (szPath, szFilespec, &nLine, dwFlags))
1289 return 1;
1290
1291 return 0;
1292 }
1293
1294 #endif
1295
1296 /* EOF */