1 |
/* |
2 |
* The nsinstall command for Win32 |
3 |
* |
4 |
* Our gmake makefiles use the nsinstall command to create the |
5 |
* object directories or installing headers and libs. This code was originally |
6 |
* taken from shmsdos.c |
7 |
*/ |
8 |
|
9 |
#include <direct.h> |
10 |
#include <stdio.h> |
11 |
#include <string.h> |
12 |
#include <assert.h> |
13 |
#include <windows.h> |
14 |
#pragma hdrstop |
15 |
|
16 |
/* |
17 |
* sh_FileFcn -- |
18 |
* |
19 |
* A function that operates on a file. The pathname is either |
20 |
* absolute or relative to the current directory, and contains |
21 |
* no wildcard characters such as * and ?. Additional arguments |
22 |
* can be passed to the function via the arg pointer. |
23 |
*/ |
24 |
|
25 |
typedef BOOL (*sh_FileFcn)( |
26 |
wchar_t *pathName, |
27 |
WIN32_FIND_DATA *fileData, |
28 |
void *arg); |
29 |
|
30 |
static int shellCp (wchar_t **pArgv); |
31 |
static int shellNsinstall (wchar_t **pArgv); |
32 |
static int shellMkdir (wchar_t **pArgv); |
33 |
static BOOL sh_EnumerateFiles(const wchar_t *pattern, const wchar_t *where, |
34 |
sh_FileFcn fileFcn, void *arg, int *nFiles); |
35 |
static const char *sh_GetLastErrorMessage(void); |
36 |
static BOOL sh_DoCopy(wchar_t *srcFileName, DWORD srcFileAttributes, |
37 |
wchar_t *dstFileName, DWORD dstFileAttributes, |
38 |
int force, int recursive); |
39 |
|
40 |
#define LONGPATH_PREFIX L"\\\\?\\" |
41 |
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) |
42 |
#define STR_LEN(a) (ARRAY_LEN(a) - 1) |
43 |
|
44 |
/* changes all forward slashes in token to backslashes */ |
45 |
void changeForwardSlashesToBackSlashes ( wchar_t *arg ) |
46 |
{ |
47 |
if ( arg == NULL ) |
48 |
return; |
49 |
|
50 |
while ( *arg ) { |
51 |
if ( *arg == '/' ) |
52 |
*arg = '\\'; |
53 |
arg++; |
54 |
} |
55 |
} |
56 |
|
57 |
int wmain(int argc, wchar_t *argv[ ]) |
58 |
{ |
59 |
return shellNsinstall ( argv + 1 ); |
60 |
} |
61 |
|
62 |
static int |
63 |
shellNsinstall (wchar_t **pArgv) |
64 |
{ |
65 |
int retVal = 0; /* exit status */ |
66 |
int dirOnly = 0; /* 1 if and only if -D is specified */ |
67 |
wchar_t **pSrc; |
68 |
wchar_t **pDst; |
69 |
|
70 |
/* |
71 |
* Process the command-line options. We ignore the |
72 |
* options except for -D. Some options, such as -m, |
73 |
* are followed by an argument. We need to skip the |
74 |
* argument too. |
75 |
*/ |
76 |
while ( *pArgv && **pArgv == '-' ) { |
77 |
wchar_t c = (*pArgv)[1]; /* The char after '-' */ |
78 |
|
79 |
if ( c == 'D' ) { |
80 |
dirOnly = 1; |
81 |
} else if ( c == 'm' ) { |
82 |
pArgv++; /* skip the next argument */ |
83 |
} |
84 |
pArgv++; |
85 |
} |
86 |
|
87 |
if ( !dirOnly ) { |
88 |
/* There are files to install. Get source files */ |
89 |
if ( *pArgv ) { |
90 |
pSrc = pArgv++; |
91 |
} else { |
92 |
fprintf( stderr, "nsinstall: not enough arguments\n"); |
93 |
return 3; |
94 |
} |
95 |
} |
96 |
|
97 |
/* Get to last token to find destination directory */ |
98 |
if ( *pArgv ) { |
99 |
pDst = pArgv++; |
100 |
if ( dirOnly && *pArgv ) { |
101 |
fprintf( stderr, "nsinstall: too many arguments with -D\n"); |
102 |
return 3; |
103 |
} |
104 |
} else { |
105 |
fprintf( stderr, "nsinstall: not enough arguments\n"); |
106 |
return 3; |
107 |
} |
108 |
while ( *pArgv ) |
109 |
pDst = pArgv++; |
110 |
|
111 |
retVal = shellMkdir ( pDst ); |
112 |
if ( retVal ) |
113 |
return retVal; |
114 |
if ( !dirOnly ) |
115 |
retVal = shellCp ( pSrc ); |
116 |
return retVal; |
117 |
} |
118 |
|
119 |
static int |
120 |
shellMkdir (wchar_t **pArgv) |
121 |
{ |
122 |
int retVal = 0; /* assume valid return */ |
123 |
wchar_t *arg; |
124 |
wchar_t *pArg; |
125 |
wchar_t path[_MAX_PATH]; |
126 |
wchar_t tmpPath[_MAX_PATH]; |
127 |
wchar_t *pTmpPath = tmpPath; |
128 |
|
129 |
/* All the options are simply ignored in this implementation */ |
130 |
while ( *pArgv && **pArgv == '-' ) { |
131 |
if ( (*pArgv)[1] == 'm' ) { |
132 |
pArgv++; /* skip the next argument (mode) */ |
133 |
} |
134 |
pArgv++; |
135 |
} |
136 |
|
137 |
while ( *pArgv ) { |
138 |
arg = *pArgv; |
139 |
changeForwardSlashesToBackSlashes ( arg ); |
140 |
pArg = arg; |
141 |
pTmpPath = tmpPath; |
142 |
while ( 1 ) { |
143 |
/* create part of path */ |
144 |
while ( *pArg ) { |
145 |
*pTmpPath++ = *pArg++; |
146 |
if ( *pArg == '\\' ) |
147 |
break; |
148 |
} |
149 |
*pTmpPath = '\0'; |
150 |
|
151 |
/* check if directory already exists */ |
152 |
_wgetcwd ( path, _MAX_PATH ); |
153 |
if ( _wchdir ( tmpPath ) != -1 ) { |
154 |
_wchdir ( path ); |
155 |
} else { |
156 |
if ( _wmkdir ( tmpPath ) == -1 ) { |
157 |
printf ( "%ls: ", tmpPath ); |
158 |
perror ( "Could not create the directory" ); |
159 |
retVal = 3; |
160 |
break; |
161 |
} |
162 |
} |
163 |
if ( *pArg == '\0' ) /* complete path? */ |
164 |
break; |
165 |
/* loop for next directory */ |
166 |
} |
167 |
|
168 |
pArgv++; |
169 |
} |
170 |
return retVal; |
171 |
} |
172 |
|
173 |
static const char * |
174 |
sh_GetLastErrorMessage() |
175 |
{ |
176 |
static char buf[128]; |
177 |
|
178 |
FormatMessageA( |
179 |
FORMAT_MESSAGE_FROM_SYSTEM, |
180 |
NULL, |
181 |
GetLastError(), |
182 |
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* default language */ |
183 |
buf, |
184 |
sizeof(buf), |
185 |
NULL |
186 |
); |
187 |
return buf; |
188 |
} |
189 |
|
190 |
/* |
191 |
* struct sh_FileData -- |
192 |
* |
193 |
* A pointer to the sh_FileData structure is passed into sh_RecordFileData, |
194 |
* which will fill in the fields. |
195 |
*/ |
196 |
|
197 |
struct sh_FileData { |
198 |
wchar_t pathName[_MAX_PATH]; |
199 |
DWORD dwFileAttributes; |
200 |
}; |
201 |
|
202 |
/* |
203 |
* sh_RecordFileData -- |
204 |
* |
205 |
* Record the pathname and attributes of the file in |
206 |
* the sh_FileData structure pointed to by arg. |
207 |
* |
208 |
* Always return TRUE (successful completion). |
209 |
* |
210 |
* This function is intended to be passed into sh_EnumerateFiles |
211 |
* to see if a certain pattern expands to exactly one file/directory, |
212 |
* and if so, record its pathname and attributes. |
213 |
*/ |
214 |
|
215 |
static BOOL |
216 |
sh_RecordFileData(wchar_t *pathName, WIN32_FIND_DATA *findData, void *arg) |
217 |
{ |
218 |
struct sh_FileData *fData = (struct sh_FileData *) arg; |
219 |
|
220 |
wcscpy(fData->pathName, pathName); |
221 |
fData->dwFileAttributes = findData->dwFileAttributes; |
222 |
return TRUE; |
223 |
} |
224 |
|
225 |
static BOOL |
226 |
sh_DoCopy(wchar_t *srcFileName, |
227 |
DWORD srcFileAttributes, |
228 |
wchar_t *dstFileName, |
229 |
DWORD dstFileAttributes, |
230 |
int force, |
231 |
int recursive |
232 |
) |
233 |
{ |
234 |
if (dstFileAttributes != 0xFFFFFFFF) { |
235 |
if ((dstFileAttributes & FILE_ATTRIBUTE_READONLY) && force) { |
236 |
dstFileAttributes &= ~FILE_ATTRIBUTE_READONLY; |
237 |
SetFileAttributes(dstFileName, dstFileAttributes); |
238 |
} |
239 |
} |
240 |
|
241 |
if (srcFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { |
242 |
fprintf(stderr, "nsinstall: %ls is a directory\n", |
243 |
srcFileName); |
244 |
return FALSE; |
245 |
} else { |
246 |
DWORD r; |
247 |
wchar_t longSrc[1004] = LONGPATH_PREFIX; |
248 |
wchar_t longDst[1004] = LONGPATH_PREFIX; |
249 |
r = GetFullPathName(srcFileName, 1000, longSrc + STR_LEN(LONGPATH_PREFIX), NULL); |
250 |
if (!r) { |
251 |
fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n", |
252 |
srcFileName, sh_GetLastErrorMessage()); |
253 |
return FALSE; |
254 |
} |
255 |
r = GetFullPathName(dstFileName, 1000, longDst + ARRAY_LEN(LONGPATH_PREFIX) - 1, NULL); |
256 |
if (!r) { |
257 |
fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n", |
258 |
dstFileName, sh_GetLastErrorMessage()); |
259 |
return FALSE; |
260 |
} |
261 |
|
262 |
if (!CopyFile(longSrc, longDst, FALSE)) { |
263 |
fprintf(stderr, "nsinstall: cannot copy %ls to %ls: %s\n", |
264 |
srcFileName, dstFileName, sh_GetLastErrorMessage()); |
265 |
return FALSE; |
266 |
} |
267 |
} |
268 |
return TRUE; |
269 |
} |
270 |
|
271 |
/* |
272 |
* struct sh_CpCmdArg -- |
273 |
* |
274 |
* A pointer to the sh_CpCmdArg structure is passed into sh_CpFileCmd. |
275 |
* The sh_CpCmdArg contains information about the cp command, and |
276 |
* provide a buffer for constructing the destination file name. |
277 |
*/ |
278 |
|
279 |
struct sh_CpCmdArg { |
280 |
int force; /* -f option, ok to overwrite an existing |
281 |
* read-only destination file */ |
282 |
int recursive; /* -r or -R option, recursively copy |
283 |
* directories. Note: this field is not used |
284 |
* by nsinstall and should always be 0. */ |
285 |
wchar_t *dstFileName; /* a buffer for constructing the destination |
286 |
* file name */ |
287 |
wchar_t *dstFileNameMarker; /* points to where in the dstFileName buffer |
288 |
* we should write the file component of the |
289 |
* destination file */ |
290 |
}; |
291 |
|
292 |
/* |
293 |
* sh_CpFileCmd -- |
294 |
* |
295 |
* Copy a file to the destination directory |
296 |
* |
297 |
* This function is intended to be passed into sh_EnumerateFiles to |
298 |
* copy all the files specified by the pattern to the destination |
299 |
* directory. |
300 |
* |
301 |
* Return TRUE if the file is successfully copied, and FALSE otherwise. |
302 |
*/ |
303 |
|
304 |
static BOOL |
305 |
sh_CpFileCmd(wchar_t *pathName, WIN32_FIND_DATA *findData, void *cpArg) |
306 |
{ |
307 |
BOOL retVal = TRUE; |
308 |
struct sh_CpCmdArg *arg = (struct sh_CpCmdArg *) cpArg; |
309 |
|
310 |
wcscpy(arg->dstFileNameMarker, findData->cFileName); |
311 |
return sh_DoCopy(pathName, findData->dwFileAttributes, |
312 |
arg->dstFileName, GetFileAttributes(arg->dstFileName), |
313 |
arg->force, arg->recursive); |
314 |
} |
315 |
|
316 |
static int |
317 |
shellCp (wchar_t **pArgv) |
318 |
{ |
319 |
int retVal = 0; |
320 |
wchar_t **pSrc; |
321 |
wchar_t **pDst; |
322 |
struct sh_CpCmdArg arg; |
323 |
struct sh_FileData dstData; |
324 |
int dstIsDir = 0; |
325 |
int n; |
326 |
|
327 |
arg.force = 0; |
328 |
arg.recursive = 0; |
329 |
arg.dstFileName = dstData.pathName; |
330 |
arg.dstFileNameMarker = 0; |
331 |
|
332 |
while (*pArgv && **pArgv == '-') { |
333 |
wchar_t *p = *pArgv; |
334 |
|
335 |
while (*(++p)) { |
336 |
if (*p == 'f') { |
337 |
arg.force = 1; |
338 |
} |
339 |
} |
340 |
pArgv++; |
341 |
} |
342 |
|
343 |
/* the first source file */ |
344 |
if (*pArgv) { |
345 |
pSrc = pArgv++; |
346 |
} else { |
347 |
fprintf(stderr, "nsinstall: not enough arguments\n"); |
348 |
return 3; |
349 |
} |
350 |
|
351 |
/* get to the last token to find destination */ |
352 |
if (*pArgv) { |
353 |
pDst = pArgv++; |
354 |
} else { |
355 |
fprintf(stderr, "nsinstall: not enough arguments\n"); |
356 |
return 3; |
357 |
} |
358 |
while (*pArgv) { |
359 |
pDst = pArgv++; |
360 |
} |
361 |
|
362 |
/* |
363 |
* The destination pattern must unambiguously expand to exactly |
364 |
* one file or directory. |
365 |
*/ |
366 |
|
367 |
changeForwardSlashesToBackSlashes(*pDst); |
368 |
sh_EnumerateFiles(*pDst, *pDst, sh_RecordFileData, &dstData, &n); |
369 |
assert(n >= 0); |
370 |
if (n == 1) { |
371 |
/* |
372 |
* Is the destination a file or directory? |
373 |
*/ |
374 |
|
375 |
if (dstData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { |
376 |
dstIsDir = 1; |
377 |
} |
378 |
} else if (n > 1) { |
379 |
fprintf(stderr, "nsinstall: %ls: ambiguous destination file " |
380 |
"or directory\n", *pDst); |
381 |
return 3; |
382 |
} else { |
383 |
/* |
384 |
* n == 0, meaning that destination file or directory does |
385 |
* not exist. In this case the destination file directory |
386 |
* name must be fully specified. |
387 |
*/ |
388 |
|
389 |
wchar_t *p; |
390 |
|
391 |
for (p = *pDst; *p; p++) { |
392 |
if (*p == '*' || *p == '?') { |
393 |
fprintf(stderr, "nsinstall: %ls: No such file or directory\n", |
394 |
*pDst); |
395 |
return 3; |
396 |
} |
397 |
} |
398 |
|
399 |
/* |
400 |
* Do not include the trailing \, if any, unless it is a root |
401 |
* directory (\ or X:\). |
402 |
*/ |
403 |
|
404 |
if (p > *pDst && p[-1] == '\\' && p != *pDst + 1 && p[-2] != ':') { |
405 |
p[-1] = '\0'; |
406 |
} |
407 |
wcscpy(dstData.pathName, *pDst); |
408 |
dstData.dwFileAttributes = 0xFFFFFFFF; |
409 |
} |
410 |
|
411 |
/* |
412 |
* If there are two or more source files, the destination has |
413 |
* to be a directory. |
414 |
*/ |
415 |
|
416 |
if (pDst - pSrc > 1 && !dstIsDir) { |
417 |
fprintf(stderr, "nsinstall: cannot copy more than" |
418 |
" one file to the same destination file\n"); |
419 |
return 3; |
420 |
} |
421 |
|
422 |
if (dstIsDir) { |
423 |
arg.dstFileNameMarker = arg.dstFileName + wcslen(arg.dstFileName); |
424 |
|
425 |
/* |
426 |
* Now arg.dstFileNameMarker is pointing to the null byte at the |
427 |
* end of string. We want to make sure that there is a \ at the |
428 |
* end of string, and arg.dstFileNameMarker should point right |
429 |
* after that \. |
430 |
*/ |
431 |
|
432 |
if (arg.dstFileNameMarker[-1] != '\\') { |
433 |
*(arg.dstFileNameMarker++) = '\\'; |
434 |
} |
435 |
} |
436 |
|
437 |
if (!dstIsDir) { |
438 |
struct sh_FileData srcData; |
439 |
|
440 |
assert(pDst - pSrc == 1); |
441 |
changeForwardSlashesToBackSlashes(*pSrc); |
442 |
sh_EnumerateFiles(*pSrc, *pSrc, sh_RecordFileData, &srcData, &n); |
443 |
if (n == 0) { |
444 |
fprintf(stderr, "nsinstall: %ls: No such file or directory\n", |
445 |
*pSrc); |
446 |
retVal = 3; |
447 |
} else if (n > 1) { |
448 |
fprintf(stderr, "nsinstall: cannot copy more than one file or " |
449 |
"directory to the same destination\n"); |
450 |
retVal = 3; |
451 |
} else { |
452 |
assert(n == 1); |
453 |
if (sh_DoCopy(srcData.pathName, srcData.dwFileAttributes, |
454 |
dstData.pathName, dstData.dwFileAttributes, |
455 |
arg.force, arg.recursive) == FALSE) { |
456 |
retVal = 3; |
457 |
} |
458 |
} |
459 |
return retVal; |
460 |
} |
461 |
|
462 |
for ( ; *pSrc != *pDst; pSrc++) { |
463 |
BOOL rv; |
464 |
|
465 |
changeForwardSlashesToBackSlashes(*pSrc); |
466 |
rv = sh_EnumerateFiles(*pSrc, *pSrc, sh_CpFileCmd, &arg, &n); |
467 |
if (rv == FALSE) { |
468 |
retVal = 3; |
469 |
} else { |
470 |
if (n == 0) { |
471 |
fprintf(stderr, "nsinstall: %ls: No such file or directory\n", |
472 |
*pSrc); |
473 |
retVal = 3; |
474 |
} |
475 |
} |
476 |
} |
477 |
|
478 |
return retVal; |
479 |
} |
480 |
|
481 |
/* |
482 |
* sh_EnumerateFiles -- |
483 |
* |
484 |
* Enumerate all the files in the specified pattern, which is a pathname |
485 |
* containing possibly wildcard characters such as * and ?. fileFcn |
486 |
* is called on each file, passing the expanded file name, a pointer |
487 |
* to the file's WIN32_FILE_DATA, and the arg pointer. |
488 |
* |
489 |
* It is assumed that there are no wildcard characters before the |
490 |
* character pointed to by 'where'. |
491 |
* |
492 |
* On return, *nFiles stores the number of files enumerated. *nFiles is |
493 |
* set to this number whether sh_EnumerateFiles or 'fileFcn' succeeds |
494 |
* or not. |
495 |
* |
496 |
* Return TRUE if the files are successfully enumerated and all |
497 |
* 'fileFcn' invocations succeeded. Return FALSE if something went |
498 |
* wrong. |
499 |
*/ |
500 |
|
501 |
static BOOL sh_EnumerateFiles( |
502 |
const wchar_t *pattern, |
503 |
const wchar_t *where, |
504 |
sh_FileFcn fileFcn, |
505 |
void *arg, |
506 |
int *nFiles |
507 |
) |
508 |
{ |
509 |
WIN32_FIND_DATA fileData; |
510 |
HANDLE hSearch; |
511 |
const wchar_t *src; |
512 |
wchar_t *dst; |
513 |
wchar_t fileName[_MAX_PATH]; |
514 |
wchar_t *fileNameMarker = fileName; |
515 |
wchar_t *oldFileNameMarker; |
516 |
BOOL hasWildcard = FALSE; |
517 |
BOOL retVal = TRUE; |
518 |
BOOL patternEndsInDotStar = FALSE; |
519 |
BOOL patternEndsInDot = FALSE; /* a special case of |
520 |
* patternEndsInDotStar */ |
521 |
int numDotsInPattern; |
522 |
int len; |
523 |
|
524 |
/* |
525 |
* Windows expands patterns ending in ".", ".*", ".**", etc. |
526 |
* differently from the glob expansion on Unix. For example, |
527 |
* both "foo." and "foo.*" match "foo", and "*.*" matches |
528 |
* everything, including filenames with no dots. So we need |
529 |
* to throw away extra files returned by the FindNextFile() |
530 |
* function. We require that a matched filename have at least |
531 |
* the number of dots in the pattern. |
532 |
*/ |
533 |
len = wcslen(pattern); |
534 |
if (len >= 2) { |
535 |
/* Start from the end of pattern and go backward */ |
536 |
const wchar_t *p = &pattern[len - 1]; |
537 |
|
538 |
/* We can have zero or more *'s */ |
539 |
while (p >= pattern && *p == '*') { |
540 |
p--; |
541 |
} |
542 |
if (p >= pattern && *p == '.') { |
543 |
patternEndsInDotStar = TRUE; |
544 |
if (p == &pattern[len - 1]) { |
545 |
patternEndsInDot = TRUE; |
546 |
} |
547 |
p--; |
548 |
numDotsInPattern = 1; |
549 |
while (p >= pattern && *p != '\\') { |
550 |
if (*p == '.') { |
551 |
numDotsInPattern++; |
552 |
} |
553 |
p--; |
554 |
} |
555 |
} |
556 |
} |
557 |
|
558 |
*nFiles = 0; |
559 |
|
560 |
/* |
561 |
* Copy pattern to fileName, but only up to and not including |
562 |
* the first \ after the first wildcard letter. |
563 |
* |
564 |
* Make fileNameMarker point to one of the following: |
565 |
* - the start of fileName, if fileName does not contain any \. |
566 |
* - right after the \ before the first wildcard letter, if there is |
567 |
* a wildcard character. |
568 |
* - right after the last \, if there is no wildcard character. |
569 |
*/ |
570 |
|
571 |
dst = fileName; |
572 |
src = pattern; |
573 |
while (src < where) { |
574 |
if (*src == '\\') { |
575 |
oldFileNameMarker = fileNameMarker; |
576 |
fileNameMarker = dst + 1; |
577 |
} |
578 |
*(dst++) = *(src++); |
579 |
} |
580 |
|
581 |
while (*src && *src != '*' && *src != '?') { |
582 |
if (*src == '\\') { |
583 |
oldFileNameMarker = fileNameMarker; |
584 |
fileNameMarker = dst + 1; |
585 |
} |
586 |
*(dst++) = *(src++); |
587 |
} |
588 |
|
589 |
if (*src) { |
590 |
/* |
591 |
* Must have seen the first wildcard letter |
592 |
*/ |
593 |
|
594 |
hasWildcard = TRUE; |
595 |
while (*src && *src != '\\') { |
596 |
*(dst++) = *(src++); |
597 |
} |
598 |
} |
599 |
|
600 |
/* Now src points to either null or \ */ |
601 |
|
602 |
assert(*src == '\0' || *src == '\\'); |
603 |
assert(hasWildcard || *src == '\0'); |
604 |
*dst = '\0'; |
605 |
|
606 |
/* |
607 |
* If the pattern does not contain any wildcard characters, then |
608 |
* we don't need to go the FindFirstFile route. |
609 |
*/ |
610 |
|
611 |
if (!hasWildcard) { |
612 |
/* |
613 |
* See if it is the root directory, \, or X:\. |
614 |
*/ |
615 |
|
616 |
assert(!wcscmp(fileName, pattern)); |
617 |
assert(wcslen(fileName) >= 1); |
618 |
if (dst[-1] == '\\' && (dst == fileName + 1 || dst[-2] == ':')) { |
619 |
fileData.cFileName[0] = '\0'; |
620 |
} else { |
621 |
/* |
622 |
* Do not include the trailing \, if any |
623 |
*/ |
624 |
|
625 |
if (dst[-1] == '\\') { |
626 |
assert(*fileNameMarker == '\0'); |
627 |
dst[-1] = '\0'; |
628 |
fileNameMarker = oldFileNameMarker; |
629 |
} |
630 |
wcscpy(fileData.cFileName, fileNameMarker); |
631 |
} |
632 |
fileData.dwFileAttributes = GetFileAttributes(fileName); |
633 |
if (fileData.dwFileAttributes == 0xFFFFFFFF) { |
634 |
return TRUE; |
635 |
} |
636 |
*nFiles = 1; |
637 |
return (*fileFcn)(fileName, &fileData, arg); |
638 |
} |
639 |
|
640 |
hSearch = FindFirstFile(fileName, &fileData); |
641 |
if (hSearch == INVALID_HANDLE_VALUE) { |
642 |
return retVal; |
643 |
} |
644 |
|
645 |
do { |
646 |
if (!wcscmp(fileData.cFileName, L".") |
647 |
|| !wcscmp(fileData.cFileName, L"..")) { |
648 |
/* |
649 |
* Skip over . and .. |
650 |
*/ |
651 |
|
652 |
continue; |
653 |
} |
654 |
|
655 |
if (patternEndsInDotStar) { |
656 |
int nDots = 0; |
657 |
wchar_t *p = fileData.cFileName; |
658 |
while (*p) { |
659 |
if (*p == '.') { |
660 |
nDots++; |
661 |
} |
662 |
p++; |
663 |
} |
664 |
/* Now p points to the null byte at the end of file name */ |
665 |
if (patternEndsInDot && (p == fileData.cFileName |
666 |
|| p[-1] != '.')) { |
667 |
/* |
668 |
* File name does not end in dot. Skip this file. |
669 |
* Note: windows file name probably cannot end in dot, |
670 |
* but we do this check anyway. |
671 |
*/ |
672 |
continue; |
673 |
} |
674 |
if (nDots < numDotsInPattern) { |
675 |
/* |
676 |
* Not enough dots in file name. Must be an extra |
677 |
* file in matching .* pattern. Skip this file. |
678 |
*/ |
679 |
continue; |
680 |
} |
681 |
} |
682 |
|
683 |
wcscpy(fileNameMarker, fileData.cFileName); |
684 |
if (*src && *(src + 1)) { |
685 |
/* |
686 |
* More to go. Recurse. |
687 |
*/ |
688 |
|
689 |
int n; |
690 |
|
691 |
assert(*src == '\\'); |
692 |
where = fileName + wcslen(fileName); |
693 |
wcscat(fileName, src); |
694 |
sh_EnumerateFiles(fileName, where, fileFcn, arg, &n); |
695 |
*nFiles += n; |
696 |
} else { |
697 |
assert(wcschr(fileName, '*') == NULL); |
698 |
assert(wcschr(fileName, '?') == NULL); |
699 |
(*nFiles)++; |
700 |
if ((*fileFcn)(fileName, &fileData, arg) == FALSE) { |
701 |
retVal = FALSE; |
702 |
} |
703 |
} |
704 |
} while (FindNextFile(hSearch, &fileData)); |
705 |
|
706 |
FindClose(hSearch); |
707 |
return retVal; |
708 |
} |