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
9Module Name:
10
11 find.c
12
13Abstract:
14
15 Implementation of the FindFile function family
16
17Revision 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
38using namespace CorUnix;
39
40SET_DEFAULT_DEBUG_CHANNEL(FILE);
41
42namespace 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
92static BOOL FILEDosGlobA(
93 CPalThread *pthrCurrent,
94 const char *pattern,
95 int flags,
96 glob_t *pgGlob );
97
98static int FILEGlobQsortCompare(const void *in_str1, const void *in_str2);
99
100static int FILEGlobFromSplitPath(
101 const char *dir,
102 const char *fname,
103 const char *ext,
104 int flags,
105 glob_t *pgGlob );
106
107/*++
108Function:
109 FindFirstFileA
110
111See MSDN doc.
112--*/
113HANDLE
114PALAPI
115FindFirstFileA(
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
217done:
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/*++
244Function:
245 FindFirstFileW
246
247See MSDN doc.
248--*/
249HANDLE
250PALAPI
251FindFirstFileW(
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 }
331done:
332 LOGEXIT("FindFirstFileW returns HANDLE %p\n", retval);
333 PERF_EXIT(FindFirstFileW);
334 return retval;
335}
336
337
338/*++
339Function:
340 FindNextFileA
341
342See MSDN doc.
343--*/
344BOOL
345PALAPI
346FindNextFileA(
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
470done:
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/*++
483Function:
484 FindNextFileW
485
486See MSDN doc.
487--*/
488BOOL
489PALAPI
490FindNextFileW(
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
537done:
538 LOGEXIT("FindNextFileW returns BOOL %d\n", retval);
539 PERF_EXIT(FindNextFileW);
540 return retval;
541}
542
543
544/*++
545Function:
546 FindClose
547
548See MSDN doc.
549--*/
550BOOL
551PALAPI
552FindClose(
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
584done:
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/*++
597Function:
598 FILEMakePathA
599
600Mimics _makepath from windows, except it's a bit safer.
601Any or all of dir, fname, and ext can be NULL.
602--*/
603static 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--*/
681static 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/*++
720Function:
721 FILEEscapeSquareBrackets
722
723Simple helper function to insert backslashes before square brackets
724to prevent glob from using them as wildcards.
725
726note: this functions assumes all backslashes have previously been
727 converted into forwardslashes by _splitpath_s.
728--*/
729static 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/*++
757Function:
758 FILEGlobFromSplitPath
759
760Simple wrapper function around glob(3), except that the pattern is accepted
761in broken-down form like _splitpath_s produces.
762
763ie. calling splitpath on a pattern then calling this function should
764produce the same result as just calling glob() on the pattern.
765--*/
766static 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/*++
832Function:
833 FILEDosGlobA
834
835Generate pathnames matching a DOS globbing pattern. This function has a similar
836prototype to glob(3), and fulfils the same purpose. However, DOS globbing
837is slightly different than Unix in the following ways:
838
839- '.*' at the end of a pattern means "any file extension, or none at all",
840whereas Unix has no concept of file extensions, and will match the '.' like
841any other character
842
843- on Unix, filenames beginning with '.' must be explicitly matched. This is
844not true in DOS
845
846- in DOS, the first two entries (if they match) will be '.' and '..', followed
847by all other matching entries sorted in ASCII order. In Unix, all entries are
848treated equally, so '+file' would appear before '.' and '..'
849
850- DOS globbing will fail if any wildcard characters occur before the last path
851separator
852
853This implementation of glob implements the DOS behavior in all these cases,
854but otherwise attempts to behave exactly like POSIX glob. The only exception
855is its return value -- it returns TRUE if it succeeded (finding matches or
856finding no matches but without any error occurring) or FALSE if any error
857occurs. It calls SetLastError() if it returns FALSE.
858
859Sorting doesn't seem to be consistent on all Windows platform, and it's
860not required for CoreCLR to have the same sorting algorithm as Windows 2000.
861This implementation will give slightly different result for the sort list
862than Windows 2000.
863
864--*/
865static 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
990done:
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