cmd.exe: Support for DEL filename /s.
[wine/testsucceed.git] / programs / cmd / directory.c
blob24c7c6c6191b0049bea2926bae2921c00e44f95e
1 /*
2 * CMD - Wine-compatible command line interface - Directory functions.
4 * Copyright (C) 1999 D A Pickles
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.
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.
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
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.
28 #define WIN32_LEAN_AND_MEAN
30 #include "wcmd.h"
31 #include "wine/debug.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
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);
41 extern int echo_mode;
42 extern char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
43 extern DWORD errorlevel;
45 typedef enum _DISPLAYTIME
47 Creation = 0,
48 Access,
49 Written
50 } DISPLAYTIME;
52 typedef enum _DISPLAYORDER
54 Name = 0,
55 Extension,
56 Size,
57 Date
58 } DISPLAYORDER;
60 static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *parms, int level);
61 static int file_total, dir_total, recurse, wide, bare, max_width, lower;
62 static int shortname, usernames;
63 static ULONGLONG byte_total;
64 static DISPLAYTIME dirTime;
65 static DISPLAYORDER dirOrder;
66 static BOOL orderReverse, orderGroupDirs, orderGroupDirsReverse, orderByCol;
67 static BOOL separator;
68 static ULONG showattrs, attrsbits;
70 /*****************************************************************************
71 * WCMD_directory
73 * List a file directory.
77 void WCMD_directory (char *cmd) {
79 char path[MAX_PATH], cwd[MAX_PATH];
80 int status, paged_mode;
81 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
82 char *p;
83 char string[MAXSTRING];
84 int argno = 0;
85 int argsProcessed = 0;
86 char *argN = cmd;
87 char lastDrive;
88 BOOL trailerReqd = FALSE;
89 DIRECTORY_STACK *fullParms = NULL;
90 DIRECTORY_STACK *prevEntry = NULL;
91 DIRECTORY_STACK *thisEntry = NULL;
92 char drive[10];
93 char dir[MAX_PATH];
94 char fname[MAX_PATH];
95 char ext[MAX_PATH];
97 errorlevel = 0;
99 /* Prefill Quals with (uppercased) DIRCMD env var */
100 if (GetEnvironmentVariable ("DIRCMD", string, sizeof(string))) {
101 p = string;
102 while ( (*p = toupper(*p)) ) ++p;
103 strcat(string,quals);
104 strcpy(quals, string);
107 byte_total = 0;
108 file_total = dir_total = 0;
110 /* Initialize all flags to their defaults as if no DIRCMD or quals */
111 paged_mode = FALSE;
112 recurse = FALSE;
113 wide = FALSE;
114 bare = FALSE;
115 lower = FALSE;
116 shortname = FALSE;
117 usernames = FALSE;
118 orderByCol = FALSE;
119 separator = TRUE;
120 dirTime = Written;
121 dirOrder = Name;
122 orderReverse = FALSE;
123 orderGroupDirs = FALSE;
124 orderGroupDirsReverse = FALSE;
125 showattrs = 0;
126 attrsbits = 0;
128 /* Handle args - Loop through so right most is the effective one */
129 /* Note: /- appears to be a negate rather than an off, eg. dir
130 /-W is wide, or dir /w /-w /-w is also wide */
131 p = quals;
132 while (*p && (*p=='/' || *p==' ')) {
133 BOOL negate = FALSE;
134 if (*p++==' ') continue; /* Skip / and blanks introduced through DIRCMD */
136 if (*p=='-') {
137 negate = TRUE;
138 p++;
141 WINE_TRACE("Processing arg '%c' (in %s)\n", *p, quals);
142 switch (*p) {
143 case 'P': if (negate) paged_mode = !paged_mode;
144 else paged_mode = TRUE;
145 break;
146 case 'S': if (negate) recurse = !recurse;
147 else recurse = TRUE;
148 break;
149 case 'W': if (negate) wide = !wide;
150 else wide = TRUE;
151 break;
152 case 'B': if (negate) bare = !bare;
153 else bare = TRUE;
154 break;
155 case 'L': if (negate) lower = !lower;
156 else lower = TRUE;
157 break;
158 case 'X': if (negate) shortname = !shortname;
159 else shortname = TRUE;
160 break;
161 case 'Q': if (negate) usernames = !usernames;
162 else usernames = TRUE;
163 break;
164 case 'D': if (negate) orderByCol = !orderByCol;
165 else orderByCol = TRUE;
166 break;
167 case 'C': if (negate) separator = !separator;
168 else separator = TRUE;
169 break;
170 case 'T': p = p + 1;
171 if (*p==':') p++; /* Skip optional : */
173 if (*p == 'A') dirTime = Access;
174 else if (*p == 'C') dirTime = Creation;
175 else if (*p == 'W') dirTime = Written;
177 /* Support /T and /T: with no parms, default to written */
178 else if (*p == 0x00 || *p == '/') {
179 dirTime = Written;
180 p = p - 1; /* So when step on, move to '/' */
181 } else {
182 SetLastError(ERROR_INVALID_PARAMETER);
183 WCMD_print_error();
184 errorlevel = 1;
185 return;
187 break;
188 case 'O': p = p + 1;
189 if (*p==':') p++; /* Skip optional : */
190 while (*p && *p != '/') {
191 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, quals);
192 switch (*p) {
193 case 'N': dirOrder = Name; break;
194 case 'E': dirOrder = Extension; break;
195 case 'S': dirOrder = Size; break;
196 case 'D': dirOrder = Date; break;
197 case '-': if (*(p+1)=='G') orderGroupDirsReverse=TRUE;
198 else orderReverse = TRUE;
199 break;
200 case 'G': orderGroupDirs = TRUE; break;
201 default:
202 SetLastError(ERROR_INVALID_PARAMETER);
203 WCMD_print_error();
204 errorlevel = 1;
205 return;
207 p++;
209 p = p - 1; /* So when step on, move to '/' */
210 break;
211 case 'A': p = p + 1;
212 showattrs = 0;
213 attrsbits = 0;
214 if (*p==':') p++; /* Skip optional : */
215 while (*p && *p != '/') {
216 BOOL anegate = FALSE;
217 ULONG mask;
219 /* Note /A: - options are 'offs' not toggles */
220 if (*p=='-') {
221 anegate = TRUE;
222 p++;
225 WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, quals);
226 switch (*p) {
227 case 'D': mask = FILE_ATTRIBUTE_DIRECTORY; break;
228 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
229 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
230 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
231 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
232 default:
233 SetLastError(ERROR_INVALID_PARAMETER);
234 WCMD_print_error();
235 errorlevel = 1;
236 return;
239 /* Keep running list of bits we care about */
240 attrsbits |= mask;
242 /* Mask shows what MUST be in the bits we care about */
243 if (anegate) showattrs = showattrs & ~mask;
244 else showattrs |= mask;
246 p++;
248 p = p - 1; /* So when step on, move to '/' */
249 WINE_TRACE("Result: showattrs %x, bits %x\n", showattrs, attrsbits);
250 break;
251 default:
252 SetLastError(ERROR_INVALID_PARAMETER);
253 WCMD_print_error();
254 errorlevel = 1;
255 return;
257 p = p + 1;
260 /* Handle conflicting args and initialization */
261 if (bare || shortname) wide = FALSE;
262 if (bare) shortname = FALSE;
263 if (wide) usernames = FALSE;
264 if (orderByCol) wide = TRUE;
266 if (wide) {
267 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
268 max_width = consoleInfo.dwSize.X;
269 else
270 max_width = 80;
272 if (paged_mode) {
273 WCMD_enter_paged_mode(NULL);
276 argno = 0;
277 argsProcessed = 0;
278 argN = cmd;
279 GetCurrentDirectory (1024, cwd);
280 strcat(cwd, "\\");
282 /* Loop through all args, calculating full effective directory */
283 fullParms = NULL;
284 prevEntry = NULL;
285 while (argN) {
286 char fullname[MAXSTRING];
287 char *thisArg = WCMD_parameter (cmd, argno++, &argN);
288 if (argN && argN[0] != '/') {
290 WINE_TRACE("Found parm '%s'\n", thisArg);
291 if (thisArg[1] == ':' && thisArg[2] == '\\') {
292 strcpy(fullname, thisArg);
293 } else if (thisArg[1] == ':' && thisArg[2] != '\\') {
294 char envvar[4];
295 sprintf(envvar, "=%c:", thisArg[0]);
296 if (!GetEnvironmentVariable(envvar, fullname, MAX_PATH)) {
297 sprintf(fullname, "%c:", thisArg[0]);
299 strcat(fullname, "\\");
300 strcat(fullname, &thisArg[2]);
301 } else if (thisArg[0] == '\\') {
302 strncpy(fullname, cwd, 2);
303 fullname[2] = 0x00;
304 strcat((fullname+2), thisArg);
305 } else {
306 strcpy(fullname, cwd);
307 strcat(fullname, thisArg);
309 WINE_TRACE("Using location '%s'\n", fullname);
311 status = GetFullPathName (fullname, sizeof(path), path, NULL);
314 * If the path supplied does not include a wildcard, and the endpoint of the
315 * path references a directory, we need to list the *contents* of that
316 * directory not the directory file itself.
318 if ((strchr(path, '*') == NULL) && (strchr(path, '%') == NULL)) {
319 status = GetFileAttributes (path);
320 if ((status != INVALID_FILE_ATTRIBUTES) && (status & FILE_ATTRIBUTE_DIRECTORY)) {
321 if (path[strlen(path)-1] == '\\') {
322 strcat (path, "*");
324 else {
325 strcat (path, "\\*");
328 } else {
329 /* Special case wildcard search with no extension (ie parameters ending in '.') as
330 GetFullPathName strips off the additional '.' */
331 if (fullname[strlen(fullname)-1] == '.') strcat(path, ".");
334 WINE_TRACE("Using path '%s'\n", path);
335 thisEntry = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
336 if (fullParms == NULL) fullParms = thisEntry;
337 if (prevEntry != NULL) prevEntry->next = thisEntry;
338 prevEntry = thisEntry;
339 thisEntry->next = NULL;
341 /* Split into components */
342 WCMD_splitpath(path, drive, dir, fname, ext);
343 WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
344 drive, dir, fname, ext);
346 thisEntry->dirName = HeapAlloc(GetProcessHeap(),0,strlen(drive)+strlen(dir)+1);
347 strcpy(thisEntry->dirName, drive);
348 strcat(thisEntry->dirName, dir);
350 thisEntry->fileName = HeapAlloc(GetProcessHeap(),0,strlen(fname)+strlen(ext)+1);
351 strcpy(thisEntry->fileName, fname);
352 strcat(thisEntry->fileName, ext);
357 /* If just 'dir' entered, a '*' parameter is assumed */
358 if (fullParms == NULL) {
359 WINE_TRACE("Inserting default '*'\n");
360 fullParms = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0, sizeof(DIRECTORY_STACK));
361 fullParms->next = NULL;
362 fullParms->dirName = HeapAlloc(GetProcessHeap(),0,(strlen(cwd)+1));
363 strcpy(fullParms->dirName, cwd);
364 fullParms->fileName = HeapAlloc(GetProcessHeap(),0,2);
365 strcpy(fullParms->fileName, "*");
368 lastDrive = '?';
369 prevEntry = NULL;
370 thisEntry = fullParms;
371 trailerReqd = FALSE;
373 while (thisEntry != NULL) {
375 /* Output disk free (trailer) and volume information (header) if the drive
376 letter changes */
377 if (lastDrive != toupper(thisEntry->dirName[0])) {
379 /* Trailer Information */
380 if (lastDrive != '?') {
381 trailerReqd = FALSE;
382 WCMD_dir_trailer(prevEntry->dirName[0]);
385 lastDrive = toupper(thisEntry->dirName[0]);
387 if (!bare) {
388 char drive[3];
390 WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
391 strncpy(drive, thisEntry->dirName, 2);
392 drive[2] = 0x00;
393 status = WCMD_volume (0, drive);
394 trailerReqd = TRUE;
395 if (!status) {
396 errorlevel = 1;
397 goto exit;
400 } else {
401 if (!bare) WCMD_output ("\n\n");
404 /* Clear any errors from previous invocations, and process it */
405 errorlevel = 0;
406 prevEntry = thisEntry;
407 thisEntry = WCMD_list_directory (thisEntry, 0);
410 /* Trailer Information */
411 if (trailerReqd) {
412 WCMD_dir_trailer(prevEntry->dirName[0]);
415 exit:
416 if (paged_mode) WCMD_leave_paged_mode();
418 /* Free storage allocated for parms */
419 while (fullParms != NULL) {
420 prevEntry = fullParms;
421 fullParms = prevEntry->next;
422 HeapFree(GetProcessHeap(),0,prevEntry->dirName);
423 HeapFree(GetProcessHeap(),0,prevEntry->fileName);
424 HeapFree(GetProcessHeap(),0,prevEntry);
428 /*****************************************************************************
429 * WCMD_list_directory
431 * List a single file directory. This function (and those below it) can be called
432 * recursively when the /S switch is used.
434 * FIXME: Assumes 24-line display for the /P qualifier.
437 static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int level) {
439 char string[1024], datestring[32], timestring[32];
440 char real_path[MAX_PATH];
441 WIN32_FIND_DATA *fd;
442 FILETIME ft;
443 SYSTEMTIME st;
444 HANDLE hff;
445 int dir_count, file_count, entry_count, i, widest, cur_width, tmp_width;
446 int numCols, numRows;
447 int rows, cols;
448 ULARGE_INTEGER byte_count, file_size;
449 DIRECTORY_STACK *parms;
450 int concurrentDirs = 0;
451 BOOL done_header = FALSE;
454 dir_count = 0;
455 file_count = 0;
456 entry_count = 0;
457 byte_count.QuadPart = 0;
458 widest = 0;
459 cur_width = 0;
461 /* Loop merging all the files from consecutive parms which relate to the
462 same directory. Note issuing a directory header with no contents
463 mirrors what windows does */
464 parms = inputparms;
465 fd = HeapAlloc(GetProcessHeap(),0,sizeof(WIN32_FIND_DATA));
466 while (parms && strcmp(inputparms->dirName, parms->dirName) == 0) {
467 concurrentDirs++;
469 /* Work out the full path + filename */
470 strcpy(real_path, parms->dirName);
471 strcat(real_path, parms->fileName);
473 /* Load all files into an in memory structure */
474 WINE_TRACE("Looking for matches to '%s'\n", real_path);
475 hff = FindFirstFile (real_path, (fd+entry_count));
476 if (hff != INVALID_HANDLE_VALUE) {
477 do {
478 /* Skip any which are filtered out by attribute */
479 if (((fd+entry_count)->dwFileAttributes & attrsbits) != showattrs) continue;
481 entry_count++;
483 /* Keep running track of longest filename for wide output */
484 if (wide || orderByCol) {
485 int tmpLen = strlen((fd+(entry_count-1))->cFileName) + 3;
486 if ((fd+(entry_count-1))->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) tmpLen = tmpLen + 2;
487 if (tmpLen > widest) widest = tmpLen;
490 fd = HeapReAlloc(GetProcessHeap(),0,fd,(entry_count+1)*sizeof(WIN32_FIND_DATA));
491 if (fd == NULL) {
492 FindClose (hff);
493 WCMD_output ("Memory Allocation Error");
494 errorlevel = 1;
495 return parms->next;
497 } while (FindNextFile(hff, (fd+entry_count)) != 0);
498 FindClose (hff);
501 /* Work out the actual current directory name without a trailing \ */
502 strcpy(real_path, parms->dirName);
503 real_path[strlen(parms->dirName)-1] = 0x00;
505 /* Output the results */
506 if (!bare) {
507 if (level != 0 && (entry_count > 0)) WCMD_output ("\n");
508 if (!recurse || ((entry_count > 0) && done_header==FALSE)) {
509 WCMD_output ("Directory of %s\n\n", real_path);
510 done_header = TRUE;
514 /* Move to next parm */
515 parms = parms->next;
518 /* Handle case where everything is filtered out */
519 if (entry_count > 0) {
521 /* Sort the list of files */
522 qsort (fd, entry_count, sizeof(WIN32_FIND_DATA), WCMD_dir_sort);
524 /* Work out the number of columns */
525 WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count, max_width, widest);
526 if (wide || orderByCol) {
527 numCols = max(1, (int)max_width / widest);
528 numRows = entry_count / numCols;
529 if (entry_count % numCols) numRows++;
530 } else {
531 numCols = 1;
532 numRows = entry_count;
534 WINE_TRACE("cols=%d, rows=%d\n", numCols, numRows);
536 for (rows=0; rows<numRows; rows++) {
537 BOOL addNewLine = TRUE;
538 for (cols=0; cols<numCols; cols++) {
539 char username[24];
541 /* Work out the index of the entry being pointed to */
542 if (orderByCol) {
543 i = (cols * numRows) + rows;
544 if (i >= entry_count) continue;
545 } else {
546 i = (rows * numCols) + cols;
547 if (i >= entry_count) continue;
550 /* /L convers all names to lower case */
551 if (lower) {
552 char *p = (fd+i)->cFileName;
553 while ( (*p = tolower(*p)) ) ++p;
556 /* /Q gets file ownership information */
557 if (usernames) {
558 lstrcpy (string, inputparms->dirName);
559 lstrcat (string, (fd+i)->cFileName);
560 WCMD_getfileowner(string, username, sizeof(username));
563 if (dirTime == Written) {
564 FileTimeToLocalFileTime (&(fd+i)->ftLastWriteTime, &ft);
565 } else if (dirTime == Access) {
566 FileTimeToLocalFileTime (&(fd+i)->ftLastAccessTime, &ft);
567 } else {
568 FileTimeToLocalFileTime (&(fd+i)->ftCreationTime, &ft);
570 FileTimeToSystemTime (&ft, &st);
571 GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
572 sizeof(datestring));
573 GetTimeFormat (0, TIME_NOSECONDS, &st,
574 NULL, timestring, sizeof(timestring));
576 if (wide) {
578 tmp_width = cur_width;
579 if ((fd+i)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
580 WCMD_output ("[%s]", (fd+i)->cFileName);
581 dir_count++;
582 tmp_width = tmp_width + strlen((fd+i)->cFileName) + 2;
583 } else {
584 WCMD_output ("%s", (fd+i)->cFileName);
585 tmp_width = tmp_width + strlen((fd+i)->cFileName) ;
586 file_count++;
587 file_size.u.LowPart = (fd+i)->nFileSizeLow;
588 file_size.u.HighPart = (fd+i)->nFileSizeHigh;
589 byte_count.QuadPart += file_size.QuadPart;
591 cur_width = cur_width + widest;
593 if ((cur_width + widest) > max_width) {
594 cur_width = 0;
595 } else {
596 WCMD_output ("%*.s", (tmp_width - cur_width) ,"");
599 } else if ((fd+i)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
600 dir_count++;
602 if (!bare) {
603 WCMD_output ("%10s %8s <DIR> ", datestring, timestring);
604 if (shortname) WCMD_output ("%-13s", (fd+i)->cAlternateFileName);
605 if (usernames) WCMD_output ("%-23s", username);
606 WCMD_output("%s",(fd+i)->cFileName);
607 } else {
608 if (!((strcmp((fd+i)->cFileName, ".") == 0) ||
609 (strcmp((fd+i)->cFileName, "..") == 0))) {
610 WCMD_output ("%s%s", recurse?inputparms->dirName:"", (fd+i)->cFileName);
611 } else {
612 addNewLine = FALSE;
616 else {
617 file_count++;
618 file_size.u.LowPart = (fd+i)->nFileSizeLow;
619 file_size.u.HighPart = (fd+i)->nFileSizeHigh;
620 byte_count.QuadPart += file_size.QuadPart;
621 if (!bare) {
622 WCMD_output ("%10s %8s %10s ", datestring, timestring,
623 WCMD_filesize64(file_size.QuadPart));
624 if (shortname) WCMD_output ("%-13s", (fd+i)->cAlternateFileName);
625 if (usernames) WCMD_output ("%-23s", username);
626 WCMD_output("%s",(fd+i)->cFileName);
627 } else {
628 WCMD_output ("%s%s", recurse?inputparms->dirName:"", (fd+i)->cFileName);
632 if (addNewLine) WCMD_output ("\n");
633 cur_width = 0;
636 if (!bare) {
637 if (file_count == 1) {
638 WCMD_output (" 1 file %25s bytes\n", WCMD_filesize64 (byte_count.QuadPart));
640 else {
641 WCMD_output ("%8d files %24s bytes\n", file_count, WCMD_filesize64 (byte_count.QuadPart));
644 byte_total = byte_total + byte_count.QuadPart;
645 file_total = file_total + file_count;
646 dir_total = dir_total + dir_count;
648 if (!bare && !recurse) {
649 if (dir_count == 1) WCMD_output ("%8d directory ", 1);
650 else WCMD_output ("%8d directories", dir_count);
653 HeapFree(GetProcessHeap(),0,fd);
655 /* When recursing, look in all subdirectories for matches */
656 if (recurse) {
657 DIRECTORY_STACK *dirStack = NULL;
658 DIRECTORY_STACK *lastEntry = NULL;
659 WIN32_FIND_DATA finddata;
661 /* Build path to search */
662 strcpy(string, inputparms->dirName);
663 strcat(string, "*");
665 WINE_TRACE("Recursive, looking for '%s'\n", string);
666 hff = FindFirstFile (string, &finddata);
667 if (hff != INVALID_HANDLE_VALUE) {
668 do {
669 if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
670 (strcmp(finddata.cFileName, "..") != 0) &&
671 (strcmp(finddata.cFileName, ".") != 0)) {
673 DIRECTORY_STACK *thisDir;
674 int dirsToCopy = concurrentDirs;
676 /* Loop creating list of subdirs for all concurrent entries */
677 parms = inputparms;
678 while (dirsToCopy > 0) {
679 dirsToCopy--;
681 /* Work out search parameter in sub dir */
682 strcpy (string, inputparms->dirName);
683 strcat (string, finddata.cFileName);
684 strcat (string, "\\");
685 WINE_TRACE("Recursive, Adding to search list '%s'\n", string);
687 /* Allocate memory, add to list */
688 thisDir = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
689 if (dirStack == NULL) dirStack = thisDir;
690 if (lastEntry != NULL) lastEntry->next = thisDir;
691 lastEntry = thisDir;
692 thisDir->next = NULL;
693 thisDir->dirName = HeapAlloc(GetProcessHeap(),0,(strlen(string)+1));
694 strcpy(thisDir->dirName, string);
695 thisDir->fileName = HeapAlloc(GetProcessHeap(),0,(strlen(parms->fileName)+1));
696 strcpy(thisDir->fileName, parms->fileName);
697 parms = parms->next;
700 } while (FindNextFile(hff, &finddata) != 0);
701 FindClose (hff);
703 while (dirStack != NULL) {
704 DIRECTORY_STACK *thisDir = dirStack;
705 dirStack = WCMD_list_directory (thisDir, 1);
706 while (thisDir != dirStack) {
707 DIRECTORY_STACK *tempDir = thisDir->next;
708 HeapFree(GetProcessHeap(),0,thisDir->dirName);
709 HeapFree(GetProcessHeap(),0,thisDir->fileName);
710 HeapFree(GetProcessHeap(),0,thisDir);
711 thisDir = tempDir;
717 /* Handle case where everything is filtered out */
718 if ((file_total + dir_total == 0) && (level == 0)) {
719 SetLastError (ERROR_FILE_NOT_FOUND);
720 WCMD_print_error ();
721 errorlevel = 1;
724 return parms;
727 /*****************************************************************************
728 * WCMD_filesize64
730 * Convert a 64-bit number into a character string, with commas every three digits.
731 * Result is returned in a static string overwritten with each call.
732 * FIXME: There must be a better algorithm!
735 char * WCMD_filesize64 (ULONGLONG n) {
737 ULONGLONG q;
738 unsigned int r, i;
739 char *p;
740 static char buff[32];
742 p = buff;
743 i = -3;
744 do {
745 if (separator && ((++i)%3 == 1)) *p++ = ',';
746 q = n / 10;
747 r = n - (q * 10);
748 *p++ = r + '0';
749 *p = '\0';
750 n = q;
751 } while (n != 0);
752 WCMD_strrev (buff);
753 return buff;
756 /*****************************************************************************
757 * WCMD_strrev
759 * Reverse a character string in-place (strrev() is not available under unixen :-( ).
762 char * WCMD_strrev (char *buff) {
764 int r, i;
765 char b;
767 r = lstrlen (buff);
768 for (i=0; i<r/2; i++) {
769 b = buff[i];
770 buff[i] = buff[r-i-1];
771 buff[r-i-1] = b;
773 return (buff);
777 /*****************************************************************************
778 * WCMD_dir_sort
780 * Sort based on the /O options supplied on the command line
782 int WCMD_dir_sort (const void *a, const void *b)
784 WIN32_FIND_DATA *filea = (WIN32_FIND_DATA *)a;
785 WIN32_FIND_DATA *fileb = (WIN32_FIND_DATA *)b;
786 int result = 0;
788 /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
789 requested sort order for the directory components */
790 if (orderGroupDirs &&
791 ((filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
792 (fileb->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)))
794 BOOL aDir = filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
795 if (aDir) result = -1;
796 else result = 1;
797 if (orderGroupDirsReverse) result = -result;
798 return result;
800 /* Order by Name: */
801 } else if (dirOrder == Name) {
802 result = lstrcmpi(filea->cFileName, fileb->cFileName);
804 /* Order by Size: */
805 } else if (dirOrder == Size) {
806 ULONG64 sizea = (((ULONG64)filea->nFileSizeHigh) << 32) + filea->nFileSizeLow;
807 ULONG64 sizeb = (((ULONG64)fileb->nFileSizeHigh) << 32) + fileb->nFileSizeLow;
808 if( sizea < sizeb ) result = -1;
809 else if( sizea == sizeb ) result = 0;
810 else result = 1;
812 /* Order by Date: (Takes into account which date (/T option) */
813 } else if (dirOrder == Date) {
815 FILETIME *ft;
816 ULONG64 timea, timeb;
818 if (dirTime == Written) {
819 ft = &filea->ftLastWriteTime;
820 timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
821 ft = &fileb->ftLastWriteTime;
822 timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
823 } else if (dirTime == Access) {
824 ft = &filea->ftLastAccessTime;
825 timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
826 ft = &fileb->ftLastAccessTime;
827 timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
828 } else {
829 ft = &filea->ftCreationTime;
830 timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
831 ft = &fileb->ftCreationTime;
832 timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
834 if( timea < timeb ) result = -1;
835 else if( timea == timeb ) result = 0;
836 else result = 1;
838 /* Order by Extension: (Takes into account which date (/T option) */
839 } else if (dirOrder == Extension) {
840 char drive[10];
841 char dir[MAX_PATH];
842 char fname[MAX_PATH];
843 char extA[MAX_PATH];
844 char extB[MAX_PATH];
846 /* Split into components */
847 WCMD_splitpath(filea->cFileName, drive, dir, fname, extA);
848 WCMD_splitpath(fileb->cFileName, drive, dir, fname, extB);
849 result = lstrcmpi(extA, extB);
852 if (orderReverse) result = -result;
853 return result;
856 /*****************************************************************************
857 * WCMD_getfileowner
859 * Reverse a character string in-place (strrev() is not available under unixen :-( ).
861 void WCMD_getfileowner(char *filename, char *owner, int ownerlen) {
863 ULONG sizeNeeded = 0;
864 DWORD rc;
865 char name[MAXSTRING];
866 char domain[MAXSTRING];
868 /* In case of error, return empty string */
869 *owner = 0x00;
871 /* Find out how much space we need for the owner security descritpor */
872 GetFileSecurity(filename, OWNER_SECURITY_INFORMATION, 0, 0, &sizeNeeded);
873 rc = GetLastError();
875 if(rc == ERROR_INSUFFICIENT_BUFFER && sizeNeeded > 0) {
877 LPBYTE secBuffer;
878 PSID pSID = NULL;
879 BOOL defaulted = FALSE;
880 ULONG nameLen = MAXSTRING;
881 ULONG domainLen = MAXSTRING;
882 SID_NAME_USE nameuse;
884 secBuffer = (LPBYTE) HeapAlloc(GetProcessHeap(),0,sizeNeeded * sizeof(BYTE));
885 if(!secBuffer) return;
887 /* Get the owners security descriptor */
888 if(!GetFileSecurity(filename, OWNER_SECURITY_INFORMATION, secBuffer,
889 sizeNeeded, &sizeNeeded)) {
890 HeapFree(GetProcessHeap(),0,secBuffer);
891 return;
894 /* Get the SID from the SD */
895 if(!GetSecurityDescriptorOwner(secBuffer, &pSID, &defaulted)) {
896 HeapFree(GetProcessHeap(),0,secBuffer);
897 return;
900 /* Convert to a username */
901 if (LookupAccountSid(NULL, pSID, name, &nameLen, domain, &domainLen, &nameuse)) {
902 snprintf(owner, ownerlen, "%s%c%s", domain, '\\', name);
904 HeapFree(GetProcessHeap(),0,secBuffer);
906 return;
909 /*****************************************************************************
910 * WCMD_dir_trailer
912 * Print out the trailer for the supplied drive letter
914 static void WCMD_dir_trailer(char drive) {
915 ULARGE_INTEGER avail, total, freebytes;
916 DWORD status;
917 char driveName[4] = "c:\\";
919 driveName[0] = drive;
920 status = GetDiskFreeSpaceEx (driveName, &avail, &total, &freebytes);
921 WINE_TRACE("Writing trailer for '%s' gave %d(%d)\n", driveName, status, GetLastError());
923 if (errorlevel==0 && !bare) {
924 if (recurse) {
925 WCMD_output ("\n Total files listed:\n%8d files%25s bytes\n",
926 file_total, WCMD_filesize64 (byte_total));
927 WCMD_output ("%8d directories %18s bytes free\n\n",
928 dir_total, WCMD_filesize64 (freebytes.QuadPart));
929 } else {
930 WCMD_output (" %18s bytes free\n\n", WCMD_filesize64 (freebytes.QuadPart));