include/mscvpdb.h: Use flexible array members for the rest of structures.
[wine.git] / programs / cmd / directory.c
blob3f4f785371035775d6f1c9448679c414a1f2f22d
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 = xrealloc(fd, (entry_count + 1) * sizeof(WIN32_FIND_DATAW));
275 } while (FindNextFileW(hff, &fd[entry_count]) != 0);
276 FindClose (hff);
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 */
284 if (!bare) {
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);
288 done_header = TRUE;
292 /* Move to next parm */
293 parms = parms->next;
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++;
308 } else {
309 numCols = 1;
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++) {
317 WCHAR username[24];
319 /* Work out the index of the entry being pointed to */
320 if (orderByCol) {
321 i = (cols * numRows) + rows;
322 if (i >= entry_count) continue;
323 } else {
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 */
332 if (usernames) {
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);
342 } else {
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));
351 if (wide) {
353 tmp_width = cur_width;
354 if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
355 WCMD_output (L"[%1]", fd[i].cFileName);
356 dir_count++;
357 tmp_width = tmp_width + lstrlenW(fd[i].cFileName) + 2;
358 } else {
359 WCMD_output (L"%1", fd[i].cFileName);
360 tmp_width = tmp_width + lstrlenW(fd[i].cFileName) ;
361 file_count++;
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) {
369 cur_width = 0;
370 } else {
371 WCMD_output(L"%1!*s!", cur_width - tmp_width, L"");
374 } else if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
375 dir_count++;
377 if (!bare) {
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);
382 } else {
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);
386 } else {
387 addNewLine = FALSE;
391 else {
392 file_count++;
393 file_size.u.LowPart = fd[i].nFileSizeLow;
394 file_size.u.HighPart = fd[i].nFileSizeHigh;
395 byte_count.QuadPart += file_size.QuadPart;
396 if (!bare) {
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);
402 } else {
403 WCMD_output(L"%1%2", recurse ? inputparms->dirName : L"", fd[i].cFileName);
407 if (addNewLine) WCMD_output_asis(L"\r\n");
408 cur_width = 0;
411 if (!bare) {
412 if (file_count == 1) {
413 WCMD_output (L" 1 file %1!25s! bytes\n", WCMD_filesize64 (byte_count.QuadPart));
415 else {
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);
426 } else {
427 WCMD_output (L"%1!8d! directories", dir_count);
431 free(fd);
433 /* When recursing, look in all subdirectories for matches */
434 if (recurse) {
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) {
446 do {
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 */
455 parms = inputparms;
456 while (dirsToCopy > 0) {
457 dirsToCopy--;
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;
469 lastEntry = thisDir;
470 thisDir->next = NULL;
471 thisDir->dirName = xstrdupW(string);
472 thisDir->fileName = xstrdupW(parms->fileName);
473 parms = parms->next;
476 } while (FindNextFileW(hff, &finddata) != 0);
477 FindClose (hff);
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);
486 free(thisDir);
487 thisDir = tempDir;
493 /* Handle case where everything is filtered out */
494 if ((file_total + dir_total == 0) && (level == 0)) {
495 SetLastError (ERROR_FILE_NOT_FOUND);
496 WCMD_print_error ();
497 errorlevel = ERROR_INVALID_FUNCTION;
500 return parms;
503 /*****************************************************************************
504 * WCMD_dir_trailer
506 * Print out the trailer for the supplied path
508 static void WCMD_dir_trailer(const WCHAR *path) {
509 ULARGE_INTEGER freebytes;
510 BOOL status;
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) {
517 if (recurse) {
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));
520 } else {
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 )
530 int i;
532 if (*pattern == '\'')
534 for (i = 1; pattern[i]; i++)
536 if (pattern[i] != '\'') continue;
537 if (pattern[++i] != '\'') return i;
539 return i;
541 if (!wcschr( accept, *pattern )) return 1;
542 for (i = 1; pattern[i]; i++) if (pattern[i] != pattern[0]) break;
543 return i;
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;
551 const WCHAR *src;
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");
559 switch (*src)
561 case '\'':
562 wmemcpy(dst, src, src_pat_len);
563 break;
565 case 'd':
566 case 'M':
567 if (src_pat_len == 4) /* full name */
568 dst_pat_len--; /* -> use abbreviated one */
569 /* fallthrough */
570 case 'y':
571 if (src_pat_len == 1) /* without leading zeros */
572 dst_pat_len++; /* -> with leading zeros */
573 wmemset(dst, *src, dst_pat_len);
574 break;
576 default:
577 *dst = *src;
578 break;
581 *dst = '\0';
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;
591 const WCHAR *src;
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");
599 switch (*src)
601 case '\'':
602 wmemcpy(dst, src, src_pat_len);
603 break;
605 case 'H':
606 case 'h':
607 case 'm':
608 case 's':
609 if (src_pat_len == 1) /* without leading zeros */
610 dst_pat_len++; /* -> with leading zeros */
611 /* fallthrough */
612 case 't':
613 wmemset(dst, *src, dst_pat_len);
614 break;
616 default:
617 *dst = *src;
618 break;
621 *dst = '\0';
623 /* seconds portion will be dropped by TIME_NOSECONDS */
624 TRACE("time format: %s\n", wine_dbgstr_w(time_format));
627 /*****************************************************************************
628 * WCMD_directory
630 * List a file directory.
634 RETURN_CODE WCMD_directory(WCHAR *args)
636 WCHAR path[MAX_PATH], cwd[MAX_PATH];
637 DWORD status;
638 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
639 WCHAR *p;
640 WCHAR string[MAXSTRING];
641 int argno = 0;
642 WCHAR *argN = args;
643 WCHAR lastDrive;
644 BOOL trailerReqd = FALSE;
645 DIRECTORY_STACK *fullParms = NULL;
646 DIRECTORY_STACK *prevEntry = NULL;
647 DIRECTORY_STACK *thisEntry = NULL;
648 WCHAR drive[10];
649 WCHAR dir[MAX_PATH];
650 WCHAR fname[MAX_PATH];
651 WCHAR ext[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))) {
658 wcsupr( string );
659 lstrcatW(string,quals);
660 lstrcpyW(quals, string);
663 byte_total = 0;
664 file_total = dir_total = 0;
666 /* Initialize all flags to their defaults as if no DIRCMD or quals */
667 paged_mode = FALSE;
668 recurse = FALSE;
669 wide = FALSE;
670 bare = FALSE;
671 lower = FALSE;
672 shortname = FALSE;
673 usernames = FALSE;
674 orderByCol = FALSE;
675 separator = TRUE;
676 dirTime = Written;
677 dirOrder = Name;
678 orderReverse = FALSE;
679 orderGroupDirs = FALSE;
680 orderGroupDirsReverse = FALSE;
681 showattrs = 0;
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 */
687 p = quals;
688 while (*p && (*p=='/' || *p==' ')) {
689 BOOL negate = FALSE;
690 if (*p++==' ') continue; /* Skip / and blanks introduced through DIRCMD */
692 if (*p=='-') {
693 negate = TRUE;
694 p++;
697 WINE_TRACE("Processing arg '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
698 switch (*p) {
699 case 'P': if (negate) paged_mode = !paged_mode;
700 else paged_mode = TRUE;
701 break;
702 case 'S': if (negate) recurse = !recurse;
703 else recurse = TRUE;
704 break;
705 case 'W': if (negate) wide = !wide;
706 else wide = TRUE;
707 break;
708 case 'B': if (negate) bare = !bare;
709 else bare = TRUE;
710 break;
711 case 'L': if (negate) lower = !lower;
712 else lower = TRUE;
713 break;
714 case 'X': if (negate) shortname = !shortname;
715 else shortname = TRUE;
716 break;
717 case 'Q': if (negate) usernames = !usernames;
718 else usernames = TRUE;
719 break;
720 case 'D': if (negate) orderByCol = !orderByCol;
721 else orderByCol = TRUE;
722 break;
723 case 'C': if (negate) separator = !separator;
724 else separator = TRUE;
725 break;
726 case 'T': p = p + 1;
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 == '/') {
735 dirTime = Written;
736 p = p - 1; /* So when step on, move to '/' */
737 } else {
738 SetLastError(ERROR_INVALID_PARAMETER);
739 WCMD_print_error();
740 return errorlevel = ERROR_INVALID_FUNCTION;
742 break;
743 case 'O': p = p + 1;
744 if (*p==':') p++; /* Skip optional : */
745 while (*p && *p != '/') {
746 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
747 switch (*p) {
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;
754 break;
755 case 'G': orderGroupDirs = TRUE; break;
756 default:
757 SetLastError(ERROR_INVALID_PARAMETER);
758 WCMD_print_error();
759 return errorlevel = ERROR_INVALID_FUNCTION;
761 p++;
763 p = p - 1; /* So when step on, move to '/' */
764 break;
765 case 'A': p = p + 1;
766 showattrs = 0;
767 attrsbits = 0;
768 if (*p==':') p++; /* Skip optional : */
769 while (*p && *p != '/') {
770 BOOL anegate = FALSE;
771 ULONG mask;
773 /* Note /A: - options are 'offs' not toggles */
774 if (*p=='-') {
775 anegate = TRUE;
776 p++;
779 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
780 switch (*p) {
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;
786 default:
787 SetLastError(ERROR_INVALID_PARAMETER);
788 WCMD_print_error();
789 return errorlevel = ERROR_INVALID_FUNCTION;
792 /* Keep running list of bits we care about */
793 attrsbits |= mask;
795 /* Mask shows what MUST be in the bits we care about */
796 if (anegate) showattrs = showattrs & ~mask;
797 else showattrs |= mask;
799 p++;
801 p = p - 1; /* So when step on, move to '/' */
802 WINE_TRACE("Result: showattrs %lx, bits %lx\n", showattrs, attrsbits);
803 break;
804 default:
805 SetLastError(ERROR_INVALID_PARAMETER);
806 WCMD_print_error();
807 return errorlevel = ERROR_INVALID_FUNCTION;
809 p = p + 1;
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;
818 if (wide) {
819 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
820 max_width = consoleInfo.dwSize.X;
821 else
822 max_width = 80;
824 if (paged_mode) {
825 WCMD_enter_paged_mode(NULL);
828 init_date_format();
829 init_time_format();
831 argno = 0;
832 argN = args;
833 GetCurrentDirectoryW(MAX_PATH, cwd);
834 lstrcatW(cwd, L"\\");
836 /* Loop through all args, calculating full effective directory */
837 fullParms = NULL;
838 prevEntry = NULL;
839 while (argN) {
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] != '\\') {
848 WCHAR envvar[4];
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);
858 } else {
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"*");
877 } else {
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"*");
916 lastDrive = '?';
917 prevEntry = NULL;
918 thisEntry = fullParms;
919 trailerReqd = FALSE;
921 while (thisEntry != NULL) {
923 /* Output disk free (trailer) and volume information (header) if the drive
924 letter changes */
925 if (lastDrive != towupper(thisEntry->dirName[0])) {
927 /* Trailer Information */
928 if (lastDrive != '?') {
929 trailerReqd = FALSE;
930 WCMD_dir_trailer(prevEntry->dirName);
931 byte_total = file_total = dir_total = 0;
934 lastDrive = towupper(thisEntry->dirName[0]);
936 if (!bare) {
937 WCHAR drive[4];
938 WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
939 drive[0] = thisEntry->dirName[0];
940 drive[1] = thisEntry->dirName[1];
941 drive[2] = L'\\';
942 drive[3] = L'\0';
943 trailerReqd = TRUE;
944 if (!WCMD_print_volume_information(drive)) {
945 errorlevel = ERROR_INVALID_FUNCTION;
946 goto exit;
949 } else {
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);
957 if (errorlevel)
958 num_empty++;
959 else
960 num_with_data++;
963 /* Trailer Information */
964 if (trailerReqd) {
965 WCMD_dir_trailer(prevEntry->dirName);
968 if (num_empty && !num_with_data)
969 errorlevel = ERROR_INVALID_FUNCTION;
970 exit:
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);
979 free(prevEntry);
982 return errorlevel;