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
22 #define WIN32_LEAN_AND_MEAN
25 #include "wine/debug.h"
27 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
29 typedef enum _DISPLAYTIME
36 typedef enum _DISPLAYORDER
44 #define MAX_DATETIME_FORMAT 80
46 static WCHAR date_format
[MAX_DATETIME_FORMAT
* 2];
47 static WCHAR time_format
[MAX_DATETIME_FORMAT
* 2];
48 static int file_total
, dir_total
, max_width
;
49 static ULONGLONG byte_total
;
50 static DISPLAYTIME dirTime
;
51 static DISPLAYORDER dirOrder
;
52 static BOOL orderReverse
, orderGroupDirs
, orderGroupDirsReverse
, orderByCol
;
53 static BOOL paged_mode
, recurse
, wide
, bare
, lower
, shortname
, usernames
, separator
;
54 static ULONG showattrs
, attrsbits
;
56 /*****************************************************************************
59 * Convert a 64-bit number into a WCHARacter string, with commas every three digits.
60 * Result is returned in a static string overwritten with each call.
61 * FIXME: There must be a better algorithm!
63 static WCHAR
* WCMD_filesize64 (ULONGLONG n
) {
68 static WCHAR buff
[32];
73 if (separator
&& ((++i
)%3 == 1)) *p
++ = ',';
84 /*****************************************************************************
87 * Sort based on the /O options supplied on the command line
89 static int __cdecl
WCMD_dir_sort (const void *a
, const void *b
)
91 const WIN32_FIND_DATAW
*filea
= (const WIN32_FIND_DATAW
*)a
;
92 const WIN32_FIND_DATAW
*fileb
= (const WIN32_FIND_DATAW
*)b
;
95 /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
96 requested sort order for the directory components */
98 ((filea
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) ||
99 (fileb
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)))
101 BOOL aDir
= filea
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
;
102 if (aDir
) result
= -1;
104 if (orderGroupDirsReverse
) result
= -result
;
108 } else if (dirOrder
== Name
) {
109 result
= lstrcmpiW(filea
->cFileName
, fileb
->cFileName
);
112 } else if (dirOrder
== Size
) {
113 ULONG64 sizea
= (((ULONG64
)filea
->nFileSizeHigh
) << 32) + filea
->nFileSizeLow
;
114 ULONG64 sizeb
= (((ULONG64
)fileb
->nFileSizeHigh
) << 32) + fileb
->nFileSizeLow
;
115 if( sizea
< sizeb
) result
= -1;
116 else if( sizea
== sizeb
) result
= 0;
119 /* Order by Date: (Takes into account which date (/T option) */
120 } else if (dirOrder
== Date
) {
123 ULONG64 timea
, timeb
;
125 if (dirTime
== Written
) {
126 ft
= &filea
->ftLastWriteTime
;
127 timea
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
128 ft
= &fileb
->ftLastWriteTime
;
129 timeb
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
130 } else if (dirTime
== Access
) {
131 ft
= &filea
->ftLastAccessTime
;
132 timea
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
133 ft
= &fileb
->ftLastAccessTime
;
134 timeb
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
136 ft
= &filea
->ftCreationTime
;
137 timea
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
138 ft
= &fileb
->ftCreationTime
;
139 timeb
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
141 if( timea
< timeb
) result
= -1;
142 else if( timea
== timeb
) result
= 0;
145 /* Order by Extension: (Takes into account which date (/T option) */
146 } else if (dirOrder
== Extension
) {
149 WCHAR fname
[MAX_PATH
];
150 WCHAR extA
[MAX_PATH
];
151 WCHAR extB
[MAX_PATH
];
153 /* Split into components */
154 _wsplitpath(filea
->cFileName
, drive
, dir
, fname
, extA
);
155 _wsplitpath(fileb
->cFileName
, drive
, dir
, fname
, extB
);
156 result
= lstrcmpiW(extA
, extB
);
159 if (orderReverse
) result
= -result
;
163 /*****************************************************************************
166 static void WCMD_getfileowner(WCHAR
*filename
, WCHAR
*owner
, int ownerlen
) {
168 ULONG sizeNeeded
= 0;
170 WCHAR name
[MAXSTRING
];
171 WCHAR domain
[MAXSTRING
];
173 /* In case of error, return empty string */
176 /* Find out how much space we need for the owner security descriptor */
177 GetFileSecurityW(filename
, OWNER_SECURITY_INFORMATION
, 0, 0, &sizeNeeded
);
180 if(rc
== ERROR_INSUFFICIENT_BUFFER
&& sizeNeeded
> 0) {
184 BOOL defaulted
= FALSE
;
185 ULONG nameLen
= MAXSTRING
;
186 ULONG domainLen
= MAXSTRING
;
187 SID_NAME_USE nameuse
;
189 secBuffer
= xalloc(sizeNeeded
* sizeof(BYTE
));
191 /* Get the owners security descriptor */
192 if(!GetFileSecurityW(filename
, OWNER_SECURITY_INFORMATION
, secBuffer
,
193 sizeNeeded
, &sizeNeeded
)) {
198 /* Get the SID from the SD */
199 if(!GetSecurityDescriptorOwner(secBuffer
, &pSID
, &defaulted
)) {
204 /* Convert to a username */
205 if (LookupAccountSidW(NULL
, pSID
, name
, &nameLen
, domain
, &domainLen
, &nameuse
)) {
206 swprintf(owner
, ownerlen
, L
"%s%c%s", domain
, '\\', name
);
213 /*****************************************************************************
214 * WCMD_list_directory
216 * List a single file directory. This function (and those below it) can be called
217 * recursively when the /S switch is used.
219 * FIXME: Assumes 24-line display for the /P qualifier.
222 static DIRECTORY_STACK
*WCMD_list_directory (DIRECTORY_STACK
*inputparms
, int level
) {
224 WCHAR string
[1024], datestring
[32], timestring
[32];
225 WCHAR real_path
[MAX_PATH
];
226 WIN32_FIND_DATAW
*fd
;
230 int dir_count
, file_count
, entry_count
, i
, widest
, cur_width
, tmp_width
;
231 int numCols
, numRows
;
233 ULARGE_INTEGER byte_count
, file_size
;
234 DIRECTORY_STACK
*parms
;
235 int concurrentDirs
= 0;
236 BOOL done_header
= FALSE
;
241 byte_count
.QuadPart
= 0;
245 /* Loop merging all the files from consecutive parms which relate to the
246 same directory. Note issuing a directory header with no contents
247 mirrors what windows does */
249 fd
= xalloc(sizeof(WIN32_FIND_DATAW
));
250 while (parms
&& lstrcmpW(inputparms
->dirName
, parms
->dirName
) == 0) {
253 /* Work out the full path + filename */
254 lstrcpyW(real_path
, parms
->dirName
);
255 lstrcatW(real_path
, parms
->fileName
);
257 /* Load all files into an in memory structure */
258 WINE_TRACE("Looking for matches to '%s'\n", wine_dbgstr_w(real_path
));
259 hff
= FindFirstFileW(real_path
, &fd
[entry_count
]);
260 if (hff
!= INVALID_HANDLE_VALUE
) {
262 /* Skip any which are filtered out by attribute */
263 if ((fd
[entry_count
].dwFileAttributes
& attrsbits
) != showattrs
) continue;
267 /* Keep running track of longest filename for wide output */
268 if (wide
|| orderByCol
) {
269 int tmpLen
= lstrlenW(fd
[entry_count
-1].cFileName
) + 3;
270 if (fd
[entry_count
-1].dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) tmpLen
= tmpLen
+ 2;
271 if (tmpLen
> widest
) widest
= tmpLen
;
274 fd
= xrealloc(fd
, (entry_count
+ 1) * sizeof(WIN32_FIND_DATAW
));
275 } while (FindNextFileW(hff
, &fd
[entry_count
]) != 0);
279 /* Work out the actual current directory name without a trailing \ */
280 lstrcpyW(real_path
, parms
->dirName
);
281 real_path
[lstrlenW(parms
->dirName
)-1] = 0x00;
283 /* Output the results */
285 if (level
!= 0 && (entry_count
> 0)) WCMD_output_asis(L
"\r\n");
286 if (!recurse
|| ((entry_count
> 0) && done_header
==FALSE
)) {
287 WCMD_output (L
"Directory of %1\n\n", real_path
);
292 /* Move to next parm */
296 /* Handle case where everything is filtered out */
297 if (entry_count
> 0) {
299 /* Sort the list of files */
300 qsort (fd
, entry_count
, sizeof(WIN32_FIND_DATAW
), WCMD_dir_sort
);
302 /* Work out the number of columns */
303 WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count
, max_width
, widest
);
304 if (wide
|| orderByCol
) {
305 numCols
= max(1, max_width
/ widest
);
306 numRows
= entry_count
/ numCols
;
307 if (entry_count
% numCols
) numRows
++;
310 numRows
= entry_count
;
312 WINE_TRACE("cols=%d, rows=%d\n", numCols
, numRows
);
314 for (rows
=0; rows
<numRows
; rows
++) {
315 BOOL addNewLine
= TRUE
;
316 for (cols
=0; cols
<numCols
; cols
++) {
319 /* Work out the index of the entry being pointed to */
321 i
= (cols
* numRows
) + rows
;
322 if (i
>= entry_count
) continue;
324 i
= (rows
* numCols
) + cols
;
325 if (i
>= entry_count
) continue;
328 /* /L convers all names to lower case */
329 if (lower
) wcslwr( fd
[i
].cFileName
);
331 /* /Q gets file ownership information */
333 lstrcpyW (string
, inputparms
->dirName
);
334 lstrcatW (string
, fd
[i
].cFileName
);
335 WCMD_getfileowner(string
, username
, ARRAY_SIZE(username
));
338 if (dirTime
== Written
) {
339 FileTimeToLocalFileTime (&fd
[i
].ftLastWriteTime
, &ft
);
340 } else if (dirTime
== Access
) {
341 FileTimeToLocalFileTime (&fd
[i
].ftLastAccessTime
, &ft
);
343 FileTimeToLocalFileTime (&fd
[i
].ftCreationTime
, &ft
);
345 FileTimeToSystemTime (&ft
, &st
);
346 GetDateFormatW(LOCALE_USER_DEFAULT
, 0, &st
, date_format
,
347 datestring
, ARRAY_SIZE(datestring
));
348 GetTimeFormatW(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, &st
, time_format
,
349 timestring
, ARRAY_SIZE(timestring
));
353 tmp_width
= cur_width
;
354 if (fd
[i
].dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
355 WCMD_output (L
"[%1]", fd
[i
].cFileName
);
357 tmp_width
= tmp_width
+ lstrlenW(fd
[i
].cFileName
) + 2;
359 WCMD_output (L
"%1", fd
[i
].cFileName
);
360 tmp_width
= tmp_width
+ lstrlenW(fd
[i
].cFileName
) ;
362 file_size
.u
.LowPart
= fd
[i
].nFileSizeLow
;
363 file_size
.u
.HighPart
= fd
[i
].nFileSizeHigh
;
364 byte_count
.QuadPart
+= file_size
.QuadPart
;
366 cur_width
= cur_width
+ widest
;
368 if ((cur_width
+ widest
) > max_width
) {
371 WCMD_output(L
"%1!*s!", cur_width
- tmp_width
, L
"");
374 } else if (fd
[i
].dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
378 WCMD_output (L
"%1 %2 <DIR> ", datestring
, timestring
);
379 if (shortname
) WCMD_output(L
"%1!-13s!", fd
[i
].cAlternateFileName
);
380 if (usernames
) WCMD_output(L
"%1!-23s!", username
);
381 WCMD_output(L
"%1",fd
[i
].cFileName
);
383 if (!((lstrcmpW(fd
[i
].cFileName
, L
".") == 0) ||
384 (lstrcmpW(fd
[i
].cFileName
, L
"..") == 0))) {
385 WCMD_output(L
"%1%2", recurse
? inputparms
->dirName
: L
"", fd
[i
].cFileName
);
393 file_size
.u
.LowPart
= fd
[i
].nFileSizeLow
;
394 file_size
.u
.HighPart
= fd
[i
].nFileSizeHigh
;
395 byte_count
.QuadPart
+= file_size
.QuadPart
;
397 WCMD_output (L
"%1 %2 %3!14s! ", datestring
, timestring
,
398 WCMD_filesize64(file_size
.QuadPart
));
399 if (shortname
) WCMD_output(L
"%1!-13s!", fd
[i
].cAlternateFileName
);
400 if (usernames
) WCMD_output(L
"%1!-23s!", username
);
401 WCMD_output(L
"%1",fd
[i
].cFileName
);
403 WCMD_output(L
"%1%2", recurse
? inputparms
->dirName
: L
"", fd
[i
].cFileName
);
407 if (addNewLine
) WCMD_output_asis(L
"\r\n");
412 if (file_count
== 1) {
413 WCMD_output (L
" 1 file %1!25s! bytes\n", WCMD_filesize64 (byte_count
.QuadPart
));
416 WCMD_output (L
"%1!8d! files %2!24s! bytes\n", file_count
, WCMD_filesize64 (byte_count
.QuadPart
));
419 byte_total
= byte_total
+ byte_count
.QuadPart
;
420 file_total
= file_total
+ file_count
;
421 dir_total
= dir_total
+ dir_count
;
423 if (!bare
&& !recurse
) {
424 if (dir_count
== 1) {
425 WCMD_output (L
"%1!8d! directory ", 1);
427 WCMD_output (L
"%1!8d! directories", dir_count
);
433 /* When recursing, look in all subdirectories for matches */
435 DIRECTORY_STACK
*dirStack
= NULL
;
436 DIRECTORY_STACK
*lastEntry
= NULL
;
437 WIN32_FIND_DATAW finddata
;
439 /* Build path to search */
440 lstrcpyW(string
, inputparms
->dirName
);
441 lstrcatW(string
, L
"*");
443 WINE_TRACE("Recursive, looking for '%s'\n", wine_dbgstr_w(string
));
444 hff
= FindFirstFileW(string
, &finddata
);
445 if (hff
!= INVALID_HANDLE_VALUE
) {
447 if ((finddata
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
448 (lstrcmpW(finddata
.cFileName
, L
"..") != 0) &&
449 (lstrcmpW(finddata
.cFileName
, L
".") != 0)) {
451 DIRECTORY_STACK
*thisDir
;
452 int dirsToCopy
= concurrentDirs
;
454 /* Loop creating list of subdirs for all concurrent entries */
456 while (dirsToCopy
> 0) {
459 /* Work out search parameter in sub dir */
460 lstrcpyW (string
, inputparms
->dirName
);
461 lstrcatW (string
, finddata
.cFileName
);
462 lstrcatW(string
, L
"\\");
463 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(string
));
465 /* Allocate memory, add to list */
466 thisDir
= xalloc(sizeof(DIRECTORY_STACK
));
467 if (dirStack
== NULL
) dirStack
= thisDir
;
468 if (lastEntry
!= NULL
) lastEntry
->next
= thisDir
;
470 thisDir
->next
= NULL
;
471 thisDir
->dirName
= xstrdupW(string
);
472 thisDir
->fileName
= xstrdupW(parms
->fileName
);
476 } while (FindNextFileW(hff
, &finddata
) != 0);
479 while (dirStack
!= NULL
) {
480 DIRECTORY_STACK
*thisDir
= dirStack
;
481 dirStack
= WCMD_list_directory (thisDir
, 1);
482 while (thisDir
!= dirStack
) {
483 DIRECTORY_STACK
*tempDir
= thisDir
->next
;
484 free(thisDir
->dirName
);
485 free(thisDir
->fileName
);
493 /* Handle case where everything is filtered out */
494 if ((file_total
+ dir_total
== 0) && (level
== 0)) {
495 SetLastError (ERROR_FILE_NOT_FOUND
);
497 errorlevel
= ERROR_INVALID_FUNCTION
;
503 /*****************************************************************************
506 * Print out the trailer for the supplied path
508 static void WCMD_dir_trailer(const WCHAR
*path
) {
509 ULARGE_INTEGER freebytes
;
512 status
= GetDiskFreeSpaceExW(path
, NULL
, NULL
, &freebytes
);
513 WINE_TRACE("Writing trailer for '%s' gave %d(%ld)\n", wine_dbgstr_w(path
),
514 status
, GetLastError());
516 if (errorlevel
== NO_ERROR
&& !bare
) {
518 WCMD_output (L
"\n Total files listed:\n%1!8d! files%2!25s! bytes\n", file_total
, WCMD_filesize64 (byte_total
));
519 WCMD_output (L
"%1!8d! directories %2!18s! bytes free\n\n", dir_total
, WCMD_filesize64 (freebytes
.QuadPart
));
521 WCMD_output (L
" %1!18s! bytes free\n\n", WCMD_filesize64 (freebytes
.QuadPart
));
526 /* Get the length of a date/time formatting pattern */
527 /* copied from dlls/kernelbase/locale.c */
528 static int get_pattern_len( const WCHAR
*pattern
, const WCHAR
*accept
)
532 if (*pattern
== '\'')
534 for (i
= 1; pattern
[i
]; i
++)
536 if (pattern
[i
] != '\'') continue;
537 if (pattern
[++i
] != '\'') return i
;
541 if (!wcschr( accept
, *pattern
)) return 1;
542 for (i
= 1; pattern
[i
]; i
++) if (pattern
[i
] != pattern
[0]) break;
546 /* Initialize date format to use abbreviated one with leading zeros */
547 static void init_date_format(void)
549 WCHAR sys_format
[MAX_DATETIME_FORMAT
];
550 int src_pat_len
, dst_pat_len
;
552 WCHAR
*dst
= date_format
;
554 GetLocaleInfoW(LOCALE_USER_DEFAULT
, LOCALE_SSHORTDATE
, sys_format
, ARRAY_SIZE(sys_format
));
556 for (src
= sys_format
; *src
; src
+= src_pat_len
, dst
+= dst_pat_len
) {
557 src_pat_len
= dst_pat_len
= get_pattern_len(src
, L
"yMd");
562 wmemcpy(dst
, src
, src_pat_len
);
567 if (src_pat_len
== 4) /* full name */
568 dst_pat_len
--; /* -> use abbreviated one */
571 if (src_pat_len
== 1) /* without leading zeros */
572 dst_pat_len
++; /* -> with leading zeros */
573 wmemset(dst
, *src
, dst_pat_len
);
583 TRACE("date format: %s\n", wine_dbgstr_w(date_format
));
586 /* Initialize time format to use leading zeros */
587 static void init_time_format(void)
589 WCHAR sys_format
[MAX_DATETIME_FORMAT
];
590 int src_pat_len
, dst_pat_len
;
592 WCHAR
*dst
= time_format
;
594 GetLocaleInfoW(LOCALE_USER_DEFAULT
, LOCALE_STIMEFORMAT
, sys_format
, ARRAY_SIZE(sys_format
));
596 for (src
= sys_format
; *src
; src
+= src_pat_len
, dst
+= dst_pat_len
) {
597 src_pat_len
= dst_pat_len
= get_pattern_len(src
, L
"Hhmst");
602 wmemcpy(dst
, src
, src_pat_len
);
609 if (src_pat_len
== 1) /* without leading zeros */
610 dst_pat_len
++; /* -> with leading zeros */
613 wmemset(dst
, *src
, dst_pat_len
);
623 /* seconds portion will be dropped by TIME_NOSECONDS */
624 TRACE("time format: %s\n", wine_dbgstr_w(time_format
));
627 /*****************************************************************************
630 * List a file directory.
634 RETURN_CODE
WCMD_directory(WCHAR
*args
)
636 WCHAR path
[MAX_PATH
], cwd
[MAX_PATH
];
638 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
640 WCHAR string
[MAXSTRING
];
644 BOOL trailerReqd
= FALSE
;
645 DIRECTORY_STACK
*fullParms
= NULL
;
646 DIRECTORY_STACK
*prevEntry
= NULL
;
647 DIRECTORY_STACK
*thisEntry
= NULL
;
650 WCHAR fname
[MAX_PATH
];
652 unsigned num_empty
= 0, num_with_data
= 0;
654 errorlevel
= NO_ERROR
;
656 /* Prefill quals with (uppercased) DIRCMD env var */
657 if (GetEnvironmentVariableW(L
"DIRCMD", string
, ARRAY_SIZE(string
))) {
659 lstrcatW(string
,quals
);
660 lstrcpyW(quals
, string
);
664 file_total
= dir_total
= 0;
666 /* Initialize all flags to their defaults as if no DIRCMD or quals */
678 orderReverse
= FALSE
;
679 orderGroupDirs
= FALSE
;
680 orderGroupDirsReverse
= FALSE
;
682 attrsbits
= FILE_ATTRIBUTE_HIDDEN
| FILE_ATTRIBUTE_SYSTEM
;
684 /* Handle args - Loop through so right most is the effective one */
685 /* Note: /- appears to be a negate rather than an off, eg. dir
686 /-W is wide, or dir /w /-w /-w is also wide */
688 while (*p
&& (*p
=='/' || *p
==' ')) {
690 if (*p
++==' ') continue; /* Skip / and blanks introduced through DIRCMD */
697 WINE_TRACE("Processing arg '%c' (in %s)\n", *p
, wine_dbgstr_w(quals
));
699 case 'P': if (negate
) paged_mode
= !paged_mode
;
700 else paged_mode
= TRUE
;
702 case 'S': if (negate
) recurse
= !recurse
;
705 case 'W': if (negate
) wide
= !wide
;
708 case 'B': if (negate
) bare
= !bare
;
711 case 'L': if (negate
) lower
= !lower
;
714 case 'X': if (negate
) shortname
= !shortname
;
715 else shortname
= TRUE
;
717 case 'Q': if (negate
) usernames
= !usernames
;
718 else usernames
= TRUE
;
720 case 'D': if (negate
) orderByCol
= !orderByCol
;
721 else orderByCol
= TRUE
;
723 case 'C': if (negate
) separator
= !separator
;
724 else separator
= TRUE
;
727 if (*p
==':') p
++; /* Skip optional : */
729 if (*p
== 'A') dirTime
= Access
;
730 else if (*p
== 'C') dirTime
= Creation
;
731 else if (*p
== 'W') dirTime
= Written
;
733 /* Support /T and /T: with no parms, default to written */
734 else if (*p
== 0x00 || *p
== '/') {
736 p
= p
- 1; /* So when step on, move to '/' */
738 SetLastError(ERROR_INVALID_PARAMETER
);
740 return errorlevel
= ERROR_INVALID_FUNCTION
;
744 if (*p
==':') p
++; /* Skip optional : */
745 while (*p
&& *p
!= '/') {
746 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p
, wine_dbgstr_w(quals
));
748 case 'N': dirOrder
= Name
; break;
749 case 'E': dirOrder
= Extension
; break;
750 case 'S': dirOrder
= Size
; break;
751 case 'D': dirOrder
= Date
; break;
752 case '-': if (*(p
+1)=='G') orderGroupDirsReverse
=TRUE
;
753 else orderReverse
= TRUE
;
755 case 'G': orderGroupDirs
= TRUE
; break;
757 SetLastError(ERROR_INVALID_PARAMETER
);
759 return errorlevel
= ERROR_INVALID_FUNCTION
;
763 p
= p
- 1; /* So when step on, move to '/' */
768 if (*p
==':') p
++; /* Skip optional : */
769 while (*p
&& *p
!= '/') {
770 BOOL anegate
= FALSE
;
773 /* Note /A: - options are 'offs' not toggles */
779 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p
, wine_dbgstr_w(quals
));
781 case 'D': mask
= FILE_ATTRIBUTE_DIRECTORY
; break;
782 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
783 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
784 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
785 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
787 SetLastError(ERROR_INVALID_PARAMETER
);
789 return errorlevel
= ERROR_INVALID_FUNCTION
;
792 /* Keep running list of bits we care about */
795 /* Mask shows what MUST be in the bits we care about */
796 if (anegate
) showattrs
= showattrs
& ~mask
;
797 else showattrs
|= mask
;
801 p
= p
- 1; /* So when step on, move to '/' */
802 WINE_TRACE("Result: showattrs %lx, bits %lx\n", showattrs
, attrsbits
);
805 SetLastError(ERROR_INVALID_PARAMETER
);
807 return errorlevel
= ERROR_INVALID_FUNCTION
;
812 /* Handle conflicting args and initialization */
813 if (bare
|| shortname
) wide
= FALSE
;
814 if (bare
) shortname
= FALSE
;
815 if (wide
) usernames
= FALSE
;
816 if (orderByCol
) wide
= TRUE
;
819 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
))
820 max_width
= consoleInfo
.dwSize
.X
;
825 WCMD_enter_paged_mode(NULL
);
833 GetCurrentDirectoryW(MAX_PATH
, cwd
);
834 lstrcatW(cwd
, L
"\\");
836 /* Loop through all args, calculating full effective directory */
840 WCHAR fullname
[MAXSTRING
];
841 WCHAR
*thisArg
= WCMD_parameter(args
, argno
++, &argN
, FALSE
, FALSE
);
842 if (argN
&& argN
[0] != '/') {
844 WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg
));
845 if (thisArg
[1] == ':' && thisArg
[2] == '\\') {
846 lstrcpyW(fullname
, thisArg
);
847 } else if (thisArg
[1] == ':' && thisArg
[2] != '\\') {
849 wsprintfW(envvar
, L
"=%c:", thisArg
[0]);
850 if (!GetEnvironmentVariableW(envvar
, fullname
, MAX_PATH
)) {
851 wsprintfW(fullname
, L
"%c:", thisArg
[0]);
853 lstrcatW(fullname
, L
"\\");
854 lstrcatW(fullname
, &thisArg
[2]);
855 } else if (thisArg
[0] == '\\') {
856 memcpy(fullname
, cwd
, 2 * sizeof(WCHAR
));
857 lstrcpyW(fullname
+2, thisArg
);
859 lstrcpyW(fullname
, cwd
);
860 lstrcatW(fullname
, thisArg
);
862 WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname
));
864 if (!WCMD_get_fullpath(fullname
, ARRAY_SIZE(path
), path
, NULL
)) continue;
867 * If the path supplied does not include a wildcard, and the endpoint of the
868 * path references a directory, we need to list the *contents* of that
869 * directory not the directory file itself.
871 if ((wcschr(path
, '*') == NULL
) && (wcschr(path
, '%') == NULL
)) {
872 status
= GetFileAttributesW(path
);
873 if ((status
!= INVALID_FILE_ATTRIBUTES
) && (status
& FILE_ATTRIBUTE_DIRECTORY
)) {
874 if (!ends_with_backslash(path
)) lstrcatW(path
, L
"\\");
875 lstrcatW(path
, L
"*");
878 /* Special case wildcard search with no extension (ie parameters ending in '.') as
879 GetFullPathName strips off the additional '.' */
880 if (fullname
[lstrlenW(fullname
)-1] == '.') lstrcatW(path
, L
".");
883 WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path
));
884 thisEntry
= xalloc(sizeof(DIRECTORY_STACK
));
885 if (fullParms
== NULL
) fullParms
= thisEntry
;
886 if (prevEntry
!= NULL
) prevEntry
->next
= thisEntry
;
887 prevEntry
= thisEntry
;
888 thisEntry
->next
= NULL
;
890 /* Split into components */
891 _wsplitpath(path
, drive
, dir
, fname
, ext
);
892 WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
893 wine_dbgstr_w(drive
), wine_dbgstr_w(dir
),
894 wine_dbgstr_w(fname
), wine_dbgstr_w(ext
));
896 thisEntry
->dirName
= xalloc(sizeof(WCHAR
) * (wcslen(drive
) + wcslen(dir
) + 1));
897 lstrcpyW(thisEntry
->dirName
, drive
);
898 lstrcatW(thisEntry
->dirName
, dir
);
900 thisEntry
->fileName
= xalloc(sizeof(WCHAR
) * (wcslen(fname
) + wcslen(ext
) + 1));
901 lstrcpyW(thisEntry
->fileName
, fname
);
902 lstrcatW(thisEntry
->fileName
, ext
);
907 /* If just 'dir' entered, a '*' parameter is assumed */
908 if (fullParms
== NULL
) {
909 WINE_TRACE("Inserting default '*'\n");
910 fullParms
= xalloc(sizeof(DIRECTORY_STACK
));
911 fullParms
->next
= NULL
;
912 fullParms
->dirName
= xstrdupW(cwd
);
913 fullParms
->fileName
= xstrdupW(L
"*");
918 thisEntry
= fullParms
;
921 while (thisEntry
!= NULL
) {
923 /* Output disk free (trailer) and volume information (header) if the drive
925 if (lastDrive
!= towupper(thisEntry
->dirName
[0])) {
927 /* Trailer Information */
928 if (lastDrive
!= '?') {
930 WCMD_dir_trailer(prevEntry
->dirName
);
931 byte_total
= file_total
= dir_total
= 0;
934 lastDrive
= towupper(thisEntry
->dirName
[0]);
938 WINE_TRACE("Writing volume for '%c:'\n", thisEntry
->dirName
[0]);
939 drive
[0] = thisEntry
->dirName
[0];
940 drive
[1] = thisEntry
->dirName
[1];
944 if (!WCMD_print_volume_information(drive
)) {
945 errorlevel
= ERROR_INVALID_FUNCTION
;
950 if (!bare
) WCMD_output_asis (L
"\n\n");
953 /* Clear any errors from previous invocations, and process it */
954 errorlevel
= NO_ERROR
;
955 prevEntry
= thisEntry
;
956 thisEntry
= WCMD_list_directory (thisEntry
, 0);
963 /* Trailer Information */
965 WCMD_dir_trailer(prevEntry
->dirName
);
968 if (num_empty
&& !num_with_data
)
969 errorlevel
= ERROR_INVALID_FUNCTION
;
971 if (paged_mode
) WCMD_leave_paged_mode();
973 /* Free storage allocated for parms */
974 while (fullParms
!= NULL
) {
975 prevEntry
= fullParms
;
976 fullParms
= prevEntry
->next
;
977 free(prevEntry
->dirName
);
978 free(prevEntry
->fileName
);