cmd: Reset totals after trailer output.
[wine.git] / programs / cmd / directory.c
blob709bbb52287870d6fb5a997c7f90018c58087d10
1 /*
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
24 #include "wcmd.h"
25 #include "wine/debug.h"
27 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
29 typedef enum _DISPLAYTIME
31 Creation = 0,
32 Access,
33 Written
34 } DISPLAYTIME;
36 typedef enum _DISPLAYORDER
38 Name = 0,
39 Extension,
40 Size,
41 Date
42 } 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 /*****************************************************************************
57 * WCMD_filesize64
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) {
65 ULONGLONG q;
66 unsigned int r, i;
67 WCHAR *p;
68 static WCHAR buff[32];
70 p = buff;
71 i = -3;
72 do {
73 if (separator && ((++i)%3 == 1)) *p++ = ',';
74 q = n / 10;
75 r = n - (q * 10);
76 *p++ = r + '0';
77 *p = '\0';
78 n = q;
79 } while (n != 0);
80 wcsrev(buff);
81 return buff;
84 /*****************************************************************************
85 * WCMD_dir_sort
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;
93 int result = 0;
95 /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
96 requested sort order for the directory components */
97 if (orderGroupDirs &&
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;
103 else result = 1;
104 if (orderGroupDirsReverse) result = -result;
105 return result;
107 /* Order by Name: */
108 } else if (dirOrder == Name) {
109 result = lstrcmpiW(filea->cFileName, fileb->cFileName);
111 /* Order by Size: */
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;
117 else result = 1;
119 /* Order by Date: (Takes into account which date (/T option) */
120 } else if (dirOrder == Date) {
122 const FILETIME *ft;
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;
135 } else {
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;
143 else result = 1;
145 /* Order by Extension: (Takes into account which date (/T option) */
146 } else if (dirOrder == Extension) {
147 WCHAR drive[10];
148 WCHAR dir[MAX_PATH];
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;
160 return result;
163 /*****************************************************************************
164 * WCMD_getfileowner
166 static void WCMD_getfileowner(WCHAR *filename, WCHAR *owner, int ownerlen) {
168 ULONG sizeNeeded = 0;
169 DWORD rc;
170 WCHAR name[MAXSTRING];
171 WCHAR domain[MAXSTRING];
173 /* In case of error, return empty string */
174 *owner = 0x00;
176 /* Find out how much space we need for the owner security descriptor */
177 GetFileSecurityW(filename, OWNER_SECURITY_INFORMATION, 0, 0, &sizeNeeded);
178 rc = GetLastError();
180 if(rc == ERROR_INSUFFICIENT_BUFFER && sizeNeeded > 0) {
182 LPBYTE secBuffer;
183 PSID pSID = NULL;
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)) {
194 free(secBuffer);
195 return;
198 /* Get the SID from the SD */
199 if(!GetSecurityDescriptorOwner(secBuffer, &pSID, &defaulted)) {
200 free(secBuffer);
201 return;
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);
208 free(secBuffer);
210 return;
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;
227 FILETIME ft;
228 SYSTEMTIME st;
229 HANDLE hff;
230 int dir_count, file_count, entry_count, i, widest, cur_width, tmp_width;
231 int numCols, numRows;
232 int rows, cols;
233 ULARGE_INTEGER byte_count, file_size;
234 DIRECTORY_STACK *parms;
235 int concurrentDirs = 0;
236 BOOL done_header = FALSE;
238 dir_count = 0;
239 file_count = 0;
240 entry_count = 0;
241 byte_count.QuadPart = 0;
242 widest = 0;
243 cur_width = 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 */
248 parms = inputparms;
249 fd = xalloc(sizeof(WIN32_FIND_DATAW));
250 while (parms && lstrcmpW(inputparms->dirName, parms->dirName) == 0) {
251 concurrentDirs++;
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) {
261 do {
262 /* Skip any which are filtered out by attribute */
263 if ((fd[entry_count].dwFileAttributes & attrsbits) != showattrs) continue;
265 entry_count++;
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 = realloc(fd, (entry_count + 1) * sizeof(WIN32_FIND_DATAW));
275 if (fd == NULL) {
276 FindClose (hff);
277 WINE_ERR("Out of memory\n");
278 errorlevel = 1;
279 return parms->next;
281 } while (FindNextFileW(hff, &fd[entry_count]) != 0);
282 FindClose (hff);
285 /* Work out the actual current directory name without a trailing \ */
286 lstrcpyW(real_path, parms->dirName);
287 real_path[lstrlenW(parms->dirName)-1] = 0x00;
289 /* Output the results */
290 if (!bare) {
291 if (level != 0 && (entry_count > 0)) WCMD_output_asis(L"\r\n");
292 if (!recurse || ((entry_count > 0) && done_header==FALSE)) {
293 WCMD_output (L"Directory of %1\n\n", real_path);
294 done_header = TRUE;
298 /* Move to next parm */
299 parms = parms->next;
302 /* Handle case where everything is filtered out */
303 if (entry_count > 0) {
305 /* Sort the list of files */
306 qsort (fd, entry_count, sizeof(WIN32_FIND_DATAW), WCMD_dir_sort);
308 /* Work out the number of columns */
309 WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count, max_width, widest);
310 if (wide || orderByCol) {
311 numCols = max(1, max_width / widest);
312 numRows = entry_count / numCols;
313 if (entry_count % numCols) numRows++;
314 } else {
315 numCols = 1;
316 numRows = entry_count;
318 WINE_TRACE("cols=%d, rows=%d\n", numCols, numRows);
320 for (rows=0; rows<numRows; rows++) {
321 BOOL addNewLine = TRUE;
322 for (cols=0; cols<numCols; cols++) {
323 WCHAR username[24];
325 /* Work out the index of the entry being pointed to */
326 if (orderByCol) {
327 i = (cols * numRows) + rows;
328 if (i >= entry_count) continue;
329 } else {
330 i = (rows * numCols) + cols;
331 if (i >= entry_count) continue;
334 /* /L convers all names to lower case */
335 if (lower) wcslwr( fd[i].cFileName );
337 /* /Q gets file ownership information */
338 if (usernames) {
339 lstrcpyW (string, inputparms->dirName);
340 lstrcatW (string, fd[i].cFileName);
341 WCMD_getfileowner(string, username, ARRAY_SIZE(username));
344 if (dirTime == Written) {
345 FileTimeToLocalFileTime (&fd[i].ftLastWriteTime, &ft);
346 } else if (dirTime == Access) {
347 FileTimeToLocalFileTime (&fd[i].ftLastAccessTime, &ft);
348 } else {
349 FileTimeToLocalFileTime (&fd[i].ftCreationTime, &ft);
351 FileTimeToSystemTime (&ft, &st);
352 GetDateFormatW(LOCALE_USER_DEFAULT, 0, &st, date_format,
353 datestring, ARRAY_SIZE(datestring));
354 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, time_format,
355 timestring, ARRAY_SIZE(timestring));
357 if (wide) {
359 tmp_width = cur_width;
360 if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
361 WCMD_output (L"[%1]", fd[i].cFileName);
362 dir_count++;
363 tmp_width = tmp_width + lstrlenW(fd[i].cFileName) + 2;
364 } else {
365 WCMD_output (L"%1", fd[i].cFileName);
366 tmp_width = tmp_width + lstrlenW(fd[i].cFileName) ;
367 file_count++;
368 file_size.u.LowPart = fd[i].nFileSizeLow;
369 file_size.u.HighPart = fd[i].nFileSizeHigh;
370 byte_count.QuadPart += file_size.QuadPart;
372 cur_width = cur_width + widest;
374 if ((cur_width + widest) > max_width) {
375 cur_width = 0;
376 } else {
377 WCMD_output(L"%1!*s!", cur_width - tmp_width, L"");
380 } else if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
381 dir_count++;
383 if (!bare) {
384 WCMD_output (L"%1 %2 <DIR> ", datestring, timestring);
385 if (shortname) WCMD_output(L"%1!-13s!", fd[i].cAlternateFileName);
386 if (usernames) WCMD_output(L"%1!-23s!", username);
387 WCMD_output(L"%1",fd[i].cFileName);
388 } else {
389 if (!((lstrcmpW(fd[i].cFileName, L".") == 0) ||
390 (lstrcmpW(fd[i].cFileName, L"..") == 0))) {
391 WCMD_output(L"%1%2", recurse ? inputparms->dirName : L"", fd[i].cFileName);
392 } else {
393 addNewLine = FALSE;
397 else {
398 file_count++;
399 file_size.u.LowPart = fd[i].nFileSizeLow;
400 file_size.u.HighPart = fd[i].nFileSizeHigh;
401 byte_count.QuadPart += file_size.QuadPart;
402 if (!bare) {
403 WCMD_output (L"%1 %2 %3!14s! ", datestring, timestring,
404 WCMD_filesize64(file_size.QuadPart));
405 if (shortname) WCMD_output(L"%1!-13s!", fd[i].cAlternateFileName);
406 if (usernames) WCMD_output(L"%1!-23s!", username);
407 WCMD_output(L"%1",fd[i].cFileName);
408 } else {
409 WCMD_output(L"%1%2", recurse ? inputparms->dirName : L"", fd[i].cFileName);
413 if (addNewLine) WCMD_output_asis(L"\r\n");
414 cur_width = 0;
417 if (!bare) {
418 if (file_count == 1) {
419 WCMD_output (L" 1 file %1!25s! bytes\n", WCMD_filesize64 (byte_count.QuadPart));
421 else {
422 WCMD_output (L"%1!8d! files %2!24s! bytes\n", file_count, WCMD_filesize64 (byte_count.QuadPart));
425 byte_total = byte_total + byte_count.QuadPart;
426 file_total = file_total + file_count;
427 dir_total = dir_total + dir_count;
429 if (!bare && !recurse) {
430 if (dir_count == 1) {
431 WCMD_output (L"%1!8d! directory ", 1);
432 } else {
433 WCMD_output (L"%1!8d! directories", dir_count);
437 free(fd);
439 /* When recursing, look in all subdirectories for matches */
440 if (recurse) {
441 DIRECTORY_STACK *dirStack = NULL;
442 DIRECTORY_STACK *lastEntry = NULL;
443 WIN32_FIND_DATAW finddata;
445 /* Build path to search */
446 lstrcpyW(string, inputparms->dirName);
447 lstrcatW(string, L"*");
449 WINE_TRACE("Recursive, looking for '%s'\n", wine_dbgstr_w(string));
450 hff = FindFirstFileW(string, &finddata);
451 if (hff != INVALID_HANDLE_VALUE) {
452 do {
453 if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
454 (lstrcmpW(finddata.cFileName, L"..") != 0) &&
455 (lstrcmpW(finddata.cFileName, L".") != 0)) {
457 DIRECTORY_STACK *thisDir;
458 int dirsToCopy = concurrentDirs;
460 /* Loop creating list of subdirs for all concurrent entries */
461 parms = inputparms;
462 while (dirsToCopy > 0) {
463 dirsToCopy--;
465 /* Work out search parameter in sub dir */
466 lstrcpyW (string, inputparms->dirName);
467 lstrcatW (string, finddata.cFileName);
468 lstrcatW(string, L"\\");
469 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(string));
471 /* Allocate memory, add to list */
472 thisDir = xalloc(sizeof(DIRECTORY_STACK));
473 if (dirStack == NULL) dirStack = thisDir;
474 if (lastEntry != NULL) lastEntry->next = thisDir;
475 lastEntry = thisDir;
476 thisDir->next = NULL;
477 thisDir->dirName = xstrdupW(string);
478 thisDir->fileName = xstrdupW(parms->fileName);
479 parms = parms->next;
482 } while (FindNextFileW(hff, &finddata) != 0);
483 FindClose (hff);
485 while (dirStack != NULL) {
486 DIRECTORY_STACK *thisDir = dirStack;
487 dirStack = WCMD_list_directory (thisDir, 1);
488 while (thisDir != dirStack) {
489 DIRECTORY_STACK *tempDir = thisDir->next;
490 free(thisDir->dirName);
491 free(thisDir->fileName);
492 free(thisDir);
493 thisDir = tempDir;
499 /* Handle case where everything is filtered out */
500 if ((file_total + dir_total == 0) && (level == 0)) {
501 SetLastError (ERROR_FILE_NOT_FOUND);
502 WCMD_print_error ();
503 errorlevel = 1;
506 return parms;
509 /*****************************************************************************
510 * WCMD_dir_trailer
512 * Print out the trailer for the supplied path
514 static void WCMD_dir_trailer(const WCHAR *path) {
515 ULARGE_INTEGER freebytes;
516 BOOL status;
518 status = GetDiskFreeSpaceExW(path, NULL, NULL, &freebytes);
519 WINE_TRACE("Writing trailer for '%s' gave %d(%ld)\n", wine_dbgstr_w(path),
520 status, GetLastError());
522 if (errorlevel==0 && !bare) {
523 if (recurse) {
524 WCMD_output (L"\n Total files listed:\n%1!8d! files%2!25s! bytes\n", file_total, WCMD_filesize64 (byte_total));
525 WCMD_output (L"%1!8d! directories %2!18s! bytes free\n\n", dir_total, WCMD_filesize64 (freebytes.QuadPart));
526 } else {
527 WCMD_output (L" %1!18s! bytes free\n\n", WCMD_filesize64 (freebytes.QuadPart));
532 /* Get the length of a date/time formatting pattern */
533 /* copied from dlls/kernelbase/locale.c */
534 static int get_pattern_len( const WCHAR *pattern, const WCHAR *accept )
536 int i;
538 if (*pattern == '\'')
540 for (i = 1; pattern[i]; i++)
542 if (pattern[i] != '\'') continue;
543 if (pattern[++i] != '\'') return i;
545 return i;
547 if (!wcschr( accept, *pattern )) return 1;
548 for (i = 1; pattern[i]; i++) if (pattern[i] != pattern[0]) break;
549 return i;
552 /* Initialize date format to use abbreviated one with leading zeros */
553 static void init_date_format(void)
555 WCHAR sys_format[MAX_DATETIME_FORMAT];
556 int src_pat_len, dst_pat_len;
557 const WCHAR *src;
558 WCHAR *dst = date_format;
560 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, sys_format, ARRAY_SIZE(sys_format));
562 for (src = sys_format; *src; src += src_pat_len, dst += dst_pat_len) {
563 src_pat_len = dst_pat_len = get_pattern_len(src, L"yMd");
565 switch (*src)
567 case '\'':
568 wmemcpy(dst, src, src_pat_len);
569 break;
571 case 'd':
572 case 'M':
573 if (src_pat_len == 4) /* full name */
574 dst_pat_len--; /* -> use abbreviated one */
575 /* fallthrough */
576 case 'y':
577 if (src_pat_len == 1) /* without leading zeros */
578 dst_pat_len++; /* -> with leading zeros */
579 wmemset(dst, *src, dst_pat_len);
580 break;
582 default:
583 *dst = *src;
584 break;
587 *dst = '\0';
589 TRACE("date format: %s\n", wine_dbgstr_w(date_format));
592 /* Initialize time format to use leading zeros */
593 static void init_time_format(void)
595 WCHAR sys_format[MAX_DATETIME_FORMAT];
596 int src_pat_len, dst_pat_len;
597 const WCHAR *src;
598 WCHAR *dst = time_format;
600 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STIMEFORMAT, sys_format, ARRAY_SIZE(sys_format));
602 for (src = sys_format; *src; src += src_pat_len, dst += dst_pat_len) {
603 src_pat_len = dst_pat_len = get_pattern_len(src, L"Hhmst");
605 switch (*src)
607 case '\'':
608 wmemcpy(dst, src, src_pat_len);
609 break;
611 case 'H':
612 case 'h':
613 case 'm':
614 case 's':
615 if (src_pat_len == 1) /* without leading zeros */
616 dst_pat_len++; /* -> with leading zeros */
617 /* fallthrough */
618 case 't':
619 wmemset(dst, *src, dst_pat_len);
620 break;
622 default:
623 *dst = *src;
624 break;
627 *dst = '\0';
629 /* seconds portion will be dropped by TIME_NOSECONDS */
630 TRACE("time format: %s\n", wine_dbgstr_w(time_format));
633 /*****************************************************************************
634 * WCMD_directory
636 * List a file directory.
640 void WCMD_directory (WCHAR *args)
642 WCHAR path[MAX_PATH], cwd[MAX_PATH];
643 DWORD status;
644 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
645 WCHAR *p;
646 WCHAR string[MAXSTRING];
647 int argno = 0;
648 WCHAR *argN = args;
649 WCHAR lastDrive;
650 BOOL trailerReqd = FALSE;
651 DIRECTORY_STACK *fullParms = NULL;
652 DIRECTORY_STACK *prevEntry = NULL;
653 DIRECTORY_STACK *thisEntry = NULL;
654 WCHAR drive[10];
655 WCHAR dir[MAX_PATH];
656 WCHAR fname[MAX_PATH];
657 WCHAR ext[MAX_PATH];
659 errorlevel = 0;
661 /* Prefill quals with (uppercased) DIRCMD env var */
662 if (GetEnvironmentVariableW(L"DIRCMD", string, ARRAY_SIZE(string))) {
663 wcsupr( string );
664 lstrcatW(string,quals);
665 lstrcpyW(quals, string);
668 byte_total = 0;
669 file_total = dir_total = 0;
671 /* Initialize all flags to their defaults as if no DIRCMD or quals */
672 paged_mode = FALSE;
673 recurse = FALSE;
674 wide = FALSE;
675 bare = FALSE;
676 lower = FALSE;
677 shortname = FALSE;
678 usernames = FALSE;
679 orderByCol = FALSE;
680 separator = TRUE;
681 dirTime = Written;
682 dirOrder = Name;
683 orderReverse = FALSE;
684 orderGroupDirs = FALSE;
685 orderGroupDirsReverse = FALSE;
686 showattrs = 0;
687 attrsbits = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
689 /* Handle args - Loop through so right most is the effective one */
690 /* Note: /- appears to be a negate rather than an off, eg. dir
691 /-W is wide, or dir /w /-w /-w is also wide */
692 p = quals;
693 while (*p && (*p=='/' || *p==' ')) {
694 BOOL negate = FALSE;
695 if (*p++==' ') continue; /* Skip / and blanks introduced through DIRCMD */
697 if (*p=='-') {
698 negate = TRUE;
699 p++;
702 WINE_TRACE("Processing arg '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
703 switch (*p) {
704 case 'P': if (negate) paged_mode = !paged_mode;
705 else paged_mode = TRUE;
706 break;
707 case 'S': if (negate) recurse = !recurse;
708 else recurse = TRUE;
709 break;
710 case 'W': if (negate) wide = !wide;
711 else wide = TRUE;
712 break;
713 case 'B': if (negate) bare = !bare;
714 else bare = TRUE;
715 break;
716 case 'L': if (negate) lower = !lower;
717 else lower = TRUE;
718 break;
719 case 'X': if (negate) shortname = !shortname;
720 else shortname = TRUE;
721 break;
722 case 'Q': if (negate) usernames = !usernames;
723 else usernames = TRUE;
724 break;
725 case 'D': if (negate) orderByCol = !orderByCol;
726 else orderByCol = TRUE;
727 break;
728 case 'C': if (negate) separator = !separator;
729 else separator = TRUE;
730 break;
731 case 'T': p = p + 1;
732 if (*p==':') p++; /* Skip optional : */
734 if (*p == 'A') dirTime = Access;
735 else if (*p == 'C') dirTime = Creation;
736 else if (*p == 'W') dirTime = Written;
738 /* Support /T and /T: with no parms, default to written */
739 else if (*p == 0x00 || *p == '/') {
740 dirTime = Written;
741 p = p - 1; /* So when step on, move to '/' */
742 } else {
743 SetLastError(ERROR_INVALID_PARAMETER);
744 WCMD_print_error();
745 errorlevel = 1;
746 return;
748 break;
749 case 'O': p = p + 1;
750 if (*p==':') p++; /* Skip optional : */
751 while (*p && *p != '/') {
752 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
753 switch (*p) {
754 case 'N': dirOrder = Name; break;
755 case 'E': dirOrder = Extension; break;
756 case 'S': dirOrder = Size; break;
757 case 'D': dirOrder = Date; break;
758 case '-': if (*(p+1)=='G') orderGroupDirsReverse=TRUE;
759 else orderReverse = TRUE;
760 break;
761 case 'G': orderGroupDirs = TRUE; break;
762 default:
763 SetLastError(ERROR_INVALID_PARAMETER);
764 WCMD_print_error();
765 errorlevel = 1;
766 return;
768 p++;
770 p = p - 1; /* So when step on, move to '/' */
771 break;
772 case 'A': p = p + 1;
773 showattrs = 0;
774 attrsbits = 0;
775 if (*p==':') p++; /* Skip optional : */
776 while (*p && *p != '/') {
777 BOOL anegate = FALSE;
778 ULONG mask;
780 /* Note /A: - options are 'offs' not toggles */
781 if (*p=='-') {
782 anegate = TRUE;
783 p++;
786 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
787 switch (*p) {
788 case 'D': mask = FILE_ATTRIBUTE_DIRECTORY; break;
789 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
790 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
791 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
792 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
793 default:
794 SetLastError(ERROR_INVALID_PARAMETER);
795 WCMD_print_error();
796 errorlevel = 1;
797 return;
800 /* Keep running list of bits we care about */
801 attrsbits |= mask;
803 /* Mask shows what MUST be in the bits we care about */
804 if (anegate) showattrs = showattrs & ~mask;
805 else showattrs |= mask;
807 p++;
809 p = p - 1; /* So when step on, move to '/' */
810 WINE_TRACE("Result: showattrs %lx, bits %lx\n", showattrs, attrsbits);
811 break;
812 default:
813 SetLastError(ERROR_INVALID_PARAMETER);
814 WCMD_print_error();
815 errorlevel = 1;
816 return;
818 p = p + 1;
821 /* Handle conflicting args and initialization */
822 if (bare || shortname) wide = FALSE;
823 if (bare) shortname = FALSE;
824 if (wide) usernames = FALSE;
825 if (orderByCol) wide = TRUE;
827 if (wide) {
828 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
829 max_width = consoleInfo.dwSize.X;
830 else
831 max_width = 80;
833 if (paged_mode) {
834 WCMD_enter_paged_mode(NULL);
837 init_date_format();
838 init_time_format();
840 argno = 0;
841 argN = args;
842 GetCurrentDirectoryW(MAX_PATH, cwd);
843 lstrcatW(cwd, L"\\");
845 /* Loop through all args, calculating full effective directory */
846 fullParms = NULL;
847 prevEntry = NULL;
848 while (argN) {
849 WCHAR fullname[MAXSTRING];
850 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
851 if (argN && argN[0] != '/') {
853 WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg));
854 if (thisArg[1] == ':' && thisArg[2] == '\\') {
855 lstrcpyW(fullname, thisArg);
856 } else if (thisArg[1] == ':' && thisArg[2] != '\\') {
857 WCHAR envvar[4];
858 wsprintfW(envvar, L"=%c:", thisArg[0]);
859 if (!GetEnvironmentVariableW(envvar, fullname, MAX_PATH)) {
860 wsprintfW(fullname, L"%c:", thisArg[0]);
862 lstrcatW(fullname, L"\\");
863 lstrcatW(fullname, &thisArg[2]);
864 } else if (thisArg[0] == '\\') {
865 memcpy(fullname, cwd, 2 * sizeof(WCHAR));
866 lstrcpyW(fullname+2, thisArg);
867 } else {
868 lstrcpyW(fullname, cwd);
869 lstrcatW(fullname, thisArg);
871 WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname));
873 if (!WCMD_get_fullpath(fullname, ARRAY_SIZE(path), path, NULL)) continue;
876 * If the path supplied does not include a wildcard, and the endpoint of the
877 * path references a directory, we need to list the *contents* of that
878 * directory not the directory file itself.
880 if ((wcschr(path, '*') == NULL) && (wcschr(path, '%') == NULL)) {
881 status = GetFileAttributesW(path);
882 if ((status != INVALID_FILE_ATTRIBUTES) && (status & FILE_ATTRIBUTE_DIRECTORY)) {
883 if (!ends_with_backslash(path)) lstrcatW(path, L"\\");
884 lstrcatW(path, L"*");
886 } else {
887 /* Special case wildcard search with no extension (ie parameters ending in '.') as
888 GetFullPathName strips off the additional '.' */
889 if (fullname[lstrlenW(fullname)-1] == '.') lstrcatW(path, L".");
892 WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path));
893 thisEntry = xalloc(sizeof(DIRECTORY_STACK));
894 if (fullParms == NULL) fullParms = thisEntry;
895 if (prevEntry != NULL) prevEntry->next = thisEntry;
896 prevEntry = thisEntry;
897 thisEntry->next = NULL;
899 /* Split into components */
900 _wsplitpath(path, drive, dir, fname, ext);
901 WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
902 wine_dbgstr_w(drive), wine_dbgstr_w(dir),
903 wine_dbgstr_w(fname), wine_dbgstr_w(ext));
905 thisEntry->dirName = xalloc(sizeof(WCHAR) * (wcslen(drive) + wcslen(dir) + 1));
906 lstrcpyW(thisEntry->dirName, drive);
907 lstrcatW(thisEntry->dirName, dir);
909 thisEntry->fileName = xalloc(sizeof(WCHAR) * (wcslen(fname) + wcslen(ext) + 1));
910 lstrcpyW(thisEntry->fileName, fname);
911 lstrcatW(thisEntry->fileName, ext);
916 /* If just 'dir' entered, a '*' parameter is assumed */
917 if (fullParms == NULL) {
918 WINE_TRACE("Inserting default '*'\n");
919 fullParms = xalloc(sizeof(DIRECTORY_STACK));
920 fullParms->next = NULL;
921 fullParms->dirName = xstrdupW(cwd);
922 fullParms->fileName = xstrdupW(L"*");
925 lastDrive = '?';
926 prevEntry = NULL;
927 thisEntry = fullParms;
928 trailerReqd = FALSE;
930 while (thisEntry != NULL) {
932 /* Output disk free (trailer) and volume information (header) if the drive
933 letter changes */
934 if (lastDrive != towupper(thisEntry->dirName[0])) {
936 /* Trailer Information */
937 if (lastDrive != '?') {
938 trailerReqd = FALSE;
939 WCMD_dir_trailer(prevEntry->dirName);
940 byte_total = file_total = dir_total = 0;
943 lastDrive = towupper(thisEntry->dirName[0]);
945 if (!bare) {
946 WCHAR drive[3];
948 WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
949 memcpy(drive, thisEntry->dirName, 2 * sizeof(WCHAR));
950 drive[2] = 0x00;
951 status = WCMD_volume (0, drive);
952 trailerReqd = TRUE;
953 if (!status) {
954 errorlevel = 1;
955 goto exit;
958 } else {
959 if (!bare) WCMD_output_asis (L"\n\n");
962 /* Clear any errors from previous invocations, and process it */
963 errorlevel = 0;
964 prevEntry = thisEntry;
965 thisEntry = WCMD_list_directory (thisEntry, 0);
968 /* Trailer Information */
969 if (trailerReqd) {
970 WCMD_dir_trailer(prevEntry->dirName);
973 exit:
974 if (paged_mode) WCMD_leave_paged_mode();
976 /* Free storage allocated for parms */
977 while (fullParms != NULL) {
978 prevEntry = fullParms;
979 fullParms = prevEntry->next;
980 free(prevEntry->dirName);
981 free(prevEntry->fileName);
982 free(prevEntry);