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