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