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