cmd: Add an output parameter to WCMD_parameter to point to the end of the extracted...
[wine] / programs / cmd / directory.c
1 /*
2  * CMD - Wine-compatible command line interface - Directory functions.
3  *
4  * Copyright (C) 1999 D A Pickles
5  * Copyright (C) 2007 J Edmeades
6  *
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.
11  *
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.
16  *
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
20  */
21
22 /*
23  * NOTES:
24  * On entry, global variables quals, param1, param2 contain
25  * the qualifiers (uppercased and concatenated) and parameters entered, with
26  * environment-variable and batch parameter substitution already done.
27  */
28
29 #define WIN32_LEAN_AND_MEAN
30
31 #include "wcmd.h"
32 #include "wine/debug.h"
33
34 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
35
36
37 extern int echo_mode;
38 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
39 extern DWORD errorlevel;
40
41 typedef enum _DISPLAYTIME
42 {
43     Creation = 0,
44     Access,
45     Written
46 } DISPLAYTIME;
47
48 typedef enum _DISPLAYORDER
49 {
50     Name = 0,
51     Extension,
52     Size,
53     Date
54 } DISPLAYORDER;
55
56 static int file_total, dir_total, recurse, wide, bare, max_width, lower;
57 static int shortname, usernames;
58 static ULONGLONG byte_total;
59 static DISPLAYTIME dirTime;
60 static DISPLAYORDER dirOrder;
61 static BOOL orderReverse, orderGroupDirs, orderGroupDirsReverse, orderByCol;
62 static BOOL separator;
63 static ULONG showattrs, attrsbits;
64
65 static const WCHAR dotW[]    = {'.','\0'};
66 static const WCHAR dotdotW[] = {'.','.','\0'};
67 static const WCHAR starW[]   = {'*','\0'};
68 static const WCHAR slashW[]  = {'\\','\0'};
69 static const WCHAR emptyW[]  = {'\0'};
70 static const WCHAR spaceW[]  = {' ','\0'};
71
72 /*****************************************************************************
73  * WCMD_strrev
74  *
75  * Reverse a WCHARacter string in-place (strrev() is not available under unixen :-( ).
76  */
77 static WCHAR * WCMD_strrev (WCHAR *buff) {
78
79   int r, i;
80   WCHAR b;
81
82   r = strlenW (buff);
83   for (i=0; i<r/2; i++) {
84     b = buff[i];
85     buff[i] = buff[r-i-1];
86     buff[r-i-1] = b;
87   }
88   return (buff);
89 }
90
91 /*****************************************************************************
92  * WCMD_filesize64
93  *
94  * Convert a 64-bit number into a WCHARacter string, with commas every three digits.
95  * Result is returned in a static string overwritten with each call.
96  * FIXME: There must be a better algorithm!
97  */
98 static WCHAR * WCMD_filesize64 (ULONGLONG n) {
99
100   ULONGLONG q;
101   unsigned int r, i;
102   WCHAR *p;
103   static WCHAR buff[32];
104
105   p = buff;
106   i = -3;
107   do {
108     if (separator && ((++i)%3 == 1)) *p++ = ',';
109     q = n / 10;
110     r = n - (q * 10);
111     *p++ = r + '0';
112     *p = '\0';
113     n = q;
114   } while (n != 0);
115   WCMD_strrev (buff);
116   return buff;
117 }
118
119 /*****************************************************************************
120  * WCMD_dir_sort
121  *
122  * Sort based on the /O options supplied on the command line
123  */
124 static int WCMD_dir_sort (const void *a, const void *b)
125 {
126   const WIN32_FIND_DATAW *filea = (const WIN32_FIND_DATAW *)a;
127   const WIN32_FIND_DATAW *fileb = (const WIN32_FIND_DATAW *)b;
128   int result = 0;
129
130   /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
131      requested sort order for the directory components                   */
132   if (orderGroupDirs &&
133       ((filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
134        (fileb->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)))
135   {
136     BOOL aDir = filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
137     if (aDir) result = -1;
138     else result = 1;
139     if (orderGroupDirsReverse) result = -result;
140     return result;
141
142   /* Order by Name: */
143   } else if (dirOrder == Name) {
144     result = lstrcmpiW(filea->cFileName, fileb->cFileName);
145
146   /* Order by Size: */
147   } else if (dirOrder == Size) {
148     ULONG64 sizea = (((ULONG64)filea->nFileSizeHigh) << 32) + filea->nFileSizeLow;
149     ULONG64 sizeb = (((ULONG64)fileb->nFileSizeHigh) << 32) + fileb->nFileSizeLow;
150     if( sizea < sizeb ) result = -1;
151     else if( sizea == sizeb ) result = 0;
152     else result = 1;
153
154   /* Order by Date: (Takes into account which date (/T option) */
155   } else if (dirOrder == Date) {
156
157     const FILETIME *ft;
158     ULONG64 timea, timeb;
159
160     if (dirTime == Written) {
161       ft = &filea->ftLastWriteTime;
162       timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
163       ft = &fileb->ftLastWriteTime;
164       timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
165     } else if (dirTime == Access) {
166       ft = &filea->ftLastAccessTime;
167       timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
168       ft = &fileb->ftLastAccessTime;
169       timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
170     } else {
171       ft = &filea->ftCreationTime;
172       timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
173       ft = &fileb->ftCreationTime;
174       timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
175     }
176     if( timea < timeb ) result = -1;
177     else if( timea == timeb ) result = 0;
178     else result = 1;
179
180   /* Order by Extension: (Takes into account which date (/T option) */
181   } else if (dirOrder == Extension) {
182       WCHAR drive[10];
183       WCHAR dir[MAX_PATH];
184       WCHAR fname[MAX_PATH];
185       WCHAR extA[MAX_PATH];
186       WCHAR extB[MAX_PATH];
187
188       /* Split into components */
189       WCMD_splitpath(filea->cFileName, drive, dir, fname, extA);
190       WCMD_splitpath(fileb->cFileName, drive, dir, fname, extB);
191       result = lstrcmpiW(extA, extB);
192   }
193
194   if (orderReverse) result = -result;
195   return result;
196 }
197
198 /*****************************************************************************
199  * WCMD_getfileowner
200  *
201  * Reverse a WCHARacter string in-place (strrev() is not available under unixen :-( ).
202  */
203 static void WCMD_getfileowner(WCHAR *filename, WCHAR *owner, int ownerlen) {
204
205     ULONG sizeNeeded = 0;
206     DWORD rc;
207     WCHAR name[MAXSTRING];
208     WCHAR domain[MAXSTRING];
209
210     /* In case of error, return empty string */
211     *owner = 0x00;
212
213     /* Find out how much space we need for the owner security descriptor */
214     GetFileSecurityW(filename, OWNER_SECURITY_INFORMATION, 0, 0, &sizeNeeded);
215     rc = GetLastError();
216
217     if(rc == ERROR_INSUFFICIENT_BUFFER && sizeNeeded > 0) {
218
219         LPBYTE secBuffer;
220         PSID pSID = NULL;
221         BOOL defaulted = FALSE;
222         ULONG nameLen = MAXSTRING;
223         ULONG domainLen = MAXSTRING;
224         SID_NAME_USE nameuse;
225
226         secBuffer = HeapAlloc(GetProcessHeap(),0,sizeNeeded * sizeof(BYTE));
227         if(!secBuffer) return;
228
229         /* Get the owners security descriptor */
230         if(!GetFileSecurityW(filename, OWNER_SECURITY_INFORMATION, secBuffer,
231                             sizeNeeded, &sizeNeeded)) {
232             HeapFree(GetProcessHeap(),0,secBuffer);
233             return;
234         }
235
236         /* Get the SID from the SD */
237         if(!GetSecurityDescriptorOwner(secBuffer, &pSID, &defaulted)) {
238             HeapFree(GetProcessHeap(),0,secBuffer);
239             return;
240         }
241
242         /* Convert to a username */
243         if (LookupAccountSidW(NULL, pSID, name, &nameLen, domain, &domainLen, &nameuse)) {
244             static const WCHAR fmt[]  = {'%','s','%','c','%','s','\0'};
245             snprintfW(owner, ownerlen, fmt, domain, '\\', name);
246         }
247         HeapFree(GetProcessHeap(),0,secBuffer);
248     }
249     return;
250 }
251
252 /*****************************************************************************
253  * WCMD_list_directory
254  *
255  * List a single file directory. This function (and those below it) can be called
256  * recursively when the /S switch is used.
257  *
258  * FIXME: Assumes 24-line display for the /P qualifier.
259  */
260
261 static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int level) {
262
263   WCHAR string[1024], datestring[32], timestring[32];
264   WCHAR real_path[MAX_PATH];
265   WIN32_FIND_DATAW *fd;
266   FILETIME ft;
267   SYSTEMTIME st;
268   HANDLE hff;
269   int dir_count, file_count, entry_count, i, widest, cur_width, tmp_width;
270   int numCols, numRows;
271   int rows, cols;
272   ULARGE_INTEGER byte_count, file_size;
273   DIRECTORY_STACK *parms;
274   int concurrentDirs = 0;
275   BOOL done_header = FALSE;
276
277   static const WCHAR fmtDir[]  = {'%','1','0','s',' ',' ','%','8','s',' ',' ',
278                                   '<','D','I','R','>',' ',' ',' ',' ',' ',' ',' ',' ',' ','\0'};
279   static const WCHAR fmtFile[] = {'%','1','0','s',' ',' ','%','8','s',' ',' ',
280                                   ' ',' ','%','1','0','s',' ',' ','\0'};
281   static const WCHAR fmt2[]  = {'%','-','1','3','s','\0'};
282   static const WCHAR fmt3[]  = {'%','-','2','3','s','\0'};
283   static const WCHAR fmt4[]  = {'%','s','\0'};
284   static const WCHAR fmt5[]  = {'%','s','%','s','\0'};
285
286   dir_count = 0;
287   file_count = 0;
288   entry_count = 0;
289   byte_count.QuadPart = 0;
290   widest = 0;
291   cur_width = 0;
292
293   /* Loop merging all the files from consecutive parms which relate to the
294      same directory. Note issuing a directory header with no contents
295      mirrors what windows does                                            */
296   parms = inputparms;
297   fd = HeapAlloc(GetProcessHeap(),0,sizeof(WIN32_FIND_DATAW));
298   while (parms && strcmpW(inputparms->dirName, parms->dirName) == 0) {
299     concurrentDirs++;
300
301     /* Work out the full path + filename */
302     strcpyW(real_path, parms->dirName);
303     strcatW(real_path, parms->fileName);
304
305     /* Load all files into an in memory structure */
306     WINE_TRACE("Looking for matches to '%s'\n", wine_dbgstr_w(real_path));
307     hff = FindFirstFileW(real_path, (fd+entry_count));
308     if (hff != INVALID_HANDLE_VALUE) {
309       do {
310         /* Skip any which are filtered out by attribute */
311         if (((fd+entry_count)->dwFileAttributes & attrsbits) != showattrs) continue;
312
313         entry_count++;
314
315         /* Keep running track of longest filename for wide output */
316         if (wide || orderByCol) {
317            int tmpLen = strlenW((fd+(entry_count-1))->cFileName) + 3;
318            if ((fd+(entry_count-1))->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) tmpLen = tmpLen + 2;
319            if (tmpLen > widest) widest = tmpLen;
320         }
321
322         fd = HeapReAlloc(GetProcessHeap(),0,fd,(entry_count+1)*sizeof(WIN32_FIND_DATAW));
323         if (fd == NULL) {
324           FindClose (hff);
325           WINE_ERR("Out of memory\n");
326           errorlevel = 1;
327           return parms->next;
328         }
329       } while (FindNextFileW(hff, (fd+entry_count)) != 0);
330       FindClose (hff);
331     }
332
333     /* Work out the actual current directory name without a trailing \ */
334     strcpyW(real_path, parms->dirName);
335     real_path[strlenW(parms->dirName)-1] = 0x00;
336
337     /* Output the results */
338     if (!bare) {
339        if (level != 0 && (entry_count > 0)) WCMD_output (newline);
340        if (!recurse || ((entry_count > 0) && done_header==FALSE)) {
341            static const WCHAR headerW[] = {'D','i','r','e','c','t','o','r','y',' ','o','f',
342                                            ' ','%','s','\n','\n','\0'};
343            WCMD_output (headerW, real_path);
344            done_header = TRUE;
345        }
346     }
347
348     /* Move to next parm */
349     parms = parms->next;
350   }
351
352   /* Handle case where everything is filtered out */
353   if (entry_count > 0) {
354
355     /* Sort the list of files */
356     qsort (fd, entry_count, sizeof(WIN32_FIND_DATAW), WCMD_dir_sort);
357
358     /* Work out the number of columns */
359     WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count, max_width, widest);
360     if (wide || orderByCol) {
361       numCols = max(1, (int)max_width / widest);
362       numRows = entry_count / numCols;
363       if (entry_count % numCols) numRows++;
364     } else {
365       numCols = 1;
366       numRows = entry_count;
367     }
368     WINE_TRACE("cols=%d, rows=%d\n", numCols, numRows);
369
370     for (rows=0; rows<numRows; rows++) {
371      BOOL addNewLine = TRUE;
372      for (cols=0; cols<numCols; cols++) {
373       WCHAR username[24];
374
375       /* Work out the index of the entry being pointed to */
376       if (orderByCol) {
377         i = (cols * numRows) + rows;
378         if (i >= entry_count) continue;
379       } else {
380         i = (rows * numCols) + cols;
381         if (i >= entry_count) continue;
382       }
383
384       /* /L convers all names to lower case */
385       if (lower) {
386           WCHAR *p = (fd+i)->cFileName;
387           while ( (*p = tolower(*p)) ) ++p;
388       }
389
390       /* /Q gets file ownership information */
391       if (usernames) {
392           strcpyW (string, inputparms->dirName);
393           strcatW (string, (fd+i)->cFileName);
394           WCMD_getfileowner(string, username, sizeof(username)/sizeof(WCHAR));
395       }
396
397       if (dirTime == Written) {
398         FileTimeToLocalFileTime (&(fd+i)->ftLastWriteTime, &ft);
399       } else if (dirTime == Access) {
400         FileTimeToLocalFileTime (&(fd+i)->ftLastAccessTime, &ft);
401       } else {
402         FileTimeToLocalFileTime (&(fd+i)->ftCreationTime, &ft);
403       }
404       FileTimeToSystemTime (&ft, &st);
405       GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
406                         sizeof(datestring)/sizeof(WCHAR));
407       GetTimeFormatW(0, TIME_NOSECONDS, &st,
408                         NULL, timestring, sizeof(timestring)/sizeof(WCHAR));
409
410       if (wide) {
411
412         tmp_width = cur_width;
413         if ((fd+i)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
414             static const WCHAR fmt[] = {'[','%','s',']','\0'};
415             WCMD_output (fmt, (fd+i)->cFileName);
416             dir_count++;
417             tmp_width = tmp_width + strlenW((fd+i)->cFileName) + 2;
418         } else {
419             static const WCHAR fmt[] = {'%','s','\0'};
420             WCMD_output (fmt, (fd+i)->cFileName);
421             tmp_width = tmp_width + strlenW((fd+i)->cFileName) ;
422             file_count++;
423             file_size.u.LowPart = (fd+i)->nFileSizeLow;
424             file_size.u.HighPart = (fd+i)->nFileSizeHigh;
425         byte_count.QuadPart += file_size.QuadPart;
426         }
427         cur_width = cur_width + widest;
428
429         if ((cur_width + widest) > max_width) {
430             cur_width = 0;
431         } else {
432             int padding = cur_width - tmp_width;
433             int toWrite = 0;
434             WCHAR temp[101];
435
436             /* Note: WCMD_output uses wvsprintf which does not allow %*
437                  so manually pad with spaces to appropriate width       */
438             strcpyW(temp, emptyW);
439             while (padding > 0) {
440                 strcatW(&temp[toWrite], spaceW);
441                 toWrite++;
442                 if (toWrite > 99) {
443                     WCMD_output(temp);
444                     toWrite = 0;
445                     strcpyW(temp, emptyW);
446                 }
447                 padding--;
448             }
449             WCMD_output(temp);
450         }
451
452       } else if ((fd+i)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
453         dir_count++;
454
455         if (!bare) {
456            WCMD_output (fmtDir, datestring, timestring);
457            if (shortname) WCMD_output (fmt2, (fd+i)->cAlternateFileName);
458            if (usernames) WCMD_output (fmt3, username);
459            WCMD_output(fmt4,(fd+i)->cFileName);
460         } else {
461            if (!((strcmpW((fd+i)->cFileName, dotW) == 0) ||
462                  (strcmpW((fd+i)->cFileName, dotdotW) == 0))) {
463               WCMD_output (fmt5, recurse?inputparms->dirName:emptyW, (fd+i)->cFileName);
464            } else {
465               addNewLine = FALSE;
466            }
467         }
468       }
469       else {
470         file_count++;
471         file_size.u.LowPart = (fd+i)->nFileSizeLow;
472         file_size.u.HighPart = (fd+i)->nFileSizeHigh;
473         byte_count.QuadPart += file_size.QuadPart;
474         if (!bare) {
475            WCMD_output (fmtFile, datestring, timestring,
476                         WCMD_filesize64(file_size.QuadPart));
477            if (shortname) WCMD_output (fmt2, (fd+i)->cAlternateFileName);
478            if (usernames) WCMD_output (fmt3, username);
479            WCMD_output(fmt4,(fd+i)->cFileName);
480         } else {
481            WCMD_output (fmt5, recurse?inputparms->dirName:emptyW, (fd+i)->cFileName);
482         }
483       }
484      }
485      if (addNewLine) WCMD_output (newline);
486      cur_width = 0;
487     }
488
489     if (!bare) {
490        if (file_count == 1) {
491          static const WCHAR fmt[] = {' ',' ',' ',' ',' ',' ',' ','1',' ','f','i','l','e',' ',
492                                      '%','2','5','s',' ','b','y','t','e','s','\n','\0'};
493          WCMD_output (fmt, WCMD_filesize64 (byte_count.QuadPart));
494        }
495        else {
496          static const WCHAR fmt[] = {'%','8','d',' ','f','i','l','e','s',' ','%','2','4','s',
497                                      ' ','b','y','t','e','s','\n','\0'};
498          WCMD_output (fmt, file_count, WCMD_filesize64 (byte_count.QuadPart));
499        }
500     }
501     byte_total = byte_total + byte_count.QuadPart;
502     file_total = file_total + file_count;
503     dir_total = dir_total + dir_count;
504
505     if (!bare && !recurse) {
506        if (dir_count == 1) {
507            static const WCHAR fmt[] = {'%','8','d',' ','d','i','r','e','c','t','o','r','y',
508                                        ' ',' ',' ',' ',' ',' ',' ',' ',' ','\0'};
509            WCMD_output (fmt, 1);
510        } else {
511            static const WCHAR fmt[] = {'%','8','d',' ','d','i','r','e','c','t','o','r','i',
512                                        'e','s','\0'};
513            WCMD_output (fmt, dir_count);
514        }
515     }
516   }
517   HeapFree(GetProcessHeap(),0,fd);
518
519   /* When recursing, look in all subdirectories for matches */
520   if (recurse) {
521     DIRECTORY_STACK *dirStack = NULL;
522     DIRECTORY_STACK *lastEntry = NULL;
523     WIN32_FIND_DATAW finddata;
524
525     /* Build path to search */
526     strcpyW(string, inputparms->dirName);
527     strcatW(string, starW);
528
529     WINE_TRACE("Recursive, looking for '%s'\n", wine_dbgstr_w(string));
530     hff = FindFirstFileW(string, &finddata);
531     if (hff != INVALID_HANDLE_VALUE) {
532       do {
533         if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
534             (strcmpW(finddata.cFileName, dotdotW) != 0) &&
535             (strcmpW(finddata.cFileName, dotW) != 0)) {
536
537           DIRECTORY_STACK *thisDir;
538           int              dirsToCopy = concurrentDirs;
539
540           /* Loop creating list of subdirs for all concurrent entries */
541           parms = inputparms;
542           while (dirsToCopy > 0) {
543             dirsToCopy--;
544
545             /* Work out search parameter in sub dir */
546             strcpyW (string, inputparms->dirName);
547             strcatW (string, finddata.cFileName);
548             strcatW (string, slashW);
549             WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(string));
550
551             /* Allocate memory, add to list */
552             thisDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
553             if (dirStack == NULL) dirStack = thisDir;
554             if (lastEntry != NULL) lastEntry->next = thisDir;
555             lastEntry = thisDir;
556             thisDir->next = NULL;
557             thisDir->dirName = HeapAlloc(GetProcessHeap(),0,
558                                          sizeof(WCHAR) * (strlenW(string)+1));
559             strcpyW(thisDir->dirName, string);
560             thisDir->fileName = HeapAlloc(GetProcessHeap(),0,
561                                           sizeof(WCHAR) * (strlenW(parms->fileName)+1));
562             strcpyW(thisDir->fileName, parms->fileName);
563             parms = parms->next;
564           }
565         }
566       } while (FindNextFileW(hff, &finddata) != 0);
567       FindClose (hff);
568
569       while (dirStack != NULL) {
570         DIRECTORY_STACK *thisDir = dirStack;
571         dirStack = WCMD_list_directory (thisDir, 1);
572         while (thisDir != dirStack) {
573           DIRECTORY_STACK *tempDir = thisDir->next;
574           HeapFree(GetProcessHeap(),0,thisDir->dirName);
575           HeapFree(GetProcessHeap(),0,thisDir->fileName);
576           HeapFree(GetProcessHeap(),0,thisDir);
577           thisDir = tempDir;
578         }
579       }
580     }
581   }
582
583   /* Handle case where everything is filtered out */
584   if ((file_total + dir_total == 0) && (level == 0)) {
585     SetLastError (ERROR_FILE_NOT_FOUND);
586     WCMD_print_error ();
587     errorlevel = 1;
588   }
589
590   return parms;
591 }
592
593 /*****************************************************************************
594  * WCMD_dir_trailer
595  *
596  * Print out the trailer for the supplied drive letter
597  */
598 static void WCMD_dir_trailer(WCHAR drive) {
599     ULARGE_INTEGER avail, total, freebytes;
600     DWORD status;
601     WCHAR driveName[4] = {'c',':','\\','\0'};
602
603     driveName[0] = drive;
604     status = GetDiskFreeSpaceExW(driveName, &avail, &total, &freebytes);
605     WINE_TRACE("Writing trailer for '%s' gave %d(%d)\n", wine_dbgstr_w(driveName),
606                status, GetLastError());
607
608     if (errorlevel==0 && !bare) {
609       if (recurse) {
610         static const WCHAR fmt1[] = {'\n',' ',' ',' ',' ',' ','T','o','t','a','l',' ','f','i','l','e','s',
611                                      ' ','l','i','s','t','e','d',':','\n','%','8','d',' ','f','i','l','e',
612                                      's','%','2','5','s',' ','b','y','t','e','s','\n','\0'};
613         static const WCHAR fmt2[] = {'%','8','d',' ','d','i','r','e','c','t','o','r','i','e','s',' ','%',
614                                      '1','8','s',' ','b','y','t','e','s',' ','f','r','e','e','\n','\n',
615                                      '\0'};
616         WCMD_output (fmt1, file_total, WCMD_filesize64 (byte_total));
617         WCMD_output (fmt2, dir_total, WCMD_filesize64 (freebytes.QuadPart));
618       } else {
619         static const WCHAR fmt[] = {' ','%','1','8','s',' ','b','y','t','e','s',' ','f','r','e','e',
620                                     '\n','\n','\0'};
621         WCMD_output (fmt, WCMD_filesize64 (freebytes.QuadPart));
622       }
623     }
624 }
625
626 /*****************************************************************************
627  * WCMD_directory
628  *
629  * List a file directory.
630  *
631  */
632
633 void WCMD_directory (WCHAR *cmd) {
634
635   WCHAR path[MAX_PATH], cwd[MAX_PATH];
636   int status, paged_mode;
637   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
638   WCHAR *p;
639   WCHAR string[MAXSTRING];
640   int   argno         = 0;
641   WCHAR *argN          = cmd;
642   WCHAR  lastDrive;
643   BOOL  trailerReqd = FALSE;
644   DIRECTORY_STACK *fullParms = NULL;
645   DIRECTORY_STACK *prevEntry = NULL;
646   DIRECTORY_STACK *thisEntry = NULL;
647   WCHAR drive[10];
648   WCHAR dir[MAX_PATH];
649   WCHAR fname[MAX_PATH];
650   WCHAR ext[MAX_PATH];
651   static const WCHAR dircmdW[] = {'D','I','R','C','M','D','\0'};
652
653   errorlevel = 0;
654
655   /* Prefill quals with (uppercased) DIRCMD env var */
656   if (GetEnvironmentVariableW(dircmdW, string, sizeof(string)/sizeof(WCHAR))) {
657     p = string;
658     while ( (*p = toupper(*p)) ) ++p;
659     strcatW(string,quals);
660     strcpyW(quals, string);
661   }
662
663   byte_total = 0;
664   file_total = dir_total = 0;
665
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;
683
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 */
691
692     if (*p=='-') {
693       negate = TRUE;
694       p++;
695     }
696
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 : */
728
729               if (*p == 'A') dirTime = Access;
730               else if (*p == 'C') dirTime = Creation;
731               else if (*p == 'W') dirTime = Written;
732
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                 errorlevel = 1;
741                 return;
742               }
743               break;
744     case 'O': p = p + 1;
745               if (*p==':') p++;  /* Skip optional : */
746               while (*p && *p != '/') {
747                 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
748                 switch (*p) {
749                 case 'N': dirOrder = Name;       break;
750                 case 'E': dirOrder = Extension;  break;
751                 case 'S': dirOrder = Size;       break;
752                 case 'D': dirOrder = Date;       break;
753                 case '-': if (*(p+1)=='G') orderGroupDirsReverse=TRUE;
754                           else orderReverse = TRUE;
755                           break;
756                 case 'G': orderGroupDirs = TRUE; break;
757                 default:
758                     SetLastError(ERROR_INVALID_PARAMETER);
759                     WCMD_print_error();
760                     errorlevel = 1;
761                     return;
762                 }
763                 p++;
764               }
765               p = p - 1; /* So when step on, move to '/' */
766               break;
767     case 'A': p = p + 1;
768               showattrs = 0;
769               attrsbits = 0;
770               if (*p==':') p++;  /* Skip optional : */
771               while (*p && *p != '/') {
772                 BOOL anegate = FALSE;
773                 ULONG mask;
774
775                 /* Note /A: - options are 'offs' not toggles */
776                 if (*p=='-') {
777                   anegate = TRUE;
778                   p++;
779                 }
780
781                 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
782                 switch (*p) {
783                 case 'D': mask = FILE_ATTRIBUTE_DIRECTORY; break;
784                 case 'H': mask = FILE_ATTRIBUTE_HIDDEN;    break;
785                 case 'S': mask = FILE_ATTRIBUTE_SYSTEM;    break;
786                 case 'R': mask = FILE_ATTRIBUTE_READONLY;  break;
787                 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE;   break;
788                 default:
789                     SetLastError(ERROR_INVALID_PARAMETER);
790                     WCMD_print_error();
791                     errorlevel = 1;
792                     return;
793                 }
794
795                 /* Keep running list of bits we care about */
796                 attrsbits |= mask;
797
798                 /* Mask shows what MUST be in the bits we care about */
799                 if (anegate) showattrs = showattrs & ~mask;
800                 else showattrs |= mask;
801
802                 p++;
803               }
804               p = p - 1; /* So when step on, move to '/' */
805               WINE_TRACE("Result: showattrs %x, bits %x\n", showattrs, attrsbits);
806               break;
807     default:
808               SetLastError(ERROR_INVALID_PARAMETER);
809               WCMD_print_error();
810               errorlevel = 1;
811               return;
812     }
813     p = p + 1;
814   }
815
816   /* Handle conflicting args and initialization */
817   if (bare || shortname) wide = FALSE;
818   if (bare) shortname = FALSE;
819   if (wide) usernames = FALSE;
820   if (orderByCol) wide = TRUE;
821
822   if (wide) {
823       if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
824           max_width = consoleInfo.dwSize.X;
825       else
826           max_width = 80;
827   }
828   if (paged_mode) {
829      WCMD_enter_paged_mode(NULL);
830   }
831
832   argno         = 0;
833   argN          = cmd;
834   GetCurrentDirectoryW(MAX_PATH, cwd);
835   strcatW(cwd, slashW);
836
837   /* Loop through all args, calculating full effective directory */
838   fullParms = NULL;
839   prevEntry = NULL;
840   while (argN) {
841     WCHAR fullname[MAXSTRING];
842     WCHAR *thisArg = WCMD_parameter(cmd, argno++, &argN, NULL);
843     if (argN && argN[0] != '/') {
844
845       WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg));
846       if (thisArg[1] == ':' && thisArg[2] == '\\') {
847         strcpyW(fullname, thisArg);
848       } else if (thisArg[1] == ':' && thisArg[2] != '\\') {
849         WCHAR envvar[4];
850         static const WCHAR envFmt[] = {'=','%','c',':','\0'};
851         wsprintfW(envvar, envFmt, thisArg[0]);
852         if (!GetEnvironmentVariableW(envvar, fullname, MAX_PATH)) {
853           static const WCHAR noEnvFmt[] = {'%','c',':','\0'};
854           wsprintfW(fullname, noEnvFmt, thisArg[0]);
855         }
856         strcatW(fullname, slashW);
857         strcatW(fullname, &thisArg[2]);
858       } else if (thisArg[0] == '\\') {
859         memcpy(fullname, cwd, 2 * sizeof(WCHAR));
860         strcpyW(fullname+2, thisArg);
861       } else {
862         strcpyW(fullname, cwd);
863         strcatW(fullname, thisArg);
864       }
865       WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname));
866
867       status = GetFullPathNameW(fullname, sizeof(path)/sizeof(WCHAR), path, NULL);
868
869       /*
870        *  If the path supplied does not include a wildcard, and the endpoint of the
871        *  path references a directory, we need to list the *contents* of that
872        *  directory not the directory file itself.
873        */
874       if ((strchrW(path, '*') == NULL) && (strchrW(path, '%') == NULL)) {
875         status = GetFileAttributesW(path);
876         if ((status != INVALID_FILE_ATTRIBUTES) && (status & FILE_ATTRIBUTE_DIRECTORY)) {
877           if (path[strlenW(path)-1] == '\\') {
878             strcatW (path, starW);
879           }
880           else {
881             static const WCHAR slashStarW[]  = {'\\','*','\0'};
882             strcatW (path, slashStarW);
883           }
884         }
885       } else {
886         /* Special case wildcard search with no extension (ie parameters ending in '.') as
887            GetFullPathName strips off the additional '.'                                  */
888         if (fullname[strlenW(fullname)-1] == '.') strcatW(path, dotW);
889       }
890
891       WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path));
892       thisEntry = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
893       if (fullParms == NULL) fullParms = thisEntry;
894       if (prevEntry != NULL) prevEntry->next = thisEntry;
895       prevEntry = thisEntry;
896       thisEntry->next = NULL;
897
898       /* Split into components */
899       WCMD_splitpath(path, drive, dir, fname, ext);
900       WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
901                  wine_dbgstr_w(drive), wine_dbgstr_w(dir),
902                  wine_dbgstr_w(fname), wine_dbgstr_w(ext));
903
904       thisEntry->dirName = HeapAlloc(GetProcessHeap(),0,
905                                      sizeof(WCHAR) * (strlenW(drive)+strlenW(dir)+1));
906       strcpyW(thisEntry->dirName, drive);
907       strcatW(thisEntry->dirName, dir);
908
909       thisEntry->fileName = HeapAlloc(GetProcessHeap(),0,
910                                      sizeof(WCHAR) * (strlenW(fname)+strlenW(ext)+1));
911       strcpyW(thisEntry->fileName, fname);
912       strcatW(thisEntry->fileName, ext);
913
914     }
915   }
916
917   /* If just 'dir' entered, a '*' parameter is assumed */
918   if (fullParms == NULL) {
919     WINE_TRACE("Inserting default '*'\n");
920     fullParms = HeapAlloc(GetProcessHeap(),0, sizeof(DIRECTORY_STACK));
921     fullParms->next = NULL;
922     fullParms->dirName = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR) * (strlenW(cwd)+1));
923     strcpyW(fullParms->dirName, cwd);
924     fullParms->fileName = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR) * 2);
925     strcpyW(fullParms->fileName, starW);
926   }
927
928   lastDrive = '?';
929   prevEntry = NULL;
930   thisEntry = fullParms;
931   trailerReqd = FALSE;
932
933   while (thisEntry != NULL) {
934
935     /* Output disk free (trailer) and volume information (header) if the drive
936        letter changes */
937     if (lastDrive != toupper(thisEntry->dirName[0])) {
938
939       /* Trailer Information */
940       if (lastDrive != '?') {
941         trailerReqd = FALSE;
942         WCMD_dir_trailer(prevEntry->dirName[0]);
943       }
944
945       lastDrive = toupper(thisEntry->dirName[0]);
946
947       if (!bare) {
948          WCHAR drive[3];
949
950          WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
951          memcpy(drive, thisEntry->dirName, 2 * sizeof(WCHAR));
952          drive[2] = 0x00;
953          status = WCMD_volume (0, drive);
954          trailerReqd = TRUE;
955          if (!status) {
956            errorlevel = 1;
957            goto exit;
958          }
959       }
960     } else {
961       static const WCHAR newLine2[] = {'\n','\n','\0'};
962       if (!bare) WCMD_output (newLine2);
963     }
964
965     /* Clear any errors from previous invocations, and process it */
966     errorlevel = 0;
967     prevEntry = thisEntry;
968     thisEntry = WCMD_list_directory (thisEntry, 0);
969   }
970
971   /* Trailer Information */
972   if (trailerReqd) {
973     WCMD_dir_trailer(prevEntry->dirName[0]);
974   }
975
976 exit:
977   if (paged_mode) WCMD_leave_paged_mode();
978
979   /* Free storage allocated for parms */
980   while (fullParms != NULL) {
981     prevEntry = fullParms;
982     fullParms = prevEntry->next;
983     HeapFree(GetProcessHeap(),0,prevEntry->dirName);
984     HeapFree(GetProcessHeap(),0,prevEntry->fileName);
985     HeapFree(GetProcessHeap(),0,prevEntry);
986   }
987 }