[NTOS:KE/x64] Handle NMI vs swapgs race condition
[reactos.git] / base / shell / cmd / dir.c
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
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
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
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
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
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
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
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
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
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
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
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 */
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
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
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
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
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
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
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
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
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
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
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
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
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 */