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