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 static int file_total
, dir_total
, max_width
;
45 static ULONGLONG byte_total
;
46 static DISPLAYTIME dirTime
;
47 static DISPLAYORDER dirOrder
;
48 static BOOL orderReverse
, orderGroupDirs
, orderGroupDirsReverse
, orderByCol
;
49 static BOOL paged_mode
, recurse
, wide
, bare
, lower
, shortname
, usernames
, separator
;
50 static ULONG showattrs
, attrsbits
;
52 /*****************************************************************************
55 * Reverse a WCHARacter string in-place (strrev() is not available under unixen :-( ).
57 static WCHAR
* WCMD_strrev (WCHAR
*buff
) {
63 for (i
=0; i
<r
/2; i
++) {
65 buff
[i
] = buff
[r
-i
-1];
71 /*****************************************************************************
74 * Convert a 64-bit number into a WCHARacter string, with commas every three digits.
75 * Result is returned in a static string overwritten with each call.
76 * FIXME: There must be a better algorithm!
78 static WCHAR
* WCMD_filesize64 (ULONGLONG n
) {
83 static WCHAR buff
[32];
88 if (separator
&& ((++i
)%3 == 1)) *p
++ = ',';
99 /*****************************************************************************
102 * Sort based on the /O options supplied on the command line
104 static int WCMD_dir_sort (const void *a
, const void *b
)
106 const WIN32_FIND_DATAW
*filea
= (const WIN32_FIND_DATAW
*)a
;
107 const WIN32_FIND_DATAW
*fileb
= (const WIN32_FIND_DATAW
*)b
;
110 /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
111 requested sort order for the directory components */
112 if (orderGroupDirs
&&
113 ((filea
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) ||
114 (fileb
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)))
116 BOOL aDir
= filea
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
;
117 if (aDir
) result
= -1;
119 if (orderGroupDirsReverse
) result
= -result
;
123 } else if (dirOrder
== Name
) {
124 result
= lstrcmpiW(filea
->cFileName
, fileb
->cFileName
);
127 } else if (dirOrder
== Size
) {
128 ULONG64 sizea
= (((ULONG64
)filea
->nFileSizeHigh
) << 32) + filea
->nFileSizeLow
;
129 ULONG64 sizeb
= (((ULONG64
)fileb
->nFileSizeHigh
) << 32) + fileb
->nFileSizeLow
;
130 if( sizea
< sizeb
) result
= -1;
131 else if( sizea
== sizeb
) result
= 0;
134 /* Order by Date: (Takes into account which date (/T option) */
135 } else if (dirOrder
== Date
) {
138 ULONG64 timea
, timeb
;
140 if (dirTime
== Written
) {
141 ft
= &filea
->ftLastWriteTime
;
142 timea
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
143 ft
= &fileb
->ftLastWriteTime
;
144 timeb
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
145 } else if (dirTime
== Access
) {
146 ft
= &filea
->ftLastAccessTime
;
147 timea
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
148 ft
= &fileb
->ftLastAccessTime
;
149 timeb
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
151 ft
= &filea
->ftCreationTime
;
152 timea
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
153 ft
= &fileb
->ftCreationTime
;
154 timeb
= (((ULONG64
)ft
->dwHighDateTime
) << 32) + ft
->dwLowDateTime
;
156 if( timea
< timeb
) result
= -1;
157 else if( timea
== timeb
) result
= 0;
160 /* Order by Extension: (Takes into account which date (/T option) */
161 } else if (dirOrder
== Extension
) {
164 WCHAR fname
[MAX_PATH
];
165 WCHAR extA
[MAX_PATH
];
166 WCHAR extB
[MAX_PATH
];
168 /* Split into components */
169 WCMD_splitpath(filea
->cFileName
, drive
, dir
, fname
, extA
);
170 WCMD_splitpath(fileb
->cFileName
, drive
, dir
, fname
, extB
);
171 result
= lstrcmpiW(extA
, extB
);
174 if (orderReverse
) result
= -result
;
178 /*****************************************************************************
181 * Reverse a WCHARacter string in-place (strrev() is not available under unixen :-( ).
183 static void WCMD_getfileowner(WCHAR
*filename
, WCHAR
*owner
, int ownerlen
) {
185 ULONG sizeNeeded
= 0;
187 WCHAR name
[MAXSTRING
];
188 WCHAR domain
[MAXSTRING
];
190 /* In case of error, return empty string */
193 /* Find out how much space we need for the owner security descriptor */
194 GetFileSecurityW(filename
, OWNER_SECURITY_INFORMATION
, 0, 0, &sizeNeeded
);
197 if(rc
== ERROR_INSUFFICIENT_BUFFER
&& sizeNeeded
> 0) {
201 BOOL defaulted
= FALSE
;
202 ULONG nameLen
= MAXSTRING
;
203 ULONG domainLen
= MAXSTRING
;
204 SID_NAME_USE nameuse
;
206 secBuffer
= heap_alloc(sizeNeeded
* sizeof(BYTE
));
208 /* Get the owners security descriptor */
209 if(!GetFileSecurityW(filename
, OWNER_SECURITY_INFORMATION
, secBuffer
,
210 sizeNeeded
, &sizeNeeded
)) {
211 heap_free(secBuffer
);
215 /* Get the SID from the SD */
216 if(!GetSecurityDescriptorOwner(secBuffer
, &pSID
, &defaulted
)) {
217 heap_free(secBuffer
);
221 /* Convert to a username */
222 if (LookupAccountSidW(NULL
, pSID
, name
, &nameLen
, domain
, &domainLen
, &nameuse
)) {
223 static const WCHAR fmt
[] = {'%','s','%','c','%','s','\0'};
224 snprintfW(owner
, ownerlen
, fmt
, domain
, '\\', name
);
226 heap_free(secBuffer
);
231 /*****************************************************************************
232 * WCMD_list_directory
234 * List a single file directory. This function (and those below it) can be called
235 * recursively when the /S switch is used.
237 * FIXME: Assumes 24-line display for the /P qualifier.
240 static DIRECTORY_STACK
*WCMD_list_directory (DIRECTORY_STACK
*inputparms
, int level
) {
242 WCHAR string
[1024], datestring
[32], timestring
[32];
243 WCHAR real_path
[MAX_PATH
];
244 WIN32_FIND_DATAW
*fd
;
248 int dir_count
, file_count
, entry_count
, i
, widest
, cur_width
, tmp_width
;
249 int numCols
, numRows
;
251 ULARGE_INTEGER byte_count
, file_size
;
252 DIRECTORY_STACK
*parms
;
253 int concurrentDirs
= 0;
254 BOOL done_header
= FALSE
;
256 static const WCHAR fmtDir
[] = {'%','1','!','1','0','s','!',' ',' ','%','2','!','8','s','!',' ',' ',
257 '<','D','I','R','>',' ',' ',' ',' ',' ',' ',' ',' ',' ','\0'};
258 static const WCHAR fmtFile
[] = {'%','1','!','1','0','s','!',' ',' ','%','2','!','8','s','!',' ',' ',
259 ' ',' ','%','3','!','1','0','s','!',' ',' ','\0'};
260 static const WCHAR fmt2
[] = {'%','1','!','-','1','3','s','!','\0'};
261 static const WCHAR fmt3
[] = {'%','1','!','-','2','3','s','!','\0'};
262 static const WCHAR fmt4
[] = {'%','1','\0'};
263 static const WCHAR fmt5
[] = {'%','1','%','2','\0'};
268 byte_count
.QuadPart
= 0;
272 /* Loop merging all the files from consecutive parms which relate to the
273 same directory. Note issuing a directory header with no contents
274 mirrors what windows does */
276 fd
= heap_alloc(sizeof(WIN32_FIND_DATAW
));
277 while (parms
&& strcmpW(inputparms
->dirName
, parms
->dirName
) == 0) {
280 /* Work out the full path + filename */
281 strcpyW(real_path
, parms
->dirName
);
282 strcatW(real_path
, parms
->fileName
);
284 /* Load all files into an in memory structure */
285 WINE_TRACE("Looking for matches to '%s'\n", wine_dbgstr_w(real_path
));
286 hff
= FindFirstFileW(real_path
, &fd
[entry_count
]);
287 if (hff
!= INVALID_HANDLE_VALUE
) {
289 /* Skip any which are filtered out by attribute */
290 if ((fd
[entry_count
].dwFileAttributes
& attrsbits
) != showattrs
) continue;
294 /* Keep running track of longest filename for wide output */
295 if (wide
|| orderByCol
) {
296 int tmpLen
= strlenW(fd
[entry_count
-1].cFileName
) + 3;
297 if (fd
[entry_count
-1].dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) tmpLen
= tmpLen
+ 2;
298 if (tmpLen
> widest
) widest
= tmpLen
;
301 fd
= HeapReAlloc(GetProcessHeap(),0,fd
,(entry_count
+1)*sizeof(WIN32_FIND_DATAW
));
304 WINE_ERR("Out of memory\n");
308 } while (FindNextFileW(hff
, &fd
[entry_count
]) != 0);
312 /* Work out the actual current directory name without a trailing \ */
313 strcpyW(real_path
, parms
->dirName
);
314 real_path
[strlenW(parms
->dirName
)-1] = 0x00;
316 /* Output the results */
318 if (level
!= 0 && (entry_count
> 0)) WCMD_output_asis (newlineW
);
319 if (!recurse
|| ((entry_count
> 0) && done_header
==FALSE
)) {
320 static const WCHAR headerW
[] = {'D','i','r','e','c','t','o','r','y',' ','o','f',
321 ' ','%','1','\n','\n','\0'};
322 WCMD_output (headerW
, real_path
);
327 /* Move to next parm */
331 /* Handle case where everything is filtered out */
332 if (entry_count
> 0) {
334 /* Sort the list of files */
335 qsort (fd
, entry_count
, sizeof(WIN32_FIND_DATAW
), WCMD_dir_sort
);
337 /* Work out the number of columns */
338 WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count
, max_width
, widest
);
339 if (wide
|| orderByCol
) {
340 numCols
= max(1, (int)max_width
/ widest
);
341 numRows
= entry_count
/ numCols
;
342 if (entry_count
% numCols
) numRows
++;
345 numRows
= entry_count
;
347 WINE_TRACE("cols=%d, rows=%d\n", numCols
, numRows
);
349 for (rows
=0; rows
<numRows
; rows
++) {
350 BOOL addNewLine
= TRUE
;
351 for (cols
=0; cols
<numCols
; cols
++) {
354 /* Work out the index of the entry being pointed to */
356 i
= (cols
* numRows
) + rows
;
357 if (i
>= entry_count
) continue;
359 i
= (rows
* numCols
) + cols
;
360 if (i
>= entry_count
) continue;
363 /* /L convers all names to lower case */
365 WCHAR
*p
= fd
[i
].cFileName
;
366 while ( (*p
= tolower(*p
)) ) ++p
;
369 /* /Q gets file ownership information */
371 strcpyW (string
, inputparms
->dirName
);
372 strcatW (string
, fd
[i
].cFileName
);
373 WCMD_getfileowner(string
, username
, sizeof(username
)/sizeof(WCHAR
));
376 if (dirTime
== Written
) {
377 FileTimeToLocalFileTime (&fd
[i
].ftLastWriteTime
, &ft
);
378 } else if (dirTime
== Access
) {
379 FileTimeToLocalFileTime (&fd
[i
].ftLastAccessTime
, &ft
);
381 FileTimeToLocalFileTime (&fd
[i
].ftCreationTime
, &ft
);
383 FileTimeToSystemTime (&ft
, &st
);
384 GetDateFormatW(0, DATE_SHORTDATE
, &st
, NULL
, datestring
,
385 sizeof(datestring
)/sizeof(WCHAR
));
386 GetTimeFormatW(0, TIME_NOSECONDS
, &st
,
387 NULL
, timestring
, sizeof(timestring
)/sizeof(WCHAR
));
391 tmp_width
= cur_width
;
392 if (fd
[i
].dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
393 static const WCHAR fmt
[] = {'[','%','1',']','\0'};
394 WCMD_output (fmt
, fd
[i
].cFileName
);
396 tmp_width
= tmp_width
+ strlenW(fd
[i
].cFileName
) + 2;
398 static const WCHAR fmt
[] = {'%','1','\0'};
399 WCMD_output (fmt
, fd
[i
].cFileName
);
400 tmp_width
= tmp_width
+ strlenW(fd
[i
].cFileName
) ;
402 file_size
.u
.LowPart
= fd
[i
].nFileSizeLow
;
403 file_size
.u
.HighPart
= fd
[i
].nFileSizeHigh
;
404 byte_count
.QuadPart
+= file_size
.QuadPart
;
406 cur_width
= cur_width
+ widest
;
408 if ((cur_width
+ widest
) > max_width
) {
411 static const WCHAR padfmt
[] = {'%','1','!','*','s','!','\0'};
412 WCMD_output(padfmt
, cur_width
- tmp_width
, nullW
);
415 } else if (fd
[i
].dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
419 WCMD_output (fmtDir
, datestring
, timestring
);
420 if (shortname
) WCMD_output (fmt2
, fd
[i
].cAlternateFileName
);
421 if (usernames
) WCMD_output (fmt3
, username
);
422 WCMD_output(fmt4
,fd
[i
].cFileName
);
424 if (!((strcmpW(fd
[i
].cFileName
, dotW
) == 0) ||
425 (strcmpW(fd
[i
].cFileName
, dotdotW
) == 0))) {
426 WCMD_output (fmt5
, recurse
?inputparms
->dirName
:nullW
, fd
[i
].cFileName
);
434 file_size
.u
.LowPart
= fd
[i
].nFileSizeLow
;
435 file_size
.u
.HighPart
= fd
[i
].nFileSizeHigh
;
436 byte_count
.QuadPart
+= file_size
.QuadPart
;
438 WCMD_output (fmtFile
, datestring
, timestring
,
439 WCMD_filesize64(file_size
.QuadPart
));
440 if (shortname
) WCMD_output (fmt2
, fd
[i
].cAlternateFileName
);
441 if (usernames
) WCMD_output (fmt3
, username
);
442 WCMD_output(fmt4
,fd
[i
].cFileName
);
444 WCMD_output (fmt5
, recurse
?inputparms
->dirName
:nullW
, fd
[i
].cFileName
);
448 if (addNewLine
) WCMD_output_asis (newlineW
);
453 if (file_count
== 1) {
454 static const WCHAR fmt
[] = {' ',' ',' ',' ',' ',' ',' ','1',' ','f','i','l','e',' ',
455 '%','1','!','2','5','s','!',' ','b','y','t','e','s','\n','\0'};
456 WCMD_output (fmt
, WCMD_filesize64 (byte_count
.QuadPart
));
459 static const WCHAR fmt
[] = {'%','1','!','8','d','!',' ','f','i','l','e','s',' ','%','2','!','2','4','s','!',
460 ' ','b','y','t','e','s','\n','\0'};
461 WCMD_output (fmt
, file_count
, WCMD_filesize64 (byte_count
.QuadPart
));
464 byte_total
= byte_total
+ byte_count
.QuadPart
;
465 file_total
= file_total
+ file_count
;
466 dir_total
= dir_total
+ dir_count
;
468 if (!bare
&& !recurse
) {
469 if (dir_count
== 1) {
470 static const WCHAR fmt
[] = {'%','1','!','8','d','!',' ','d','i','r','e','c','t','o','r','y',
471 ' ',' ',' ',' ',' ',' ',' ',' ',' ','\0'};
472 WCMD_output (fmt
, 1);
474 static const WCHAR fmt
[] = {'%','1','!','8','d','!',' ','d','i','r','e','c','t','o','r','i',
476 WCMD_output (fmt
, dir_count
);
482 /* When recursing, look in all subdirectories for matches */
484 DIRECTORY_STACK
*dirStack
= NULL
;
485 DIRECTORY_STACK
*lastEntry
= NULL
;
486 WIN32_FIND_DATAW finddata
;
488 /* Build path to search */
489 strcpyW(string
, inputparms
->dirName
);
490 strcatW(string
, starW
);
492 WINE_TRACE("Recursive, looking for '%s'\n", wine_dbgstr_w(string
));
493 hff
= FindFirstFileW(string
, &finddata
);
494 if (hff
!= INVALID_HANDLE_VALUE
) {
496 if ((finddata
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
497 (strcmpW(finddata
.cFileName
, dotdotW
) != 0) &&
498 (strcmpW(finddata
.cFileName
, dotW
) != 0)) {
500 DIRECTORY_STACK
*thisDir
;
501 int dirsToCopy
= concurrentDirs
;
503 /* Loop creating list of subdirs for all concurrent entries */
505 while (dirsToCopy
> 0) {
508 /* Work out search parameter in sub dir */
509 strcpyW (string
, inputparms
->dirName
);
510 strcatW (string
, finddata
.cFileName
);
511 strcatW (string
, slashW
);
512 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(string
));
514 /* Allocate memory, add to list */
515 thisDir
= heap_alloc(sizeof(DIRECTORY_STACK
));
516 if (dirStack
== NULL
) dirStack
= thisDir
;
517 if (lastEntry
!= NULL
) lastEntry
->next
= thisDir
;
519 thisDir
->next
= NULL
;
520 thisDir
->dirName
= heap_strdupW(string
);
521 thisDir
->fileName
= heap_strdupW(parms
->fileName
);
525 } while (FindNextFileW(hff
, &finddata
) != 0);
528 while (dirStack
!= NULL
) {
529 DIRECTORY_STACK
*thisDir
= dirStack
;
530 dirStack
= WCMD_list_directory (thisDir
, 1);
531 while (thisDir
!= dirStack
) {
532 DIRECTORY_STACK
*tempDir
= thisDir
->next
;
533 heap_free(thisDir
->dirName
);
534 heap_free(thisDir
->fileName
);
542 /* Handle case where everything is filtered out */
543 if ((file_total
+ dir_total
== 0) && (level
== 0)) {
544 SetLastError (ERROR_FILE_NOT_FOUND
);
552 /*****************************************************************************
555 * Print out the trailer for the supplied drive letter
557 static void WCMD_dir_trailer(WCHAR drive
) {
558 ULARGE_INTEGER avail
, total
, freebytes
;
560 WCHAR driveName
[] = {'c',':','\\','\0'};
562 driveName
[0] = drive
;
563 status
= GetDiskFreeSpaceExW(driveName
, &avail
, &total
, &freebytes
);
564 WINE_TRACE("Writing trailer for '%s' gave %d(%d)\n", wine_dbgstr_w(driveName
),
565 status
, GetLastError());
567 if (errorlevel
==0 && !bare
) {
569 static const WCHAR fmt1
[] = {'\n',' ',' ',' ',' ',' ','T','o','t','a','l',' ','f','i','l','e','s',
570 ' ','l','i','s','t','e','d',':','\n','%','1','!','8','d','!',' ','f','i','l','e',
571 's','%','2','!','2','5','s','!',' ','b','y','t','e','s','\n','\0'};
572 static const WCHAR fmt2
[] = {'%','1','!','8','d','!',' ','d','i','r','e','c','t','o','r','i','e','s',' ','%',
573 '2','!','1','8','s','!',' ','b','y','t','e','s',' ','f','r','e','e','\n','\n',
575 WCMD_output (fmt1
, file_total
, WCMD_filesize64 (byte_total
));
576 WCMD_output (fmt2
, dir_total
, WCMD_filesize64 (freebytes
.QuadPart
));
578 static const WCHAR fmt
[] = {' ','%','1','!','1','8','s','!',' ','b','y','t','e','s',' ','f','r','e','e',
580 WCMD_output (fmt
, WCMD_filesize64 (freebytes
.QuadPart
));
585 /*****************************************************************************
588 * List a file directory.
592 void WCMD_directory (WCHAR
*args
)
594 WCHAR path
[MAX_PATH
], cwd
[MAX_PATH
];
596 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
598 WCHAR string
[MAXSTRING
];
602 BOOL trailerReqd
= FALSE
;
603 DIRECTORY_STACK
*fullParms
= NULL
;
604 DIRECTORY_STACK
*prevEntry
= NULL
;
605 DIRECTORY_STACK
*thisEntry
= NULL
;
608 WCHAR fname
[MAX_PATH
];
610 static const WCHAR dircmdW
[] = {'D','I','R','C','M','D','\0'};
614 /* Prefill quals with (uppercased) DIRCMD env var */
615 if (GetEnvironmentVariableW(dircmdW
, string
, sizeof(string
)/sizeof(WCHAR
))) {
617 while ( (*p
= toupper(*p
)) ) ++p
;
618 strcatW(string
,quals
);
619 strcpyW(quals
, string
);
623 file_total
= dir_total
= 0;
625 /* Initialize all flags to their defaults as if no DIRCMD or quals */
637 orderReverse
= FALSE
;
638 orderGroupDirs
= FALSE
;
639 orderGroupDirsReverse
= FALSE
;
641 attrsbits
= FILE_ATTRIBUTE_HIDDEN
| FILE_ATTRIBUTE_SYSTEM
;
643 /* Handle args - Loop through so right most is the effective one */
644 /* Note: /- appears to be a negate rather than an off, eg. dir
645 /-W is wide, or dir /w /-w /-w is also wide */
647 while (*p
&& (*p
=='/' || *p
==' ')) {
649 if (*p
++==' ') continue; /* Skip / and blanks introduced through DIRCMD */
656 WINE_TRACE("Processing arg '%c' (in %s)\n", *p
, wine_dbgstr_w(quals
));
658 case 'P': if (negate
) paged_mode
= !paged_mode
;
659 else paged_mode
= TRUE
;
661 case 'S': if (negate
) recurse
= !recurse
;
664 case 'W': if (negate
) wide
= !wide
;
667 case 'B': if (negate
) bare
= !bare
;
670 case 'L': if (negate
) lower
= !lower
;
673 case 'X': if (negate
) shortname
= !shortname
;
674 else shortname
= TRUE
;
676 case 'Q': if (negate
) usernames
= !usernames
;
677 else usernames
= TRUE
;
679 case 'D': if (negate
) orderByCol
= !orderByCol
;
680 else orderByCol
= TRUE
;
682 case 'C': if (negate
) separator
= !separator
;
683 else separator
= TRUE
;
686 if (*p
==':') p
++; /* Skip optional : */
688 if (*p
== 'A') dirTime
= Access
;
689 else if (*p
== 'C') dirTime
= Creation
;
690 else if (*p
== 'W') dirTime
= Written
;
692 /* Support /T and /T: with no parms, default to written */
693 else if (*p
== 0x00 || *p
== '/') {
695 p
= p
- 1; /* So when step on, move to '/' */
697 SetLastError(ERROR_INVALID_PARAMETER
);
704 if (*p
==':') p
++; /* Skip optional : */
705 while (*p
&& *p
!= '/') {
706 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p
, wine_dbgstr_w(quals
));
708 case 'N': dirOrder
= Name
; break;
709 case 'E': dirOrder
= Extension
; break;
710 case 'S': dirOrder
= Size
; break;
711 case 'D': dirOrder
= Date
; break;
712 case '-': if (*(p
+1)=='G') orderGroupDirsReverse
=TRUE
;
713 else orderReverse
= TRUE
;
715 case 'G': orderGroupDirs
= TRUE
; break;
717 SetLastError(ERROR_INVALID_PARAMETER
);
724 p
= p
- 1; /* So when step on, move to '/' */
729 if (*p
==':') p
++; /* Skip optional : */
730 while (*p
&& *p
!= '/') {
731 BOOL anegate
= FALSE
;
734 /* Note /A: - options are 'offs' not toggles */
740 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p
, wine_dbgstr_w(quals
));
742 case 'D': mask
= FILE_ATTRIBUTE_DIRECTORY
; break;
743 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
744 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
745 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
746 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
748 SetLastError(ERROR_INVALID_PARAMETER
);
754 /* Keep running list of bits we care about */
757 /* Mask shows what MUST be in the bits we care about */
758 if (anegate
) showattrs
= showattrs
& ~mask
;
759 else showattrs
|= mask
;
763 p
= p
- 1; /* So when step on, move to '/' */
764 WINE_TRACE("Result: showattrs %x, bits %x\n", showattrs
, attrsbits
);
767 SetLastError(ERROR_INVALID_PARAMETER
);
775 /* Handle conflicting args and initialization */
776 if (bare
|| shortname
) wide
= FALSE
;
777 if (bare
) shortname
= FALSE
;
778 if (wide
) usernames
= FALSE
;
779 if (orderByCol
) wide
= TRUE
;
782 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
))
783 max_width
= consoleInfo
.dwSize
.X
;
788 WCMD_enter_paged_mode(NULL
);
793 GetCurrentDirectoryW(MAX_PATH
, cwd
);
794 strcatW(cwd
, slashW
);
796 /* Loop through all args, calculating full effective directory */
800 WCHAR fullname
[MAXSTRING
];
801 WCHAR
*thisArg
= WCMD_parameter(args
, argno
++, &argN
, FALSE
, FALSE
);
802 if (argN
&& argN
[0] != '/') {
804 WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg
));
805 if (thisArg
[1] == ':' && thisArg
[2] == '\\') {
806 strcpyW(fullname
, thisArg
);
807 } else if (thisArg
[1] == ':' && thisArg
[2] != '\\') {
809 static const WCHAR envFmt
[] = {'=','%','c',':','\0'};
810 wsprintfW(envvar
, envFmt
, thisArg
[0]);
811 if (!GetEnvironmentVariableW(envvar
, fullname
, MAX_PATH
)) {
812 static const WCHAR noEnvFmt
[] = {'%','c',':','\0'};
813 wsprintfW(fullname
, noEnvFmt
, thisArg
[0]);
815 strcatW(fullname
, slashW
);
816 strcatW(fullname
, &thisArg
[2]);
817 } else if (thisArg
[0] == '\\') {
818 memcpy(fullname
, cwd
, 2 * sizeof(WCHAR
));
819 strcpyW(fullname
+2, thisArg
);
821 strcpyW(fullname
, cwd
);
822 strcatW(fullname
, thisArg
);
824 WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname
));
826 status
= GetFullPathNameW(fullname
, sizeof(path
)/sizeof(WCHAR
), path
, NULL
);
829 * If the path supplied does not include a wildcard, and the endpoint of the
830 * path references a directory, we need to list the *contents* of that
831 * directory not the directory file itself.
833 if ((strchrW(path
, '*') == NULL
) && (strchrW(path
, '%') == NULL
)) {
834 status
= GetFileAttributesW(path
);
835 if ((status
!= INVALID_FILE_ATTRIBUTES
) && (status
& FILE_ATTRIBUTE_DIRECTORY
)) {
836 if (!ends_with_backslash( path
)) strcatW( path
, slashW
);
837 strcatW (path
, starW
);
840 /* Special case wildcard search with no extension (ie parameters ending in '.') as
841 GetFullPathName strips off the additional '.' */
842 if (fullname
[strlenW(fullname
)-1] == '.') strcatW(path
, dotW
);
845 WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path
));
846 thisEntry
= heap_alloc(sizeof(DIRECTORY_STACK
));
847 if (fullParms
== NULL
) fullParms
= thisEntry
;
848 if (prevEntry
!= NULL
) prevEntry
->next
= thisEntry
;
849 prevEntry
= thisEntry
;
850 thisEntry
->next
= NULL
;
852 /* Split into components */
853 WCMD_splitpath(path
, drive
, dir
, fname
, ext
);
854 WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
855 wine_dbgstr_w(drive
), wine_dbgstr_w(dir
),
856 wine_dbgstr_w(fname
), wine_dbgstr_w(ext
));
858 thisEntry
->dirName
= heap_alloc(sizeof(WCHAR
) * (strlenW(drive
)+strlenW(dir
)+1));
859 strcpyW(thisEntry
->dirName
, drive
);
860 strcatW(thisEntry
->dirName
, dir
);
862 thisEntry
->fileName
= heap_alloc(sizeof(WCHAR
) * (strlenW(fname
)+strlenW(ext
)+1));
863 strcpyW(thisEntry
->fileName
, fname
);
864 strcatW(thisEntry
->fileName
, ext
);
869 /* If just 'dir' entered, a '*' parameter is assumed */
870 if (fullParms
== NULL
) {
871 WINE_TRACE("Inserting default '*'\n");
872 fullParms
= heap_alloc(sizeof(DIRECTORY_STACK
));
873 fullParms
->next
= NULL
;
874 fullParms
->dirName
= heap_strdupW(cwd
);
875 fullParms
->fileName
= heap_strdupW(starW
);
880 thisEntry
= fullParms
;
883 while (thisEntry
!= NULL
) {
885 /* Output disk free (trailer) and volume information (header) if the drive
887 if (lastDrive
!= toupper(thisEntry
->dirName
[0])) {
889 /* Trailer Information */
890 if (lastDrive
!= '?') {
892 WCMD_dir_trailer(prevEntry
->dirName
[0]);
895 lastDrive
= toupper(thisEntry
->dirName
[0]);
900 WINE_TRACE("Writing volume for '%c:'\n", thisEntry
->dirName
[0]);
901 memcpy(drive
, thisEntry
->dirName
, 2 * sizeof(WCHAR
));
903 status
= WCMD_volume (0, drive
);
911 static const WCHAR newLine2
[] = {'\n','\n','\0'};
912 if (!bare
) WCMD_output_asis (newLine2
);
915 /* Clear any errors from previous invocations, and process it */
917 prevEntry
= thisEntry
;
918 thisEntry
= WCMD_list_directory (thisEntry
, 0);
921 /* Trailer Information */
923 WCMD_dir_trailer(prevEntry
->dirName
[0]);
927 if (paged_mode
) WCMD_leave_paged_mode();
929 /* Free storage allocated for parms */
930 while (fullParms
!= NULL
) {
931 prevEntry
= fullParms
;
932 fullParms
= prevEntry
->next
;
933 heap_free(prevEntry
->dirName
);
934 heap_free(prevEntry
->fileName
);
935 heap_free(prevEntry
);