1
/* $Id: RTPathGlob.cpp $ */
7
* Copyright (C) 2006-2015 Oracle Corporation
9
* This file is part of VirtualBox Open Source Edition (OSE), as
10
* available from http://www.virtualbox.org. This file is free software;
11
* you can redistribute it and/or modify it under the terms of the GNU
12
* General Public License (GPL) as published by the Free Software
13
* Foundation, in version 2 as it comes in the "COPYING" file of the
14
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17
* The contents of this file may alternatively be used under the terms
18
* of the Common Development and Distribution License Version 1.0
19
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20
* VirtualBox OSE distribution, in which case the provisions of the
21
* CDDL are applicable instead of those of the GPL.
23
* You may elect to license modified versions of this file under the
24
* terms and conditions of either the GPL or the CDDL or both.
28
/*********************************************************************************************************************************
30
*********************************************************************************************************************************/
31
#include "internal/iprt.h"
32
#include <iprt/path.h>
35
#include <iprt/assert.h>
36
#include <iprt/buildconfig.h>
37
#include <iprt/ctype.h>
42
#include <iprt/string.h>
45
#if defined(RT_OS_WINDOWS)
48
#elif defined(RT_OS_OS2)
51
# undef RT_MAX /* collision */
56
/*********************************************************************************************************************************
57
* Defined Constants And Macros *
58
*********************************************************************************************************************************/
59
/** Maximum number of results. */
60
#define RTPATHGLOB_MAX_RESULTS _32K
61
/** Maximum number of zero-or-more wildcards in a pattern.
62
* This limits stack usage and recursion depth, as well as execution time. */
63
#define RTPATHMATCH_MAX_ZERO_OR_MORE 24
64
/** Maximum number of variable items. */
65
#define RTPATHMATCH_MAX_VAR_ITEMS _4K
69
/*********************************************************************************************************************************
70
* Structures and Typedefs *
71
*********************************************************************************************************************************/
75
typedef enum RTPATHMATCHOP
77
RTPATHMATCHOP_INVALID = 0,
78
/** EOS: Returns a match if at end of string. */
79
RTPATHMATCHOP_RETURN_MATCH_IF_AT_END,
80
/** Asterisk: Returns a match (trailing asterisk). */
81
RTPATHMATCHOP_RETURN_MATCH,
82
/** Asterisk: Returns a match (just asterisk), unless it's '.' or '..'. */
83
RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT,
84
/** Plain text: Case sensitive string compare. */
86
/** Plain text: Case insensitive string compare. */
87
RTPATHMATCHOP_STRICMP,
88
/** Question marks: Skips exactly one code point. */
89
RTPATHMATCHOP_SKIP_ONE_CODEPOINT,
90
/** Question marks: Skips exactly RTPATHMATCHCORE::cch code points. */
91
RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS,
92
/** Char set: Requires the next codepoint to be in the ASCII-7 set defined by
93
* RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. No ranges. */
94
RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7,
95
/** Char set: Requires the next codepoint to not be in the ASCII-7 set defined
96
* by RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. No ranges. */
97
RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7,
98
/** Char set: Requires the next codepoint to be in the extended set defined by
99
* RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. Ranges, UTF-8. */
100
RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED,
101
/** Char set: Requires the next codepoint to not be in the extended set defined
102
* by RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. Ranges, UTF-8. */
103
RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED,
104
/** Variable: Case sensitive variable value compare, RTPATHMATCHCORE::uOp2 is
105
* the variable table index. */
106
RTPATHMATCHOP_VARIABLE_VALUE_CMP,
107
/** Variable: Case insensitive variable value compare, RTPATHMATCHCORE::uOp2 is
108
* the variable table index. */
109
RTPATHMATCHOP_VARIABLE_VALUE_ICMP,
110
/** Asterisk: Match zero or more code points, there must be at least
111
* RTPATHMATCHCORE::cch code points after it. */
112
RTPATHMATCHOP_ZERO_OR_MORE,
113
/** Asterisk: Match zero or more code points, there must be at least
114
* RTPATHMATCHCORE::cch code points after it, unless it's '.' or '..'. */
115
RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT,
116
/** End of valid operations. */
121
* Matching instruction.
123
typedef struct RTPATHMATCHCORE
125
/** The action to take. */
126
RTPATHMATCHOP enmOpCode;
127
/** Generic value operand. */
129
/** Generic length operand. */
131
/** Generic string pointer operand. */
134
/** Pointer to a matching instruction. */
135
typedef RTPATHMATCHCORE *PRTPATHMATCHCORE;
136
/** Pointer to a const matching instruction. */
137
typedef RTPATHMATCHCORE const *PCRTPATHMATCHCORE;
140
* Path matching instruction allocator.
142
typedef struct RTPATHMATCHALLOC
144
/** Allocated array of instructions. */
145
PRTPATHMATCHCORE paInstructions;
146
/** Index of the next free entry in paScratch. */
148
/** Number of instructions allocated. */
151
/** Pointer to a matching instruction allocator. */
152
typedef RTPATHMATCHALLOC *PRTPATHMATCHALLOC;
155
* Path matching cache, mainly intended for variables like the PATH.
157
typedef struct RTPATHMATCHCACHE
159
/** @todo optimize later. */
160
uint32_t iNothingYet;
162
/** Pointer to a path matching cache. */
163
typedef RTPATHMATCHCACHE *PRTPATHMATCHCACHE;
167
/** Parsed path entry.*/
168
typedef struct RTPATHGLOBPPE
170
/** Normal: Index into RTPATHGLOB::MatchInstrAlloc.paInstructions. */
171
uint32_t iMatchProg : 16;
172
/** Set if this is a normal entry which is matched using iMatchProg. */
173
uint32_t fNormal : 1;
174
/** !fNormal: Plain name that can be dealt with using without
175
* enumerating the whole directory, unless of course the file system is case
176
* sensitive and the globbing isn't (that needs figuring out on a per
177
* directory basis). */
179
/** !fNormal: Match zero or more subdirectories. */
180
uint32_t fStarStar : 1;
181
/** !fNormal: The whole component is a variable expansion. */
182
uint32_t fExpVariable : 1;
184
/** Filter: Set if it only matches directories. */
186
/** Set if it's the final component. */
190
uint32_t fReserved : 2+8;
194
typedef struct RTPATHGLOB
197
char szPath[RTPATH_MAX];
198
/** Temporary buffers. */
201
/** File system object info structure. */
203
/** Directory entry buffer. */
205
/** Padding the buffer to an unreasonably large size. */
206
uint8_t abPadding[RTPATH_MAX + sizeof(RTDIRENTRY)];
210
/** Where to insert the next one.*/
211
PRTPATHGLOBENTRY *ppNext;
212
/** The head pointer. */
213
PRTPATHGLOBENTRY pHead;
216
/** Counts path overflows. */
217
uint32_t cPathOverflows;
218
/** The input flags. */
220
/** Matching instruction allocator. */
221
RTPATHMATCHALLOC MatchInstrAlloc;
222
/** Matching state. */
223
RTPATHMATCHCACHE MatchCache;
225
/** The pattern string. */
226
const char *pszPattern;
227
/** The parsed path. */
228
PRTPATHPARSED pParsed;
229
/** The component to start with. */
231
/** The corresponding path offset (previous components already present). */
232
uint16_t offFirstPath;
233
/** Path component information we need. */
234
RTPATHGLOBPPE aComps[1];
236
typedef RTPATHGLOB *PRTPATHGLOB;
239
/*********************************************************************************************************************************
240
* Internal Functions *
241
*********************************************************************************************************************************/
242
static int rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp, size_t offStarStarPath);
243
static int rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
244
static int rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
245
static int rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
249
* Implements the two variable access functions for a simple one value variable.
251
#define RTPATHMATCHVAR_SIMPLE(a_Name, a_GetStrExpr) \
252
static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
253
PRTPATHMATCHCACHE pCache) \
257
const char *pszValue = a_GetStrExpr; \
258
size_t cchValue = strlen(pszValue); \
259
if (cchValue + 1 <= cbBuf) \
261
memcpy(pszBuf, pszValue, cchValue + 1); \
262
*pcchValue = cchValue; \
265
return VERR_BUFFER_OVERFLOW; \
270
static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
271
size_t *pcchMatched) \
273
const char *pszValue = a_GetStrExpr; \
274
size_t cchValue = strlen(pszValue); \
275
if ( cchValue >= cchMatch \
277
? memcmp(pszValue, pchMatch, cchValue) == 0 \
278
: RTStrNICmp(pszValue, pchMatch, cchValue) == 0) ) \
280
*pcchMatched = cchValue; \
281
return VINF_SUCCESS; \
283
return VERR_MISMATCH; \
285
typedef int RT_CONCAT(DummyColonType_,a_Name)
288
* Implements mapping a glob variable to an environment variable.
290
#define RTPATHMATCHVAR_SIMPLE_ENVVAR(a_Name, a_pszEnvVar, a_cbMaxValue) \
291
static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
292
PRTPATHMATCHCACHE pCache) \
296
int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, pszBuf, cbBuf, pcchValue); \
297
if (RT_SUCCESS(rc)) \
299
if (rc != VERR_ENV_VAR_NOT_FOUND) \
305
static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
306
size_t *pcchMatched) \
308
char szValue[a_cbMaxValue]; \
310
int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, szValue, sizeof(szValue), &cchValue); \
311
if ( RT_SUCCESS(rc) \
312
&& cchValue >= cchMatch \
314
? memcmp(szValue, pchMatch, cchValue) == 0 \
315
: RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
317
*pcchMatched = cchValue; \
318
return VINF_SUCCESS; \
320
return VERR_MISMATCH; \
322
typedef int RT_CONCAT(DummyColonType_,a_Name)
325
* Implements mapping a glob variable to multiple environment variable values.
326
* @param a_apszVarNames Assumes to be a global variable that RT_ELEMENTS
327
* works correctly on.
329
#define RTPATHMATCHVAR_MULTIPLE_ENVVARS(a_Name, a_apszVarNames, a_cbMaxValue) \
330
static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
331
PRTPATHMATCHCACHE pCache) \
333
if (iItem < RT_ELEMENTS(a_apszVarNames)) \
335
int rc = RTEnvGetEx(RTENV_DEFAULT, a_apszVarNames[iItem], pszBuf, cbBuf, pcchValue); \
336
if (RT_SUCCESS(rc)) \
337
return iItem + 1 == RT_ELEMENTS(a_apszVarNames) ? VINF_EOF : VINF_SUCCESS; \
338
if (rc == VERR_ENV_VAR_NOT_FOUND) \
339
rc = VERR_TRY_AGAIN; \
345
static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
346
size_t *pcchMatched) \
348
for (uint32_t iItem = 0; iItem < RT_ELEMENTS(a_apszVarNames); iItem++) \
350
char szValue[a_cbMaxValue]; \
352
int rc = RTEnvGetEx(RTENV_DEFAULT, a_apszVarNames[iItem], szValue, sizeof(szValue), &cchValue);\
353
if ( RT_SUCCESS(rc) \
354
&& cchValue >= cchMatch \
356
? memcmp(szValue, pchMatch, cchValue) == 0 \
357
: RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
359
*pcchMatched = cchValue; \
360
return VINF_SUCCESS; \
363
return VERR_MISMATCH; \
365
typedef int RT_CONCAT(DummyColonType_,a_Name)
368
RTPATHMATCHVAR_SIMPLE(Arch, RTBldCfgTargetArch());
369
RTPATHMATCHVAR_SIMPLE(Bits, RT_XSTR(ARCH_BITS));
371
RTPATHMATCHVAR_SIMPLE_ENVVAR(WinAppData, "AppData", RTPATH_MAX);
372
RTPATHMATCHVAR_SIMPLE_ENVVAR(WinProgramData, "ProgramData", RTPATH_MAX);
373
RTPATHMATCHVAR_SIMPLE_ENVVAR(WinProgramFiles, "ProgramFiles", RTPATH_MAX);
374
RTPATHMATCHVAR_SIMPLE_ENVVAR(WinCommonProgramFiles, "CommonProgramFiles", RTPATH_MAX);
375
# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
376
RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherProgramFiles, "ProgramFiles(x86)", RTPATH_MAX);
377
RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherCommonProgramFiles, "CommonProgramFiles(x86)", RTPATH_MAX);
381
static const char * const a_apszWinProgramFilesVars[] =
384
# ifdef RT_ARCH_AMD64
388
RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllProgramFiles, a_apszWinProgramFilesVars, RTPATH_MAX);
389
static const char * const a_apszWinCommonProgramFilesVars[] =
391
"CommonProgramFiles",
392
# ifdef RT_ARCH_AMD64
393
"CommonProgramFiles(x86)",
396
RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllCommonProgramFiles, a_apszWinCommonProgramFilesVars, RTPATH_MAX);
401
* @interface_method_impl{RTPATHMATCHVAR::pfnQuery, Enumerates the PATH}.
403
static DECLCALLBACK(int) rtPathVarQuery_Path(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
404
PRTPATHMATCHCACHE pCache)
407
* Query the PATH value.
409
/** @todo cache this in pCache with iItem and offset. */
410
char *pszPathFree = NULL;
411
char *pszPath = pszBuf;
413
const char *pszVarNm = "PATH";
414
int rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPath, cbBuf, &cchActual);
416
if (rc == VERR_ENV_VAR_NOT_FOUND)
417
rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm = "Path", pszPath, cbBuf, &cchActual);
419
if (rc == VERR_BUFFER_OVERFLOW)
421
for (uint32_t iTry = 0; iTry < 10; iTry++)
423
size_t cbPathBuf = RT_ALIGN_Z(cchActual + 1 + 64 * iTry, 64);
424
pszPathFree = (char *)RTMemTmpAlloc(cbPathBuf);
425
rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPathFree, cbPathBuf, &cchActual);
428
RTMemTmpFree(pszPathFree);
429
AssertReturn(cchActual >= cbPathBuf, VERR_INTERNAL_ERROR_3);
431
pszPath = pszPathFree;
435
* Spool forward to the given PATH item.
438
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
439
const char chSep = ';';
441
const char chSep = ':';
443
while (*pszPath != '\0')
445
char *pchSep = strchr(pszPath, chSep);
447
/* We ignore empty strings, which is probably not entirely correct,
448
but works better on DOS based system with many entries added
449
without checking whether there is a trailing separator or not.
450
Thus, the current directory is only searched if a '.' is present
452
if (pchSep == pszPath)
456
/* If we didn't find a separator, the item doesn't exists. Quit. */
460
pszPath = pchSep + 1;
465
/* We've reached the item we wanted. */
466
size_t cchComp = pchSep ? pchSep - pszPath : strlen(pszPath);
469
if (pszBuf != pszPath)
470
memmove(pszBuf, pszPath, cchComp);
471
pszBuf[cchComp] = '\0';
472
rc = pchSep ? VINF_SUCCESS : VINF_EOF;
475
rc = VERR_BUFFER_OVERFLOW;
476
*pcchValue = cchComp;
482
RTMemTmpFree(pszPathFree);
487
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
489
* @interface_method_impl{RTPATHMATCHVAR::pfnQuery,
490
* The system drive letter + colon.}.
492
static DECLCALLBACK(int) rtPathVarQuery_DosSystemDrive(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
493
PRTPATHMATCHCACHE pCache)
497
AssertReturn(cbBuf >= 3, VERR_BUFFER_OVERFLOW);
499
# ifdef RT_OS_WINDOWS
500
/* Since this is used at the start of a pattern, we assume
501
we've got more than enough buffer space. */
502
PRTUTF16 pwszTmp = (PRTUTF16)pszBuf;
503
UINT cch = GetSystemWindowsDirectoryW(pwszTmp, (UINT)(cbBuf / sizeof(WCHAR)));
506
RTUTF16 wcDrive = pwszTmp[0];
507
if ( RT_C_IS_ALPHA(wcDrive)
508
&& pwszTmp[1] == ':')
518
ULONG ulDrive = ~(ULONG)0;
519
APIRET rc = DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, &ulDrive, sizeof(ulDrive));
520
ulDrive--; /* 1 = 'A' */
522
&& ulDrive <= (ULONG)'Z')
524
pszBuf[0] = (char)ulDrive + 'A';
531
return VERR_INTERNAL_ERROR_4;
540
* @interface_method_impl{RTPATHMATCHVAR::pfnQuery,
541
* The system root directory (C:\Windows).}.
543
static DECLCALLBACK(int) rtPathVarQuery_WinSystemRoot(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
544
PRTPATHMATCHCACHE pCache)
548
Assert(pszBuf); Assert(cbBuf);
549
RTUTF16 wszSystemRoot[MAX_PATH];
550
UINT cchSystemRoot = GetSystemWindowsDirectoryW(wszSystemRoot, MAX_PATH);
551
if (cchSystemRoot > 0)
552
return RTUtf16ToUtf8Ex(wszSystemRoot, cchSystemRoot, &pszBuf, cbBuf, pcchValue);
553
return RTErrConvertFromWin32(GetLastError());
559
#undef RTPATHMATCHVAR_SIMPLE
560
#undef RTPATHMATCHVAR_SIMPLE_ENVVAR
561
#undef RTPATHMATCHVAR_DOUBLE_ENVVAR
564
* Matching variable lookup table.
565
* Currently so small we don't bother sorting it and doing binary lookups.
567
static struct RTPATHMATCHVAR
569
/** The variable name. */
571
/** The variable name length. */
573
/** Only available as the verify first component. */
577
* Queries a given variable value.
579
* @returns IPRT status code.
580
* @retval VERR_BUFFER_OVERFLOW
581
* @retval VERR_TRY_AGAIN if the caller should skip this value item and try the
582
* next one instead (e.g. env var not present).
583
* @retval VINF_EOF when retrieving the last one, if possible.
584
* @retval VERR_EOF when @a iItem is past the item space.
586
* @param iItem The variable value item to retrieve. (A variable may
587
* have more than one value, e.g. 'BothProgramFile' on a
588
* 64-bit system or 'Path'.)
589
* @param pszBuf Where to return the value.
590
* @param cbBuf The buffer size.
591
* @param pcchValue Where to return the length of the return string.
592
* @param pCache Pointer to the path matching cache. May speed up
593
* enumerating PATH items and similar.
595
DECLCALLBACKMEMBER(int, pfnQuery)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, PRTPATHMATCHCACHE pCache);
598
* Matching method, optional.
600
* @returns IPRT status code.
601
* @retval VINF_SUCCESS on match.
602
* @retval VERR_MISMATCH on mismatch.
604
* @param pszMatch String to match with (not terminated).
605
* @param cchMatch The length of what we match with.
606
* @param fIgnoreCase Whether to ignore case or not when comparing.
607
* @param pcchMatched Where to return the length of the match (value length).
609
DECLCALLBACKMEMBER(int, pfnMatch)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, size_t *pcchMatched);
611
} const g_aVariables[] =
613
{ RT_STR_TUPLE("Arch"), false, rtPathVarQuery_Arch, rtPathVarMatch_Arch },
614
{ RT_STR_TUPLE("Bits"), false, rtPathVarQuery_Bits, rtPathVarMatch_Bits },
615
{ RT_STR_TUPLE("Path"), true, rtPathVarQuery_Path, NULL },
616
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
617
{ RT_STR_TUPLE("SystemDrive"), true, rtPathVarQuery_DosSystemDrive, NULL },
620
{ RT_STR_TUPLE("SystemRoot"), true, rtPathVarQuery_WinSystemRoot, NULL },
621
{ RT_STR_TUPLE("AppData"), true, rtPathVarQuery_WinAppData, NULL },
622
{ RT_STR_TUPLE("ProgramData"), true, rtPathVarQuery_WinProgramData, NULL },
623
{ RT_STR_TUPLE("ProgramFiles"), true, rtPathVarQuery_WinProgramFiles, NULL },
624
{ RT_STR_TUPLE("OtherProgramFiles"), true, rtPathVarQuery_WinOtherProgramFiles, NULL },
625
{ RT_STR_TUPLE("AllProgramFiles"), true, rtPathVarQuery_WinAllProgramFiles, NULL },
626
{ RT_STR_TUPLE("CommonProgramFiles"), true, rtPathVarQuery_WinCommonProgramFiles, NULL },
627
{ RT_STR_TUPLE("OtherCommonProgramFiles"), true, rtPathVarQuery_WinOtherCommonProgramFiles, NULL },
628
{ RT_STR_TUPLE("AllCommonProgramFiles"), true, rtPathVarQuery_WinAllCommonProgramFiles, NULL },
635
* Handles a complicated set.
637
* A complicated set is either using ranges, character classes or code points
638
* outside the ASCII-7 range.
640
* @returns VINF_SUCCESS or VERR_MISMATCH. May also return UTF-8 decoding
641
* errors as well as VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED.
643
* @param ucInput The input code point to match with.
644
* @param pchSet The start of the set specification (after caret).
645
* @param cchSet The length of the set specification.
647
static int rtPathMatchExecExtendedSet(RTUNICP ucInput, const char *pchSet, size_t cchSet)
652
int rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet);
653
AssertRCReturn(rc, rc);
656
* Check for character class, collating symbol and equvalence class.
658
if (ucSet == '[' && cchSet > 0)
660
char chNext = *pchSet;
663
#define CHECK_CHAR_CLASS(a_szClassNm, a_BoolTestExpr) \
664
if ( cchSet >= sizeof(a_szClassNm) \
665
&& memcmp(pchSet, a_szClassNm "]", sizeof(a_szClassNm)) == 0) \
667
if (a_BoolTestExpr) \
668
return VINF_SUCCESS; \
669
pchSet += sizeof(a_szClassNm); \
670
cchSet -= sizeof(a_szClassNm); \
674
CHECK_CHAR_CLASS(":alpha:", RTUniCpIsAlphabetic(ucInput));
675
CHECK_CHAR_CLASS(":alnum:", RTUniCpIsAlphabetic(ucInput) || RTUniCpIsDecDigit(ucInput)); /** @todo figure what's correct here and fix uni.h */
676
CHECK_CHAR_CLASS(":blank:", ucInput == ' ' || ucInput == '\t');
677
CHECK_CHAR_CLASS(":cntrl:", ucInput < 31 || ucInput == 127);
678
CHECK_CHAR_CLASS(":digit:", RTUniCpIsDecDigit(ucInput));
679
CHECK_CHAR_CLASS(":lower:", RTUniCpIsLower(ucInput));
680
CHECK_CHAR_CLASS(":print:", RTUniCpIsAlphabetic(ucInput) || (RT_C_IS_PRINT(ucInput) && ucInput < 127)); /** @todo fixme*/
681
CHECK_CHAR_CLASS(":punct:", RT_C_IS_PRINT(ucInput) && ucInput < 127); /** @todo fixme*/
682
CHECK_CHAR_CLASS(":space:", RTUniCpIsSpace(ucInput));
683
CHECK_CHAR_CLASS(":upper:", RTUniCpIsUpper(ucInput));
684
CHECK_CHAR_CLASS(":xdigit:", RTUniCpIsHexDigit(ucInput));
685
AssertMsgFailedReturn(("Unknown or malformed char class: '%.*s'\n", cchSet + 1, pchSet - 1),
686
VERR_PATH_GLOB_UNKNOWN_CHAR_CLASS);
687
#undef CHECK_CHAR_CLASS
689
/** @todo implement collating symbol and equvalence class. */
690
else if (chNext == '=' || chNext == '.')
691
AssertFailedReturn(VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
695
* Check for range (leading or final dash does not constitute a range).
697
if (cchSet > 1 && *pchSet == '-')
699
pchSet++; /* skip dash */
703
rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet2);
704
AssertRCReturn(rc, rc);
705
Assert(ucSet < ucSet2);
706
if (ucInput >= ucSet && ucInput <= ucSet2)
710
* Single char comparison.
712
else if (ucInput == ucSet)
715
return VERR_MISMATCH;
720
* Variable matching fallback using the query function.
722
* This must not be inlined as it consuming a lot of stack! Which is why it's
723
* placed a couple of functions away from the recursive rtPathExecMatch.
725
* @returns VINF_SUCCESS or VERR_MISMATCH.
726
* @param pchInput The current input position.
727
* @param cchInput The amount of input left..
728
* @param idxVar The variable table index.
729
* @param fIgnoreCase Whether to ignore case when comparing.
730
* @param pcchMatched Where to return how much we actually matched up.
731
* @param pCache Pointer to the path matching cache.
733
DECL_NO_INLINE(static, int) rtPathMatchExecVariableFallback(const char *pchInput, size_t cchInput, uint16_t idxVar,
734
bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
736
for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
738
char szValue[RTPATH_MAX];
740
int rc = g_aVariables[idxVar].pfnQuery(iItem, szValue, sizeof(szValue), &cchValue, pCache);
743
if (cchValue <= cchInput)
746
? memcmp(pchInput, szValue, cchValue) == 0
747
: RTStrNICmp(pchInput, szValue, cchValue) == 0)
749
*pcchMatched = cchValue;
754
return VERR_MISMATCH;
756
else if (rc == VERR_EOF)
757
return VERR_MISMATCH;
759
Assert(rc == VERR_BUFFER_OVERFLOW || rc == VERR_TRY_AGAIN);
762
return VERR_MISMATCH;
767
* Variable matching worker.
769
* @returns VINF_SUCCESS or VERR_MISMATCH.
770
* @param pchInput The current input position.
771
* @param cchInput The amount of input left..
772
* @param idxVar The variable table index.
773
* @param fIgnoreCase Whether to ignore case when comparing.
774
* @param pcchMatched Where to return how much we actually matched up.
775
* @param pCache Pointer to the path matching cache.
777
static int rtPathMatchExecVariable(const char *pchInput, size_t cchInput, uint16_t idxVar,
778
bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
780
Assert(idxVar < RT_ELEMENTS(g_aVariables));
781
if (g_aVariables[idxVar].pfnMatch)
782
return g_aVariables[idxVar].pfnMatch(pchInput, cchInput, fIgnoreCase, pcchMatched);
783
return rtPathMatchExecVariableFallback(pchInput, cchInput, idxVar, fIgnoreCase, pcchMatched, pCache);
788
* Variable matching worker.
790
* @returns VINF_SUCCESS or VERR_MISMATCH.
791
* @param pchInput The current input position.
792
* @param cchInput The amount of input left..
793
* @param pProg The first matching program instruction.
794
* @param pCache Pointer to the path matching cache.
796
static int rtPathMatchExec(const char *pchInput, size_t cchInput, PCRTPATHMATCHCORE pProg, PRTPATHMATCHCACHE pCache)
800
switch (pProg->enmOpCode)
802
case RTPATHMATCHOP_RETURN_MATCH_IF_AT_END:
803
return cchInput == 0 ? VINF_SUCCESS : VERR_MISMATCH;
805
case RTPATHMATCHOP_RETURN_MATCH:
808
case RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT:
811
|| pchInput[0] != '.'
812
|| (cchInput == 2 && pchInput[1] != '.') )
814
return VERR_MISMATCH;
816
case RTPATHMATCHOP_STRCMP:
817
if (pProg->cch > cchInput)
818
return VERR_MISMATCH;
819
if (memcmp(pchInput, pProg->pch, pProg->cch) != 0)
820
return VERR_MISMATCH;
821
cchInput -= pProg->cch;
822
pchInput += pProg->cch;
825
case RTPATHMATCHOP_STRICMP:
826
if (pProg->cch > cchInput)
827
return VERR_MISMATCH;
828
if (RTStrNICmp(pchInput, pProg->pch, pProg->cch) != 0)
829
return VERR_MISMATCH;
830
cchInput -= pProg->cch;
831
pchInput += pProg->cch;
834
case RTPATHMATCHOP_SKIP_ONE_CODEPOINT:
837
return VERR_MISMATCH;
838
RTUNICP ucInputIgnore;
839
int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
840
AssertRCReturn(rc, rc);
844
case RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS:
846
uint16_t cCpsLeft = pProg->cch;
847
Assert(cCpsLeft > 1);
848
if (cCpsLeft > cchInput)
849
return VERR_MISMATCH;
850
while (cCpsLeft-- > 0)
852
RTUNICP ucInputIgnore;
853
int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
855
return rc == VERR_END_OF_STRING ? VERR_MISMATCH : rc;
860
case RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7:
863
return VERR_MISMATCH;
865
int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
866
AssertRCReturn(rc, rc);
868
return VERR_MISMATCH;
869
if (memchr(pProg->pch, (char)ucInput, pProg->cch) == NULL)
870
return VERR_MISMATCH;
874
case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7:
877
return VERR_MISMATCH;
879
int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
880
AssertRCReturn(rc, rc);
883
if (memchr(pProg->pch, (char)ucInput, pProg->cch) != NULL)
884
return VERR_MISMATCH;
888
case RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED:
891
return VERR_MISMATCH;
893
int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
894
AssertRCReturn(rc, rc);
895
rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
896
if (rc == VINF_SUCCESS)
901
case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED:
904
return VERR_MISMATCH;
906
int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
907
AssertRCReturn(rc, rc);
908
rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
909
if (rc == VERR_MISMATCH)
911
if (rc == VINF_SUCCESS)
916
case RTPATHMATCHOP_VARIABLE_VALUE_CMP:
917
case RTPATHMATCHOP_VARIABLE_VALUE_ICMP:
919
size_t cchMatched = 0;
920
int rc = rtPathMatchExecVariable(pchInput, cchInput, pProg->uOp2,
921
pProg->enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP, &cchMatched, pCache);
922
if (rc == VINF_SUCCESS)
924
pchInput += cchMatched;
925
cchInput -= cchMatched;
932
* This is the expensive one. It always completes the program.
934
case RTPATHMATCHOP_ZERO_OR_MORE:
936
if (cchInput < pProg->cch)
937
return VERR_MISMATCH;
938
size_t cchMatched = cchInput - pProg->cch;
941
int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
944
} while (cchMatched-- > 0);
945
return VERR_MISMATCH;
949
* Variant of the above that doesn't match '.' and '..' entries.
951
case RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT:
953
if (cchInput < pProg->cch)
954
return VERR_MISMATCH;
957
&& pchInput[0] == '.'
958
&& (cchInput == 1 || pchInput[1] == '.') )
959
return VERR_MISMATCH;
960
size_t cchMatched = cchInput - pProg->cch;
963
int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
966
} while (cchMatched-- > 0);
967
return VERR_MISMATCH;
971
AssertMsgFailedReturn(("enmOpCode=%d\n", pProg->enmOpCode), VERR_INTERNAL_ERROR_3);
982
* Compiles a path matching program.
984
* @returns IPRT status code.
985
* @param pchPattern The pattern to compile.
986
* @param cchPattern The length of the pattern.
987
* @param pAllocator Pointer to the instruction allocator & result
988
* array. The compiled "program" starts at
989
* PRTPATHMATCHALLOC::paInstructions[PRTPATHMATCHALLOC::iNext]
990
* (input iNext value).
992
* @todo Expose this matching code and also use it for RTDirOpenFiltered
994
static int rtPathMatchCompile(const char *pchPattern, size_t cchPattern, bool fIgnoreCase, PRTPATHMATCHALLOC pAllocator)
996
/** @todo PORTME: big endian. */
997
static const uint8_t s_bmMetaChars[256/8] =
999
0x00, 0x00, 0x00, 0x00, /* 0 thru 31 */
1000
0x10, 0x04, 0x00, 0x80, /* 32 thru 63 */
1001
0x00, 0x00, 0x00, 0x08, /* 64 thru 95 */
1002
0x00, 0x00, 0x00, 0x00, /* 96 thru 127 */
1003
/* UTF-8 multibyte: */
1004
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1006
Assert(ASMBitTest(s_bmMetaChars, '$')); AssertCompile('$' == 0x24 /*36*/);
1007
Assert(ASMBitTest(s_bmMetaChars, '*')); AssertCompile('*' == 0x2a /*42*/);
1008
Assert(ASMBitTest(s_bmMetaChars, '?')); AssertCompile('?' == 0x3f /*63*/);
1009
Assert(ASMBitTest(s_bmMetaChars, '[')); AssertCompile('[' == 0x5b /*91*/);
1012
* For checking for the first instruction.
1014
uint16_t const iFirst = pAllocator->iNext;
1017
* This is for tracking zero-or-more instructions and for calculating
1018
* the minimum amount of input required for it to be considered.
1020
uint16_t aiZeroOrMore[RTPATHMATCH_MAX_ZERO_OR_MORE];
1021
uint8_t cZeroOrMore = 0;
1022
size_t offInput = 0;
1025
* Loop thru the pattern and translate it into string matching instructions.
1030
* Allocate the next instruction.
1032
if (pAllocator->iNext >= pAllocator->cAllocated)
1034
uint32_t cNew = pAllocator->cAllocated ? pAllocator->cAllocated * 2 : 2;
1035
void *pvNew = RTMemRealloc(pAllocator->paInstructions, cNew * sizeof(pAllocator->paInstructions[0]));
1036
AssertReturn(pvNew, VERR_NO_MEMORY);
1037
pAllocator->paInstructions = (PRTPATHMATCHCORE)pvNew;
1038
pAllocator->cAllocated = cNew;
1040
PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[pAllocator->iNext++];
1041
pInstr->pch = pchPattern;
1046
* Special case: End of pattern.
1050
pInstr->enmOpCode = RTPATHMATCHOP_RETURN_MATCH_IF_AT_END;
1055
* Parse the next bit of the pattern.
1057
char ch = *pchPattern;
1058
if (ASMBitTest(s_bmMetaChars, (uint8_t)ch))
1061
* Zero or more characters wildcard.
1065
/* Skip extra asterisks. */
1070
} while (cchPattern > 0 && *pchPattern == '*');
1072
/* There is a special optimization for trailing '*'. */
1074
if (cchPattern == 0)
1076
pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
1077
? RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_RETURN_MATCH;
1081
pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
1082
? RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_ZERO_OR_MORE;
1083
pInstr->uOp2 = (uint16_t)offInput;
1084
AssertReturn(cZeroOrMore < RT_ELEMENTS(aiZeroOrMore), VERR_OUT_OF_RANGE);
1085
aiZeroOrMore[cZeroOrMore] = (uint16_t)(pInstr - pAllocator->paInstructions);
1087
/* cchInput unchanged, zero-or-more matches. */
1092
* Single character wildcard.
1096
/* Count them if more. */
1097
uint16_t cchQms = 1;
1098
while (cchQms < cchPattern && pchPattern[cchQms] == '?')
1101
pInstr->cch = cchQms;
1102
pInstr->enmOpCode = cchQms == 1 ? RTPATHMATCHOP_SKIP_ONE_CODEPOINT : RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS;
1104
cchPattern -= cchQms;
1105
pchPattern += cchQms;
1113
* Note that we skip the first char in the set as that is the only place
1114
* ']' can be placed if one desires to explicitly include it in the set.
1115
* To make life a bit more interesting, [:class:] is allowed inside the
1116
* set, so we have to do the counting game to find the end.
1121
&& (const char *)memchr(pchPattern + 2, ']', cchPattern) != NULL)
1124
/* Check for not-in. */
1125
bool fInverted = false;
1126
size_t offStart = 1;
1127
if (pchPattern[offStart] == '^')
1133
/* Special case for ']' as the first char, it doesn't indicate closing then. */
1134
size_t off = offStart;
1135
if (pchPattern[off] == ']')
1138
bool fExtended = false;
1139
while (off < cchPattern)
1141
ch = pchPattern[off++];
1144
if (off < cchPattern)
1146
char chOpen = pchPattern[off];
1152
const char *pchFound = (const char *)memchr(&pchPattern[off], ']', cchPattern - off);
1154
&& pchFound[-1] == chOpen)
1157
off = pchFound - pchPattern + 1;
1164
/* Check for closing. */
1167
/* Check for range expression, promote to extended if this happens. */
1169
&& off != offStart + 1
1171
&& pchPattern[off] != ']')
1173
/* UTF-8 multibyte chars forces us to use the extended version too. */
1174
else if ((uint8_t)ch >= 0x80)
1180
pInstr->pch = &pchPattern[offStart];
1181
pInstr->cch = (uint16_t)(off - offStart - 1);
1183
pInstr->enmOpCode = !fInverted
1184
? RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7 : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7;
1186
pInstr->enmOpCode = !fInverted
1187
? RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED
1188
: RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED;
1195
/* else: invalid, treat it as */
1200
* Variable matching.
1204
const char *pchFound;
1206
&& pchPattern[1] == '{'
1207
&& (pchFound = (const char *)memchr(pchPattern + 2, '}', cchPattern)) != NULL
1208
&& pchFound != &pchPattern[2])
1210
/* skip to the variable name. */
1213
size_t cchVarNm = pchFound - pchPattern;
1217
for (iVar = 0; iVar < RT_ELEMENTS(g_aVariables); iVar++)
1218
if ( g_aVariables[iVar].cchName == cchVarNm
1219
&& memcmp(g_aVariables[iVar].pszName, pchPattern, cchVarNm) == 0)
1221
if (iVar < RT_ELEMENTS(g_aVariables))
1223
pInstr->uOp2 = (uint16_t)iVar;
1224
pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_VARIABLE_VALUE_CMP : RTPATHMATCHOP_VARIABLE_VALUE_ICMP;
1225
pInstr->pch = pchPattern; /* not necessary */
1226
pInstr->cch = (uint16_t)cchPattern; /* ditto */
1227
pchPattern += cchVarNm + 1;
1228
cchPattern -= cchVarNm + 1;
1229
AssertMsgReturn(!g_aVariables[iVar].fFirstOnly || iFirst + 1U == pAllocator->iNext,
1230
("Glob variable '%s' should be first\n", g_aVariables[iVar].pszName),
1231
VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
1232
/* cchInput unchanged, value can be empty. */
1235
AssertMsgFailedReturn(("Unknown path matching variable '%.*s'\n", cchVarNm, pchPattern),
1236
VERR_PATH_MATCH_UNKNOWN_VARIABLE);
1240
AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
1244
* Plain text. Look for the next meta char.
1246
uint32_t cchPlain = 1;
1247
while (cchPlain < cchPattern)
1249
ch = pchPattern[cchPlain];
1250
if (!ASMBitTest(s_bmMetaChars, (uint8_t)ch))
1257
const char *pchFound;
1258
if ( cchPattern > cchPlain + 3
1259
&& pchPattern[cchPlain + 1] == '{'
1260
&& (pchFound = (const char *)memchr(&pchPattern[cchPlain + 2], '}', cchPattern - cchPlain - 2)) != NULL
1261
&& pchFound != &pchPattern[cchPlain + 2])
1266
/* We don't put a lot of effort into getting this 100% right here,
1267
no point it complicating things for malformed expressions. */
1268
if ( cchPattern > cchPlain + 2
1269
&& memchr(&pchPattern[cchPlain + 2], ']', cchPattern - cchPlain - 1) != NULL)
1273
AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
1276
pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_STRCMP : RTPATHMATCHOP_STRICMP;
1277
pInstr->cch = cchPlain;
1278
Assert(pInstr->pch == pchPattern);
1279
Assert(pInstr->uOp2 == 0);
1280
pchPattern += cchPlain;
1281
cchPattern -= cchPlain;
1282
offInput += cchPlain;
1286
* Optimize zero-or-more matching.
1288
while (cZeroOrMore-- > 0)
1290
PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[aiZeroOrMore[cZeroOrMore]];
1291
pInstr->uOp2 = (uint16_t)(offInput - pInstr->uOp2);
1294
/** @todo It's possible to use offInput to inject a instruction for checking
1295
* minimum input length at the start of the program. Not sure it's
1296
* worth it though, unless it's long a complicated expression... */
1297
return VINF_SUCCESS;
1302
* Parses the glob pattern.
1304
* This compiles filename matching programs for each component and determins the
1305
* optimal search strategy for them.
1307
* @returns IPRT status code.
1308
* @param pGlob The glob instance data.
1309
* @param pszPattern The pattern to parse.
1310
* @param pParsed The RTPathParse output for the pattern.
1311
* @param fFlags The glob flags (same as pGlob->fFlags).
1313
static int rtPathGlobParse(PRTPATHGLOB pGlob, const char *pszPattern, PRTPATHPARSED pParsed, uint32_t fFlags)
1315
AssertReturn(pParsed->cComps > 0, VERR_INVALID_PARAMETER); /* shouldn't happen */
1319
* If we've got a rootspec, mark it as plain. On platforms with
1320
* drive letter and/or UNC we don't allow wildcards or such in
1321
* the drive letter spec or UNC server name. (At least not yet.)
1323
if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
1325
AssertReturn(pParsed->aComps[0].cch < sizeof(pGlob->szPath) - 1, VERR_FILENAME_TOO_LONG);
1326
memcpy(pGlob->szPath, &pszPattern[pParsed->aComps[0].off], pParsed->aComps[0].cch);
1327
pGlob->offFirstPath = pParsed->aComps[0].cch;
1328
pGlob->iFirstComp = iComp = 1;
1332
const char * const pszComp = &pszPattern[pParsed->aComps[0].off];
1335
* The tilde is only applicable to the first component, expand it
1338
if ( *pszComp == '~'
1339
&& !(fFlags & RTPATHGLOB_F_NO_TILDE))
1341
if (pParsed->aComps[0].cch == 1)
1343
int rc = RTPathUserHome(pGlob->szPath, sizeof(pGlob->szPath) - 1);
1344
AssertRCReturn(rc, rc);
1347
AssertMsgFailedReturn(("'%.*s' is not supported yet\n", pszComp, pParsed->aComps[0].cch),
1348
VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
1349
pGlob->offFirstPath = (uint32_t)RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
1350
pGlob->iFirstComp = iComp = 1;
1355
* Process the other components.
1357
bool fStarStar = false;
1358
for (; iComp < pParsed->cComps; iComp++)
1360
const char *pszComp = &pszPattern[pParsed->aComps[iComp].off];
1361
uint16_t cchComp = pParsed->aComps[iComp].cch;
1362
Assert(pGlob->aComps[iComp].fNormal == false);
1364
pGlob->aComps[iComp].fDir = iComp + 1 < pParsed->cComps || (fFlags & RTPATHGLOB_F_ONLY_DIRS);
1366
|| pszComp[0] != '*'
1367
|| pszComp[1] != '*'
1368
|| (fFlags & RTPATHGLOB_F_NO_STARSTAR) )
1370
/* Compile the pattern. */
1371
uint16_t const iMatchProg = pGlob->MatchInstrAlloc.iNext;
1372
pGlob->aComps[iComp].iMatchProg = iMatchProg;
1373
int rc = rtPathMatchCompile(pszComp, cchComp, RT_BOOL(fFlags & RTPATHGLOB_F_IGNORE_CASE),
1374
&pGlob->MatchInstrAlloc);
1378
/* Check for plain text as well as full variable matching (not applicable after '**'). */
1379
uint16_t const cInstructions = pGlob->MatchInstrAlloc.iNext - iMatchProg;
1380
if ( cInstructions == 2
1382
&& pGlob->MatchInstrAlloc.paInstructions[iMatchProg + 1].enmOpCode == RTPATHMATCHOP_RETURN_MATCH_IF_AT_END)
1384
if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRCMP
1385
|| pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRICMP)
1386
pGlob->aComps[iComp].fPlain = true;
1387
else if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_CMP
1388
|| pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP)
1390
pGlob->aComps[iComp].fExpVariable = true;
1391
AssertMsgReturn( iComp == 0
1392
|| !g_aVariables[pGlob->MatchInstrAlloc.paInstructions[iMatchProg].uOp2].fFirstOnly,
1393
("Glob variable '%.*s' can only be used as the path component.\n", cchComp, pszComp),
1394
VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
1397
pGlob->aComps[iComp].fNormal = true;
1400
pGlob->aComps[iComp].fNormal = true;
1404
/* Recursive "**" matching. */
1405
pGlob->aComps[iComp].fNormal = false;
1406
pGlob->aComps[iComp].fStarStar = true;
1407
AssertReturn(!fStarStar, VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED); /** @todo implement multiple '**' sequences in a pattern. */
1411
pGlob->aComps[pParsed->cComps - 1].fFinal = true;
1413
return VINF_SUCCESS;
1418
* This is for skipping overly long directories entries.
1420
* Since our directory entry buffer can hold filenames of RTPATH_MAX bytes, we
1421
* can safely skip filenames that are longer. There are very few file systems
1422
* that can actually store filenames longer than 255 bytes at time of coding
1423
* (2015-09), and extremely few which can exceed 4096 (RTPATH_MAX) bytes.
1425
* @returns IPRT status code.
1426
* @param hDir The directory handle.
1427
* @param cbNeeded The required entry size.
1429
DECL_NO_INLINE(static, int) rtPathGlobSkipDirEntry(PRTDIR hDir, size_t cbNeeded)
1431
int rc = VERR_BUFFER_OVERFLOW;
1432
cbNeeded = RT_ALIGN_Z(cbNeeded, 16);
1433
PRTDIRENTRY pDirEntry = (PRTDIRENTRY)RTMemTmpAlloc(cbNeeded);
1436
rc = RTDirRead(hDir, pDirEntry, &cbNeeded);
1437
RTMemTmpFree(pDirEntry);
1446
* @returns IPRT status code.
1447
* @retval VINF_CALLBACK_RETURN if we can stop searching.
1449
* @param pGlob The glob instance data.
1450
* @param cchPath The number of bytes to add from pGlob->szPath.
1451
* @param uType The RTDIRENTRYTYPE value.
1453
DECL_NO_INLINE(static, int) rtPathGlobAddResult(PRTPATHGLOB pGlob, size_t cchPath, uint8_t uType)
1455
if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
1457
PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_OFFSETOF(RTPATHGLOBENTRY, szPath[cchPath + 1]));
1460
pEntry->uType = uType;
1461
pEntry->cchPath = (uint16_t)cchPath;
1462
memcpy(pEntry->szPath, pGlob->szPath, cchPath);
1463
pEntry->szPath[cchPath] = '\0';
1465
pEntry->pNext = NULL;
1466
*pGlob->ppNext = pEntry;
1467
pGlob->ppNext = &pEntry->pNext;
1470
if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
1471
return VINF_SUCCESS;
1472
return VINF_CALLBACK_RETURN;
1474
return VERR_NO_MEMORY;
1476
return VERR_TOO_MUCH_DATA;
1481
* Adds a result, constructing the path from two string.
1483
* @returns IPRT status code.
1484
* @retval VINF_CALLBACK_RETURN if we can stop searching.
1486
* @param pGlob The glob instance data.
1487
* @param cchPath The number of bytes to add from pGlob->szPath.
1488
* @param pchName The string (usual filename) to append to the szPath.
1489
* @param cchName The length of the string to append.
1490
* @param uType The RTDIRENTRYTYPE value.
1492
DECL_NO_INLINE(static, int) rtPathGlobAddResult2(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
1495
if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
1497
PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_OFFSETOF(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
1500
pEntry->uType = uType;
1501
pEntry->cchPath = (uint16_t)(cchPath + cchName);
1502
memcpy(pEntry->szPath, pGlob->szPath, cchPath);
1503
memcpy(&pEntry->szPath[cchPath], pchName, cchName);
1504
pEntry->szPath[cchPath + cchName] = '\0';
1506
pEntry->pNext = NULL;
1507
*pGlob->ppNext = pEntry;
1508
pGlob->ppNext = &pEntry->pNext;
1511
if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
1512
return VINF_SUCCESS;
1513
return VINF_CALLBACK_RETURN;
1515
return VERR_NO_MEMORY;
1517
return VERR_TOO_MUCH_DATA;
1522
* Prepares a result, constructing the path from two string.
1524
* The caller must call either rtPathGlobCommitResult or
1525
* rtPathGlobRollbackResult to complete the operation.
1527
* @returns IPRT status code.
1528
* @retval VINF_CALLBACK_RETURN if we can stop searching.
1530
* @param pGlob The glob instance data.
1531
* @param cchPath The number of bytes to add from pGlob->szPath.
1532
* @param pchName The string (usual filename) to append to the szPath.
1533
* @param cchName The length of the string to append.
1534
* @param uType The RTDIRENTRYTYPE value.
1536
DECL_NO_INLINE(static, int) rtPathGlobAlmostAddResult(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
1539
if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
1541
PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_OFFSETOF(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
1544
pEntry->uType = uType;
1545
pEntry->cchPath = (uint16_t)(cchPath + cchName);
1546
memcpy(pEntry->szPath, pGlob->szPath, cchPath);
1547
memcpy(&pEntry->szPath[cchPath], pchName, cchName);
1548
pEntry->szPath[cchPath + cchName] = '\0';
1550
pEntry->pNext = NULL;
1551
*pGlob->ppNext = pEntry;
1552
/* Note! We don't update ppNext here, that is done in rtPathGlobCommitResult. */
1554
if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
1555
return VINF_SUCCESS;
1556
return VINF_CALLBACK_RETURN;
1558
return VERR_NO_MEMORY;
1560
return VERR_TOO_MUCH_DATA;
1565
* Commits a pending result from rtPathGlobAlmostAddResult.
1567
* @param pGlob The glob instance data.
1568
* @param uType The RTDIRENTRYTYPE value.
1570
static void rtPathGlobCommitResult(PRTPATHGLOB pGlob, uint8_t uType)
1572
PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
1574
pEntry->uType = uType;
1575
pGlob->ppNext = &pEntry->pNext;
1581
* Rolls back a pending result from rtPathGlobAlmostAddResult.
1583
* @param pGlob The glob instance data.
1585
static void rtPathGlobRollbackResult(PRTPATHGLOB pGlob)
1587
PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
1590
*pGlob->ppNext = NULL;
1596
* Whether to call rtPathGlobExecRecursiveVarExp for the next component.
1598
* @returns true / false.
1599
* @param pGlob The glob instance data.
1600
* @param offPath The next path offset/length.
1601
* @param iComp The next component.
1603
DECLINLINE(bool) rtPathGlobExecIsExpVar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1605
return pGlob->aComps[iComp].fExpVariable
1606
&& ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
1607
|| (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
1611
* Whether to call rtPathGlobExecRecursivePlainText for the next component.
1613
* @returns true / false.
1614
* @param pGlob The glob instance data.
1615
* @param offPath The next path offset/length.
1616
* @param iComp The next component.
1618
DECLINLINE(bool) rtPathGlobExecIsPlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1620
return pGlob->aComps[iComp].fPlain
1621
&& ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
1622
|| (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
1627
* Helper for rtPathGlobExecRecursiveVarExp and rtPathGlobExecRecursivePlainText
1628
* that compares a file mode mask with dir/no-dir wishes of the caller.
1630
* @returns true if match, false if not.
1631
* @param pGlob The glob instance data.
1632
* @param fMode The file mode (only the type is used).
1634
DECLINLINE(bool) rtPathGlobExecIsMatchFinalWithFileMode(PRTPATHGLOB pGlob, RTFMODE fMode)
1636
if (!(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)))
1638
return RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS) == RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode);
1643
* Recursive globbing - star-star mode.
1645
* @returns IPRT status code.
1646
* @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1648
* @param pGlob The glob instance data.
1649
* @param offPath The current path offset/length.
1650
* @param iComp The current component.
1652
DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp,
1653
size_t offStarStarPath)
1655
/** @todo implement multi subdir matching. */
1656
return VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED;
1662
* Recursive globbing - variable expansion optimization.
1664
* @returns IPRT status code.
1665
* @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1667
* @param pGlob The glob instance data.
1668
* @param offPath The current path offset/length.
1669
* @param iComp The current component.
1671
DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1673
Assert(iComp < pGlob->pParsed->cComps);
1674
Assert(pGlob->szPath[offPath] == '\0');
1675
Assert(pGlob->aComps[iComp].fExpVariable);
1676
Assert(!pGlob->aComps[iComp].fPlain);
1677
Assert(!pGlob->aComps[iComp].fStarStar);
1678
Assert(rtPathGlobExecIsExpVar(pGlob, offPath, iComp));
1681
* Fish the variable index out of the first matching instruction.
1683
Assert( pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1684
== RTPATHMATCHOP_VARIABLE_VALUE_CMP
1685
|| pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1686
== RTPATHMATCHOP_VARIABLE_VALUE_ICMP);
1687
uint16_t const iVar = pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].uOp2;
1690
* Enumerate all the variable, giving them the plain text treatment.
1692
for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
1695
int rcVar = g_aVariables[iVar].pfnQuery(iItem, &pGlob->szPath[offPath], sizeof(pGlob->szPath) - offPath, &cch,
1696
&pGlob->MatchCache);
1697
if (RT_SUCCESS(rcVar))
1699
Assert(pGlob->szPath[offPath + cch] == '\0');
1701
int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1704
if (pGlob->aComps[iComp].fFinal)
1706
if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
1708
rc = rtPathGlobAddResult(pGlob, cch,
1709
(pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
1710
>> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
1711
if (rc != VINF_SUCCESS)
1715
else if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
1717
Assert(pGlob->aComps[iComp].fDir);
1718
cch = RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
1721
if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
1722
rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
1723
else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
1724
rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
1725
else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1726
rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
1728
rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
1729
if (rc != VINF_SUCCESS)
1733
pGlob->cPathOverflows++;
1736
/* else: file doesn't exist or something else is wrong, ignore this. */
1737
if (rcVar == VINF_EOF)
1738
return VINF_SUCCESS;
1740
else if (rcVar == VERR_EOF)
1741
return VINF_SUCCESS;
1742
else if (rcVar != VERR_TRY_AGAIN)
1744
Assert(rcVar == VERR_BUFFER_OVERFLOW);
1745
pGlob->cPathOverflows++;
1748
AssertFailedReturn(VINF_SUCCESS); /* Too many items returned, probably buggy query method. */
1753
* Recursive globbing - plain text optimization.
1755
* @returns IPRT status code.
1756
* @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1758
* @param pGlob The glob instance data.
1759
* @param offPath The current path offset/length.
1760
* @param iComp The current component.
1762
DECL_NO_INLINE(static, int) rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1765
* Instead of recursing, we loop thru adjacent plain text components.
1772
Assert(iComp < pGlob->pParsed->cComps);
1773
Assert(pGlob->szPath[offPath] == '\0');
1774
Assert(pGlob->aComps[iComp].fPlain);
1775
Assert(!pGlob->aComps[iComp].fExpVariable);
1776
Assert(!pGlob->aComps[iComp].fStarStar);
1777
Assert(rtPathGlobExecIsPlainText(pGlob, offPath, iComp));
1778
Assert(pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1779
== RTPATHMATCHOP_STRCMP
1780
|| pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1781
== RTPATHMATCHOP_STRICMP);
1784
* Add the plain text component to the path.
1786
size_t const cch = pGlob->pParsed->aComps[iComp].cch;
1787
if (cch + pGlob->aComps[iComp].fDir < sizeof(pGlob->szPath) - offPath)
1789
memcpy(&pGlob->szPath[offPath], &pGlob->pszPattern[pGlob->pParsed->aComps[iComp].off], cch);
1791
pGlob->szPath[offPath] = '\0';
1794
* Check if it exists.
1796
int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1799
if (pGlob->aComps[iComp].fFinal)
1801
if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
1802
return rtPathGlobAddResult(pGlob, offPath,
1803
(pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
1804
>> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
1808
if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
1810
Assert(pGlob->aComps[iComp].fDir);
1811
pGlob->szPath[offPath++] = RTPATH_SLASH;
1812
pGlob->szPath[offPath] = '\0';
1815
if (rtPathGlobExecIsExpVar(pGlob, offPath, iComp))
1816
return rtPathGlobExecRecursiveVarExp(pGlob, offPath, iComp);
1817
if (!rtPathGlobExecIsPlainText(pGlob, offPath, iComp))
1818
return rtPathGlobExecRecursiveGeneric(pGlob, offPath, iComp);
1819
if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1820
return rtPathGlobExecRecursiveStarStar(pGlob, offPath, iComp, offPath);
1822
/* Continue with the next plain text component. */
1826
/* else: file doesn't exist or something else is wrong, ignore this. */
1829
pGlob->cPathOverflows++;
1832
return VINF_SUCCESS;
1837
* Recursive globbing - generic.
1839
* @returns IPRT status code.
1840
* @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1842
* @param pGlob The glob instance data.
1843
* @param offPath The current path offset/length.
1844
* @param iComp The current component.
1846
DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1849
* Enumerate entire directory and match each entry.
1852
int rc = RTDirOpen(&hDir, offPath ? pGlob->szPath : ".");
1857
size_t cch = sizeof(pGlob->u);
1858
rc = RTDirRead(hDir, &pGlob->u.DirEntry, &cch);
1861
if (pGlob->aComps[iComp].fFinal)
1864
* Final component: Check if it matches the current pattern.
1866
if ( !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS))
1867
|| RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
1868
== (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
1869
|| pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
1871
rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1872
&pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
1873
&pGlob->MatchCache);
1876
/* Construct the result. */
1877
if ( pGlob->u.DirEntry.enmType != RTDIRENTRYTYPE_UNKNOWN
1878
|| !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) )
1879
rc = rtPathGlobAddResult2(pGlob, offPath, pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1880
(uint8_t)pGlob->u.DirEntry.enmType);
1883
rc = rtPathGlobAlmostAddResult(pGlob, offPath,
1884
pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1885
(uint8_t)RTDIRENTRYTYPE_UNKNOWN);
1888
RTDirQueryUnknownType((*pGlob->ppNext)->szPath, false /*fFollowSymlinks*/,
1889
&pGlob->u.DirEntry.enmType);
1890
if ( RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
1891
== (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY))
1892
rtPathGlobCommitResult(pGlob, (uint8_t)pGlob->u.DirEntry.enmType);
1894
rtPathGlobRollbackResult(pGlob);
1897
if (rc != VINF_SUCCESS)
1902
AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
1908
* Intermediate component: Directories only.
1910
else if ( pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY
1911
|| pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
1913
rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1914
&pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
1915
&pGlob->MatchCache);
1918
/* Recurse down into the alleged directory. */
1919
cch = offPath + pGlob->u.DirEntry.cbName;
1920
if (cch + 1 < sizeof(pGlob->szPath))
1922
memcpy(&pGlob->szPath[offPath], pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName);
1923
pGlob->szPath[cch++] = RTPATH_SLASH;
1924
pGlob->szPath[cch] = '\0';
1926
if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
1927
rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
1928
else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
1929
rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
1930
else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1931
rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
1933
rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
1934
if (rc != VINF_SUCCESS)
1938
pGlob->cPathOverflows++;
1942
AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
1948
* RTDirRead failure.
1953
if (rc == VERR_NO_MORE_FILES)
1955
/* Try skip the entry if we end up with an overflow (szPath can't hold it either then). */
1956
else if (rc == VERR_BUFFER_OVERFLOW)
1958
pGlob->cPathOverflows++;
1959
rc = rtPathGlobSkipDirEntry(hDir, cch);
1963
/* else: Any other error is unexpected and should be reported. */
1970
/* Directory doesn't exist or something else is wrong, ignore this. */
1978
* Executes a glob search.
1980
* @returns IPRT status code.
1981
* @param pGlob The glob instance data.
1983
static int rtPathGlobExec(PRTPATHGLOB pGlob)
1985
Assert(pGlob->offFirstPath < sizeof(pGlob->szPath));
1986
Assert(pGlob->szPath[pGlob->offFirstPath] == '\0');
1989
if (RT_LIKELY(pGlob->iFirstComp < pGlob->pParsed->cComps))
1992
* Call the appropriate function.
1994
if (rtPathGlobExecIsExpVar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
1995
rc = rtPathGlobExecRecursiveVarExp(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
1996
else if (rtPathGlobExecIsPlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
1997
rc = rtPathGlobExecRecursivePlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
1998
else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1999
rc = rtPathGlobExecRecursiveStarStar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp, pGlob->offFirstPath);
2001
rc = rtPathGlobExecRecursiveGeneric(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
2006
* Special case where we only have a root component or tilde expansion.
2008
Assert(pGlob->offFirstPath > 0);
2009
rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
2011
&& rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
2012
rc = rtPathGlobAddResult(pGlob, pGlob->offFirstPath,
2013
(pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK) >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
2019
* Adjust the status code. Check for results, hide RTPATHGLOB_F_FIRST_ONLY
2020
* status code, and add warning if necessary.
2022
if (pGlob->cResults > 0)
2024
if (rc == VINF_CALLBACK_RETURN)
2026
if (rc == VINF_SUCCESS)
2028
if (pGlob->cPathOverflows > 0)
2029
rc = VINF_BUFFER_OVERFLOW;
2033
rc = VERR_FILE_NOT_FOUND;
2039
RTDECL(int) RTPathGlob(const char *pszPattern, uint32_t fFlags, PPCRTPATHGLOBENTRY ppHead, uint32_t *pcResults)
2044
AssertPtrReturn(ppHead, VERR_INVALID_POINTER);
2048
AssertPtrReturn(pcResults, VERR_INVALID_POINTER);
2051
AssertPtrReturn(pszPattern, VERR_INVALID_POINTER);
2052
AssertReturn(!(fFlags & ~RTPATHGLOB_F_MASK), VERR_INVALID_FLAGS);
2053
AssertReturn((fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) != (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS),
2054
VERR_INVALID_FLAGS);
2059
size_t cbParsed = RT_OFFSETOF(RTPATHPARSED, aComps[1]); /** @todo 16 after testing */
2060
PRTPATHPARSED pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
2061
AssertReturn(pParsed, VERR_NO_MEMORY);
2062
int rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
2063
if (rc == VERR_BUFFER_OVERFLOW)
2065
cbParsed = RT_OFFSETOF(RTPATHPARSED, aComps[pParsed->cComps + 1]);
2066
RTMemTmpFree(pParsed);
2067
pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
2068
AssertReturn(pParsed, VERR_NO_MEMORY);
2070
rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
2075
* Check dir slash vs. only/not dir flag.
2077
if ( !(fFlags & RTPATHGLOB_F_NO_DIRS)
2078
|| ( !(pParsed->fProps & RTPATH_PROP_DIR_SLASH)
2079
&& ( !(pParsed->fProps & (RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_UNC))
2080
|| pParsed->cComps > 1) ) )
2082
if (pParsed->fProps & RTPATH_PROP_DIR_SLASH)
2083
fFlags |= RTPATHGLOB_F_ONLY_DIRS;
2086
* Allocate and initialize the glob state data structure.
2088
size_t cbGlob = RT_OFFSETOF(RTPATHGLOB, aComps[pParsed->cComps + 1]);
2089
PRTPATHGLOB pGlob = (PRTPATHGLOB)RTMemTmpAllocZ(cbGlob);
2092
pGlob->pszPattern = pszPattern;
2093
pGlob->fFlags = fFlags;
2094
pGlob->pParsed = pParsed;
2095
pGlob->ppNext = &pGlob->pHead;
2096
rc = rtPathGlobParse(pGlob, pszPattern, pParsed, fFlags);
2100
* Execute the search.
2102
rc = rtPathGlobExec(pGlob);
2105
*ppHead = pGlob->pHead;
2107
*pcResults = pGlob->cResults;
2110
RTPathGlobFree(pGlob->pHead);
2113
RTMemTmpFree(pGlob->MatchInstrAlloc.paInstructions);
2114
RTMemTmpFree(pGlob);
2117
rc = VERR_NO_MEMORY;
2120
rc = VERR_NOT_FOUND;
2122
RTMemTmpFree(pParsed);
2129
RTDECL(void) RTPathGlobFree(PCRTPATHGLOBENTRY pHead)
2131
PRTPATHGLOBENTRY pCur = (PRTPATHGLOBENTRY)pHead;
2134
PRTPATHGLOBENTRY pNext = pCur->pNext;