cmd.exe: Make dir support multiple parameters.
[wine] / programs / cmd / directory.c
1 /*
2  * CMD - Wine-compatible command line interface - Directory functions.
3  *
4  * Copyright (C) 1999 D A Pickles
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 /*
22  * NOTES:
23  * On entry, global variables quals, param1, param2 contain
24  * the qualifiers (uppercased and concatenated) and parameters entered, with
25  * environment-variable and batch parameter substitution already done.
26  */
27
28 #define WIN32_LEAN_AND_MEAN
29
30 #include "wcmd.h"
31 #include "wine/debug.h"
32
33 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
34
35 int WCMD_dir_sort (const void *a, const void *b);
36 static void WCMD_list_directory (DIRECTORY_STACK *parms, int level);
37 char * WCMD_filesize64 (ULONGLONG free);
38 char * WCMD_strrev (char *buff);
39 static void WCMD_getfileowner(char *filename, char *owner, int ownerlen);
40 static void WCMD_dir_trailer(char drive);
41
42 extern int echo_mode;
43 extern char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
44 extern DWORD errorlevel;
45
46 typedef enum _DISPLAYTIME
47 {
48     Creation = 0,
49     Access,
50     Written
51 } DISPLAYTIME;
52
53 typedef enum _DISPLAYORDER
54 {
55     Name = 0,
56     Extension,
57     Size,
58     Date
59 } DISPLAYORDER;
60
61 typedef struct _DIRECTORY_STACK
62 {
63   struct _DIRECTORY_STACK *next;
64   char  *dirName;
65   char  *fileName;
66 } DIRECTORY_STACK;
67
68 static int file_total, dir_total, recurse, wide, bare, max_width, lower;
69 static int shortname, usernames;
70 static ULONGLONG byte_total;
71 static DISPLAYTIME dirTime;
72 static DISPLAYORDER dirOrder;
73 static BOOL orderReverse, orderGroupDirs, orderGroupDirsReverse, orderByCol;
74 static BOOL separator;
75 static ULONG showattrs, attrsbits;
76
77 /*****************************************************************************
78  * WCMD_directory
79  *
80  * List a file directory.
81  *
82  */
83
84 void WCMD_directory (char *cmd) {
85
86   char path[MAX_PATH], cwd[MAX_PATH];
87   int status, paged_mode;
88   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
89   char *p;
90   char string[MAXSTRING];
91   int   argno         = 0;
92   int   argsProcessed = 0;
93   char *argN          = cmd;
94   char  lastDrive;
95   BOOL  trailerReqd = FALSE;
96   DIRECTORY_STACK *fullParms = NULL;
97   DIRECTORY_STACK *prevEntry = NULL;
98   DIRECTORY_STACK *thisEntry = NULL;
99   char drive[10];
100   char dir[MAX_PATH];
101   char fname[MAX_PATH];
102   char ext[MAX_PATH];
103
104   errorlevel = 0;
105
106   /* Prefill Quals with (uppercased) DIRCMD env var */
107   if (GetEnvironmentVariable ("DIRCMD", string, sizeof(string))) {
108     p = string;
109     while ( (*p = toupper(*p)) ) ++p;
110     strcat(string,quals);
111     strcpy(quals, string);
112   }
113
114   byte_total = 0;
115   file_total = dir_total = 0;
116
117   /* Initialize all flags to their defaults as if no DIRCMD or quals */
118   paged_mode = FALSE;
119   recurse    = FALSE;
120   wide       = FALSE;
121   bare       = FALSE;
122   lower      = FALSE;
123   shortname  = FALSE;
124   usernames  = FALSE;
125   orderByCol = FALSE;
126   separator  = TRUE;
127   dirTime = Written;
128   dirOrder = Name;
129   orderReverse = FALSE;
130   orderGroupDirs = FALSE;
131   orderGroupDirsReverse = FALSE;
132   showattrs  = 0;
133   attrsbits  = 0;
134
135   /* Handle args - Loop through so right most is the effective one */
136   /* Note: /- appears to be a negate rather than an off, eg. dir
137            /-W is wide, or dir /w /-w /-w is also wide             */
138   p = quals;
139   while (*p && (*p=='/' || *p==' ')) {
140     BOOL negate = FALSE;
141     if (*p++==' ') continue;  /* Skip / and blanks introduced through DIRCMD */
142
143     if (*p=='-') {
144       negate = TRUE;
145       p++;
146     }
147
148     WINE_TRACE("Processing arg '%c' (in %s)\n", *p, quals);
149     switch (*p) {
150     case 'P': if (negate) paged_mode = !paged_mode;
151               else paged_mode = TRUE;
152               break;
153     case 'S': if (negate) recurse = !recurse;
154               else recurse = TRUE;
155               break;
156     case 'W': if (negate) wide = !wide;
157               else wide = TRUE;
158               break;
159     case 'B': if (negate) bare = !bare;
160               else bare = TRUE;
161               break;
162     case 'L': if (negate) lower = !lower;
163               else lower = TRUE;
164               break;
165     case 'X': if (negate) shortname = !shortname;
166               else shortname = TRUE;
167               break;
168     case 'Q': if (negate) usernames = !usernames;
169               else usernames = TRUE;
170               break;
171     case 'D': if (negate) orderByCol = !orderByCol;
172               else orderByCol = TRUE;
173               break;
174     case 'C': if (negate) separator = !separator;
175               else separator = TRUE;
176               break;
177     case 'T': p = p + 1;
178               if (*p==':') p++;  /* Skip optional : */
179
180               if (*p == 'A') dirTime = Access;
181               else if (*p == 'C') dirTime = Creation;
182               else if (*p == 'W') dirTime = Written;
183
184               /* Support /T and /T: with no parms, default to written */
185               else if (*p == 0x00 || *p == '/') {
186                 dirTime = Written;
187                 p = p - 1; /* So when step on, move to '/' */
188               } else {
189                 SetLastError(ERROR_INVALID_PARAMETER);
190                 WCMD_print_error();
191                 errorlevel = 1;
192                 return;
193               }
194               break;
195     case 'O': p = p + 1;
196               if (*p==':') p++;  /* Skip optional : */
197               while (*p && *p != '/') {
198                 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, quals);
199                 switch (*p) {
200                 case 'N': dirOrder = Name;       break;
201                 case 'E': dirOrder = Extension;  break;
202                 case 'S': dirOrder = Size;       break;
203                 case 'D': dirOrder = Date;       break;
204                 case '-': if (*(p+1)=='G') orderGroupDirsReverse=TRUE;
205                           else orderReverse = TRUE;
206                           break;
207                 case 'G': orderGroupDirs = TRUE; break;
208                 default:
209                     SetLastError(ERROR_INVALID_PARAMETER);
210                     WCMD_print_error();
211                     errorlevel = 1;
212                     return;
213                 }
214                 p++;
215               }
216               p = p - 1; /* So when step on, move to '/' */
217               break;
218     case 'A': p = p + 1;
219               showattrs = 0;
220               attrsbits = 0;
221               if (*p==':') p++;  /* Skip optional : */
222               while (*p && *p != '/') {
223                 BOOL anegate = FALSE;
224                 ULONG mask;
225
226                 /* Note /A: - options are 'offs' not toggles */
227                 if (*p=='-') {
228                   anegate = TRUE;
229                   p++;
230                 }
231
232                 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, quals);
233                 switch (*p) {
234                 case 'D': mask = FILE_ATTRIBUTE_DIRECTORY; break;
235                 case 'H': mask = FILE_ATTRIBUTE_HIDDEN;    break;
236                 case 'S': mask = FILE_ATTRIBUTE_SYSTEM;    break;
237                 case 'R': mask = FILE_ATTRIBUTE_READONLY;  break;
238                 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE;   break;
239                 default:
240                     SetLastError(ERROR_INVALID_PARAMETER);
241                     WCMD_print_error();
242                     errorlevel = 1;
243                     return;
244                 }
245
246                 /* Keep running list of bits we care about */
247                 attrsbits |= mask;
248
249                 /* Mask shows what MUST be in the bits we care about */
250                 if (anegate) showattrs = showattrs & ~mask;
251                 else showattrs |= mask;
252
253                 p++;
254               }
255               p = p - 1; /* So when step on, move to '/' */
256               WINE_TRACE("Result: showattrs %x, bits %x\n", showattrs, attrsbits);
257               break;
258     default:
259               SetLastError(ERROR_INVALID_PARAMETER);
260               WCMD_print_error();
261               errorlevel = 1;
262               return;
263     }
264     p = p + 1;
265   }
266
267   /* Handle conflicting args and initialization */
268   if (bare || shortname) wide = FALSE;
269   if (bare) shortname = FALSE;
270   if (wide) usernames = FALSE;
271   if (orderByCol) wide = TRUE;
272
273   if (wide) {
274       if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
275           max_width = consoleInfo.dwSize.X;
276       else
277           max_width = 80;
278   }
279   if (paged_mode) {
280      WCMD_enter_paged_mode();
281   }
282
283   argno         = 0;
284   argsProcessed = 0;
285   argN          = cmd;
286   GetCurrentDirectory (1024, cwd);
287   strcat(cwd, "\\");
288
289   /* Loop through all args, calculating full effective directory */
290   fullParms = NULL;
291   prevEntry = NULL;
292   while (argN) {
293     char fullname[MAXSTRING];
294     char *thisArg = WCMD_parameter (cmd, argno++, &argN);
295     if (argN && argN[0] != '/') {
296
297       WINE_TRACE("Found parm '%s'\n", thisArg);
298       if (thisArg[1] == ':' && thisArg[2] == '\\') {
299         strcpy(fullname, thisArg);
300       } else if (thisArg[1] == ':' && thisArg[2] != '\\') {
301         char envvar[4];
302         sprintf(envvar, "=%c:", thisArg[0]);
303         if (!GetEnvironmentVariable(envvar, fullname, MAX_PATH)) {
304           sprintf(fullname, "%c:", thisArg[0]);
305         }
306         strcat(fullname, "\\");
307         strcat(fullname, &thisArg[2]);
308       } else if (thisArg[0] == '\\') {
309         strncpy(fullname, cwd, 2);
310         fullname[2] = 0x00;
311         strcat((fullname+2), thisArg);
312       } else {
313         strcpy(fullname, cwd);
314         strcat(fullname, thisArg);
315       }
316       WINE_TRACE("Using location '%s'\n", fullname);
317
318       status = GetFullPathName (fullname, sizeof(path), path, NULL);
319       WINE_TRACE("Using path '%s'\n", path);
320
321       /*
322        *  If the path supplied does not include a wildcard, and the endpoint of the
323        *  path references a directory, we need to list the *contents* of that
324        *  directory not the directory file itself.
325        */
326       if ((strchr(path, '*') == NULL) && (strchr(path, '%') == NULL)) {
327         status = GetFileAttributes (path);
328         if ((status != INVALID_FILE_ATTRIBUTES) && (status & FILE_ATTRIBUTE_DIRECTORY)) {
329           if (path[strlen(path)-1] == '\\') {
330             strcat (path, "*");
331           }
332           else {
333             strcat (path, "\\*");
334           }
335         }
336       }
337
338       thisEntry = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
339       if (fullParms == NULL) fullParms = thisEntry;
340       if (prevEntry != NULL) prevEntry->next = thisEntry;
341       prevEntry = thisEntry;
342       thisEntry->next = NULL;
343
344       /* Split into components */
345       WCMD_splitpath(path, drive, dir, fname, ext);
346       WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
347                  drive, dir, fname, ext);
348
349       thisEntry->dirName = HeapAlloc(GetProcessHeap(),0,strlen(drive)+strlen(dir)+1);
350       strcpy(thisEntry->dirName, drive);
351       strcat(thisEntry->dirName, dir);
352
353       thisEntry->fileName = HeapAlloc(GetProcessHeap(),0,strlen(fname)+strlen(ext)+1);
354       strcpy(thisEntry->fileName, fname);
355       strcat(thisEntry->fileName, ext);
356
357     }
358   }
359
360   /* If just 'dir' entered, a '*' parameter is assumed */
361   if (fullParms == NULL) {
362     WINE_TRACE("Inserting default '*'\n");
363     fullParms = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0, sizeof(DIRECTORY_STACK));
364     fullParms->next = NULL;
365     fullParms->dirName = HeapAlloc(GetProcessHeap(),0,(strlen(cwd)+1));
366     strcpy(fullParms->dirName, cwd);
367     fullParms->fileName = HeapAlloc(GetProcessHeap(),0,2);
368     strcpy(fullParms->fileName, "*");
369   }
370
371   lastDrive = '?';
372   prevEntry = NULL;
373   thisEntry = fullParms;
374   trailerReqd = FALSE;
375
376   while (thisEntry != NULL) {
377
378     /* Output disk free (trailer) and volume information (header) if the drive
379        letter changes */
380     if (lastDrive != toupper(thisEntry->dirName[0])) {
381
382       /* Trailer Information */
383       if (lastDrive != '?') {
384         trailerReqd = FALSE;
385         WCMD_dir_trailer(prevEntry->dirName[0]);
386       }
387
388       lastDrive = toupper(thisEntry->dirName[0]);
389
390       if (!bare) {
391          char drive[3];
392
393          WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
394          strncpy(drive, thisEntry->dirName, 2);
395          drive[2] = 0x00;
396          status = WCMD_volume (0, drive);
397          trailerReqd = TRUE;
398          if (!status) {
399            errorlevel = 1;
400            goto exit;
401          }
402       }
403     } else {
404       if (!bare) WCMD_output ("\n\n");
405     }
406
407     /* Clear any errors from previous invocations, and process it */
408     errorlevel = 0;
409     WCMD_list_directory (thisEntry, 0);
410
411     /* Step to next parm */
412     prevEntry = thisEntry;
413     thisEntry = thisEntry->next;
414   }
415
416   /* Trailer Information */
417   if (trailerReqd) {
418     WCMD_dir_trailer(prevEntry->dirName[0]);
419   }
420
421 exit:
422   if (paged_mode) WCMD_leave_paged_mode();
423
424   /* Free storage allocated for parms */
425   while (fullParms != NULL) {
426     prevEntry = fullParms;
427     fullParms = prevEntry->next;
428     HeapFree(GetProcessHeap(),0,prevEntry->dirName);
429     HeapFree(GetProcessHeap(),0,prevEntry->fileName);
430     HeapFree(GetProcessHeap(),0,prevEntry);
431   }
432 }
433
434 /*****************************************************************************
435  * WCMD_list_directory
436  *
437  * List a single file directory. This function (and those below it) can be called
438  * recursively when the /S switch is used.
439  *
440  * FIXME: Assumes 24-line display for the /P qualifier.
441  */
442
443 static void WCMD_list_directory (DIRECTORY_STACK *parms, int level) {
444
445   char string[1024], datestring[32], timestring[32];
446   char real_path[MAX_PATH];
447   WIN32_FIND_DATA *fd;
448   FILETIME ft;
449   SYSTEMTIME st;
450   HANDLE hff;
451   int dir_count, file_count, entry_count, i, widest, cur_width, tmp_width;
452   int numCols, numRows;
453   int rows, cols;
454   ULARGE_INTEGER byte_count, file_size;
455
456   dir_count = 0;
457   file_count = 0;
458   entry_count = 0;
459   byte_count.QuadPart = 0;
460   widest = 0;
461   cur_width = 0;
462
463   /* Work out the full path + filename */
464   strcpy(real_path, parms->dirName);
465   strcat(real_path, parms->fileName);
466
467   /* Load all files into an in memory structure */
468   fd = HeapAlloc(GetProcessHeap(),0,sizeof(WIN32_FIND_DATA));
469   WINE_TRACE("Looking for matches to '%s'\n", real_path);
470   hff = FindFirstFile (real_path, fd);
471   if (hff == INVALID_HANDLE_VALUE) {
472     entry_count = 0;
473   } else {
474     do {
475       /* Skip any which are filtered out by attribute */
476       if (((fd+entry_count)->dwFileAttributes & attrsbits) != showattrs) continue;
477
478       entry_count++;
479
480       /* Keep running track of longest filename for wide output */
481       if (wide || orderByCol) {
482          int tmpLen = strlen((fd+(entry_count-1))->cFileName) + 3;
483          if ((fd+(entry_count-1))->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) tmpLen = tmpLen + 2;
484          if (tmpLen > widest) widest = tmpLen;
485       }
486
487       fd = HeapReAlloc(GetProcessHeap(),0,fd,(entry_count+1)*sizeof(WIN32_FIND_DATA));
488       if (fd == NULL) {
489         FindClose (hff);
490         WCMD_output ("Memory Allocation Error");
491         errorlevel = 1;
492         return;
493       }
494     } while (FindNextFile(hff, (fd+entry_count)) != 0);
495     FindClose (hff);
496   }
497
498   /* Work out the actual current directory name without a trailing \ */
499   strcpy(real_path, parms->dirName);
500   real_path[strlen(parms->dirName)-1] = 0x00;
501
502   /* Output the results */
503   if (!bare) {
504      if (level != 0 && (entry_count > 0)) WCMD_output ("\n");
505      if ((entry_count > 0) || (!recurse && level == 0)) WCMD_output ("Directory of %s\n\n", real_path);
506   }
507
508   /* Handle case where everything is filtered out */
509   if (entry_count > 0) {
510
511     /* Sort the list of files */
512     qsort (fd, entry_count, sizeof(WIN32_FIND_DATA), WCMD_dir_sort);
513
514     /* Work out the number of columns */
515     WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count, max_width, widest);
516     if (wide || orderByCol) {
517       numCols = max(1, (int)max_width / widest);
518       numRows = entry_count / numCols;
519       if (entry_count % numCols) numRows++;
520     } else {
521       numCols = 1;
522       numRows = entry_count;
523     }
524     WINE_TRACE("cols=%d, rows=%d\n", numCols, numRows);
525
526     for (rows=0; rows<numRows; rows++) {
527      BOOL addNewLine = TRUE;
528      for (cols=0; cols<numCols; cols++) {
529       char username[24];
530
531       /* Work out the index of the entry being pointed to */
532       if (orderByCol) {
533         i = (cols * numRows) + rows;
534         if (i >= entry_count) continue;
535       } else {
536         i = (rows * numCols) + cols;
537         if (i >= entry_count) continue;
538       }
539
540       /* /L convers all names to lower case */
541       if (lower) {
542           char *p = (fd+i)->cFileName;
543           while ( (*p = tolower(*p)) ) ++p;
544       }
545
546       /* /Q gets file ownership information */
547       if (usernames) {
548           lstrcpy (string, parms->dirName);
549           lstrcat (string, (fd+i)->cFileName);
550           WCMD_getfileowner(string, username, sizeof(username));
551       }
552
553       if (dirTime == Written) {
554         FileTimeToLocalFileTime (&(fd+i)->ftLastWriteTime, &ft);
555       } else if (dirTime == Access) {
556         FileTimeToLocalFileTime (&(fd+i)->ftLastAccessTime, &ft);
557       } else {
558         FileTimeToLocalFileTime (&(fd+i)->ftCreationTime, &ft);
559       }
560       FileTimeToSystemTime (&ft, &st);
561       GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
562                         sizeof(datestring));
563       GetTimeFormat (0, TIME_NOSECONDS, &st,
564                         NULL, timestring, sizeof(timestring));
565
566       if (wide) {
567
568         tmp_width = cur_width;
569         if ((fd+i)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
570             WCMD_output ("[%s]", (fd+i)->cFileName);
571             dir_count++;
572             tmp_width = tmp_width + strlen((fd+i)->cFileName) + 2;
573         } else {
574             WCMD_output ("%s", (fd+i)->cFileName);
575             tmp_width = tmp_width + strlen((fd+i)->cFileName) ;
576             file_count++;
577             file_size.u.LowPart = (fd+i)->nFileSizeLow;
578             file_size.u.HighPart = (fd+i)->nFileSizeHigh;
579         byte_count.QuadPart += file_size.QuadPart;
580         }
581         cur_width = cur_width + widest;
582
583         if ((cur_width + widest) > max_width) {
584             cur_width = 0;
585         } else {
586             WCMD_output ("%*.s", (tmp_width - cur_width) ,"");
587         }
588
589       } else if ((fd+i)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
590         dir_count++;
591
592         if (!bare) {
593            WCMD_output ("%10s  %8s  <DIR>         ", datestring, timestring);
594            if (shortname) WCMD_output ("%-13s", (fd+i)->cAlternateFileName);
595            if (usernames) WCMD_output ("%-23s", username);
596            WCMD_output("%s",(fd+i)->cFileName);
597         } else {
598            if (!((strcmp((fd+i)->cFileName, ".") == 0) ||
599                  (strcmp((fd+i)->cFileName, "..") == 0))) {
600               WCMD_output ("%s%s", recurse?parms->dirName:"", (fd+i)->cFileName);
601            } else {
602               addNewLine = FALSE;
603            }
604         }
605       }
606       else {
607         file_count++;
608         file_size.u.LowPart = (fd+i)->nFileSizeLow;
609         file_size.u.HighPart = (fd+i)->nFileSizeHigh;
610         byte_count.QuadPart += file_size.QuadPart;
611         if (!bare) {
612            WCMD_output ("%10s  %8s    %10s  ", datestring, timestring,
613                         WCMD_filesize64(file_size.QuadPart));
614            if (shortname) WCMD_output ("%-13s", (fd+i)->cAlternateFileName);
615            if (usernames) WCMD_output ("%-23s", username);
616            WCMD_output("%s",(fd+i)->cFileName);
617         } else {
618            WCMD_output ("%s%s", recurse?parms->dirName:"", (fd+i)->cFileName);
619         }
620       }
621      }
622      if (addNewLine) WCMD_output ("\n");
623      cur_width = 0;
624     }
625
626     if (!bare) {
627        if (file_count == 1) {
628          WCMD_output ("       1 file %25s bytes\n", WCMD_filesize64 (byte_count.QuadPart));
629        }
630        else {
631          WCMD_output ("%8d files %24s bytes\n", file_count, WCMD_filesize64 (byte_count.QuadPart));
632        }
633     }
634     byte_total = byte_total + byte_count.QuadPart;
635     file_total = file_total + file_count;
636     dir_total = dir_total + dir_count;
637
638     if (!bare && !recurse) {
639        if (dir_count == 1) WCMD_output ("%8d directory         ", 1);
640        else WCMD_output ("%8d directories", dir_count);
641     }
642   }
643   HeapFree(GetProcessHeap(),0,fd);
644
645   /* When recursing, look in all subdirectories for matches */
646   if (recurse) {
647     DIRECTORY_STACK *dirStack = NULL;
648     DIRECTORY_STACK *lastEntry = NULL;
649     WIN32_FIND_DATA finddata;
650
651     /* Build path to search */
652     strcpy(string, parms->dirName);
653     strcat(string, "*");
654
655     WINE_TRACE("Recursive, looking for '%s'\n", string);
656     hff = FindFirstFile (string, &finddata);
657     if (hff != INVALID_HANDLE_VALUE) {
658       do {
659         if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
660             (strcmp(finddata.cFileName, "..") != 0) &&
661             (strcmp(finddata.cFileName, ".") != 0)) {
662
663           DIRECTORY_STACK *thisDir;
664
665           /* Work out search parameter in sub dir */
666           strcpy (string, parms->dirName);
667           strcat (string, finddata.cFileName);
668           strcat (string, "\\");
669           WINE_TRACE("Recursive, Adding to search list '%s'\n", string);
670
671           /* Allocate memory, add to list */
672           thisDir = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
673           if (dirStack == NULL) dirStack = thisDir;
674           if (lastEntry != NULL) lastEntry->next = thisDir;
675           lastEntry = thisDir;
676           thisDir->next = NULL;
677           thisDir->dirName = HeapAlloc(GetProcessHeap(),0,(strlen(string)+1));
678           strcpy(thisDir->dirName, string);
679           thisDir->fileName = HeapAlloc(GetProcessHeap(),0,(strlen(parms->fileName)+1));
680           strcpy(thisDir->fileName, parms->fileName);
681         }
682       } while (FindNextFile(hff, &finddata) != 0);
683       FindClose (hff);
684
685       while (dirStack != NULL) {
686         DIRECTORY_STACK *thisDir = dirStack;
687         dirStack = thisDir->next;
688
689         WCMD_list_directory (thisDir, 1);
690         HeapFree(GetProcessHeap(),0,thisDir->dirName);
691         HeapFree(GetProcessHeap(),0,thisDir->fileName);
692         HeapFree(GetProcessHeap(),0,thisDir);
693       }
694     }
695   }
696
697   /* Handle case where everything is filtered out */
698   if ((file_total + dir_total == 0) && (level == 0)) {
699     SetLastError (ERROR_FILE_NOT_FOUND);
700     WCMD_print_error ();
701     errorlevel = 1;
702   }
703 }
704
705 /*****************************************************************************
706  * WCMD_filesize64
707  *
708  * Convert a 64-bit number into a character string, with commas every three digits.
709  * Result is returned in a static string overwritten with each call.
710  * FIXME: There must be a better algorithm!
711  */
712
713 char * WCMD_filesize64 (ULONGLONG n) {
714
715   ULONGLONG q;
716   unsigned int r, i;
717   char *p;
718   static char buff[32];
719
720   p = buff;
721   i = -3;
722   do {
723     if (separator && ((++i)%3 == 1)) *p++ = ',';
724     q = n / 10;
725     r = n - (q * 10);
726     *p++ = r + '0';
727     *p = '\0';
728     n = q;
729   } while (n != 0);
730   WCMD_strrev (buff);
731   return buff;
732 }
733
734 /*****************************************************************************
735  * WCMD_strrev
736  *
737  * Reverse a character string in-place (strrev() is not available under unixen :-( ).
738  */
739
740 char * WCMD_strrev (char *buff) {
741
742   int r, i;
743   char b;
744
745   r = lstrlen (buff);
746   for (i=0; i<r/2; i++) {
747     b = buff[i];
748     buff[i] = buff[r-i-1];
749     buff[r-i-1] = b;
750   }
751   return (buff);
752 }
753
754
755 /*****************************************************************************
756  * WCMD_dir_sort
757  *
758  * Sort based on the /O options supplied on the command line
759  */
760 int WCMD_dir_sort (const void *a, const void *b)
761 {
762   WIN32_FIND_DATA *filea = (WIN32_FIND_DATA *)a;
763   WIN32_FIND_DATA *fileb = (WIN32_FIND_DATA *)b;
764   int result = 0;
765
766   /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
767      requested sort order for the directory components                   */
768   if (orderGroupDirs &&
769       ((filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
770        (fileb->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)))
771   {
772     BOOL aDir = filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
773     if (aDir) result = -1;
774     else result = 1;
775     if (orderGroupDirsReverse) result = -result;
776     return result;
777
778   /* Order by Name: */
779   } else if (dirOrder == Name) {
780     result = lstrcmpi(filea->cFileName, fileb->cFileName);
781
782   /* Order by Size: */
783   } else if (dirOrder == Size) {
784     ULONG64 sizea = (((ULONG64)filea->nFileSizeHigh) << 32) + filea->nFileSizeLow;
785     ULONG64 sizeb = (((ULONG64)fileb->nFileSizeHigh) << 32) + fileb->nFileSizeLow;
786     if( sizea < sizeb ) result = -1;
787     else if( sizea == sizeb ) result = 0;
788     else result = 1;
789
790   /* Order by Date: (Takes into account which date (/T option) */
791   } else if (dirOrder == Date) {
792
793     FILETIME *ft;
794     ULONG64 timea, timeb;
795
796     if (dirTime == Written) {
797       ft = &filea->ftLastWriteTime;
798       timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
799       ft = &fileb->ftLastWriteTime;
800       timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
801     } else if (dirTime == Access) {
802       ft = &filea->ftLastAccessTime;
803       timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
804       ft = &fileb->ftLastAccessTime;
805       timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
806     } else {
807       ft = &filea->ftCreationTime;
808       timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
809       ft = &fileb->ftCreationTime;
810       timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
811     }
812     if( timea < timeb ) result = -1;
813     else if( timea == timeb ) result = 0;
814     else result = 1;
815
816   /* Order by Extension: (Takes into account which date (/T option) */
817   } else if (dirOrder == Extension) {
818       char drive[10];
819       char dir[MAX_PATH];
820       char fname[MAX_PATH];
821       char extA[MAX_PATH];
822       char extB[MAX_PATH];
823
824       /* Split into components */
825       WCMD_splitpath(filea->cFileName, drive, dir, fname, extA);
826       WCMD_splitpath(fileb->cFileName, drive, dir, fname, extB);
827       result = lstrcmpi(extA, extB);
828   }
829
830   if (orderReverse) result = -result;
831   return result;
832 }
833
834 /*****************************************************************************
835  * WCMD_getfileowner
836  *
837  * Reverse a character string in-place (strrev() is not available under unixen :-( ).
838  */
839 void WCMD_getfileowner(char *filename, char *owner, int ownerlen) {
840
841     ULONG sizeNeeded = 0;
842     DWORD rc;
843     char name[MAXSTRING];
844     char domain[MAXSTRING];
845
846     /* In case of error, return empty string */
847     *owner = 0x00;
848
849     /* Find out how much space we need for the owner security descritpor */
850     GetFileSecurity(filename, OWNER_SECURITY_INFORMATION, 0, 0, &sizeNeeded);
851     rc = GetLastError();
852
853     if(rc == ERROR_INSUFFICIENT_BUFFER && sizeNeeded > 0) {
854
855         LPBYTE secBuffer;
856         PSID pSID = NULL;
857         BOOL defaulted = FALSE;
858         ULONG nameLen = MAXSTRING;
859         ULONG domainLen = MAXSTRING;
860         SID_NAME_USE nameuse;
861
862         secBuffer = (LPBYTE) HeapAlloc(GetProcessHeap(),0,sizeNeeded * sizeof(BYTE));
863         if(!secBuffer) return;
864
865         /* Get the owners security descriptor */
866         if(!GetFileSecurity(filename, OWNER_SECURITY_INFORMATION, secBuffer,
867                             sizeNeeded, &sizeNeeded)) {
868             HeapFree(GetProcessHeap(),0,secBuffer);
869             return;
870         }
871
872         /* Get the SID from the SD */
873         if(!GetSecurityDescriptorOwner(secBuffer, &pSID, &defaulted)) {
874             HeapFree(GetProcessHeap(),0,secBuffer);
875             return;
876         }
877
878         /* Convert to a username */
879         if (LookupAccountSid(NULL, pSID, name, &nameLen, domain, &domainLen, &nameuse)) {
880             snprintf(owner, ownerlen, "%s%c%s", domain, '\\', name);
881         }
882         HeapFree(GetProcessHeap(),0,secBuffer);
883     }
884     return;
885 }
886
887 /*****************************************************************************
888  * WCMD_dir_trailer
889  *
890  * Print out the trailer for the supplied drive letter
891  */
892 static void WCMD_dir_trailer(char drive) {
893     ULARGE_INTEGER avail, total, freebytes;
894     DWORD status;
895     char driveName[4] = "c:\\";
896
897     driveName[0] = drive;
898     status = GetDiskFreeSpaceEx (driveName, &avail, &total, &freebytes);
899     WINE_TRACE("Writing trailer for '%s' gave %d(%d)\n", driveName, status, GetLastError());
900
901     if (errorlevel==0 && !bare) {
902       if (recurse) {
903         WCMD_output ("\n     Total files listed:\n%8d files%25s bytes\n",
904              file_total, WCMD_filesize64 (byte_total));
905         WCMD_output ("%8d directories %18s bytes free\n\n",
906              dir_total, WCMD_filesize64 (freebytes.QuadPart));
907       } else {
908         WCMD_output (" %18s bytes free\n\n", WCMD_filesize64 (freebytes.QuadPart));
909       }
910     }
911 }