2 * CMD - Wine-compatible command line interface - Directory functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * On entry, global variables quals, param1, param2 contain
25 * the qualifiers (uppercased and concatenated) and parameters entered, with
26 * environment-variable and batch parameter substitution already done.
29 #define WIN32_LEAN_AND_MEAN
32 #include "wine/debug.h"
34 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
36 int WCMD_dir_sort (const void *a
, const void *b
);
37 WCHAR
* WCMD_filesize64 (ULONGLONG free
);
38 WCHAR
* WCMD_strrev (WCHAR
*buff
);
39 static void WCMD_getfileowner(WCHAR
*filename
, WCHAR
*owner
, int ownerlen
);
40 static void WCMD_dir_trailer(WCHAR drive
);
43 extern WCHAR quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
44 extern DWORD errorlevel
;
46 typedef enum _DISPLAYTIME
53 typedef enum _DISPLAYORDER
61 static DIRECTORY_STACK
*WCMD_list_directory (DIRECTORY_STACK
*parms
, int level
);
62 static int file_total
, dir_total
, recurse
, wide
, bare
, max_width
, lower
;
63 static int shortname
, usernames
;
64 static ULONGLONG byte_total
;
65 static DISPLAYTIME dirTime
;
66 static DISPLAYORDER dirOrder
;
67 static BOOL orderReverse
, orderGroupDirs
, orderGroupDirsReverse
, orderByCol
;
68 static BOOL separator
;
69 static ULONG showattrs
, attrsbits
;
71 static const WCHAR dotW
[] = {'.','\0'};
72 static const WCHAR dotdotW
[] = {'.','.','\0'};
73 static const WCHAR starW
[] = {'*','\0'};
74 static const WCHAR slashW
[] = {'\\','\0'};
75 static const WCHAR emptyW
[] = {'\0'};
76 static const WCHAR spaceW
[] = {' ','\0'};
78 /*****************************************************************************
81 * List a file directory.
85 void WCMD_directory (WCHAR
*cmd
) {
87 WCHAR path
[MAX_PATH
], cwd
[MAX_PATH
];
88 int status
, paged_mode
;
89 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
91 WCHAR string
[MAXSTRING
];
93 int argsProcessed
= 0;
96 BOOL trailerReqd
= FALSE
;
97 DIRECTORY_STACK
*fullParms
= NULL
;
98 DIRECTORY_STACK
*prevEntry
= NULL
;
99 DIRECTORY_STACK
*thisEntry
= NULL
;
102 WCHAR fname
[MAX_PATH
];
104 static const WCHAR dircmdW
[] = {'D','I','R','C','M','D','\0'};
108 /* Prefill Quals with (uppercased) DIRCMD env var */
109 if (GetEnvironmentVariable (dircmdW
, string
, sizeof(string
)/sizeof(WCHAR
))) {
111 while ( (*p
= toupper(*p
)) ) ++p
;
112 strcatW(string
,quals
);
113 strcpyW(quals
, string
);
117 file_total
= dir_total
= 0;
119 /* Initialize all flags to their defaults as if no DIRCMD or quals */
131 orderReverse
= FALSE
;
132 orderGroupDirs
= FALSE
;
133 orderGroupDirsReverse
= FALSE
;
137 /* Handle args - Loop through so right most is the effective one */
138 /* Note: /- appears to be a negate rather than an off, eg. dir
139 /-W is wide, or dir /w /-w /-w is also wide */
141 while (*p
&& (*p
=='/' || *p
==' ')) {
143 if (*p
++==' ') continue; /* Skip / and blanks introduced through DIRCMD */
150 WINE_TRACE("Processing arg '%c' (in %s)\n", *p
, wine_dbgstr_w(quals
));
152 case 'P': if (negate
) paged_mode
= !paged_mode
;
153 else paged_mode
= TRUE
;
155 case 'S': if (negate
) recurse
= !recurse
;
158 case 'W': if (negate
) wide
= !wide
;
161 case 'B': if (negate
) bare
= !bare
;
164 case 'L': if (negate
) lower
= !lower
;
167 case 'X': if (negate
) shortname
= !shortname
;
168 else shortname
= TRUE
;
170 case 'Q': if (negate
) usernames
= !usernames
;
171 else usernames
= TRUE
;
173 case 'D': if (negate
) orderByCol
= !orderByCol
;
174 else orderByCol
= TRUE
;
176 case 'C': if (negate
) separator
= !separator
;
177 else separator
= TRUE
;
180 if (*p
==':') p
++; /* Skip optional : */
182 if (*p
== 'A') dirTime
= Access
;
183 else if (*p
== 'C') dirTime
= Creation
;
184 else if (*p
== 'W') dirTime
= Written
;
186 /* Support /T and /T: with no parms, default to written */
187 else if (*p
== 0x00 || *p
== '/') {
189 p
= p
- 1; /* So when step on, move to '/' */
191 SetLastError(ERROR_INVALID_PARAMETER
);
198 if (*p
==':') p
++; /* Skip optional : */
199 while (*p
&& *p
!= '/') {
200 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p
, wine_dbgstr_w(quals
));
202 case 'N': dirOrder
= Name
; break;
203 case 'E': dirOrder
= Extension
; break;
204 case 'S': dirOrder
= Size
; break;
205 case 'D': dirOrder
= Date
; break;
206 case '-': if (*(p
+1)=='G') orderGroupDirsReverse
=TRUE
;
207 else orderReverse
= TRUE
;
209 case 'G': orderGroupDirs
= TRUE
; break;
211 SetLastError(ERROR_INVALID_PARAMETER
);
218 p
= p
- 1; /* So when step on, move to '/' */
223 if (*p
==':') p
++; /* Skip optional : */
224 while (*p
&& *p
!= '/') {
225 BOOL anegate
= FALSE
;
228 /* Note /A: - options are 'offs' not toggles */
234 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p
, wine_dbgstr_w(quals
));
236 case 'D': mask
= FILE_ATTRIBUTE_DIRECTORY
; break;
237 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
238 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
239 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
240 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
242 SetLastError(ERROR_INVALID_PARAMETER
);
248 /* Keep running list of bits we care about */
251 /* Mask shows what MUST be in the bits we care about */
252 if (anegate
) showattrs
= showattrs
& ~mask
;
253 else showattrs
|= mask
;
257 p
= p
- 1; /* So when step on, move to '/' */
258 WINE_TRACE("Result: showattrs %x, bits %x\n", showattrs
, attrsbits
);
261 SetLastError(ERROR_INVALID_PARAMETER
);
269 /* Handle conflicting args and initialization */
270 if (bare
|| shortname
) wide
= FALSE
;
271 if (bare
) shortname
= FALSE
;
272 if (wide
) usernames
= FALSE
;
273 if (orderByCol
) wide
= TRUE
;
276 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
))
277 max_width
= consoleInfo
.dwSize
.X
;
282 WCMD_enter_paged_mode(NULL
);
288 GetCurrentDirectory (MAX_PATH
, cwd
);
289 strcatW(cwd
, slashW
);
291 /* Loop through all args, calculating full effective directory */
295 WCHAR fullname
[MAXSTRING
];
296 WCHAR
*thisArg
= WCMD_parameter (cmd
, argno
++, &argN
);
297 if (argN
&& argN
[0] != '/') {
299 WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg
));
300 if (thisArg
[1] == ':' && thisArg
[2] == '\\') {
301 strcpyW(fullname
, thisArg
);
302 } else if (thisArg
[1] == ':' && thisArg
[2] != '\\') {
304 static const WCHAR envFmt
[] = {'=','%','c',':','\0'};
305 wsprintf(envvar
, envFmt
, thisArg
[0]);
306 if (!GetEnvironmentVariable(envvar
, fullname
, MAX_PATH
)) {
307 static const WCHAR noEnvFmt
[] = {'%','c',':','\0'};
308 wsprintf(fullname
, noEnvFmt
, thisArg
[0]);
310 strcatW(fullname
, slashW
);
311 strcatW(fullname
, &thisArg
[2]);
312 } else if (thisArg
[0] == '\\') {
313 memcpy(fullname
, cwd
, 2 * sizeof(WCHAR
));
314 strcpyW(fullname
+2, thisArg
);
316 strcpyW(fullname
, cwd
);
317 strcatW(fullname
, thisArg
);
319 WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname
));
321 status
= GetFullPathName (fullname
, sizeof(path
)/sizeof(WCHAR
), path
, NULL
);
324 * If the path supplied does not include a wildcard, and the endpoint of the
325 * path references a directory, we need to list the *contents* of that
326 * directory not the directory file itself.
328 if ((strchrW(path
, '*') == NULL
) && (strchrW(path
, '%') == NULL
)) {
329 status
= GetFileAttributes (path
);
330 if ((status
!= INVALID_FILE_ATTRIBUTES
) && (status
& FILE_ATTRIBUTE_DIRECTORY
)) {
331 if (path
[strlenW(path
)-1] == '\\') {
332 strcatW (path
, starW
);
335 const WCHAR slashStarW
[] = {'\\','*','\0'};
336 strcatW (path
, slashStarW
);
340 /* Special case wildcard search with no extension (ie parameters ending in '.') as
341 GetFullPathName strips off the additional '.' */
342 if (fullname
[strlenW(fullname
)-1] == '.') strcatW(path
, dotW
);
345 WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path
));
346 thisEntry
= HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
347 if (fullParms
== NULL
) fullParms
= thisEntry
;
348 if (prevEntry
!= NULL
) prevEntry
->next
= thisEntry
;
349 prevEntry
= thisEntry
;
350 thisEntry
->next
= NULL
;
352 /* Split into components */
353 WCMD_splitpath(path
, drive
, dir
, fname
, ext
);
354 WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
355 wine_dbgstr_w(drive
), wine_dbgstr_w(dir
),
356 wine_dbgstr_w(fname
), wine_dbgstr_w(ext
));
358 thisEntry
->dirName
= HeapAlloc(GetProcessHeap(),0,
359 sizeof(WCHAR
) * (strlenW(drive
)+strlenW(dir
)+1));
360 strcpyW(thisEntry
->dirName
, drive
);
361 strcatW(thisEntry
->dirName
, dir
);
363 thisEntry
->fileName
= HeapAlloc(GetProcessHeap(),0,
364 sizeof(WCHAR
) * (strlenW(fname
)+strlenW(ext
)+1));
365 strcpyW(thisEntry
->fileName
, fname
);
366 strcatW(thisEntry
->fileName
, ext
);
371 /* If just 'dir' entered, a '*' parameter is assumed */
372 if (fullParms
== NULL
) {
373 WINE_TRACE("Inserting default '*'\n");
374 fullParms
= HeapAlloc(GetProcessHeap(),0, sizeof(DIRECTORY_STACK
));
375 fullParms
->next
= NULL
;
376 fullParms
->dirName
= HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR
) * (strlenW(cwd
)+1));
377 strcpyW(fullParms
->dirName
, cwd
);
378 fullParms
->fileName
= HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR
) * 2);
379 strcpyW(fullParms
->fileName
, starW
);
384 thisEntry
= fullParms
;
387 while (thisEntry
!= NULL
) {
389 /* Output disk free (trailer) and volume information (header) if the drive
391 if (lastDrive
!= toupper(thisEntry
->dirName
[0])) {
393 /* Trailer Information */
394 if (lastDrive
!= '?') {
396 WCMD_dir_trailer(prevEntry
->dirName
[0]);
399 lastDrive
= toupper(thisEntry
->dirName
[0]);
404 WINE_TRACE("Writing volume for '%c:'\n", thisEntry
->dirName
[0]);
405 memcpy(drive
, thisEntry
->dirName
, 2 * sizeof(WCHAR
));
407 status
= WCMD_volume (0, drive
);
415 static const WCHAR newLine2
[] = {'\n','\n','\0'};
416 if (!bare
) WCMD_output (newLine2
);
419 /* Clear any errors from previous invocations, and process it */
421 prevEntry
= thisEntry
;
422 thisEntry
= WCMD_list_directory (thisEntry
, 0);
425 /* Trailer Information */
427 WCMD_dir_trailer(prevEntry
->dirName
[0]);
431 if (paged_mode
) WCMD_leave_paged_mode();
433 /* Free storage allocated for parms */
434 while (fullParms
!= NULL
) {
435 prevEntry
= fullParms
;
436 fullParms
= prevEntry
->next
;
437 HeapFree(GetProcessHeap(),0,prevEntry
->dirName
);
438 HeapFree(GetProcessHeap(),0,prevEntry
->fileName
);
439 HeapFree(GetProcessHeap(),0,prevEntry
);
443 /*****************************************************************************
444 * WCMD_list_directory
446 * List a single file directory. This function (and those below it) can be called
447 * recursively when the /S switch is used.
449 * FIXME: Assumes 24-line display for the /P qualifier.
452 static DIRECTORY_STACK
*WCMD_list_directory (DIRECTORY_STACK
*inputparms
, int level
) {
454 WCHAR string
[1024], datestring
[32], timestring
[32];
455 WCHAR real_path
[MAX_PATH
];
460 int dir_count
, file_count
, entry_count
, i
, widest
, cur_width
, tmp_width
;
461 int numCols
, numRows
;
463 ULARGE_INTEGER byte_count
, file_size
;
464 DIRECTORY_STACK
*parms
;
465 int concurrentDirs
= 0;
466 BOOL done_header
= FALSE
;
468 static const WCHAR fmtDir
[] = {'%','1','0','s',' ',' ','%','8','s',' ',' ',
469 '<','D','I','R','>',' ',' ',' ',' ',' ',' ',' ',' ',' ','\0'};
470 static const WCHAR fmtFile
[] = {'%','1','0','s',' ',' ','%','8','s',' ',' ',
471 ' ',' ','%','1','0','s',' ',' ','\0'};
472 static const WCHAR fmt2
[] = {'%','-','1','3','s','\0'};
473 static const WCHAR fmt3
[] = {'%','-','2','3','s','\0'};
474 static const WCHAR fmt4
[] = {'%','s','\0'};
475 static const WCHAR fmt5
[] = {'%','s','%','s','\0'};
480 byte_count
.QuadPart
= 0;
484 /* Loop merging all the files from consecutive parms which relate to the
485 same directory. Note issuing a directory header with no contents
486 mirrors what windows does */
488 fd
= HeapAlloc(GetProcessHeap(),0,sizeof(WIN32_FIND_DATA
));
489 while (parms
&& strcmpW(inputparms
->dirName
, parms
->dirName
) == 0) {
492 /* Work out the full path + filename */
493 strcpyW(real_path
, parms
->dirName
);
494 strcatW(real_path
, parms
->fileName
);
496 /* Load all files into an in memory structure */
497 WINE_TRACE("Looking for matches to '%s'\n", wine_dbgstr_w(real_path
));
498 hff
= FindFirstFile (real_path
, (fd
+entry_count
));
499 if (hff
!= INVALID_HANDLE_VALUE
) {
501 /* Skip any which are filtered out by attribute */
502 if (((fd
+entry_count
)->dwFileAttributes
& attrsbits
) != showattrs
) continue;
506 /* Keep running track of longest filename for wide output */
507 if (wide
|| orderByCol
) {
508 int tmpLen
= strlenW((fd
+(entry_count
-1))->cFileName
) + 3;
509 if ((fd
+(entry_count
-1))->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) tmpLen
= tmpLen
+ 2;
510 if (tmpLen
> widest
) widest
= tmpLen
;
513 fd
= HeapReAlloc(GetProcessHeap(),0,fd
,(entry_count
+1)*sizeof(WIN32_FIND_DATA
));
516 WINE_ERR("Out of memory\n");
520 } while (FindNextFile(hff
, (fd
+entry_count
)) != 0);
524 /* Work out the actual current directory name without a trailing \ */
525 strcpyW(real_path
, parms
->dirName
);
526 real_path
[strlenW(parms
->dirName
)-1] = 0x00;
528 /* Output the results */
530 if (level
!= 0 && (entry_count
> 0)) WCMD_output (newline
);
531 if (!recurse
|| ((entry_count
> 0) && done_header
==FALSE
)) {
532 static const WCHAR headerW
[] = {'D','i','r','e','c','t','o','r','y',' ','o','f',
533 ' ','%','s','\n','\n','\0'};
534 WCMD_output (headerW
, real_path
);
539 /* Move to next parm */
543 /* Handle case where everything is filtered out */
544 if (entry_count
> 0) {
546 /* Sort the list of files */
547 qsort (fd
, entry_count
, sizeof(WIN32_FIND_DATA
), WCMD_dir_sort
);
549 /* Work out the number of columns */
550 WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count
, max_width
, widest
);
551 if (wide
|| orderByCol
) {
552 numCols
= max(1, (int)max_width
/ widest
);
553 numRows
= entry_count
/ numCols
;
554 if (entry_count
% numCols
) numRows
++;
557 numRows
= entry_count
;
559 WINE_TRACE("cols=%d, rows=%d\n", numCols
, numRows
);
561 for (rows
=0; rows
<numRows
; rows
++) {
562 BOOL addNewLine
= TRUE
;
563 for (cols
=0; cols
<numCols
; cols
++) {
566 /* Work out the index of the entry being pointed to */
568 i
= (cols
* numRows
) + rows
;
569 if (i
>= entry_count
) continue;
571 i
= (rows
* numCols
) + cols
;
572 if (i
>= entry_count
) continue;
575 /* /L convers all names to lower case */
577 WCHAR
*p
= (fd
+i
)->cFileName
;
578 while ( (*p
= tolower(*p
)) ) ++p
;
581 /* /Q gets file ownership information */
583 strcpyW (string
, inputparms
->dirName
);
584 strcatW (string
, (fd
+i
)->cFileName
);
585 WCMD_getfileowner(string
, username
, sizeof(username
)/sizeof(WCHAR
));
588 if (dirTime
== Written
) {
589 FileTimeToLocalFileTime (&(fd
+i
)->ftLastWriteTime
, &ft
);
590 } else if (dirTime
== Access
) {
591 FileTimeToLocalFileTime (&(fd
+i
)->ftLastAccessTime
, &ft
);
593 FileTimeToLocalFileTime (&(fd
+i
)->ftCreationTime
, &ft
);
595 FileTimeToSystemTime (&ft
, &st
);
596 GetDateFormat (0, DATE_SHORTDATE
, &st
, NULL
, datestring
,
597 sizeof(datestring
)/sizeof(WCHAR
));
598 GetTimeFormat (0, TIME_NOSECONDS
, &st
,
599 NULL
, timestring
, sizeof(timestring
)/sizeof(WCHAR
));
603 tmp_width
= cur_width
;
604 if ((fd
+i
)->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
605 static const WCHAR fmt
[] = {'[','%','s',']','\0'};
606 WCMD_output (fmt
, (fd
+i
)->cFileName
);
608 tmp_width
= tmp_width
+ strlenW((fd
+i
)->cFileName
) + 2;
610 static const WCHAR fmt
[] = {'%','s','\0'};
611 WCMD_output (fmt
, (fd
+i
)->cFileName
);
612 tmp_width
= tmp_width
+ strlenW((fd
+i
)->cFileName
) ;
614 file_size
.u
.LowPart
= (fd
+i
)->nFileSizeLow
;
615 file_size
.u
.HighPart
= (fd
+i
)->nFileSizeHigh
;
616 byte_count
.QuadPart
+= file_size
.QuadPart
;
618 cur_width
= cur_width
+ widest
;
620 if ((cur_width
+ widest
) > max_width
) {
623 int padding
= cur_width
- tmp_width
;
627 /* Note: WCMD_output uses wvsprintf which does not allow %*
628 so manually pad with spaces to appropriate width */
629 strcpyW(temp
, emptyW
);
630 while (padding
> 0) {
631 strcatW(&temp
[toWrite
], spaceW
);
636 strcpyW(temp
, emptyW
);
643 } else if ((fd
+i
)->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
647 WCMD_output (fmtDir
, datestring
, timestring
);
648 if (shortname
) WCMD_output (fmt2
, (fd
+i
)->cAlternateFileName
);
649 if (usernames
) WCMD_output (fmt3
, username
);
650 WCMD_output(fmt4
,(fd
+i
)->cFileName
);
652 if (!((strcmpW((fd
+i
)->cFileName
, dotW
) == 0) ||
653 (strcmpW((fd
+i
)->cFileName
, dotdotW
) == 0))) {
654 WCMD_output (fmt5
, recurse
?inputparms
->dirName
:emptyW
, (fd
+i
)->cFileName
);
662 file_size
.u
.LowPart
= (fd
+i
)->nFileSizeLow
;
663 file_size
.u
.HighPart
= (fd
+i
)->nFileSizeHigh
;
664 byte_count
.QuadPart
+= file_size
.QuadPart
;
666 WCMD_output (fmtFile
, datestring
, timestring
,
667 WCMD_filesize64(file_size
.QuadPart
));
668 if (shortname
) WCMD_output (fmt2
, (fd
+i
)->cAlternateFileName
);
669 if (usernames
) WCMD_output (fmt3
, username
);
670 WCMD_output(fmt4
,(fd
+i
)->cFileName
);
672 WCMD_output (fmt5
, recurse
?inputparms
->dirName
:emptyW
, (fd
+i
)->cFileName
);
676 if (addNewLine
) WCMD_output (newline
);
681 if (file_count
== 1) {
682 static const WCHAR fmt
[] = {' ',' ',' ',' ',' ',' ',' ','1',' ','f','i','l','e',' ',
683 '%','2','5','s',' ','b','y','t','e','s','\n','\0'};
684 WCMD_output (fmt
, WCMD_filesize64 (byte_count
.QuadPart
));
687 static const WCHAR fmt
[] = {'%','8','d',' ','f','i','l','e','s',' ','%','2','4','s',
688 ' ','b','y','t','e','s','\n','\0'};
689 WCMD_output (fmt
, file_count
, WCMD_filesize64 (byte_count
.QuadPart
));
692 byte_total
= byte_total
+ byte_count
.QuadPart
;
693 file_total
= file_total
+ file_count
;
694 dir_total
= dir_total
+ dir_count
;
696 if (!bare
&& !recurse
) {
697 if (dir_count
== 1) {
698 static const WCHAR fmt
[] = {'%','8','d',' ','d','i','r','e','c','t','o','r','y',
699 ' ',' ',' ',' ',' ',' ',' ',' ',' ','\0'};
700 WCMD_output (fmt
, 1);
702 static const WCHAR fmt
[] = {'%','8','d',' ','d','i','r','e','c','t','o','r','i',
704 WCMD_output (fmt
, dir_count
);
708 HeapFree(GetProcessHeap(),0,fd
);
710 /* When recursing, look in all subdirectories for matches */
712 DIRECTORY_STACK
*dirStack
= NULL
;
713 DIRECTORY_STACK
*lastEntry
= NULL
;
714 WIN32_FIND_DATA finddata
;
716 /* Build path to search */
717 strcpyW(string
, inputparms
->dirName
);
718 strcatW(string
, starW
);
720 WINE_TRACE("Recursive, looking for '%s'\n", wine_dbgstr_w(string
));
721 hff
= FindFirstFile (string
, &finddata
);
722 if (hff
!= INVALID_HANDLE_VALUE
) {
724 if ((finddata
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
725 (strcmpW(finddata
.cFileName
, dotdotW
) != 0) &&
726 (strcmpW(finddata
.cFileName
, dotW
) != 0)) {
728 DIRECTORY_STACK
*thisDir
;
729 int dirsToCopy
= concurrentDirs
;
731 /* Loop creating list of subdirs for all concurrent entries */
733 while (dirsToCopy
> 0) {
736 /* Work out search parameter in sub dir */
737 strcpyW (string
, inputparms
->dirName
);
738 strcatW (string
, finddata
.cFileName
);
739 strcatW (string
, slashW
);
740 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(string
));
742 /* Allocate memory, add to list */
743 thisDir
= HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
744 if (dirStack
== NULL
) dirStack
= thisDir
;
745 if (lastEntry
!= NULL
) lastEntry
->next
= thisDir
;
747 thisDir
->next
= NULL
;
748 thisDir
->dirName
= HeapAlloc(GetProcessHeap(),0,
749 sizeof(WCHAR
) * (strlenW(string
)+1));
750 strcpyW(thisDir
->dirName
, string
);
751 thisDir
->fileName
= HeapAlloc(GetProcessHeap(),0,
752 sizeof(WCHAR
) * (strlenW(parms
->fileName
)+1));
753 strcpyW(thisDir
->fileName
, parms
->fileName
);
757 } while (FindNextFile(hff
, &finddata
) != 0);
760 while (dirStack
!= NULL
) {
761 DIRECTORY_STACK
*thisDir
= dirStack
;
762 dirStack
= WCMD_list_directory (thisDir
, 1);
763 while (thisDir
!= dirStack
) {
764 DIRECTORY_STACK
*tempDir
= thisDir
->next
;
765 HeapFree(GetProcessHeap(),0,thisDir
->dirName
);
766 HeapFree(GetProcessHeap(),0,thisDir
->fileName
);
767 HeapFree(GetProcessHeap(),0,thisDir
);
774 /* Handle case where everything is filtered out */
775 if ((file_total
+ dir_total
== 0) && (level
== 0)) {
776 SetLastError (ERROR_FILE_NOT_FOUND
);
784 /*****************************************************************************
787 * Convert a 64-bit number into a WCHARacter string, with commas every three digits.
788 * Result is returned in a static string overwritten with each call.
789 * FIXME: There must be a better algorithm!
792 WCHAR
* WCMD_filesize64 (ULONGLONG n
) {
797 static WCHAR buff
[32];
802 if (separator
&& ((++i
)%3 == 1)) *p
++ = ',';
813 /*****************************************************************************
816 * Reverse a WCHARacter string in-place (strrev() is not available under unixen :-( ).
819 WCHAR
* WCMD_strrev (WCHAR
*buff
) {
825 for (i
=0; i
<r
/2; i
++) {
827 buff
[i
] = buff
[r
-i
-1];
834 /*****************************************************************************
837 * Sort based on the /O options supplied on the command line
839 int WCMD_dir_sort (const void *a
, const void *b
)
841 WIN32_FIND_DATA
*filea
= (WIN32_FIND_DATA
*)a
;
842 WIN32_FIND_DATA
*fileb
= (WIN32_FIND_DATA
*)b
;
845 /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
846 requested sort order for the directory components */
847 if (orderGroupDirs
&&
848 ((filea
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) ||
849 (fileb
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)))
851 BOOL aDir
= filea
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
;
852 if (aDir
) result
= -1;
854 if (orderGroupDirsReverse
) result
= -result
;
858 } else if (dirOrder
== Name
) {
859 result
= lstrcmpiW(filea
->cFileName
, fileb
->cFileName
);
862 } else if (dirOrder
== Size
) {
863 ULONG64 sizea
= (((ULONG64
)filea
->nFileSizeHigh
) << 32) + filea
->nFileSizeLow
;
864 ULONG64 sizeb
= (((ULONG64
)fileb
->nFileSizeHigh
) << 32) + fileb
->nFileSizeLow
;
865 if( sizea
< sizeb
) result
= -1;
866 else if( sizea
== sizeb
) result
= 0;
869 /* Order by Date: (Takes into account which date (/T option) */
870 } else if (dirOrder
== Date
) {
873 ULONG64 timea
, timeb
;
875 if (dirTime
== Written
) {
876 ft
= &filea
->ftLastWriteTime
;
877 timea
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
878 ft
= &fileb
->ftLastWriteTime
;
879 timeb
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
880 } else if (dirTime
== Access
) {
881 ft
= &filea
->ftLastAccessTime
;
882 timea
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
883 ft
= &fileb
->ftLastAccessTime
;
884 timeb
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
886 ft
= &filea
->ftCreationTime
;
887 timea
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
888 ft
= &fileb
->ftCreationTime
;
889 timeb
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
891 if( timea
< timeb
) result
= -1;
892 else if( timea
== timeb
) result
= 0;
895 /* Order by Extension: (Takes into account which date (/T option) */
896 } else if (dirOrder
== Extension
) {
899 WCHAR fname
[MAX_PATH
];
900 WCHAR extA
[MAX_PATH
];
901 WCHAR extB
[MAX_PATH
];
903 /* Split into components */
904 WCMD_splitpath(filea
->cFileName
, drive
, dir
, fname
, extA
);
905 WCMD_splitpath(fileb
->cFileName
, drive
, dir
, fname
, extB
);
906 result
= lstrcmpiW(extA
, extB
);
909 if (orderReverse
) result
= -result
;
913 /*****************************************************************************
916 * Reverse a WCHARacter string in-place (strrev() is not available under unixen :-( ).
918 void WCMD_getfileowner(WCHAR
*filename
, WCHAR
*owner
, int ownerlen
) {
920 ULONG sizeNeeded
= 0;
922 WCHAR name
[MAXSTRING
];
923 WCHAR domain
[MAXSTRING
];
925 /* In case of error, return empty string */
928 /* Find out how much space we need for the owner security descritpor */
929 GetFileSecurity(filename
, OWNER_SECURITY_INFORMATION
, 0, 0, &sizeNeeded
);
932 if(rc
== ERROR_INSUFFICIENT_BUFFER
&& sizeNeeded
> 0) {
936 BOOL defaulted
= FALSE
;
937 ULONG nameLen
= MAXSTRING
;
938 ULONG domainLen
= MAXSTRING
;
939 SID_NAME_USE nameuse
;
941 secBuffer
= (LPBYTE
) HeapAlloc(GetProcessHeap(),0,sizeNeeded
* sizeof(BYTE
));
942 if(!secBuffer
) return;
944 /* Get the owners security descriptor */
945 if(!GetFileSecurity(filename
, OWNER_SECURITY_INFORMATION
, secBuffer
,
946 sizeNeeded
, &sizeNeeded
)) {
947 HeapFree(GetProcessHeap(),0,secBuffer
);
951 /* Get the SID from the SD */
952 if(!GetSecurityDescriptorOwner(secBuffer
, &pSID
, &defaulted
)) {
953 HeapFree(GetProcessHeap(),0,secBuffer
);
957 /* Convert to a username */
958 if (LookupAccountSid(NULL
, pSID
, name
, &nameLen
, domain
, &domainLen
, &nameuse
)) {
959 static const WCHAR fmt
[] = {'%','s','%','c','%','s','\0'};
960 snprintfW(owner
, ownerlen
, fmt
, domain
, '\\', name
);
962 HeapFree(GetProcessHeap(),0,secBuffer
);
967 /*****************************************************************************
970 * Print out the trailer for the supplied drive letter
972 static void WCMD_dir_trailer(WCHAR drive
) {
973 ULARGE_INTEGER avail
, total
, freebytes
;
975 WCHAR driveName
[4] = {'c',':','\\','\0'};
977 driveName
[0] = drive
;
978 status
= GetDiskFreeSpaceEx (driveName
, &avail
, &total
, &freebytes
);
979 WINE_TRACE("Writing trailer for '%s' gave %d(%d)\n", wine_dbgstr_w(driveName
),
980 status
, GetLastError());
982 if (errorlevel
==0 && !bare
) {
984 static const WCHAR fmt1
[] = {'\n',' ',' ',' ',' ',' ','T','o','t','a','l',' ','f','i','l','e','s',
985 ' ','l','i','s','t','e','d',':','\n','%','8','d',' ','f','i','l','e',
986 's','%','2','5','s',' ','b','y','t','e','s','\n','\0'};
987 static const WCHAR fmt2
[] = {'%','8','d',' ','d','i','r','e','c','t','o','r','i','e','s',' ','%',
988 '1','8','s',' ','b','y','t','e','s',' ','f','r','e','e','\n','\n',
990 WCMD_output (fmt1
, file_total
, WCMD_filesize64 (byte_total
));
991 WCMD_output (fmt2
, dir_total
, WCMD_filesize64 (freebytes
.QuadPart
));
993 static const WCHAR fmt
[] = {' ','%','1','8','s',' ','b','y','t','e','s',' ','f','r','e','e',
995 WCMD_output (fmt
, WCMD_filesize64 (freebytes
.QuadPart
));