comctl32: Remove DECLSPEC_HIDDEN usage.
[wine.git] / programs / cmd / directory.c
blob6efcbb3a841182a6f5f581110c602858f3570179
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 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 /*****************************************************************************
53 * WCMD_filesize64
55 * Convert a 64-bit number into a WCHARacter string, with commas every three digits.
56 * Result is returned in a static string overwritten with each call.
57 * FIXME: There must be a better algorithm!
59 static WCHAR * WCMD_filesize64 (ULONGLONG n) {
61 ULONGLONG q;
62 unsigned int r, i;
63 WCHAR *p;
64 static WCHAR buff[32];
66 p = buff;
67 i = -3;
68 do {
69 if (separator && ((++i)%3 == 1)) *p++ = ',';
70 q = n / 10;
71 r = n - (q * 10);
72 *p++ = r + '0';
73 *p = '\0';
74 n = q;
75 } while (n != 0);
76 wcsrev(buff);
77 return buff;
80 /*****************************************************************************
81 * WCMD_dir_sort
83 * Sort based on the /O options supplied on the command line
85 static int __cdecl WCMD_dir_sort (const void *a, const void *b)
87 const WIN32_FIND_DATAW *filea = (const WIN32_FIND_DATAW *)a;
88 const WIN32_FIND_DATAW *fileb = (const WIN32_FIND_DATAW *)b;
89 int result = 0;
91 /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
92 requested sort order for the directory components */
93 if (orderGroupDirs &&
94 ((filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
95 (fileb->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)))
97 BOOL aDir = filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
98 if (aDir) result = -1;
99 else result = 1;
100 if (orderGroupDirsReverse) result = -result;
101 return result;
103 /* Order by Name: */
104 } else if (dirOrder == Name) {
105 result = lstrcmpiW(filea->cFileName, fileb->cFileName);
107 /* Order by Size: */
108 } else if (dirOrder == Size) {
109 ULONG64 sizea = (((ULONG64)filea->nFileSizeHigh) << 32) + filea->nFileSizeLow;
110 ULONG64 sizeb = (((ULONG64)fileb->nFileSizeHigh) << 32) + fileb->nFileSizeLow;
111 if( sizea < sizeb ) result = -1;
112 else if( sizea == sizeb ) result = 0;
113 else result = 1;
115 /* Order by Date: (Takes into account which date (/T option) */
116 } else if (dirOrder == Date) {
118 const FILETIME *ft;
119 ULONG64 timea, timeb;
121 if (dirTime == Written) {
122 ft = &filea->ftLastWriteTime;
123 timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
124 ft = &fileb->ftLastWriteTime;
125 timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
126 } else if (dirTime == Access) {
127 ft = &filea->ftLastAccessTime;
128 timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
129 ft = &fileb->ftLastAccessTime;
130 timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
131 } else {
132 ft = &filea->ftCreationTime;
133 timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
134 ft = &fileb->ftCreationTime;
135 timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
137 if( timea < timeb ) result = -1;
138 else if( timea == timeb ) result = 0;
139 else result = 1;
141 /* Order by Extension: (Takes into account which date (/T option) */
142 } else if (dirOrder == Extension) {
143 WCHAR drive[10];
144 WCHAR dir[MAX_PATH];
145 WCHAR fname[MAX_PATH];
146 WCHAR extA[MAX_PATH];
147 WCHAR extB[MAX_PATH];
149 /* Split into components */
150 _wsplitpath(filea->cFileName, drive, dir, fname, extA);
151 _wsplitpath(fileb->cFileName, drive, dir, fname, extB);
152 result = lstrcmpiW(extA, extB);
155 if (orderReverse) result = -result;
156 return result;
159 /*****************************************************************************
160 * WCMD_getfileowner
162 static void WCMD_getfileowner(WCHAR *filename, WCHAR *owner, int ownerlen) {
164 ULONG sizeNeeded = 0;
165 DWORD rc;
166 WCHAR name[MAXSTRING];
167 WCHAR domain[MAXSTRING];
169 /* In case of error, return empty string */
170 *owner = 0x00;
172 /* Find out how much space we need for the owner security descriptor */
173 GetFileSecurityW(filename, OWNER_SECURITY_INFORMATION, 0, 0, &sizeNeeded);
174 rc = GetLastError();
176 if(rc == ERROR_INSUFFICIENT_BUFFER && sizeNeeded > 0) {
178 LPBYTE secBuffer;
179 PSID pSID = NULL;
180 BOOL defaulted = FALSE;
181 ULONG nameLen = MAXSTRING;
182 ULONG domainLen = MAXSTRING;
183 SID_NAME_USE nameuse;
185 secBuffer = xalloc(sizeNeeded * sizeof(BYTE));
187 /* Get the owners security descriptor */
188 if(!GetFileSecurityW(filename, OWNER_SECURITY_INFORMATION, secBuffer,
189 sizeNeeded, &sizeNeeded)) {
190 free(secBuffer);
191 return;
194 /* Get the SID from the SD */
195 if(!GetSecurityDescriptorOwner(secBuffer, &pSID, &defaulted)) {
196 free(secBuffer);
197 return;
200 /* Convert to a username */
201 if (LookupAccountSidW(NULL, pSID, name, &nameLen, domain, &domainLen, &nameuse)) {
202 swprintf(owner, ownerlen, L"%s%c%s", domain, '\\', name);
204 free(secBuffer);
206 return;
209 /*****************************************************************************
210 * WCMD_list_directory
212 * List a single file directory. This function (and those below it) can be called
213 * recursively when the /S switch is used.
215 * FIXME: Assumes 24-line display for the /P qualifier.
218 static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int level) {
220 WCHAR string[1024], datestring[32], timestring[32];
221 WCHAR real_path[MAX_PATH];
222 WIN32_FIND_DATAW *fd;
223 FILETIME ft;
224 SYSTEMTIME st;
225 HANDLE hff;
226 int dir_count, file_count, entry_count, i, widest, cur_width, tmp_width;
227 int numCols, numRows;
228 int rows, cols;
229 ULARGE_INTEGER byte_count, file_size;
230 DIRECTORY_STACK *parms;
231 int concurrentDirs = 0;
232 BOOL done_header = FALSE;
234 dir_count = 0;
235 file_count = 0;
236 entry_count = 0;
237 byte_count.QuadPart = 0;
238 widest = 0;
239 cur_width = 0;
241 /* Loop merging all the files from consecutive parms which relate to the
242 same directory. Note issuing a directory header with no contents
243 mirrors what windows does */
244 parms = inputparms;
245 fd = xalloc(sizeof(WIN32_FIND_DATAW));
246 while (parms && lstrcmpW(inputparms->dirName, parms->dirName) == 0) {
247 concurrentDirs++;
249 /* Work out the full path + filename */
250 lstrcpyW(real_path, parms->dirName);
251 lstrcatW(real_path, parms->fileName);
253 /* Load all files into an in memory structure */
254 WINE_TRACE("Looking for matches to '%s'\n", wine_dbgstr_w(real_path));
255 hff = FindFirstFileW(real_path, &fd[entry_count]);
256 if (hff != INVALID_HANDLE_VALUE) {
257 do {
258 /* Skip any which are filtered out by attribute */
259 if ((fd[entry_count].dwFileAttributes & attrsbits) != showattrs) continue;
261 entry_count++;
263 /* Keep running track of longest filename for wide output */
264 if (wide || orderByCol) {
265 int tmpLen = lstrlenW(fd[entry_count-1].cFileName) + 3;
266 if (fd[entry_count-1].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) tmpLen = tmpLen + 2;
267 if (tmpLen > widest) widest = tmpLen;
270 fd = realloc(fd, (entry_count + 1) * sizeof(WIN32_FIND_DATAW));
271 if (fd == NULL) {
272 FindClose (hff);
273 WINE_ERR("Out of memory\n");
274 errorlevel = 1;
275 return parms->next;
277 } while (FindNextFileW(hff, &fd[entry_count]) != 0);
278 FindClose (hff);
281 /* Work out the actual current directory name without a trailing \ */
282 lstrcpyW(real_path, parms->dirName);
283 real_path[lstrlenW(parms->dirName)-1] = 0x00;
285 /* Output the results */
286 if (!bare) {
287 if (level != 0 && (entry_count > 0)) WCMD_output_asis(L"\r\n");
288 if (!recurse || ((entry_count > 0) && done_header==FALSE)) {
289 WCMD_output (L"Directory of %1\n\n", real_path);
290 done_header = TRUE;
294 /* Move to next parm */
295 parms = parms->next;
298 /* Handle case where everything is filtered out */
299 if (entry_count > 0) {
301 /* Sort the list of files */
302 qsort (fd, entry_count, sizeof(WIN32_FIND_DATAW), WCMD_dir_sort);
304 /* Work out the number of columns */
305 WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count, max_width, widest);
306 if (wide || orderByCol) {
307 numCols = max(1, max_width / widest);
308 numRows = entry_count / numCols;
309 if (entry_count % numCols) numRows++;
310 } else {
311 numCols = 1;
312 numRows = entry_count;
314 WINE_TRACE("cols=%d, rows=%d\n", numCols, numRows);
316 for (rows=0; rows<numRows; rows++) {
317 BOOL addNewLine = TRUE;
318 for (cols=0; cols<numCols; cols++) {
319 WCHAR username[24];
321 /* Work out the index of the entry being pointed to */
322 if (orderByCol) {
323 i = (cols * numRows) + rows;
324 if (i >= entry_count) continue;
325 } else {
326 i = (rows * numCols) + cols;
327 if (i >= entry_count) continue;
330 /* /L convers all names to lower case */
331 if (lower) wcslwr( fd[i].cFileName );
333 /* /Q gets file ownership information */
334 if (usernames) {
335 lstrcpyW (string, inputparms->dirName);
336 lstrcatW (string, fd[i].cFileName);
337 WCMD_getfileowner(string, username, ARRAY_SIZE(username));
340 if (dirTime == Written) {
341 FileTimeToLocalFileTime (&fd[i].ftLastWriteTime, &ft);
342 } else if (dirTime == Access) {
343 FileTimeToLocalFileTime (&fd[i].ftLastAccessTime, &ft);
344 } else {
345 FileTimeToLocalFileTime (&fd[i].ftCreationTime, &ft);
347 FileTimeToSystemTime (&ft, &st);
348 GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring, ARRAY_SIZE(datestring));
349 GetTimeFormatW(0, TIME_NOSECONDS, &st, NULL, 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!10s! %2!8s! <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!10s! %2!8s! %3!10s! ", 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 = 1;
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==0 && !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 /*****************************************************************************
527 * WCMD_directory
529 * List a file directory.
533 void WCMD_directory (WCHAR *args)
535 WCHAR path[MAX_PATH], cwd[MAX_PATH];
536 DWORD status;
537 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
538 WCHAR *p;
539 WCHAR string[MAXSTRING];
540 int argno = 0;
541 WCHAR *argN = args;
542 WCHAR lastDrive;
543 BOOL trailerReqd = FALSE;
544 DIRECTORY_STACK *fullParms = NULL;
545 DIRECTORY_STACK *prevEntry = NULL;
546 DIRECTORY_STACK *thisEntry = NULL;
547 WCHAR drive[10];
548 WCHAR dir[MAX_PATH];
549 WCHAR fname[MAX_PATH];
550 WCHAR ext[MAX_PATH];
552 errorlevel = 0;
554 /* Prefill quals with (uppercased) DIRCMD env var */
555 if (GetEnvironmentVariableW(L"DIRCMD", string, ARRAY_SIZE(string))) {
556 wcsupr( string );
557 lstrcatW(string,quals);
558 lstrcpyW(quals, string);
561 byte_total = 0;
562 file_total = dir_total = 0;
564 /* Initialize all flags to their defaults as if no DIRCMD or quals */
565 paged_mode = FALSE;
566 recurse = FALSE;
567 wide = FALSE;
568 bare = FALSE;
569 lower = FALSE;
570 shortname = FALSE;
571 usernames = FALSE;
572 orderByCol = FALSE;
573 separator = TRUE;
574 dirTime = Written;
575 dirOrder = Name;
576 orderReverse = FALSE;
577 orderGroupDirs = FALSE;
578 orderGroupDirsReverse = FALSE;
579 showattrs = 0;
580 attrsbits = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
582 /* Handle args - Loop through so right most is the effective one */
583 /* Note: /- appears to be a negate rather than an off, eg. dir
584 /-W is wide, or dir /w /-w /-w is also wide */
585 p = quals;
586 while (*p && (*p=='/' || *p==' ')) {
587 BOOL negate = FALSE;
588 if (*p++==' ') continue; /* Skip / and blanks introduced through DIRCMD */
590 if (*p=='-') {
591 negate = TRUE;
592 p++;
595 WINE_TRACE("Processing arg '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
596 switch (*p) {
597 case 'P': if (negate) paged_mode = !paged_mode;
598 else paged_mode = TRUE;
599 break;
600 case 'S': if (negate) recurse = !recurse;
601 else recurse = TRUE;
602 break;
603 case 'W': if (negate) wide = !wide;
604 else wide = TRUE;
605 break;
606 case 'B': if (negate) bare = !bare;
607 else bare = TRUE;
608 break;
609 case 'L': if (negate) lower = !lower;
610 else lower = TRUE;
611 break;
612 case 'X': if (negate) shortname = !shortname;
613 else shortname = TRUE;
614 break;
615 case 'Q': if (negate) usernames = !usernames;
616 else usernames = TRUE;
617 break;
618 case 'D': if (negate) orderByCol = !orderByCol;
619 else orderByCol = TRUE;
620 break;
621 case 'C': if (negate) separator = !separator;
622 else separator = TRUE;
623 break;
624 case 'T': p = p + 1;
625 if (*p==':') p++; /* Skip optional : */
627 if (*p == 'A') dirTime = Access;
628 else if (*p == 'C') dirTime = Creation;
629 else if (*p == 'W') dirTime = Written;
631 /* Support /T and /T: with no parms, default to written */
632 else if (*p == 0x00 || *p == '/') {
633 dirTime = Written;
634 p = p - 1; /* So when step on, move to '/' */
635 } else {
636 SetLastError(ERROR_INVALID_PARAMETER);
637 WCMD_print_error();
638 errorlevel = 1;
639 return;
641 break;
642 case 'O': p = p + 1;
643 if (*p==':') p++; /* Skip optional : */
644 while (*p && *p != '/') {
645 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
646 switch (*p) {
647 case 'N': dirOrder = Name; break;
648 case 'E': dirOrder = Extension; break;
649 case 'S': dirOrder = Size; break;
650 case 'D': dirOrder = Date; break;
651 case '-': if (*(p+1)=='G') orderGroupDirsReverse=TRUE;
652 else orderReverse = TRUE;
653 break;
654 case 'G': orderGroupDirs = TRUE; break;
655 default:
656 SetLastError(ERROR_INVALID_PARAMETER);
657 WCMD_print_error();
658 errorlevel = 1;
659 return;
661 p++;
663 p = p - 1; /* So when step on, move to '/' */
664 break;
665 case 'A': p = p + 1;
666 showattrs = 0;
667 attrsbits = 0;
668 if (*p==':') p++; /* Skip optional : */
669 while (*p && *p != '/') {
670 BOOL anegate = FALSE;
671 ULONG mask;
673 /* Note /A: - options are 'offs' not toggles */
674 if (*p=='-') {
675 anegate = TRUE;
676 p++;
679 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
680 switch (*p) {
681 case 'D': mask = FILE_ATTRIBUTE_DIRECTORY; break;
682 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
683 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
684 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
685 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
686 default:
687 SetLastError(ERROR_INVALID_PARAMETER);
688 WCMD_print_error();
689 errorlevel = 1;
690 return;
693 /* Keep running list of bits we care about */
694 attrsbits |= mask;
696 /* Mask shows what MUST be in the bits we care about */
697 if (anegate) showattrs = showattrs & ~mask;
698 else showattrs |= mask;
700 p++;
702 p = p - 1; /* So when step on, move to '/' */
703 WINE_TRACE("Result: showattrs %lx, bits %lx\n", showattrs, attrsbits);
704 break;
705 default:
706 SetLastError(ERROR_INVALID_PARAMETER);
707 WCMD_print_error();
708 errorlevel = 1;
709 return;
711 p = p + 1;
714 /* Handle conflicting args and initialization */
715 if (bare || shortname) wide = FALSE;
716 if (bare) shortname = FALSE;
717 if (wide) usernames = FALSE;
718 if (orderByCol) wide = TRUE;
720 if (wide) {
721 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
722 max_width = consoleInfo.dwSize.X;
723 else
724 max_width = 80;
726 if (paged_mode) {
727 WCMD_enter_paged_mode(NULL);
730 argno = 0;
731 argN = args;
732 GetCurrentDirectoryW(MAX_PATH, cwd);
733 lstrcatW(cwd, L"\\");
735 /* Loop through all args, calculating full effective directory */
736 fullParms = NULL;
737 prevEntry = NULL;
738 while (argN) {
739 WCHAR fullname[MAXSTRING];
740 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
741 if (argN && argN[0] != '/') {
743 WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg));
744 if (thisArg[1] == ':' && thisArg[2] == '\\') {
745 lstrcpyW(fullname, thisArg);
746 } else if (thisArg[1] == ':' && thisArg[2] != '\\') {
747 WCHAR envvar[4];
748 wsprintfW(envvar, L"=%c:", thisArg[0]);
749 if (!GetEnvironmentVariableW(envvar, fullname, MAX_PATH)) {
750 wsprintfW(fullname, L"%c:", thisArg[0]);
752 lstrcatW(fullname, L"\\");
753 lstrcatW(fullname, &thisArg[2]);
754 } else if (thisArg[0] == '\\') {
755 memcpy(fullname, cwd, 2 * sizeof(WCHAR));
756 lstrcpyW(fullname+2, thisArg);
757 } else {
758 lstrcpyW(fullname, cwd);
759 lstrcatW(fullname, thisArg);
761 WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname));
763 if (!WCMD_get_fullpath(fullname, ARRAY_SIZE(path), path, NULL)) continue;
766 * If the path supplied does not include a wildcard, and the endpoint of the
767 * path references a directory, we need to list the *contents* of that
768 * directory not the directory file itself.
770 if ((wcschr(path, '*') == NULL) && (wcschr(path, '%') == NULL)) {
771 status = GetFileAttributesW(path);
772 if ((status != INVALID_FILE_ATTRIBUTES) && (status & FILE_ATTRIBUTE_DIRECTORY)) {
773 if (!ends_with_backslash(path)) lstrcatW(path, L"\\");
774 lstrcatW(path, L"*");
776 } else {
777 /* Special case wildcard search with no extension (ie parameters ending in '.') as
778 GetFullPathName strips off the additional '.' */
779 if (fullname[lstrlenW(fullname)-1] == '.') lstrcatW(path, L".");
782 WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path));
783 thisEntry = xalloc(sizeof(DIRECTORY_STACK));
784 if (fullParms == NULL) fullParms = thisEntry;
785 if (prevEntry != NULL) prevEntry->next = thisEntry;
786 prevEntry = thisEntry;
787 thisEntry->next = NULL;
789 /* Split into components */
790 _wsplitpath(path, drive, dir, fname, ext);
791 WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
792 wine_dbgstr_w(drive), wine_dbgstr_w(dir),
793 wine_dbgstr_w(fname), wine_dbgstr_w(ext));
795 thisEntry->dirName = xalloc(sizeof(WCHAR) * (wcslen(drive) + wcslen(dir) + 1));
796 lstrcpyW(thisEntry->dirName, drive);
797 lstrcatW(thisEntry->dirName, dir);
799 thisEntry->fileName = xalloc(sizeof(WCHAR) * (wcslen(fname) + wcslen(ext) + 1));
800 lstrcpyW(thisEntry->fileName, fname);
801 lstrcatW(thisEntry->fileName, ext);
806 /* If just 'dir' entered, a '*' parameter is assumed */
807 if (fullParms == NULL) {
808 WINE_TRACE("Inserting default '*'\n");
809 fullParms = xalloc(sizeof(DIRECTORY_STACK));
810 fullParms->next = NULL;
811 fullParms->dirName = xstrdupW(cwd);
812 fullParms->fileName = xstrdupW(L"*");
815 lastDrive = '?';
816 prevEntry = NULL;
817 thisEntry = fullParms;
818 trailerReqd = FALSE;
820 while (thisEntry != NULL) {
822 /* Output disk free (trailer) and volume information (header) if the drive
823 letter changes */
824 if (lastDrive != towupper(thisEntry->dirName[0])) {
826 /* Trailer Information */
827 if (lastDrive != '?') {
828 trailerReqd = FALSE;
829 WCMD_dir_trailer(prevEntry->dirName);
832 lastDrive = towupper(thisEntry->dirName[0]);
834 if (!bare) {
835 WCHAR drive[3];
837 WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
838 memcpy(drive, thisEntry->dirName, 2 * sizeof(WCHAR));
839 drive[2] = 0x00;
840 status = WCMD_volume (0, drive);
841 trailerReqd = TRUE;
842 if (!status) {
843 errorlevel = 1;
844 goto exit;
847 } else {
848 if (!bare) WCMD_output_asis (L"\n\n");
851 /* Clear any errors from previous invocations, and process it */
852 errorlevel = 0;
853 prevEntry = thisEntry;
854 thisEntry = WCMD_list_directory (thisEntry, 0);
857 /* Trailer Information */
858 if (trailerReqd) {
859 WCMD_dir_trailer(prevEntry->dirName);
862 exit:
863 if (paged_mode) WCMD_leave_paged_mode();
865 /* Free storage allocated for parms */
866 while (fullParms != NULL) {
867 prevEntry = fullParms;
868 fullParms = prevEntry->next;
869 free(prevEntry->dirName);
870 free(prevEntry->fileName);
871 free(prevEntry);