1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | /*++ |
6 | |
7 | |
8 | |
9 | Module Name: |
10 | |
11 | find.c |
12 | |
13 | Abstract: |
14 | |
15 | Implementation of the FindFile function family |
16 | |
17 | Revision History: |
18 | |
19 | |
20 | |
21 | --*/ |
22 | |
23 | #include "pal/thread.hpp" |
24 | #include "pal/malloc.hpp" |
25 | #include "pal/file.hpp" |
26 | #include "pal/stackstring.hpp" |
27 | |
28 | #include "pal/palinternal.h" |
29 | #include "pal/dbgmsg.h" |
30 | #include "pal/file.h" |
31 | #include "pal/filetime.h" |
32 | |
33 | #include <sys/types.h> |
34 | #include <sys/stat.h> |
35 | #include <errno.h> |
36 | #include <limits.h> |
37 | |
38 | using namespace CorUnix; |
39 | |
40 | SET_DEFAULT_DEBUG_CHANNEL(FILE); |
41 | |
42 | namespace CorUnix |
43 | { |
44 | int InternalGlob( |
45 | const char *szPattern, |
46 | int nFlags, |
47 | #if ERROR_FUNC_FOR_GLOB_HAS_FIXED_PARAMS |
48 | int (*pnErrFunc)(const char *, int), |
49 | #else |
50 | int (*pnErrFunc)(...), |
51 | #endif |
52 | glob_t *pgGlob |
53 | ); |
54 | |
55 | /*++ |
56 | InternalGlob |
57 | |
58 | Input parameters: |
59 | |
60 | szPattern = pointer to a pathname pattern to be expanded |
61 | nFlags = arguments to modify the behavior of glob |
62 | pnErrFunc = pointer to a routine that handles errors during the glob call |
63 | pgGlob = pointer to a glob structure |
64 | |
65 | Return value: |
66 | 0 on success, -1 on failure. |
67 | |
68 | Some platforms expect the error function for glob to take a variable number |
69 | of parameters, whereas other platforms insist that the error function take |
70 | a const char * and an int. A test in configure determines which is the case |
71 | for each platform and sets ERROR_FUNC_FOR_GLOB_HAS_FIXED_PARAMS |
72 | to 1 if the error func must have the char * and int parameters. |
73 | --*/ |
74 | int |
75 | InternalGlob( |
76 | const char *szPattern, |
77 | int nFlags, |
78 | #if ERROR_FUNC_FOR_GLOB_HAS_FIXED_PARAMS |
79 | int (*pnErrFunc)(const char *, int), |
80 | #else |
81 | int (*pnErrFunc)(...), |
82 | #endif |
83 | glob_t *pgGlob |
84 | ) |
85 | { |
86 | int nRet = -1; |
87 | nRet = glob(szPattern, nFlags, pnErrFunc, pgGlob); |
88 | return nRet; |
89 | } |
90 | } |
91 | |
92 | static BOOL FILEDosGlobA( |
93 | CPalThread *pthrCurrent, |
94 | const char *pattern, |
95 | int flags, |
96 | glob_t *pgGlob ); |
97 | |
98 | static int FILEGlobQsortCompare(const void *in_str1, const void *in_str2); |
99 | |
100 | static int FILEGlobFromSplitPath( |
101 | const char *dir, |
102 | const char *fname, |
103 | const char *ext, |
104 | int flags, |
105 | glob_t *pgGlob ); |
106 | |
107 | /*++ |
108 | Function: |
109 | FindFirstFileA |
110 | |
111 | See MSDN doc. |
112 | --*/ |
113 | HANDLE |
114 | PALAPI |
115 | FindFirstFileA( |
116 | IN LPCSTR lpFileName, |
117 | OUT LPWIN32_FIND_DATAA lpFindFileData) |
118 | { |
119 | HANDLE hRet = INVALID_HANDLE_VALUE; |
120 | DWORD dwLastError = NO_ERROR; |
121 | find_obj *find_data = NULL; |
122 | CPalThread *pthrCurrent = InternalGetCurrentThread(); |
123 | |
124 | PERF_ENTRY(FindFirstFileA); |
125 | ENTRY("FindFirstFileA(lpFileName=%p (%s), lpFindFileData=%p)\n" , |
126 | lpFileName?lpFileName:"NULL" , |
127 | lpFileName?lpFileName:"NULL" , lpFindFileData); |
128 | |
129 | if(NULL == lpFileName) |
130 | { |
131 | ERROR("lpFileName is NULL!\n" ); |
132 | dwLastError = ERROR_PATH_NOT_FOUND; |
133 | goto done; |
134 | } |
135 | if(NULL == lpFindFileData) |
136 | { |
137 | ASSERT("lpFindFileData is NULL!\n" ); |
138 | dwLastError = ERROR_INVALID_PARAMETER; |
139 | goto done; |
140 | } |
141 | |
142 | find_data = (find_obj *)InternalMalloc(sizeof(find_obj)); |
143 | if ( find_data == NULL ) |
144 | { |
145 | ERROR("Unable to allocate memory for find_data\n" ); |
146 | dwLastError = ERROR_NOT_ENOUGH_MEMORY; |
147 | goto done; |
148 | } |
149 | |
150 | find_data->self_addr = find_data; |
151 | |
152 | // Clear the glob_t so we can safely call globfree() on it |
153 | // regardless of whether FILEDosGlobA ends up calling glob(). |
154 | memset(&(find_data->gGlob), 0, sizeof(find_data->gGlob)); |
155 | |
156 | if (!FILEDosGlobA(pthrCurrent, lpFileName, 0, &(find_data->gGlob))) |
157 | { |
158 | // FILEDosGlobA will call SetLastError() on failure. |
159 | goto done; |
160 | } |
161 | else |
162 | { |
163 | // Check if there's at least one match. |
164 | if (find_data->gGlob.gl_pathc == 0) |
165 | { |
166 | /* Testing has indicated that for this API the |
167 | * last errors are as follows |
168 | * c:\temp\foo.txt - no error |
169 | * c:\temp\foo - ERROR_FILE_NOT_FOUND |
170 | * c:\temp\foo\bar - ERROR_PATH_NOT_FOUND |
171 | * c:\temp\foo.txt\bar - ERROR_DIRECTORY |
172 | * |
173 | */ |
174 | LPSTR lpTemp = strdup((LPSTR)lpFileName); |
175 | if ( !lpTemp ) |
176 | { |
177 | ERROR( "strdup failed!\n" ); |
178 | SetLastError( ERROR_INTERNAL_ERROR ); |
179 | goto done; |
180 | } |
181 | FILEDosToUnixPathA( lpTemp ); |
182 | FILEGetProperNotFoundError( lpTemp, &dwLastError ); |
183 | |
184 | if ( ERROR_PATH_NOT_FOUND == dwLastError ) |
185 | { |
186 | /* If stripping the last segment reveals a file name then |
187 | the error is ERROR_DIRECTORY. */ |
188 | struct stat stat_data; |
189 | LPSTR lpLastPathSeparator = NULL; |
190 | |
191 | lpLastPathSeparator = strrchr( lpTemp, '/'); |
192 | |
193 | if ( lpLastPathSeparator != NULL ) |
194 | { |
195 | *lpLastPathSeparator = '\0'; |
196 | |
197 | if ( stat( lpTemp, &stat_data) == 0 && |
198 | (stat_data.st_mode & S_IFMT) == S_IFREG ) |
199 | { |
200 | dwLastError = ERROR_DIRECTORY; |
201 | } |
202 | } |
203 | } |
204 | free(lpTemp); |
205 | lpTemp = NULL; |
206 | goto done; |
207 | } |
208 | |
209 | find_data->next = find_data->gGlob.gl_pathv; |
210 | } |
211 | |
212 | if ( FindNextFileA( (HANDLE)find_data, lpFindFileData ) ) |
213 | { |
214 | hRet = (HANDLE)find_data; |
215 | } |
216 | |
217 | done: |
218 | |
219 | if ( hRet == INVALID_HANDLE_VALUE ) |
220 | { |
221 | if(NULL != find_data) |
222 | { |
223 | // Call globfree only when there is any pattern match |
224 | // otherwise, HPUX C library segfaults. |
225 | if (NULL != find_data->gGlob.gl_pathv) |
226 | { |
227 | globfree( &(find_data->gGlob) ); |
228 | } |
229 | free(find_data); |
230 | } |
231 | if (dwLastError) |
232 | { |
233 | SetLastError(dwLastError); |
234 | } |
235 | } |
236 | |
237 | LOGEXIT("FindFirstFileA returns HANDLE %p\n" , hRet ); |
238 | PERF_EXIT(FindFirstFileA); |
239 | return hRet; |
240 | } |
241 | |
242 | |
243 | /*++ |
244 | Function: |
245 | FindFirstFileW |
246 | |
247 | See MSDN doc. |
248 | --*/ |
249 | HANDLE |
250 | PALAPI |
251 | FindFirstFileW( |
252 | IN LPCWSTR lpFileName, |
253 | OUT LPWIN32_FIND_DATAW lpFindFileData) |
254 | { |
255 | // MAX_PATH_FNAME in this context is a file name, not a full path to a file. |
256 | HANDLE retval = INVALID_HANDLE_VALUE; |
257 | CHAR FileNameA[MAX_PATH_FNAME]; |
258 | WIN32_FIND_DATAA FindFileDataA; |
259 | |
260 | PERF_ENTRY(FindFirstFileW); |
261 | ENTRY("FindFirstFileW(lpFileName=%p (%S), lpFindFileData=%p)\n" , |
262 | lpFileName?lpFileName:W16_NULLSTRING, |
263 | lpFileName?lpFileName:W16_NULLSTRING, lpFindFileData); |
264 | |
265 | if(NULL == lpFileName) |
266 | { |
267 | ERROR("lpFileName is NULL!\n" ); |
268 | SetLastError(ERROR_PATH_NOT_FOUND); |
269 | goto done; |
270 | } |
271 | |
272 | if(NULL == lpFindFileData) |
273 | { |
274 | ERROR("lpFindFileData is NULL!\n" ); |
275 | SetLastError(ERROR_INVALID_PARAMETER); |
276 | goto done; |
277 | } |
278 | if( 0 == WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, lpFileName, -1, |
279 | FileNameA, MAX_PATH_FNAME, NULL, NULL)) |
280 | { |
281 | DWORD dwLastError = GetLastError(); |
282 | if (dwLastError == ERROR_INSUFFICIENT_BUFFER) |
283 | { |
284 | WARN("lpFileName is larger than MAX_PATH_FNAME (%d)!\n" , MAX_PATH_FNAME); |
285 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
286 | } |
287 | else |
288 | { |
289 | ASSERT("WideCharToMultiByte failed! error is %d\n" , dwLastError); |
290 | SetLastError(ERROR_INTERNAL_ERROR); |
291 | } |
292 | goto done; |
293 | } |
294 | |
295 | retval = FindFirstFileA(FileNameA, &FindFileDataA); |
296 | if( INVALID_HANDLE_VALUE == retval ) |
297 | { |
298 | TRACE("FindFirstFileA failed!\n" ); |
299 | goto done; |
300 | } |
301 | |
302 | lpFindFileData->dwFileAttributes = FindFileDataA.dwFileAttributes; |
303 | lpFindFileData->dwReserved0 = FindFileDataA.dwReserved0; |
304 | lpFindFileData->dwReserved1 = FindFileDataA.dwReserved1; |
305 | lpFindFileData->ftCreationTime = FindFileDataA.ftCreationTime; |
306 | lpFindFileData->ftLastAccessTime = FindFileDataA.ftLastAccessTime; |
307 | lpFindFileData->ftLastWriteTime = FindFileDataA.ftLastWriteTime; |
308 | lpFindFileData->nFileSizeHigh = FindFileDataA.nFileSizeHigh; |
309 | lpFindFileData->nFileSizeLow = FindFileDataA.nFileSizeLow; |
310 | |
311 | /* no 8.3 file names */ |
312 | lpFindFileData->cAlternateFileName[0] = 0; |
313 | |
314 | if( 0 == MultiByteToWideChar(CP_ACP, 0, FindFileDataA.cFileName, -1, |
315 | lpFindFileData->cFileName, MAX_PATH_FNAME)) |
316 | { |
317 | DWORD dwLastError = GetLastError(); |
318 | if (dwLastError == ERROR_INSUFFICIENT_BUFFER) |
319 | { |
320 | WARN("FindFileDataA.cFileName is larger than MAX_PATH_FNAME (%d)!\n" , MAX_PATH_FNAME); |
321 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
322 | } |
323 | else |
324 | { |
325 | ASSERT("MultiByteToWideChar failed! error is %d\n" , dwLastError); |
326 | SetLastError(ERROR_INTERNAL_ERROR); |
327 | } |
328 | FindClose(retval); |
329 | retval = INVALID_HANDLE_VALUE; |
330 | } |
331 | done: |
332 | LOGEXIT("FindFirstFileW returns HANDLE %p\n" , retval); |
333 | PERF_EXIT(FindFirstFileW); |
334 | return retval; |
335 | } |
336 | |
337 | |
338 | /*++ |
339 | Function: |
340 | FindNextFileA |
341 | |
342 | See MSDN doc. |
343 | --*/ |
344 | BOOL |
345 | PALAPI |
346 | FindNextFileA( |
347 | IN HANDLE hFindFile, |
348 | OUT LPWIN32_FIND_DATAA lpFindFileData) |
349 | { |
350 | find_obj *find_data; |
351 | |
352 | BOOL bRet = FALSE; |
353 | DWORD dwLastError = 0; |
354 | DWORD Attr; |
355 | |
356 | PERF_ENTRY(FindNextFileA); |
357 | ENTRY("FindNextFileA(hFindFile=%p, lpFindFileData=%p)\n" , |
358 | hFindFile, lpFindFileData); |
359 | |
360 | find_data = (find_obj*)hFindFile; |
361 | |
362 | if ( hFindFile == INVALID_HANDLE_VALUE || |
363 | find_data == NULL || |
364 | find_data->self_addr != find_data ) |
365 | { |
366 | TRACE("FindNextFileA received an invalid handle\n" ); |
367 | dwLastError = ERROR_INVALID_HANDLE; |
368 | goto done; |
369 | } |
370 | |
371 | if ( find_data->next) |
372 | { |
373 | struct stat stat_data; |
374 | char ext[_MAX_EXT]; |
375 | int stat_result; |
376 | |
377 | while (*(find_data->next)) |
378 | { |
379 | char *path = *(find_data->next); |
380 | |
381 | TRACE("Found [%s]\n" , path); |
382 | |
383 | // Split the path into a dir and filename. |
384 | if (_splitpath_s(path, NULL, 0, find_data->dir, _MAX_DIR, find_data->fname, _MAX_PATH, ext, _MAX_EXT) != 0) |
385 | { |
386 | ASSERT("_splitpath_s failed on %s\n" , path); |
387 | dwLastError = ERROR_INTERNAL_ERROR; |
388 | goto done; |
389 | } |
390 | strcat_s( find_data->fname, sizeof(find_data->fname), ext ); |
391 | |
392 | /* get the attributes, but continue if it fails */ |
393 | Attr = GetFileAttributesA(path); |
394 | if (Attr == INVALID_FILE_ATTRIBUTES) |
395 | { |
396 | WARN("GetFileAttributes returned -1 on file [%s]\n" , |
397 | *(find_data->next)); |
398 | } |
399 | lpFindFileData->dwFileAttributes = Attr; |
400 | |
401 | /* Note that cFileName is NOT the relative path */ |
402 | if (strcpy_s( lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName), find_data->fname ) != SAFECRT_SUCCESS) |
403 | { |
404 | TRACE("strcpy_s failed!\n" ); |
405 | dwLastError = ERROR_FILENAME_EXCED_RANGE; |
406 | goto done; |
407 | } |
408 | |
409 | /* we don't support 8.3 filenames, so just leave it empty */ |
410 | lpFindFileData->cAlternateFileName[0] = 0; |
411 | |
412 | /* get the filetimes */ |
413 | stat_result = stat(path, &stat_data) == 0 || |
414 | lstat(path, &stat_data) == 0; |
415 | |
416 | find_data->next++; |
417 | |
418 | if ( stat_result ) |
419 | { |
420 | lpFindFileData->ftCreationTime = |
421 | FILEUnixTimeToFileTime( stat_data.st_ctime, |
422 | ST_CTIME_NSEC(&stat_data) ); |
423 | lpFindFileData->ftLastAccessTime = |
424 | FILEUnixTimeToFileTime( stat_data.st_atime, |
425 | ST_ATIME_NSEC(&stat_data) ); |
426 | lpFindFileData->ftLastWriteTime = |
427 | FILEUnixTimeToFileTime( stat_data.st_mtime, |
428 | ST_MTIME_NSEC(&stat_data) ); |
429 | |
430 | /* if Unix mtime is greater than atime, return mtime |
431 | as the last access time */ |
432 | if (CompareFileTime(&lpFindFileData->ftLastAccessTime, |
433 | &lpFindFileData->ftLastWriteTime) < 0) |
434 | { |
435 | lpFindFileData->ftLastAccessTime = lpFindFileData->ftLastWriteTime; |
436 | } |
437 | |
438 | /* if Unix ctime is greater than mtime, return mtime |
439 | as the create time */ |
440 | if (CompareFileTime(&lpFindFileData->ftLastWriteTime, |
441 | &lpFindFileData->ftCreationTime) < 0) |
442 | { |
443 | lpFindFileData->ftCreationTime = lpFindFileData->ftLastWriteTime; |
444 | } |
445 | |
446 | /* get file size */ |
447 | lpFindFileData->nFileSizeLow = (DWORD)stat_data.st_size; |
448 | #if SIZEOF_OFF_T > 4 |
449 | lpFindFileData->nFileSizeHigh = |
450 | (DWORD)(stat_data.st_size >> 32); |
451 | #else |
452 | lpFindFileData->nFileSizeHigh = 0; |
453 | #endif |
454 | |
455 | bRet = TRUE; |
456 | break; |
457 | } |
458 | } |
459 | if(!bRet) |
460 | { |
461 | dwLastError = ERROR_NO_MORE_FILES; |
462 | } |
463 | } |
464 | else |
465 | { |
466 | |
467 | ASSERT("find_data->next is (mysteriously) NULL\n" ); |
468 | } |
469 | |
470 | done: |
471 | if (dwLastError) |
472 | { |
473 | SetLastError(dwLastError); |
474 | } |
475 | |
476 | LOGEXIT("FindNextFileA returns BOOL %d\n" , bRet); |
477 | PERF_EXIT(FindNextFileA); |
478 | return bRet; |
479 | } |
480 | |
481 | |
482 | /*++ |
483 | Function: |
484 | FindNextFileW |
485 | |
486 | See MSDN doc. |
487 | --*/ |
488 | BOOL |
489 | PALAPI |
490 | FindNextFileW( |
491 | IN HANDLE hFindFile, |
492 | OUT LPWIN32_FIND_DATAW lpFindFileData) |
493 | { |
494 | BOOL retval = FALSE; |
495 | WIN32_FIND_DATAA FindFileDataA; |
496 | |
497 | PERF_ENTRY(FindNextFileW); |
498 | ENTRY("FindNextFileW(hFindFile=%p, lpFindFileData=%p)\n" , |
499 | hFindFile, lpFindFileData); |
500 | |
501 | retval = FindNextFileA(hFindFile, &FindFileDataA); |
502 | if(!retval) |
503 | { |
504 | WARN("FindNextFileA failed!\n" ); |
505 | goto done; |
506 | } |
507 | |
508 | lpFindFileData->dwFileAttributes = FindFileDataA.dwFileAttributes; |
509 | lpFindFileData->dwReserved0 = FindFileDataA.dwReserved0; |
510 | lpFindFileData->dwReserved1 = FindFileDataA.dwReserved1; |
511 | lpFindFileData->ftCreationTime = FindFileDataA.ftCreationTime; |
512 | lpFindFileData->ftLastAccessTime = FindFileDataA.ftLastAccessTime; |
513 | lpFindFileData->ftLastWriteTime = FindFileDataA.ftLastWriteTime; |
514 | lpFindFileData->nFileSizeHigh = FindFileDataA.nFileSizeHigh; |
515 | lpFindFileData->nFileSizeLow = FindFileDataA.nFileSizeLow; |
516 | |
517 | /* no 8.3 file names */ |
518 | lpFindFileData->cAlternateFileName[0] = 0; |
519 | |
520 | if( 0 == MultiByteToWideChar(CP_ACP, 0, FindFileDataA.cFileName, -1, |
521 | lpFindFileData->cFileName, MAX_PATH_FNAME)) |
522 | { |
523 | DWORD dwLastError = GetLastError(); |
524 | if (dwLastError == ERROR_INSUFFICIENT_BUFFER) |
525 | { |
526 | WARN("FindFileDataA.cFileName is larger than MAX_PATH_FNAME (%d)!\n" , MAX_PATH_FNAME); |
527 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
528 | } |
529 | else |
530 | { |
531 | ASSERT("MultiByteToWideChar failed! error is %d\n" , dwLastError); |
532 | SetLastError(ERROR_INTERNAL_ERROR); |
533 | } |
534 | retval = FALSE; |
535 | } |
536 | |
537 | done: |
538 | LOGEXIT("FindNextFileW returns BOOL %d\n" , retval); |
539 | PERF_EXIT(FindNextFileW); |
540 | return retval; |
541 | } |
542 | |
543 | |
544 | /*++ |
545 | Function: |
546 | FindClose |
547 | |
548 | See MSDN doc. |
549 | --*/ |
550 | BOOL |
551 | PALAPI |
552 | FindClose( |
553 | IN OUT HANDLE hFindFile) |
554 | { |
555 | find_obj *find_data; |
556 | BOOL hRet = TRUE; |
557 | DWORD dwLastError = 0; |
558 | |
559 | PERF_ENTRY(FindClose); |
560 | ENTRY("FindClose(hFindFile=%p)\n" , hFindFile); |
561 | |
562 | find_data = (find_obj*)hFindFile; |
563 | |
564 | if ( hFindFile == INVALID_HANDLE_VALUE || |
565 | find_data == NULL || |
566 | find_data->self_addr != find_data ) |
567 | { |
568 | ERROR("Invalid find handle\n" ); |
569 | hRet = FALSE; |
570 | dwLastError = ERROR_INVALID_PARAMETER; |
571 | goto done; |
572 | } |
573 | |
574 | find_data->self_addr = NULL; |
575 | |
576 | // Call globfree only when there is any pattern match |
577 | // otherwise, HPUX C library segfaults. |
578 | if (NULL != find_data->gGlob.gl_pathv) |
579 | { |
580 | globfree( &(find_data->gGlob) ); |
581 | } |
582 | free(find_data); |
583 | |
584 | done: |
585 | if (dwLastError) |
586 | { |
587 | SetLastError(dwLastError); |
588 | } |
589 | |
590 | LOGEXIT("FindClose returns BOOL %d\n" , hRet); |
591 | PERF_EXIT(FindClose); |
592 | return hRet; |
593 | } |
594 | |
595 | |
596 | /*++ |
597 | Function: |
598 | FILEMakePathA |
599 | |
600 | Mimics _makepath from windows, except it's a bit safer. |
601 | Any or all of dir, fname, and ext can be NULL. |
602 | --*/ |
603 | static int FILEMakePathA( char *buff, |
604 | int buff_size, |
605 | const char *dir, |
606 | const char *fname, |
607 | const char *ext ) |
608 | { |
609 | int dir_len = 0; |
610 | int fname_len = 0; |
611 | int ext_len = 0; |
612 | int len; |
613 | char *p; |
614 | |
615 | TRACE("Attempting to assemble path from [%s][%s][%s], buff_size = %d\n" , |
616 | dir?dir:"NULL" , fname?fname:"NULL" , ext?ext:"NULL" , buff_size); |
617 | |
618 | if (dir) dir_len = strlen(dir); |
619 | if (fname) fname_len = strlen(fname); |
620 | if (ext) ext_len = strlen(ext); |
621 | |
622 | len = dir_len + fname_len + ext_len + 1; |
623 | |
624 | TRACE("Required buffer size is %d bytes\n" , len); |
625 | |
626 | if ( len > buff_size ) |
627 | { |
628 | ERROR("Buffer is too small (%d bytes), needs %d bytes\n" , |
629 | buff_size, len); |
630 | return -1; |
631 | } |
632 | else |
633 | { |
634 | buff[0] = 0; |
635 | |
636 | p = buff; |
637 | if (dir_len > 0) |
638 | { |
639 | if (strncpy_s( buff, buff_size, dir, dir_len + 1 ) != SAFECRT_SUCCESS) |
640 | { |
641 | ERROR("FILEMakePathA: strncpy_s failed\n" ); |
642 | return -1; |
643 | } |
644 | |
645 | p += dir_len; |
646 | buff_size-= dir_len; |
647 | } |
648 | if (fname_len > 0) |
649 | { |
650 | if (strncpy_s( p, buff_size, fname, fname_len + 1 ) != SAFECRT_SUCCESS) |
651 | { |
652 | ERROR("FILEMakePathA: strncpy_s failed\n" ); |
653 | return -1; |
654 | } |
655 | |
656 | p += fname_len; |
657 | buff_size-=fname_len; |
658 | } |
659 | if (ext_len > 0) |
660 | { |
661 | if (strncpy_s( p, buff_size, ext, ext_len + 1) != SAFECRT_SUCCESS) |
662 | { |
663 | ERROR("FILEMakePathA: strncpy_s failed\n" ); |
664 | return -1; |
665 | } |
666 | } |
667 | |
668 | TRACE("FILEMakePathA assembled [%s]\n" , buff); |
669 | return len - 1; |
670 | } |
671 | } |
672 | |
673 | |
674 | /*++ |
675 | FILEGlobQsortCompare |
676 | |
677 | Comparison function required by qsort, so that the |
678 | . and .. directories end up on top of the sorted list |
679 | of directories. |
680 | --*/ |
681 | static int FILEGlobQsortCompare(const void *in_str1, const void *in_str2) |
682 | { |
683 | char **str1 = (char**)in_str1; |
684 | char **str2 = (char**)in_str2; |
685 | const int FIRST_ARG_LESS = -1; |
686 | const int FIRST_ARG_EQUAL = 0; |
687 | const int FIRST_ARG_GREATER = 1; |
688 | |
689 | /* If both strings are equal, return immediately */ |
690 | if (strcmp(*(str1), *(str2)) == 0) |
691 | { |
692 | return(FIRST_ARG_EQUAL); |
693 | } |
694 | |
695 | /* Have '.' always on top than any other search result */ |
696 | if (strcmp(*(str1), "." ) == 0) |
697 | { |
698 | return (FIRST_ARG_LESS); |
699 | } |
700 | if (strcmp(*(str2), "." ) == 0) |
701 | { |
702 | return (FIRST_ARG_GREATER); |
703 | } |
704 | |
705 | /* Have '..' next on top, over any other search result */ |
706 | if (strcmp(*(str1), ".." ) == 0) |
707 | { |
708 | return (FIRST_ARG_LESS); |
709 | } |
710 | if (strcmp(*(str2), ".." ) == 0) |
711 | { |
712 | return (FIRST_ARG_GREATER); |
713 | } |
714 | |
715 | /* Finally, let strcmp do the rest for us */ |
716 | return (strcmp(*(str1),*(str2))); |
717 | } |
718 | |
719 | /*++ |
720 | Function: |
721 | FILEEscapeSquareBrackets |
722 | |
723 | Simple helper function to insert backslashes before square brackets |
724 | to prevent glob from using them as wildcards. |
725 | |
726 | note: this functions assumes all backslashes have previously been |
727 | converted into forwardslashes by _splitpath_s. |
728 | --*/ |
729 | static void FILEEscapeSquareBrackets(char *pattern, char *escaped_pattern) |
730 | { |
731 | TRACE("Entering FILEEscapeSquareBrackets: [%p (%s)][%p]\n" , |
732 | pattern,pattern,escaped_pattern); |
733 | |
734 | #if _ENABLE_DEBUG_MESSAGES_ |
735 | char *escaped_pattern_base = escaped_pattern; |
736 | #endif // _ENABLE_DEBUG_MESSAGES_ |
737 | |
738 | while(*pattern) |
739 | { |
740 | if('[' == *pattern || ']' == *pattern) |
741 | { |
742 | *escaped_pattern = '\\'; |
743 | escaped_pattern++; |
744 | } |
745 | *escaped_pattern = *pattern; |
746 | pattern++; |
747 | escaped_pattern++; |
748 | } |
749 | *escaped_pattern='\0'; |
750 | |
751 | TRACE("FILEEscapeSquareBrackets done. escaped_pattern=%s\n" , |
752 | escaped_pattern_base); |
753 | } |
754 | |
755 | |
756 | /*++ |
757 | Function: |
758 | FILEGlobFromSplitPath |
759 | |
760 | Simple wrapper function around glob(3), except that the pattern is accepted |
761 | in broken-down form like _splitpath_s produces. |
762 | |
763 | ie. calling splitpath on a pattern then calling this function should |
764 | produce the same result as just calling glob() on the pattern. |
765 | --*/ |
766 | static int FILEGlobFromSplitPath( const char *dir, |
767 | const char *fname, |
768 | const char *ext, |
769 | int flags, |
770 | glob_t *pgGlob ) |
771 | { |
772 | int Ret; |
773 | PathCharString PatternPS; |
774 | PathCharString EscapedPatternPS; |
775 | char * Pattern; |
776 | int length = 0; |
777 | char * EscapedPattern; |
778 | |
779 | TRACE("We shall attempt to glob from components [%s][%s][%s]\n" , |
780 | dir?dir:"NULL" , fname?fname:"NULL" , ext?ext:"NULL" ); |
781 | |
782 | if (dir) length = strlen(dir); |
783 | if (fname) length += strlen(fname); |
784 | if (ext) length += strlen(ext); |
785 | |
786 | Pattern = PatternPS.OpenStringBuffer(length); |
787 | if (NULL == Pattern) |
788 | { |
789 | ERROR("Not Enough memory." ); |
790 | return -1; |
791 | } |
792 | FILEMakePathA( Pattern, length+1, dir, fname, ext ); |
793 | PatternPS.CloseBuffer(length); |
794 | TRACE("Assembled Pattern = [%s]\n" , Pattern); |
795 | |
796 | /* special handling is needed to handle the case where |
797 | filename contains '[' and ']' */ |
798 | EscapedPattern = EscapedPatternPS.OpenStringBuffer(length*2); |
799 | if (NULL == EscapedPattern) |
800 | { |
801 | ERROR("Not Enough memory." ); |
802 | return -1; |
803 | } |
804 | FILEEscapeSquareBrackets( Pattern, EscapedPattern); |
805 | EscapedPatternPS.CloseBuffer(strlen(EscapedPattern)); |
806 | #ifdef GLOB_QUOTE |
807 | flags |= GLOB_QUOTE; |
808 | #endif // GLOB_QUOTE |
809 | Ret = InternalGlob(EscapedPattern, flags, NULL, pgGlob); |
810 | |
811 | #ifdef GLOB_NOMATCH |
812 | if (Ret == GLOB_NOMATCH) |
813 | { |
814 | // pgGlob->gl_pathc will be 0 in this case. We'll check |
815 | // the return value to see if an error occurred, so we |
816 | // don't want to return an error if we simply didn't match |
817 | // anything. |
818 | Ret = 0; |
819 | } |
820 | #endif // GLOB_NOMATCH |
821 | |
822 | /* Ensure that . and .. are placed in front, and sort the rest */ |
823 | qsort(pgGlob->gl_pathv, pgGlob->gl_pathc, sizeof(char*), |
824 | FILEGlobQsortCompare); |
825 | TRACE("Result of glob() is %d\n" , Ret); |
826 | |
827 | return Ret; |
828 | } |
829 | |
830 | |
831 | /*++ |
832 | Function: |
833 | FILEDosGlobA |
834 | |
835 | Generate pathnames matching a DOS globbing pattern. This function has a similar |
836 | prototype to glob(3), and fulfils the same purpose. However, DOS globbing |
837 | is slightly different than Unix in the following ways: |
838 | |
839 | - '.*' at the end of a pattern means "any file extension, or none at all", |
840 | whereas Unix has no concept of file extensions, and will match the '.' like |
841 | any other character |
842 | |
843 | - on Unix, filenames beginning with '.' must be explicitly matched. This is |
844 | not true in DOS |
845 | |
846 | - in DOS, the first two entries (if they match) will be '.' and '..', followed |
847 | by all other matching entries sorted in ASCII order. In Unix, all entries are |
848 | treated equally, so '+file' would appear before '.' and '..' |
849 | |
850 | - DOS globbing will fail if any wildcard characters occur before the last path |
851 | separator |
852 | |
853 | This implementation of glob implements the DOS behavior in all these cases, |
854 | but otherwise attempts to behave exactly like POSIX glob. The only exception |
855 | is its return value -- it returns TRUE if it succeeded (finding matches or |
856 | finding no matches but without any error occurring) or FALSE if any error |
857 | occurs. It calls SetLastError() if it returns FALSE. |
858 | |
859 | Sorting doesn't seem to be consistent on all Windows platform, and it's |
860 | not required for CoreCLR to have the same sorting algorithm as Windows 2000. |
861 | This implementation will give slightly different result for the sort list |
862 | than Windows 2000. |
863 | |
864 | --*/ |
865 | static BOOL FILEDosGlobA( CPalThread *pthrCurrent, |
866 | const char *pattern, |
867 | int flags, |
868 | glob_t *pgGlob ) |
869 | { |
870 | char Dir[_MAX_DIR]; |
871 | char FilenameBuff[_MAX_FNAME + 1]; |
872 | char *Filename = FilenameBuff + 1; |
873 | char Ext[_MAX_EXT]; |
874 | int A, B, C; |
875 | BOOL result = TRUE; |
876 | int globResult = 0; |
877 | |
878 | Dir[0] = 0; |
879 | FilenameBuff[0] = '.'; |
880 | FilenameBuff[1] = 0; |
881 | Ext[0] = 0; |
882 | |
883 | _splitpath_s( pattern, NULL, 0, Dir, _MAX_DIR, Filename, _MAX_FNAME+1, Ext, _MAX_EXT); |
884 | |
885 | /* check to see if _splitpath_s failed */ |
886 | if ( Filename[0] == 0 ) |
887 | { |
888 | if ( Dir[0] == 0 ) |
889 | { |
890 | ERROR("_splitpath_s failed on path [%s]\n" , pattern); |
891 | } |
892 | else |
893 | { |
894 | ERROR("Pattern contains a trailing backslash\n" ); |
895 | } |
896 | SetLastError(ERROR_PATH_NOT_FOUND); |
897 | result = FALSE; |
898 | goto done; |
899 | } |
900 | |
901 | TRACE("glob pattern [%s] split into [%s][%s][%s]\n" , |
902 | pattern, Dir, Filename, Ext); |
903 | |
904 | if ( strchr(Dir, '*') != NULL || strchr(Dir, '?') != NULL ) |
905 | { |
906 | ERROR("Found wildcard character(s) ('*' and/or '?') before " |
907 | "last path separator\n" ); |
908 | SetLastError(ERROR_PATH_NOT_FOUND); |
909 | result = FALSE; |
910 | goto done; |
911 | } |
912 | |
913 | if (Dir[0] != 0) |
914 | { |
915 | FILEDosToUnixPathA( Dir ); |
916 | } |
917 | |
918 | /* The meat of the routine happens below. Basically, there are three |
919 | special things to check for: |
920 | |
921 | (A) If the extension is _exactly_ '.*', we will need to do two globs, |
922 | one for 'filename.*' and one for 'filename', EXCEPT if (B) the last |
923 | character of filename is '*', in which case we can eliminate the |
924 | extension altogether, since '*.*' and '*' are the same in DOS. |
925 | (C) If the first character of the filename is '*', we need to do |
926 | an additional glob for each one we have already done, except with |
927 | '.' prepended to the filename of the patterns, because in Unix, |
928 | hidden files need to be matched explicitly. |
929 | |
930 | We can ignore the extension by calling FILEGlobFromSplitPath with |
931 | the extension parameter as "", and we can prepend '.' to the |
932 | filename by using (Filename - 1), since Filename conveniently points |
933 | to the second character of a buffer which happens to have '.' as |
934 | its first character. |
935 | */ |
936 | |
937 | A = strncmp(Ext, ".*" , 3) == 0; |
938 | B = (Filename[strlen(Filename) - 1] == '*'); |
939 | C = (*Filename == '*'); |
940 | |
941 | TRACE("Extension IS%s '.*', filename DOES%s end with '*', " |
942 | "and filename DOES%s begin with '*'\n" , |
943 | A?"" :" NOT" , B?"" :" NOT" , C?"" :" NOT" ); |
944 | |
945 | if ( !(A && B) ) |
946 | { |
947 | /* the original pattern */ |
948 | globResult = FILEGlobFromSplitPath(Dir, Filename, Ext, 0, pgGlob); |
949 | if ( globResult != 0 ) |
950 | { |
951 | goto done; |
952 | } |
953 | |
954 | if (C) |
955 | { |
956 | /* the original pattern but '.' prepended to filename */ |
957 | globResult = FILEGlobFromSplitPath(Dir, Filename - 1, Ext, |
958 | GLOB_APPEND, pgGlob); |
959 | if ( globResult != 0 ) |
960 | { |
961 | goto done; |
962 | } |
963 | } |
964 | } |
965 | |
966 | if (A) |
967 | { |
968 | /* if (A && B), this is the first glob() call. The first call |
969 | to glob must use flags = 0, while proceeding calls should |
970 | set the GLOB_APPEND flag. */ |
971 | globResult = FILEGlobFromSplitPath(Dir, Filename, "" , |
972 | (A && B)?0:GLOB_APPEND, pgGlob); |
973 | if ( globResult != 0 ) |
974 | { |
975 | goto done; |
976 | } |
977 | |
978 | if (C) |
979 | { |
980 | /* omit the extension and prepend '.' to filename */ |
981 | globResult = FILEGlobFromSplitPath(Dir, Filename - 1, "" , |
982 | GLOB_APPEND, pgGlob); |
983 | if ( globResult != 0 ) |
984 | { |
985 | goto done; |
986 | } |
987 | } |
988 | } |
989 | |
990 | done: |
991 | if (globResult != 0) |
992 | { |
993 | if (globResult == GLOB_NOSPACE) |
994 | { |
995 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
996 | } |
997 | else |
998 | { |
999 | SetLastError(ERROR_INTERNAL_ERROR); |
1000 | } |
1001 | result = FALSE; |
1002 | } |
1003 | TRACE("Returning %d\n" , result); |
1004 | return result; |
1005 | } |
1006 | |
1007 | |
1008 | |