xref: /reactos/base/shell/cmd/dir.c (revision 5e2fe089)
1 /*
2  *  DIR.C - dir internal command.
3  *
4  *
5  *  History:
6  *
7  *    01/29/97 (Tim Norman)
8  *        started.
9  *
10  *    06/13/97 (Tim Norman)
11  *      Fixed code.
12  *
13  *    07/12/97 (Tim Norman)
14  *        Fixed bug that caused the root directory to be unlistable
15  *
16  *    07/12/97 (Marc Desrochers)
17  *        Changed to use maxx, maxy instead of findxy()
18  *
19  *    06/08/98 (Rob Lake)
20  *        Added compatibility for /w in dir
21  *
22  *    06/09/98 (Rob Lake)
23  *        Compatibility for dir/s started
24  *        Tested that program finds directories off root fine
25  *
26  *    06/10/98 (Rob Lake)
27  *        do_recurse saves the cwd and also stores it in Root
28  *        build_tree adds the cwd to the beginning of its' entries
29  *        Program runs fine, added print_tree -- works fine.. as EXE,
30  *        program won't work properly as COM.
31  *
32  *    06/11/98 (Rob Lake)
33  *        Found problem that caused COM not to work
34  *
35  *    06/12/98 (Rob Lake)
36  *        debugged...
37  *        added free mem routine
38  *
39  *    06/13/98 (Rob Lake)
40  *        debugged the free mem routine
41  *        debugged whole thing some more
42  *        Notes:
43  *        ReadDir stores Root name and _Read_Dir does the hard work
44  *        PrintDir prints Root and _Print_Dir does the hard work
45  *        KillDir kills Root _after_ _Kill_Dir does the hard work
46  *        Integrated program into DIR.C(this file) and made some same
47  *        changes throughout
48  *
49  *    06/14/98 (Rob Lake)
50  *        Cleaned up code a bit, added comments
51  *
52  *    06/16/98 (Rob Lake)
53  *        Added error checking to my previously added routines
54  *
55  *    06/17/98 (Rob Lake)
56  *        Rewrote recursive functions, again! Most other recursive
57  *        functions are now obsolete -- ReadDir, PrintDir, _Print_Dir,
58  *        KillDir and _Kill_Dir.  do_recurse does what PrintDir did
59  *        and _Read_Dir did what it did before along with what _Print_Dir
60  *        did.  Makes /s a lot faster!
61  *        Reports 2 more files/dirs that MS-DOS actually reports
62  *        when used in root directory(is this because dir defaults
63  *        to look for read only files?)
64  *        Added support for /b, /a and /l
65  *        Made error message similar to DOS error messages
66  *        Added help screen
67  *
68  *    06/20/98 (Rob Lake)
69  *        Added check for /-(switch) to turn off previously defined
70  *        switches.
71  *        Added ability to check for DIRCMD in environment and
72  *        process it
73  *
74  *    06/21/98 (Rob Lake)
75  *        Fixed up /B
76  *        Now can dir *.ext/X, no spaces!
77  *
78  *    06/29/98 (Rob Lake)
79  *        error message now found in command.h
80  *
81  *    07/08/1998 (John P. Price)
82  *        removed extra returns; closer to MSDOS
83  *        fixed wide display so that an extra return is not displayed
84  *        when there is five filenames in the last line.
85  *
86  *    07/12/98 (Rob Lake)
87  *        Changed error messages
88  *
89  *    27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
90  *        added config.h include
91  *
92  *
93  *    04-Dec-1998 (Eric Kohl)
94  *        Converted source code to Win32, except recursive dir ("dir /s").
95  *
96  *    10-Dec-1998 (Eric Kohl)
97  *        Fixed recursive dir ("dir /s").
98  *
99  *    14-Dec-1998 (Eric Kohl)
100  *        Converted to Win32 directory functions and
101  *        fixed some output bugs. There are still some more ;)
102  *
103  *    10-Jan-1999 (Eric Kohl)
104  *        Added "/N" and "/4" options, "/O" is a dummy.
105  *        Added locale support.
106  *
107  *    20-Jan-1999 (Eric Kohl)
108  *        Redirection safe!
109  *
110  *    01-Mar-1999 (Eric Kohl)
111  *        Replaced all runtime io functions by their Win32 counterparts.
112  *
113  *    23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
114  *        dir /s now works in deeper trees
115  *
116  *    28-Jan-2004 (Michael Fritscher <michael@fritscher.net>)
117  *        Fix for /p, so it is working under Windows in GUI-mode, too.
118  *
119  *    30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
120  *        Fix /w to print long names.
121  *
122  *    27-Feb-2005 (Konstantinos Paliouras <squarious@gmail.com>)
123  *        Implemented all the switches that were missing, and made
124  *        the ROS dir very similar to windows dir. Major part of
125  *        the code is rewritten. /p is removed, to be rewritten in
126  *        the main cmd code.
127  *
128  *    1-Jul-2004 (Brandon Turner <turnerb7@msu.edu>)
129  *        Added /p back in using ConOutPrintfPaging
130  *
131  *    3-Feb-2007 (Paolo Devoti devotip at gmail)
132  *        Removed variables formerly in use to handle pagination
133  *        Pagination belongs to ConOutPrintfPaging
134  *        Removed already commented out code of old pagination
135  *
136  *    25-Aug-2015 (Pierre Schweitzer)
137  *        Implemented /R switch
138  *
139  *    6-Aug-2018 (Hermes Belusca-Maito and Katayama Hirofumi MZ)
140  *        Fix handling of patterns containing trailing dots.
141  */
142 
143 #include "precomp.h"
144 
145 #ifdef INCLUDE_CMD_DIR
146 
147 /* Time Field enumeration */
148 enum ETimeField
149 {
150     TF_CREATIONDATE     = 0,
151     TF_MODIFIEDDATE     = 1,
152     TF_LASTACCESSEDDATE = 2
153 };
154 
155 /* Ordered by enumeration */
156 enum EOrderBy
157 {
158     ORDER_NAME      = 0,
159     ORDER_SIZE      = 1,
160     ORDER_DIRECTORY = 2,
161     ORDER_EXTENSION = 3,
162     ORDER_TIME      = 4
163 };
164 
165 /* The struct for holding the switches */
166 typedef struct _DirSwitchesFlags
167 {
168     BOOL bBareFormat;   /* Bare Format */
169     BOOL bTSeparator;   /* Thousands separator */
170     BOOL bWideList;     /* Wide list format */
171     BOOL bWideListColSort;  /* Wide list format but sorted by column */
172     BOOL bLowerCase;    /* Uses lower case */
173     BOOL bNewLongList;  /* New long list */
174     BOOL bPause;        /* Pause per page */
175     BOOL bUser;         /* Displays the owner of file */
176     BOOL bRecursive;    /* Displays files in specified directory and all sub */
177     BOOL bShortName;    /* Displays the sort name of files if exist */
178     BOOL b4Digit;       /* Four digit year */
179     BOOL bDataStreams;  /* Displays alternate data streams */
180     struct
181     {
182         DWORD dwAttribVal;  /* The desired state of attribute */
183         DWORD dwAttribMask; /* Which attributes to check */
184     } stAttribs;            /* Displays files with this attributes only */
185     struct
186     {
187         enum EOrderBy eCriteria[3]; /* Criterias used to order by */
188         BOOL bCriteriaRev[3];       /* If the criteria is in reversed order */
189         short sCriteriaCount;       /* The quantity of criterias */
190     } stOrderBy;                    /* Ordered by criterias */
191     struct
192     {
193         enum ETimeField eTimeField; /* The time field that will be used for */
194     } stTimeField;                  /* The time field to display or use for sorting */
195 } DIRSWITCHFLAGS, *LPDIRSWITCHFLAGS;
196 
197 typedef struct _DIRFINDSTREAMNODE
198 {
199     WIN32_FIND_STREAM_DATA stStreamInfo;
200     struct _DIRFINDSTREAMNODE *ptrNext;
201 } DIRFINDSTREAMNODE, *PDIRFINDSTREAMNODE;
202 
203 typedef struct _DIRFINDINFO
204 {
205     WIN32_FIND_DATA stFindInfo;
206     PDIRFINDSTREAMNODE ptrHead;
207 } DIRFINDINFO, *PDIRFINDINFO;
208 
209 typedef struct _DIRFINDLISTNODE
210 {
211     DIRFINDINFO stInfo;
212     struct _DIRFINDLISTNODE *ptrNext;
213 } DIRFINDLISTNODE, *PDIRFINDLISTNODE;
214 
215 typedef BOOL
216 (WINAPI *PGETFREEDISKSPACEEX)(LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
217 
218 /* Globally save the # of dirs, files and bytes,
219  * probably later pass them to functions. Rob Lake  */
220 static ULONG recurse_dir_cnt;
221 static ULONG recurse_file_cnt;
222 static ULONGLONG recurse_bytes;
223 
224 /*
225  * help
226  *
227  * displays help screen for dir
228  * Rob Lake
229  */
230 static VOID
231 DirHelp(VOID)
232 {
233     ConOutResPaging(TRUE, STRING_DIR_HELP1);
234 }
235 
236 /* Check whether this is a dot-directory "." or "..", speed-optimized */
237 FORCEINLINE
238 BOOL
239 IsDotDirectory(
240     IN LPCTSTR pszPath)
241 {
242     return (  pszPath[0] == _T('.') &&
243             ( pszPath[1] == 0 || /* pszPath[1] == _T('\\') || */
244              (pszPath[1] == _T('.') && (pszPath[2] == 0 /* || pszPath[2] == _T('\\') */))
245             ) );
246 }
247 
248 FORCEINLINE
249 BOOL
250 IsDotDirectoryN(
251     IN const TCHAR* pPath,
252     IN SIZE_T Length)
253 {
254     return ((Length == 1 && pPath[0] == _T('.')) ||
255             (Length == 2 && pPath[0] == _T('.') && pPath[1] == _T('.')));
256 }
257 
258 /*
259  * DirReadParameters
260  *
261  * Parse the parameters and switches of the command line and exports them
262  */
263 static BOOL
264 DirReadParam(LPTSTR Line,               /* [IN] The line with the parameters & switches */
265              LPTSTR** params,           /* [OUT] The parameters after parsing */
266              LPINT entries,             /* [OUT] The number of parameters after parsing */
267              LPDIRSWITCHFLAGS lpFlags)  /* [IN/OUT] The flags after calculating switches */
268 {
269     TCHAR cCurSwitch;   /* The current switch */
270     TCHAR cCurChar;     /* Current examined character */
271     TCHAR cCurUChar;    /* Current upper examined character */
272     BOOL bNegative;     /* Negative switch */
273     BOOL bPNegative;    /* Negative switch parameter */
274     BOOL bIntoQuotes;   /* A flag showing if we are in quotes (") */
275     LPTSTR ptrStart;    /* A pointer to the first character of a parameter */
276     LPTSTR ptrEnd;      /* A pointer to the last character of a parameter */
277     BOOL bOrderByNoPar; /* A flag to indicate /O with no switch parameter */
278     LPTSTR temp;
279 
280     /* Initialize parameter array */
281     *params = NULL;
282     *entries = 0;
283 
284     /* Initialize variables; */
285     cCurSwitch = _T(' ');
286     bNegative = FALSE;
287     bPNegative = FALSE;
288 
289     /* We suppose that switch parameters
290        were given to avoid setting them to default
291        if the switch was not given */
292     bOrderByNoPar = FALSE;
293 
294     /* Main Loop (see README_DIR.txt) */
295     /* scan the command line char per char, and we process its char */
296     while (*Line)
297     {
298         /* we save current character as it is and its upper case */
299         cCurChar = *Line;
300         cCurUChar = _totupper(*Line);
301 
302         /* 1st section (see README_DIR.txt) */
303         /* When a switch is expecting */
304         if (cCurSwitch == _T('/'))
305         {
306             while (_istspace(*Line))
307                 Line++;
308 
309             bNegative = (*Line == _T('-'));
310             Line += bNegative;
311 
312             cCurChar = *Line;
313             cCurUChar = _totupper(*Line);
314 
315             if ((cCurUChar == _T('A')) ||(cCurUChar == _T('T')) || (cCurUChar == _T('O')))
316             {
317                 /* If positive, prepare for parameters... if negative, reset to defaults */
318                 switch (cCurUChar)
319                 {
320                 case _T('A'):
321                     lpFlags->stAttribs.dwAttribVal = 0L;
322                     lpFlags->stAttribs.dwAttribMask = 0L;
323                     if (bNegative)
324                         lpFlags->stAttribs.dwAttribMask = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
325                     break;
326                 case _T('T'):
327                     if (bNegative)
328                         lpFlags->stTimeField.eTimeField = TF_MODIFIEDDATE;
329                     break;
330                 case _T('O'):
331                     bOrderByNoPar = !bNegative;
332                     lpFlags->stOrderBy.sCriteriaCount = 0;
333                     break;
334                 }
335 
336                 if (!bNegative)
337                 {
338                     /* Positive switch, so it can take parameters. */
339                     cCurSwitch = cCurUChar;
340                     Line++;
341                     /* Skip optional leading colon */
342                     if (*Line == _T(':'))
343                         Line++;
344                     continue;
345                 }
346             }
347             else if (cCurUChar == _T('L'))
348                 lpFlags->bLowerCase = ! bNegative;
349             else if (cCurUChar == _T('B'))
350                 lpFlags->bBareFormat = ! bNegative;
351             else if (cCurUChar == _T('C'))
352                 lpFlags->bTSeparator = ! bNegative;
353             else if (cCurUChar == _T('W'))
354                 lpFlags->bWideList = ! bNegative;
355             else if (cCurUChar == _T('D'))
356                 lpFlags->bWideListColSort = ! bNegative;
357             else if (cCurUChar == _T('N'))
358                 lpFlags->bNewLongList = ! bNegative;
359             else if (cCurUChar == _T('P'))
360                 lpFlags->bPause = ! bNegative;
361             else if (cCurUChar == _T('Q'))
362                 lpFlags->bUser = ! bNegative;
363             else if (cCurUChar == _T('S'))
364                 lpFlags->bRecursive = ! bNegative;
365             else if (cCurUChar == _T('X'))
366                 lpFlags->bShortName = ! bNegative;
367             else if (cCurUChar == _T('R'))
368                 lpFlags->bDataStreams = ! bNegative;
369             else if (cCurChar == _T('4'))
370                 lpFlags->b4Digit = ! bNegative;
371             else if (cCurChar == _T('?'))
372             {
373                 DirHelp();
374                 return FALSE;
375             }
376             else
377             {
378                 error_invalid_switch ((TCHAR)_totupper(*Line));
379                 return FALSE;
380             }
381 
382             /* Make sure there's no extra characters at the end of the switch */
383             if (Line[1] && Line[1] != _T('/') && !_istspace(Line[1]))
384             {
385                 error_parameter_format(Line[1]);
386                 return FALSE;
387             }
388 
389             cCurSwitch = _T(' ');
390         }
391         else if (cCurSwitch == _T(' '))
392         {
393             /* 2nd section (see README_DIR.txt) */
394             /* We are expecting parameter or the unknown */
395 
396             if (cCurChar == _T('/'))
397                 cCurSwitch = _T('/');
398             else if (_istspace(cCurChar))
399                 /* do nothing */;
400             else
401             {
402                 /* This is a file/directory name parameter. Find its end */
403                 ptrStart = Line;
404                 bIntoQuotes = FALSE;
405                 while (*Line)
406                 {
407                     if (!bIntoQuotes && (*Line == _T('/') || _istspace(*Line)))
408                         break;
409                     bIntoQuotes ^= (*Line == _T('"'));
410                     Line++;
411                 }
412                 ptrEnd = Line;
413 
414                 /* Copy it to the entries list */
415                 temp = cmd_alloc((ptrEnd - ptrStart + 1) * sizeof(TCHAR));
416                 if (!temp)
417                     return FALSE;
418                 memcpy(temp, ptrStart, (ptrEnd - ptrStart) * sizeof(TCHAR));
419                 temp[ptrEnd - ptrStart] = _T('\0');
420                 StripQuotes(temp);
421                 if (!add_entry(entries, params, temp))
422                 {
423                     cmd_free(temp);
424                     freep(*params);
425                     return FALSE;
426                 }
427 
428                 cmd_free(temp);
429                 continue;
430             }
431         }
432         else
433         {
434             /* 3rd section (see README_DIR.txt) */
435             /* We are waiting for switch parameters */
436 
437             /* Check if there are no more switch parameters */
438             if ((cCurChar == _T('/')) || _istspace(cCurChar))
439             {
440                 /* Wrong decision path, reprocess current character */
441                 cCurSwitch = _T(' ');
442                 continue;
443             }
444             /* Process parameter switch */
445             switch (cCurSwitch)
446             {
447             case _T('A'):   /* Switch parameters for /A (attributes filter) */
448                 if (cCurChar == _T('-'))
449                     bPNegative = TRUE;
450                 else if (cCurUChar == _T('D'))
451                 {
452                     lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_DIRECTORY;
453                     if (bPNegative)
454                         lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_DIRECTORY;
455                     else
456                         lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_DIRECTORY;
457                 }
458                 else if (cCurUChar == _T('R'))
459                 {
460                     lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_READONLY;
461                     if (bPNegative)
462                         lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_READONLY;
463                     else
464                         lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_READONLY;
465                 }
466                 else if (cCurUChar == _T('H'))
467                 {
468                     lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_HIDDEN;
469                     if (bPNegative)
470                         lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_HIDDEN;
471                     else
472                         lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_HIDDEN;
473                 }
474                 else if (cCurUChar == _T('A'))
475                 {
476                     lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_ARCHIVE;
477                     if (bPNegative)
478                         lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_ARCHIVE;
479                     else
480                         lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_ARCHIVE;
481                 }
482                 else if (cCurUChar == _T('S'))
483                 {
484                     lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_SYSTEM;
485                     if (bPNegative)
486                         lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_SYSTEM;
487                     else
488                         lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_SYSTEM;
489                 }
490                 else
491                 {
492                     error_parameter_format((TCHAR)_totupper (*Line));
493                     return FALSE;
494                 }
495                 break;
496             case _T('T'):   /* Switch parameters for /T (time field) */
497                 if (cCurUChar == _T('C'))
498                     lpFlags->stTimeField.eTimeField= TF_CREATIONDATE ;
499                 else if (cCurUChar == _T('A'))
500                     lpFlags->stTimeField.eTimeField= TF_LASTACCESSEDDATE ;
501                 else if (cCurUChar == _T('W'))
502                     lpFlags->stTimeField.eTimeField= TF_MODIFIEDDATE  ;
503                 else
504                 {
505                     error_parameter_format((TCHAR)_totupper (*Line));
506                     return FALSE;
507                 }
508                 break;
509             case _T('O'):   /* Switch parameters for /O (order) */
510                 /* Ok a switch parameter was given */
511                 bOrderByNoPar = FALSE;
512 
513                 if (cCurChar == _T('-'))
514                     bPNegative = TRUE;
515                 else if (cCurUChar == _T('N'))
516                 {
517                     if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
518                     lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
519                     lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_NAME;
520                 }
521                 else if (cCurUChar == _T('S'))
522                 {
523                     if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
524                     lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
525                     lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_SIZE;
526                 }
527                 else if (cCurUChar == _T('G'))
528                 {
529                     if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
530                     lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
531                     lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_DIRECTORY;
532                 }
533                 else if (cCurUChar == _T('E'))
534                 {
535                     if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
536                     lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
537                     lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_EXTENSION;
538                 }
539                 else if (cCurUChar == _T('D'))
540                 {
541                     if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
542                     lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
543                     lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_TIME;
544                 }
545 
546                 else
547                 {
548                     error_parameter_format((TCHAR)_totupper (*Line));
549                     return FALSE;
550                 }
551 
552 
553             }
554             /* We check if we calculated the negative value and release the flag */
555             if ((cCurChar != _T('-')) && bPNegative)
556                 bPNegative = FALSE;
557         }
558 
559         Line++;
560     }
561 
562     /* /O with no switch parameters acts like /O:GN */
563     if (bOrderByNoPar)
564     {
565         lpFlags->stOrderBy.sCriteriaCount = 2;
566         lpFlags->stOrderBy.eCriteria[0] = ORDER_DIRECTORY;
567         lpFlags->stOrderBy.bCriteriaRev[0] = FALSE;
568         lpFlags->stOrderBy.eCriteria[1] = ORDER_NAME;
569         lpFlags->stOrderBy.bCriteriaRev[1] = FALSE;
570     }
571 
572     return TRUE;
573 }
574 
575 /* Print either with or without paging, depending on /P switch */
576 static BOOL
577 DirPrintf(LPDIRSWITCHFLAGS lpFlags, LPTSTR szFormat, ...)
578 {
579     BOOL Done = TRUE;
580     va_list arg_ptr;
581     va_start(arg_ptr, szFormat);
582     if (lpFlags->bPause)
583         Done = ConPrintfVPaging(&StdOutPager, FALSE, szFormat, arg_ptr);
584     else
585         ConPrintfV(StdOut, szFormat, arg_ptr);
586     va_end(arg_ptr);
587     return Done;
588 }
589 
590 
591 /*
592  * PrintDirectoryHeader
593  *
594  * print the header for the dir command
595  */
596 static BOOL
597 PrintDirectoryHeader(LPCTSTR szPath, LPDIRSWITCHFLAGS lpFlags)
598 {
599     TCHAR szMsg[RC_STRING_MAX_SIZE];
600     LPCTSTR szFullDir;
601     TCHAR szRootName[MAX_PATH];
602     TCHAR szVolName[80];
603     DWORD dwSerialNr;
604 
605     if (lpFlags->bBareFormat)
606         return TRUE;
607 
608     szFullDir = szPath;
609 
610     /* Get the media ID of the drive */
611     if (!GetVolumePathName(szFullDir, szRootName, ARRAYSIZE(szRootName)) ||
612         !GetVolumeInformation(szRootName, szVolName, ARRAYSIZE(szVolName),
613                               &dwSerialNr, NULL, NULL, NULL, 0))
614     {
615         return TRUE;
616     }
617 
618     /* Print drive info */
619     if (szVolName[0] != _T('\0'))
620     {
621         LoadString(CMD_ModuleHandle, STRING_DIR_HELP2, szMsg, ARRAYSIZE(szMsg));
622         DirPrintf(lpFlags, szMsg, _totupper(szRootName[0]), szVolName);
623     }
624     else
625     {
626         LoadString(CMD_ModuleHandle, STRING_DIR_HELP3, szMsg, ARRAYSIZE(szMsg));
627         DirPrintf(lpFlags, szMsg, _totupper(szRootName[0]));
628     }
629 
630     /* Print the volume serial number if the return was successful */
631     LoadString(CMD_ModuleHandle, STRING_DIR_HELP4, szMsg, ARRAYSIZE(szMsg));
632     DirPrintf(lpFlags, szMsg, HIWORD(dwSerialNr), LOWORD(dwSerialNr));
633 
634     return TRUE;
635 }
636 
637 
638 static VOID
639 DirPrintFileDateTime(TCHAR *lpDate,
640                      TCHAR *lpTime,
641                      LPWIN32_FIND_DATA lpFile,
642                      LPDIRSWITCHFLAGS lpFlags)
643 {
644     FILETIME ft;
645     SYSTEMTIME dt;
646 
647     /* Select the right time field */
648     switch (lpFlags->stTimeField.eTimeField)
649     {
650         case TF_CREATIONDATE:
651             if (!FileTimeToLocalFileTime(&lpFile->ftCreationTime, &ft))
652                 return;
653             FileTimeToSystemTime(&ft, &dt);
654             break;
655 
656         case TF_LASTACCESSEDDATE :
657             if (!FileTimeToLocalFileTime(&lpFile->ftLastAccessTime, &ft))
658                 return;
659             FileTimeToSystemTime(&ft, &dt);
660             break;
661 
662         case TF_MODIFIEDDATE:
663             if (!FileTimeToLocalFileTime(&lpFile->ftLastWriteTime, &ft))
664                 return;
665             FileTimeToSystemTime(&ft, &dt);
666             break;
667     }
668 
669     FormatDate(lpDate, &dt, lpFlags->b4Digit);
670     FormatTime(lpTime, &dt);
671 }
672 
673 INT
674 FormatDate(TCHAR *lpDate, LPSYSTEMTIME dt, BOOL b4Digit)
675 {
676     /* Format date */
677     WORD wYear = b4Digit ? dt->wYear : dt->wYear%100;
678     switch (nDateFormat)
679     {
680         case 0: /* mmddyy */
681         default:
682             return _stprintf(lpDate, _T("%02d%c%02d%c%0*d"),
683                     dt->wMonth, cDateSeparator,
684                     dt->wDay, cDateSeparator,
685                     b4Digit?4:2, wYear);
686             break;
687 
688         case 1: /* ddmmyy */
689             return _stprintf(lpDate, _T("%02d%c%02d%c%0*d"),
690                     dt->wDay, cDateSeparator, dt->wMonth,
691                     cDateSeparator, b4Digit?4:2, wYear);
692             break;
693 
694         case 2: /* yymmdd */
695             return _stprintf(lpDate, _T("%0*d%c%02d%c%02d"),
696                     b4Digit?4:2, wYear, cDateSeparator,
697                     dt->wMonth, cDateSeparator, dt->wDay);
698             break;
699     }
700 }
701 
702 INT
703 FormatTime(TCHAR *lpTime, LPSYSTEMTIME dt)
704 {
705     /* Format Time */
706     switch (nTimeFormat)
707     {
708         case 0: /* 12 hour format */
709         default:
710             return _stprintf(lpTime,_T("%02d%c%02u %cM"),
711                     (dt->wHour == 0 ? 12 : (dt->wHour <= 12 ? dt->wHour : dt->wHour - 12)),
712                     cTimeSeparator,
713                      dt->wMinute, (dt->wHour <= 11 ? _T('A') : _T('P')));
714             break;
715 
716         case 1: /* 24 hour format */
717             return _stprintf(lpTime, _T("%02d%c%02u"),
718                     dt->wHour, cTimeSeparator, dt->wMinute);
719             break;
720     }
721 }
722 
723 
724 static VOID
725 GetUserDiskFreeSpace(LPCTSTR lpRoot,
726                      PULARGE_INTEGER lpFreeSpace)
727 {
728     PGETFREEDISKSPACEEX pGetFreeDiskSpaceEx;
729     HINSTANCE hInstance;
730     DWORD dwSecPerCl;
731     DWORD dwBytPerSec;
732     DWORD dwFreeCl;
733     DWORD dwTotCl;
734     ULARGE_INTEGER TotalNumberOfBytes, TotalNumberOfFreeBytes;
735 
736     lpFreeSpace->QuadPart = 0;
737 
738     hInstance = GetModuleHandle(_T("KERNEL32"));
739     if (hInstance != NULL)
740     {
741         pGetFreeDiskSpaceEx = (PGETFREEDISKSPACEEX)GetProcAddress(hInstance,
742 #ifdef _UNICODE
743                                                 "GetDiskFreeSpaceExW");
744 #else
745                                                 "GetDiskFreeSpaceExA");
746 #endif
747         if (pGetFreeDiskSpaceEx != NULL)
748         {
749             if (pGetFreeDiskSpaceEx(lpRoot, lpFreeSpace, &TotalNumberOfBytes, &TotalNumberOfFreeBytes) != FALSE)
750                 return;
751         }
752     }
753 
754     GetDiskFreeSpace(lpRoot,
755                      &dwSecPerCl,
756                      &dwBytPerSec,
757                      &dwFreeCl,
758                      &dwTotCl);
759 
760     lpFreeSpace->QuadPart = dwSecPerCl * dwBytPerSec * dwFreeCl;
761 }
762 
763 
764 /*
765  * print_summary: prints dir summary
766  * Added by Rob Lake 06/17/98 to compact code
767  * Just copied Tim's Code and patched it a bit
768  */
769 static INT
770 PrintSummary(LPCTSTR szPath,
771              ULONG ulFiles,
772              ULONG ulDirs,
773              ULONGLONG u64Bytes,
774              LPDIRSWITCHFLAGS lpFlags,
775              BOOL TotalSummary)
776 {
777     TCHAR szMsg[RC_STRING_MAX_SIZE];
778     TCHAR szBuffer[64];
779     ULARGE_INTEGER uliFree;
780 
781     /* Here we check if we didn't find anything */
782     if (!(ulFiles + ulDirs))
783     {
784         if (!lpFlags->bRecursive || (TotalSummary && lpFlags->bRecursive))
785             error_file_not_found();
786         return 1;
787     }
788 
789     /* In bare format we don't print results */
790     if (lpFlags->bBareFormat)
791         return 0;
792 
793     /* Print recursive specific results */
794 
795     /* Take this code offline to fix /S does not print double info */
796     if (TotalSummary && lpFlags->bRecursive)
797     {
798         ConvertULargeInteger(u64Bytes, szBuffer, ARRAYSIZE(szBuffer), lpFlags->bTSeparator);
799         LoadString(CMD_ModuleHandle, STRING_DIR_HELP5, szMsg, ARRAYSIZE(szMsg));
800         DirPrintf(lpFlags, szMsg, ulFiles, szBuffer);
801     }
802     else
803     {
804         /* Print File Summary */
805         /* Condition to print summary is:
806         If we are not in bare format and if we have results! */
807         ConvertULargeInteger(u64Bytes, szBuffer, ARRAYSIZE(szBuffer), lpFlags->bTSeparator);
808         LoadString(CMD_ModuleHandle, STRING_DIR_HELP8, szMsg, ARRAYSIZE(szMsg));
809         DirPrintf(lpFlags, szMsg, ulFiles, szBuffer);
810     }
811 
812     /* Print total directories and free space */
813     if (!lpFlags->bRecursive || TotalSummary)
814     {
815         GetUserDiskFreeSpace(szPath, &uliFree);
816         ConvertULargeInteger(uliFree.QuadPart, szBuffer, ARRAYSIZE(szBuffer), lpFlags->bTSeparator);
817         LoadString(CMD_ModuleHandle, STRING_DIR_HELP6, szMsg, ARRAYSIZE(szMsg));
818         DirPrintf(lpFlags, szMsg, ulDirs, szBuffer);
819     }
820 
821     return 0;
822 }
823 
824 /*
825  * getExt
826  *
827  * Get the extension of a filename
828  */
829 TCHAR* getExt(const TCHAR* file)
830 {
831     static TCHAR *NoExt = _T("");
832     TCHAR* lastdot = _tcsrchr(file, _T('.'));
833     return (lastdot != NULL ? lastdot + 1 : NoExt);
834 }
835 
836 /*
837  * getName
838  *
839  * Get the name of the file without extension
840  */
841 static LPTSTR
842 getName(const TCHAR* file, TCHAR * dest)
843 {
844     INT_PTR iLen;
845     LPTSTR end;
846 
847     /* Check for dot-directories "." and ".." */
848     if (IsDotDirectory(file))
849     {
850         _tcscpy(dest, file);
851         return dest;
852     }
853 
854     end = _tcsrchr(file, _T('.'));
855     if (!end)
856         iLen = _tcslen(file);
857     else
858         iLen = (end - file);
859 
860     _tcsncpy(dest, file, iLen);
861     *(dest + iLen) = _T('\0');
862 
863     return dest;
864 }
865 
866 
867 /*
868  *  DirPrintNewList
869  *
870  * The function that prints in new style
871  */
872 static VOID
873 DirPrintNewList(PDIRFINDINFO ptrFiles[],        /* [IN]Files' Info */
874                 DWORD dwCount,                  /* [IN] The quantity of files */
875                 LPCTSTR szCurPath,              /* [IN] Full path of current directory */
876                 LPDIRSWITCHFLAGS lpFlags)       /* [IN] The flags used */
877 {
878     DWORD i;
879     TCHAR szSize[30];
880     TCHAR szShortName[15];
881     TCHAR szDate[20];
882     TCHAR szTime[20];
883     INT iSizeFormat;
884     ULARGE_INTEGER u64FileSize;
885     PDIRFINDSTREAMNODE ptrCurStream;
886 
887     for (i = 0; i < dwCount && !CheckCtrlBreak(BREAK_INPUT); i++)
888     {
889         /* Calculate size */
890         if (ptrFiles[i]->stFindInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
891         {
892             /* Junction */
893             iSizeFormat = -14;
894             _tcscpy(szSize, _T("<JUNCTION>"));
895         }
896         else if (ptrFiles[i]->stFindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
897         {
898             /* Directory */
899             iSizeFormat = -14;
900             _tcscpy(szSize, _T("<DIR>"));
901         }
902         else
903         {
904             /* File */
905             iSizeFormat = 14;
906             u64FileSize.HighPart = ptrFiles[i]->stFindInfo.nFileSizeHigh;
907             u64FileSize.LowPart = ptrFiles[i]->stFindInfo.nFileSizeLow;
908             ConvertULargeInteger(u64FileSize.QuadPart, szSize, 20, lpFlags->bTSeparator);
909         }
910 
911         /* Calculate short name */
912         szShortName[0] = _T('\0');
913         if (lpFlags->bShortName)
914             _stprintf(szShortName, _T(" %-12s"), ptrFiles[i]->stFindInfo.cAlternateFileName);
915 
916         /* Format date and time */
917         DirPrintFileDateTime(szDate, szTime, &ptrFiles[i]->stFindInfo, lpFlags);
918 
919         /* Print the line */
920         DirPrintf(lpFlags, _T("%10s  %-6s    %*s%s %s\n"),
921                   szDate,
922                   szTime,
923                   iSizeFormat,
924                   szSize,
925                   szShortName,
926                   ptrFiles[i]->stFindInfo.cFileName);
927 
928         /* Now, loop on the streams */
929         ptrCurStream = ptrFiles[i]->ptrHead;
930         while (ptrCurStream)
931         {
932             ConvertULargeInteger(ptrCurStream->stStreamInfo.StreamSize.QuadPart, szSize, 20, lpFlags->bTSeparator);
933 
934             /* Print the line */
935             DirPrintf(lpFlags, _T("%10s  %-6s    %*s%s %s%s\n"),
936                       L"",
937                       L"",
938                       16,
939                       szSize,
940                       L"",
941                       ptrFiles[i]->stFindInfo.cFileName,
942                       ptrCurStream->stStreamInfo.cStreamName);
943             ptrCurStream = ptrCurStream->ptrNext;
944         }
945     }
946 }
947 
948 
949 /*
950  *  DirPrintWideList
951  *
952  * The function that prints in wide list
953  */
954 static VOID
955 DirPrintWideList(PDIRFINDINFO ptrFiles[],       /* [IN] Files' Info */
956                  DWORD dwCount,                 /* [IN] The quantity of files */
957                  LPCTSTR szCurPath,             /* [IN] Full path of current directory */
958                  LPDIRSWITCHFLAGS lpFlags)      /* [IN] The flags used */
959 {
960     SHORT iScreenWidth;
961     USHORT iColumns;
962     USHORT iLines;
963     UINT_PTR iLongestName;
964     TCHAR szTempFname[MAX_PATH];
965     DWORD i;
966     DWORD j;
967     DWORD temp;
968 
969     /* Calculate longest name */
970     iLongestName = 1;
971     for (i = 0; i < dwCount; i++)
972     {
973         if (ptrFiles[i]->stFindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
974         {
975             /* Directories need 2 additional characters for brackets */
976             if ((_tcslen(ptrFiles[i]->stFindInfo.cFileName) + 2) > iLongestName)
977                 iLongestName = _tcslen(ptrFiles[i]->stFindInfo.cFileName) + 2;
978         }
979         else
980         {
981             if (_tcslen(ptrFiles[i]->stFindInfo.cFileName) > iLongestName)
982                 iLongestName = _tcslen(ptrFiles[i]->stFindInfo.cFileName);
983         }
984     }
985 
986     /* Count the highest number of columns */
987     GetScreenSize(&iScreenWidth, NULL);
988     iColumns = (USHORT)(iScreenWidth / iLongestName);
989 
990     /* Check if there is enough space for spaces between names */
991     if (((iLongestName * iColumns) + iColumns) >= (UINT)iScreenWidth)
992         iColumns --;
993 
994     /* A last check at iColumns to avoid division by zero */
995     if (!iColumns) iColumns = 1;
996 
997     /* Calculate the lines that will be printed */
998     iLines = (USHORT)((dwCount + iColumns - 1) / iColumns);
999 
1000     for (i = 0; i < iLines && !CheckCtrlBreak(BREAK_INPUT); i++)
1001     {
1002         for (j = 0; j < iColumns; j++)
1003         {
1004             if (lpFlags->bWideListColSort)
1005             {
1006                 /* Print Column sorted */
1007                 temp = (j * iLines) + i;
1008             }
1009             else
1010             {
1011                 /* Print Line sorted */
1012                 temp = (i * iColumns) + j;
1013             }
1014 
1015             if (temp >= dwCount) break;
1016 
1017             if (ptrFiles[temp]->stFindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1018                 _stprintf(szTempFname, _T("[%s]"), ptrFiles[temp]->stFindInfo.cFileName);
1019             else
1020                 _stprintf(szTempFname, _T("%s"), ptrFiles[temp]->stFindInfo.cFileName);
1021 
1022             DirPrintf(lpFlags, _T("%-*s"), iLongestName + 1, szTempFname);
1023         }
1024 
1025         /* Add a new line after the last item in the column */
1026         DirPrintf(lpFlags, _T("\n"));
1027     }
1028 }
1029 
1030 
1031 /*
1032  *  DirPrintOldList
1033  *
1034  * The function that prints in old style
1035  */
1036 static VOID
1037 DirPrintOldList(PDIRFINDINFO ptrFiles[],        /* [IN] Files' Info */
1038                 DWORD dwCount,                  /* [IN] The quantity of files */
1039                 LPCTSTR szCurPath,              /* [IN] Full path of current directory */
1040                 LPDIRSWITCHFLAGS lpFlags)       /* [IN] The flags used */
1041 {
1042     DWORD i;                        /* An indexer for "for"s */
1043     TCHAR szName[10];               /* The name of file */
1044     TCHAR szExt[5];                 /* The extension of file */
1045     TCHAR szDate[30],szTime[30];    /* Used to format time and date */
1046     TCHAR szSize[30];               /* The size of file */
1047     int iSizeFormat;                /* The format of size field */
1048     ULARGE_INTEGER u64FileSize;     /* The file size */
1049 
1050     for (i = 0; i < dwCount && !CheckCtrlBreak(BREAK_INPUT); i++)
1051     {
1052         /* Broke 8.3 format */
1053         if (*ptrFiles[i]->stFindInfo.cAlternateFileName )
1054         {
1055             /* If the file is long named then we read the alter name */
1056             getName( ptrFiles[i]->stFindInfo.cAlternateFileName, szName);
1057             _tcscpy(szExt, getExt( ptrFiles[i]->stFindInfo.cAlternateFileName));
1058         }
1059         else
1060         {
1061             /* If the file is not long name we read its original name */
1062             getName( ptrFiles[i]->stFindInfo.cFileName, szName);
1063             _tcscpy(szExt, getExt( ptrFiles[i]->stFindInfo.cFileName));
1064         }
1065 
1066         /* Calculate size */
1067         if (ptrFiles[i]->stFindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1068         {
1069             /* Directory, no size it's a directory */
1070             iSizeFormat = -17;
1071             _tcscpy(szSize, _T("<DIR>"));
1072         }
1073         else
1074         {
1075             /* File */
1076             iSizeFormat = 17;
1077             u64FileSize.HighPart = ptrFiles[i]->stFindInfo.nFileSizeHigh;
1078             u64FileSize.LowPart = ptrFiles[i]->stFindInfo.nFileSizeLow;
1079             ConvertULargeInteger(u64FileSize.QuadPart, szSize, 20, lpFlags->bTSeparator);
1080         }
1081 
1082         /* Format date and time */
1083         DirPrintFileDateTime(szDate,szTime,&ptrFiles[i]->stFindInfo,lpFlags);
1084 
1085         /* Print the line */
1086         DirPrintf(lpFlags, _T("%-8s %-3s  %*s %s  %s\n"),
1087                   szName,       /* The file's 8.3 name */
1088                   szExt,        /* The file's 8.3 extension */
1089                   iSizeFormat,  /* print format for size column */
1090                   szSize,       /* The size of file or "<DIR>" for dirs */
1091                   szDate,       /* The date of file/dir */
1092                   szTime);      /* The time of file/dir */
1093     }
1094 }
1095 
1096 /*
1097  *  DirPrintBareList
1098  *
1099  * The function that prints in bare format
1100  */
1101 static VOID
1102 DirPrintBareList(PDIRFINDINFO ptrFiles[],       /* [IN] Files' Info */
1103                  DWORD dwCount,                 /* [IN] The number of files */
1104                  LPCTSTR szCurPath,             /* [IN] Full path of current directory */
1105                  LPDIRSWITCHFLAGS lpFlags)      /* [IN] The flags used */
1106 {
1107     DWORD i;
1108 
1109     for (i = 0; i < dwCount && !CheckCtrlBreak(BREAK_INPUT); i++)
1110     {
1111         if (IsDotDirectory(ptrFiles[i]->stFindInfo.cFileName))
1112         {
1113             /* At bare format we don't print the dot-directories "." and ".." */
1114             continue;
1115         }
1116         if (lpFlags->bRecursive)
1117         {
1118             /* At recursive mode we print full path of file */
1119             DirPrintf(lpFlags, _T("%s\\%s\n"), szCurPath, ptrFiles[i]->stFindInfo.cFileName);
1120         }
1121         else
1122         {
1123             /* If we are not in recursive mode we print the file names */
1124             DirPrintf(lpFlags, _T("%s\n"), ptrFiles[i]->stFindInfo.cFileName);
1125         }
1126     }
1127 }
1128 
1129 
1130 /*
1131  * DirPrintFiles
1132  *
1133  * The functions that prints the files list
1134  */
1135 static VOID
1136 DirPrintFiles(PDIRFINDINFO ptrFiles[],      /* [IN] Files' Info */
1137               DWORD dwCount,                /* [IN] The quantity of files */
1138               LPCTSTR szCurPath,            /* [IN] Full path of current directory */
1139               LPDIRSWITCHFLAGS lpFlags)     /* [IN] The flags used */
1140 {
1141     TCHAR szMsg[RC_STRING_MAX_SIZE];
1142     TCHAR szTemp[MAX_PATH]; /* A buffer to format the directory header */
1143 
1144     /* Print trailing backslash for root directory of drive */
1145     _tcscpy(szTemp, szCurPath);
1146     if (_tcslen(szTemp) == 2 && szTemp[1] == _T(':'))
1147         _tcscat(szTemp, _T("\\"));
1148 
1149     /* Condition to print header:
1150        We are not printing in bare format
1151        and if we are in recursive mode... we must have results */
1152     if (!lpFlags->bBareFormat && !(lpFlags->bRecursive && (dwCount <= 0)))
1153     {
1154         LoadString(CMD_ModuleHandle, STRING_DIR_HELP7, szMsg, ARRAYSIZE(szMsg));
1155         if (!DirPrintf(lpFlags, szMsg, szTemp))
1156             return;
1157     }
1158 
1159     if (lpFlags->bBareFormat)
1160     {
1161         /* Bare format */
1162         DirPrintBareList(ptrFiles, dwCount, szCurPath, lpFlags);
1163     }
1164     else if (lpFlags->bShortName)
1165     {
1166         /* New list style / Short names */
1167         DirPrintNewList(ptrFiles, dwCount, szCurPath, lpFlags);
1168     }
1169     else if (lpFlags->bWideListColSort || lpFlags->bWideList)
1170     {
1171         /* Wide list */
1172         DirPrintWideList(ptrFiles, dwCount, szCurPath, lpFlags);
1173     }
1174     else if (lpFlags->bNewLongList )
1175     {
1176         /* New list style*/
1177         DirPrintNewList(ptrFiles, dwCount, szCurPath, lpFlags);
1178     }
1179     else
1180     {
1181         /* If nothing is selected old list is the default */
1182         DirPrintOldList(ptrFiles, dwCount, szCurPath, lpFlags);
1183     }
1184 }
1185 
1186 /*
1187  * CompareFiles
1188  *
1189  * Compares 2 files based on the order criteria
1190  */
1191 static BOOL
1192 CompareFiles(PDIRFINDINFO lpFile1,          /* [IN] A pointer to WIN32_FIND_DATA of file 1 */
1193              PDIRFINDINFO lpFile2,          /* [IN] A pointer to WIN32_FIND_DATA of file 2 */
1194              LPDIRSWITCHFLAGS lpFlags)      /* [IN] The flags that we use to list */
1195 {
1196   ULARGE_INTEGER u64File1;
1197   ULARGE_INTEGER u64File2;
1198   int i;
1199   long iComp = 0;   /* The comparison result */
1200 
1201     /* Calculate criteria by order given from user */
1202     for (i = 0; i < lpFlags->stOrderBy.sCriteriaCount; i++)
1203     {
1204 
1205         /* Calculate criteria */
1206         switch (lpFlags->stOrderBy.eCriteria[i])
1207         {
1208         case ORDER_SIZE:        /* Order by size /o:s */
1209             /* concat the 32bit integers to a 64bit */
1210             u64File1.LowPart = lpFile1->stFindInfo.nFileSizeLow;
1211             u64File1.HighPart = lpFile1->stFindInfo.nFileSizeHigh;
1212             u64File2.LowPart = lpFile2->stFindInfo.nFileSizeLow;
1213             u64File2.HighPart = lpFile2->stFindInfo.nFileSizeHigh;
1214 
1215             /* In case that difference is too big for a long */
1216             if (u64File1.QuadPart < u64File2.QuadPart)
1217                 iComp = -1;
1218             else if (u64File1.QuadPart > u64File2.QuadPart)
1219                 iComp = 1;
1220             else
1221                 iComp = 0;
1222             break;
1223 
1224         case ORDER_DIRECTORY:   /* Order by directory attribute /o:g */
1225             iComp = ((lpFile2->stFindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)-
1226                 (lpFile1->stFindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY));
1227             break;
1228 
1229         case ORDER_EXTENSION:   /* Order by extension name /o:e */
1230             iComp = _tcsicmp(getExt(lpFile1->stFindInfo.cFileName),getExt(lpFile2->stFindInfo.cFileName));
1231             break;
1232 
1233         case ORDER_NAME:        /* Order by filename /o:n */
1234             iComp = _tcsicmp(lpFile1->stFindInfo.cFileName, lpFile2->stFindInfo.cFileName);
1235             break;
1236 
1237         case ORDER_TIME:        /* Order by file's time /o:t */
1238             /* We compare files based on the time field selected by /t */
1239             switch (lpFlags->stTimeField.eTimeField)
1240             {
1241             case TF_CREATIONDATE:
1242                 /* concat the 32bit integers to a 64bit */
1243                 u64File1.LowPart = lpFile1->stFindInfo.ftCreationTime.dwLowDateTime;
1244                 u64File1.HighPart = lpFile1->stFindInfo.ftCreationTime.dwHighDateTime ;
1245                 u64File2.LowPart = lpFile2->stFindInfo.ftCreationTime.dwLowDateTime;
1246                 u64File2.HighPart = lpFile2->stFindInfo.ftCreationTime.dwHighDateTime ;
1247                 break;
1248             case TF_LASTACCESSEDDATE :
1249                 /* concat the 32bit integers to a 64bit */
1250                 u64File1.LowPart = lpFile1->stFindInfo.ftLastAccessTime.dwLowDateTime;
1251                 u64File1.HighPart = lpFile1->stFindInfo.ftLastAccessTime.dwHighDateTime ;
1252                 u64File2.LowPart = lpFile2->stFindInfo.ftLastAccessTime.dwLowDateTime;
1253                 u64File2.HighPart = lpFile2->stFindInfo.ftLastAccessTime.dwHighDateTime ;
1254                 break;
1255             case TF_MODIFIEDDATE:
1256                 /* concat the 32bit integers to a 64bit */
1257                 u64File1.LowPart = lpFile1->stFindInfo.ftLastWriteTime.dwLowDateTime;
1258                 u64File1.HighPart = lpFile1->stFindInfo.ftLastWriteTime.dwHighDateTime ;
1259                 u64File2.LowPart = lpFile2->stFindInfo.ftLastWriteTime.dwLowDateTime;
1260                 u64File2.HighPart = lpFile2->stFindInfo.ftLastWriteTime.dwHighDateTime ;
1261                 break;
1262             }
1263 
1264             /* In case that difference is too big for a long */
1265             if (u64File1.QuadPart < u64File2.QuadPart)
1266                 iComp = -1;
1267             else if (u64File1.QuadPart > u64File2.QuadPart)
1268                 iComp = 1;
1269             else
1270                 iComp = 0;
1271             break;
1272         }
1273 
1274         /* Reverse if desired */
1275         if (lpFlags->stOrderBy.bCriteriaRev[i])
1276             iComp *= -1;
1277 
1278         /* If that criteria was enough for distinguishing
1279            the files/dirs,there is no need to calculate the others*/
1280         if (iComp != 0) break;
1281     }
1282 
1283     /* Translate the value of iComp to boolean */
1284     return iComp > 0;
1285 }
1286 
1287 /*
1288  * QsortFiles
1289  *
1290  * Sort files by the order criterias using quicksort method
1291  */
1292 static VOID
1293 QsortFiles(PDIRFINDINFO ptrArray[],         /* [IN/OUT] The array with file info pointers */
1294            int i,                           /* [IN]     The index of first item in array */
1295            int j,                           /* [IN]     The index to last item in array */
1296            LPDIRSWITCHFLAGS lpFlags)        /* [IN]     The flags that we will use to sort */
1297 {
1298     PDIRFINDINFO lpTemp;   /* A temporary pointer */
1299     BOOL Way;
1300 
1301     if (i < j)
1302     {
1303         int First = i, Last = j, Temp;
1304         Way = TRUE;
1305         while (i != j)
1306         {
1307             if (Way == CompareFiles(ptrArray[i], ptrArray[j], lpFlags))
1308             {
1309                 /* Swap the pointers of the array */
1310                 lpTemp = ptrArray[i];
1311                 ptrArray[i]= ptrArray[j];
1312                 ptrArray[j] = lpTemp;
1313 
1314                 /* Swap the indexes for inverting sorting */
1315                 Temp = i;
1316                 i = j;
1317                 j =Temp;
1318 
1319                 Way = !Way;
1320             }
1321 
1322             j += (!Way - Way);
1323         }
1324 
1325         QsortFiles(ptrArray,First, i-1, lpFlags);
1326         QsortFiles(ptrArray,i+1,Last, lpFlags);
1327     }
1328 }
1329 
1330 static VOID
1331 DirNodeCleanup(PDIRFINDLISTNODE ptrStartNode,
1332                PDWORD pdwCount)
1333 {
1334     PDIRFINDLISTNODE ptrNextNode;
1335     PDIRFINDSTREAMNODE ptrFreeNode;
1336     while (ptrStartNode)
1337     {
1338         ptrNextNode = ptrStartNode->ptrNext;
1339         while (ptrStartNode->stInfo.ptrHead)
1340         {
1341             ptrFreeNode = ptrStartNode->stInfo.ptrHead;
1342             ptrStartNode->stInfo.ptrHead = ptrFreeNode->ptrNext;
1343             cmd_free(ptrFreeNode);
1344         }
1345         cmd_free(ptrStartNode);
1346         ptrStartNode = ptrNextNode;
1347         --(*pdwCount);
1348     }
1349 }
1350 
1351 /*
1352  * DirList
1353  *
1354  * The function that does everything except for printing results
1355  */
1356 static INT
1357 DirList(IN OUT LPTSTR szFullPath,   /* [IN] The full path we are listing with trailing '\', where dir starts */
1358         IN LPTSTR pszFilePart,      /* [IN] Pointer in the szFullPath buffer where the file (pattern) part starts*/
1359         LPDIRSWITCHFLAGS lpFlags)   /* [IN] The flags of the listing */
1360 {
1361     HANDLE hSearch;                     /* The handle of the search */
1362     HANDLE hRecSearch;                  /* The handle for searching recursively */
1363     HANDLE hStreams;                    /* The handle for alternate streams */
1364     WIN32_FIND_DATA wfdFileInfo;        /* The info of file that found */
1365     PDIRFINDINFO * ptrFileArray;        /* An array of pointers with all the files */
1366     PDIRFINDLISTNODE ptrStartNode;      /* The pointer to the first node */
1367     PDIRFINDLISTNODE ptrNextNode;       /* A pointer used for relatives references */
1368     TCHAR szSubPath[MAX_PATH];          /* The full path used for the recursive search */
1369     LPTSTR pszSubFilePart;
1370     TCHAR cPathSep;
1371     DWORD dwCount;                      /* A counter of files found in directory */
1372     DWORD dwCountFiles;                 /* Counter for files */
1373     DWORD dwCountDirs;                  /* Counter for directories */
1374     ULONGLONG u64CountBytes;            /* Counter for bytes */
1375     ULARGE_INTEGER u64Temp;             /* A temporary counter */
1376     WIN32_FIND_STREAM_DATA wfsdStreamInfo;
1377     PDIRFINDSTREAMNODE * ptrCurNode;    /* The pointer to the first stream */
1378     static HANDLE (WINAPI *pFindFirstStreamW)(LPCWSTR, STREAM_INFO_LEVELS, LPVOID, DWORD);
1379     static BOOL (WINAPI *pFindNextStreamW)(HANDLE, LPVOID);
1380 
1381     /* Initialize variables */
1382     ptrStartNode = NULL;
1383     ptrNextNode = NULL;
1384     dwCount = 0;
1385     dwCountFiles = 0;
1386     dwCountDirs = 0;
1387     u64CountBytes = 0;
1388 
1389     /* Prepare the linked list, first node is allocated */
1390     ptrStartNode = cmd_alloc(sizeof(DIRFINDLISTNODE));
1391     if (ptrStartNode == NULL)
1392     {
1393         WARN("Cannot allocate memory for ptrStartNode!\n");
1394         return 1;   /* Error cannot allocate memory for 1st object */
1395     }
1396     ptrStartNode->stInfo.ptrHead = NULL;
1397     ptrNextNode = ptrStartNode;
1398 
1399     /* Collect the results for the current directory */
1400     hSearch = FindFirstFile(szFullPath, &wfdFileInfo);
1401     if (hSearch != INVALID_HANDLE_VALUE)
1402     {
1403         do
1404         {
1405             if ((wfdFileInfo.dwFileAttributes & lpFlags->stAttribs.dwAttribMask) ==
1406                 (lpFlags->stAttribs.dwAttribMask & lpFlags->stAttribs.dwAttribVal))
1407             {
1408                 ptrNextNode->ptrNext = cmd_alloc(sizeof(DIRFINDLISTNODE));
1409                 if (ptrNextNode->ptrNext == NULL)
1410                 {
1411                     WARN("Cannot allocate memory for ptrNextNode->ptrNext!\n");
1412                     DirNodeCleanup(ptrStartNode, &dwCount);
1413                     FindClose(hSearch);
1414                     return 1;
1415                 }
1416 
1417                 /* Copy the info of search at linked list */
1418                 memcpy(&ptrNextNode->ptrNext->stInfo.stFindInfo,
1419                        &wfdFileInfo,
1420                        sizeof(WIN32_FIND_DATA));
1421 
1422                 /* If lower case is selected do it here */
1423                 if (lpFlags->bLowerCase)
1424                 {
1425                     _tcslwr(ptrNextNode->ptrNext->stInfo.stFindInfo.cAlternateFileName);
1426                     _tcslwr(ptrNextNode->ptrNext->stInfo.stFindInfo.cFileName);
1427                 }
1428 
1429                 /* No streams (yet?) */
1430                 ptrNextNode->ptrNext->stInfo.ptrHead = NULL;
1431 
1432                 /* Alternate streams are only displayed with new long list */
1433                 if (lpFlags->bNewLongList && lpFlags->bDataStreams)
1434                 {
1435                     if (!pFindFirstStreamW)
1436                     {
1437                         pFindFirstStreamW = (PVOID)GetProcAddress(GetModuleHandle(_T("kernel32")), "FindFirstStreamW");
1438                         pFindNextStreamW = (PVOID)GetProcAddress(GetModuleHandle(_T("kernel32")), "FindNextStreamW");
1439                     }
1440 
1441                     /* Try to get stream information */
1442                     if (pFindFirstStreamW && pFindNextStreamW)
1443                     {
1444                         hStreams = pFindFirstStreamW(wfdFileInfo.cFileName, FindStreamInfoStandard, &wfsdStreamInfo, 0);
1445                     }
1446                     else
1447                     {
1448                         hStreams = INVALID_HANDLE_VALUE;
1449                         ERR("FindFirstStreamW not supported!\n");
1450                     }
1451 
1452                     if (hStreams != INVALID_HANDLE_VALUE)
1453                     {
1454                         /* We totally ignore first stream. It contains data about ::$DATA */
1455                         ptrCurNode = &ptrNextNode->ptrNext->stInfo.ptrHead;
1456                         while (pFindNextStreamW(hStreams, &wfsdStreamInfo))
1457                         {
1458                             *ptrCurNode = cmd_alloc(sizeof(DIRFINDSTREAMNODE));
1459                             if (*ptrCurNode == NULL)
1460                             {
1461                                 WARN("Cannot allocate memory for *ptrCurNode!\n");
1462                                 DirNodeCleanup(ptrStartNode, &dwCount);
1463                                 FindClose(hStreams);
1464                                 FindClose(hSearch);
1465                                 return 1;
1466                             }
1467 
1468                             memcpy(&(*ptrCurNode)->stStreamInfo, &wfsdStreamInfo,
1469                                    sizeof(WIN32_FIND_STREAM_DATA));
1470 
1471                             /* If lower case is selected do it here */
1472                             if (lpFlags->bLowerCase)
1473                             {
1474                                 _tcslwr((*ptrCurNode)->stStreamInfo.cStreamName);
1475                             }
1476 
1477                             ptrCurNode = &(*ptrCurNode)->ptrNext;
1478                         }
1479 
1480                         FindClose(hStreams);
1481                         *ptrCurNode = NULL;
1482                     }
1483                 }
1484 
1485                 /* Continue at next node at linked list */
1486                 ptrNextNode = ptrNextNode->ptrNext;
1487                 dwCount++;
1488 
1489                 /* Grab statistics */
1490                 if (wfdFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1491                 {
1492                     /* Directory */
1493                     dwCountDirs++;
1494                 }
1495                 else
1496                 {
1497                     /* File */
1498                     dwCountFiles++;
1499                     u64Temp.HighPart = wfdFileInfo.nFileSizeHigh;
1500                     u64Temp.LowPart = wfdFileInfo.nFileSizeLow;
1501                     u64CountBytes += u64Temp.QuadPart;
1502                 }
1503             }
1504         } while (FindNextFile(hSearch, &wfdFileInfo));
1505         FindClose(hSearch);
1506     }
1507 
1508     /* Terminate list */
1509     ptrNextNode->ptrNext = NULL;
1510 
1511     /* Calculate and allocate space need for making an array of pointers */
1512     ptrFileArray = cmd_alloc(sizeof(PDIRFINDINFO) * dwCount);
1513     if (ptrFileArray == NULL)
1514     {
1515         WARN("Cannot allocate memory for ptrFileArray!\n");
1516         DirNodeCleanup(ptrStartNode, &dwCount);
1517         return 1;
1518     }
1519 
1520     /*
1521      * Create an array of pointers from the linked list
1522      * this will be used to sort and print data, rather than the list
1523      */
1524     ptrNextNode = ptrStartNode;
1525     dwCount = 0;
1526     while (ptrNextNode->ptrNext)
1527     {
1528         ptrFileArray[dwCount] = &ptrNextNode->ptrNext->stInfo;
1529         ptrNextNode = ptrNextNode->ptrNext;
1530         dwCount++;
1531     }
1532 
1533     /* Sort Data if requested */
1534     if (lpFlags->stOrderBy.sCriteriaCount > 0)
1535         QsortFiles(ptrFileArray, 0, dwCount-1, lpFlags);
1536 
1537     /* Print Data */
1538     cPathSep = pszFilePart[-1];
1539     pszFilePart[-1] = _T('\0'); /* Truncate to directory name only */
1540     DirPrintFiles(ptrFileArray, dwCount, szFullPath, lpFlags);
1541 
1542     if (lpFlags->bRecursive)
1543     {
1544         PrintSummary(szFullPath,
1545                      dwCountFiles,
1546                      dwCountDirs,
1547                      u64CountBytes,
1548                      lpFlags,
1549                      FALSE);
1550     }
1551     pszFilePart[-1] = cPathSep;
1552 
1553     /* Free array */
1554     cmd_free(ptrFileArray);
1555 
1556     /* Free linked list */
1557     DirNodeCleanup(ptrStartNode, &dwCount);
1558 
1559     if (CheckCtrlBreak(BREAK_INPUT))
1560         return 1;
1561 
1562     /* Add statistics to recursive statistics */
1563     recurse_dir_cnt += dwCountDirs;
1564     recurse_file_cnt += dwCountFiles;
1565     recurse_bytes += u64CountBytes;
1566 
1567     /*
1568      * Do the recursive job if requested.
1569      * The recursion is done on ALL (independent of their attributes)
1570      * directories of the current one.
1571      */
1572     if (lpFlags->bRecursive)
1573     {
1574         /* The new search is involving any *.* file */
1575         memcpy(szSubPath, szFullPath, (pszFilePart - szFullPath) * sizeof(TCHAR));
1576         _tcscpy(&szSubPath[pszFilePart - szFullPath], _T("*.*"));
1577 
1578         hRecSearch = FindFirstFile(szSubPath, &wfdFileInfo);
1579         if (hRecSearch != INVALID_HANDLE_VALUE)
1580         {
1581             do
1582             {
1583                 /* We search for directories other than "." and ".." */
1584                 if (!IsDotDirectory(wfdFileInfo.cFileName) &&
1585                     (wfdFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1586                 {
1587                     /* Concat the path and the directory to do recursive */
1588                     memcpy(szSubPath, szFullPath, (pszFilePart - szFullPath) * sizeof(TCHAR));
1589                     _tcscpy(&szSubPath[pszFilePart - szFullPath], wfdFileInfo.cFileName);
1590                     _tcscat(szSubPath, _T("\\"));
1591                     pszSubFilePart = &szSubPath[_tcslen(szSubPath)];
1592                     _tcscat(pszSubFilePart, pszFilePart);
1593 
1594                     /* We do the same for the directory */
1595                     if (DirList(szSubPath, pszSubFilePart, lpFlags) != 0)
1596                     {
1597                         FindClose(hRecSearch);
1598                         return 1;
1599                     }
1600                 }
1601             } while (FindNextFile(hRecSearch, &wfdFileInfo));
1602         }
1603         FindClose(hRecSearch);
1604     }
1605 
1606     return 0;
1607 }
1608 
1609 static VOID
1610 ResolvePattern(
1611     IN LPTSTR pszPattern,
1612     IN DWORD nBufferLength,
1613     OUT LPTSTR pszFullPath,
1614     OUT LPTSTR* ppszPatternPart OPTIONAL)
1615 {
1616     LPTSTR pCurDir, pNextDir, ptr;
1617     LPTSTR pszPatternPart;
1618     TCHAR  szNewPattern[MAX_PATH];
1619 
1620     /*
1621      * We are going to use GetFullPathName() to properly determine the actual
1622      * full path from the pattern. However, due to the fact GetFullPathName()
1623      * strips parts of the file name component in case the pattern contains
1624      * path specification with trailing dots, it is required to perform a
1625      * pre-treatment on the pattern and a post-treatment on the obtained path.
1626      * This is mandatory in order to use the correct file search criterion.
1627      *
1628      * One particular case is when the pattern specifies a dots-only directory
1629      * followed by either the "." or ".." special directories. In this case the
1630      * GetFullPathName() function may completely miss the dots-only directory.
1631      * An example is given by the pattern (C-string notation) "\\...\\." .
1632      * To cope with this problem we need to partially canonicalize the pattern
1633      * by collapsing any "." or ".." special directory that immediately follows
1634      * a dots-only directory. We collapse in addition consecutive backslashes.
1635      *
1636      * Finally, trailing dots are skipped by GetFullPathName(). Therefore
1637      * a pattern that matches files with no extension, for example: "*." ,
1638      * or: "dir\\noextfile." , are reduced to simply "*" or "dir\\noextfile",
1639      * that match files with extensions. Or, a pattern specifying a trailing
1640      * dots-only directory: "dir\\..." gets completely ignored and only the
1641      * full path to "dir" is returned.
1642      * To fix this second problem we need to restore the last part of the path
1643      * pattern using the pattern that has been first partially canonicalized.
1644      *
1645      * Note however that the "." or ".." special directories are always
1646      * interpreted correctly by GetFullPathName().
1647      */
1648 
1649     /* Make a copy of the path pattern */
1650     ASSERT(_tcslen(pszPattern) < ARRAYSIZE(szNewPattern));
1651     _tcscpy(szNewPattern, pszPattern);
1652     pszPattern = szNewPattern;
1653 
1654     TRACE("Original pszPattern: %S\n", pszPattern);
1655 
1656     /* Convert slashes into backslashes */
1657     pNextDir = pszPattern;
1658     while ((pNextDir = _tcschr(pNextDir, _T('/'))))
1659         *pNextDir++ = _T('\\');
1660 
1661     /*
1662      * Find any dots-only directory and collapse any "." or ".." special
1663      * directory that immediately follows it.
1664      * Note that we just start looking after the first path separator. Indeed,
1665      * dots-only directories that are not preceded by a path separator, and so
1666      * appear first in the pattern, for example: "...\dir", or: "..." , are
1667      * either correctly handled by GetFullPathName() because they are followed
1668      * by a non-pathological directory, or because they are handled when we
1669      * restore the trailing dots pattern piece in the next step.
1670      */
1671     pNextDir = pszPattern;
1672     while (pNextDir)
1673     {
1674         pCurDir = pNextDir;
1675 
1676         /* Find the next path separator in the pattern */
1677         pNextDir = _tcschr(pNextDir, _T('\\'));
1678         if (!pNextDir)
1679             break;
1680 
1681         /* Ignore the special "." and ".." directories that are correctly handled */
1682         if ((pNextDir - pCurDir == 0) || IsDotDirectoryN(pCurDir, pNextDir - pCurDir))
1683         {
1684             /* Found such a directory, ignore */
1685             ++pNextDir;
1686             continue;
1687         }
1688 
1689         /* Check whether this is a dots-only directory */
1690         for (ptr = pCurDir; ptr < pNextDir; ++ptr)
1691         {
1692             if (*ptr != _T('.'))
1693                 break;
1694         }
1695         if (ptr < pNextDir)
1696         {
1697             /* Not a dots-only directory, ignore */
1698             ++pNextDir;
1699             continue;
1700         }
1701 
1702         /* Skip any consecutive backslashes */
1703         for (ptr = pNextDir; *ptr == _T('\\'); ++ptr) ;
1704 
1705         /* pCurDir is a dots-only directory, perform partial canonicalization */
1706 
1707         /* Remove any following "." directory */
1708         if (ptr[0] == _T('.') && (ptr[1] == _T('\\') || ptr[1] == 0))
1709         {
1710             memmove(pNextDir, ptr + 1, (_tcslen(ptr + 1) + 1) * sizeof(TCHAR));
1711         }
1712         /* Remove any following ".." directory */
1713         else if (ptr[0] == _T('.') && ptr[1] == _T('.') && (ptr[2] == _T('\\') || ptr[2] == 0))
1714         {
1715             /* Skip any consecutive backslashes before the next directory */
1716             for (ptr = ptr + 2; *ptr == _T('\\'); ++ptr) ;
1717 
1718             memmove(pCurDir, ptr, (_tcslen(ptr) + 1) * sizeof(TCHAR));
1719             pNextDir = pCurDir;
1720         }
1721         else
1722         {
1723             ++pNextDir;
1724 
1725             /* Collapse consecutive backslashes */
1726             if (ptr > pNextDir)
1727                 memmove(pNextDir, ptr, (_tcslen(ptr) + 1) * sizeof(TCHAR));
1728         }
1729     }
1730 
1731     /* An empty pattern means we enumerate all files in the current directory */
1732     if (!*pszPattern)
1733         _tcscpy(pszPattern, _T("*"));
1734 
1735     TRACE("New pszPattern: %S\n", pszPattern);
1736 
1737     /* Create the full path */
1738     if (GetFullPathName(pszPattern, nBufferLength, pszFullPath, &pszPatternPart) == 0)
1739     {
1740         _tcscpy(pszFullPath, pszPattern);
1741         pszPatternPart = NULL;
1742     }
1743 
1744     TRACE("pszFullPath (1): %S\n", pszFullPath);
1745     TRACE("pszPatternPart (1): %S\n", pszPatternPart);
1746 
1747     /*
1748      * Restore the correct file name component in case the pattern contained
1749      * trailing dots that have been skipped by GetFullPathName().
1750      */
1751 
1752     /* Find the last path separator in the original szPath */
1753     pNextDir = _tcsrchr(pszPattern, _T('\\'));
1754     if (pNextDir)
1755     {
1756         /* Skip past the separator and look at the path */
1757         ++pNextDir;
1758     }
1759     else
1760     {
1761         /* The pattern is the path we need to look at */
1762         pNextDir = pszPattern;
1763     }
1764 
1765     /*
1766      * When pszPatternPart == NULL this means that pszFullPath should be a
1767      * directory; however it might have happened that the original pattern
1768      * was specifying a dots-only directory, that has been stripped off by
1769      * GetFullPathName(). In both these cases we need to restore these as
1770      * they are part of the actual directory path; the exception being if
1771      * these are the special "." or ".." directories.
1772      */
1773     if (pszPatternPart == NULL)
1774     {
1775         ASSERT(pszFullPath[_tcslen(pszFullPath)-1] == _T('\\'));
1776 
1777         /* Anything NOT being "." or ".." (the special directories) must be fully restored */
1778         if (*pNextDir && !IsDotDirectory(pNextDir))
1779         {
1780             pszPatternPart = &pszFullPath[_tcslen(pszFullPath)];
1781             _tcscpy(pszPatternPart, pNextDir);
1782             pszPatternPart = NULL;
1783         }
1784     }
1785     else if (_tcscmp(pNextDir, pszPatternPart) != 0)
1786     {
1787         /*
1788          * For example, pszPatternPart == "." or ".." and we do not need to
1789          * do anything for these, or pszPatternPart == "dir\\noextfile." and
1790          * we need to restore all the trailing points.
1791          */
1792         TRACE("pszPatternPart: %S is DIFFERENT from file criterion: %S\n", pszPatternPart, pNextDir);
1793 
1794         /* Anything NOT being "." or ".." (the special directories) must be fully restored */
1795         if (*pNextDir && !IsDotDirectory(pNextDir))
1796         {
1797             /* Restore the correct file criterion */
1798             _tcscpy(pszPatternPart, pNextDir);
1799         }
1800     }
1801 
1802     TRACE("pszFullPath (2): %S\n", pszFullPath);
1803 
1804     /*
1805      * If no wildcard or file was specified and this is a directory,
1806      * display all files in it.
1807      */
1808     if (pszPatternPart == NULL || IsExistingDirectory(pszFullPath))
1809     {
1810         pszPatternPart = &pszFullPath[_tcslen(pszFullPath)];
1811         if (pszPatternPart[-1] != _T('\\'))
1812             *pszPatternPart++ = _T('\\');
1813         _tcscpy(pszPatternPart, _T("*"));
1814     }
1815 
1816     TRACE("pszPatternPart (2): %S\n", pszPatternPart);
1817 
1818     if (ppszPatternPart)
1819         *ppszPatternPart = pszPatternPart;
1820 }
1821 
1822 /*
1823  * dir
1824  *
1825  * internal dir command
1826  */
1827 INT
1828 CommandDir(LPTSTR rest)
1829 {
1830     TCHAR   dircmd[MAX_PATH];   /* A variable to store the DIRCMD environment variable */
1831     TCHAR   prev_volume[MAX_PATH];
1832     TCHAR   szFullPath[MAX_PATH];
1833     LPTSTR* params = NULL;
1834     LPTSTR  pszFilePart;
1835     TCHAR   cPathSep;
1836     INT     entries = 0;
1837     UINT    loop = 0;
1838     DIRSWITCHFLAGS stFlags;
1839     INT ret = 1;
1840     BOOL ChangedVolume;
1841 
1842     /* Initialize Switch Flags < Default switches are set here! > */
1843     stFlags.b4Digit = TRUE;
1844     stFlags.bBareFormat = FALSE;
1845     stFlags.bDataStreams = FALSE;
1846     stFlags.bLowerCase = FALSE;
1847     stFlags.bNewLongList = TRUE;
1848     stFlags.bPause = FALSE;
1849     stFlags.bRecursive = FALSE;
1850     stFlags.bShortName = FALSE;
1851     stFlags.bTSeparator = TRUE;
1852     stFlags.bUser = FALSE;
1853     stFlags.bWideList = FALSE;
1854     stFlags.bWideListColSort = FALSE;
1855     stFlags.stTimeField.eTimeField = TF_MODIFIEDDATE;
1856     stFlags.stAttribs.dwAttribMask = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
1857     stFlags.stAttribs.dwAttribVal = 0L;
1858     stFlags.stOrderBy.sCriteriaCount = 0;
1859 
1860     nErrorLevel = 0;
1861 
1862     /* Read the parameters from the DIRCMD environment variable */
1863     if (GetEnvironmentVariable (_T("DIRCMD"), dircmd, ARRAYSIZE(dircmd)))
1864     {
1865         if (!DirReadParam(dircmd, &params, &entries, &stFlags))
1866         {
1867             nErrorLevel = 1;
1868             goto cleanup;
1869         }
1870     }
1871 
1872     /* Read the parameters */
1873     if (!DirReadParam(rest, &params, &entries, &stFlags) || CheckCtrlBreak(BREAK_INPUT))
1874     {
1875         nErrorLevel = 1;
1876         goto cleanup;
1877     }
1878 
1879     /* Default to current directory */
1880     if (entries == 0)
1881     {
1882         if (!add_entry(&entries, &params, _T("*")))
1883         {
1884             nErrorLevel = 1;
1885             goto cleanup;
1886         }
1887     }
1888 
1889     prev_volume[0] = _T('\0');
1890 
1891     /* Reset paging state */
1892     if (stFlags.bPause)
1893         ConOutPrintfPaging(TRUE, _T(""));
1894 
1895     for (loop = 0; loop < (UINT)entries; loop++)
1896     {
1897         if (CheckCtrlBreak(BREAK_INPUT))
1898         {
1899             nErrorLevel = 1;
1900             goto cleanup;
1901         }
1902 
1903         recurse_dir_cnt = 0L;
1904         recurse_file_cnt = 0L;
1905         recurse_bytes = 0;
1906 
1907         /* <Debug :>
1908            Uncomment this to show the final state of switch flags*/
1909         {
1910             int i;
1911             TRACE("Attributes mask/value %x/%x\n",stFlags.stAttribs.dwAttribMask,stFlags.stAttribs.dwAttribVal);
1912             TRACE("(B) Bare format : %i\n", stFlags.bBareFormat);
1913             TRACE("(C) Thousand : %i\n", stFlags.bTSeparator);
1914             TRACE("(W) Wide list : %i\n", stFlags.bWideList);
1915             TRACE("(D) Wide list sort by column : %i\n", stFlags.bWideListColSort);
1916             TRACE("(L) Lowercase : %i\n", stFlags.bLowerCase);
1917             TRACE("(N) New : %i\n", stFlags.bNewLongList);
1918             TRACE("(O) Order : %i\n", stFlags.stOrderBy.sCriteriaCount);
1919             for (i =0;i<stFlags.stOrderBy.sCriteriaCount;i++)
1920                 TRACE(" Order Criteria [%i]: %i (Reversed: %i)\n",i, stFlags.stOrderBy.eCriteria[i], stFlags.stOrderBy.bCriteriaRev[i]);
1921             TRACE("(P) Pause : %i\n", stFlags.bPause);
1922             TRACE("(Q) Owner : %i\n", stFlags.bUser);
1923             TRACE("(R) Data stream : %i\n", stFlags.bDataStreams);
1924             TRACE("(S) Recursive : %i\n", stFlags.bRecursive);
1925             TRACE("(T) Time field : %i\n", stFlags.stTimeField.eTimeField);
1926             TRACE("(X) Short names : %i\n", stFlags.bShortName);
1927             TRACE("Parameter : %s\n", debugstr_aw(params[loop]));
1928         }
1929 
1930         /* Print the drive header if the volume changed */
1931         ChangedVolume = TRUE;
1932 
1933         if (!stFlags.bBareFormat &&
1934             GetVolumePathName(params[loop], szFullPath, ARRAYSIZE(szFullPath)))
1935         {
1936             if (!_tcscmp(szFullPath, prev_volume))
1937                 ChangedVolume = FALSE;
1938             else
1939                 _tcscpy(prev_volume, szFullPath);
1940         }
1941 
1942         /* Resolve the pattern */
1943         ResolvePattern(params[loop], ARRAYSIZE(szFullPath), szFullPath, &pszFilePart);
1944 
1945         /* Print the header */
1946         cPathSep = pszFilePart[-1];
1947         pszFilePart[-1] = _T('\0'); /* Truncate to directory name only */
1948         if (ChangedVolume && !stFlags.bBareFormat &&
1949             !PrintDirectoryHeader(szFullPath, &stFlags))
1950         {
1951             nErrorLevel = 1;
1952             goto cleanup;
1953         }
1954         pszFilePart[-1] = cPathSep;
1955 
1956         /* Perform the actual directory listing */
1957         if (DirList(szFullPath, pszFilePart, &stFlags) != 0)
1958         {
1959             nErrorLevel = 1;
1960             goto cleanup;
1961         }
1962 
1963         /* Print the footer */
1964         pszFilePart[-1] = _T('\0'); /* Truncate to directory name only */
1965         PrintSummary(szFullPath,
1966                      recurse_file_cnt,
1967                      recurse_dir_cnt,
1968                      recurse_bytes,
1969                      &stFlags,
1970                      TRUE);
1971     }
1972 
1973     ret = 0;
1974 
1975 cleanup:
1976     freep(params);
1977 
1978     return ret;
1979 }
1980 
1981 #endif
1982 
1983 /* EOF */
1984