cmd.exe: Make dir a* b* or dir a* b* /s mirror windows.
[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 char * WCMD_filesize64 (ULONGLONG free);
37 char * WCMD_strrev (char *buff);
38 static void WCMD_getfileowner(char *filename, char *owner, int ownerlen);
39 static void WCMD_dir_trailer(char drive);
40
41 extern int echo_mode;
42 extern char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
43 extern DWORD errorlevel;
44
45 typedef enum _DISPLAYTIME
46 {
47     Creation = 0,
48     Access,
49     Written
50 } DISPLAYTIME;
51
52 typedef enum _DISPLAYORDER
53 {
54     Name = 0,
55     Extension,
56     Size,
57     Date
58 } DISPLAYORDER;
59
60 typedef struct _DIRECTORY_STACK
61 {
62   struct _DIRECTORY_STACK *next;
63   char  *dirName;
64   char  *fileName;
65 } DIRECTORY_STACK;
66
67 static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *parms, int level);
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     prevEntry = thisEntry;
410     thisEntry = WCMD_list_directory (thisEntry, 0);
411   }
412
413   /* Trailer Information */
414   if (trailerReqd) {
415     WCMD_dir_trailer(prevEntry->dirName[0]);
416   }
417
418 exit:
419   if (paged_mode) WCMD_leave_paged_mode();
420
421   /* Free storage allocated for parms */
422   while (fullParms != NULL) {
423     prevEntry = fullParms;
424     fullParms = prevEntry->next;
425     HeapFree(GetProcessHeap(),0,prevEntry->dirName);
426     HeapFree(GetProcessHeap(),0,prevEntry->fileName);
427     HeapFree(GetProcessHeap(),0,prevEntry);
428   }
429 }
430
431 /*****************************************************************************
432  * WCMD_list_directory
433  *
434  * List a single file directory. This function (and those below it) can be called
435  * recursively when the /S switch is used.
436  *
437  * FIXME: Assumes 24-line display for the /P qualifier.
438  */
439
440 static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int level) {
441
442   char string[1024], datestring[32], timestring[32];
443   char real_path[MAX_PATH];
444   WIN32_FIND_DATA *fd;
445   FILETIME ft;
446   SYSTEMTIME st;
447   HANDLE hff;
448   int dir_count, file_count, entry_count, i, widest, cur_width, tmp_width;
449   int numCols, numRows;
450   int rows, cols;
451   ULARGE_INTEGER byte_count, file_size;
452   DIRECTORY_STACK *parms;
453   int concurrentDirs = 0;
454   BOOL done_header = FALSE;
455
456
457   dir_count = 0;
458   file_count = 0;
459   entry_count = 0;
460   byte_count.QuadPart = 0;
461   widest = 0;
462   cur_width = 0;
463
464   /* Loop merging all the files from consecutive parms which relate to the
465      same directory. Note issuing a directory header with no contents
466      mirrors what windows does                                            */
467   parms = inputparms;
468   fd = HeapAlloc(GetProcessHeap(),0,sizeof(WIN32_FIND_DATA));
469   while (parms && strcmp(inputparms->dirName, parms->dirName) == 0) {
470     concurrentDirs++;
471
472     /* Work out the full path + filename */
473     strcpy(real_path, parms->dirName);
474     strcat(real_path, parms->fileName);
475
476     /* Load all files into an in memory structure */
477     WINE_TRACE("Looking for matches to '%s'\n", real_path);
478     hff = FindFirstFile (real_path, (fd+entry_count));
479     if (hff != INVALID_HANDLE_VALUE) {
480       do {
481         /* Skip any which are filtered out by attribute */
482         if (((fd+entry_count)->dwFileAttributes & attrsbits) != showattrs) continue;
483
484         entry_count++;
485
486         /* Keep running track of longest filename for wide output */
487         if (wide || orderByCol) {
488            int tmpLen = strlen((fd+(entry_count-1))->cFileName) + 3;
489            if ((fd+(entry_count-1))->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) tmpLen = tmpLen + 2;
490            if (tmpLen > widest) widest = tmpLen;
491         }
492
493         fd = HeapReAlloc(GetProcessHeap(),0,fd,(entry_count+1)*sizeof(WIN32_FIND_DATA));
494         if (fd == NULL) {
495           FindClose (hff);
496           WCMD_output ("Memory Allocation Error");
497           errorlevel = 1;
498           return parms->next;
499         }
500       } while (FindNextFile(hff, (fd+entry_count)) != 0);
501       FindClose (hff);
502     }
503
504     /* Work out the actual current directory name without a trailing \ */
505     strcpy(real_path, parms->dirName);
506     real_path[strlen(parms->dirName)-1] = 0x00;
507
508     /* Output the results */
509     if (!bare) {
510        if (level != 0 && (entry_count > 0)) WCMD_output ("\n");
511        if (!recurse || ((entry_count > 0) && done_header==FALSE)) {
512            WCMD_output ("Directory of %s\n\n", real_path);
513            done_header = TRUE;
514        }
515     }
516
517     /* Move to next parm */
518     parms = parms->next;
519   }
520
521   /* Handle case where everything is filtered out */
522   if (entry_count > 0) {
523
524     /* Sort the list of files */
525     qsort (fd, entry_count, sizeof(WIN32_FIND_DATA), WCMD_dir_sort);
526
527     /* Work out the number of columns */
528     WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count, max_width, widest);
529     if (wide || orderByCol) {
530       numCols = max(1, (int)max_width / widest);
531       numRows = entry_count / numCols;
532       if (entry_count % numCols) numRows++;
533     } else {
534       numCols = 1;
535       numRows = entry_count;
536     }
537     WINE_TRACE("cols=%d, rows=%d\n", numCols, numRows);
538
539     for (rows=0; rows<numRows; rows++) {
540      BOOL addNewLine = TRUE;
541      for (cols=0; cols<numCols; cols++) {
542       char username[24];
543
544       /* Work out the index of the entry being pointed to */
545       if (orderByCol) {
546         i = (cols * numRows) + rows;
547         if (i >= entry_count) continue;
548       } else {
549         i = (rows * numCols) + cols;
550         if (i >= entry_count) continue;
551       }
552
553       /* /L convers all names to lower case */
554       if (lower) {
555           char *p = (fd+i)->cFileName;
556           while ( (*p = tolower(*p)) ) ++p;
557       }
558
559       /* /Q gets file ownership information */
560       if (usernames) {
561           lstrcpy (string, inputparms->dirName);
562           lstrcat (string, (fd+i)->cFileName);
563           WCMD_getfileowner(string, username, sizeof(username));
564       }
565
566       if (dirTime == Written) {
567         FileTimeToLocalFileTime (&(fd+i)->ftLastWriteTime, &ft);
568       } else if (dirTime == Access) {
569         FileTimeToLocalFileTime (&(fd+i)->ftLastAccessTime, &ft);
570       } else {
571         FileTimeToLocalFileTime (&(fd+i)->ftCreationTime, &ft);
572       }
573       FileTimeToSystemTime (&ft, &st);
574       GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
575                         sizeof(datestring));
576       GetTimeFormat (0, TIME_NOSECONDS, &st,
577                         NULL, timestring, sizeof(timestring));
578
579       if (wide) {
580
581         tmp_width = cur_width;
582         if ((fd+i)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
583             WCMD_output ("[%s]", (fd+i)->cFileName);
584             dir_count++;
585             tmp_width = tmp_width + strlen((fd+i)->cFileName) + 2;
586         } else {
587             WCMD_output ("%s", (fd+i)->cFileName);
588             tmp_width = tmp_width + strlen((fd+i)->cFileName) ;
589             file_count++;
590             file_size.u.LowPart = (fd+i)->nFileSizeLow;
591             file_size.u.HighPart = (fd+i)->nFileSizeHigh;
592         byte_count.QuadPart += file_size.QuadPart;
593         }
594         cur_width = cur_width + widest;
595
596         if ((cur_width + widest) > max_width) {
597             cur_width = 0;
598         } else {
599             WCMD_output ("%*.s", (tmp_width - cur_width) ,"");
600         }
601
602       } else if ((fd+i)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
603         dir_count++;
604
605         if (!bare) {
606            WCMD_output ("%10s  %8s  <DIR>         ", datestring, timestring);
607            if (shortname) WCMD_output ("%-13s", (fd+i)->cAlternateFileName);
608            if (usernames) WCMD_output ("%-23s", username);
609            WCMD_output("%s",(fd+i)->cFileName);
610         } else {
611            if (!((strcmp((fd+i)->cFileName, ".") == 0) ||
612                  (strcmp((fd+i)->cFileName, "..") == 0))) {
613               WCMD_output ("%s%s", recurse?inputparms->dirName:"", (fd+i)->cFileName);
614            } else {
615               addNewLine = FALSE;
616            }
617         }
618       }
619       else {
620         file_count++;
621         file_size.u.LowPart = (fd+i)->nFileSizeLow;
622         file_size.u.HighPart = (fd+i)->nFileSizeHigh;
623         byte_count.QuadPart += file_size.QuadPart;
624         if (!bare) {
625            WCMD_output ("%10s  %8s    %10s  ", datestring, timestring,
626                         WCMD_filesize64(file_size.QuadPart));
627            if (shortname) WCMD_output ("%-13s", (fd+i)->cAlternateFileName);
628            if (usernames) WCMD_output ("%-23s", username);
629            WCMD_output("%s",(fd+i)->cFileName);
630         } else {
631            WCMD_output ("%s%s", recurse?inputparms->dirName:"", (fd+i)->cFileName);
632         }
633       }
634      }
635      if (addNewLine) WCMD_output ("\n");
636      cur_width = 0;
637     }
638
639     if (!bare) {
640        if (file_count == 1) {
641          WCMD_output ("       1 file %25s bytes\n", WCMD_filesize64 (byte_count.QuadPart));
642        }
643        else {
644          WCMD_output ("%8d files %24s bytes\n", file_count, WCMD_filesize64 (byte_count.QuadPart));
645        }
646     }
647     byte_total = byte_total + byte_count.QuadPart;
648     file_total = file_total + file_count;
649     dir_total = dir_total + dir_count;
650
651     if (!bare && !recurse) {
652        if (dir_count == 1) WCMD_output ("%8d directory         ", 1);
653        else WCMD_output ("%8d directories", dir_count);
654     }
655   }
656   HeapFree(GetProcessHeap(),0,fd);
657
658   /* When recursing, look in all subdirectories for matches */
659   if (recurse) {
660     DIRECTORY_STACK *dirStack = NULL;
661     DIRECTORY_STACK *lastEntry = NULL;
662     WIN32_FIND_DATA finddata;
663
664     /* Build path to search */
665     strcpy(string, inputparms->dirName);
666     strcat(string, "*");
667
668     WINE_TRACE("Recursive, looking for '%s'\n", string);
669     hff = FindFirstFile (string, &finddata);
670     if (hff != INVALID_HANDLE_VALUE) {
671       do {
672         if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
673             (strcmp(finddata.cFileName, "..") != 0) &&
674             (strcmp(finddata.cFileName, ".") != 0)) {
675
676           DIRECTORY_STACK *thisDir;
677           int              dirsToCopy = concurrentDirs;
678
679           /* Loop creating list of subdirs for all concurrent entries */
680           parms = inputparms;
681           while (dirsToCopy > 0) {
682             dirsToCopy--;
683
684             /* Work out search parameter in sub dir */
685             strcpy (string, inputparms->dirName);
686             strcat (string, finddata.cFileName);
687             strcat (string, "\\");
688             WINE_TRACE("Recursive, Adding to search list '%s'\n", string);
689
690             /* Allocate memory, add to list */
691             thisDir = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
692             if (dirStack == NULL) dirStack = thisDir;
693             if (lastEntry != NULL) lastEntry->next = thisDir;
694             lastEntry = thisDir;
695             thisDir->next = NULL;
696             thisDir->dirName = HeapAlloc(GetProcessHeap(),0,(strlen(string)+1));
697             strcpy(thisDir->dirName, string);
698             thisDir->fileName = HeapAlloc(GetProcessHeap(),0,(strlen(parms->fileName)+1));
699             strcpy(thisDir->fileName, parms->fileName);
700             parms = parms->next;
701           }
702         }
703       } while (FindNextFile(hff, &finddata) != 0);
704       FindClose (hff);
705
706       while (dirStack != NULL) {
707         DIRECTORY_STACK *thisDir = dirStack;
708         dirStack = WCMD_list_directory (thisDir, 1);
709         while (thisDir != dirStack) {
710           DIRECTORY_STACK *tempDir = thisDir->next;
711           HeapFree(GetProcessHeap(),0,thisDir->dirName);
712           HeapFree(GetProcessHeap(),0,thisDir->fileName);
713           HeapFree(GetProcessHeap(),0,thisDir);
714           thisDir = tempDir;
715         }
716       }
717     }
718   }
719
720   /* Handle case where everything is filtered out */
721   if ((file_total + dir_total == 0) && (level == 0)) {
722     SetLastError (ERROR_FILE_NOT_FOUND);
723     WCMD_print_error ();
724     errorlevel = 1;
725   }
726
727   return parms;
728 }
729
730 /*****************************************************************************
731  * WCMD_filesize64
732  *
733  * Convert a 64-bit number into a character string, with commas every three digits.
734  * Result is returned in a static string overwritten with each call.
735  * FIXME: There must be a better algorithm!
736  */
737
738 char * WCMD_filesize64 (ULONGLONG n) {
739
740   ULONGLONG q;
741   unsigned int r, i;
742   char *p;
743   static char buff[32];
744
745   p = buff;
746   i = -3;
747   do {
748     if (separator && ((++i)%3 == 1)) *p++ = ',';
749     q = n / 10;
750     r = n - (q * 10);
751     *p++ = r + '0';
752     *p = '\0';
753     n = q;
754   } while (n != 0);
755   WCMD_strrev (buff);
756   return buff;
757 }
758
759 /*****************************************************************************
760  * WCMD_strrev
761  *
762  * Reverse a character string in-place (strrev() is not available under unixen :-( ).
763  */
764
765 char * WCMD_strrev (char *buff) {
766
767   int r, i;
768   char b;
769
770   r = lstrlen (buff);
771   for (i=0; i<r/2; i++) {
772     b = buff[i];
773     buff[i] = buff[r-i-1];
774     buff[r-i-1] = b;
775   }
776   return (buff);
777 }
778
779
780 /*****************************************************************************
781  * WCMD_dir_sort
782  *
783  * Sort based on the /O options supplied on the command line
784  */
785 int WCMD_dir_sort (const void *a, const void *b)
786 {
787   WIN32_FIND_DATA *filea = (WIN32_FIND_DATA *)a;
788   WIN32_FIND_DATA *fileb = (WIN32_FIND_DATA *)b;
789   int result = 0;
790
791   /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
792      requested sort order for the directory components                   */
793   if (orderGroupDirs &&
794       ((filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
795        (fileb->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)))
796   {
797     BOOL aDir = filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
798     if (aDir) result = -1;
799     else result = 1;
800     if (orderGroupDirsReverse) result = -result;
801     return result;
802
803   /* Order by Name: */
804   } else if (dirOrder == Name) {
805     result = lstrcmpi(filea->cFileName, fileb->cFileName);
806
807   /* Order by Size: */
808   } else if (dirOrder == Size) {
809     ULONG64 sizea = (((ULONG64)filea->nFileSizeHigh) << 32) + filea->nFileSizeLow;
810     ULONG64 sizeb = (((ULONG64)fileb->nFileSizeHigh) << 32) + fileb->nFileSizeLow;
811     if( sizea < sizeb ) result = -1;
812     else if( sizea == sizeb ) result = 0;
813     else result = 1;
814
815   /* Order by Date: (Takes into account which date (/T option) */
816   } else if (dirOrder == Date) {
817
818     FILETIME *ft;
819     ULONG64 timea, timeb;
820
821     if (dirTime == Written) {
822       ft = &filea->ftLastWriteTime;
823       timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
824       ft = &fileb->ftLastWriteTime;
825       timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
826     } else if (dirTime == Access) {
827       ft = &filea->ftLastAccessTime;
828       timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
829       ft = &fileb->ftLastAccessTime;
830       timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
831     } else {
832       ft = &filea->ftCreationTime;
833       timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
834       ft = &fileb->ftCreationTime;
835       timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
836     }
837     if( timea < timeb ) result = -1;
838     else if( timea == timeb ) result = 0;
839     else result = 1;
840
841   /* Order by Extension: (Takes into account which date (/T option) */
842   } else if (dirOrder == Extension) {
843       char drive[10];
844       char dir[MAX_PATH];
845       char fname[MAX_PATH];
846       char extA[MAX_PATH];
847       char extB[MAX_PATH];
848
849       /* Split into components */
850       WCMD_splitpath(filea->cFileName, drive, dir, fname, extA);
851       WCMD_splitpath(fileb->cFileName, drive, dir, fname, extB);
852       result = lstrcmpi(extA, extB);
853   }
854
855   if (orderReverse) result = -result;
856   return result;
857 }
858
859 /*****************************************************************************
860  * WCMD_getfileowner
861  *
862  * Reverse a character string in-place (strrev() is not available under unixen :-( ).
863  */
864 void WCMD_getfileowner(char *filename, char *owner, int ownerlen) {
865
866     ULONG sizeNeeded = 0;
867     DWORD rc;
868     char name[MAXSTRING];
869     char domain[MAXSTRING];
870
871     /* In case of error, return empty string */
872     *owner = 0x00;
873
874     /* Find out how much space we need for the owner security descritpor */
875     GetFileSecurity(filename, OWNER_SECURITY_INFORMATION, 0, 0, &sizeNeeded);
876     rc = GetLastError();
877
878     if(rc == ERROR_INSUFFICIENT_BUFFER && sizeNeeded > 0) {
879
880         LPBYTE secBuffer;
881         PSID pSID = NULL;
882         BOOL defaulted = FALSE;
883         ULONG nameLen = MAXSTRING;
884         ULONG domainLen = MAXSTRING;
885         SID_NAME_USE nameuse;
886
887         secBuffer = (LPBYTE) HeapAlloc(GetProcessHeap(),0,sizeNeeded * sizeof(BYTE));
888         if(!secBuffer) return;
889
890         /* Get the owners security descriptor */
891         if(!GetFileSecurity(filename, OWNER_SECURITY_INFORMATION, secBuffer,
892                             sizeNeeded, &sizeNeeded)) {
893             HeapFree(GetProcessHeap(),0,secBuffer);
894             return;
895         }
896
897         /* Get the SID from the SD */
898         if(!GetSecurityDescriptorOwner(secBuffer, &pSID, &defaulted)) {
899             HeapFree(GetProcessHeap(),0,secBuffer);
900             return;
901         }
902
903         /* Convert to a username */
904         if (LookupAccountSid(NULL, pSID, name, &nameLen, domain, &domainLen, &nameuse)) {
905             snprintf(owner, ownerlen, "%s%c%s", domain, '\\', name);
906         }
907         HeapFree(GetProcessHeap(),0,secBuffer);
908     }
909     return;
910 }
911
912 /*****************************************************************************
913  * WCMD_dir_trailer
914  *
915  * Print out the trailer for the supplied drive letter
916  */
917 static void WCMD_dir_trailer(char drive) {
918     ULARGE_INTEGER avail, total, freebytes;
919     DWORD status;
920     char driveName[4] = "c:\\";
921
922     driveName[0] = drive;
923     status = GetDiskFreeSpaceEx (driveName, &avail, &total, &freebytes);
924     WINE_TRACE("Writing trailer for '%s' gave %d(%d)\n", driveName, status, GetLastError());
925
926     if (errorlevel==0 && !bare) {
927       if (recurse) {
928         WCMD_output ("\n     Total files listed:\n%8d files%25s bytes\n",
929              file_total, WCMD_filesize64 (byte_total));
930         WCMD_output ("%8d directories %18s bytes free\n\n",
931              dir_total, WCMD_filesize64 (freebytes.QuadPart));
932       } else {
933         WCMD_output (" %18s bytes free\n\n", WCMD_filesize64 (freebytes.QuadPart));
934       }
935     }
936 }