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