directory.c 31.2 KB
Newer Older
1
/*
2
 * CMD - Wine-compatible command line interface - Directory functions.
3
 *
4
 * Copyright (C) 1999 D A Pickles
5
 * Copyright (C) 2007 J Edmeades
6
 *
7 8 9 10 11 12 13 14 15 16 17 18
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 21
 */

22 23
#define WIN32_LEAN_AND_MEAN

24
#include "wcmd.h"
25 26 27
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(cmd);
28

29 30 31 32 33 34 35
typedef enum _DISPLAYTIME
{
    Creation = 0,
    Access,
    Written
} DISPLAYTIME;

36 37 38 39 40 41 42 43
typedef enum _DISPLAYORDER
{
    Name = 0,
    Extension,
    Size,
    Date
} DISPLAYORDER;

44
static int file_total, dir_total, max_width;
45
static ULONGLONG byte_total;
46
static DISPLAYTIME dirTime;
47
static DISPLAYORDER dirOrder;
48
static BOOL orderReverse, orderGroupDirs, orderGroupDirsReverse, orderByCol;
49
static BOOL paged_mode, recurse, wide, bare, lower, shortname, usernames, separator;
50
static ULONG showattrs, attrsbits;
51 52

/*****************************************************************************
53
 * WCMD_strrev
54
 *
55
 * Reverse a WCHARacter string in-place (strrev() is not available under unixen :-( ).
56
 */
57
static WCHAR * WCMD_strrev (WCHAR *buff) {
58

59 60
  int r, i;
  WCHAR b;
61

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
  r = strlenW (buff);
  for (i=0; i<r/2; i++) {
    b = buff[i];
    buff[i] = buff[r-i-1];
    buff[r-i-1] = b;
  }
  return (buff);
}

/*****************************************************************************
 * WCMD_filesize64
 *
 * Convert a 64-bit number into a WCHARacter string, with commas every three digits.
 * Result is returned in a static string overwritten with each call.
 * FIXME: There must be a better algorithm!
 */
78
static WCHAR * WCMD_filesize64 (ULONGLONG n) {
79 80 81

  ULONGLONG q;
  unsigned int r, i;
82
  WCHAR *p;
83
  static WCHAR buff[32];
84

85 86 87 88 89 90 91 92 93 94 95 96 97
  p = buff;
  i = -3;
  do {
    if (separator && ((++i)%3 == 1)) *p++ = ',';
    q = n / 10;
    r = n - (q * 10);
    *p++ = r + '0';
    *p = '\0';
    n = q;
  } while (n != 0);
  WCMD_strrev (buff);
  return buff;
}
98

99 100 101 102 103
/*****************************************************************************
 * WCMD_dir_sort
 *
 * Sort based on the /O options supplied on the command line
 */
104
static int WCMD_dir_sort (const void *a, const void *b)
105
{
106 107
  const WIN32_FIND_DATAW *filea = (const WIN32_FIND_DATAW *)a;
  const WIN32_FIND_DATAW *fileb = (const WIN32_FIND_DATAW *)b;
108
  int result = 0;
109

110 111 112 113 114 115 116 117 118 119 120
  /* If /OG or /O-G supplied, dirs go at the top or bottom, ignoring the
     requested sort order for the directory components                   */
  if (orderGroupDirs &&
      ((filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
       (fileb->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)))
  {
    BOOL aDir = filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
    if (aDir) result = -1;
    else result = 1;
    if (orderGroupDirsReverse) result = -result;
    return result;
121

122 123 124
  /* Order by Name: */
  } else if (dirOrder == Name) {
    result = lstrcmpiW(filea->cFileName, fileb->cFileName);
125

126 127 128 129 130 131 132
  /* Order by Size: */
  } else if (dirOrder == Size) {
    ULONG64 sizea = (((ULONG64)filea->nFileSizeHigh) << 32) + filea->nFileSizeLow;
    ULONG64 sizeb = (((ULONG64)fileb->nFileSizeHigh) << 32) + fileb->nFileSizeLow;
    if( sizea < sizeb ) result = -1;
    else if( sizea == sizeb ) result = 0;
    else result = 1;
133

134 135 136
  /* Order by Date: (Takes into account which date (/T option) */
  } else if (dirOrder == Date) {

137
    const FILETIME *ft;
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
    ULONG64 timea, timeb;

    if (dirTime == Written) {
      ft = &filea->ftLastWriteTime;
      timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
      ft = &fileb->ftLastWriteTime;
      timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
    } else if (dirTime == Access) {
      ft = &filea->ftLastAccessTime;
      timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
      ft = &fileb->ftLastAccessTime;
      timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
    } else {
      ft = &filea->ftCreationTime;
      timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
      ft = &fileb->ftCreationTime;
      timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
155
    }
156 157 158
    if( timea < timeb ) result = -1;
    else if( timea == timeb ) result = 0;
    else result = 1;
159

160 161 162 163 164 165 166
  /* Order by Extension: (Takes into account which date (/T option) */
  } else if (dirOrder == Extension) {
      WCHAR drive[10];
      WCHAR dir[MAX_PATH];
      WCHAR fname[MAX_PATH];
      WCHAR extA[MAX_PATH];
      WCHAR extB[MAX_PATH];
167

168 169 170 171 172
      /* Split into components */
      WCMD_splitpath(filea->cFileName, drive, dir, fname, extA);
      WCMD_splitpath(fileb->cFileName, drive, dir, fname, extB);
      result = lstrcmpiW(extA, extB);
  }
173

174 175 176
  if (orderReverse) result = -result;
  return result;
}
177

178 179 180 181 182
/*****************************************************************************
 * WCMD_getfileowner
 *
 * Reverse a WCHARacter string in-place (strrev() is not available under unixen :-( ).
 */
183
static void WCMD_getfileowner(WCHAR *filename, WCHAR *owner, int ownerlen) {
184

185 186 187 188
    ULONG sizeNeeded = 0;
    DWORD rc;
    WCHAR name[MAXSTRING];
    WCHAR domain[MAXSTRING];
189

190 191
    /* In case of error, return empty string */
    *owner = 0x00;
192

193
    /* Find out how much space we need for the owner security descriptor */
194
    GetFileSecurityW(filename, OWNER_SECURITY_INFORMATION, 0, 0, &sizeNeeded);
195
    rc = GetLastError();
196

197
    if(rc == ERROR_INSUFFICIENT_BUFFER && sizeNeeded > 0) {
198

199 200 201 202 203 204
        LPBYTE secBuffer;
        PSID pSID = NULL;
        BOOL defaulted = FALSE;
        ULONG nameLen = MAXSTRING;
        ULONG domainLen = MAXSTRING;
        SID_NAME_USE nameuse;
205

206
        secBuffer = heap_alloc(sizeNeeded * sizeof(BYTE));
207

208
        /* Get the owners security descriptor */
209
        if(!GetFileSecurityW(filename, OWNER_SECURITY_INFORMATION, secBuffer,
210
                            sizeNeeded, &sizeNeeded)) {
211
            heap_free(secBuffer);
212
            return;
213 214
        }

215 216
        /* Get the SID from the SD */
        if(!GetSecurityDescriptorOwner(secBuffer, &pSID, &defaulted)) {
217
            heap_free(secBuffer);
218
            return;
219 220
        }

221
        /* Convert to a username */
222
        if (LookupAccountSidW(NULL, pSID, name, &nameLen, domain, &domainLen, &nameuse)) {
223 224 225
            static const WCHAR fmt[]  = {'%','s','%','c','%','s','\0'};
            snprintfW(owner, ownerlen, fmt, domain, '\\', name);
        }
226
        heap_free(secBuffer);
227
    }
228
    return;
229 230 231 232 233 234 235 236 237 238 239
}

/*****************************************************************************
 * WCMD_list_directory
 *
 * List a single file directory. This function (and those below it) can be called
 * recursively when the /S switch is used.
 *
 * FIXME: Assumes 24-line display for the /P qualifier.
 */

240
static DIRECTORY_STACK *WCMD_list_directory (DIRECTORY_STACK *inputparms, int level) {
241

242 243
  WCHAR string[1024], datestring[32], timestring[32];
  WCHAR real_path[MAX_PATH];
244
  WIN32_FIND_DATAW *fd;
245 246 247
  FILETIME ft;
  SYSTEMTIME st;
  HANDLE hff;
248
  int dir_count, file_count, entry_count, i, widest, cur_width, tmp_width;
249 250
  int numCols, numRows;
  int rows, cols;
251
  ULARGE_INTEGER byte_count, file_size;
252 253 254 255
  DIRECTORY_STACK *parms;
  int concurrentDirs = 0;
  BOOL done_header = FALSE;

256
  static const WCHAR fmtDir[]  = {'%','1','!','1','0','s','!',' ',' ','%','2','!','8','s','!',' ',' ',
257
                                  '<','D','I','R','>',' ',' ',' ',' ',' ',' ',' ',' ',' ','\0'};
258 259 260 261 262 263
  static const WCHAR fmtFile[] = {'%','1','!','1','0','s','!',' ',' ','%','2','!','8','s','!',' ',' ',
                                  ' ',' ','%','3','!','1','0','s','!',' ',' ','\0'};
  static const WCHAR fmt2[]  = {'%','1','!','-','1','3','s','!','\0'};
  static const WCHAR fmt3[]  = {'%','1','!','-','2','3','s','!','\0'};
  static const WCHAR fmt4[]  = {'%','1','\0'};
  static const WCHAR fmt5[]  = {'%','1','%','2','\0'};
264 265 266 267

  dir_count = 0;
  file_count = 0;
  entry_count = 0;
268
  byte_count.QuadPart = 0;
269 270
  widest = 0;
  cur_width = 0;
271

272 273 274 275
  /* Loop merging all the files from consecutive parms which relate to the
     same directory. Note issuing a directory header with no contents
     mirrors what windows does                                            */
  parms = inputparms;
276
  fd = heap_alloc(sizeof(WIN32_FIND_DATAW));
277
  while (parms && strcmpW(inputparms->dirName, parms->dirName) == 0) {
278
    concurrentDirs++;
279

280
    /* Work out the full path + filename */
281 282
    strcpyW(real_path, parms->dirName);
    strcatW(real_path, parms->fileName);
283

284
    /* Load all files into an in memory structure */
285
    WINE_TRACE("Looking for matches to '%s'\n", wine_dbgstr_w(real_path));
286
    hff = FindFirstFileW(real_path, &fd[entry_count]);
287 288 289
    if (hff != INVALID_HANDLE_VALUE) {
      do {
        /* Skip any which are filtered out by attribute */
290
        if ((fd[entry_count].dwFileAttributes & attrsbits) != showattrs) continue;
291 292 293 294 295

        entry_count++;

        /* Keep running track of longest filename for wide output */
        if (wide || orderByCol) {
296 297
           int tmpLen = strlenW(fd[entry_count-1].cFileName) + 3;
           if (fd[entry_count-1].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) tmpLen = tmpLen + 2;
298 299
           if (tmpLen > widest) widest = tmpLen;
        }
300

301
        fd = HeapReAlloc(GetProcessHeap(),0,fd,(entry_count+1)*sizeof(WIN32_FIND_DATAW));
302 303
        if (fd == NULL) {
          FindClose (hff);
304
          WINE_ERR("Out of memory\n");
305 306 307
          errorlevel = 1;
          return parms->next;
        }
308
      } while (FindNextFileW(hff, &fd[entry_count]) != 0);
309 310 311 312
      FindClose (hff);
    }

    /* Work out the actual current directory name without a trailing \ */
313 314
    strcpyW(real_path, parms->dirName);
    real_path[strlenW(parms->dirName)-1] = 0x00;
315 316 317

    /* Output the results */
    if (!bare) {
318
       if (level != 0 && (entry_count > 0)) WCMD_output_asis (newlineW);
319
       if (!recurse || ((entry_count > 0) && done_header==FALSE)) {
320
           static const WCHAR headerW[] = {'D','i','r','e','c','t','o','r','y',' ','o','f',
321
                                           ' ','%','1','\n','\n','\0'};
322
           WCMD_output (headerW, real_path);
323 324 325 326 327 328
           done_header = TRUE;
       }
    }

    /* Move to next parm */
    parms = parms->next;
329
  }
330

331 332
  /* Handle case where everything is filtered out */
  if (entry_count > 0) {
333

334
    /* Sort the list of files */
335
    qsort (fd, entry_count, sizeof(WIN32_FIND_DATAW), WCMD_dir_sort);
336

337 338 339 340 341 342
    /* Work out the number of columns */
    WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count, max_width, widest);
    if (wide || orderByCol) {
      numCols = max(1, (int)max_width / widest);
      numRows = entry_count / numCols;
      if (entry_count % numCols) numRows++;
343
    } else {
344 345
      numCols = 1;
      numRows = entry_count;
346
    }
347
    WINE_TRACE("cols=%d, rows=%d\n", numCols, numRows);
348

349 350 351
    for (rows=0; rows<numRows; rows++) {
     BOOL addNewLine = TRUE;
     for (cols=0; cols<numCols; cols++) {
352
      WCHAR username[24];
353

354 355 356 357
      /* Work out the index of the entry being pointed to */
      if (orderByCol) {
        i = (cols * numRows) + rows;
        if (i >= entry_count) continue;
358
      } else {
359 360
        i = (rows * numCols) + cols;
        if (i >= entry_count) continue;
361 362
      }

363 364
      /* /L convers all names to lower case */
      if (lower) {
365
          WCHAR *p = fd[i].cFileName;
366
          while ( (*p = tolower(*p)) ) ++p;
367 368
      }

369 370
      /* /Q gets file ownership information */
      if (usernames) {
371
          strcpyW (string, inputparms->dirName);
372
          strcatW (string, fd[i].cFileName);
373
          WCMD_getfileowner(string, username, sizeof(username)/sizeof(WCHAR));
374
      }
375

376
      if (dirTime == Written) {
377
        FileTimeToLocalFileTime (&fd[i].ftLastWriteTime, &ft);
378
      } else if (dirTime == Access) {
379
        FileTimeToLocalFileTime (&fd[i].ftLastAccessTime, &ft);
380
      } else {
381
        FileTimeToLocalFileTime (&fd[i].ftCreationTime, &ft);
382
      }
383
      FileTimeToSystemTime (&ft, &st);
384
      GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
385
			sizeof(datestring)/sizeof(WCHAR));
386
      GetTimeFormatW(0, TIME_NOSECONDS, &st,
387
			NULL, timestring, sizeof(timestring)/sizeof(WCHAR));
388 389 390 391

      if (wide) {

        tmp_width = cur_width;
392
        if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
393
            static const WCHAR fmt[] = {'[','%','1',']','\0'};
394
            WCMD_output (fmt, fd[i].cFileName);
395
            dir_count++;
396
            tmp_width = tmp_width + strlenW(fd[i].cFileName) + 2;
397
        } else {
398
            static const WCHAR fmt[] = {'%','1','\0'};
399 400
            WCMD_output (fmt, fd[i].cFileName);
            tmp_width = tmp_width + strlenW(fd[i].cFileName) ;
401
            file_count++;
402 403
            file_size.u.LowPart = fd[i].nFileSizeLow;
            file_size.u.HighPart = fd[i].nFileSizeHigh;
404 405 406 407 408 409 410
        byte_count.QuadPart += file_size.QuadPart;
        }
        cur_width = cur_width + widest;

        if ((cur_width + widest) > max_width) {
            cur_width = 0;
        } else {
411
            static const WCHAR padfmt[] = {'%','1','!','*','s','!','\0'};
412
            WCMD_output(padfmt, cur_width - tmp_width, nullW);
413 414
        }

415
      } else if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
416 417 418
        dir_count++;

        if (!bare) {
419
           WCMD_output (fmtDir, datestring, timestring);
420
           if (shortname) WCMD_output (fmt2, fd[i].cAlternateFileName);
421
           if (usernames) WCMD_output (fmt3, username);
422
           WCMD_output(fmt4,fd[i].cFileName);
423
        } else {
424 425 426
           if (!((strcmpW(fd[i].cFileName, dotW) == 0) ||
                 (strcmpW(fd[i].cFileName, dotdotW) == 0))) {
              WCMD_output (fmt5, recurse?inputparms->dirName:nullW, fd[i].cFileName);
427 428 429 430
           } else {
              addNewLine = FALSE;
           }
        }
431
      }
432 433
      else {
        file_count++;
434 435
        file_size.u.LowPart = fd[i].nFileSizeLow;
        file_size.u.HighPart = fd[i].nFileSizeHigh;
436 437
        byte_count.QuadPart += file_size.QuadPart;
        if (!bare) {
438
           WCMD_output (fmtFile, datestring, timestring,
439
                        WCMD_filesize64(file_size.QuadPart));
440
           if (shortname) WCMD_output (fmt2, fd[i].cAlternateFileName);
441
           if (usernames) WCMD_output (fmt3, username);
442
           WCMD_output(fmt4,fd[i].cFileName);
443
        } else {
444
           WCMD_output (fmt5, recurse?inputparms->dirName:nullW, fd[i].cFileName);
445 446 447
        }
      }
     }
448
     if (addNewLine) WCMD_output_asis (newlineW);
449
     cur_width = 0;
450
    }
451

452 453
    if (!bare) {
       if (file_count == 1) {
454
         static const WCHAR fmt[] = {' ',' ',' ',' ',' ',' ',' ','1',' ','f','i','l','e',' ',
455
                                     '%','1','!','2','5','s','!',' ','b','y','t','e','s','\n','\0'};
456
         WCMD_output (fmt, WCMD_filesize64 (byte_count.QuadPart));
457 458
       }
       else {
459
         static const WCHAR fmt[] = {'%','1','!','8','d','!',' ','f','i','l','e','s',' ','%','2','!','2','4','s','!',
460 461
                                     ' ','b','y','t','e','s','\n','\0'};
         WCMD_output (fmt, file_count, WCMD_filesize64 (byte_count.QuadPart));
462 463 464 465 466
       }
    }
    byte_total = byte_total + byte_count.QuadPart;
    file_total = file_total + file_count;
    dir_total = dir_total + dir_count;
467

468
    if (!bare && !recurse) {
469
       if (dir_count == 1) {
470
           static const WCHAR fmt[] = {'%','1','!','8','d','!',' ','d','i','r','e','c','t','o','r','y',
471 472 473
                                       ' ',' ',' ',' ',' ',' ',' ',' ',' ','\0'};
           WCMD_output (fmt, 1);
       } else {
474
           static const WCHAR fmt[] = {'%','1','!','8','d','!',' ','d','i','r','e','c','t','o','r','i',
475 476 477
                                       'e','s','\0'};
           WCMD_output (fmt, dir_count);
       }
478 479
    }
  }
480
  heap_free(fd);
481 482 483

  /* When recursing, look in all subdirectories for matches */
  if (recurse) {
484 485
    DIRECTORY_STACK *dirStack = NULL;
    DIRECTORY_STACK *lastEntry = NULL;
486
    WIN32_FIND_DATAW finddata;
487 488

    /* Build path to search */
489 490
    strcpyW(string, inputparms->dirName);
    strcatW(string, starW);
491

492
    WINE_TRACE("Recursive, looking for '%s'\n", wine_dbgstr_w(string));
493
    hff = FindFirstFileW(string, &finddata);
494 495 496
    if (hff != INVALID_HANDLE_VALUE) {
      do {
        if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
497 498
            (strcmpW(finddata.cFileName, dotdotW) != 0) &&
            (strcmpW(finddata.cFileName, dotW) != 0)) {
499

500
          DIRECTORY_STACK *thisDir;
501 502 503 504 505 506 507 508
          int              dirsToCopy = concurrentDirs;

          /* Loop creating list of subdirs for all concurrent entries */
          parms = inputparms;
          while (dirsToCopy > 0) {
            dirsToCopy--;

            /* Work out search parameter in sub dir */
509 510 511 512
            strcpyW (string, inputparms->dirName);
            strcatW (string, finddata.cFileName);
            strcatW (string, slashW);
            WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(string));
513 514

            /* Allocate memory, add to list */
515
            thisDir = heap_alloc(sizeof(DIRECTORY_STACK));
516 517 518 519
            if (dirStack == NULL) dirStack = thisDir;
            if (lastEntry != NULL) lastEntry->next = thisDir;
            lastEntry = thisDir;
            thisDir->next = NULL;
520 521
            thisDir->dirName = heap_strdupW(string);
            thisDir->fileName = heap_strdupW(parms->fileName);
522 523
            parms = parms->next;
          }
524
        }
525
      } while (FindNextFileW(hff, &finddata) != 0);
526 527 528
      FindClose (hff);

      while (dirStack != NULL) {
529
        DIRECTORY_STACK *thisDir = dirStack;
530 531 532
        dirStack = WCMD_list_directory (thisDir, 1);
        while (thisDir != dirStack) {
          DIRECTORY_STACK *tempDir = thisDir->next;
533 534 535
          heap_free(thisDir->dirName);
          heap_free(thisDir->fileName);
          heap_free(thisDir);
536 537
          thisDir = tempDir;
        }
538 539 540 541 542 543 544 545 546 547
      }
    }
  }

  /* Handle case where everything is filtered out */
  if ((file_total + dir_total == 0) && (level == 0)) {
    SetLastError (ERROR_FILE_NOT_FOUND);
    WCMD_print_error ();
    errorlevel = 1;
  }
548 549

  return parms;
550 551 552
}

/*****************************************************************************
553
 * WCMD_dir_trailer
554
 *
555
 * Print out the trailer for the supplied drive letter
556
 */
557 558 559
static void WCMD_dir_trailer(WCHAR drive) {
    ULARGE_INTEGER avail, total, freebytes;
    DWORD status;
560
    WCHAR driveName[] = {'c',':','\\','\0'};
561

562
    driveName[0] = drive;
563
    status = GetDiskFreeSpaceExW(driveName, &avail, &total, &freebytes);
564 565
    WINE_TRACE("Writing trailer for '%s' gave %d(%d)\n", wine_dbgstr_w(driveName),
               status, GetLastError());
566

567 568 569
    if (errorlevel==0 && !bare) {
      if (recurse) {
        static const WCHAR fmt1[] = {'\n',' ',' ',' ',' ',' ','T','o','t','a','l',' ','f','i','l','e','s',
570 571 572 573
                                     ' ','l','i','s','t','e','d',':','\n','%','1','!','8','d','!',' ','f','i','l','e',
                                     's','%','2','!','2','5','s','!',' ','b','y','t','e','s','\n','\0'};
        static const WCHAR fmt2[] = {'%','1','!','8','d','!',' ','d','i','r','e','c','t','o','r','i','e','s',' ','%',
                                     '2','!','1','8','s','!',' ','b','y','t','e','s',' ','f','r','e','e','\n','\n',
574 575 576 577
                                     '\0'};
        WCMD_output (fmt1, file_total, WCMD_filesize64 (byte_total));
        WCMD_output (fmt2, dir_total, WCMD_filesize64 (freebytes.QuadPart));
      } else {
578
        static const WCHAR fmt[] = {' ','%','1','!','1','8','s','!',' ','b','y','t','e','s',' ','f','r','e','e',
579 580 581 582
                                    '\n','\n','\0'};
        WCMD_output (fmt, WCMD_filesize64 (freebytes.QuadPart));
      }
    }
583 584 585
}

/*****************************************************************************
586 587 588
 * WCMD_directory
 *
 * List a file directory.
589 590 591
 *
 */

592
void WCMD_directory (WCHAR *args)
593
{
594
  WCHAR path[MAX_PATH], cwd[MAX_PATH];
595
  DWORD status;
596 597 598 599
  CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
  WCHAR *p;
  WCHAR string[MAXSTRING];
  int   argno         = 0;
600
  WCHAR *argN          = args;
601 602 603 604 605 606 607 608 609 610
  WCHAR  lastDrive;
  BOOL  trailerReqd = FALSE;
  DIRECTORY_STACK *fullParms = NULL;
  DIRECTORY_STACK *prevEntry = NULL;
  DIRECTORY_STACK *thisEntry = NULL;
  WCHAR drive[10];
  WCHAR dir[MAX_PATH];
  WCHAR fname[MAX_PATH];
  WCHAR ext[MAX_PATH];
  static const WCHAR dircmdW[] = {'D','I','R','C','M','D','\0'};
611

612 613 614
  errorlevel = 0;

  /* Prefill quals with (uppercased) DIRCMD env var */
615
  if (GetEnvironmentVariableW(dircmdW, string, sizeof(string)/sizeof(WCHAR))) {
616 617 618 619
    p = string;
    while ( (*p = toupper(*p)) ) ++p;
    strcatW(string,quals);
    strcpyW(quals, string);
620 621
  }

622 623
  byte_total = 0;
  file_total = dir_total = 0;
624

625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
  /* Initialize all flags to their defaults as if no DIRCMD or quals */
  paged_mode = FALSE;
  recurse    = FALSE;
  wide       = FALSE;
  bare       = FALSE;
  lower      = FALSE;
  shortname  = FALSE;
  usernames  = FALSE;
  orderByCol = FALSE;
  separator  = TRUE;
  dirTime = Written;
  dirOrder = Name;
  orderReverse = FALSE;
  orderGroupDirs = FALSE;
  orderGroupDirsReverse = FALSE;
  showattrs  = 0;
641
  attrsbits  = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
642

643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
  /* Handle args - Loop through so right most is the effective one */
  /* Note: /- appears to be a negate rather than an off, eg. dir
           /-W is wide, or dir /w /-w /-w is also wide             */
  p = quals;
  while (*p && (*p=='/' || *p==' ')) {
    BOOL negate = FALSE;
    if (*p++==' ') continue;  /* Skip / and blanks introduced through DIRCMD */

    if (*p=='-') {
      negate = TRUE;
      p++;
    }

    WINE_TRACE("Processing arg '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
    switch (*p) {
    case 'P': if (negate) paged_mode = !paged_mode;
              else paged_mode = TRUE;
              break;
    case 'S': if (negate) recurse = !recurse;
              else recurse = TRUE;
              break;
    case 'W': if (negate) wide = !wide;
              else wide = TRUE;
              break;
    case 'B': if (negate) bare = !bare;
              else bare = TRUE;
              break;
    case 'L': if (negate) lower = !lower;
              else lower = TRUE;
              break;
    case 'X': if (negate) shortname = !shortname;
              else shortname = TRUE;
              break;
    case 'Q': if (negate) usernames = !usernames;
              else usernames = TRUE;
              break;
    case 'D': if (negate) orderByCol = !orderByCol;
              else orderByCol = TRUE;
              break;
    case 'C': if (negate) separator = !separator;
              else separator = TRUE;
              break;
    case 'T': p = p + 1;
              if (*p==':') p++;  /* Skip optional : */

              if (*p == 'A') dirTime = Access;
              else if (*p == 'C') dirTime = Creation;
              else if (*p == 'W') dirTime = Written;

              /* Support /T and /T: with no parms, default to written */
              else if (*p == 0x00 || *p == '/') {
                dirTime = Written;
                p = p - 1; /* So when step on, move to '/' */
              } else {
                SetLastError(ERROR_INVALID_PARAMETER);
                WCMD_print_error();
                errorlevel = 1;
                return;
              }
              break;
    case 'O': p = p + 1;
              if (*p==':') p++;  /* Skip optional : */
              while (*p && *p != '/') {
                WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
                switch (*p) {
                case 'N': dirOrder = Name;       break;
                case 'E': dirOrder = Extension;  break;
                case 'S': dirOrder = Size;       break;
                case 'D': dirOrder = Date;       break;
                case '-': if (*(p+1)=='G') orderGroupDirsReverse=TRUE;
                          else orderReverse = TRUE;
                          break;
                case 'G': orderGroupDirs = TRUE; break;
                default:
                    SetLastError(ERROR_INVALID_PARAMETER);
                    WCMD_print_error();
                    errorlevel = 1;
                    return;
                }
                p++;
              }
              p = p - 1; /* So when step on, move to '/' */
              break;
    case 'A': p = p + 1;
              showattrs = 0;
              attrsbits = 0;
              if (*p==':') p++;  /* Skip optional : */
              while (*p && *p != '/') {
                BOOL anegate = FALSE;
                ULONG mask;

                /* Note /A: - options are 'offs' not toggles */
                if (*p=='-') {
                  anegate = TRUE;
                  p++;
                }

                WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
                switch (*p) {
                case 'D': mask = FILE_ATTRIBUTE_DIRECTORY; break;
                case 'H': mask = FILE_ATTRIBUTE_HIDDEN;    break;
                case 'S': mask = FILE_ATTRIBUTE_SYSTEM;    break;
                case 'R': mask = FILE_ATTRIBUTE_READONLY;  break;
                case 'A': mask = FILE_ATTRIBUTE_ARCHIVE;   break;
                default:
                    SetLastError(ERROR_INVALID_PARAMETER);
                    WCMD_print_error();
                    errorlevel = 1;
                    return;
                }

                /* Keep running list of bits we care about */
                attrsbits |= mask;

                /* Mask shows what MUST be in the bits we care about */
                if (anegate) showattrs = showattrs & ~mask;
                else showattrs |= mask;

                p++;
              }
              p = p - 1; /* So when step on, move to '/' */
              WINE_TRACE("Result: showattrs %x, bits %x\n", showattrs, attrsbits);
              break;
    default:
              SetLastError(ERROR_INVALID_PARAMETER);
              WCMD_print_error();
              errorlevel = 1;
              return;
    }
    p = p + 1;
  }

  /* Handle conflicting args and initialization */
  if (bare || shortname) wide = FALSE;
  if (bare) shortname = FALSE;
  if (wide) usernames = FALSE;
  if (orderByCol) wide = TRUE;

  if (wide) {
      if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
          max_width = consoleInfo.dwSize.X;
      else
          max_width = 80;
  }
  if (paged_mode) {
     WCMD_enter_paged_mode(NULL);
  }
790

791
  argno         = 0;
792
  argN          = args;
793
  GetCurrentDirectoryW(MAX_PATH, cwd);
794
  strcatW(cwd, slashW);
795

796 797 798 799 800
  /* Loop through all args, calculating full effective directory */
  fullParms = NULL;
  prevEntry = NULL;
  while (argN) {
    WCHAR fullname[MAXSTRING];
801
    WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
802
    if (argN && argN[0] != '/') {
803

804 805 806 807 808 809
      WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg));
      if (thisArg[1] == ':' && thisArg[2] == '\\') {
        strcpyW(fullname, thisArg);
      } else if (thisArg[1] == ':' && thisArg[2] != '\\') {
        WCHAR envvar[4];
        static const WCHAR envFmt[] = {'=','%','c',':','\0'};
810 811
        wsprintfW(envvar, envFmt, thisArg[0]);
        if (!GetEnvironmentVariableW(envvar, fullname, MAX_PATH)) {
812
          static const WCHAR noEnvFmt[] = {'%','c',':','\0'};
813
          wsprintfW(fullname, noEnvFmt, thisArg[0]);
814 815 816 817 818 819 820 821 822 823 824
        }
        strcatW(fullname, slashW);
        strcatW(fullname, &thisArg[2]);
      } else if (thisArg[0] == '\\') {
        memcpy(fullname, cwd, 2 * sizeof(WCHAR));
        strcpyW(fullname+2, thisArg);
      } else {
        strcpyW(fullname, cwd);
        strcatW(fullname, thisArg);
      }
      WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname));
825

826
      status = GetFullPathNameW(fullname, sizeof(path)/sizeof(WCHAR), path, NULL);
827

828 829 830 831 832 833
      /*
       *  If the path supplied does not include a wildcard, and the endpoint of the
       *  path references a directory, we need to list the *contents* of that
       *  directory not the directory file itself.
       */
      if ((strchrW(path, '*') == NULL) && (strchrW(path, '%') == NULL)) {
834
        status = GetFileAttributesW(path);
835
        if ((status != INVALID_FILE_ATTRIBUTES) && (status & FILE_ATTRIBUTE_DIRECTORY)) {
836 837
          if (!ends_with_backslash( path )) strcatW( path, slashW );
          strcatW (path, starW);
838 839 840 841 842 843
        }
      } else {
        /* Special case wildcard search with no extension (ie parameters ending in '.') as
           GetFullPathName strips off the additional '.'                                  */
        if (fullname[strlenW(fullname)-1] == '.') strcatW(path, dotW);
      }
844

845
      WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path));
846
      thisEntry = heap_alloc(sizeof(DIRECTORY_STACK));
847 848 849 850
      if (fullParms == NULL) fullParms = thisEntry;
      if (prevEntry != NULL) prevEntry->next = thisEntry;
      prevEntry = thisEntry;
      thisEntry->next = NULL;
851 852

      /* Split into components */
853 854 855 856
      WCMD_splitpath(path, drive, dir, fname, ext);
      WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
                 wine_dbgstr_w(drive), wine_dbgstr_w(dir),
                 wine_dbgstr_w(fname), wine_dbgstr_w(ext));
857

858
      thisEntry->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(drive)+strlenW(dir)+1));
859 860
      strcpyW(thisEntry->dirName, drive);
      strcatW(thisEntry->dirName, dir);
861

862
      thisEntry->fileName = heap_alloc(sizeof(WCHAR) * (strlenW(fname)+strlenW(ext)+1));
863 864
      strcpyW(thisEntry->fileName, fname);
      strcatW(thisEntry->fileName, ext);
865

866 867
    }
  }
868

869 870 871
  /* If just 'dir' entered, a '*' parameter is assumed */
  if (fullParms == NULL) {
    WINE_TRACE("Inserting default '*'\n");
872
    fullParms = heap_alloc(sizeof(DIRECTORY_STACK));
873
    fullParms->next = NULL;
874 875
    fullParms->dirName = heap_strdupW(cwd);
    fullParms->fileName = heap_strdupW(starW);
876
  }
877

878 879 880 881
  lastDrive = '?';
  prevEntry = NULL;
  thisEntry = fullParms;
  trailerReqd = FALSE;
882

883
  while (thisEntry != NULL) {
884

885 886 887
    /* Output disk free (trailer) and volume information (header) if the drive
       letter changes */
    if (lastDrive != toupper(thisEntry->dirName[0])) {
888

889 890 891 892 893
      /* Trailer Information */
      if (lastDrive != '?') {
        trailerReqd = FALSE;
        WCMD_dir_trailer(prevEntry->dirName[0]);
      }
894

895
      lastDrive = toupper(thisEntry->dirName[0]);
896

897 898
      if (!bare) {
         WCHAR drive[3];
899

900 901 902 903 904 905 906 907 908 909 910 911
         WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
         memcpy(drive, thisEntry->dirName, 2 * sizeof(WCHAR));
         drive[2] = 0x00;
         status = WCMD_volume (0, drive);
         trailerReqd = TRUE;
         if (!status) {
           errorlevel = 1;
           goto exit;
         }
      }
    } else {
      static const WCHAR newLine2[] = {'\n','\n','\0'};
912
      if (!bare) WCMD_output_asis (newLine2);
913
    }
914

915 916 917 918 919
    /* Clear any errors from previous invocations, and process it */
    errorlevel = 0;
    prevEntry = thisEntry;
    thisEntry = WCMD_list_directory (thisEntry, 0);
  }
920

921 922 923 924
  /* Trailer Information */
  if (trailerReqd) {
    WCMD_dir_trailer(prevEntry->dirName[0]);
  }
925

926 927 928 929 930 931 932
exit:
  if (paged_mode) WCMD_leave_paged_mode();

  /* Free storage allocated for parms */
  while (fullParms != NULL) {
    prevEntry = fullParms;
    fullParms = prevEntry->next;
933 934 935
    heap_free(prevEntry->dirName);
    heap_free(prevEntry->fileName);
    heap_free(prevEntry);
936
  }
937
}