2 * DIR.C - dir internal command.
7 * 01/29/97 (Tim Norman)
10 * 06/13/97 (Tim Norman)
13 * 07/12/97 (Tim Norman)
14 * Fixed bug that caused the root directory to be unlistable
16 * 07/12/97 (Marc Desrochers)
17 * Changed to use maxx, maxy instead of findxy()
20 * Added compatibility for /w in dir
23 * Compatibility for dir/s started
24 * Tested that program finds directories off root fine
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.
33 * Found problem that caused COM not to work
37 * added free mem routine
40 * debugged the free mem routine
41 * debugged whole thing some more
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
50 * Cleaned up code a bit, added comments
53 * Added error checking to my previously added routines
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
69 * Added check for /-(switch) to turn off previously defined
71 * Added ability to check for DIRCMD in environment and
76 * Now can dir *.ext/X, no spaces!
79 * error message now found in command.h
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.
87 * Changed error messages
89 * 27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
90 * added config.h include
93 * 04-Dec-1998 (Eric Kohl)
94 * Converted source code to Win32, except recursive dir ("dir /s").
96 * 10-Dec-1998 (Eric Kohl)
97 * Fixed recursive dir ("dir /s").
99 * 14-Dec-1998 (Eric Kohl)
100 * Converted to Win32 directory functions and
101 * fixed some output bugs. There are still some more ;)
103 * 10-Jan-1999 (Eric Kohl)
104 * Added "/N" and "/4" options, "/O" is a dummy.
105 * Added locale support.
107 * 20-Jan-1999 (Eric Kohl)
110 * 01-Mar-1999 (Eric Kohl)
111 * Replaced all runtime io functions by their Win32 counterparts.
113 * 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
114 * dir /s now works in deeper trees
116 * 28-Jan-2004 (Michael Fritscher <michael@fritscher.net>)
117 * Fix for /p, so it is working under Windows in GUI-mode, too.
119 * 30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
120 * Fix /w to print long names.
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
128 * 1-Jul-2004 (Brandon Turner <turnerb7@msu.edu>)
129 * Added /p back in using ConOutPrintfPaging
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
136 * 25-Aug-2015 (Pierre Schweitzer)
137 * Implemented /R switch
139 * 6-Aug-2018 (Hermes Belusca-Maito and Katayama Hirofumi MZ)
140 * Fix handling of patterns containing trailing dots.
146 #ifdef INCLUDE_CMD_DIR
148 /* Time Field enumeration */
153 TF_LASTACCESSEDDATE
= 2
156 /* Ordered by enumeration */
166 /* The struct for holding the switches */
167 typedef struct _DirSwitchesFlags
169 BOOL bBareFormat
; /* Bare Format */
170 BOOL bTSeparator
; /* Thousands separator */
171 BOOL bWideList
; /* Wide list format */
172 BOOL bWideListColSort
; /* Wide list format but sorted by column */
173 BOOL bLowerCase
; /* Uses lower case */
174 BOOL bNewLongList
; /* New long list */
175 BOOL bPause
; /* Pause per page */
176 BOOL bUser
; /* Displays the owner of file */
177 BOOL bRecursive
; /* Displays files in specified directory and all sub */
178 BOOL bShortName
; /* Displays the sort name of files if exist */
179 BOOL b4Digit
; /* Four digit year */
180 BOOL bDataStreams
; /* Displays alternate data streams */
183 DWORD dwAttribVal
; /* The desired state of attribute */
184 DWORD dwAttribMask
; /* Which attributes to check */
185 } stAttribs
; /* Displays files with this attributes only */
188 enum EOrderBy eCriteria
[3]; /* Criterias used to order by */
189 BOOL bCriteriaRev
[3]; /* If the criteria is in reversed order */
190 short sCriteriaCount
; /* The quantity of criterias */
191 } stOrderBy
; /* Ordered by criterias */
194 enum ETimeField eTimeField
; /* The time field that will be used for */
195 } stTimeField
; /* The time field to display or use for sorting */
196 } DIRSWITCHFLAGS
, *LPDIRSWITCHFLAGS
;
198 typedef struct _DIRFINDSTREAMNODE
200 WIN32_FIND_STREAM_DATA stStreamInfo
;
201 struct _DIRFINDSTREAMNODE
*ptrNext
;
202 } DIRFINDSTREAMNODE
, *PDIRFINDSTREAMNODE
;
204 typedef struct _DIRFINDINFO
206 WIN32_FIND_DATA stFindInfo
;
207 PDIRFINDSTREAMNODE ptrHead
;
208 } DIRFINDINFO
, *PDIRFINDINFO
;
210 typedef struct _DIRFINDLISTNODE
213 struct _DIRFINDLISTNODE
*ptrNext
;
214 } DIRFINDLISTNODE
, *PDIRFINDLISTNODE
;
217 (WINAPI
*PGETFREEDISKSPACEEX
)(LPCTSTR
, PULARGE_INTEGER
, PULARGE_INTEGER
, PULARGE_INTEGER
);
219 /* Globally save the # of dirs, files and bytes,
220 * probably later pass them to functions. Rob Lake */
221 static ULONG recurse_dir_cnt
;
222 static ULONG recurse_file_cnt
;
223 static ULONGLONG recurse_bytes
;
228 * displays help screen for dir
234 ConOutResPaging(TRUE
, STRING_DIR_HELP1
);
237 /* Check whether this is a dot-directory "." or "..", speed-optimized */
243 return ( pszPath
[0] == _T('.') &&
244 ( pszPath
[1] == 0 || /* pszPath[1] == _T('\\') || */
245 (pszPath
[1] == _T('.') && (pszPath
[2] == 0 /* || pszPath[2] == _T('\\') */))
252 IN
const TCHAR
* pPath
,
255 return ((Length
== 1 && pPath
[0] == _T('.')) ||
256 (Length
== 2 && pPath
[0] == _T('.') && pPath
[1] == _T('.')));
262 * Parse the parameters and switches of the command line and exports them
265 DirReadParam(LPTSTR Line
, /* [IN] The line with the parameters & switches */
266 LPTSTR
** params
, /* [OUT] The parameters after parsing */
267 LPINT entries
, /* [OUT] The number of parameters after parsing */
268 LPDIRSWITCHFLAGS lpFlags
) /* [IN/OUT] The flags after calculating switches */
270 TCHAR cCurSwitch
; /* The current switch */
271 TCHAR cCurChar
; /* Current examined character */
272 TCHAR cCurUChar
; /* Current upper examined character */
273 BOOL bNegative
; /* Negative switch */
274 BOOL bPNegative
; /* Negative switch parameter */
275 BOOL bIntoQuotes
; /* A flag showing if we are in quotes (") */
276 LPTSTR ptrStart
; /* A pointer to the first character of a parameter */
277 LPTSTR ptrEnd
; /* A pointer to the last character of a parameter */
278 BOOL bOrderByNoPar
; /* A flag to indicate /O with no switch parameter */
281 /* Initialize parameter array */
285 /* Initialize variables; */
286 cCurSwitch
= _T(' ');
290 /* We suppose that switch parameters
291 were given to avoid setting them to default
292 if the switch was not given */
293 bOrderByNoPar
= FALSE
;
295 /* Main Loop (see README_DIR.txt) */
296 /* scan the command line char per char, and we process its char */
299 /* we save current character as it is and its upper case */
301 cCurUChar
= _totupper(*Line
);
303 /* 1st section (see README_DIR.txt) */
304 /* When a switch is expecting */
305 if (cCurSwitch
== _T('/'))
307 while (_istspace(*Line
))
310 bNegative
= (*Line
== _T('-'));
314 cCurUChar
= _totupper(*Line
);
316 if ((cCurUChar
== _T('A')) ||(cCurUChar
== _T('T')) || (cCurUChar
== _T('O')))
318 /* If positive, prepare for parameters... if negative, reset to defaults */
322 lpFlags
->stAttribs
.dwAttribVal
= 0L;
323 lpFlags
->stAttribs
.dwAttribMask
= 0L;
325 lpFlags
->stAttribs
.dwAttribMask
= FILE_ATTRIBUTE_HIDDEN
| FILE_ATTRIBUTE_SYSTEM
;
329 lpFlags
->stTimeField
.eTimeField
= TF_MODIFIEDDATE
;
332 bOrderByNoPar
= !bNegative
;
333 lpFlags
->stOrderBy
.sCriteriaCount
= 0;
339 /* Positive switch, so it can take parameters. */
340 cCurSwitch
= cCurUChar
;
342 /* Skip optional leading colon */
343 if (*Line
== _T(':'))
348 else if (cCurUChar
== _T('L'))
349 lpFlags
->bLowerCase
= ! bNegative
;
350 else if (cCurUChar
== _T('B'))
351 lpFlags
->bBareFormat
= ! bNegative
;
352 else if (cCurUChar
== _T('C'))
353 lpFlags
->bTSeparator
= ! bNegative
;
354 else if (cCurUChar
== _T('W'))
355 lpFlags
->bWideList
= ! bNegative
;
356 else if (cCurUChar
== _T('D'))
357 lpFlags
->bWideListColSort
= ! bNegative
;
358 else if (cCurUChar
== _T('N'))
359 lpFlags
->bNewLongList
= ! bNegative
;
360 else if (cCurUChar
== _T('P'))
361 lpFlags
->bPause
= ! bNegative
;
362 else if (cCurUChar
== _T('Q'))
363 lpFlags
->bUser
= ! bNegative
;
364 else if (cCurUChar
== _T('S'))
365 lpFlags
->bRecursive
= ! bNegative
;
366 else if (cCurUChar
== _T('X'))
367 lpFlags
->bShortName
= ! bNegative
;
368 else if (cCurUChar
== _T('R'))
369 lpFlags
->bDataStreams
= ! bNegative
;
370 else if (cCurChar
== _T('4'))
371 lpFlags
->b4Digit
= ! bNegative
;
372 else if (cCurChar
== _T('?'))
379 error_invalid_switch ((TCHAR
)_totupper(*Line
));
383 /* Make sure there's no extra characters at the end of the switch */
384 if (Line
[1] && Line
[1] != _T('/') && !_istspace(Line
[1]))
386 error_parameter_format(Line
[1]);
390 cCurSwitch
= _T(' ');
392 else if (cCurSwitch
== _T(' '))
394 /* 2nd section (see README_DIR.txt) */
395 /* We are expecting parameter or the unknown */
397 if (cCurChar
== _T('/'))
398 cCurSwitch
= _T('/');
399 else if (_istspace(cCurChar
))
403 /* This is a file/directory name parameter. Find its end */
408 if (!bIntoQuotes
&& (*Line
== _T('/') || _istspace(*Line
)))
410 bIntoQuotes
^= (*Line
== _T('"'));
415 /* Copy it to the entries list */
416 temp
= cmd_alloc((ptrEnd
- ptrStart
+ 1) * sizeof(TCHAR
));
419 memcpy(temp
, ptrStart
, (ptrEnd
- ptrStart
) * sizeof(TCHAR
));
420 temp
[ptrEnd
- ptrStart
] = _T('\0');
422 if (!add_entry(entries
, params
, temp
))
435 /* 3rd section (see README_DIR.txt) */
436 /* We are waiting for switch parameters */
438 /* Check if there are no more switch parameters */
439 if ((cCurChar
== _T('/')) || _istspace(cCurChar
))
441 /* Wrong decision path, reprocess current character */
442 cCurSwitch
= _T(' ');
445 /* Process parameter switch */
448 case _T('A'): /* Switch parameters for /A (attributes filter) */
449 if (cCurChar
== _T('-'))
451 else if (cCurUChar
== _T('D'))
453 lpFlags
->stAttribs
.dwAttribMask
|= FILE_ATTRIBUTE_DIRECTORY
;
455 lpFlags
->stAttribs
.dwAttribVal
&= ~FILE_ATTRIBUTE_DIRECTORY
;
457 lpFlags
->stAttribs
.dwAttribVal
|= FILE_ATTRIBUTE_DIRECTORY
;
459 else if (cCurUChar
== _T('R'))
461 lpFlags
->stAttribs
.dwAttribMask
|= FILE_ATTRIBUTE_READONLY
;
463 lpFlags
->stAttribs
.dwAttribVal
&= ~FILE_ATTRIBUTE_READONLY
;
465 lpFlags
->stAttribs
.dwAttribVal
|= FILE_ATTRIBUTE_READONLY
;
467 else if (cCurUChar
== _T('H'))
469 lpFlags
->stAttribs
.dwAttribMask
|= FILE_ATTRIBUTE_HIDDEN
;
471 lpFlags
->stAttribs
.dwAttribVal
&= ~FILE_ATTRIBUTE_HIDDEN
;
473 lpFlags
->stAttribs
.dwAttribVal
|= FILE_ATTRIBUTE_HIDDEN
;
475 else if (cCurUChar
== _T('A'))
477 lpFlags
->stAttribs
.dwAttribMask
|= FILE_ATTRIBUTE_ARCHIVE
;
479 lpFlags
->stAttribs
.dwAttribVal
&= ~FILE_ATTRIBUTE_ARCHIVE
;
481 lpFlags
->stAttribs
.dwAttribVal
|= FILE_ATTRIBUTE_ARCHIVE
;
483 else if (cCurUChar
== _T('S'))
485 lpFlags
->stAttribs
.dwAttribMask
|= FILE_ATTRIBUTE_SYSTEM
;
487 lpFlags
->stAttribs
.dwAttribVal
&= ~FILE_ATTRIBUTE_SYSTEM
;
489 lpFlags
->stAttribs
.dwAttribVal
|= FILE_ATTRIBUTE_SYSTEM
;
493 error_parameter_format((TCHAR
)_totupper (*Line
));
497 case _T('T'): /* Switch parameters for /T (time field) */
498 if (cCurUChar
== _T('C'))
499 lpFlags
->stTimeField
.eTimeField
= TF_CREATIONDATE
;
500 else if (cCurUChar
== _T('A'))
501 lpFlags
->stTimeField
.eTimeField
= TF_LASTACCESSEDDATE
;
502 else if (cCurUChar
== _T('W'))
503 lpFlags
->stTimeField
.eTimeField
= TF_MODIFIEDDATE
;
506 error_parameter_format((TCHAR
)_totupper (*Line
));
510 case _T('O'): /* Switch parameters for /O (order) */
511 /* Ok a switch parameter was given */
512 bOrderByNoPar
= FALSE
;
514 if (cCurChar
== _T('-'))
516 else if (cCurUChar
== _T('N'))
518 if (lpFlags
->stOrderBy
.sCriteriaCount
< 3) lpFlags
->stOrderBy
.sCriteriaCount
++;
519 lpFlags
->stOrderBy
.bCriteriaRev
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = bPNegative
;
520 lpFlags
->stOrderBy
.eCriteria
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = ORDER_NAME
;
522 else if (cCurUChar
== _T('S'))
524 if (lpFlags
->stOrderBy
.sCriteriaCount
< 3) lpFlags
->stOrderBy
.sCriteriaCount
++;
525 lpFlags
->stOrderBy
.bCriteriaRev
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = bPNegative
;
526 lpFlags
->stOrderBy
.eCriteria
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = ORDER_SIZE
;
528 else if (cCurUChar
== _T('G'))
530 if (lpFlags
->stOrderBy
.sCriteriaCount
< 3) lpFlags
->stOrderBy
.sCriteriaCount
++;
531 lpFlags
->stOrderBy
.bCriteriaRev
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = bPNegative
;
532 lpFlags
->stOrderBy
.eCriteria
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = ORDER_DIRECTORY
;
534 else if (cCurUChar
== _T('E'))
536 if (lpFlags
->stOrderBy
.sCriteriaCount
< 3) lpFlags
->stOrderBy
.sCriteriaCount
++;
537 lpFlags
->stOrderBy
.bCriteriaRev
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = bPNegative
;
538 lpFlags
->stOrderBy
.eCriteria
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = ORDER_EXTENSION
;
540 else if (cCurUChar
== _T('D'))
542 if (lpFlags
->stOrderBy
.sCriteriaCount
< 3) lpFlags
->stOrderBy
.sCriteriaCount
++;
543 lpFlags
->stOrderBy
.bCriteriaRev
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = bPNegative
;
544 lpFlags
->stOrderBy
.eCriteria
[lpFlags
->stOrderBy
.sCriteriaCount
- 1] = ORDER_TIME
;
549 error_parameter_format((TCHAR
)_totupper (*Line
));
555 /* We check if we calculated the negative value and release the flag */
556 if ((cCurChar
!= _T('-')) && bPNegative
)
563 /* /O with no switch parameters acts like /O:GN */
566 lpFlags
->stOrderBy
.sCriteriaCount
= 2;
567 lpFlags
->stOrderBy
.eCriteria
[0] = ORDER_DIRECTORY
;
568 lpFlags
->stOrderBy
.bCriteriaRev
[0] = FALSE
;
569 lpFlags
->stOrderBy
.eCriteria
[1] = ORDER_NAME
;
570 lpFlags
->stOrderBy
.bCriteriaRev
[1] = FALSE
;
576 /* Print either with or without paging, depending on /P switch */
578 DirPrintf(LPDIRSWITCHFLAGS lpFlags
, LPTSTR szFormat
, ...)
582 va_start(arg_ptr
, szFormat
);
584 Done
= ConPrintfVPaging(&StdOutPager
, FALSE
, szFormat
, arg_ptr
);
586 ConPrintfV(StdOut
, szFormat
, arg_ptr
);
593 * PrintDirectoryHeader
595 * print the header for the dir command
598 PrintDirectoryHeader(LPCTSTR szPath
, LPDIRSWITCHFLAGS lpFlags
)
600 TCHAR szMsg
[RC_STRING_MAX_SIZE
];
602 TCHAR szRootName
[MAX_PATH
];
606 if (lpFlags
->bBareFormat
)
611 /* Get the media ID of the drive */
612 if (!GetVolumePathName(szFullDir
, szRootName
, ARRAYSIZE(szRootName
)) ||
613 !GetVolumeInformation(szRootName
, szVolName
, ARRAYSIZE(szVolName
),
614 &dwSerialNr
, NULL
, NULL
, NULL
, 0))
619 /* Print drive info */
620 if (szVolName
[0] != _T('\0'))
622 LoadString(CMD_ModuleHandle
, STRING_DIR_HELP2
, szMsg
, ARRAYSIZE(szMsg
));
623 DirPrintf(lpFlags
, szMsg
, _totupper(szRootName
[0]), szVolName
);
627 LoadString(CMD_ModuleHandle
, STRING_DIR_HELP3
, szMsg
, ARRAYSIZE(szMsg
));
628 DirPrintf(lpFlags
, szMsg
, _totupper(szRootName
[0]));
631 /* Print the volume serial number if the return was successful */
632 LoadString(CMD_ModuleHandle
, STRING_DIR_HELP4
, szMsg
, ARRAYSIZE(szMsg
));
633 DirPrintf(lpFlags
, szMsg
, HIWORD(dwSerialNr
), LOWORD(dwSerialNr
));
640 DirPrintFileDateTime(TCHAR
*lpDate
,
642 LPWIN32_FIND_DATA lpFile
,
643 LPDIRSWITCHFLAGS lpFlags
)
648 /* Select the right time field */
649 switch (lpFlags
->stTimeField
.eTimeField
)
651 case TF_CREATIONDATE
:
652 if (!FileTimeToLocalFileTime(&lpFile
->ftCreationTime
, &ft
))
654 FileTimeToSystemTime(&ft
, &dt
);
657 case TF_LASTACCESSEDDATE
:
658 if (!FileTimeToLocalFileTime(&lpFile
->ftLastAccessTime
, &ft
))
660 FileTimeToSystemTime(&ft
, &dt
);
663 case TF_MODIFIEDDATE
:
664 if (!FileTimeToLocalFileTime(&lpFile
->ftLastWriteTime
, &ft
))
666 FileTimeToSystemTime(&ft
, &dt
);
670 FormatDate(lpDate
, &dt
, lpFlags
->b4Digit
);
671 FormatTime(lpTime
, &dt
);
675 FormatDate(TCHAR
*lpDate
, LPSYSTEMTIME dt
, BOOL b4Digit
)
678 WORD wYear
= b4Digit
? dt
->wYear
: dt
->wYear
%100;
683 return _stprintf(lpDate
, _T("%02d%c%02d%c%0*d"),
684 dt
->wMonth
, cDateSeparator
,
685 dt
->wDay
, cDateSeparator
,
690 return _stprintf(lpDate
, _T("%02d%c%02d%c%0*d"),
691 dt
->wDay
, cDateSeparator
, dt
->wMonth
,
692 cDateSeparator
, b4Digit
?4:2, wYear
);
696 return _stprintf(lpDate
, _T("%0*d%c%02d%c%02d"),
697 b4Digit
?4:2, wYear
, cDateSeparator
,
698 dt
->wMonth
, cDateSeparator
, dt
->wDay
);
704 FormatTime(TCHAR
*lpTime
, LPSYSTEMTIME dt
)
709 case 0: /* 12 hour format */
711 return _stprintf(lpTime
,_T("%02d%c%02u %cM"),
712 (dt
->wHour
== 0 ? 12 : (dt
->wHour
<= 12 ? dt
->wHour
: dt
->wHour
- 12)),
714 dt
->wMinute
, (dt
->wHour
<= 11 ? _T('A') : _T('P')));
717 case 1: /* 24 hour format */
718 return _stprintf(lpTime
, _T("%02d%c%02u"),
719 dt
->wHour
, cTimeSeparator
, dt
->wMinute
);
726 GetUserDiskFreeSpace(LPCTSTR lpRoot
,
727 PULARGE_INTEGER lpFreeSpace
)
729 PGETFREEDISKSPACEEX pGetFreeDiskSpaceEx
;
735 ULARGE_INTEGER TotalNumberOfBytes
, TotalNumberOfFreeBytes
;
737 lpFreeSpace
->QuadPart
= 0;
739 hInstance
= GetModuleHandle(_T("KERNEL32"));
740 if (hInstance
!= NULL
)
742 pGetFreeDiskSpaceEx
= (PGETFREEDISKSPACEEX
)GetProcAddress(hInstance
,
744 "GetDiskFreeSpaceExW");
746 "GetDiskFreeSpaceExA");
748 if (pGetFreeDiskSpaceEx
!= NULL
)
750 if (pGetFreeDiskSpaceEx(lpRoot
, lpFreeSpace
, &TotalNumberOfBytes
, &TotalNumberOfFreeBytes
) != FALSE
)
755 GetDiskFreeSpace(lpRoot
,
761 lpFreeSpace
->QuadPart
= dwSecPerCl
* dwBytPerSec
* dwFreeCl
;
766 * print_summary: prints dir summary
767 * Added by Rob Lake 06/17/98 to compact code
768 * Just copied Tim's Code and patched it a bit
771 PrintSummary(LPCTSTR szPath
,
775 LPDIRSWITCHFLAGS lpFlags
,
778 TCHAR szMsg
[RC_STRING_MAX_SIZE
];
780 ULARGE_INTEGER uliFree
;
782 /* Here we check if we didn't find anything */
783 if (!(ulFiles
+ ulDirs
))
785 if (!lpFlags
->bRecursive
|| (TotalSummary
&& lpFlags
->bRecursive
))
786 error_file_not_found();
790 /* In bare format we don't print results */
791 if (lpFlags
->bBareFormat
)
794 /* Print recursive specific results */
796 /* Take this code offline to fix /S does not print double info */
797 if (TotalSummary
&& lpFlags
->bRecursive
)
799 ConvertULargeInteger(u64Bytes
, szBuffer
, ARRAYSIZE(szBuffer
), lpFlags
->bTSeparator
);
800 LoadString(CMD_ModuleHandle
, STRING_DIR_HELP5
, szMsg
, ARRAYSIZE(szMsg
));
801 DirPrintf(lpFlags
, szMsg
, ulFiles
, szBuffer
);
805 /* Print File Summary */
806 /* Condition to print summary is:
807 If we are not in bare format and if we have results! */
808 ConvertULargeInteger(u64Bytes
, szBuffer
, ARRAYSIZE(szBuffer
), lpFlags
->bTSeparator
);
809 LoadString(CMD_ModuleHandle
, STRING_DIR_HELP8
, szMsg
, ARRAYSIZE(szMsg
));
810 DirPrintf(lpFlags
, szMsg
, ulFiles
, szBuffer
);
813 /* Print total directories and free space */
814 if (!lpFlags
->bRecursive
|| TotalSummary
)
816 GetUserDiskFreeSpace(szPath
, &uliFree
);
817 ConvertULargeInteger(uliFree
.QuadPart
, szBuffer
, ARRAYSIZE(szBuffer
), lpFlags
->bTSeparator
);
818 LoadString(CMD_ModuleHandle
, STRING_DIR_HELP6
, szMsg
, ARRAYSIZE(szMsg
));
819 DirPrintf(lpFlags
, szMsg
, ulDirs
, szBuffer
);
828 * Get the extension of a filename
830 TCHAR
* getExt(const TCHAR
* file
)
832 static TCHAR
*NoExt
= _T("");
833 TCHAR
* lastdot
= _tcsrchr(file
, _T('.'));
834 return (lastdot
!= NULL
? lastdot
+ 1 : NoExt
);
840 * Get the name of the file without extension
843 getName(const TCHAR
* file
, TCHAR
* dest
)
848 /* Check for dot-directories "." and ".." */
849 if (IsDotDirectory(file
))
855 end
= _tcsrchr(file
, _T('.'));
857 iLen
= _tcslen(file
);
861 _tcsncpy(dest
, file
, iLen
);
862 *(dest
+ iLen
) = _T('\0');
871 * The function that prints in new style
874 DirPrintNewList(PDIRFINDINFO ptrFiles
[], /* [IN]Files' Info */
875 DWORD dwCount
, /* [IN] The quantity of files */
876 LPCTSTR szCurPath
, /* [IN] Full path of current directory */
877 LPDIRSWITCHFLAGS lpFlags
) /* [IN] The flags used */
881 TCHAR szShortName
[15];
885 ULARGE_INTEGER u64FileSize
;
886 PDIRFINDSTREAMNODE ptrCurStream
;
888 for (i
= 0; i
< dwCount
&& !CheckCtrlBreak(BREAK_INPUT
); i
++)
891 if (ptrFiles
[i
]->stFindInfo
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
)
895 _tcscpy(szSize
, _T("<JUNCTION>"));
897 else if (ptrFiles
[i
]->stFindInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
901 _tcscpy(szSize
, _T("<DIR>"));
907 u64FileSize
.HighPart
= ptrFiles
[i
]->stFindInfo
.nFileSizeHigh
;
908 u64FileSize
.LowPart
= ptrFiles
[i
]->stFindInfo
.nFileSizeLow
;
909 ConvertULargeInteger(u64FileSize
.QuadPart
, szSize
, 20, lpFlags
->bTSeparator
);
912 /* Calculate short name */
913 szShortName
[0] = _T('\0');
914 if (lpFlags
->bShortName
)
915 _stprintf(szShortName
, _T(" %-12s"), ptrFiles
[i
]->stFindInfo
.cAlternateFileName
);
917 /* Format date and time */
918 DirPrintFileDateTime(szDate
, szTime
, &ptrFiles
[i
]->stFindInfo
, lpFlags
);
921 DirPrintf(lpFlags
, _T("%10s %-6s %*s%s %s\n"),
927 ptrFiles
[i
]->stFindInfo
.cFileName
);
929 /* Now, loop on the streams */
930 ptrCurStream
= ptrFiles
[i
]->ptrHead
;
933 ConvertULargeInteger(ptrCurStream
->stStreamInfo
.StreamSize
.QuadPart
, szSize
, 20, lpFlags
->bTSeparator
);
936 DirPrintf(lpFlags
, _T("%10s %-6s %*s%s %s%s\n"),
942 ptrFiles
[i
]->stFindInfo
.cFileName
,
943 ptrCurStream
->stStreamInfo
.cStreamName
);
944 ptrCurStream
= ptrCurStream
->ptrNext
;
953 * The function that prints in wide list
956 DirPrintWideList(PDIRFINDINFO ptrFiles
[], /* [IN] Files' Info */
957 DWORD dwCount
, /* [IN] The quantity of files */
958 LPCTSTR szCurPath
, /* [IN] Full path of current directory */
959 LPDIRSWITCHFLAGS lpFlags
) /* [IN] The flags used */
964 UINT_PTR iLongestName
;
965 TCHAR szTempFname
[MAX_PATH
];
969 BOOL bCJK
= IsCJKCodePage(OutputCodePage
);
972 /* Calculate longest name */
974 for (i
= 0; i
< dwCount
; i
++)
976 cxWidth
= ConGetTextWidth(ptrFiles
[i
]->stFindInfo
.cFileName
);
978 /* Directories need 2 additional characters for brackets */
979 if (ptrFiles
[i
]->stFindInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
982 iLongestName
= max(iLongestName
, cxWidth
);
985 /* Count the highest number of columns */
986 GetScreenSize(&iScreenWidth
, NULL
);
987 iColumns
= (USHORT
)(iScreenWidth
/ iLongestName
);
989 /* Check if there is enough space for spaces between names */
990 if (((iLongestName
* iColumns
) + iColumns
) >= (UINT
)iScreenWidth
)
993 /* A last check at iColumns to avoid division by zero */
994 if (!iColumns
) iColumns
= 1;
996 /* Calculate the lines that will be printed */
997 iLines
= (USHORT
)((dwCount
+ iColumns
- 1) / iColumns
);
999 for (i
= 0; i
< iLines
&& !CheckCtrlBreak(BREAK_INPUT
); i
++)
1001 for (j
= 0; j
< iColumns
; j
++)
1003 if (lpFlags
->bWideListColSort
)
1005 /* Print Column sorted */
1006 temp
= (j
* iLines
) + i
;
1010 /* Print Line sorted */
1011 temp
= (i
* iColumns
) + j
;
1014 if (temp
>= dwCount
) break;
1016 if (ptrFiles
[temp
]->stFindInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
1017 _stprintf(szTempFname
, _T("[%s]"), ptrFiles
[temp
]->stFindInfo
.cFileName
);
1019 _stprintf(szTempFname
, _T("%s"), ptrFiles
[temp
]->stFindInfo
.cFileName
);
1023 cxWidth
= ConGetTextWidth(szTempFname
);
1024 /* Print string and add space padding */
1025 DirPrintf(lpFlags
, _T("%s%*s"), szTempFname
, iLongestName
+ 1 - cxWidth
, _T(""));
1029 DirPrintf(lpFlags
, _T("%-*s"), iLongestName
+ 1, szTempFname
);
1033 /* Add a new line after the last item in the column */
1034 DirPrintf(lpFlags
, _T("\n"));
1042 * The function that prints in old style
1045 DirPrintOldList(PDIRFINDINFO ptrFiles
[], /* [IN] Files' Info */
1046 DWORD dwCount
, /* [IN] The quantity of files */
1047 LPCTSTR szCurPath
, /* [IN] Full path of current directory */
1048 LPDIRSWITCHFLAGS lpFlags
) /* [IN] The flags used */
1050 DWORD i
; /* An indexer for "for"s */
1051 TCHAR szName
[10]; /* The name of file */
1052 TCHAR szExt
[5]; /* The extension of file */
1053 TCHAR szDate
[30],szTime
[30]; /* Used to format time and date */
1054 TCHAR szSize
[30]; /* The size of file */
1055 int iSizeFormat
; /* The format of size field */
1056 ULARGE_INTEGER u64FileSize
; /* The file size */
1058 for (i
= 0; i
< dwCount
&& !CheckCtrlBreak(BREAK_INPUT
); i
++)
1060 /* Broke 8.3 format */
1061 if (*ptrFiles
[i
]->stFindInfo
.cAlternateFileName
)
1063 /* If the file is long named then we read the alter name */
1064 getName( ptrFiles
[i
]->stFindInfo
.cAlternateFileName
, szName
);
1065 _tcscpy(szExt
, getExt( ptrFiles
[i
]->stFindInfo
.cAlternateFileName
));
1069 /* If the file is not long name we read its original name */
1070 getName( ptrFiles
[i
]->stFindInfo
.cFileName
, szName
);
1071 _tcscpy(szExt
, getExt( ptrFiles
[i
]->stFindInfo
.cFileName
));
1074 /* Calculate size */
1075 if (ptrFiles
[i
]->stFindInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
1077 /* Directory, no size it's a directory */
1079 _tcscpy(szSize
, _T("<DIR>"));
1085 u64FileSize
.HighPart
= ptrFiles
[i
]->stFindInfo
.nFileSizeHigh
;
1086 u64FileSize
.LowPart
= ptrFiles
[i
]->stFindInfo
.nFileSizeLow
;
1087 ConvertULargeInteger(u64FileSize
.QuadPart
, szSize
, 20, lpFlags
->bTSeparator
);
1090 /* Format date and time */
1091 DirPrintFileDateTime(szDate
,szTime
,&ptrFiles
[i
]->stFindInfo
,lpFlags
);
1093 /* Print the line */
1094 DirPrintf(lpFlags
, _T("%-8s %-3s %*s %s %s\n"),
1095 szName
, /* The file's 8.3 name */
1096 szExt
, /* The file's 8.3 extension */
1097 iSizeFormat
, /* print format for size column */
1098 szSize
, /* The size of file or "<DIR>" for dirs */
1099 szDate
, /* The date of file/dir */
1100 szTime
); /* The time of file/dir */
1107 * The function that prints in bare format
1110 DirPrintBareList(PDIRFINDINFO ptrFiles
[], /* [IN] Files' Info */
1111 DWORD dwCount
, /* [IN] The number of files */
1112 LPCTSTR szCurPath
, /* [IN] Full path of current directory */
1113 LPDIRSWITCHFLAGS lpFlags
) /* [IN] The flags used */
1117 for (i
= 0; i
< dwCount
&& !CheckCtrlBreak(BREAK_INPUT
); i
++)
1119 if (IsDotDirectory(ptrFiles
[i
]->stFindInfo
.cFileName
))
1121 /* At bare format we don't print the dot-directories "." and ".." */
1124 if (lpFlags
->bRecursive
)
1126 /* At recursive mode we print full path of file */
1127 DirPrintf(lpFlags
, _T("%s\\%s\n"), szCurPath
, ptrFiles
[i
]->stFindInfo
.cFileName
);
1131 /* If we are not in recursive mode we print the file names */
1132 DirPrintf(lpFlags
, _T("%s\n"), ptrFiles
[i
]->stFindInfo
.cFileName
);
1141 * The functions that prints the files list
1144 DirPrintFiles(PDIRFINDINFO ptrFiles
[], /* [IN] Files' Info */
1145 DWORD dwCount
, /* [IN] The quantity of files */
1146 LPCTSTR szCurPath
, /* [IN] Full path of current directory */
1147 LPDIRSWITCHFLAGS lpFlags
) /* [IN] The flags used */
1149 TCHAR szMsg
[RC_STRING_MAX_SIZE
];
1150 TCHAR szTemp
[MAX_PATH
]; /* A buffer to format the directory header */
1152 /* Print trailing backslash for root directory of drive */
1153 _tcscpy(szTemp
, szCurPath
);
1154 if (_tcslen(szTemp
) == 2 && szTemp
[1] == _T(':'))
1155 _tcscat(szTemp
, _T("\\"));
1157 /* Condition to print header:
1158 We are not printing in bare format
1159 and if we are in recursive mode... we must have results */
1160 if (!lpFlags
->bBareFormat
&& !(lpFlags
->bRecursive
&& (dwCount
<= 0)))
1162 LoadString(CMD_ModuleHandle
, STRING_DIR_HELP7
, szMsg
, ARRAYSIZE(szMsg
));
1163 if (!DirPrintf(lpFlags
, szMsg
, szTemp
))
1167 if (lpFlags
->bBareFormat
)
1170 DirPrintBareList(ptrFiles
, dwCount
, szCurPath
, lpFlags
);
1172 else if (lpFlags
->bShortName
)
1174 /* New list style / Short names */
1175 DirPrintNewList(ptrFiles
, dwCount
, szCurPath
, lpFlags
);
1177 else if (lpFlags
->bWideListColSort
|| lpFlags
->bWideList
)
1180 DirPrintWideList(ptrFiles
, dwCount
, szCurPath
, lpFlags
);
1182 else if (lpFlags
->bNewLongList
)
1185 DirPrintNewList(ptrFiles
, dwCount
, szCurPath
, lpFlags
);
1189 /* If nothing is selected old list is the default */
1190 DirPrintOldList(ptrFiles
, dwCount
, szCurPath
, lpFlags
);
1197 * Compares 2 files based on the order criteria
1200 CompareFiles(PDIRFINDINFO lpFile1
, /* [IN] A pointer to WIN32_FIND_DATA of file 1 */
1201 PDIRFINDINFO lpFile2
, /* [IN] A pointer to WIN32_FIND_DATA of file 2 */
1202 LPDIRSWITCHFLAGS lpFlags
) /* [IN] The flags that we use to list */
1204 ULARGE_INTEGER u64File1
;
1205 ULARGE_INTEGER u64File2
;
1207 long iComp
= 0; /* The comparison result */
1209 /* Calculate criteria by order given from user */
1210 for (i
= 0; i
< lpFlags
->stOrderBy
.sCriteriaCount
; i
++)
1213 /* Calculate criteria */
1214 switch (lpFlags
->stOrderBy
.eCriteria
[i
])
1216 case ORDER_SIZE
: /* Order by size /o:s */
1217 /* concat the 32bit integers to a 64bit */
1218 u64File1
.LowPart
= lpFile1
->stFindInfo
.nFileSizeLow
;
1219 u64File1
.HighPart
= lpFile1
->stFindInfo
.nFileSizeHigh
;
1220 u64File2
.LowPart
= lpFile2
->stFindInfo
.nFileSizeLow
;
1221 u64File2
.HighPart
= lpFile2
->stFindInfo
.nFileSizeHigh
;
1223 /* In case that difference is too big for a long */
1224 if (u64File1
.QuadPart
< u64File2
.QuadPart
)
1226 else if (u64File1
.QuadPart
> u64File2
.QuadPart
)
1232 case ORDER_DIRECTORY
: /* Order by directory attribute /o:g */
1233 iComp
= ((lpFile2
->stFindInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)-
1234 (lpFile1
->stFindInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
));
1237 case ORDER_EXTENSION
: /* Order by extension name /o:e */
1238 iComp
= _tcsicmp(getExt(lpFile1
->stFindInfo
.cFileName
),getExt(lpFile2
->stFindInfo
.cFileName
));
1241 case ORDER_NAME
: /* Order by filename /o:n */
1242 iComp
= _tcsicmp(lpFile1
->stFindInfo
.cFileName
, lpFile2
->stFindInfo
.cFileName
);
1245 case ORDER_TIME
: /* Order by file's time /o:t */
1246 /* We compare files based on the time field selected by /t */
1247 switch (lpFlags
->stTimeField
.eTimeField
)
1249 case TF_CREATIONDATE
:
1250 /* concat the 32bit integers to a 64bit */
1251 u64File1
.LowPart
= lpFile1
->stFindInfo
.ftCreationTime
.dwLowDateTime
;
1252 u64File1
.HighPart
= lpFile1
->stFindInfo
.ftCreationTime
.dwHighDateTime
;
1253 u64File2
.LowPart
= lpFile2
->stFindInfo
.ftCreationTime
.dwLowDateTime
;
1254 u64File2
.HighPart
= lpFile2
->stFindInfo
.ftCreationTime
.dwHighDateTime
;
1256 case TF_LASTACCESSEDDATE
:
1257 /* concat the 32bit integers to a 64bit */
1258 u64File1
.LowPart
= lpFile1
->stFindInfo
.ftLastAccessTime
.dwLowDateTime
;
1259 u64File1
.HighPart
= lpFile1
->stFindInfo
.ftLastAccessTime
.dwHighDateTime
;
1260 u64File2
.LowPart
= lpFile2
->stFindInfo
.ftLastAccessTime
.dwLowDateTime
;
1261 u64File2
.HighPart
= lpFile2
->stFindInfo
.ftLastAccessTime
.dwHighDateTime
;
1263 case TF_MODIFIEDDATE
:
1264 /* concat the 32bit integers to a 64bit */
1265 u64File1
.LowPart
= lpFile1
->stFindInfo
.ftLastWriteTime
.dwLowDateTime
;
1266 u64File1
.HighPart
= lpFile1
->stFindInfo
.ftLastWriteTime
.dwHighDateTime
;
1267 u64File2
.LowPart
= lpFile2
->stFindInfo
.ftLastWriteTime
.dwLowDateTime
;
1268 u64File2
.HighPart
= lpFile2
->stFindInfo
.ftLastWriteTime
.dwHighDateTime
;
1272 /* In case that difference is too big for a long */
1273 if (u64File1
.QuadPart
< u64File2
.QuadPart
)
1275 else if (u64File1
.QuadPart
> u64File2
.QuadPart
)
1282 /* Reverse if desired */
1283 if (lpFlags
->stOrderBy
.bCriteriaRev
[i
])
1286 /* If that criteria was enough for distinguishing
1287 the files/dirs,there is no need to calculate the others*/
1288 if (iComp
!= 0) break;
1291 /* Translate the value of iComp to boolean */
1298 * Sort files by the order criterias using quicksort method
1301 QsortFiles(PDIRFINDINFO ptrArray
[], /* [IN/OUT] The array with file info pointers */
1302 int i
, /* [IN] The index of first item in array */
1303 int j
, /* [IN] The index to last item in array */
1304 LPDIRSWITCHFLAGS lpFlags
) /* [IN] The flags that we will use to sort */
1306 PDIRFINDINFO lpTemp
; /* A temporary pointer */
1311 int First
= i
, Last
= j
, Temp
;
1315 if (Way
== CompareFiles(ptrArray
[i
], ptrArray
[j
], lpFlags
))
1317 /* Swap the pointers of the array */
1318 lpTemp
= ptrArray
[i
];
1319 ptrArray
[i
]= ptrArray
[j
];
1320 ptrArray
[j
] = lpTemp
;
1322 /* Swap the indexes for inverting sorting */
1333 QsortFiles(ptrArray
,First
, i
-1, lpFlags
);
1334 QsortFiles(ptrArray
,i
+1,Last
, lpFlags
);
1339 DirNodeCleanup(PDIRFINDLISTNODE ptrStartNode
,
1342 PDIRFINDLISTNODE ptrNextNode
;
1343 PDIRFINDSTREAMNODE ptrFreeNode
;
1344 while (ptrStartNode
)
1346 ptrNextNode
= ptrStartNode
->ptrNext
;
1347 while (ptrStartNode
->stInfo
.ptrHead
)
1349 ptrFreeNode
= ptrStartNode
->stInfo
.ptrHead
;
1350 ptrStartNode
->stInfo
.ptrHead
= ptrFreeNode
->ptrNext
;
1351 cmd_free(ptrFreeNode
);
1353 cmd_free(ptrStartNode
);
1354 ptrStartNode
= ptrNextNode
;
1362 * The function that does everything except for printing results
1365 DirList(IN OUT LPTSTR szFullPath
, /* [IN] The full path we are listing with trailing '\', where dir starts */
1366 IN LPTSTR pszFilePart
, /* [IN] Pointer in the szFullPath buffer where the file (pattern) part starts*/
1367 LPDIRSWITCHFLAGS lpFlags
) /* [IN] The flags of the listing */
1369 HANDLE hSearch
; /* The handle of the search */
1370 HANDLE hRecSearch
; /* The handle for searching recursively */
1371 HANDLE hStreams
; /* The handle for alternate streams */
1372 WIN32_FIND_DATA wfdFileInfo
; /* The info of file that found */
1373 PDIRFINDINFO
* ptrFileArray
; /* An array of pointers with all the files */
1374 PDIRFINDLISTNODE ptrStartNode
; /* The pointer to the first node */
1375 PDIRFINDLISTNODE ptrNextNode
; /* A pointer used for relatives references */
1376 TCHAR szSubPath
[MAX_PATH
]; /* The full path used for the recursive search */
1377 LPTSTR pszSubFilePart
;
1379 DWORD dwCount
; /* A counter of files found in directory */
1380 DWORD dwCountFiles
; /* Counter for files */
1381 DWORD dwCountDirs
; /* Counter for directories */
1382 ULONGLONG u64CountBytes
; /* Counter for bytes */
1383 ULARGE_INTEGER u64Temp
; /* A temporary counter */
1384 WIN32_FIND_STREAM_DATA wfsdStreamInfo
;
1385 PDIRFINDSTREAMNODE
* ptrCurNode
; /* The pointer to the first stream */
1386 static HANDLE (WINAPI
*pFindFirstStreamW
)(LPCWSTR
, STREAM_INFO_LEVELS
, LPVOID
, DWORD
);
1387 static BOOL (WINAPI
*pFindNextStreamW
)(HANDLE
, LPVOID
);
1389 /* Initialize variables */
1390 ptrStartNode
= NULL
;
1397 /* Prepare the linked list, first node is allocated */
1398 ptrStartNode
= cmd_alloc(sizeof(DIRFINDLISTNODE
));
1399 if (ptrStartNode
== NULL
)
1401 WARN("Cannot allocate memory for ptrStartNode!\n");
1402 return 1; /* Error cannot allocate memory for 1st object */
1404 ptrStartNode
->stInfo
.ptrHead
= NULL
;
1405 ptrNextNode
= ptrStartNode
;
1407 /* Collect the results for the current directory */
1408 hSearch
= FindFirstFile(szFullPath
, &wfdFileInfo
);
1409 if (hSearch
!= INVALID_HANDLE_VALUE
)
1413 if ((wfdFileInfo
.dwFileAttributes
& lpFlags
->stAttribs
.dwAttribMask
) ==
1414 (lpFlags
->stAttribs
.dwAttribMask
& lpFlags
->stAttribs
.dwAttribVal
))
1416 ptrNextNode
->ptrNext
= cmd_alloc(sizeof(DIRFINDLISTNODE
));
1417 if (ptrNextNode
->ptrNext
== NULL
)
1419 WARN("Cannot allocate memory for ptrNextNode->ptrNext!\n");
1420 DirNodeCleanup(ptrStartNode
, &dwCount
);
1425 /* Copy the info of search at linked list */
1426 memcpy(&ptrNextNode
->ptrNext
->stInfo
.stFindInfo
,
1428 sizeof(WIN32_FIND_DATA
));
1430 /* If lower case is selected do it here */
1431 if (lpFlags
->bLowerCase
)
1433 _tcslwr(ptrNextNode
->ptrNext
->stInfo
.stFindInfo
.cAlternateFileName
);
1434 _tcslwr(ptrNextNode
->ptrNext
->stInfo
.stFindInfo
.cFileName
);
1437 /* No streams (yet?) */
1438 ptrNextNode
->ptrNext
->stInfo
.ptrHead
= NULL
;
1440 /* Alternate streams are only displayed with new long list */
1441 if (lpFlags
->bNewLongList
&& lpFlags
->bDataStreams
)
1443 if (!pFindFirstStreamW
)
1445 pFindFirstStreamW
= (PVOID
)GetProcAddress(GetModuleHandle(_T("kernel32")), "FindFirstStreamW");
1446 pFindNextStreamW
= (PVOID
)GetProcAddress(GetModuleHandle(_T("kernel32")), "FindNextStreamW");
1449 /* Try to get stream information */
1450 if (pFindFirstStreamW
&& pFindNextStreamW
)
1452 hStreams
= pFindFirstStreamW(wfdFileInfo
.cFileName
, FindStreamInfoStandard
, &wfsdStreamInfo
, 0);
1456 hStreams
= INVALID_HANDLE_VALUE
;
1457 ERR("FindFirstStreamW not supported!\n");
1460 if (hStreams
!= INVALID_HANDLE_VALUE
)
1462 /* We totally ignore first stream. It contains data about ::$DATA */
1463 ptrCurNode
= &ptrNextNode
->ptrNext
->stInfo
.ptrHead
;
1464 while (pFindNextStreamW(hStreams
, &wfsdStreamInfo
))
1466 *ptrCurNode
= cmd_alloc(sizeof(DIRFINDSTREAMNODE
));
1467 if (*ptrCurNode
== NULL
)
1469 WARN("Cannot allocate memory for *ptrCurNode!\n");
1470 DirNodeCleanup(ptrStartNode
, &dwCount
);
1471 FindClose(hStreams
);
1476 memcpy(&(*ptrCurNode
)->stStreamInfo
, &wfsdStreamInfo
,
1477 sizeof(WIN32_FIND_STREAM_DATA
));
1479 /* If lower case is selected do it here */
1480 if (lpFlags
->bLowerCase
)
1482 _tcslwr((*ptrCurNode
)->stStreamInfo
.cStreamName
);
1485 ptrCurNode
= &(*ptrCurNode
)->ptrNext
;
1488 FindClose(hStreams
);
1493 /* Continue at next node at linked list */
1494 ptrNextNode
= ptrNextNode
->ptrNext
;
1497 /* Grab statistics */
1498 if (wfdFileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
1507 u64Temp
.HighPart
= wfdFileInfo
.nFileSizeHigh
;
1508 u64Temp
.LowPart
= wfdFileInfo
.nFileSizeLow
;
1509 u64CountBytes
+= u64Temp
.QuadPart
;
1512 } while (FindNextFile(hSearch
, &wfdFileInfo
));
1516 /* Terminate list */
1517 ptrNextNode
->ptrNext
= NULL
;
1519 /* Calculate and allocate space need for making an array of pointers */
1520 ptrFileArray
= cmd_alloc(sizeof(PDIRFINDINFO
) * dwCount
);
1521 if (ptrFileArray
== NULL
)
1523 WARN("Cannot allocate memory for ptrFileArray!\n");
1524 DirNodeCleanup(ptrStartNode
, &dwCount
);
1529 * Create an array of pointers from the linked list
1530 * this will be used to sort and print data, rather than the list
1532 ptrNextNode
= ptrStartNode
;
1534 while (ptrNextNode
->ptrNext
)
1536 ptrFileArray
[dwCount
] = &ptrNextNode
->ptrNext
->stInfo
;
1537 ptrNextNode
= ptrNextNode
->ptrNext
;
1541 /* Sort Data if requested */
1542 if (lpFlags
->stOrderBy
.sCriteriaCount
> 0)
1543 QsortFiles(ptrFileArray
, 0, dwCount
-1, lpFlags
);
1546 cPathSep
= pszFilePart
[-1];
1547 pszFilePart
[-1] = _T('\0'); /* Truncate to directory name only */
1548 DirPrintFiles(ptrFileArray
, dwCount
, szFullPath
, lpFlags
);
1550 if (lpFlags
->bRecursive
)
1552 PrintSummary(szFullPath
,
1559 pszFilePart
[-1] = cPathSep
;
1562 cmd_free(ptrFileArray
);
1564 /* Free linked list */
1565 DirNodeCleanup(ptrStartNode
, &dwCount
);
1567 if (CheckCtrlBreak(BREAK_INPUT
))
1570 /* Add statistics to recursive statistics */
1571 recurse_dir_cnt
+= dwCountDirs
;
1572 recurse_file_cnt
+= dwCountFiles
;
1573 recurse_bytes
+= u64CountBytes
;
1576 * Do the recursive job if requested.
1577 * The recursion is done on ALL (independent of their attributes)
1578 * directories of the current one.
1580 if (lpFlags
->bRecursive
)
1582 /* The new search is involving any *.* file */
1583 memcpy(szSubPath
, szFullPath
, (pszFilePart
- szFullPath
) * sizeof(TCHAR
));
1584 _tcscpy(&szSubPath
[pszFilePart
- szFullPath
], _T("*.*"));
1586 hRecSearch
= FindFirstFile(szSubPath
, &wfdFileInfo
);
1587 if (hRecSearch
!= INVALID_HANDLE_VALUE
)
1591 /* We search for directories other than "." and ".." */
1592 if (!IsDotDirectory(wfdFileInfo
.cFileName
) &&
1593 (wfdFileInfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
1595 /* Concat the path and the directory to do recursive */
1596 memcpy(szSubPath
, szFullPath
, (pszFilePart
- szFullPath
) * sizeof(TCHAR
));
1597 _tcscpy(&szSubPath
[pszFilePart
- szFullPath
], wfdFileInfo
.cFileName
);
1598 _tcscat(szSubPath
, _T("\\"));
1599 pszSubFilePart
= &szSubPath
[_tcslen(szSubPath
)];
1600 _tcscat(pszSubFilePart
, pszFilePart
);
1602 /* We do the same for the directory */
1603 if (DirList(szSubPath
, pszSubFilePart
, lpFlags
) != 0)
1605 FindClose(hRecSearch
);
1609 } while (FindNextFile(hRecSearch
, &wfdFileInfo
));
1611 FindClose(hRecSearch
);
1619 IN LPTSTR pszPattern
,
1620 IN DWORD nBufferLength
,
1621 OUT LPTSTR pszFullPath
,
1622 OUT LPTSTR
* ppszPatternPart OPTIONAL
)
1624 LPTSTR pCurDir
, pNextDir
, ptr
;
1625 LPTSTR pszPatternPart
;
1626 TCHAR szNewPattern
[MAX_PATH
];
1629 * We are going to use GetFullPathName() to properly determine the actual
1630 * full path from the pattern. However, due to the fact GetFullPathName()
1631 * strips parts of the file name component in case the pattern contains
1632 * path specification with trailing dots, it is required to perform a
1633 * pre-treatment on the pattern and a post-treatment on the obtained path.
1634 * This is mandatory in order to use the correct file search criterion.
1636 * One particular case is when the pattern specifies a dots-only directory
1637 * followed by either the "." or ".." special directories. In this case the
1638 * GetFullPathName() function may completely miss the dots-only directory.
1639 * An example is given by the pattern (C-string notation) "\\...\\." .
1640 * To cope with this problem we need to partially canonicalize the pattern
1641 * by collapsing any "." or ".." special directory that immediately follows
1642 * a dots-only directory. We collapse in addition consecutive backslashes.
1644 * Finally, trailing dots are skipped by GetFullPathName(). Therefore
1645 * a pattern that matches files with no extension, for example: "*." ,
1646 * or: "dir\\noextfile." , are reduced to simply "*" or "dir\\noextfile",
1647 * that match files with extensions. Or, a pattern specifying a trailing
1648 * dots-only directory: "dir\\..." gets completely ignored and only the
1649 * full path to "dir" is returned.
1650 * To fix this second problem we need to restore the last part of the path
1651 * pattern using the pattern that has been first partially canonicalized.
1653 * Note however that the "." or ".." special directories are always
1654 * interpreted correctly by GetFullPathName().
1657 /* Make a copy of the path pattern */
1658 ASSERT(_tcslen(pszPattern
) < ARRAYSIZE(szNewPattern
));
1659 _tcscpy(szNewPattern
, pszPattern
);
1660 pszPattern
= szNewPattern
;
1662 TRACE("Original pszPattern: %S\n", pszPattern
);
1664 /* Convert slashes into backslashes */
1665 pNextDir
= pszPattern
;
1666 while ((pNextDir
= _tcschr(pNextDir
, _T('/'))))
1667 *pNextDir
++ = _T('\\');
1670 * Find any dots-only directory and collapse any "." or ".." special
1671 * directory that immediately follows it.
1672 * Note that we just start looking after the first path separator. Indeed,
1673 * dots-only directories that are not preceded by a path separator, and so
1674 * appear first in the pattern, for example: "...\dir", or: "..." , are
1675 * either correctly handled by GetFullPathName() because they are followed
1676 * by a non-pathological directory, or because they are handled when we
1677 * restore the trailing dots pattern piece in the next step.
1679 pNextDir
= pszPattern
;
1684 /* Find the next path separator in the pattern */
1685 pNextDir
= _tcschr(pNextDir
, _T('\\'));
1689 /* Ignore the special "." and ".." directories that are correctly handled */
1690 if ((pNextDir
- pCurDir
== 0) || IsDotDirectoryN(pCurDir
, pNextDir
- pCurDir
))
1692 /* Found such a directory, ignore */
1697 /* Check whether this is a dots-only directory */
1698 for (ptr
= pCurDir
; ptr
< pNextDir
; ++ptr
)
1700 if (*ptr
!= _T('.'))
1705 /* Not a dots-only directory, ignore */
1710 /* Skip any consecutive backslashes */
1711 for (ptr
= pNextDir
; *ptr
== _T('\\'); ++ptr
) ;
1713 /* pCurDir is a dots-only directory, perform partial canonicalization */
1715 /* Remove any following "." directory */
1716 if (ptr
[0] == _T('.') && (ptr
[1] == _T('\\') || ptr
[1] == 0))
1718 memmove(pNextDir
, ptr
+ 1, (_tcslen(ptr
+ 1) + 1) * sizeof(TCHAR
));
1720 /* Remove any following ".." directory */
1721 else if (ptr
[0] == _T('.') && ptr
[1] == _T('.') && (ptr
[2] == _T('\\') || ptr
[2] == 0))
1723 /* Skip any consecutive backslashes before the next directory */
1724 for (ptr
= ptr
+ 2; *ptr
== _T('\\'); ++ptr
) ;
1726 memmove(pCurDir
, ptr
, (_tcslen(ptr
) + 1) * sizeof(TCHAR
));
1733 /* Collapse consecutive backslashes */
1735 memmove(pNextDir
, ptr
, (_tcslen(ptr
) + 1) * sizeof(TCHAR
));
1739 /* An empty pattern means we enumerate all files in the current directory */
1741 _tcscpy(pszPattern
, _T("*"));
1743 TRACE("New pszPattern: %S\n", pszPattern
);
1745 /* Create the full path */
1746 if (GetFullPathName(pszPattern
, nBufferLength
, pszFullPath
, &pszPatternPart
) == 0)
1748 _tcscpy(pszFullPath
, pszPattern
);
1749 pszPatternPart
= NULL
;
1752 TRACE("pszFullPath (1): %S\n", pszFullPath
);
1753 TRACE("pszPatternPart (1): %S\n", pszPatternPart
);
1756 * Restore the correct file name component in case the pattern contained
1757 * trailing dots that have been skipped by GetFullPathName().
1760 /* Find the last path separator in the original szPath */
1761 pNextDir
= _tcsrchr(pszPattern
, _T('\\'));
1764 /* Skip past the separator and look at the path */
1769 /* The pattern is the path we need to look at */
1770 pNextDir
= pszPattern
;
1774 * When pszPatternPart == NULL this means that pszFullPath should be a
1775 * directory; however it might have happened that the original pattern
1776 * was specifying a dots-only directory, that has been stripped off by
1777 * GetFullPathName(). In both these cases we need to restore these as
1778 * they are part of the actual directory path; the exception being if
1779 * these are the special "." or ".." directories.
1781 if (_istalpha(pNextDir
[0]) && pNextDir
[1] == _T(':') && pNextDir
[2] != _T('\\'))
1784 * The syntax "<drive_letter>:" without any trailing backslash actually
1785 * means: "current directory on this drive".
1788 else if (pszPatternPart
== NULL
)
1790 ASSERT(pszFullPath
[_tcslen(pszFullPath
)-1] == _T('\\'));
1792 /* Anything NOT being "." or ".." (the special directories) must be fully restored */
1793 if (*pNextDir
&& !IsDotDirectory(pNextDir
))
1795 pszPatternPart
= &pszFullPath
[_tcslen(pszFullPath
)];
1796 _tcscpy(pszPatternPart
, pNextDir
);
1797 pszPatternPart
= NULL
;
1800 else if (_tcscmp(pNextDir
, pszPatternPart
) != 0)
1803 * For example, pszPatternPart == "." or ".." and we do not need to
1804 * do anything for these, or pszPatternPart == "dir\\noextfile." and
1805 * we need to restore all the trailing points.
1807 TRACE("pszPatternPart: %S is DIFFERENT from file criterion: %S\n", pszPatternPart
, pNextDir
);
1809 /* Anything NOT being "." or ".." (the special directories) must be fully restored */
1810 if (*pNextDir
&& !IsDotDirectory(pNextDir
))
1812 /* Restore the correct file criterion */
1813 _tcscpy(pszPatternPart
, pNextDir
);
1817 TRACE("pszFullPath (2): %S\n", pszFullPath
);
1820 * If no wildcard or file was specified and this is a directory,
1821 * display all files in it.
1823 if (pszPatternPart
== NULL
|| IsExistingDirectory(pszFullPath
))
1825 pszPatternPart
= &pszFullPath
[_tcslen(pszFullPath
)];
1826 if (pszPatternPart
[-1] != _T('\\'))
1827 *pszPatternPart
++ = _T('\\');
1828 _tcscpy(pszPatternPart
, _T("*"));
1831 TRACE("pszPatternPart (2): %S\n", pszPatternPart
);
1833 if (ppszPatternPart
)
1834 *ppszPatternPart
= pszPatternPart
;
1840 * internal dir command
1843 CommandDir(LPTSTR rest
)
1845 TCHAR dircmd
[MAX_PATH
]; /* A variable to store the DIRCMD environment variable */
1846 TCHAR prev_volume
[MAX_PATH
];
1847 TCHAR szFullPath
[MAX_PATH
];
1848 LPTSTR
* params
= NULL
;
1853 DIRSWITCHFLAGS stFlags
;
1857 /* Initialize Switch Flags < Default switches are set here! > */
1858 stFlags
.b4Digit
= TRUE
;
1859 stFlags
.bBareFormat
= FALSE
;
1860 stFlags
.bDataStreams
= FALSE
;
1861 stFlags
.bLowerCase
= FALSE
;
1862 stFlags
.bNewLongList
= TRUE
;
1863 stFlags
.bPause
= FALSE
;
1864 stFlags
.bRecursive
= FALSE
;
1865 stFlags
.bShortName
= FALSE
;
1866 stFlags
.bTSeparator
= TRUE
;
1867 stFlags
.bUser
= FALSE
;
1868 stFlags
.bWideList
= FALSE
;
1869 stFlags
.bWideListColSort
= FALSE
;
1870 stFlags
.stTimeField
.eTimeField
= TF_MODIFIEDDATE
;
1871 stFlags
.stAttribs
.dwAttribMask
= FILE_ATTRIBUTE_HIDDEN
| FILE_ATTRIBUTE_SYSTEM
;
1872 stFlags
.stAttribs
.dwAttribVal
= 0L;
1873 stFlags
.stOrderBy
.sCriteriaCount
= 0;
1877 /* Read the parameters from the DIRCMD environment variable */
1878 if (GetEnvironmentVariable (_T("DIRCMD"), dircmd
, ARRAYSIZE(dircmd
)))
1880 if (!DirReadParam(dircmd
, ¶ms
, &entries
, &stFlags
))
1887 /* Read the parameters */
1888 if (!DirReadParam(rest
, ¶ms
, &entries
, &stFlags
) || CheckCtrlBreak(BREAK_INPUT
))
1894 /* Default to current directory */
1897 if (!add_entry(&entries
, ¶ms
, _T("*")))
1904 prev_volume
[0] = _T('\0');
1906 /* Reset paging state */
1908 ConOutPrintfPaging(TRUE
, _T(""));
1910 for (loop
= 0; loop
< (UINT
)entries
; loop
++)
1912 if (CheckCtrlBreak(BREAK_INPUT
))
1918 recurse_dir_cnt
= 0L;
1919 recurse_file_cnt
= 0L;
1923 Uncomment this to show the final state of switch flags*/
1926 TRACE("Attributes mask/value %x/%x\n",stFlags
.stAttribs
.dwAttribMask
,stFlags
.stAttribs
.dwAttribVal
);
1927 TRACE("(B) Bare format : %i\n", stFlags
.bBareFormat
);
1928 TRACE("(C) Thousand : %i\n", stFlags
.bTSeparator
);
1929 TRACE("(W) Wide list : %i\n", stFlags
.bWideList
);
1930 TRACE("(D) Wide list sort by column : %i\n", stFlags
.bWideListColSort
);
1931 TRACE("(L) Lowercase : %i\n", stFlags
.bLowerCase
);
1932 TRACE("(N) New : %i\n", stFlags
.bNewLongList
);
1933 TRACE("(O) Order : %i\n", stFlags
.stOrderBy
.sCriteriaCount
);
1934 for (i
=0;i
<stFlags
.stOrderBy
.sCriteriaCount
;i
++)
1935 TRACE(" Order Criteria [%i]: %i (Reversed: %i)\n",i
, stFlags
.stOrderBy
.eCriteria
[i
], stFlags
.stOrderBy
.bCriteriaRev
[i
]);
1936 TRACE("(P) Pause : %i\n", stFlags
.bPause
);
1937 TRACE("(Q) Owner : %i\n", stFlags
.bUser
);
1938 TRACE("(R) Data stream : %i\n", stFlags
.bDataStreams
);
1939 TRACE("(S) Recursive : %i\n", stFlags
.bRecursive
);
1940 TRACE("(T) Time field : %i\n", stFlags
.stTimeField
.eTimeField
);
1941 TRACE("(X) Short names : %i\n", stFlags
.bShortName
);
1942 TRACE("Parameter : %s\n", debugstr_aw(params
[loop
]));
1945 /* Print the drive header if the volume changed */
1946 ChangedVolume
= TRUE
;
1948 if (!stFlags
.bBareFormat
&&
1949 GetVolumePathName(params
[loop
], szFullPath
, ARRAYSIZE(szFullPath
)))
1951 if (!_tcscmp(szFullPath
, prev_volume
))
1952 ChangedVolume
= FALSE
;
1954 _tcscpy(prev_volume
, szFullPath
);
1957 /* Resolve the pattern */
1958 ResolvePattern(params
[loop
], ARRAYSIZE(szFullPath
), szFullPath
, &pszFilePart
);
1960 /* Print the header */
1961 cPathSep
= pszFilePart
[-1];
1962 pszFilePart
[-1] = _T('\0'); /* Truncate to directory name only */
1963 if (ChangedVolume
&& !stFlags
.bBareFormat
&&
1964 !PrintDirectoryHeader(szFullPath
, &stFlags
))
1969 pszFilePart
[-1] = cPathSep
;
1971 /* Perform the actual directory listing */
1972 if (DirList(szFullPath
, pszFilePart
, &stFlags
) != 0)
1978 /* Print the footer */
1979 pszFilePart
[-1] = _T('\0'); /* Truncate to directory name only */
1980 PrintSummary(szFullPath
,