summaryrefslogtreecommitdiff
path: root/src/pal/src/file/find.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pal/src/file/find.cpp')
-rw-r--r--src/pal/src/file/find.cpp999
1 files changed, 999 insertions, 0 deletions
diff --git a/src/pal/src/file/find.cpp b/src/pal/src/file/find.cpp
new file mode 100644
index 0000000000..18639d3d14
--- /dev/null
+++ b/src/pal/src/file/find.cpp
@@ -0,0 +1,999 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*++
+
+
+
+Module Name:
+
+ find.c
+
+Abstract:
+
+ Implementation of the FindFile function family
+
+Revision History:
+
+
+
+--*/
+
+#include "pal/thread.hpp"
+#include "pal/malloc.hpp"
+#include "pal/file.hpp"
+#include "pal/stackstring.hpp"
+
+#include "pal/palinternal.h"
+#include "pal/dbgmsg.h"
+#include "pal/file.h"
+#include "pal/filetime.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <limits.h>
+
+using namespace CorUnix;
+
+SET_DEFAULT_DEBUG_CHANNEL(FILE);
+
+namespace CorUnix
+{
+ int InternalGlob(
+ const char *szPattern,
+ int nFlags,
+#if ERROR_FUNC_FOR_GLOB_HAS_FIXED_PARAMS
+ int (*pnErrFunc)(const char *, int),
+#else
+ int (*pnErrFunc)(...),
+#endif
+ glob_t *pgGlob
+ );
+
+ /*++
+ InternalGlob
+
+ Input parameters:
+
+ szPattern = pointer to a pathname pattern to be expanded
+ nFlags = arguments to modify the behavior of glob
+ pnErrFunc = pointer to a routine that handles errors during the glob call
+ pgGlob = pointer to a glob structure
+
+ Return value:
+ 0 on success, -1 on failure.
+
+ Some platforms expect the error function for glob to take a variable number
+ of parameters, whereas other platforms insist that the error function take
+ a const char * and an int. A test in configure determines which is the case
+ for each platform and sets ERROR_FUNC_FOR_GLOB_HAS_FIXED_PARAMS
+ to 1 if the error func must have the char * and int parameters.
+ --*/
+ int
+ InternalGlob(
+ const char *szPattern,
+ int nFlags,
+#if ERROR_FUNC_FOR_GLOB_HAS_FIXED_PARAMS
+ int (*pnErrFunc)(const char *, int),
+#else
+ int (*pnErrFunc)(...),
+#endif
+ glob_t *pgGlob
+ )
+ {
+ int nRet = -1;
+ nRet = glob(szPattern, nFlags, pnErrFunc, pgGlob);
+ return nRet;
+ }
+}
+
+static BOOL FILEDosGlobA(
+ CPalThread *pthrCurrent,
+ const char *pattern,
+ int flags,
+ glob_t *pgGlob );
+
+static int FILEGlobQsortCompare(const void *in_str1, const void *in_str2);
+
+static int FILEGlobFromSplitPath(
+ const char *dir,
+ const char *fname,
+ const char *ext,
+ int flags,
+ glob_t *pgGlob );
+
+/*++
+Function:
+ FindFirstFileA
+
+See MSDN doc.
+--*/
+HANDLE
+PALAPI
+FindFirstFileA(
+ IN LPCSTR lpFileName,
+ OUT LPWIN32_FIND_DATAA lpFindFileData)
+{
+ HANDLE hRet = INVALID_HANDLE_VALUE;
+ DWORD dwLastError = NO_ERROR;
+ find_obj *find_data = NULL;
+ CPalThread *pthrCurrent = InternalGetCurrentThread();
+
+ PERF_ENTRY(FindFirstFileA);
+ ENTRY("FindFirstFileA(lpFileName=%p (%s), lpFindFileData=%p)\n",
+ lpFileName?lpFileName:"NULL",
+ lpFileName?lpFileName:"NULL", lpFindFileData);
+
+ if(NULL == lpFileName)
+ {
+ ERROR("lpFileName is NULL!\n");
+ dwLastError = ERROR_PATH_NOT_FOUND;
+ goto done;
+ }
+ if(NULL == lpFindFileData)
+ {
+ ASSERT("lpFindFileData is NULL!\n");
+ dwLastError = ERROR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ find_data = (find_obj *)InternalMalloc(sizeof(find_obj));
+ if ( find_data == NULL )
+ {
+ ERROR("Unable to allocate memory for find_data\n");
+ dwLastError = ERROR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ find_data->self_addr = find_data;
+
+ // Clear the glob_t so we can safely call globfree() on it
+ // regardless of whether FILEDosGlobA ends up calling glob().
+ memset(&(find_data->gGlob), 0, sizeof(find_data->gGlob));
+
+ if (!FILEDosGlobA(pthrCurrent, lpFileName, 0, &(find_data->gGlob)))
+ {
+ // FILEDosGlobA will call SetLastError() on failure.
+ goto done;
+ }
+ else
+ {
+ // Check if there's at least one match.
+ if (find_data->gGlob.gl_pathc == 0)
+ {
+ /* Testing has indicated that for this API the
+ * last errors are as follows
+ * c:\temp\foo.txt - no error
+ * c:\temp\foo - ERROR_FILE_NOT_FOUND
+ * c:\temp\foo\bar - ERROR_PATH_NOT_FOUND
+ * c:\temp\foo.txt\bar - ERROR_DIRECTORY
+ *
+ */
+ LPSTR lpTemp = strdup((LPSTR)lpFileName);
+ if ( !lpTemp )
+ {
+ ERROR( "strdup failed!\n" );
+ SetLastError( ERROR_INTERNAL_ERROR );
+ goto done;
+ }
+ FILEDosToUnixPathA( lpTemp );
+ FILEGetProperNotFoundError( lpTemp, &dwLastError );
+
+ if ( ERROR_PATH_NOT_FOUND == dwLastError )
+ {
+ /* If stripping the last segment reveals a file name then
+ the error is ERROR_DIRECTORY. */
+ struct stat stat_data;
+ LPSTR lpLastPathSeparator = NULL;
+
+ lpLastPathSeparator = strrchr( lpTemp, '/');
+
+ if ( lpLastPathSeparator != NULL )
+ {
+ *lpLastPathSeparator = '\0';
+
+ if ( stat( lpTemp, &stat_data) == 0 &&
+ (stat_data.st_mode & S_IFMT) == S_IFREG )
+ {
+ dwLastError = ERROR_DIRECTORY;
+ }
+ }
+ }
+ free(lpTemp);
+ lpTemp = NULL;
+ goto done;
+ }
+
+ find_data->next = find_data->gGlob.gl_pathv;
+ }
+
+ if ( FindNextFileA( (HANDLE)find_data, lpFindFileData ) )
+ {
+ hRet = (HANDLE)find_data;
+ }
+
+done:
+
+ if ( hRet == INVALID_HANDLE_VALUE )
+ {
+ if(NULL != find_data)
+ {
+ // Call globfree only when there is any pattern match
+ // otherwise, HPUX C library segfaults.
+ if (NULL != find_data->gGlob.gl_pathv)
+ {
+ globfree( &(find_data->gGlob) );
+ }
+ free(find_data);
+ }
+ if (dwLastError)
+ {
+ SetLastError(dwLastError);
+ }
+ }
+
+ LOGEXIT("FindFirstFileA returns HANDLE %p\n", hRet );
+ PERF_EXIT(FindFirstFileA);
+ return hRet;
+}
+
+
+/*++
+Function:
+ FindFirstFileW
+
+See MSDN doc.
+--*/
+HANDLE
+PALAPI
+FindFirstFileW(
+ IN LPCWSTR lpFileName,
+ OUT LPWIN32_FIND_DATAW lpFindFileData)
+{
+ // MAX_PATH_FNAME in this context is a file name, not a full path to a file.
+ HANDLE retval = INVALID_HANDLE_VALUE;
+ CHAR FileNameA[MAX_PATH_FNAME];
+ WIN32_FIND_DATAA FindFileDataA;
+
+ PERF_ENTRY(FindFirstFileW);
+ ENTRY("FindFirstFileW(lpFileName=%p (%S), lpFindFileData=%p)\n",
+ lpFileName?lpFileName:W16_NULLSTRING,
+ lpFileName?lpFileName:W16_NULLSTRING, lpFindFileData);
+
+ if(NULL == lpFileName)
+ {
+ ERROR("lpFileName is NULL!\n");
+ SetLastError(ERROR_PATH_NOT_FOUND);
+ goto done;
+ }
+
+ if(NULL == lpFindFileData)
+ {
+ ERROR("lpFindFileData is NULL!\n");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ goto done;
+ }
+ if( 0 == WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, lpFileName, -1,
+ FileNameA, MAX_PATH_FNAME, NULL, NULL))
+ {
+ DWORD dwLastError = GetLastError();
+ if (dwLastError == ERROR_INSUFFICIENT_BUFFER)
+ {
+ WARN("lpFileName is larger than MAX_PATH_FNAME (%d)!\n", MAX_PATH_FNAME);
+ SetLastError(ERROR_FILENAME_EXCED_RANGE);
+ }
+ else
+ {
+ ASSERT("WideCharToMultiByte failed! error is %d\n", dwLastError);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ }
+ goto done;
+ }
+
+ retval = FindFirstFileA(FileNameA, &FindFileDataA);
+ if( INVALID_HANDLE_VALUE == retval )
+ {
+ TRACE("FindFirstFileA failed!\n");
+ goto done;
+ }
+
+ lpFindFileData->dwFileAttributes = FindFileDataA.dwFileAttributes;
+ lpFindFileData->dwReserved0 = FindFileDataA.dwReserved0;
+ lpFindFileData->dwReserved1 = FindFileDataA.dwReserved1;
+ lpFindFileData->ftCreationTime = FindFileDataA.ftCreationTime;
+ lpFindFileData->ftLastAccessTime = FindFileDataA.ftLastAccessTime;
+ lpFindFileData->ftLastWriteTime = FindFileDataA.ftLastWriteTime;
+ lpFindFileData->nFileSizeHigh = FindFileDataA.nFileSizeHigh;
+ lpFindFileData->nFileSizeLow = FindFileDataA.nFileSizeLow;
+
+ /* no 8.3 file names */
+ lpFindFileData->cAlternateFileName[0] = 0;
+
+ if( 0 == MultiByteToWideChar(CP_ACP, 0, FindFileDataA.cFileName, -1,
+ lpFindFileData->cFileName, MAX_PATH_FNAME))
+ {
+ DWORD dwLastError = GetLastError();
+ if (dwLastError == ERROR_INSUFFICIENT_BUFFER)
+ {
+ WARN("FindFileDataA.cFileName is larger than MAX_PATH_FNAME (%d)!\n", MAX_PATH_FNAME);
+ SetLastError(ERROR_FILENAME_EXCED_RANGE);
+ }
+ else
+ {
+ ASSERT("MultiByteToWideChar failed! error is %d\n", dwLastError);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ }
+ FindClose(retval);
+ retval = INVALID_HANDLE_VALUE;
+ }
+done:
+ LOGEXIT("FindFirstFileW returns HANDLE %p\n", retval);
+ PERF_EXIT(FindFirstFileW);
+ return retval;
+}
+
+
+/*++
+Function:
+ FindNextFileA
+
+See MSDN doc.
+--*/
+BOOL
+PALAPI
+FindNextFileA(
+ IN HANDLE hFindFile,
+ OUT LPWIN32_FIND_DATAA lpFindFileData)
+{
+ find_obj *find_data;
+
+ BOOL bRet = FALSE;
+ DWORD dwLastError = 0;
+ DWORD Attr;
+
+ PERF_ENTRY(FindNextFileA);
+ ENTRY("FindNextFileA(hFindFile=%p, lpFindFileData=%p)\n",
+ hFindFile, lpFindFileData);
+
+ find_data = (find_obj*)hFindFile;
+
+ if ( hFindFile == INVALID_HANDLE_VALUE ||
+ find_data == NULL ||
+ find_data->self_addr != find_data )
+ {
+ TRACE("FindNextFileA received an invalid handle\n");
+ dwLastError = ERROR_INVALID_HANDLE;
+ goto done;
+ }
+
+ if ( find_data->next)
+ {
+ struct stat stat_data;
+ char ext[_MAX_EXT];
+ int stat_result;
+
+ while (*(find_data->next))
+ {
+ char *path = *(find_data->next);
+
+ TRACE("Found [%s]\n", path);
+
+ // Split the path into a dir and filename.
+ if (_splitpath_s(path, NULL, 0, find_data->dir, _MAX_DIR, find_data->fname, _MAX_PATH, ext, _MAX_EXT) != 0)
+ {
+ ASSERT("_splitpath failed on %s\n", path);
+ dwLastError = ERROR_INTERNAL_ERROR;
+ goto done;
+ }
+ strcat_s( find_data->fname, sizeof(find_data->fname), ext );
+
+ /* get the attributes, but continue if it fails */
+ Attr = GetFileAttributesA(path);
+ if (Attr == INVALID_FILE_ATTRIBUTES)
+ {
+ WARN("GetFileAttributes returned -1 on file [%s]\n",
+ *(find_data->next));
+ }
+ lpFindFileData->dwFileAttributes = Attr;
+
+ /* Note that cFileName is NOT the relative path */
+ if (strcpy_s( lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName), find_data->fname ) != SAFECRT_SUCCESS)
+ {
+ TRACE("strcpy_s failed!\n");
+ dwLastError = ERROR_FILENAME_EXCED_RANGE;
+ goto done;
+ }
+
+ /* we don't support 8.3 filenames, so just leave it empty */
+ lpFindFileData->cAlternateFileName[0] = 0;
+
+ /* get the filetimes */
+ stat_result = stat(path, &stat_data) == 0 ||
+ lstat(path, &stat_data) == 0;
+
+ find_data->next++;
+
+ if ( stat_result )
+ {
+ lpFindFileData->ftCreationTime =
+ FILEUnixTimeToFileTime( stat_data.st_ctime,
+ ST_CTIME_NSEC(&stat_data) );
+ lpFindFileData->ftLastAccessTime =
+ FILEUnixTimeToFileTime( stat_data.st_atime,
+ ST_ATIME_NSEC(&stat_data) );
+ lpFindFileData->ftLastWriteTime =
+ FILEUnixTimeToFileTime( stat_data.st_mtime,
+ ST_MTIME_NSEC(&stat_data) );
+
+ /* if Unix mtime is greater than atime, return mtime
+ as the last access time */
+ if (CompareFileTime(&lpFindFileData->ftLastAccessTime,
+ &lpFindFileData->ftLastWriteTime) < 0)
+ {
+ lpFindFileData->ftLastAccessTime = lpFindFileData->ftLastWriteTime;
+ }
+
+ /* get file size */
+ lpFindFileData->nFileSizeLow = (DWORD)stat_data.st_size;
+ #if SIZEOF_OFF_T > 4
+ lpFindFileData->nFileSizeHigh =
+ (DWORD)(stat_data.st_size >> 32);
+ #else
+ lpFindFileData->nFileSizeHigh = 0;
+ #endif
+
+ bRet = TRUE;
+ break;
+ }
+ }
+ if(!bRet)
+ {
+ dwLastError = ERROR_NO_MORE_FILES;
+ }
+ }
+ else
+ {
+
+ ASSERT("find_data->next is (mysteriously) NULL\n");
+ }
+
+done:
+ if (dwLastError)
+ {
+ SetLastError(dwLastError);
+ }
+
+ LOGEXIT("FindNextFileA returns BOOL %d\n", bRet);
+ PERF_EXIT(FindNextFileA);
+ return bRet;
+}
+
+
+/*++
+Function:
+ FindNextFileW
+
+See MSDN doc.
+--*/
+BOOL
+PALAPI
+FindNextFileW(
+ IN HANDLE hFindFile,
+ OUT LPWIN32_FIND_DATAW lpFindFileData)
+{
+ BOOL retval = FALSE;
+ WIN32_FIND_DATAA FindFileDataA;
+
+ PERF_ENTRY(FindNextFileW);
+ ENTRY("FindNextFileW(hFindFile=%p, lpFindFileData=%p)\n",
+ hFindFile, lpFindFileData);
+
+ retval = FindNextFileA(hFindFile, &FindFileDataA);
+ if(!retval)
+ {
+ WARN("FindNextFileA failed!\n");
+ goto done;
+ }
+
+ lpFindFileData->dwFileAttributes = FindFileDataA.dwFileAttributes;
+ lpFindFileData->dwReserved0 = FindFileDataA.dwReserved0;
+ lpFindFileData->dwReserved1 = FindFileDataA.dwReserved1;
+ lpFindFileData->ftCreationTime = FindFileDataA.ftCreationTime;
+ lpFindFileData->ftLastAccessTime = FindFileDataA.ftLastAccessTime;
+ lpFindFileData->ftLastWriteTime = FindFileDataA.ftLastWriteTime;
+ lpFindFileData->nFileSizeHigh = FindFileDataA.nFileSizeHigh;
+ lpFindFileData->nFileSizeLow = FindFileDataA.nFileSizeLow;
+
+ /* no 8.3 file names */
+ lpFindFileData->cAlternateFileName[0] = 0;
+
+ if( 0 == MultiByteToWideChar(CP_ACP, 0, FindFileDataA.cFileName, -1,
+ lpFindFileData->cFileName, MAX_PATH_FNAME))
+ {
+ DWORD dwLastError = GetLastError();
+ if (dwLastError == ERROR_INSUFFICIENT_BUFFER)
+ {
+ WARN("FindFileDataA.cFileName is larger than MAX_PATH_FNAME (%d)!\n", MAX_PATH_FNAME);
+ SetLastError(ERROR_FILENAME_EXCED_RANGE);
+ }
+ else
+ {
+ ASSERT("MultiByteToWideChar failed! error is %d\n", dwLastError);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ }
+ retval = FALSE;
+ }
+
+done:
+ LOGEXIT("FindNextFileW returns BOOL %d\n", retval);
+ PERF_EXIT(FindNextFileW);
+ return retval;
+}
+
+
+/*++
+Function:
+ FindClose
+
+See MSDN doc.
+--*/
+BOOL
+PALAPI
+FindClose(
+ IN OUT HANDLE hFindFile)
+{
+ find_obj *find_data;
+ BOOL hRet = TRUE;
+ DWORD dwLastError = 0;
+
+ PERF_ENTRY(FindClose);
+ ENTRY("FindClose(hFindFile=%p)\n", hFindFile);
+
+ find_data = (find_obj*)hFindFile;
+
+ if ( hFindFile == INVALID_HANDLE_VALUE ||
+ find_data == NULL ||
+ find_data->self_addr != find_data )
+ {
+ ERROR("Invalid find handle\n");
+ hRet = FALSE;
+ dwLastError = ERROR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ find_data->self_addr = NULL;
+
+ // Call globfree only when there is any pattern match
+ // otherwise, HPUX C library segfaults.
+ if (NULL != find_data->gGlob.gl_pathv)
+ {
+ globfree( &(find_data->gGlob) );
+ }
+ free(find_data);
+
+done:
+ if (dwLastError)
+ {
+ SetLastError(dwLastError);
+ }
+
+ LOGEXIT("FindClose returns BOOL %d\n", hRet);
+ PERF_EXIT(FindClose);
+ return hRet;
+}
+
+
+/*++
+Function:
+ FILEMakePathA
+
+Mimics _makepath from windows, except it's a bit safer.
+Any or all of dir, fname, and ext can be NULL.
+--*/
+static int FILEMakePathA( char *buff,
+ int buff_size,
+ const char *dir,
+ const char *fname,
+ const char *ext )
+{
+ int dir_len = 0;
+ int fname_len = 0;
+ int ext_len = 0;
+ int len;
+ char *p;
+
+ TRACE("Attempting to assemble path from [%s][%s][%s], buff_size = %d\n",
+ dir?dir:"NULL", fname?fname:"NULL", ext?ext:"NULL", buff_size);
+
+ if (dir) dir_len = strlen(dir);
+ if (fname) fname_len = strlen(fname);
+ if (ext) ext_len = strlen(ext);
+
+ len = dir_len + fname_len + ext_len + 1;
+
+ TRACE("Required buffer size is %d bytes\n", len);
+
+ if ( len > buff_size )
+ {
+ ERROR("Buffer is too small (%d bytes), needs %d bytes\n",
+ buff_size, len);
+ return -1;
+ }
+ else
+ {
+ buff[0] = 0;
+
+ p = buff;
+ if (dir_len > 0)
+ {
+ if (strncpy_s( buff, buff_size, dir, dir_len + 1 ) != SAFECRT_SUCCESS)
+ {
+ ERROR("FILEMakePathA: strncpy_s failed\n");
+ return -1;
+ }
+
+ p += dir_len;
+ buff_size-= dir_len;
+ }
+ if (fname_len > 0)
+ {
+ if (strncpy_s( p, buff_size, fname, fname_len + 1 ) != SAFECRT_SUCCESS)
+ {
+ ERROR("FILEMakePathA: strncpy_s failed\n");
+ return -1;
+ }
+
+ p += fname_len;
+ buff_size-=fname_len;
+ }
+ if (ext_len > 0)
+ {
+ if (strncpy_s( p, buff_size, ext, ext_len + 1) != SAFECRT_SUCCESS)
+ {
+ ERROR("FILEMakePathA: strncpy_s failed\n");
+ return -1;
+ }
+ }
+
+ TRACE("FILEMakePathA assembled [%s]\n", buff);
+ return len - 1;
+ }
+}
+
+
+/*++
+ FILEGlobQsortCompare
+
+ Comparison function required by qsort, so that the
+ . and .. directories end up on top of the sorted list
+ of directories.
+--*/
+static int FILEGlobQsortCompare(const void *in_str1, const void *in_str2)
+{
+ char **str1 = (char**)in_str1;
+ char **str2 = (char**)in_str2;
+ const int FIRST_ARG_LESS = -1;
+ const int FIRST_ARG_EQUAL = 0;
+ const int FIRST_ARG_GREATER = 1;
+
+ /* If both strings are equal, return immediately */
+ if (strcmp(*(str1), *(str2)) == 0)
+ {
+ return(FIRST_ARG_EQUAL);
+ }
+
+ /* Have '.' always on top than any other search result */
+ if (strcmp(*(str1), ".") == 0)
+ {
+ return (FIRST_ARG_LESS);
+ }
+ if (strcmp(*(str2), ".") == 0)
+ {
+ return (FIRST_ARG_GREATER);
+ }
+
+ /* Have '..' next on top, over any other search result */
+ if (strcmp(*(str1), "..") == 0)
+ {
+ return (FIRST_ARG_LESS);
+ }
+ if (strcmp(*(str2), "..") == 0)
+ {
+ return (FIRST_ARG_GREATER);
+ }
+
+ /* Finally, let strcmp do the rest for us */
+ return (strcmp(*(str1),*(str2)));
+}
+
+/*++
+Function:
+ FILEEscapeSquareBrackets
+
+Simple helper function to insert backslashes before square brackets
+to prevent glob from using them as wildcards.
+
+note: this functions assumes all backslashes have previously been
+ converted into forwardslashes by _splitpath.
+--*/
+static void FILEEscapeSquareBrackets(char *pattern, char *escaped_pattern)
+{
+ TRACE("Entering FILEEscapeSquareBrackets: [%p (%s)][%p]\n",
+ pattern,pattern,escaped_pattern);
+
+#if _ENABLE_DEBUG_MESSAGES_
+ char *escaped_pattern_base = escaped_pattern;
+#endif // _ENABLE_DEBUG_MESSAGES_
+
+ while(*pattern)
+ {
+ if('[' == *pattern || ']' == *pattern)
+ {
+ *escaped_pattern = '\\';
+ escaped_pattern++;
+ }
+ *escaped_pattern = *pattern;
+ pattern++;
+ escaped_pattern++;
+ }
+ *escaped_pattern='\0';
+
+ TRACE("FILEEscapeSquareBrackets done. escaped_pattern=%s\n",
+ escaped_pattern_base);
+}
+
+
+/*++
+Function:
+ FILEGlobFromSplitPath
+
+Simple wrapper function around glob(3), except that the pattern is accepted
+in broken-down form like _splitpath produces.
+
+ie. calling splitpath on a pattern then calling this function should
+produce the same result as just calling glob() on the pattern.
+--*/
+static int FILEGlobFromSplitPath( const char *dir,
+ const char *fname,
+ const char *ext,
+ int flags,
+ glob_t *pgGlob )
+{
+ int Ret;
+ PathCharString PatternPS;
+ PathCharString EscapedPatternPS;
+ char * Pattern;
+ int length = 0;
+ char * EscapedPattern;
+
+ TRACE("We shall attempt to glob from components [%s][%s][%s]\n",
+ dir?dir:"NULL", fname?fname:"NULL", ext?ext:"NULL");
+
+ if (dir) length = strlen(dir);
+ if (fname) length += strlen(fname);
+ if (ext) length += strlen(ext);
+
+ Pattern = PatternPS.OpenStringBuffer(length);
+ if (NULL == Pattern)
+ {
+ ERROR("Not Enough memory.");
+ return -1;
+ }
+ FILEMakePathA( Pattern, length+1, dir, fname, ext );
+ PatternPS.CloseBuffer(length);
+ TRACE("Assembled Pattern = [%s]\n", Pattern);
+
+ /* special handling is needed to handle the case where
+ filename contains '[' and ']' */
+ EscapedPattern = EscapedPatternPS.OpenStringBuffer(length*2);
+ if (NULL == EscapedPattern)
+ {
+ ERROR("Not Enough memory.");
+ return -1;
+ }
+ FILEEscapeSquareBrackets( Pattern, EscapedPattern);
+ EscapedPatternPS.CloseBuffer(strlen(EscapedPattern));
+#ifdef GLOB_QUOTE
+ flags |= GLOB_QUOTE;
+#endif // GLOB_QUOTE
+ Ret = InternalGlob(EscapedPattern, flags, NULL, pgGlob);
+
+#ifdef GLOB_NOMATCH
+ if (Ret == GLOB_NOMATCH)
+ {
+ // pgGlob->gl_pathc will be 0 in this case. We'll check
+ // the return value to see if an error occurred, so we
+ // don't want to return an error if we simply didn't match
+ // anything.
+ Ret = 0;
+ }
+#endif // GLOB_NOMATCH
+
+ /* Ensure that . and .. are placed in front, and sort the rest */
+ qsort(pgGlob->gl_pathv, pgGlob->gl_pathc, sizeof(char*),
+ FILEGlobQsortCompare);
+ TRACE("Result of glob() is %d\n", Ret);
+
+ return Ret;
+}
+
+
+/*++
+Function:
+ FILEDosGlobA
+
+Generate pathnames matching a DOS globbing pattern. This function has a similar
+prototype to glob(3), and fulfils the same purpose. However, DOS globbing
+is slightly different than Unix in the following ways:
+
+- '.*' at the end of a pattern means "any file extension, or none at all",
+whereas Unix has no concept of file extensions, and will match the '.' like
+any other character
+
+- on Unix, filenames beginning with '.' must be explicitly matched. This is
+not true in DOS
+
+- in DOS, the first two entries (if they match) will be '.' and '..', followed
+by all other matching entries sorted in ASCII order. In Unix, all entries are
+treated equally, so '+file' would appear before '.' and '..'
+
+- DOS globbing will fail if any wildcard characters occur before the last path
+separator
+
+This implementation of glob implements the DOS behavior in all these cases,
+but otherwise attempts to behave exactly like POSIX glob. The only exception
+is its return value -- it returns TRUE if it succeeded (finding matches or
+finding no matches but without any error occurring) or FALSE if any error
+occurs. It calls SetLastError() if it returns FALSE.
+
+Sorting doesn't seem to be consistent on all Windows platform, and it's
+not required for Rotor to have the same sorting alogrithm than Windows 2000.
+This implementation will give slightly different result for the sort list
+than Windows 2000.
+
+--*/
+static BOOL FILEDosGlobA( CPalThread *pthrCurrent,
+ const char *pattern,
+ int flags,
+ glob_t *pgGlob )
+{
+ char Dir[_MAX_DIR];
+ char FilenameBuff[_MAX_FNAME + 1];
+ char *Filename = FilenameBuff + 1;
+ char Ext[_MAX_EXT];
+ int A, B, C;
+ BOOL result = TRUE;
+ int globResult = 0;
+
+ Dir[0] = 0;
+ FilenameBuff[0] = '.';
+ FilenameBuff[1] = 0;
+ Ext[0] = 0;
+
+ _splitpath_s( pattern, NULL, 0, Dir, _MAX_DIR, Filename, _MAX_FNAME+1, Ext, _MAX_EXT);
+
+ /* check to see if _splitpath failed */
+ if ( Filename[0] == 0 )
+ {
+ if ( Dir[0] == 0 )
+ {
+ ERROR("_splitpath failed on path [%s]\n", pattern);
+ }
+ else
+ {
+ ERROR("Pattern contains a trailing backslash\n");
+ }
+ SetLastError(ERROR_PATH_NOT_FOUND);
+ result = FALSE;
+ goto done;
+ }
+
+ TRACE("glob pattern [%s] split into [%s][%s][%s]\n",
+ pattern, Dir, Filename, Ext);
+
+ if ( strchr(Dir, '*') != NULL || strchr(Dir, '?') != NULL )
+ {
+ ERROR("Found wildcard character(s) ('*' and/or '?') before "
+ "last path separator\n");
+ SetLastError(ERROR_PATH_NOT_FOUND);
+ result = FALSE;
+ goto done;
+ }
+
+ if (Dir[0] != 0)
+ {
+ FILEDosToUnixPathA( Dir );
+ }
+
+ /* The meat of the routine happens below. Basically, there are three
+ special things to check for:
+
+ (A) If the extension is _exactly_ '.*', we will need to do two globs,
+ one for 'filename.*' and one for 'filename', EXCEPT if (B) the last
+ character of filename is '*', in which case we can eliminate the
+ extension altogether, since '*.*' and '*' are the same in DOS.
+ (C) If the first character of the filename is '*', we need to do
+ an additional glob for each one we have already done, except with
+ '.' prepended to the filename of the patterns, because in Unix,
+ hidden files need to be matched explicitly.
+
+ We can ignore the extension by calling FILEGlobFromSplitPath with
+ the extension parameter as "", and we can prepend '.' to the
+ filename by using (Filename - 1), since Filename conveniently points
+ to the second character of a buffer which happens to have '.' as
+ its first character.
+ */
+
+ A = strncmp(Ext, ".*", 3) == 0;
+ B = (Filename[strlen(Filename) - 1] == '*');
+ C = (*Filename == '*');
+
+ TRACE("Extension IS%s '.*', filename DOES%s end with '*', "
+ "and filename DOES%s begin with '*'\n",
+ A?"":" NOT", B?"":" NOT", C?"":" NOT");
+
+ if ( !(A && B) )
+ {
+ /* the original pattern */
+ globResult = FILEGlobFromSplitPath(Dir, Filename, Ext, 0, pgGlob);
+ if ( globResult != 0 )
+ {
+ goto done;
+ }
+
+ if (C)
+ {
+ /* the original pattern but '.' prepended to filename */
+ globResult = FILEGlobFromSplitPath(Dir, Filename - 1, Ext,
+ GLOB_APPEND, pgGlob);
+ if ( globResult != 0 )
+ {
+ goto done;
+ }
+ }
+ }
+
+ if (A)
+ {
+ /* if (A && B), this is the first glob() call. The first call
+ to glob must use flags = 0, while proceeding calls should
+ set the GLOB_APPEND flag. */
+ globResult = FILEGlobFromSplitPath(Dir, Filename, "",
+ (A && B)?0:GLOB_APPEND, pgGlob);
+ if ( globResult != 0 )
+ {
+ goto done;
+ }
+
+ if (C)
+ {
+ /* omit the extension and prepend '.' to filename */
+ globResult = FILEGlobFromSplitPath(Dir, Filename - 1, "",
+ GLOB_APPEND, pgGlob);
+ if ( globResult != 0 )
+ {
+ goto done;
+ }
+ }
+ }
+
+done:
+ if (globResult != 0)
+ {
+ if (globResult == GLOB_NOSPACE)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ }
+ else
+ {
+ SetLastError(ERROR_INTERNAL_ERROR);
+ }
+ result = FALSE;
+ }
+ TRACE("Returning %d\n", result);
+ return result;
+}
+
+