cards: Build the version.rc file separately.
[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   WIN32_FIND_DATA *filea = (WIN32_FIND_DATA *)a;
127   WIN32_FIND_DATA *fileb = (WIN32_FIND_DATA *)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     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     GetFileSecurity(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(!GetFileSecurity(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 (LookupAccountSid(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_DATA *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_DATA));
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 = FindFirstFile (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_DATA));
323         if (fd == NULL) {
324           FindClose (hff);
325           WINE_ERR("Out of memory\n");
326           errorlevel = 1;
327           return parms->next;
328         }
329       } while (FindNextFile(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_DATA), 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       GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
406                         sizeof(datestring)/sizeof(WCHAR));
407       GetTimeFormat (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_DATA 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 = FindFirstFile (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 (FindNextFile(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 = GetDiskFreeSpaceEx (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   int   argsProcessed = 0;
642   WCHAR *argN          = cmd;
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   static const WCHAR dircmdW[] = {'D','I','R','C','M','D','\0'};
653
654   errorlevel = 0;
655
656   /* Prefill quals with (uppercased) DIRCMD env var */
657   if (GetEnvironmentVariable (dircmdW, string, sizeof(string)/sizeof(WCHAR))) {
658     p = string;
659     while ( (*p = toupper(*p)) ) ++p;
660     strcatW(string,quals);
661     strcpyW(quals, string);
662   }
663
664   byte_total = 0;
665   file_total = dir_total = 0;
666
667   /* Initialize all flags to their defaults as if no DIRCMD or quals */
668   paged_mode = FALSE;
669   recurse    = FALSE;
670   wide       = FALSE;
671   bare       = FALSE;
672   lower      = FALSE;
673   shortname  = FALSE;
674   usernames  = FALSE;
675   orderByCol = FALSE;
676   separator  = TRUE;
677   dirTime = Written;
678   dirOrder = Name;
679   orderReverse = FALSE;
680   orderGroupDirs = FALSE;
681   orderGroupDirsReverse = FALSE;
682   showattrs  = 0;
683   attrsbits  = 0;
684
685   /* Handle args - Loop through so right most is the effective one */
686   /* Note: /- appears to be a negate rather than an off, eg. dir
687            /-W is wide, or dir /w /-w /-w is also wide             */
688   p = quals;
689   while (*p && (*p=='/' || *p==' ')) {
690     BOOL negate = FALSE;
691     if (*p++==' ') continue;  /* Skip / and blanks introduced through DIRCMD */
692
693     if (*p=='-') {
694       negate = TRUE;
695       p++;
696     }
697
698     WINE_TRACE("Processing arg '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
699     switch (*p) {
700     case 'P': if (negate) paged_mode = !paged_mode;
701               else paged_mode = TRUE;
702               break;
703     case 'S': if (negate) recurse = !recurse;
704               else recurse = TRUE;
705               break;
706     case 'W': if (negate) wide = !wide;
707               else wide = TRUE;
708               break;
709     case 'B': if (negate) bare = !bare;
710               else bare = TRUE;
711               break;
712     case 'L': if (negate) lower = !lower;
713               else lower = TRUE;
714               break;
715     case 'X': if (negate) shortname = !shortname;
716               else shortname = TRUE;
717               break;
718     case 'Q': if (negate) usernames = !usernames;
719               else usernames = TRUE;
720               break;
721     case 'D': if (negate) orderByCol = !orderByCol;
722               else orderByCol = TRUE;
723               break;
724     case 'C': if (negate) separator = !separator;
725               else separator = TRUE;
726               break;
727     case 'T': p = p + 1;
728               if (*p==':') p++;  /* Skip optional : */
729
730               if (*p == 'A') dirTime = Access;
731               else if (*p == 'C') dirTime = Creation;
732               else if (*p == 'W') dirTime = Written;
733
734               /* Support /T and /T: with no parms, default to written */
735               else if (*p == 0x00 || *p == '/') {
736                 dirTime = Written;
737                 p = p - 1; /* So when step on, move to '/' */
738               } else {
739                 SetLastError(ERROR_INVALID_PARAMETER);
740                 WCMD_print_error();
741                 errorlevel = 1;
742                 return;
743               }
744               break;
745     case 'O': p = p + 1;
746               if (*p==':') p++;  /* Skip optional : */
747               while (*p && *p != '/') {
748                 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
749                 switch (*p) {
750                 case 'N': dirOrder = Name;       break;
751                 case 'E': dirOrder = Extension;  break;
752                 case 'S': dirOrder = Size;       break;
753                 case 'D': dirOrder = Date;       break;
754                 case '-': if (*(p+1)=='G') orderGroupDirsReverse=TRUE;
755                           else orderReverse = TRUE;
756                           break;
757                 case 'G': orderGroupDirs = TRUE; break;
758                 default:
759                     SetLastError(ERROR_INVALID_PARAMETER);
760                     WCMD_print_error();
761                     errorlevel = 1;
762                     return;
763                 }
764                 p++;
765               }
766               p = p - 1; /* So when step on, move to '/' */
767               break;
768     case 'A': p = p + 1;
769               showattrs = 0;
770               attrsbits = 0;
771               if (*p==':') p++;  /* Skip optional : */
772               while (*p && *p != '/') {
773                 BOOL anegate = FALSE;
774                 ULONG mask;
775
776                 /* Note /A: - options are 'offs' not toggles */
777                 if (*p=='-') {
778                   anegate = TRUE;
779                   p++;
780                 }
781
782                 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
783                 switch (*p) {
784                 case 'D': mask = FILE_ATTRIBUTE_DIRECTORY; break;
785                 case 'H': mask = FILE_ATTRIBUTE_HIDDEN;    break;
786                 case 'S': mask = FILE_ATTRIBUTE_SYSTEM;    break;
787                 case 'R': mask = FILE_ATTRIBUTE_READONLY;  break;
788                 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE;   break;
789                 default:
790                     SetLastError(ERROR_INVALID_PARAMETER);
791                     WCMD_print_error();
792                     errorlevel = 1;
793                     return;
794                 }
795
796                 /* Keep running list of bits we care about */
797                 attrsbits |= mask;
798
799                 /* Mask shows what MUST be in the bits we care about */
800                 if (anegate) showattrs = showattrs & ~mask;
801                 else showattrs |= mask;
802
803                 p++;
804               }
805               p = p - 1; /* So when step on, move to '/' */
806               WINE_TRACE("Result: showattrs %x, bits %x\n", showattrs, attrsbits);
807               break;
808     default:
809               SetLastError(ERROR_INVALID_PARAMETER);
810               WCMD_print_error();
811               errorlevel = 1;
812               return;
813     }
814     p = p + 1;
815   }
816
817   /* Handle conflicting args and initialization */
818   if (bare || shortname) wide = FALSE;
819   if (bare) shortname = FALSE;
820   if (wide) usernames = FALSE;
821   if (orderByCol) wide = TRUE;
822
823   if (wide) {
824       if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
825           max_width = consoleInfo.dwSize.X;
826       else
827           max_width = 80;
828   }
829   if (paged_mode) {
830      WCMD_enter_paged_mode(NULL);
831   }
832
833   argno         = 0;
834   argsProcessed = 0;
835   argN          = cmd;
836   GetCurrentDirectory (MAX_PATH, cwd);
837   strcatW(cwd, slashW);
838
839   /* Loop through all args, calculating full effective directory */
840   fullParms = NULL;
841   prevEntry = NULL;
842   while (argN) {
843     WCHAR fullname[MAXSTRING];
844     WCHAR *thisArg = WCMD_parameter (cmd, argno++, &argN);
845     if (argN && argN[0] != '/') {
846
847       WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg));
848       if (thisArg[1] == ':' && thisArg[2] == '\\') {
849         strcpyW(fullname, thisArg);
850       } else if (thisArg[1] == ':' && thisArg[2] != '\\') {
851         WCHAR envvar[4];
852         static const WCHAR envFmt[] = {'=','%','c',':','\0'};
853         wsprintf(envvar, envFmt, thisArg[0]);
854         if (!GetEnvironmentVariable(envvar, fullname, MAX_PATH)) {
855           static const WCHAR noEnvFmt[] = {'%','c',':','\0'};
856           wsprintf(fullname, noEnvFmt, thisArg[0]);
857         }
858         strcatW(fullname, slashW);
859         strcatW(fullname, &thisArg[2]);
860       } else if (thisArg[0] == '\\') {
861         memcpy(fullname, cwd, 2 * sizeof(WCHAR));
862         strcpyW(fullname+2, thisArg);
863       } else {
864         strcpyW(fullname, cwd);
865         strcatW(fullname, thisArg);
866       }
867       WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname));
868
869       status = GetFullPathName (fullname, sizeof(path)/sizeof(WCHAR), path, NULL);
870
871       /*
872        *  If the path supplied does not include a wildcard, and the endpoint of the
873        *  path references a directory, we need to list the *contents* of that
874        *  directory not the directory file itself.
875        */
876       if ((strchrW(path, '*') == NULL) && (strchrW(path, '%') == NULL)) {
877         status = GetFileAttributes (path);
878         if ((status != INVALID_FILE_ATTRIBUTES) && (status & FILE_ATTRIBUTE_DIRECTORY)) {
879           if (path[strlenW(path)-1] == '\\') {
880             strcatW (path, starW);
881           }
882           else {
883             const WCHAR slashStarW[]  = {'\\','*','\0'};
884             strcatW (path, slashStarW);
885           }
886         }
887       } else {
888         /* Special case wildcard search with no extension (ie parameters ending in '.') as
889            GetFullPathName strips off the additional '.'                                  */
890         if (fullname[strlenW(fullname)-1] == '.') strcatW(path, dotW);
891       }
892
893       WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path));
894       thisEntry = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
895       if (fullParms == NULL) fullParms = thisEntry;
896       if (prevEntry != NULL) prevEntry->next = thisEntry;
897       prevEntry = thisEntry;
898       thisEntry->next = NULL;
899
900       /* Split into components */
901       WCMD_splitpath(path, drive, dir, fname, ext);
902       WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
903                  wine_dbgstr_w(drive), wine_dbgstr_w(dir),
904                  wine_dbgstr_w(fname), wine_dbgstr_w(ext));
905
906       thisEntry->dirName = HeapAlloc(GetProcessHeap(),0,
907                                      sizeof(WCHAR) * (strlenW(drive)+strlenW(dir)+1));
908       strcpyW(thisEntry->dirName, drive);
909       strcatW(thisEntry->dirName, dir);
910
911       thisEntry->fileName = HeapAlloc(GetProcessHeap(),0,
912                                      sizeof(WCHAR) * (strlenW(fname)+strlenW(ext)+1));
913       strcpyW(thisEntry->fileName, fname);
914       strcatW(thisEntry->fileName, ext);
915
916     }
917   }
918
919   /* If just 'dir' entered, a '*' parameter is assumed */
920   if (fullParms == NULL) {
921     WINE_TRACE("Inserting default '*'\n");
922     fullParms = HeapAlloc(GetProcessHeap(),0, sizeof(DIRECTORY_STACK));
923     fullParms->next = NULL;
924     fullParms->dirName = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR) * (strlenW(cwd)+1));
925     strcpyW(fullParms->dirName, cwd);
926     fullParms->fileName = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR) * 2);
927     strcpyW(fullParms->fileName, starW);
928   }
929
930   lastDrive = '?';
931   prevEntry = NULL;
932   thisEntry = fullParms;
933   trailerReqd = FALSE;
934
935   while (thisEntry != NULL) {
936
937     /* Output disk free (trailer) and volume information (header) if the drive
938        letter changes */
939     if (lastDrive != toupper(thisEntry->dirName[0])) {
940
941       /* Trailer Information */
942       if (lastDrive != '?') {
943         trailerReqd = FALSE;
944         WCMD_dir_trailer(prevEntry->dirName[0]);
945       }
946
947       lastDrive = toupper(thisEntry->dirName[0]);
948
949       if (!bare) {
950          WCHAR drive[3];
951
952          WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
953          memcpy(drive, thisEntry->dirName, 2 * sizeof(WCHAR));
954          drive[2] = 0x00;
955          status = WCMD_volume (0, drive);
956          trailerReqd = TRUE;
957          if (!status) {
958            errorlevel = 1;
959            goto exit;
960          }
961       }
962     } else {
963       static const WCHAR newLine2[] = {'\n','\n','\0'};
964       if (!bare) WCMD_output (newLine2);
965     }
966
967     /* Clear any errors from previous invocations, and process it */
968     errorlevel = 0;
969     prevEntry = thisEntry;
970     thisEntry = WCMD_list_directory (thisEntry, 0);
971   }
972
973   /* Trailer Information */
974   if (trailerReqd) {
975     WCMD_dir_trailer(prevEntry->dirName[0]);
976   }
977
978 exit:
979   if (paged_mode) WCMD_leave_paged_mode();
980
981   /* Free storage allocated for parms */
982   while (fullParms != NULL) {
983     prevEntry = fullParms;
984     fullParms = prevEntry->next;
985     HeapFree(GetProcessHeap(),0,prevEntry->dirName);
986     HeapFree(GetProcessHeap(),0,prevEntry->fileName);
987     HeapFree(GetProcessHeap(),0,prevEntry);
988   }
989 }