Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wine-mirror
GitHub Repository: wine-mirror/wine
Path: blob/master/programs/cmd/directory.c
4387 views
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
* This library is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU Lesser General Public
9
* License as published by the Free Software Foundation; either
10
* version 2.1 of the License, or (at your option) any later version.
11
*
12
* This library is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* Lesser General Public License for more details.
16
*
17
* You should have received a copy of the GNU Lesser General Public
18
* 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
#define WIN32_LEAN_AND_MEAN
23
24
#include "wcmd.h"
25
#include "wine/debug.h"
26
27
WINE_DEFAULT_DEBUG_CHANNEL(cmd);
28
29
typedef enum _DISPLAYTIME
30
{
31
Creation = 0,
32
Access,
33
Written
34
} DISPLAYTIME;
35
36
typedef enum _DISPLAYORDER
37
{
38
Unspecified = 0,
39
Name,
40
Extension,
41
Size,
42
Date
43
} DISPLAYORDER;
44
45
#define MAX_DATETIME_FORMAT 80
46
47
static WCHAR date_format[MAX_DATETIME_FORMAT * 2];
48
static WCHAR time_format[MAX_DATETIME_FORMAT * 2];
49
static int file_total, dir_total, max_width;
50
static ULONGLONG byte_total;
51
static DISPLAYTIME dirTime;
52
static DISPLAYORDER dirOrder;
53
static BOOL orderReverse, orderGroupDirs, orderGroupDirsReverse, orderByCol;
54
static BOOL paged_mode, recurse, wide, bare, lower, shortname, usernames, separator;
55
static ULONG showattrs, attrsbits;
56
57
/*****************************************************************************
58
* WCMD_filesize64
59
*
60
* Convert a 64-bit number into a WCHARacter string, with commas every three digits.
61
* Result is returned in a static string overwritten with each call.
62
* FIXME: There must be a better algorithm!
63
*/
64
static WCHAR * WCMD_filesize64 (ULONGLONG n) {
65
66
ULONGLONG q;
67
unsigned int r, i;
68
WCHAR *p;
69
static WCHAR buff[32];
70
71
p = buff;
72
i = -3;
73
do {
74
if (separator && ((++i)%3 == 1)) *p++ = ',';
75
q = n / 10;
76
r = n - (q * 10);
77
*p++ = r + '0';
78
*p = '\0';
79
n = q;
80
} while (n != 0);
81
wcsrev(buff);
82
return buff;
83
}
84
85
/*****************************************************************************
86
* WCMD_dir_sort
87
*
88
* Sort based on the /O options supplied on the command line
89
*/
90
static int __cdecl WCMD_dir_sort (const void *a, const void *b)
91
{
92
const WIN32_FIND_DATAW *filea = (const WIN32_FIND_DATAW *)a;
93
const WIN32_FIND_DATAW *fileb = (const WIN32_FIND_DATAW *)b;
94
BOOL aDir = filea->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
95
BOOL bDir = fileb->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
96
int result = 0;
97
98
if (orderGroupDirs && dirOrder == Unspecified) {
99
/* Special case: If ordering groups and not sorting by other criteria, "." and ".." always go first. */
100
if (aDir && !lstrcmpW(filea->cFileName, L".")) {
101
result = -1;
102
} else if (bDir && !lstrcmpW(fileb->cFileName, L".")) {
103
result = 1;
104
} else if (aDir && !lstrcmpW(filea->cFileName, L"..")) {
105
result = -1;
106
} else if (bDir && !lstrcmpW(fileb->cFileName, L"..")) {
107
result = 1;
108
}
109
110
if (result) {
111
if (orderGroupDirsReverse) result = -result;
112
return result;
113
}
114
}
115
116
/* If /OG or /O-G supplied, dirs go at the top or bottom, also sorted
117
if requested sort order is by name. */
118
if (orderGroupDirs && (aDir || bDir)) {
119
if (aDir && bDir && dirOrder == Name) {
120
result = lstrcmpiW(filea->cFileName, fileb->cFileName);
121
if (orderReverse) result = -result;
122
} else if (aDir) {
123
result = -1;
124
}
125
else result = 1;
126
if (orderGroupDirsReverse) result = -result;
127
return result;
128
129
/* Order by Name: */
130
} else if (dirOrder == Name) {
131
result = lstrcmpiW(filea->cFileName, fileb->cFileName);
132
133
/* Order by Size: */
134
} else if (dirOrder == Size) {
135
ULONG64 sizea = (((ULONG64)filea->nFileSizeHigh) << 32) + filea->nFileSizeLow;
136
ULONG64 sizeb = (((ULONG64)fileb->nFileSizeHigh) << 32) + fileb->nFileSizeLow;
137
if( sizea < sizeb ) result = -1;
138
else if( sizea == sizeb ) result = 0;
139
else result = 1;
140
141
/* Order by Date: (Takes into account which date (/T option) */
142
} else if (dirOrder == Date) {
143
144
const FILETIME *ft;
145
ULONG64 timea, timeb;
146
147
if (dirTime == Written) {
148
ft = &filea->ftLastWriteTime;
149
timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
150
ft = &fileb->ftLastWriteTime;
151
timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
152
} else if (dirTime == Access) {
153
ft = &filea->ftLastAccessTime;
154
timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
155
ft = &fileb->ftLastAccessTime;
156
timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
157
} else {
158
ft = &filea->ftCreationTime;
159
timea = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
160
ft = &fileb->ftCreationTime;
161
timeb = (((ULONG64)ft->dwHighDateTime) << 32) + ft->dwLowDateTime;
162
}
163
if( timea < timeb ) result = -1;
164
else if( timea == timeb ) result = 0;
165
else result = 1;
166
167
/* Order by Extension: (Takes into account which date (/T option) */
168
} else if (dirOrder == Extension) {
169
WCHAR drive[10];
170
WCHAR dir[MAX_PATH];
171
WCHAR fname[MAX_PATH];
172
WCHAR extA[MAX_PATH];
173
WCHAR extB[MAX_PATH];
174
175
/* Split into components */
176
_wsplitpath(filea->cFileName, drive, dir, fname, extA);
177
_wsplitpath(fileb->cFileName, drive, dir, fname, extB);
178
result = lstrcmpiW(extA, extB);
179
}
180
181
if (orderReverse) result = -result;
182
return result;
183
}
184
185
/*****************************************************************************
186
* WCMD_getfileowner
187
*/
188
static void WCMD_getfileowner(WCHAR *filename, WCHAR *owner, int ownerlen) {
189
190
ULONG sizeNeeded = 0;
191
DWORD rc;
192
WCHAR name[MAXSTRING];
193
WCHAR domain[MAXSTRING];
194
195
/* In case of error, return empty string */
196
*owner = 0x00;
197
198
/* Find out how much space we need for the owner security descriptor */
199
GetFileSecurityW(filename, OWNER_SECURITY_INFORMATION, 0, 0, &sizeNeeded);
200
rc = GetLastError();
201
202
if(rc == ERROR_INSUFFICIENT_BUFFER && sizeNeeded > 0) {
203
204
LPBYTE secBuffer;
205
PSID pSID = NULL;
206
BOOL defaulted = FALSE;
207
ULONG nameLen = MAXSTRING;
208
ULONG domainLen = MAXSTRING;
209
SID_NAME_USE nameuse;
210
211
secBuffer = xalloc(sizeNeeded * sizeof(BYTE));
212
213
/* Get the owners security descriptor */
214
if(!GetFileSecurityW(filename, OWNER_SECURITY_INFORMATION, secBuffer,
215
sizeNeeded, &sizeNeeded)) {
216
free(secBuffer);
217
return;
218
}
219
220
/* Get the SID from the SD */
221
if(!GetSecurityDescriptorOwner(secBuffer, &pSID, &defaulted)) {
222
free(secBuffer);
223
return;
224
}
225
226
/* Convert to a username */
227
if (LookupAccountSidW(NULL, pSID, name, &nameLen, domain, &domainLen, &nameuse)) {
228
swprintf(owner, ownerlen, L"%s%c%s", domain, '\\', name);
229
}
230
free(secBuffer);
231
}
232
return;
233
}
234
235
/*****************************************************************************
236
* WCMD_list_directory
237
*
238
* List a single file directory. This function (and those below it) can be called
239
* recursively when the /S switch is used.
240
*/
241
242
static RETURN_CODE WCMD_list_directory (DIRECTORY_STACK *inputparms, int level, DIRECTORY_STACK **outputparms) {
243
244
WCHAR string[1024], datestring[32], timestring[32];
245
WCHAR real_path[MAX_PATH];
246
WIN32_FIND_DATAW *fd;
247
FILETIME ft;
248
SYSTEMTIME st;
249
HANDLE hff;
250
int dir_count, file_count, entry_count, i, widest, cur_width, tmp_width;
251
int numCols, numRows;
252
int rows, cols;
253
ULARGE_INTEGER byte_count, file_size;
254
DIRECTORY_STACK *parms;
255
int concurrentDirs = 0;
256
BOOL done_header = FALSE;
257
RETURN_CODE return_code = NO_ERROR;
258
259
dir_count = 0;
260
file_count = 0;
261
entry_count = 0;
262
byte_count.QuadPart = 0;
263
widest = 0;
264
cur_width = 0;
265
266
/* Loop merging all the files from consecutive parms which relate to the
267
same directory. Note issuing a directory header with no contents
268
mirrors what windows does */
269
parms = inputparms;
270
fd = xalloc(sizeof(WIN32_FIND_DATAW));
271
while (parms && lstrcmpW(inputparms->dirName, parms->dirName) == 0) {
272
concurrentDirs++;
273
274
/* Work out the full path + filename */
275
lstrcpyW(real_path, parms->dirName);
276
lstrcatW(real_path, parms->fileName);
277
278
/* Load all files into an in memory structure */
279
WINE_TRACE("Looking for matches to '%s'\n", wine_dbgstr_w(real_path));
280
hff = FindFirstFileW(real_path, &fd[entry_count]);
281
if (hff != INVALID_HANDLE_VALUE) {
282
do {
283
/* Skip any which are filtered out by attribute */
284
if ((fd[entry_count].dwFileAttributes & attrsbits) != showattrs) continue;
285
286
entry_count++;
287
288
/* Keep running track of longest filename for wide output */
289
if (wide || orderByCol) {
290
int tmpLen = lstrlenW(fd[entry_count-1].cFileName) + 3;
291
if (fd[entry_count-1].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) tmpLen = tmpLen + 2;
292
if (tmpLen > widest) widest = tmpLen;
293
}
294
295
fd = xrealloc(fd, (entry_count + 1) * sizeof(WIN32_FIND_DATAW));
296
} while (FindNextFileW(hff, &fd[entry_count]) != 0);
297
FindClose (hff);
298
}
299
300
/* Work out the actual current directory name without a trailing \ */
301
lstrcpyW(real_path, parms->dirName);
302
real_path[lstrlenW(parms->dirName)-1] = 0x00;
303
304
/* Output the results */
305
if (!bare) {
306
if (level != 0 && (entry_count > 0)) return_code = WCMD_output_asis(L"\r\n");
307
if (!recurse || ((entry_count > 0) && done_header==FALSE)) {
308
WCMD_output (L"Directory of %1\n\n", real_path);
309
done_header = TRUE;
310
}
311
}
312
313
/* Move to next parm */
314
parms = parms->next;
315
}
316
317
/* Handle case where everything is filtered out */
318
if (entry_count > 0) {
319
320
/* Sort the list of files */
321
qsort (fd, entry_count, sizeof(WIN32_FIND_DATAW), WCMD_dir_sort);
322
323
/* Work out the number of columns */
324
WINE_TRACE("%d entries, maxwidth=%d, widest=%d\n", entry_count, max_width, widest);
325
if (wide || orderByCol) {
326
numCols = max(1, max_width / widest);
327
numRows = entry_count / numCols;
328
if (entry_count % numCols) numRows++;
329
} else {
330
numCols = 1;
331
numRows = entry_count;
332
}
333
WINE_TRACE("cols=%d, rows=%d\n", numCols, numRows);
334
335
for (rows=0; rows<numRows && return_code == NO_ERROR; rows++) {
336
BOOL addNewLine = TRUE;
337
for (cols=0; cols<numCols; cols++) {
338
WCHAR username[24];
339
340
/* Work out the index of the entry being pointed to */
341
if (orderByCol) {
342
i = (cols * numRows) + rows;
343
if (i >= entry_count) continue;
344
} else {
345
i = (rows * numCols) + cols;
346
if (i >= entry_count) continue;
347
}
348
349
/* /L convers all names to lower case */
350
if (lower) wcslwr( fd[i].cFileName );
351
352
/* /Q gets file ownership information */
353
if (usernames) {
354
lstrcpyW (string, inputparms->dirName);
355
lstrcatW (string, fd[i].cFileName);
356
WCMD_getfileowner(string, username, ARRAY_SIZE(username));
357
}
358
359
if (dirTime == Written) {
360
FileTimeToLocalFileTime (&fd[i].ftLastWriteTime, &ft);
361
} else if (dirTime == Access) {
362
FileTimeToLocalFileTime (&fd[i].ftLastAccessTime, &ft);
363
} else {
364
FileTimeToLocalFileTime (&fd[i].ftCreationTime, &ft);
365
}
366
FileTimeToSystemTime (&ft, &st);
367
GetDateFormatW(LOCALE_USER_DEFAULT, 0, &st, date_format,
368
datestring, ARRAY_SIZE(datestring));
369
GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, time_format,
370
timestring, ARRAY_SIZE(timestring));
371
372
if (wide) {
373
374
tmp_width = cur_width;
375
if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
376
WCMD_output (L"[%1]", fd[i].cFileName);
377
dir_count++;
378
tmp_width = tmp_width + lstrlenW(fd[i].cFileName) + 2;
379
} else {
380
WCMD_output (L"%1", fd[i].cFileName);
381
tmp_width = tmp_width + lstrlenW(fd[i].cFileName) ;
382
file_count++;
383
file_size.u.LowPart = fd[i].nFileSizeLow;
384
file_size.u.HighPart = fd[i].nFileSizeHigh;
385
byte_count.QuadPart += file_size.QuadPart;
386
}
387
cur_width = cur_width + widest;
388
389
if ((cur_width + widest) > max_width) {
390
cur_width = 0;
391
} else {
392
WCMD_output(L"%1!*s!", cur_width - tmp_width, L"");
393
}
394
395
} else if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
396
dir_count++;
397
398
if (!bare) {
399
WCMD_output (L"%1 %2 <DIR> ", datestring, timestring);
400
if (shortname) WCMD_output(L"%1!-13s!", fd[i].cAlternateFileName);
401
if (usernames) WCMD_output(L"%1!-23s!", username);
402
WCMD_output(L"%1",fd[i].cFileName);
403
} else {
404
if (!((lstrcmpW(fd[i].cFileName, L".") == 0) ||
405
(lstrcmpW(fd[i].cFileName, L"..") == 0))) {
406
WCMD_output(L"%1%2", recurse ? inputparms->dirName : L"", fd[i].cFileName);
407
} else {
408
addNewLine = FALSE;
409
}
410
}
411
}
412
else {
413
file_count++;
414
file_size.u.LowPart = fd[i].nFileSizeLow;
415
file_size.u.HighPart = fd[i].nFileSizeHigh;
416
byte_count.QuadPart += file_size.QuadPart;
417
if (!bare) {
418
WCMD_output (L"%1 %2 %3!14s! ", datestring, timestring,
419
WCMD_filesize64(file_size.QuadPart));
420
if (shortname) WCMD_output(L"%1!-13s!", fd[i].cAlternateFileName);
421
if (usernames) WCMD_output(L"%1!-23s!", username);
422
WCMD_output(L"%1",fd[i].cFileName);
423
} else {
424
WCMD_output(L"%1%2", recurse ? inputparms->dirName : L"", fd[i].cFileName);
425
}
426
}
427
}
428
if (addNewLine) return_code = WCMD_output_asis(L"\r\n");
429
cur_width = 0;
430
431
/* Allow command to be aborted if user presses Ctrl-C.
432
* Don't overwrite any existing error code.
433
*/
434
if (return_code == NO_ERROR) {
435
return_code = WCMD_ctrlc_status();
436
}
437
}
438
439
if (!bare && return_code == NO_ERROR) {
440
if (file_count == 1) {
441
WCMD_output (L" 1 file %1!25s! bytes\n", WCMD_filesize64 (byte_count.QuadPart));
442
}
443
else {
444
WCMD_output (L"%1!8d! files %2!24s! bytes\n", file_count, WCMD_filesize64 (byte_count.QuadPart));
445
}
446
}
447
byte_total = byte_total + byte_count.QuadPart;
448
file_total = file_total + file_count;
449
dir_total = dir_total + dir_count;
450
451
if (!bare && !recurse && return_code == NO_ERROR) {
452
if (dir_count == 1) {
453
WCMD_output (L"%1!8d! directory ", 1);
454
} else {
455
WCMD_output (L"%1!8d! directories", dir_count);
456
}
457
}
458
}
459
free(fd);
460
461
/* When recursing, look in all subdirectories for matches */
462
if (recurse && return_code == NO_ERROR) {
463
DIRECTORY_STACK *dirStack = NULL;
464
DIRECTORY_STACK *lastEntry = NULL;
465
WIN32_FIND_DATAW finddata;
466
467
/* Build path to search */
468
lstrcpyW(string, inputparms->dirName);
469
lstrcatW(string, L"*");
470
471
WINE_TRACE("Recursive, looking for '%s'\n", wine_dbgstr_w(string));
472
hff = FindFirstFileW(string, &finddata);
473
if (hff != INVALID_HANDLE_VALUE) {
474
do {
475
if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
476
(lstrcmpW(finddata.cFileName, L"..") != 0) &&
477
(lstrcmpW(finddata.cFileName, L".") != 0)) {
478
479
DIRECTORY_STACK *thisDir;
480
int dirsToCopy = concurrentDirs;
481
482
/* Loop creating list of subdirs for all concurrent entries */
483
parms = inputparms;
484
while (dirsToCopy > 0) {
485
dirsToCopy--;
486
487
/* Work out search parameter in sub dir */
488
lstrcpyW (string, inputparms->dirName);
489
lstrcatW (string, finddata.cFileName);
490
lstrcatW(string, L"\\");
491
WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(string));
492
493
/* Allocate memory, add to list */
494
thisDir = xalloc(sizeof(DIRECTORY_STACK));
495
if (dirStack == NULL) dirStack = thisDir;
496
if (lastEntry != NULL) lastEntry->next = thisDir;
497
lastEntry = thisDir;
498
thisDir->next = NULL;
499
thisDir->dirName = xstrdupW(string);
500
thisDir->fileName = xstrdupW(parms->fileName);
501
parms = parms->next;
502
}
503
}
504
} while (FindNextFileW(hff, &finddata) != 0);
505
FindClose (hff);
506
507
while (dirStack != NULL && return_code == NO_ERROR) {
508
DIRECTORY_STACK *thisDir = dirStack;
509
return_code = WCMD_list_directory (thisDir, 1, &dirStack);
510
if (return_code != NO_ERROR) dirStack = NULL;
511
while (thisDir != dirStack) {
512
DIRECTORY_STACK *tempDir = thisDir->next;
513
free(thisDir->dirName);
514
free(thisDir->fileName);
515
free(thisDir);
516
thisDir = tempDir;
517
}
518
}
519
}
520
}
521
522
/* Handle case where everything is filtered out */
523
if ((file_total + dir_total == 0) && (level == 0)) {
524
return_code = ERROR_FILE_NOT_FOUND;
525
SetLastError (return_code);
526
WCMD_print_error ();
527
}
528
529
*outputparms = parms;
530
return return_code;
531
}
532
533
/*****************************************************************************
534
* WCMD_dir_trailer
535
*
536
* Print out the trailer for the supplied path
537
*/
538
static void WCMD_dir_trailer(const WCHAR *path) {
539
ULARGE_INTEGER freebytes;
540
BOOL status;
541
542
status = GetDiskFreeSpaceExW(path, NULL, NULL, &freebytes);
543
WINE_TRACE("Writing trailer for '%s' gave %d(%ld)\n", wine_dbgstr_w(path),
544
status, GetLastError());
545
546
if (!bare) {
547
if (recurse) {
548
WCMD_output (L"\n Total files listed:\n%1!8d! files%2!25s! bytes\n", file_total, WCMD_filesize64 (byte_total));
549
WCMD_output (L"%1!8d! directories %2!18s! bytes free\n\n", dir_total, WCMD_filesize64 (freebytes.QuadPart));
550
} else {
551
WCMD_output (L" %1!18s! bytes free\n\n", WCMD_filesize64 (freebytes.QuadPart));
552
}
553
}
554
}
555
556
/* Get the length of a date/time formatting pattern */
557
/* copied from dlls/kernelbase/locale.c */
558
static int get_pattern_len( const WCHAR *pattern, const WCHAR *accept )
559
{
560
int i;
561
562
if (*pattern == '\'')
563
{
564
for (i = 1; pattern[i]; i++)
565
{
566
if (pattern[i] != '\'') continue;
567
if (pattern[++i] != '\'') return i;
568
}
569
return i;
570
}
571
if (!wcschr( accept, *pattern )) return 1;
572
for (i = 1; pattern[i]; i++) if (pattern[i] != pattern[0]) break;
573
return i;
574
}
575
576
/* Initialize date format to use abbreviated one with leading zeros */
577
static void init_date_format(void)
578
{
579
WCHAR sys_format[MAX_DATETIME_FORMAT];
580
int src_pat_len, dst_pat_len;
581
const WCHAR *src;
582
WCHAR *dst = date_format;
583
584
GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, sys_format, ARRAY_SIZE(sys_format));
585
586
for (src = sys_format; *src; src += src_pat_len, dst += dst_pat_len) {
587
src_pat_len = dst_pat_len = get_pattern_len(src, L"yMd");
588
589
switch (*src)
590
{
591
case '\'':
592
wmemcpy(dst, src, src_pat_len);
593
break;
594
595
case 'd':
596
case 'M':
597
if (src_pat_len == 4) /* full name */
598
dst_pat_len--; /* -> use abbreviated one */
599
/* fallthrough */
600
case 'y':
601
if (src_pat_len == 1) /* without leading zeros */
602
dst_pat_len++; /* -> with leading zeros */
603
wmemset(dst, *src, dst_pat_len);
604
break;
605
606
default:
607
*dst = *src;
608
break;
609
}
610
}
611
*dst = '\0';
612
613
TRACE("date format: %s\n", wine_dbgstr_w(date_format));
614
}
615
616
/* Initialize time format to use leading zeros */
617
static void init_time_format(void)
618
{
619
WCHAR sys_format[MAX_DATETIME_FORMAT];
620
int src_pat_len, dst_pat_len;
621
const WCHAR *src;
622
WCHAR *dst = time_format;
623
624
GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STIMEFORMAT, sys_format, ARRAY_SIZE(sys_format));
625
626
for (src = sys_format; *src; src += src_pat_len, dst += dst_pat_len) {
627
src_pat_len = dst_pat_len = get_pattern_len(src, L"Hhmst");
628
629
switch (*src)
630
{
631
case '\'':
632
wmemcpy(dst, src, src_pat_len);
633
break;
634
635
case 'H':
636
case 'h':
637
case 'm':
638
case 's':
639
if (src_pat_len == 1) /* without leading zeros */
640
dst_pat_len++; /* -> with leading zeros */
641
/* fallthrough */
642
case 't':
643
wmemset(dst, *src, dst_pat_len);
644
break;
645
646
default:
647
*dst = *src;
648
break;
649
}
650
}
651
*dst = '\0';
652
653
/* seconds portion will be dropped by TIME_NOSECONDS */
654
TRACE("time format: %s\n", wine_dbgstr_w(time_format));
655
}
656
657
/*****************************************************************************
658
* WCMD_directory
659
*
660
* List a file directory.
661
*
662
*/
663
664
RETURN_CODE WCMD_directory(WCHAR *args)
665
{
666
WCHAR path[MAX_PATH], cwd[MAX_PATH];
667
DWORD status;
668
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
669
WCHAR *p;
670
WCHAR string[MAXSTRING];
671
int argno = 0;
672
WCHAR *argN = args;
673
WCHAR lastDrive;
674
BOOL trailerReqd = FALSE;
675
DIRECTORY_STACK *fullParms = NULL;
676
DIRECTORY_STACK *prevEntry = NULL;
677
DIRECTORY_STACK *thisEntry = NULL;
678
WCHAR drive[10];
679
WCHAR dir[MAX_PATH];
680
WCHAR fname[MAX_PATH];
681
WCHAR ext[MAX_PATH];
682
unsigned num_empty = 0, num_with_data = 0;
683
RETURN_CODE return_code = NO_ERROR;
684
685
/* Prefill quals with (uppercased) DIRCMD env var */
686
if (GetEnvironmentVariableW(L"DIRCMD", string, ARRAY_SIZE(string))) {
687
wcsupr( string );
688
lstrcatW(string,quals);
689
lstrcpyW(quals, string);
690
}
691
692
byte_total = 0;
693
file_total = dir_total = 0;
694
695
/* Initialize all flags to their defaults as if no DIRCMD or quals */
696
paged_mode = FALSE;
697
recurse = FALSE;
698
wide = FALSE;
699
bare = FALSE;
700
lower = FALSE;
701
shortname = FALSE;
702
usernames = FALSE;
703
orderByCol = FALSE;
704
separator = TRUE;
705
dirTime = Written;
706
dirOrder = Unspecified;
707
orderReverse = FALSE;
708
orderGroupDirs = FALSE;
709
orderGroupDirsReverse = FALSE;
710
showattrs = 0;
711
attrsbits = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
712
713
/* Handle args - Loop through so right most is the effective one */
714
/* Note: /- appears to be a negate rather than an off, eg. dir
715
/-W is wide, or dir /w /-w /-w is also wide */
716
p = quals;
717
while (*p && (*p=='/' || *p==' ')) {
718
BOOL negate = FALSE;
719
if (*p++==' ') continue; /* Skip / and blanks introduced through DIRCMD */
720
721
if (*p=='-') {
722
negate = TRUE;
723
p++;
724
}
725
726
WINE_TRACE("Processing arg '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
727
switch (*p) {
728
case 'P': if (negate) paged_mode = !paged_mode;
729
else paged_mode = TRUE;
730
break;
731
case 'S': if (negate) recurse = !recurse;
732
else recurse = TRUE;
733
break;
734
case 'W': if (negate) wide = !wide;
735
else wide = TRUE;
736
break;
737
case 'B': if (negate) bare = !bare;
738
else bare = TRUE;
739
break;
740
case 'L': if (negate) lower = !lower;
741
else lower = TRUE;
742
break;
743
case 'X': if (negate) shortname = !shortname;
744
else shortname = TRUE;
745
break;
746
case 'Q': if (negate) usernames = !usernames;
747
else usernames = TRUE;
748
break;
749
case 'D': if (negate) orderByCol = !orderByCol;
750
else orderByCol = TRUE;
751
break;
752
case 'C': if (negate) separator = !separator;
753
else separator = TRUE;
754
break;
755
case 'T': p = p + 1;
756
if (*p==':') p++; /* Skip optional : */
757
758
if (*p == 'A') dirTime = Access;
759
else if (*p == 'C') dirTime = Creation;
760
else if (*p == 'W') dirTime = Written;
761
762
/* Support /T and /T: with no parms, default to written */
763
else if (*p == 0x00 || *p == '/') {
764
dirTime = Written;
765
p = p - 1; /* So when step on, move to '/' */
766
} else {
767
SetLastError(return_code = ERROR_INVALID_PARAMETER);
768
WCMD_print_error();
769
goto exit;
770
}
771
break;
772
case 'O': p = p + 1;
773
if (*p==':') p++; /* Skip optional : */
774
while (*p && *p != '/') {
775
WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
776
/* Options N,E,S,D are mutually-exclusive, first encountered takes precedence. */
777
switch (*p) {
778
case 'N': if (dirOrder == Unspecified) dirOrder = Name; break;
779
case 'E': if (dirOrder == Unspecified) dirOrder = Extension; break;
780
case 'S': if (dirOrder == Unspecified) dirOrder = Size; break;
781
case 'D': if (dirOrder == Unspecified) dirOrder = Date; break;
782
case '-': if (dirOrder == Unspecified) {
783
if (*(p+1)=='G') orderGroupDirsReverse=TRUE;
784
else if (*(p+1)=='N'||*(p+1)=='E'||*(p+1)=='S'||*(p+1)=='D') orderReverse = TRUE;
785
}
786
break;
787
case 'G': orderGroupDirs = TRUE; break;
788
default:
789
SetLastError(return_code = ERROR_INVALID_PARAMETER);
790
WCMD_print_error();
791
goto exit;
792
}
793
p++;
794
}
795
/* Handle default case of /O specified by itself, with no specific options.
796
This is equivalent to /O:GN. */
797
if (dirOrder == Unspecified && !orderGroupDirs) {
798
orderGroupDirs = TRUE;
799
orderGroupDirsReverse = FALSE;
800
dirOrder = Name;
801
}
802
p = p - 1; /* So when step on, move to '/' */
803
break;
804
case 'A': p = p + 1;
805
showattrs = 0;
806
attrsbits = 0;
807
if (*p==':') p++; /* Skip optional : */
808
while (*p && *p != '/') {
809
BOOL anegate = FALSE;
810
ULONG mask;
811
812
/* Note /A: - options are 'offs' not toggles */
813
if (*p=='-') {
814
anegate = TRUE;
815
p++;
816
}
817
818
WINE_TRACE("Processing subparm '%c' (in %s)\n", *p, wine_dbgstr_w(quals));
819
switch (*p) {
820
case 'D': mask = FILE_ATTRIBUTE_DIRECTORY; break;
821
case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
822
case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
823
case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
824
case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
825
default:
826
SetLastError(return_code = ERROR_INVALID_PARAMETER);
827
WCMD_print_error();
828
goto exit;
829
}
830
831
/* Keep running list of bits we care about */
832
attrsbits |= mask;
833
834
/* Mask shows what MUST be in the bits we care about */
835
if (anegate) showattrs = showattrs & ~mask;
836
else showattrs |= mask;
837
838
p++;
839
}
840
p = p - 1; /* So when step on, move to '/' */
841
WINE_TRACE("Result: showattrs %lx, bits %lx\n", showattrs, attrsbits);
842
break;
843
default:
844
SetLastError(return_code = ERROR_INVALID_PARAMETER);
845
WCMD_print_error();
846
goto exit;
847
}
848
p = p + 1;
849
}
850
851
/* Handle conflicting args and initialization */
852
if (bare || shortname) wide = FALSE;
853
if (bare) shortname = FALSE;
854
if (wide) usernames = FALSE;
855
if (orderByCol) wide = TRUE;
856
857
if (wide) {
858
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
859
max_width = consoleInfo.dwSize.X;
860
else
861
max_width = 80;
862
}
863
if (paged_mode) {
864
WCMD_enter_paged_mode(NULL);
865
}
866
867
init_date_format();
868
init_time_format();
869
870
argno = 0;
871
argN = args;
872
GetCurrentDirectoryW(MAX_PATH, cwd);
873
lstrcatW(cwd, L"\\");
874
875
/* Loop through all args, calculating full effective directory */
876
fullParms = NULL;
877
prevEntry = NULL;
878
while (argN) {
879
WCHAR fullname[MAXSTRING];
880
WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
881
if (argN && argN[0] != '/') {
882
883
WINE_TRACE("Found parm '%s'\n", wine_dbgstr_w(thisArg));
884
if (thisArg[1] == ':' && thisArg[2] == '\\') {
885
lstrcpyW(fullname, thisArg);
886
} else if (thisArg[1] == ':' && thisArg[2] != '\\') {
887
WCHAR envvar[4];
888
wsprintfW(envvar, L"=%c:", thisArg[0]);
889
if (!GetEnvironmentVariableW(envvar, fullname, MAX_PATH)) {
890
wsprintfW(fullname, L"%c:", thisArg[0]);
891
}
892
lstrcatW(fullname, L"\\");
893
lstrcatW(fullname, &thisArg[2]);
894
} else if (thisArg[0] == '\\') {
895
memcpy(fullname, cwd, 2 * sizeof(WCHAR));
896
lstrcpyW(fullname+2, thisArg);
897
} else {
898
lstrcpyW(fullname, cwd);
899
lstrcatW(fullname, thisArg);
900
}
901
WINE_TRACE("Using location '%s'\n", wine_dbgstr_w(fullname));
902
903
if (!WCMD_get_fullpath(fullname, ARRAY_SIZE(path), path, NULL)) continue;
904
905
/*
906
* If the path supplied does not include a wildcard, and the endpoint of the
907
* path references a directory, we need to list the *contents* of that
908
* directory not the directory file itself.
909
*/
910
if ((wcschr(path, '*') == NULL) && (wcschr(path, '%') == NULL)) {
911
status = GetFileAttributesW(path);
912
if ((status != INVALID_FILE_ATTRIBUTES) && (status & FILE_ATTRIBUTE_DIRECTORY)) {
913
if (!ends_with_backslash(path)) lstrcatW(path, L"\\");
914
lstrcatW(path, L"*");
915
}
916
} else {
917
/* Special case wildcard search with no extension (ie parameters ending in '.') as
918
GetFullPathName strips off the additional '.' */
919
if (fullname[lstrlenW(fullname)-1] == '.') lstrcatW(path, L".");
920
}
921
922
WINE_TRACE("Using path '%s'\n", wine_dbgstr_w(path));
923
thisEntry = xalloc(sizeof(DIRECTORY_STACK));
924
if (fullParms == NULL) fullParms = thisEntry;
925
if (prevEntry != NULL) prevEntry->next = thisEntry;
926
prevEntry = thisEntry;
927
thisEntry->next = NULL;
928
929
/* Split into components */
930
_wsplitpath(path, drive, dir, fname, ext);
931
WINE_TRACE("Path Parts: drive: '%s' dir: '%s' name: '%s' ext:'%s'\n",
932
wine_dbgstr_w(drive), wine_dbgstr_w(dir),
933
wine_dbgstr_w(fname), wine_dbgstr_w(ext));
934
935
thisEntry->dirName = xalloc(sizeof(WCHAR) * (wcslen(drive) + wcslen(dir) + 1));
936
lstrcpyW(thisEntry->dirName, drive);
937
lstrcatW(thisEntry->dirName, dir);
938
939
thisEntry->fileName = xalloc(sizeof(WCHAR) * (wcslen(fname) + wcslen(ext) + 1));
940
lstrcpyW(thisEntry->fileName, fname);
941
lstrcatW(thisEntry->fileName, ext);
942
943
}
944
}
945
946
/* If just 'dir' entered, a '*' parameter is assumed */
947
if (fullParms == NULL) {
948
WINE_TRACE("Inserting default '*'\n");
949
fullParms = xalloc(sizeof(DIRECTORY_STACK));
950
fullParms->next = NULL;
951
fullParms->dirName = xstrdupW(cwd);
952
fullParms->fileName = xstrdupW(L"*");
953
}
954
955
lastDrive = '?';
956
prevEntry = NULL;
957
thisEntry = fullParms;
958
trailerReqd = FALSE;
959
960
while (thisEntry != NULL && return_code == NO_ERROR) {
961
962
/* Output disk free (trailer) and volume information (header) if the drive
963
letter changes */
964
if (lastDrive != towupper(thisEntry->dirName[0])) {
965
966
/* Trailer Information */
967
if (lastDrive != '?') {
968
trailerReqd = FALSE;
969
if (return_code == NO_ERROR)
970
WCMD_dir_trailer(prevEntry->dirName);
971
byte_total = file_total = dir_total = 0;
972
}
973
974
lastDrive = towupper(thisEntry->dirName[0]);
975
976
if (!bare) {
977
WCHAR drive[4];
978
WINE_TRACE("Writing volume for '%c:'\n", thisEntry->dirName[0]);
979
drive[0] = thisEntry->dirName[0];
980
drive[1] = thisEntry->dirName[1];
981
drive[2] = L'\\';
982
drive[3] = L'\0';
983
trailerReqd = TRUE;
984
if (!WCMD_print_volume_information(drive)) {
985
return_code = ERROR_INVALID_PARAMETER;
986
goto exit;
987
}
988
}
989
} else {
990
if (!bare) WCMD_output_asis (L"\n\n");
991
}
992
993
prevEntry = thisEntry;
994
return_code = WCMD_list_directory (thisEntry, 0, &thisEntry);
995
if (return_code == ERROR_FILE_NOT_FOUND)
996
num_empty++;
997
else
998
num_with_data++;
999
}
1000
1001
/* Trailer Information */
1002
if (trailerReqd && return_code == NO_ERROR) {
1003
WCMD_dir_trailer(prevEntry->dirName);
1004
}
1005
1006
exit:
1007
if (return_code == STATUS_CONTROL_C_EXIT)
1008
errorlevel = ERROR_INVALID_FUNCTION;
1009
else if (return_code != NO_ERROR || (num_empty && !num_with_data))
1010
return_code = errorlevel = ERROR_INVALID_FUNCTION;
1011
else
1012
errorlevel = NO_ERROR;
1013
1014
if (paged_mode) WCMD_leave_paged_mode();
1015
1016
/* Free storage allocated for parms */
1017
while (fullParms != NULL) {
1018
prevEntry = fullParms;
1019
fullParms = prevEntry->next;
1020
free(prevEntry->dirName);
1021
free(prevEntry->fileName);
1022
free(prevEntry);
1023
}
1024
1025
return return_code;
1026
}
1027
1028