iphlpapi: Link NotifyAddrChange and CancelIPChangeNotify to nsi implementation.
[wine.git] / programs / cmd / directory.c
blobdfe7e925419036c114641435f5e7ea57c0d5d84f
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) {
332 WCHAR *p = fd[i].cFileName;
333 while ( (*p = tolower(*p)) ) ++p;
336 /* /Q gets file ownership information */
337 if (usernames) {
338 lstrcpyW (string, inputparms->dirName);
339 lstrcatW (string, fd[i].cFileName);
340 WCMD_getfileowner(string, username, ARRAY_SIZE(username));
343 if (dirTime == Written) {
344 FileTimeToLocalFileTime (&fd[i].ftLastWriteTime, &ft);
345 } else if (dirTime == Access) {
346 FileTimeToLocalFileTime (&fd[i].ftLastAccessTime, &ft);
347 } else {
348 FileTimeToLocalFileTime (&fd[i].ftCreationTime, &ft);
350 FileTimeToSystemTime (&ft, &st);
351 GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring, ARRAY_SIZE(datestring));
352 GetTimeFormatW(0, TIME_NOSECONDS, &st, NULL, timestring, ARRAY_SIZE(timestring));
354 if (wide) {
356 tmp_width = cur_width;
357 if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
358 WCMD_output (L"[%1]", fd[i].cFileName);
359 dir_count++;
360 tmp_width = tmp_width + lstrlenW(fd[i].cFileName) + 2;
361 } else {
362 WCMD_output (L"%1", fd[i].cFileName);
363 tmp_width = tmp_width + lstrlenW(fd[i].cFileName) ;
364 file_count++;
365 file_size.u.LowPart = fd[i].nFileSizeLow;
366 file_size.u.HighPart = fd[i].nFileSizeHigh;
367 byte_count.QuadPart += file_size.QuadPart;
369 cur_width = cur_width + widest;
371 if ((cur_width + widest) > max_width) {
372 cur_width = 0;
373 } else {
374 WCMD_output(L"%1!*s!", cur_width - tmp_width, L"");
377 } else if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
378 dir_count++;
380 if (!bare) {
381 WCMD_output (L"%1!10s! %2!8s! <DIR> ", datestring, timestring);
382 if (shortname) WCMD_output(L"%1!-13s!", fd[i].cAlternateFileName);
383 if (usernames) WCMD_output(L"%1!-23s!", username);
384 WCMD_output(L"%1",fd[i].cFileName);
385 } else {
386 if (!((lstrcmpW(fd[i].cFileName, L".") == 0) ||
387 (lstrcmpW(fd[i].cFileName, L"..") == 0))) {
388 WCMD_output(L"%1%2", recurse ? inputparms->dirName : L"", fd[i].cFileName);
389 } else {
390 addNewLine = FALSE;
394 else {
395 file_count++;
396 file_size.u.LowPart = fd[i].nFileSizeLow;
397 file_size.u.HighPart = fd[i].nFileSizeHigh;
398 byte_count.QuadPart += file_size.QuadPart;
399 if (!bare) {
400 WCMD_output (L"%1!10s! %2!8s! %3!10s! ", datestring, timestring,
401 WCMD_filesize64(file_size.QuadPart));
402 if (shortname) WCMD_output(L"%1!-13s!", fd[i].cAlternateFileName);
403 if (usernames) WCMD_output(L"%1!-23s!", username);
404 WCMD_output(L"%1",fd[i].cFileName);
405 } else {
406 WCMD_output(L"%1%2", recurse ? inputparms->dirName : L"", fd[i].cFileName);
410 if (addNewLine) WCMD_output_asis(L"\r\n");
411 cur_width = 0;
414 if (!bare) {
415 if (file_count == 1) {
416 WCMD_output (L" 1 file %1!25s! bytes\n", WCMD_filesize64 (byte_count.QuadPart));
418 else {
419 WCMD_output (L"%1!8d! files %2!24s! bytes\n", file_count, WCMD_filesize64 (byte_count.QuadPart));
422 byte_total = byte_total + byte_count.QuadPart;
423 file_total = file_total + file_count;
424 dir_total = dir_total + dir_count;
426 if (!bare && !recurse) {
427 if (dir_count == 1) {
428 WCMD_output (L"%1!8d! directory ", 1);
429 } else {
430 WCMD_output (L"%1!8d! directories", dir_count);
434 free(fd);
436 /* When recursing, look in all subdirectories for matches */
437 if (recurse) {
438 DIRECTORY_STACK *dirStack = NULL;
439 DIRECTORY_STACK *lastEntry = NULL;
440 WIN32_FIND_DATAW finddata;
442 /* Build path to search */
443 lstrcpyW(string, inputparms->dirName);
444 lstrcatW(string, L"*");
446 WINE_TRACE("Recursive, looking for '%s'\n", wine_dbgstr_w(string));
447 hff = FindFirstFileW(string, &finddata);
448 if (hff != INVALID_HANDLE_VALUE) {
449 do {
450 if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
451 (lstrcmpW(finddata.cFileName, L"..") != 0) &&
452 (lstrcmpW(finddata.cFileName, L".") != 0)) {
454 DIRECTORY_STACK *thisDir;
455 int dirsToCopy = concurrentDirs;
457 /* Loop creating list of subdirs for all concurrent entries */
458 parms = inputparms;
459 while (dirsToCopy > 0) {
460 dirsToCopy--;
462 /* Work out search parameter in sub dir */
463 lstrcpyW (string, inputparms->dirName);
464 lstrcatW (string, finddata.cFileName);
465 lstrcatW(string, L"\\");
466 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(string));
468 /* Allocate memory, add to list */
469 thisDir = xalloc(sizeof(DIRECTORY_STACK));
470 if (dirStack == NULL) dirStack = thisDir;
471 if (lastEntry != NULL) lastEntry->next = thisDir;
472 lastEntry = thisDir;
473 thisDir->next = NULL;
474 thisDir->dirName = xstrdupW(string);
475 thisDir->fileName = xstrdupW(parms->fileName);
476 parms = parms->next;
479 } while (FindNextFileW(hff, &finddata) != 0);
480 FindClose (hff);
482 while (dirStack != NULL) {
483 DIRECTORY_STACK *thisDir = dirStack;
484 dirStack = WCMD_list_directory (thisDir, 1);
485 while (thisDir != dirStack) {
486 DIRECTORY_STACK *tempDir = thisDir->next;
487 free(thisDir->dirName);
488 free(thisDir->fileName);
489 free(thisDir);
490 thisDir = tempDir;
496 /* Handle case where everything is filtered out */
497 if ((file_total + dir_total == 0) && (level == 0)) {
498 SetLastError (ERROR_FILE_NOT_FOUND);
499 WCMD_print_error ();
500 errorlevel = 1;
503 return parms;
506 /*****************************************************************************
507 * WCMD_dir_trailer
509 * Print out the trailer for the supplied drive letter
511 static void WCMD_dir_trailer(WCHAR drive) {
512 ULARGE_INTEGER avail, total, freebytes;
513 DWORD status;
514 WCHAR driveName[] = L"c:\\";
516 driveName[0] = drive;
517 status = GetDiskFreeSpaceExW(driveName, &avail, &total, &freebytes);
518 WINE_TRACE("Writing trailer for '%s' gave %ld(%ld)\n", wine_dbgstr_w(driveName),
519 status, GetLastError());
521 if (errorlevel==0 && !bare) {
522 if (recurse) {
523 WCMD_output (L"\n Total files listed:\n%1!8d! files%2!25s! bytes\n", file_total, WCMD_filesize64 (byte_total));
524 WCMD_output (L"%1!8d! directories %2!18s! bytes free\n\n", dir_total, WCMD_filesize64 (freebytes.QuadPart));
525 } else {
526 WCMD_output (L" %1!18s! bytes free\n\n", WCMD_filesize64 (freebytes.QuadPart));
531 /*****************************************************************************
532 * WCMD_directory
534 * List a file directory.
538 void WCMD_directory (WCHAR *args)
540 WCHAR path[MAX_PATH], cwd[MAX_PATH];
541 DWORD status;
542 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
543 WCHAR *p;
544 WCHAR string[MAXSTRING];
545 int argno = 0;
546 WCHAR *argN = args;
547 WCHAR lastDrive;
548 BOOL trailerReqd = FALSE;
549 DIRECTORY_STACK *fullParms = NULL;
550 DIRECTORY_STACK *prevEntry = NULL;
551 DIRECTORY_STACK *thisEntry = NULL;
552 WCHAR drive[10];
553 WCHAR dir[MAX_PATH];
554 WCHAR fname[MAX_PATH];
555 WCHAR ext[MAX_PATH];
557 errorlevel = 0;
559 /* Prefill quals with (uppercased) DIRCMD env var */
560 if (GetEnvironmentVariableW(L"DIRCMD", string, ARRAY_SIZE(string))) {
561 p = string;
562 while ( (*p = toupper(*p)) ) ++p;
563 lstrcatW(string,quals);
564 lstrcpyW(quals, string);
567 byte_total = 0;
568 file_total = dir_total = 0;
570 /* Initialize all flags to their defaults as if no DIRCMD or quals */
571 paged_mode = FALSE;
572 recurse = FALSE;
573 wide = FALSE;
574 bare = FALSE;
575 lower = FALSE;
576 shortname = FALSE;
577 usernames = FALSE;
578 orderByCol = FALSE;
579 separator = TRUE;
580 dirTime = Written;
581 dirOrder = Name;
582 orderReverse = FALSE;
583 orderGroupDirs = FALSE;
584 orderGroupDirsReverse = FALSE;
585 showattrs = 0;
586 attrsbits = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
588 /* Handle args - Loop through so right most is the effective one */
589 /* Note: /- appears to be a negate rather than an off, eg. dir
590 /-W is wide, or dir /w /-w /-w is also wide */
591 p = quals;
592 while (*p && (*p=='/' || *p==' ')) {
593 BOOL negate = FALSE;
594 if (*p++==' ') continue; /* Skip / and blanks introduced through DIRCMD */
596 if (*p=='-') {
597 negate = TRUE;
598 p++;
601 WINE_TRACE("Processing arg '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
602 switch (*p) {
603 case 'P': if (negate) paged_mode = !paged_mode;
604 else paged_mode = TRUE;
605 break;
606 case 'S': if (negate) recurse = !recurse;
607 else recurse = TRUE;
608 break;
609 case 'W': if (negate) wide = !wide;
610 else wide = TRUE;
611 break;
612 case 'B': if (negate) bare = !bare;
613 else bare = TRUE;
614 break;
615 case 'L': if (negate) lower = !lower;
616 else lower = TRUE;
617 break;
618 case 'X': if (negate) shortname = !shortname;
619 else shortname = TRUE;
620 break;
621 case 'Q': if (negate) usernames = !usernames;
622 else usernames = TRUE;
623 break;
624 case 'D': if (negate) orderByCol = !orderByCol;
625 else orderByCol = TRUE;
626 break;
627 case 'C': if (negate) separator = !separator;
628 else separator = TRUE;
629 break;
630 case 'T': p = p + 1;
631 if (*p==':') p++; /* Skip optional : */
633 if (*p == 'A') dirTime = Access;
634 else if (*p == 'C') dirTime = Creation;
635 else if (*p == 'W') dirTime = Written;
637 /* Support /T and /T: with no parms, default to written */
638 else if (*p == 0x00 || *p == '/') {
639 dirTime = Written;
640 p = p - 1; /* So when step on, move to '/' */
641 } else {
642 SetLastError(ERROR_INVALID_PARAMETER);
643 WCMD_print_error();
644 errorlevel = 1;
645 return;
647 break;
648 case 'O': p = p + 1;
649 if (*p==':') p++; /* Skip optional : */
650 while (*p && *p != '/') {
651 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
652 switch (*p) {
653 case 'N': dirOrder = Name; break;
654 case 'E': dirOrder = Extension; break;
655 case 'S': dirOrder = Size; break;
656 case 'D': dirOrder = Date; break;
657 case '-': if (*(p+1)=='G') orderGroupDirsReverse=TRUE;
658 else orderReverse = TRUE;
659 break;
660 case 'G': orderGroupDirs = TRUE; break;
661 default:
662 SetLastError(ERROR_INVALID_PARAMETER);
663 WCMD_print_error();
664 errorlevel = 1;
665 return;
667 p++;
669 p = p - 1; /* So when step on, move to '/' */
670 break;
671 case 'A': p = p + 1;
672 showattrs = 0;
673 attrsbits = 0;
674 if (*p==':') p++; /* Skip optional : */
675 while (*p && *p != '/') {
676 BOOL anegate = FALSE;
677 ULONG mask;
679 /* Note /A: - options are 'offs' not toggles */
680 if (*p=='-') {
681 anegate = TRUE;
682 p++;
685 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
686 switch (*p) {
687 case 'D': mask = FILE_ATTRIBUTE_DIRECTORY; break;
688 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
689 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
690 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
691 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
692 default:
693 SetLastError(ERROR_INVALID_PARAMETER);
694 WCMD_print_error();
695 errorlevel = 1;
696 return;
699 /* Keep running list of bits we care about */
700 attrsbits |= mask;
702 /* Mask shows what MUST be in the bits we care about */
703 if (anegate) showattrs = showattrs & ~mask;
704 else showattrs |= mask;
706 p++;
708 p = p - 1; /* So when step on, move to '/' */
709 WINE_TRACE("Result: showattrs %lx, bits %lx\n", showattrs, attrsbits);
710 break;
711 default:
712 SetLastError(ERROR_INVALID_PARAMETER);
713 WCMD_print_error();
714 errorlevel = 1;
715 return;
717 p = p + 1;
720 /* Handle conflicting args and initialization */
721 if (bare || shortname) wide = FALSE;
722 if (bare) shortname = FALSE;
723 if (wide) usernames = FALSE;
724 if (orderByCol) wide = TRUE;
726 if (wide) {
727 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
728 max_width = consoleInfo.dwSize.X;
729 else
730 max_width = 80;
732 if (paged_mode) {
733 WCMD_enter_paged_mode(NULL);
736 argno = 0;
737 argN = args;
738 GetCurrentDirectoryW(MAX_PATH, cwd);
739 lstrcatW(cwd, L"\\");
741 /* Loop through all args, calculating full effective directory */
742 fullParms = NULL;
743 prevEntry = NULL;
744 while (argN) {
745 WCHAR fullname[MAXSTRING];
746 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
747 if (argN && argN[0] != '/') {
749 WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg));
750 if (thisArg[1] == ':' && thisArg[2] == '\\') {
751 lstrcpyW(fullname, thisArg);
752 } else if (thisArg[1] == ':' && thisArg[2] != '\\') {
753 WCHAR envvar[4];
754 wsprintfW(envvar, L"=%c:", thisArg[0]);
755 if (!GetEnvironmentVariableW(envvar, fullname, MAX_PATH)) {
756 wsprintfW(fullname, L"%c:", thisArg[0]);
758 lstrcatW(fullname, L"\\");
759 lstrcatW(fullname, &thisArg[2]);
760 } else if (thisArg[0] == '\\') {
761 memcpy(fullname, cwd, 2 * sizeof(WCHAR));
762 lstrcpyW(fullname+2, thisArg);
763 } else {
764 lstrcpyW(fullname, cwd);
765 lstrcatW(fullname, thisArg);
767 WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname));
769 if (!WCMD_get_fullpath(fullname, ARRAY_SIZE(path), path, NULL)) continue;
772 * If the path supplied does not include a wildcard, and the endpoint of the
773 * path references a directory, we need to list the *contents* of that
774 * directory not the directory file itself.
776 if ((wcschr(path, '*') == NULL) && (wcschr(path, '%') == NULL)) {
777 status = GetFileAttributesW(path);
778 if ((status != INVALID_FILE_ATTRIBUTES) && (status & FILE_ATTRIBUTE_DIRECTORY)) {
779 if (!ends_with_backslash(path)) lstrcatW(path, L"\\");
780 lstrcatW(path, L"*");
782 } else {
783 /* Special case wildcard search with no extension (ie parameters ending in '.') as
784 GetFullPathName strips off the additional '.' */
785 if (fullname[lstrlenW(fullname)-1] == '.') lstrcatW(path, L".");
788 WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path));
789 thisEntry = xalloc(sizeof(DIRECTORY_STACK));
790 if (fullParms == NULL) fullParms = thisEntry;
791 if (prevEntry != NULL) prevEntry->next = thisEntry;
792 prevEntry = thisEntry;
793 thisEntry->next = NULL;
795 /* Split into components */
796 _wsplitpath(path, drive, dir, fname, ext);
797 WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
798 wine_dbgstr_w(drive), wine_dbgstr_w(dir),
799 wine_dbgstr_w(fname), wine_dbgstr_w(ext));
801 thisEntry->dirName = xalloc(sizeof(WCHAR) * (wcslen(drive) + wcslen(dir) + 1));
802 lstrcpyW(thisEntry->dirName, drive);
803 lstrcatW(thisEntry->dirName, dir);
805 thisEntry->fileName = xalloc(sizeof(WCHAR) * (wcslen(fname) + wcslen(ext) + 1));
806 lstrcpyW(thisEntry->fileName, fname);
807 lstrcatW(thisEntry->fileName, ext);
812 /* If just 'dir' entered, a '*' parameter is assumed */
813 if (fullParms == NULL) {
814 WINE_TRACE("Inserting default '*'\n");
815 fullParms = xalloc(sizeof(DIRECTORY_STACK));
816 fullParms->next = NULL;
817 fullParms->dirName = xstrdupW(cwd);
818 fullParms->fileName = xstrdupW(L"*");
821 lastDrive = '?';
822 prevEntry = NULL;
823 thisEntry = fullParms;
824 trailerReqd = FALSE;
826 while (thisEntry != NULL) {
828 /* Output disk free (trailer) and volume information (header) if the drive
829 letter changes */
830 if (lastDrive != toupper(thisEntry->dirName[0])) {
832 /* Trailer Information */
833 if (lastDrive != '?') {
834 trailerReqd = FALSE;
835 WCMD_dir_trailer(prevEntry->dirName[0]);
838 lastDrive = toupper(thisEntry->dirName[0]);
840 if (!bare) {
841 WCHAR drive[3];
843 WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
844 memcpy(drive, thisEntry->dirName, 2 * sizeof(WCHAR));
845 drive[2] = 0x00;
846 status = WCMD_volume (0, drive);
847 trailerReqd = TRUE;
848 if (!status) {
849 errorlevel = 1;
850 goto exit;
853 } else {
854 if (!bare) WCMD_output_asis (L"\n\n");
857 /* Clear any errors from previous invocations, and process it */
858 errorlevel = 0;
859 prevEntry = thisEntry;
860 thisEntry = WCMD_list_directory (thisEntry, 0);
863 /* Trailer Information */
864 if (trailerReqd) {
865 WCMD_dir_trailer(prevEntry->dirName[0]);
868 exit:
869 if (paged_mode) WCMD_leave_paged_mode();
871 /* Free storage allocated for parms */
872 while (fullParms != NULL) {
873 prevEntry = fullParms;
874 fullParms = prevEntry->next;
875 free(prevEntry->dirName);
876 free(prevEntry->fileName);
877 free(prevEntry);