Various improvements by Carl Nettelblad.
[reactos.git] / rosapps / cmd / dir.c
1 /* $Id: dir.c,v 1.10 2001/02/28 22:33:23 ekohl Exp $
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
118 #include "config.h"
119
120 #ifdef INCLUDE_CMD_DIR
121 #include <windows.h>
122 #include <tchar.h>
123 #include <string.h>
124 #include <stdlib.h>
125 #include <stdio.h>
126 #include <ctype.h>
127
128 #include "cmd.h"
129
130
131
132 /* flag definitions */
133 enum
134 {
135 DIR_RECURSE = 0x0001,
136 DIR_PAGE = 0x0002,
137 DIR_WIDE = 0x0004, /* Rob Lake */
138 DIR_BARE = 0x0008, /* Rob Lake */
139 DIR_ALL = 0x0010, /* Rob Lake */
140 DIR_LWR = 0x0020, /* Rob Lake */
141 DIR_SORT = 0x0040, /* /O sort */
142 DIR_NEW = 0x0080, /* /N new style */
143 DIR_FOUR = 0x0100 /* /4 four digit year */
144 };
145
146
147 /* Globally save the # of dirs, files and bytes,
148 * probabaly later pass them to functions. Rob Lake */
149 static ULONG recurse_dir_cnt;
150 static ULONG recurse_file_cnt;
151 static ULARGE_INTEGER recurse_bytes;
152
153
154 /*
155 * help
156 *
157 * displays help screen for dir
158 * Rob Lake
159 */
160 static VOID Help (VOID)
161 {
162 ConOutPuts (_T("Displays a list of files and subdirectories in a directory.\n"
163 "\n"
164 "DIR [drive:][path][filename] [/A] [/B] [/L] [/N] [/S] [/P] [/W] [/4]\n"
165 "\n"
166 " [drive:][path][filename]\n"
167 " Specifies drive, directory, and/or files to list.\n"
168 "\n"
169 " /A Displays files with HIDDEN SYSTEM attributes\n"
170 " default is ARCHIVE and READ ONLY\n"
171 " /B Uses bare format (no heading information or summary).\n"
172 " /L Uses lowercase.\n"
173 " /N New long list format where filenames are on the far right.\n"
174 " /S Displays files in specified directory and all subdirectories\n"
175 " /P Pauses after each screen full\n"
176 " /W Prints in wide format\n"
177 " /4 Display four digit years.\n"
178 "\n"
179 "Switches may be present in the DIRCMD environment variable. Use\n"
180 "of the - (hyphen) can turn off defined swtiches. Ex. /-W would\n"
181 "turn off printing in wide format.\n"
182 ));
183 }
184
185
186 /*
187 * DirReadParam
188 *
189 * read the parameters from the command line
190 */
191 static BOOL
192 DirReadParam (LPTSTR line, LPTSTR *param, LPDWORD lpFlags)
193 {
194 INT slash = 0;
195
196 if (!line)
197 return TRUE;
198
199 *param = NULL;
200
201 /* scan the command line, processing switches */
202 while (*line)
203 {
204 /* process switch */
205 if (*line == _T('/') || slash)
206 {
207 if (!slash)
208 line++;
209 slash = 0;
210 if (*line == _T('-'))
211 {
212 line++;
213 if (_totupper (*line) == _T('S'))
214 *lpFlags &= ~DIR_RECURSE;
215 else if (_totupper (*line) == _T('P'))
216 *lpFlags &= ~DIR_PAGE;
217 else if (_totupper (*line) == _T('W'))
218 *lpFlags &= ~DIR_WIDE;
219 else if (_totupper (*line) == _T('B'))
220 *lpFlags &= ~DIR_BARE;
221 else if (_totupper (*line) == _T('A'))
222 *lpFlags &= ~DIR_ALL;
223 else if (_totupper (*line) == _T('L'))
224 *lpFlags &= ~DIR_LWR;
225 else if (_totupper (*line) == _T('N'))
226 *lpFlags &= ~DIR_NEW;
227 else if (_totupper (*line) == _T('O'))
228 *lpFlags &= ~DIR_SORT;
229 else if (_totupper (*line) == _T('4'))
230 *lpFlags &= ~DIR_FOUR;
231 else
232 {
233 error_invalid_switch ((TCHAR)_totupper (*line));
234 return FALSE;
235 }
236 line++;
237 continue;
238 }
239 else
240 {
241 if (_totupper (*line) == _T('S'))
242 *lpFlags |= DIR_RECURSE;
243 else if (_totupper (*line) == _T('P'))
244 *lpFlags |= DIR_PAGE;
245 else if (_totupper (*line) == _T('W'))
246 *lpFlags |= DIR_WIDE;
247 else if (_totupper (*line) == _T('B'))
248 *lpFlags |= DIR_BARE;
249 else if (_totupper (*line) == _T('A'))
250 *lpFlags |= DIR_ALL;
251 else if (_totupper (*line) == _T('L'))
252 *lpFlags |= DIR_LWR;
253 else if (_totupper (*line) == _T('N'))
254 *lpFlags |= DIR_NEW;
255 else if (_totupper (*line) == _T('O'))
256 *lpFlags |= DIR_SORT;
257 else if (_totupper (*line) == _T('4'))
258 *lpFlags |= DIR_FOUR;
259 else if (*line == _T('?'))
260 {
261 Help();
262 return FALSE;
263 }
264 else
265 {
266 error_invalid_switch ((TCHAR)_totupper (*line));
267 return FALSE;
268 }
269 line++;
270 continue;
271 }
272 }
273
274 /* process parameter */
275 if (!_istspace (*line))
276 {
277 if (*param)
278 {
279 error_too_many_parameters (*param);
280 return FALSE;
281 }
282
283 *param = line;
284
285 /* skip to end of line or next whitespace or next / */
286 while (*line && !_istspace (*line) && *line != _T('/'))
287 line++;
288
289 /* if end of line, return */
290 if (!*line)
291 return TRUE;
292
293 /* if parameter, remember to process it later */
294 if (*line == _T('/'))
295 slash = 1;
296
297 *line++ = 0;
298 continue;
299 }
300
301 line++;
302 }
303
304 if (slash)
305 {
306 error_invalid_switch ((TCHAR)_totupper (*line));
307 return FALSE;
308 }
309
310 return TRUE;
311 }
312
313
314 /*
315 * ExtendFilespec
316 *
317 * extend the filespec, possibly adding wildcards
318 */
319 static VOID
320 ExtendFilespec (LPTSTR file)
321 {
322 INT len = 0;
323
324 if (!file)
325 return;
326
327 /* if no file spec, change to "*.*" */
328 if (*file == _T('\0'))
329 {
330 _tcscpy (file, _T("*.*"));
331 return;
332 }
333
334 /* if starts with . add * in front */
335 if (*file == _T('.'))
336 {
337 memmove (&file[1], &file[0], (_tcslen (file) + 1) * sizeof(TCHAR));
338 file[0] = _T('*');
339 }
340
341 /* if no . add .* */
342 if (!_tcschr (file, _T('.')))
343 {
344 _tcscat (file, _T(".*"));
345 return;
346 }
347
348 /* if last character is '.' add '*' */
349 len = _tcslen (file);
350 if (file[len - 1] == _T('.'))
351 {
352 _tcscat (file, _T("*"));
353 return;
354 }
355 }
356
357
358 /*
359 * dir_parse_pathspec
360 *
361 * split the pathspec into drive, directory, and filespec
362 */
363 static INT
364 DirParsePathspec (LPTSTR szPathspec, LPTSTR szPath, LPTSTR szFilespec)
365 {
366 TCHAR szOrigPath[MAX_PATH];
367 LPTSTR start;
368 LPTSTR tmp;
369 INT i;
370 BOOL bWildcards = FALSE;
371
372 GetCurrentDirectory (MAX_PATH, szOrigPath);
373
374 /* get the drive and change to it */
375 if (szPathspec[1] == _T(':'))
376 {
377 TCHAR szRootPath[] = _T("A:");
378
379 szRootPath[0] = szPathspec[0];
380 start = szPathspec + 2;
381 SetCurrentDirectory (szRootPath);
382 }
383 else
384 {
385 start = szPathspec;
386 }
387
388
389 /* check for wildcards */
390 for (i = 0; szPathspec[i]; i++)
391 {
392 if (szPathspec[i] == _T('*') || szPathspec[i] == _T('?'))
393 bWildcards = TRUE;
394 }
395
396 /* check if this spec is a directory */
397 if (!bWildcards)
398 {
399 if (SetCurrentDirectory (szPathspec))
400 {
401 _tcscpy (szFilespec, _T("*.*"));
402
403 if (!GetCurrentDirectory (MAX_PATH, szPath))
404 {
405 szFilespec[0] = _T('\0');
406 SetCurrentDirectory (szOrigPath);
407 error_out_of_memory();
408 return 1;
409 }
410
411 SetCurrentDirectory (szOrigPath);
412 return 0;
413 }
414 }
415
416 /* find the file spec */
417 tmp = _tcsrchr (start, _T('\\'));
418
419 /* if no path is specified */
420 if (!tmp)
421 {
422 _tcscpy (szFilespec, start);
423 ExtendFilespec (szFilespec);
424 if (!GetCurrentDirectory (MAX_PATH, szPath))
425 {
426 szFilespec[0] = _T('\0');
427 SetCurrentDirectory (szOrigPath);
428 error_out_of_memory();
429 return 1;
430 }
431
432 SetCurrentDirectory (szOrigPath);
433 return 0;
434 }
435
436 /* get the filename */
437 _tcscpy (szFilespec, tmp+1);
438 ExtendFilespec (szFilespec);
439
440 *tmp = _T('\0');
441
442 /* change to this directory and get its full name */
443 if (!SetCurrentDirectory (start))
444 {
445 *tmp = _T('\\');
446 szFilespec[0] = _T('\0');
447 SetCurrentDirectory (szOrigPath);
448 error_path_not_found ();
449 return 1;
450 }
451
452 if (!GetCurrentDirectory (MAX_PATH, szPath))
453 {
454 *tmp = _T('\\');
455 szFilespec[0] = _T('\0');
456 SetCurrentDirectory (szOrigPath);
457 error_out_of_memory ();
458 return 1;
459 }
460
461 *tmp = _T('\\');
462
463 SetCurrentDirectory (szOrigPath);
464
465 return 0;
466 }
467
468
469 /*
470 * incline
471 *
472 * increment our line if paginating, display message at end of screen
473 */
474 static BOOL
475 IncLine (LPINT pLine, DWORD dwFlags)
476 {
477 if (!(dwFlags & DIR_PAGE))
478 return FALSE;
479
480 (*pLine)++;
481
482 if (*pLine >= (int)maxy - 2)
483 {
484 *pLine = 0;
485 return (PagePrompt () == PROMPT_BREAK);
486 }
487
488 return FALSE;
489 }
490
491
492 /*
493 * PrintDirectoryHeader
494 *
495 * print the header for the dir command
496 */
497 static BOOL
498 PrintDirectoryHeader (LPTSTR szPath, LPINT pLine, DWORD dwFlags)
499 {
500 TCHAR szRootName[] = _T("A:\\");
501 TCHAR szVolName[80];
502 DWORD dwSerialNr;
503
504 if (dwFlags & DIR_BARE)
505 return TRUE;
506
507 /* get the media ID of the drive */
508 szRootName[0] = szPath[0];
509 if (!GetVolumeInformation (szRootName, szVolName, 80, &dwSerialNr,
510 NULL, NULL, NULL, 0))
511 {
512 error_invalid_drive();
513 return FALSE;
514 }
515
516 /* print drive info */
517 ConOutPrintf (_T(" Volume in drive %c"), szRootName[0]);
518
519 if (szVolName[0] != _T('\0'))
520 ConOutPrintf (_T(" is %s\n"), szVolName);
521 else
522 ConOutPrintf (_T(" has no label\n"));
523
524 if (IncLine (pLine, dwFlags))
525 return FALSE;
526
527 /* print the volume serial number if the return was successful */
528 ConOutPrintf (_T(" Volume Serial Number is %04X-%04X\n"),
529 HIWORD(dwSerialNr),
530 LOWORD(dwSerialNr));
531 if (IncLine (pLine, dwFlags))
532 return FALSE;
533
534 return TRUE;
535 }
536
537
538 /*
539 * convert
540 *
541 * insert commas into a number
542 */
543 static INT
544 ConvertULong (ULONG num, LPTSTR des, INT len)
545 {
546 TCHAR temp[32];
547 INT c = 0;
548 INT n = 0;
549
550 if (num == 0)
551 {
552 des[0] = _T('0');
553 des[1] = _T('\0');
554 n = 1;
555 }
556 else
557 {
558 temp[31] = 0;
559 while (num > 0)
560 {
561 if (((c + 1) % (nNumberGroups + 1)) == 0)
562 temp[30 - c++] = cThousandSeparator;
563 temp[30 - c++] = (TCHAR)(num % 10) + _T('0');
564 num /= 10;
565 }
566
567 for (n = 0; n <= c; n++)
568 des[n] = temp[31 - c + n];
569 }
570
571 return n;
572 }
573
574
575 static INT
576 ConvertULargeInteger (ULARGE_INTEGER num, LPTSTR des, INT len)
577 {
578 TCHAR temp[32];
579 INT c = 0;
580 INT n = 0;
581
582 if (num.QuadPart == 0)
583 {
584 des[0] = _T('0');
585 des[1] = _T('\0');
586 n = 1;
587 }
588 else
589 {
590 temp[31] = 0;
591 while (num.QuadPart > 0)
592 {
593 if (((c + 1) % (nNumberGroups + 1)) == 0)
594 temp[30 - c++] = cThousandSeparator;
595 temp[30 - c++] = (TCHAR)(num.QuadPart % 10) + _T('0');
596 num.QuadPart /= 10;
597 }
598
599 for (n = 0; n <= c; n++)
600 des[n] = temp[31 - c + n];
601 }
602
603 return n;
604 }
605
606
607 static VOID
608 PrintFileDateTime (LPSYSTEMTIME dt, DWORD dwFlags)
609 {
610 WORD wYear = (dwFlags & DIR_FOUR) ? dt->wYear : dt->wYear%100;
611
612 switch (nDateFormat)
613 {
614 case 0: /* mmddyy */
615 default:
616 ConOutPrintf (_T("%.2d%c%.2d%c%d"),
617 dt->wMonth, cDateSeparator, dt->wDay, cDateSeparator, wYear);
618 break;
619
620 case 1: /* ddmmyy */
621 ConOutPrintf (_T("%.2d%c%.2d%c%d"),
622 dt->wDay, cDateSeparator, dt->wMonth, cDateSeparator, wYear);
623 break;
624
625 case 2: /* yymmdd */
626 ConOutPrintf (_T("%d%c%.2d%c%.2d"),
627 wYear, cDateSeparator, dt->wMonth, cDateSeparator, dt->wDay);
628 break;
629 }
630
631 switch (nTimeFormat)
632 {
633 case 0: /* 12 hour format */
634 default:
635 ConOutPrintf (_T(" %2d%c%.2u%c"),
636 (dt->wHour == 0 ? 12 : (dt->wHour <= 12 ? dt->wHour : dt->wHour - 12)),
637 cTimeSeparator,
638 dt->wMinute, (dt->wHour <= 11 ? 'a' : 'p'));
639 break;
640
641 case 1: /* 24 hour format */
642 ConOutPrintf (_T(" %2d%c%.2u"),
643 dt->wHour, cTimeSeparator, dt->wMinute);
644 break;
645 }
646 }
647
648
649 /*
650 * print_summary: prints dir summary
651 * Added by Rob Lake 06/17/98 to compact code
652 * Just copied Tim's Code and patched it a bit
653 *
654 */
655 static INT
656 PrintSummary (LPTSTR szPath, ULONG ulFiles, ULONG ulDirs, ULARGE_INTEGER bytes,
657 LPINT pLine, DWORD dwFlags)
658 {
659 TCHAR buffer[64];
660
661 if (dwFlags & DIR_BARE)
662 return 0;
663
664 /* print number of files and bytes */
665 ConvertULong (ulFiles, buffer, sizeof(buffer));
666 ConOutPrintf (_T(" %6s File%c"),
667 buffer, ulFiles == 1 ? _T(' ') : _T('s'));
668
669 ConvertULargeInteger (bytes, buffer, sizeof(buffer));
670 ConOutPrintf (_T(" %15s byte%c\n"),
671 buffer, bytes.QuadPart == 1 ? _T(' ') : _T('s'));
672
673 if (IncLine (pLine, dwFlags))
674 return 1;
675
676 /* print number of dirs and bytes free */
677 ConvertULong (ulDirs, buffer, sizeof(buffer));
678 ConOutPrintf (_T(" %6s Dir%c"),
679 buffer, ulDirs == 1 ? _T(' ') : _T('s'));
680
681
682 if (!(dwFlags & DIR_RECURSE))
683 {
684 ULARGE_INTEGER uliFree;
685 TCHAR szRoot[] = _T("A:\\");
686 DWORD dwSecPerCl;
687 DWORD dwBytPerSec;
688 DWORD dwFreeCl;
689 DWORD dwTotCl;
690
691 szRoot[0] = szPath[0];
692 GetDiskFreeSpace (szRoot, &dwSecPerCl, &dwBytPerSec, &dwFreeCl, &dwTotCl);
693 uliFree.QuadPart = dwSecPerCl * dwBytPerSec * dwFreeCl;
694 ConvertULargeInteger (uliFree, buffer, sizeof(buffer));
695 ConOutPrintf (_T(" %15s bytes free\n"), buffer);
696 }
697
698 if (IncLine (pLine, dwFlags))
699 return 1;
700
701 return 0;
702 }
703
704
705 /*
706 * dir_list
707 *
708 * list the files in the directory
709 */
710 static INT
711 DirList (LPTSTR szPath, LPTSTR szFilespec, LPINT pLine, DWORD dwFlags)
712 {
713 TCHAR szFullPath[MAX_PATH];
714 WIN32_FIND_DATA file;
715 ULARGE_INTEGER bytecount;
716 FILETIME ft;
717 SYSTEMTIME dt;
718 HANDLE hFile;
719 TCHAR buffer[32];
720 ULONG filecount = 0;
721 ULONG dircount = 0;
722 INT count;
723
724 bytecount.QuadPart = 0;
725
726 _tcscpy (szFullPath, szPath);
727 if (szFullPath[_tcslen(szFullPath) - 1] != _T('\\'))
728 _tcscat (szFullPath, _T("\\"));
729 _tcscat (szFullPath, szFilespec);
730
731 hFile = FindFirstFile (szFullPath, &file);
732 if (hFile == INVALID_HANDLE_VALUE)
733 {
734 /* Don't want to print anything if scanning recursively
735 * for a file. RL
736 */
737 if ((dwFlags & DIR_RECURSE) == 0)
738 {
739 FindClose (hFile);
740 error_file_not_found ();
741 if (IncLine (pLine, dwFlags))
742 return 0;
743 return 1;
744 }
745 FindClose (hFile);
746 return 0;
747 }
748
749 /* moved down here because if we are recursively searching and
750 * don't find any files, we don't want just to print
751 * Directory of C:\SOMEDIR
752 * with nothing else
753 * Rob Lake 06/13/98
754 */
755 if ((dwFlags & DIR_BARE) == 0)
756 {
757 ConOutPrintf (_T(" Directory of %s\n"), szPath);
758 if (IncLine (pLine, dwFlags))
759 return 1;
760 ConOutPrintf (_T("\n"));
761 if (IncLine (pLine, dwFlags))
762 return 1;
763 }
764
765 /* For counting columns of output */
766 count = 0;
767
768 do
769 {
770 /* next file, if user doesn't want all files */
771 if (!(dwFlags & DIR_ALL) &&
772 ((file.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ||
773 (file.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)))
774 continue;
775
776 if (dwFlags & DIR_LWR)
777 {
778 _tcslwr (file.cAlternateFileName);
779 }
780
781 if (dwFlags & DIR_WIDE && (dwFlags & DIR_BARE) == 0)
782 {
783 ULARGE_INTEGER uliSize;
784
785 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
786 {
787 if (file.cAlternateFileName[0] == _T('\0'))
788 _stprintf (buffer, _T("[%s]"), file.cFileName);
789 else
790 _stprintf (buffer, _T("[%s]"), file.cAlternateFileName);
791 dircount++;
792 }
793 else
794 {
795 if (file.cAlternateFileName[0] == _T('\0'))
796 _stprintf (buffer, _T("%s"), file.cFileName);
797 else
798 _stprintf (buffer, _T("%s"), file.cAlternateFileName);
799 filecount++;
800 }
801
802 ConOutPrintf (_T("%-15s"), buffer);
803 count++;
804 if (count == 5)
805 {
806 /* output 5 columns */
807 ConOutPrintf (_T("\n"));
808 if (IncLine (pLine, dwFlags))
809 return 1;
810 count = 0;
811 }
812
813 uliSize.u.LowPart = file.nFileSizeLow;
814 uliSize.u.HighPart = file.nFileSizeHigh;
815 bytecount.QuadPart += uliSize.QuadPart;
816 }
817 else if (dwFlags & DIR_BARE)
818 {
819 ULARGE_INTEGER uliSize;
820
821 if (_tcscmp (file.cFileName, _T(".")) == 0 ||
822 _tcscmp (file.cFileName, _T("..")) == 0)
823 continue;
824
825 if (dwFlags & DIR_RECURSE)
826 {
827 TCHAR dir[MAX_PATH];
828
829 _tcscpy (dir, szPath);
830 _tcscat (dir, _T("\\"));
831 if (dwFlags & DIR_LWR)
832 _tcslwr (dir);
833 ConOutPrintf (dir);
834 }
835
836 ConOutPrintf (_T("%-13s\n"), file.cFileName);
837 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
838 dircount++;
839 else
840 filecount++;
841 if (IncLine (pLine, dwFlags))
842 return 1;
843
844 uliSize.u.LowPart = file.nFileSizeLow;
845 uliSize.u.HighPart = file.nFileSizeHigh;
846 bytecount.QuadPart += uliSize.QuadPart;
847 }
848 else
849 {
850 if (dwFlags & DIR_NEW)
851 {
852 /* print file date and time */
853 if (FileTimeToLocalFileTime (&file.ftLastWriteTime, &ft))
854 {
855 FileTimeToSystemTime (&ft, &dt);
856 PrintFileDateTime (&dt, dwFlags);
857 }
858
859 /* print file size */
860 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
861 {
862 ConOutPrintf (_T(" <DIR> "));
863 dircount++;
864 }
865 else
866 {
867 ULARGE_INTEGER uliSize;
868
869 uliSize.u.LowPart = file.nFileSizeLow;
870 uliSize.u.HighPart = file.nFileSizeHigh;
871
872 ConvertULargeInteger (uliSize, buffer, sizeof(buffer));
873 ConOutPrintf (_T(" %20s"), buffer);
874
875 bytecount.QuadPart += uliSize.QuadPart;
876 filecount++;
877 }
878
879 /* print long filename */
880 ConOutPrintf (_T(" %s\n"), file.cFileName);
881 }
882 else
883 {
884 if (file.cFileName[0] == _T('.'))
885 ConOutPrintf (_T("%-13s "), file.cFileName);
886 else if (file.cAlternateFileName[0] == _T('\0'))
887 {
888 TCHAR szShortName[13];
889 LPTSTR ext;
890
891 _tcsncpy (szShortName, file.cFileName, 13);
892 ext = _tcschr (szShortName, _T('.'));
893 if (!ext)
894 ext = _T("");
895 else
896 *ext++ = _T('\0');
897 ConOutPrintf (_T("%-8s %-3s "), szShortName, ext);
898 }
899 else
900 {
901 LPTSTR ext;
902
903 ext = _tcschr (file.cAlternateFileName, _T('.'));
904 if (!ext)
905 ext = _T("");
906 else
907 *ext++ = _T('\0');
908 ConOutPrintf (_T("%-8s %-3s "), file.cAlternateFileName, ext);
909 }
910
911 /* print file size */
912 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
913 {
914 ConOutPrintf ("%-14s", "<DIR>");
915 dircount++;
916 }
917 else
918 {
919 ULARGE_INTEGER uliSize;
920
921 uliSize.u.LowPart = file.nFileSizeLow;
922 uliSize.u.HighPart = file.nFileSizeHigh;
923 ConvertULargeInteger (uliSize, buffer, sizeof(buffer));
924 ConOutPrintf (_T(" %10s "), buffer);
925 bytecount.QuadPart += uliSize.QuadPart;
926 filecount++;
927 }
928
929 /* print file date and time */
930 if (FileTimeToLocalFileTime (&file.ftLastWriteTime, &ft))
931 {
932 FileTimeToSystemTime (&ft, &dt);
933 PrintFileDateTime (&dt, dwFlags);
934 }
935
936 /* print long filename */
937 ConOutPrintf (" %s\n", file.cFileName);
938 }
939
940 if (IncLine (pLine, dwFlags))
941 return 1;
942 }
943 }
944 while (FindNextFile (hFile, &file));
945 FindClose (hFile);
946
947 /* Rob Lake, need to make clean output */
948 /* JPP 07/08/1998 added check for count != 0 */
949 if ((dwFlags & DIR_WIDE) && (count != 0))
950 {
951 ConOutPrintf (_T("\n"));
952 if (IncLine (pLine, dwFlags))
953 return 1;
954 }
955
956 if (filecount || dircount)
957 {
958 recurse_dir_cnt += dircount;
959 recurse_file_cnt += filecount;
960 recurse_bytes.QuadPart += bytecount.QuadPart;
961
962 /* print_summary */
963 if (PrintSummary (szPath, filecount, dircount, bytecount, pLine, dwFlags))
964 return 1;
965 }
966 else
967 {
968 error_file_not_found ();
969 return 1;
970 }
971
972 return 0;
973 }
974
975
976 /*
977 * _Read_Dir: Actual function that does recursive listing
978 */
979 static INT
980 DirRead (LPTSTR szPath, LPTSTR szFilespec, LPINT pLine, DWORD dwFlags)
981 {
982 TCHAR szFullPath[MAX_PATH];
983 WIN32_FIND_DATA file;
984 HANDLE hFile;
985
986 _tcscpy (szFullPath, szPath);
987 if (szFullPath[_tcslen (szFullPath) - 1] != _T('\\'))
988 _tcscat (szFullPath, _T("\\"));
989 _tcscat (szFullPath, szFilespec);
990
991 hFile = FindFirstFile (szFullPath, &file);
992 if (hFile == INVALID_HANDLE_VALUE)
993 return 1;
994
995 do
996 {
997 /* don't list "." and ".." */
998 if (_tcscmp (file.cFileName, _T(".")) == 0 ||
999 _tcscmp (file.cFileName, _T("..")) == 0)
1000 continue;
1001
1002 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1003 {
1004 _tcscpy (szFullPath, szPath);
1005 if (szFullPath[_tcslen (szFullPath) - 1] != _T('\\'))
1006 _tcscat (szFullPath, _T("\\"));
1007 _tcscat (szFullPath, file.cFileName);
1008
1009 if (DirList (szFullPath, szFilespec, pLine, dwFlags))
1010 {
1011 FindClose (hFile);
1012 return 1;
1013 }
1014
1015 if ((dwFlags & DIR_BARE) == 0)
1016 {
1017 ConOutPrintf ("\n");
1018 if (IncLine (pLine, dwFlags) != 0)
1019 return 1;
1020 ConOutPrintf ("\n");
1021 if (IncLine (pLine, dwFlags) != 0)
1022 return 1;
1023 }
1024
1025 if (DirRead (szFullPath, szFilespec, pLine, dwFlags) == 1)
1026 {
1027 FindClose (hFile);
1028 return 1;
1029 }
1030 }
1031 }
1032 while (FindNextFile (hFile, &file));
1033
1034 if (!FindClose (hFile))
1035 return 1;
1036
1037 return 0;
1038 }
1039
1040
1041 /*
1042 * do_recurse: Sets up for recursive directory listing
1043 */
1044 static INT
1045 DirRecurse (LPTSTR szPath, LPTSTR szSpec, LPINT pLine, DWORD dwFlags)
1046 {
1047 if (!PrintDirectoryHeader (szPath, pLine, dwFlags))
1048 return 1;
1049
1050 if (DirList (szPath, szSpec, pLine, dwFlags))
1051 return 1;
1052
1053 if ((dwFlags & DIR_BARE) == 0)
1054 {
1055 ConOutPrintf (_T("\n"));
1056 if (IncLine (pLine, dwFlags))
1057 return 1;
1058 }
1059
1060 if (DirRead (szPath, szSpec, pLine, dwFlags))
1061 return 1;
1062
1063 if ((dwFlags & DIR_BARE) == 0)
1064 ConOutPrintf (_T("Total files listed:\n"));
1065
1066 dwFlags &= ~DIR_RECURSE;
1067
1068 if (PrintSummary (szPath, recurse_file_cnt,
1069 recurse_dir_cnt, recurse_bytes, pLine, dwFlags))
1070 return 1;
1071
1072 if ((dwFlags & DIR_BARE) == 0)
1073 {
1074 ConOutPrintf (_T("\n"));
1075 if (IncLine (pLine, dwFlags))
1076 return 1;
1077 }
1078
1079 return 0;
1080 }
1081
1082
1083 /*
1084 * dir
1085 *
1086 * internal dir command
1087 */
1088 INT CommandDir (LPTSTR first, LPTSTR rest)
1089 {
1090 DWORD dwFlags = DIR_NEW | DIR_FOUR;
1091 TCHAR dircmd[256];
1092 TCHAR szPath[MAX_PATH];
1093 TCHAR szFilespec[MAX_PATH];
1094 LPTSTR param;
1095 INT nLine = 0;
1096
1097
1098 recurse_dir_cnt = 0L;
1099 recurse_file_cnt = 0L;
1100 recurse_bytes.QuadPart = 0;
1101
1102 /* read the parameters from the DIRCMD environment variable */
1103 if (GetEnvironmentVariable (_T("DIRCMD"), dircmd, 256))
1104 {
1105 if (!DirReadParam (dircmd, &param, &dwFlags))
1106 return 1;
1107 }
1108
1109 /* read the parameters */
1110 if (!DirReadParam (rest, &param, &dwFlags))
1111 return 1;
1112
1113 /* default to current directory */
1114 if (!param)
1115 param = ".";
1116
1117 /* parse the directory info */
1118 if (DirParsePathspec (param, szPath, szFilespec))
1119 return 1;
1120
1121 if (dwFlags & DIR_RECURSE)
1122 {
1123 if (IncLine (&nLine, dwFlags))
1124 return 0;
1125 if (DirRecurse (szPath, szFilespec, &nLine, dwFlags))
1126 return 1;
1127 return 0;
1128 }
1129
1130 /* print the header */
1131 if (!PrintDirectoryHeader (szPath, &nLine, dwFlags))
1132 return 1;
1133
1134 if (DirList (szPath, szFilespec, &nLine, dwFlags))
1135 return 1;
1136
1137 return 0;
1138 }
1139
1140 #endif
1141
1142 /* EOF */