~ubuntu-branches/ubuntu/trusty/virtualbox-lts-xenial/trusty-proposed

« back to all changes in this revision

Viewing changes to src/VBox/Runtime/common/path/RTPathGlob.cpp

  • Committer: Package Import Robot
  • Author(s): Gianfranco Costamagna
  • Date: 2016-02-23 14:28:26 UTC
  • Revision ID: package-import@ubuntu.com-20160223142826-bdu69el2z6wa2a44
Tags: upstream-4.3.36-dfsg
ImportĀ upstreamĀ versionĀ 4.3.36-dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* $Id: RTPathGlob.cpp $ */
 
2
/** @file
 
3
 * IPRT - RTPathGlob
 
4
 */
 
5
 
 
6
/*
 
7
 * Copyright (C) 2006-2015 Oracle Corporation
 
8
 *
 
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.
 
16
 *
 
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.
 
22
 *
 
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.
 
25
 */
 
26
 
 
27
 
 
28
/*********************************************************************************************************************************
 
29
*   Header Files                                                                                                                 *
 
30
*********************************************************************************************************************************/
 
31
#include "internal/iprt.h"
 
32
#include <iprt/path.h>
 
33
 
 
34
#include <iprt/asm.h>
 
35
#include <iprt/assert.h>
 
36
#include <iprt/buildconfig.h>
 
37
#include <iprt/ctype.h>
 
38
#include <iprt/dir.h>
 
39
#include <iprt/env.h>
 
40
#include <iprt/err.h>
 
41
#include <iprt/mem.h>
 
42
#include <iprt/string.h>
 
43
#include <iprt/uni.h>
 
44
 
 
45
#if defined(RT_OS_WINDOWS)
 
46
# include <Windows.h>
 
47
 
 
48
#elif defined(RT_OS_OS2)
 
49
# define INCL_BASE
 
50
# include <os2.h>
 
51
# undef RT_MAX /* collision */
 
52
 
 
53
#endif
 
54
 
 
55
 
 
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
 
66
 
 
67
 
 
68
 
 
69
/*********************************************************************************************************************************
 
70
*   Structures and Typedefs                                                                                                      *
 
71
*********************************************************************************************************************************/
 
72
/**
 
73
 * Matching operation.
 
74
 */
 
75
typedef enum RTPATHMATCHOP
 
76
{
 
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. */
 
85
    RTPATHMATCHOP_STRCMP,
 
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.   */
 
117
    RTPATHMATCHOP_END
 
118
} RTPATHMATCHOP;
 
119
 
 
120
/**
 
121
 * Matching instruction.
 
122
 */
 
123
typedef struct RTPATHMATCHCORE
 
124
{
 
125
    /** The action to take. */
 
126
    RTPATHMATCHOP       enmOpCode;
 
127
    /** Generic value operand. */
 
128
    uint16_t            uOp2;
 
129
    /** Generic length operand. */
 
130
    uint16_t            cch;
 
131
    /** Generic string pointer operand. */
 
132
    const char         *pch;
 
133
} RTPATHMATCHCORE;
 
134
/** Pointer to a matching instruction. */
 
135
typedef RTPATHMATCHCORE *PRTPATHMATCHCORE;
 
136
/** Pointer to a const matching instruction. */
 
137
typedef RTPATHMATCHCORE const *PCRTPATHMATCHCORE;
 
138
 
 
139
/**
 
140
 * Path matching instruction allocator.
 
141
 */
 
142
typedef struct RTPATHMATCHALLOC
 
143
{
 
144
    /** Allocated array of instructions. */
 
145
    PRTPATHMATCHCORE    paInstructions;
 
146
    /** Index of the next free entry in paScratch. */
 
147
    uint32_t            iNext;
 
148
    /** Number of instructions allocated. */
 
149
    uint32_t            cAllocated;
 
150
} RTPATHMATCHALLOC;
 
151
/** Pointer to a matching instruction allocator. */
 
152
typedef RTPATHMATCHALLOC *PRTPATHMATCHALLOC;
 
153
 
 
154
/**
 
155
 * Path matching cache, mainly intended for variables like the PATH.
 
156
 */
 
157
typedef struct RTPATHMATCHCACHE
 
158
{
 
159
    /** @todo optimize later. */
 
160
    uint32_t            iNothingYet;
 
161
} RTPATHMATCHCACHE;
 
162
/** Pointer to a path matching cache. */
 
163
typedef RTPATHMATCHCACHE *PRTPATHMATCHCACHE;
 
164
 
 
165
 
 
166
 
 
167
/** Parsed path entry.*/
 
168
typedef struct RTPATHGLOBPPE
 
169
{
 
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). */
 
178
    uint32_t            fPlain : 1;
 
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;
 
183
 
 
184
    /** Filter: Set if it only matches directories. */
 
185
    uint32_t            fDir : 1;
 
186
    /** Set if it's the final component. */
 
187
    uint32_t            fFinal : 1;
 
188
 
 
189
    /** Unused bits. */
 
190
    uint32_t            fReserved : 2+8;
 
191
} RTPATHGLOBPPE;
 
192
 
 
193
 
 
194
typedef struct RTPATHGLOB
 
195
{
 
196
    /** Path buffer. */
 
197
    char                szPath[RTPATH_MAX];
 
198
    /** Temporary buffers. */
 
199
    union
 
200
    {
 
201
        /** File system object info structure. */
 
202
        RTFSOBJINFO     ObjInfo;
 
203
        /** Directory entry buffer. */
 
204
        RTDIRENTRY      DirEntry;
 
205
        /** Padding the buffer to an unreasonably large size. */
 
206
        uint8_t         abPadding[RTPATH_MAX + sizeof(RTDIRENTRY)];
 
207
    } u;
 
208
 
 
209
 
 
210
    /** Where to insert the next one.*/
 
211
    PRTPATHGLOBENTRY   *ppNext;
 
212
    /** The head pointer. */
 
213
    PRTPATHGLOBENTRY    pHead;
 
214
    /** Result count. */
 
215
    uint32_t            cResults;
 
216
    /** Counts path overflows. */
 
217
    uint32_t            cPathOverflows;
 
218
    /** The input flags. */
 
219
    uint32_t            fFlags;
 
220
    /** Matching instruction allocator. */
 
221
    RTPATHMATCHALLOC    MatchInstrAlloc;
 
222
    /** Matching state. */
 
223
    RTPATHMATCHCACHE    MatchCache;
 
224
 
 
225
    /** The pattern string.   */
 
226
    const char         *pszPattern;
 
227
    /** The parsed path.   */
 
228
    PRTPATHPARSED       pParsed;
 
229
    /** The component to start with. */
 
230
    uint16_t            iFirstComp;
 
231
    /** The corresponding path offset (previous components already present). */
 
232
    uint16_t            offFirstPath;
 
233
    /** Path component information we need. */
 
234
    RTPATHGLOBPPE       aComps[1];
 
235
} RTPATHGLOB;
 
236
typedef RTPATHGLOB *PRTPATHGLOB;
 
237
 
 
238
 
 
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);
 
246
 
 
247
 
 
248
/**
 
249
 * Implements the two variable access functions for a simple one value variable.
 
250
 */
 
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) \
 
254
    { \
 
255
        if (iItem == 0) \
 
256
        { \
 
257
            const char *pszValue = a_GetStrExpr; \
 
258
            size_t      cchValue = strlen(pszValue); \
 
259
            if (cchValue + 1 <= cbBuf) \
 
260
            { \
 
261
                memcpy(pszBuf, pszValue, cchValue + 1); \
 
262
                *pcchValue = cchValue; \
 
263
                return VINF_EOF; \
 
264
            } \
 
265
            return VERR_BUFFER_OVERFLOW; \
 
266
        } \
 
267
        NOREF(pCache);\
 
268
        return VERR_EOF; \
 
269
    } \
 
270
    static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
 
271
                                                               size_t *pcchMatched) \
 
272
    { \
 
273
        const char *pszValue = a_GetStrExpr; \
 
274
        size_t      cchValue = strlen(pszValue); \
 
275
        if (   cchValue >= cchMatch \
 
276
            && (  !fIgnoreCase \
 
277
                ? memcmp(pszValue, pchMatch, cchValue) == 0 \
 
278
                : RTStrNICmp(pszValue, pchMatch, cchValue) == 0) ) \
 
279
        { \
 
280
            *pcchMatched = cchValue; \
 
281
            return VINF_SUCCESS; \
 
282
        } \
 
283
        return VERR_MISMATCH; \
 
284
    } \
 
285
    typedef int RT_CONCAT(DummyColonType_,a_Name)
 
286
 
 
287
/**
 
288
 * Implements mapping a glob variable to an environment variable.
 
289
 */
 
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) \
 
293
    { \
 
294
        if (iItem == 0) \
 
295
        { \
 
296
            int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, pszBuf, cbBuf, pcchValue); \
 
297
            if (RT_SUCCESS(rc)) \
 
298
                return VINF_EOF; \
 
299
            if (rc != VERR_ENV_VAR_NOT_FOUND) \
 
300
                return rc; \
 
301
        } \
 
302
        NOREF(pCache);\
 
303
        return VERR_EOF; \
 
304
    } \
 
305
    static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
 
306
                                                               size_t *pcchMatched) \
 
307
    { \
 
308
        char   szValue[a_cbMaxValue]; \
 
309
        size_t cchValue; \
 
310
        int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, szValue, sizeof(szValue), &cchValue); \
 
311
        if (   RT_SUCCESS(rc) \
 
312
            && cchValue >= cchMatch \
 
313
            && (  !fIgnoreCase \
 
314
                ? memcmp(szValue, pchMatch, cchValue) == 0 \
 
315
                : RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
 
316
        { \
 
317
            *pcchMatched = cchValue; \
 
318
            return VINF_SUCCESS; \
 
319
        } \
 
320
        return VERR_MISMATCH; \
 
321
    } \
 
322
    typedef int RT_CONCAT(DummyColonType_,a_Name)
 
323
 
 
324
/**
 
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.
 
328
 */
 
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) \
 
332
    { \
 
333
        if (iItem < RT_ELEMENTS(a_apszVarNames)) \
 
334
        { \
 
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; \
 
340
            return rc; \
 
341
        } \
 
342
        NOREF(pCache);\
 
343
        return VERR_EOF; \
 
344
    } \
 
345
    static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
 
346
                                                               size_t *pcchMatched) \
 
347
    { \
 
348
        for (uint32_t iItem = 0; iItem < RT_ELEMENTS(a_apszVarNames); iItem++) \
 
349
        { \
 
350
            char   szValue[a_cbMaxValue]; \
 
351
            size_t cchValue; \
 
352
            int rc = RTEnvGetEx(RTENV_DEFAULT, a_apszVarNames[iItem], szValue, sizeof(szValue), &cchValue);\
 
353
            if (   RT_SUCCESS(rc) \
 
354
                && cchValue >= cchMatch \
 
355
                && (  !fIgnoreCase \
 
356
                    ? memcmp(szValue, pchMatch, cchValue) == 0 \
 
357
                    : RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
 
358
            { \
 
359
                *pcchMatched = cchValue; \
 
360
                return VINF_SUCCESS; \
 
361
            } \
 
362
        } \
 
363
        return VERR_MISMATCH; \
 
364
    } \
 
365
    typedef int RT_CONCAT(DummyColonType_,a_Name)
 
366
 
 
367
 
 
368
RTPATHMATCHVAR_SIMPLE(Arch, RTBldCfgTargetArch());
 
369
RTPATHMATCHVAR_SIMPLE(Bits, RT_XSTR(ARCH_BITS));
 
370
#ifdef RT_OS_WINDOWS
 
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);
 
378
# else
 
379
#  error "Port ME!"
 
380
# endif
 
381
static const char * const a_apszWinProgramFilesVars[] =
 
382
{
 
383
    "ProgramFiles",
 
384
# ifdef RT_ARCH_AMD64
 
385
    "ProgramFiles(x86)",
 
386
# endif
 
387
};
 
388
RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllProgramFiles, a_apszWinProgramFilesVars, RTPATH_MAX);
 
389
static const char * const a_apszWinCommonProgramFilesVars[] =
 
390
{
 
391
    "CommonProgramFiles",
 
392
# ifdef RT_ARCH_AMD64
 
393
    "CommonProgramFiles(x86)",
 
394
# endif
 
395
};
 
396
RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllCommonProgramFiles, a_apszWinCommonProgramFilesVars, RTPATH_MAX);
 
397
#endif
 
398
 
 
399
 
 
400
/**
 
401
 * @interface_method_impl{RTPATHMATCHVAR::pfnQuery, Enumerates the PATH}.
 
402
 */
 
403
static DECLCALLBACK(int) rtPathVarQuery_Path(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
 
404
                                             PRTPATHMATCHCACHE pCache)
 
405
{
 
406
    /*
 
407
     * Query the PATH value.
 
408
     */
 
409
/** @todo cache this in pCache with iItem and offset.   */
 
410
    char       *pszPathFree = NULL;
 
411
    char       *pszPath     = pszBuf;
 
412
    size_t      cchActual;
 
413
    const char *pszVarNm    = "PATH";
 
414
    int rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPath, cbBuf, &cchActual);
 
415
#ifdef RT_OS_WINDOWS
 
416
    if (rc == VERR_ENV_VAR_NOT_FOUND)
 
417
        rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm = "Path", pszPath, cbBuf, &cchActual);
 
418
#endif
 
419
    if (rc == VERR_BUFFER_OVERFLOW)
 
420
    {
 
421
        for (uint32_t iTry = 0; iTry < 10; iTry++)
 
422
        {
 
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);
 
426
            if (RT_SUCCESS(rc))
 
427
                break;
 
428
            RTMemTmpFree(pszPathFree);
 
429
            AssertReturn(cchActual >= cbPathBuf, VERR_INTERNAL_ERROR_3);
 
430
        }
 
431
        pszPath = pszPathFree;
 
432
    }
 
433
 
 
434
    /*
 
435
     * Spool forward to the given PATH item.
 
436
     */
 
437
    rc = VERR_EOF;
 
438
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
 
439
    const char  chSep = ';';
 
440
#else
 
441
    const char  chSep = ':';
 
442
#endif
 
443
    while (*pszPath != '\0')
 
444
    {
 
445
        char *pchSep = strchr(pszPath, chSep);
 
446
 
 
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
 
451
           in the PATH. */
 
452
        if (pchSep == pszPath)
 
453
            pszPath++;
 
454
        else if (iItem > 0)
 
455
        {
 
456
            /* If we didn't find a separator, the item doesn't exists. Quit. */
 
457
            if (!pchSep)
 
458
                break;
 
459
 
 
460
            pszPath = pchSep + 1;
 
461
            iItem--;
 
462
        }
 
463
        else
 
464
        {
 
465
            /* We've reached the item we wanted. */
 
466
            size_t cchComp = pchSep ? pchSep - pszPath : strlen(pszPath);
 
467
            if (cchComp < cbBuf)
 
468
            {
 
469
                if (pszBuf != pszPath)
 
470
                    memmove(pszBuf, pszPath, cchComp);
 
471
                pszBuf[cchComp] = '\0';
 
472
                rc = pchSep ? VINF_SUCCESS : VINF_EOF;
 
473
            }
 
474
            else
 
475
                rc = VERR_BUFFER_OVERFLOW;
 
476
            *pcchValue = cchComp;
 
477
            break;
 
478
        }
 
479
    }
 
480
 
 
481
    if (pszPathFree)
 
482
        RTMemTmpFree(pszPathFree);
 
483
    return rc;
 
484
}
 
485
 
 
486
 
 
487
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
 
488
/**
 
489
 * @interface_method_impl{RTPATHMATCHVAR::pfnQuery,
 
490
 *      The system drive letter + colon.}.
 
491
 */
 
492
static DECLCALLBACK(int) rtPathVarQuery_DosSystemDrive(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
 
493
                                                       PRTPATHMATCHCACHE pCache)
 
494
{
 
495
    if (iItem == 0)
 
496
    {
 
497
        AssertReturn(cbBuf >= 3, VERR_BUFFER_OVERFLOW);
 
498
 
 
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)));
 
504
        if (cch >= 2)
 
505
        {
 
506
            RTUTF16 wcDrive = pwszTmp[0];
 
507
            if (   RT_C_IS_ALPHA(wcDrive)
 
508
                && pwszTmp[1] == ':')
 
509
            {
 
510
                pszBuf[0] = wcDrive;
 
511
                pszBuf[1] = ':';
 
512
                pszBuf[2] = '\0';
 
513
                *pcchValue = 2;
 
514
                return VINF_EOF;
 
515
            }
 
516
        }
 
517
# else
 
518
        ULONG ulDrive = ~(ULONG)0;
 
519
        APIRET rc = DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, &ulDrive, sizeof(ulDrive));
 
520
        ulDrive--; /* 1 = 'A' */
 
521
        if (   rc == NO_ERROR
 
522
            && ulDrive <= (ULONG)'Z')
 
523
        {
 
524
            pszBuf[0] = (char)ulDrive + 'A';
 
525
            pszBuf[1] = ':';
 
526
            pszBuf[2] = '\0';
 
527
            *pcchValue = 2;
 
528
            return VINF_EOF;
 
529
        }
 
530
# endif
 
531
        return VERR_INTERNAL_ERROR_4;
 
532
    }
 
533
    return VERR_EOF;
 
534
}
 
535
#endif
 
536
 
 
537
 
 
538
#ifdef RT_OS_WINDOWS
 
539
/**
 
540
 * @interface_method_impl{RTPATHMATCHVAR::pfnQuery,
 
541
 *      The system root directory (C:\Windows).}.
 
542
 */
 
543
static DECLCALLBACK(int) rtPathVarQuery_WinSystemRoot(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
 
544
                                                      PRTPATHMATCHCACHE pCache)
 
545
{
 
546
    if (iItem == 0)
 
547
    {
 
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());
 
554
    }
 
555
    return VERR_EOF;
 
556
}
 
557
#endif
 
558
 
 
559
#undef RTPATHMATCHVAR_SIMPLE
 
560
#undef RTPATHMATCHVAR_SIMPLE_ENVVAR
 
561
#undef RTPATHMATCHVAR_DOUBLE_ENVVAR
 
562
 
 
563
/**
 
564
 * Matching variable lookup table.
 
565
 * Currently so small we don't bother sorting it and doing binary lookups.
 
566
 */
 
567
static struct RTPATHMATCHVAR
 
568
{
 
569
    /** The variable name. */
 
570
    const char     *pszName;
 
571
    /** The variable name length. */
 
572
    uint16_t        cchName;
 
573
    /** Only available as the verify first component.  */
 
574
    bool            fFirstOnly;
 
575
 
 
576
    /**
 
577
     * Queries a given variable value.
 
578
     *
 
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.
 
585
     *
 
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.
 
594
     */
 
595
    DECLCALLBACKMEMBER(int, pfnQuery)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, PRTPATHMATCHCACHE pCache);
 
596
 
 
597
    /**
 
598
     * Matching method, optional.
 
599
     *
 
600
     * @returns IPRT status code.
 
601
     * @retval  VINF_SUCCESS on match.
 
602
     * @retval  VERR_MISMATCH on mismatch.
 
603
     *
 
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).
 
608
     */
 
609
    DECLCALLBACKMEMBER(int, pfnMatch)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, size_t *pcchMatched);
 
610
 
 
611
} const g_aVariables[] =
 
612
{
 
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 },
 
618
#endif
 
619
#ifdef RT_OS_WINDOWS
 
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 },
 
629
#endif
 
630
};
 
631
 
 
632
 
 
633
 
 
634
/**
 
635
 * Handles a complicated set.
 
636
 *
 
637
 * A complicated set is either using ranges, character classes or code points
 
638
 * outside the ASCII-7 range.
 
639
 *
 
640
 * @returns VINF_SUCCESS or VERR_MISMATCH.  May also return UTF-8 decoding
 
641
 *          errors as well as VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED.
 
642
 *
 
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.
 
646
 */
 
647
static int rtPathMatchExecExtendedSet(RTUNICP ucInput, const char *pchSet, size_t cchSet)
 
648
{
 
649
    while (cchSet > 0)
 
650
    {
 
651
        RTUNICP ucSet;
 
652
        int rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet);
 
653
        AssertRCReturn(rc, rc);
 
654
 
 
655
        /*
 
656
         * Check for character class, collating symbol and equvalence class.
 
657
         */
 
658
        if (ucSet == '[' && cchSet > 0)
 
659
        {
 
660
            char chNext = *pchSet;
 
661
            if (chNext == ':')
 
662
            {
 
663
#define CHECK_CHAR_CLASS(a_szClassNm, a_BoolTestExpr) \
 
664
                    if (   cchSet >= sizeof(a_szClassNm) \
 
665
                        && memcmp(pchSet, a_szClassNm "]", sizeof(a_szClassNm)) == 0) \
 
666
                    { \
 
667
                        if (a_BoolTestExpr) \
 
668
                            return VINF_SUCCESS; \
 
669
                        pchSet += sizeof(a_szClassNm); \
 
670
                        cchSet -= sizeof(a_szClassNm); \
 
671
                        continue; \
 
672
                    } do { } while (0)
 
673
 
 
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
 
688
            }
 
689
            /** @todo implement collating symbol and equvalence class. */
 
690
            else if (chNext == '=' || chNext == '.')
 
691
                AssertFailedReturn(VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
 
692
        }
 
693
 
 
694
        /*
 
695
         * Check for range (leading or final dash does not constitute a range).
 
696
         */
 
697
        if (cchSet > 1 && *pchSet == '-')
 
698
        {
 
699
            pchSet++;                   /* skip dash */
 
700
            cchSet--;
 
701
 
 
702
            RTUNICP ucSet2;
 
703
            rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet2);
 
704
            AssertRCReturn(rc, rc);
 
705
            Assert(ucSet < ucSet2);
 
706
            if (ucInput >= ucSet && ucInput <= ucSet2)
 
707
                return VINF_SUCCESS;
 
708
        }
 
709
        /*
 
710
         * Single char comparison.
 
711
         */
 
712
        else if (ucInput == ucSet)
 
713
            return VINF_SUCCESS;
 
714
    }
 
715
    return VERR_MISMATCH;
 
716
}
 
717
 
 
718
 
 
719
/**
 
720
 * Variable matching fallback using the query function.
 
721
 *
 
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.
 
724
 *
 
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.
 
732
 */
 
733
DECL_NO_INLINE(static, int) rtPathMatchExecVariableFallback(const char *pchInput, size_t cchInput, uint16_t idxVar,
 
734
                                                            bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
 
735
{
 
736
    for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
 
737
    {
 
738
        char   szValue[RTPATH_MAX];
 
739
        size_t cchValue;
 
740
        int rc = g_aVariables[idxVar].pfnQuery(iItem, szValue, sizeof(szValue), &cchValue, pCache);
 
741
        if (RT_SUCCESS(rc))
 
742
        {
 
743
            if (cchValue <= cchInput)
 
744
            {
 
745
                if (  !fIgnoreCase
 
746
                    ? memcmp(pchInput, szValue, cchValue) == 0
 
747
                    : RTStrNICmp(pchInput, szValue, cchValue) == 0)
 
748
                {
 
749
                    *pcchMatched = cchValue;
 
750
                    return VINF_SUCCESS;
 
751
                }
 
752
            }
 
753
            if (rc == VINF_EOF)
 
754
                return VERR_MISMATCH;
 
755
        }
 
756
        else if (rc == VERR_EOF)
 
757
            return VERR_MISMATCH;
 
758
        else
 
759
            Assert(rc == VERR_BUFFER_OVERFLOW || rc == VERR_TRY_AGAIN);
 
760
    }
 
761
    AssertFailed();
 
762
    return VERR_MISMATCH;
 
763
}
 
764
 
 
765
 
 
766
/**
 
767
 * Variable matching worker.
 
768
 *
 
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.
 
776
 */
 
777
static int rtPathMatchExecVariable(const char *pchInput, size_t cchInput, uint16_t idxVar,
 
778
                                   bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
 
779
{
 
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);
 
784
}
 
785
 
 
786
 
 
787
/**
 
788
 * Variable matching worker.
 
789
 *
 
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.
 
795
 */
 
796
static int rtPathMatchExec(const char *pchInput, size_t cchInput, PCRTPATHMATCHCORE pProg, PRTPATHMATCHCACHE pCache)
 
797
{
 
798
    for (;;)
 
799
    {
 
800
        switch (pProg->enmOpCode)
 
801
        {
 
802
            case RTPATHMATCHOP_RETURN_MATCH_IF_AT_END:
 
803
                return cchInput == 0 ? VINF_SUCCESS : VERR_MISMATCH;
 
804
 
 
805
            case RTPATHMATCHOP_RETURN_MATCH:
 
806
                return VINF_SUCCESS;
 
807
 
 
808
            case RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT:
 
809
                if (   cchInput > 2
 
810
                    || cchInput < 1
 
811
                    || pchInput[0] != '.'
 
812
                    || (cchInput == 2 && pchInput[1] != '.') )
 
813
                    return VINF_SUCCESS;
 
814
                return VERR_MISMATCH;
 
815
 
 
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;
 
823
                break;
 
824
 
 
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;
 
832
                break;
 
833
 
 
834
            case RTPATHMATCHOP_SKIP_ONE_CODEPOINT:
 
835
            {
 
836
                if (cchInput == 0)
 
837
                    return VERR_MISMATCH;
 
838
                RTUNICP ucInputIgnore;
 
839
                int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
 
840
                AssertRCReturn(rc, rc);
 
841
                break;
 
842
            }
 
843
 
 
844
            case RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS:
 
845
            {
 
846
                uint16_t cCpsLeft = pProg->cch;
 
847
                Assert(cCpsLeft > 1);
 
848
                if (cCpsLeft > cchInput)
 
849
                    return VERR_MISMATCH;
 
850
                while (cCpsLeft-- > 0)
 
851
                {
 
852
                    RTUNICP ucInputIgnore;
 
853
                    int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
 
854
                    if (RT_FAILURE(rc))
 
855
                        return rc == VERR_END_OF_STRING ? VERR_MISMATCH : rc;
 
856
                }
 
857
                break;
 
858
            }
 
859
 
 
860
            case RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7:
 
861
            {
 
862
                if (cchInput == 0)
 
863
                    return VERR_MISMATCH;
 
864
                RTUNICP ucInput;
 
865
                int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
 
866
                AssertRCReturn(rc, rc);
 
867
                if (ucInput >= 0x80)
 
868
                    return VERR_MISMATCH;
 
869
                if (memchr(pProg->pch, (char)ucInput, pProg->cch) == NULL)
 
870
                    return VERR_MISMATCH;
 
871
                break;
 
872
            }
 
873
 
 
874
            case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7:
 
875
            {
 
876
                if (cchInput == 0)
 
877
                    return VERR_MISMATCH;
 
878
                RTUNICP ucInput;
 
879
                int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
 
880
                AssertRCReturn(rc, rc);
 
881
                if (ucInput >= 0x80)
 
882
                    break;
 
883
                if (memchr(pProg->pch, (char)ucInput, pProg->cch) != NULL)
 
884
                    return VERR_MISMATCH;
 
885
                break;
 
886
            }
 
887
 
 
888
            case RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED:
 
889
            {
 
890
                if (cchInput == 0)
 
891
                    return VERR_MISMATCH;
 
892
                RTUNICP ucInput;
 
893
                int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
 
894
                AssertRCReturn(rc, rc);
 
895
                rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
 
896
                if (rc == VINF_SUCCESS)
 
897
                    break;
 
898
                return rc;
 
899
            }
 
900
 
 
901
            case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED:
 
902
            {
 
903
                if (cchInput == 0)
 
904
                    return VERR_MISMATCH;
 
905
                RTUNICP ucInput;
 
906
                int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
 
907
                AssertRCReturn(rc, rc);
 
908
                rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
 
909
                if (rc == VERR_MISMATCH)
 
910
                    break;
 
911
                if (rc == VINF_SUCCESS)
 
912
                    rc = VERR_MISMATCH;
 
913
                return rc;
 
914
            }
 
915
 
 
916
            case RTPATHMATCHOP_VARIABLE_VALUE_CMP:
 
917
            case RTPATHMATCHOP_VARIABLE_VALUE_ICMP:
 
918
            {
 
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)
 
923
                {
 
924
                    pchInput += cchMatched;
 
925
                    cchInput -= cchMatched;
 
926
                    break;
 
927
                }
 
928
                return rc;
 
929
            }
 
930
 
 
931
            /*
 
932
             * This is the expensive one. It always completes the program.
 
933
             */
 
934
            case RTPATHMATCHOP_ZERO_OR_MORE:
 
935
            {
 
936
                if (cchInput < pProg->cch)
 
937
                    return VERR_MISMATCH;
 
938
                size_t cchMatched = cchInput - pProg->cch;
 
939
                do
 
940
                {
 
941
                    int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
 
942
                    if (RT_SUCCESS(rc))
 
943
                        return rc;
 
944
                } while (cchMatched-- > 0);
 
945
                return VERR_MISMATCH;
 
946
            }
 
947
 
 
948
            /*
 
949
             * Variant of the above that doesn't match '.' and '..' entries.
 
950
             */
 
951
            case RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT:
 
952
            {
 
953
                if (cchInput < pProg->cch)
 
954
                    return VERR_MISMATCH;
 
955
                if (   cchInput <= 2
 
956
                    && cchInput > 0
 
957
                    && pchInput[0] == '.'
 
958
                    && (cchInput == 1 || pchInput[1] == '.') )
 
959
                    return VERR_MISMATCH;
 
960
                size_t cchMatched = cchInput - pProg->cch;
 
961
                do
 
962
                {
 
963
                    int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
 
964
                    if (RT_SUCCESS(rc))
 
965
                        return rc;
 
966
                } while (cchMatched-- > 0);
 
967
                return VERR_MISMATCH;
 
968
            }
 
969
 
 
970
            default:
 
971
                AssertMsgFailedReturn(("enmOpCode=%d\n", pProg->enmOpCode), VERR_INTERNAL_ERROR_3);
 
972
        }
 
973
 
 
974
        pProg++;
 
975
    }
 
976
}
 
977
 
 
978
 
 
979
 
 
980
 
 
981
/**
 
982
 * Compiles a path matching program.
 
983
 *
 
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).
 
991
 *
 
992
 * @todo Expose this matching code and also use it for RTDirOpenFiltered
 
993
 */
 
994
static int rtPathMatchCompile(const char *pchPattern, size_t cchPattern, bool fIgnoreCase, PRTPATHMATCHALLOC pAllocator)
 
995
{
 
996
    /** @todo PORTME: big endian. */
 
997
    static const uint8_t s_bmMetaChars[256/8] =
 
998
    {
 
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,
 
1005
    };
 
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*/);
 
1010
 
 
1011
    /*
 
1012
     * For checking for the first instruction.
 
1013
     */
 
1014
    uint16_t const iFirst = pAllocator->iNext;
 
1015
 
 
1016
    /*
 
1017
     * This is for tracking zero-or-more instructions and for calculating
 
1018
     * the minimum amount of input required for it to be considered.
 
1019
     */
 
1020
    uint16_t aiZeroOrMore[RTPATHMATCH_MAX_ZERO_OR_MORE];
 
1021
    uint8_t  cZeroOrMore = 0;
 
1022
    size_t   offInput    = 0;
 
1023
 
 
1024
    /*
 
1025
     * Loop thru the pattern and translate it into string matching instructions.
 
1026
     */
 
1027
    for (;;)
 
1028
    {
 
1029
        /*
 
1030
         * Allocate the next instruction.
 
1031
         */
 
1032
        if (pAllocator->iNext >= pAllocator->cAllocated)
 
1033
        {
 
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;
 
1039
        }
 
1040
        PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[pAllocator->iNext++];
 
1041
        pInstr->pch  = pchPattern;
 
1042
        pInstr->cch  = 0;
 
1043
        pInstr->uOp2 = 0;
 
1044
 
 
1045
        /*
 
1046
         * Special case: End of pattern.
 
1047
         */
 
1048
        if (!cchPattern)
 
1049
        {
 
1050
            pInstr->enmOpCode = RTPATHMATCHOP_RETURN_MATCH_IF_AT_END;
 
1051
            break;
 
1052
        }
 
1053
 
 
1054
        /*
 
1055
         * Parse the next bit of the pattern.
 
1056
         */
 
1057
        char ch = *pchPattern;
 
1058
        if (ASMBitTest(s_bmMetaChars, (uint8_t)ch))
 
1059
        {
 
1060
            /*
 
1061
             * Zero or more characters wildcard.
 
1062
             */
 
1063
            if (ch == '*')
 
1064
            {
 
1065
                /* Skip extra asterisks. */
 
1066
                do
 
1067
                {
 
1068
                    cchPattern--;
 
1069
                    pchPattern++;
 
1070
                } while (cchPattern > 0 && *pchPattern == '*');
 
1071
 
 
1072
                /* There is a special optimization for trailing '*'. */
 
1073
                pInstr->cch = 1;
 
1074
                if (cchPattern == 0)
 
1075
                {
 
1076
                    pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
 
1077
                                      ? RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_RETURN_MATCH;
 
1078
                    break;
 
1079
                }
 
1080
 
 
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);
 
1086
 
 
1087
                /* cchInput unchanged, zero-or-more matches. */
 
1088
                continue;
 
1089
            }
 
1090
 
 
1091
            /*
 
1092
             * Single character wildcard.
 
1093
             */
 
1094
            if (ch == '?')
 
1095
            {
 
1096
                /* Count them if more. */
 
1097
                uint16_t cchQms = 1;
 
1098
                while (cchQms < cchPattern && pchPattern[cchQms] == '?')
 
1099
                    cchQms++;
 
1100
 
 
1101
                pInstr->cch = cchQms;
 
1102
                pInstr->enmOpCode = cchQms == 1 ? RTPATHMATCHOP_SKIP_ONE_CODEPOINT : RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS;
 
1103
 
 
1104
                cchPattern -= cchQms;
 
1105
                pchPattern += cchQms;
 
1106
                offInput   += cchQms;
 
1107
                continue;
 
1108
            }
 
1109
 
 
1110
            /*
 
1111
             * Character in set.
 
1112
             *
 
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.
 
1117
             */
 
1118
            if (ch == '[')
 
1119
            {
 
1120
                if (   cchPattern > 2
 
1121
                    && (const char *)memchr(pchPattern + 2, ']', cchPattern) != NULL)
 
1122
                {
 
1123
 
 
1124
                    /* Check for not-in. */
 
1125
                    bool   fInverted = false;
 
1126
                    size_t offStart  = 1;
 
1127
                    if (pchPattern[offStart] == '^')
 
1128
                    {
 
1129
                        fInverted = true;
 
1130
                        offStart++;
 
1131
                    }
 
1132
 
 
1133
                    /* Special case for ']' as the first char, it doesn't indicate closing then. */
 
1134
                    size_t off = offStart;
 
1135
                    if (pchPattern[off] == ']')
 
1136
                        off++;
 
1137
 
 
1138
                    bool fExtended = false;
 
1139
                    while (off < cchPattern)
 
1140
                    {
 
1141
                        ch = pchPattern[off++];
 
1142
                        if (ch == '[')
 
1143
                        {
 
1144
                            if (off < cchPattern)
 
1145
                            {
 
1146
                                char chOpen = pchPattern[off];
 
1147
                                if (   chOpen == ':'
 
1148
                                    || chOpen == '='
 
1149
                                    || chOpen == '.')
 
1150
                                {
 
1151
                                    off++;
 
1152
                                    const char *pchFound = (const char *)memchr(&pchPattern[off], ']', cchPattern - off);
 
1153
                                    if (   pchFound
 
1154
                                        && pchFound[-1] == chOpen)
 
1155
                                    {
 
1156
                                        fExtended = true;
 
1157
                                        off = pchFound - pchPattern + 1;
 
1158
                                    }
 
1159
                                    else
 
1160
                                        AssertFailed();
 
1161
                                }
 
1162
                            }
 
1163
                        }
 
1164
                        /* Check for closing. */
 
1165
                        else if (ch == ']')
 
1166
                            break;
 
1167
                        /* Check for range expression, promote to extended if this happens. */
 
1168
                        else if (   ch == '-'
 
1169
                                 && off != offStart + 1
 
1170
                                 && off < cchPattern
 
1171
                                 && pchPattern[off] != ']')
 
1172
                            fExtended = true;
 
1173
                        /* UTF-8 multibyte chars forces us to use the extended version too. */
 
1174
                        else if ((uint8_t)ch >= 0x80)
 
1175
                            fExtended = true;
 
1176
                    }
 
1177
 
 
1178
                    if (ch == ']')
 
1179
                    {
 
1180
                        pInstr->pch = &pchPattern[offStart];
 
1181
                        pInstr->cch = (uint16_t)(off - offStart - 1);
 
1182
                        if (!fExtended)
 
1183
                            pInstr->enmOpCode = !fInverted
 
1184
                                              ? RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7 : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7;
 
1185
                        else
 
1186
                            pInstr->enmOpCode = !fInverted
 
1187
                                              ? RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED
 
1188
                                              : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED;
 
1189
                        pchPattern += off;
 
1190
                        cchPattern -= off;
 
1191
                        offInput   += 1;
 
1192
                        continue;
 
1193
                    }
 
1194
 
 
1195
                    /* else: invalid, treat it as */
 
1196
                    AssertFailed();
 
1197
                }
 
1198
            }
 
1199
            /*
 
1200
             * Variable matching.
 
1201
             */
 
1202
            else if (ch == '$')
 
1203
            {
 
1204
                const char *pchFound;
 
1205
                if (   cchPattern > 3
 
1206
                    && pchPattern[1] == '{'
 
1207
                    && (pchFound = (const char *)memchr(pchPattern + 2, '}', cchPattern)) != NULL
 
1208
                    && pchFound != &pchPattern[2])
 
1209
                {
 
1210
                    /* skip to the variable name. */
 
1211
                    pchPattern += 2;
 
1212
                    cchPattern -= 2;
 
1213
                    size_t cchVarNm = pchFound - pchPattern;
 
1214
 
 
1215
                    /* Look it up. */
 
1216
                    uint32_t iVar;
 
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)
 
1220
                            break;
 
1221
                    if (iVar < RT_ELEMENTS(g_aVariables))
 
1222
                    {
 
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. */
 
1233
                        continue;
 
1234
                    }
 
1235
                    AssertMsgFailedReturn(("Unknown path matching variable '%.*s'\n", cchVarNm, pchPattern),
 
1236
                                          VERR_PATH_MATCH_UNKNOWN_VARIABLE);
 
1237
                }
 
1238
            }
 
1239
            else
 
1240
                AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
 
1241
        }
 
1242
 
 
1243
        /*
 
1244
         * Plain text.  Look for the next meta char.
 
1245
         */
 
1246
        uint32_t cchPlain = 1;
 
1247
        while (cchPlain < cchPattern)
 
1248
        {
 
1249
            ch = pchPattern[cchPlain];
 
1250
            if (!ASMBitTest(s_bmMetaChars, (uint8_t)ch))
 
1251
            { /* probable */ }
 
1252
            else if (   ch == '?'
 
1253
                     || ch == '*')
 
1254
                break;
 
1255
            else if (ch == '$')
 
1256
            {
 
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])
 
1262
                break;
 
1263
            }
 
1264
            else if (ch == '[')
 
1265
            {
 
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)
 
1270
                    break;
 
1271
            }
 
1272
            else
 
1273
                AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
 
1274
            cchPlain++;
 
1275
        }
 
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;
 
1283
    }
 
1284
 
 
1285
    /*
 
1286
     * Optimize zero-or-more matching.
 
1287
     */
 
1288
    while (cZeroOrMore-- > 0)
 
1289
    {
 
1290
        PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[aiZeroOrMore[cZeroOrMore]];
 
1291
        pInstr->uOp2 = (uint16_t)(offInput - pInstr->uOp2);
 
1292
    }
 
1293
 
 
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;
 
1298
}
 
1299
 
 
1300
 
 
1301
/**
 
1302
 * Parses the glob pattern.
 
1303
 *
 
1304
 * This compiles filename matching programs for each component and determins the
 
1305
 * optimal search strategy for them.
 
1306
 *
 
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).
 
1312
 */
 
1313
static int rtPathGlobParse(PRTPATHGLOB pGlob, const char *pszPattern, PRTPATHPARSED pParsed, uint32_t fFlags)
 
1314
{
 
1315
    AssertReturn(pParsed->cComps > 0, VERR_INVALID_PARAMETER); /* shouldn't happen */
 
1316
    uint32_t iComp = 0;
 
1317
 
 
1318
    /*
 
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.)
 
1322
     */
 
1323
    if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
 
1324
    {
 
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;
 
1329
    }
 
1330
    else
 
1331
    {
 
1332
        const char * const pszComp = &pszPattern[pParsed->aComps[0].off];
 
1333
 
 
1334
        /*
 
1335
         * The tilde is only applicable to the first component, expand it
 
1336
         * immediately.
 
1337
         */
 
1338
        if (   *pszComp == '~'
 
1339
            && !(fFlags & RTPATHGLOB_F_NO_TILDE))
 
1340
        {
 
1341
            if (pParsed->aComps[0].cch == 1)
 
1342
            {
 
1343
                int rc = RTPathUserHome(pGlob->szPath, sizeof(pGlob->szPath) - 1);
 
1344
                AssertRCReturn(rc, rc);
 
1345
            }
 
1346
            else
 
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;
 
1351
        }
 
1352
    }
 
1353
 
 
1354
    /*
 
1355
     * Process the other components.
 
1356
     */
 
1357
    bool fStarStar = false;
 
1358
    for (; iComp < pParsed->cComps; iComp++)
 
1359
    {
 
1360
        const char *pszComp = &pszPattern[pParsed->aComps[iComp].off];
 
1361
        uint16_t    cchComp = pParsed->aComps[iComp].cch;
 
1362
        Assert(pGlob->aComps[iComp].fNormal == false);
 
1363
 
 
1364
        pGlob->aComps[iComp].fDir = iComp + 1 < pParsed->cComps || (fFlags & RTPATHGLOB_F_ONLY_DIRS);
 
1365
        if (   cchComp != 2
 
1366
            || pszComp[0] != '*'
 
1367
            || pszComp[1] != '*'
 
1368
            || (fFlags & RTPATHGLOB_F_NO_STARSTAR) )
 
1369
        {
 
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);
 
1375
            if (RT_FAILURE(rc))
 
1376
                return rc;
 
1377
 
 
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
 
1381
                && !fStarStar
 
1382
                && pGlob->MatchInstrAlloc.paInstructions[iMatchProg + 1].enmOpCode == RTPATHMATCHOP_RETURN_MATCH_IF_AT_END)
 
1383
            {
 
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)
 
1389
                {
 
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);
 
1395
                }
 
1396
                else
 
1397
                    pGlob->aComps[iComp].fNormal = true;
 
1398
            }
 
1399
            else
 
1400
                pGlob->aComps[iComp].fNormal = true;
 
1401
        }
 
1402
        else
 
1403
        {
 
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. */
 
1408
            fStarStar = true;
 
1409
        }
 
1410
    }
 
1411
    pGlob->aComps[pParsed->cComps - 1].fFinal = true;
 
1412
 
 
1413
    return VINF_SUCCESS;
 
1414
}
 
1415
 
 
1416
 
 
1417
/**
 
1418
 * This is for skipping overly long directories entries.
 
1419
 *
 
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.
 
1424
 *
 
1425
 * @returns IPRT status code.
 
1426
 * @param   hDir        The directory handle.
 
1427
 * @param   cbNeeded    The required entry size.
 
1428
 */
 
1429
DECL_NO_INLINE(static, int) rtPathGlobSkipDirEntry(PRTDIR hDir, size_t cbNeeded)
 
1430
{
 
1431
    int rc = VERR_BUFFER_OVERFLOW;
 
1432
    cbNeeded = RT_ALIGN_Z(cbNeeded, 16);
 
1433
    PRTDIRENTRY pDirEntry = (PRTDIRENTRY)RTMemTmpAlloc(cbNeeded);
 
1434
    if (pDirEntry)
 
1435
    {
 
1436
        rc = RTDirRead(hDir, pDirEntry, &cbNeeded);
 
1437
        RTMemTmpFree(pDirEntry);
 
1438
    }
 
1439
    return rc;
 
1440
}
 
1441
 
 
1442
 
 
1443
/**
 
1444
 * Adds a result.
 
1445
 *
 
1446
 * @returns IPRT status code.
 
1447
 * @retval  VINF_CALLBACK_RETURN if we can stop searching.
 
1448
 *
 
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.
 
1452
 */
 
1453
DECL_NO_INLINE(static, int) rtPathGlobAddResult(PRTPATHGLOB pGlob, size_t cchPath, uint8_t uType)
 
1454
{
 
1455
    if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
 
1456
    {
 
1457
        PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_OFFSETOF(RTPATHGLOBENTRY, szPath[cchPath + 1]));
 
1458
        if (pEntry)
 
1459
        {
 
1460
            pEntry->uType   = uType;
 
1461
            pEntry->cchPath = (uint16_t)cchPath;
 
1462
            memcpy(pEntry->szPath, pGlob->szPath, cchPath);
 
1463
            pEntry->szPath[cchPath] = '\0';
 
1464
 
 
1465
            pEntry->pNext  = NULL;
 
1466
            *pGlob->ppNext = pEntry;
 
1467
            pGlob->ppNext  = &pEntry->pNext;
 
1468
            pGlob->cResults++;
 
1469
 
 
1470
            if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
 
1471
                return VINF_SUCCESS;
 
1472
            return VINF_CALLBACK_RETURN;
 
1473
        }
 
1474
        return VERR_NO_MEMORY;
 
1475
    }
 
1476
    return VERR_TOO_MUCH_DATA;
 
1477
}
 
1478
 
 
1479
 
 
1480
/**
 
1481
 * Adds a result, constructing the path from two string.
 
1482
 *
 
1483
 * @returns IPRT status code.
 
1484
 * @retval  VINF_CALLBACK_RETURN if we can stop searching.
 
1485
 *
 
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.
 
1491
 */
 
1492
DECL_NO_INLINE(static, int) rtPathGlobAddResult2(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
 
1493
                                                 uint8_t uType)
 
1494
{
 
1495
    if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
 
1496
    {
 
1497
        PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_OFFSETOF(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
 
1498
        if (pEntry)
 
1499
        {
 
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';
 
1505
 
 
1506
            pEntry->pNext  = NULL;
 
1507
            *pGlob->ppNext = pEntry;
 
1508
            pGlob->ppNext  = &pEntry->pNext;
 
1509
            pGlob->cResults++;
 
1510
 
 
1511
            if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
 
1512
                return VINF_SUCCESS;
 
1513
            return VINF_CALLBACK_RETURN;
 
1514
        }
 
1515
        return VERR_NO_MEMORY;
 
1516
    }
 
1517
    return VERR_TOO_MUCH_DATA;
 
1518
}
 
1519
 
 
1520
 
 
1521
/**
 
1522
 * Prepares a result, constructing the path from two string.
 
1523
 *
 
1524
 * The caller must call either rtPathGlobCommitResult or
 
1525
 * rtPathGlobRollbackResult to complete the operation.
 
1526
 *
 
1527
 * @returns IPRT status code.
 
1528
 * @retval  VINF_CALLBACK_RETURN if we can stop searching.
 
1529
 *
 
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.
 
1535
 */
 
1536
DECL_NO_INLINE(static, int) rtPathGlobAlmostAddResult(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
 
1537
                                                      uint8_t uType)
 
1538
{
 
1539
    if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
 
1540
    {
 
1541
        PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_OFFSETOF(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
 
1542
        if (pEntry)
 
1543
        {
 
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';
 
1549
 
 
1550
            pEntry->pNext  = NULL;
 
1551
            *pGlob->ppNext = pEntry;
 
1552
            /* Note! We don't update ppNext here, that is done in rtPathGlobCommitResult. */
 
1553
 
 
1554
            if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
 
1555
                return VINF_SUCCESS;
 
1556
            return VINF_CALLBACK_RETURN;
 
1557
        }
 
1558
        return VERR_NO_MEMORY;
 
1559
    }
 
1560
    return VERR_TOO_MUCH_DATA;
 
1561
}
 
1562
 
 
1563
 
 
1564
/**
 
1565
 * Commits a pending result from rtPathGlobAlmostAddResult.
 
1566
 *
 
1567
 * @param   pGlob       The glob instance data.
 
1568
 * @param   uType       The RTDIRENTRYTYPE value.
 
1569
 */
 
1570
static void rtPathGlobCommitResult(PRTPATHGLOB pGlob, uint8_t uType)
 
1571
{
 
1572
    PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
 
1573
    AssertPtr(pEntry);
 
1574
    pEntry->uType = uType;
 
1575
    pGlob->ppNext = &pEntry->pNext;
 
1576
    pGlob->cResults++;
 
1577
}
 
1578
 
 
1579
 
 
1580
/**
 
1581
 * Rolls back a pending result from rtPathGlobAlmostAddResult.
 
1582
 *
 
1583
 * @param   pGlob       The glob instance data.
 
1584
 */
 
1585
static void rtPathGlobRollbackResult(PRTPATHGLOB pGlob)
 
1586
{
 
1587
    PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
 
1588
    AssertPtr(pEntry);
 
1589
    RTMemFree(pEntry);
 
1590
    *pGlob->ppNext = NULL;
 
1591
}
 
1592
 
 
1593
 
 
1594
 
 
1595
/**
 
1596
 * Whether to call rtPathGlobExecRecursiveVarExp for the next component.
 
1597
 *
 
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.
 
1602
 */
 
1603
DECLINLINE(bool) rtPathGlobExecIsExpVar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
 
1604
{
 
1605
    return pGlob->aComps[iComp].fExpVariable
 
1606
        && (  !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
 
1607
            || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
 
1608
}
 
1609
 
 
1610
/**
 
1611
 * Whether to call rtPathGlobExecRecursivePlainText for the next component.
 
1612
 *
 
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.
 
1617
 */
 
1618
DECLINLINE(bool) rtPathGlobExecIsPlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
 
1619
{
 
1620
    return pGlob->aComps[iComp].fPlain
 
1621
        && (  !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
 
1622
            || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
 
1623
}
 
1624
 
 
1625
 
 
1626
/**
 
1627
 * Helper for rtPathGlobExecRecursiveVarExp and rtPathGlobExecRecursivePlainText
 
1628
 * that compares a file mode mask with dir/no-dir wishes of the caller.
 
1629
 *
 
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).
 
1633
 */
 
1634
DECLINLINE(bool) rtPathGlobExecIsMatchFinalWithFileMode(PRTPATHGLOB pGlob, RTFMODE fMode)
 
1635
{
 
1636
    if (!(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)))
 
1637
        return true;
 
1638
    return RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS) == RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode);
 
1639
}
 
1640
 
 
1641
 
 
1642
/**
 
1643
 * Recursive globbing - star-star mode.
 
1644
 *
 
1645
 * @returns IPRT status code.
 
1646
 * @retval  VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
 
1647
 *
 
1648
 * @param   pGlob               The glob instance data.
 
1649
 * @param   offPath             The current path offset/length.
 
1650
 * @param   iComp               The current component.
 
1651
 */
 
1652
DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp,
 
1653
                                                            size_t offStarStarPath)
 
1654
{
 
1655
    /** @todo implement multi subdir matching. */
 
1656
    return VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED;
 
1657
}
 
1658
 
 
1659
 
 
1660
 
 
1661
/**
 
1662
 * Recursive globbing - variable expansion optimization.
 
1663
 *
 
1664
 * @returns IPRT status code.
 
1665
 * @retval  VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
 
1666
 *
 
1667
 * @param   pGlob               The glob instance data.
 
1668
 * @param   offPath             The current path offset/length.
 
1669
 * @param   iComp               The current component.
 
1670
 */
 
1671
DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
 
1672
{
 
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));
 
1679
 
 
1680
    /*
 
1681
     * Fish the variable index out of the first matching instruction.
 
1682
     */
 
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;
 
1688
 
 
1689
    /*
 
1690
     * Enumerate all the variable, giving them the plain text treatment.
 
1691
     */
 
1692
    for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
 
1693
    {
 
1694
        size_t cch;
 
1695
        int rcVar = g_aVariables[iVar].pfnQuery(iItem, &pGlob->szPath[offPath], sizeof(pGlob->szPath) - offPath, &cch,
 
1696
                                                &pGlob->MatchCache);
 
1697
        if (RT_SUCCESS(rcVar))
 
1698
        {
 
1699
            Assert(pGlob->szPath[offPath + cch] == '\0');
 
1700
 
 
1701
            int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
 
1702
            if (RT_SUCCESS(rc))
 
1703
            {
 
1704
                if (pGlob->aComps[iComp].fFinal)
 
1705
                {
 
1706
                    if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
 
1707
                    {
 
1708
                        rc = rtPathGlobAddResult(pGlob, cch,
 
1709
                                                 (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
 
1710
                                                 >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
 
1711
                        if (rc != VINF_SUCCESS)
 
1712
                            return rc;
 
1713
                    }
 
1714
                }
 
1715
                else if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
 
1716
                {
 
1717
                    Assert(pGlob->aComps[iComp].fDir);
 
1718
                    cch = RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
 
1719
                    if (cch > 0)
 
1720
                    {
 
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);
 
1727
                        else
 
1728
                            rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
 
1729
                        if (rc != VINF_SUCCESS)
 
1730
                            return rc;
 
1731
                    }
 
1732
                    else
 
1733
                        pGlob->cPathOverflows++;
 
1734
                }
 
1735
            }
 
1736
            /* else: file doesn't exist or something else is wrong, ignore this. */
 
1737
            if (rcVar == VINF_EOF)
 
1738
                return VINF_SUCCESS;
 
1739
        }
 
1740
        else if (rcVar == VERR_EOF)
 
1741
            return VINF_SUCCESS;
 
1742
        else if (rcVar != VERR_TRY_AGAIN)
 
1743
        {
 
1744
            Assert(rcVar == VERR_BUFFER_OVERFLOW);
 
1745
            pGlob->cPathOverflows++;
 
1746
        }
 
1747
    }
 
1748
    AssertFailedReturn(VINF_SUCCESS); /* Too many items returned, probably buggy query method. */
 
1749
}
 
1750
 
 
1751
 
 
1752
/**
 
1753
 * Recursive globbing - plain text optimization.
 
1754
 *
 
1755
 * @returns IPRT status code.
 
1756
 * @retval  VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
 
1757
 *
 
1758
 * @param   pGlob               The glob instance data.
 
1759
 * @param   offPath             The current path offset/length.
 
1760
 * @param   iComp               The current component.
 
1761
 */
 
1762
DECL_NO_INLINE(static, int) rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
 
1763
{
 
1764
    /*
 
1765
     * Instead of recursing, we loop thru adjacent plain text components.
 
1766
     */
 
1767
    for (;;)
 
1768
    {
 
1769
        /*
 
1770
         * Preconditions.
 
1771
         */
 
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);
 
1782
 
 
1783
        /*
 
1784
         * Add the plain text component to the path.
 
1785
         */
 
1786
        size_t const cch = pGlob->pParsed->aComps[iComp].cch;
 
1787
        if (cch + pGlob->aComps[iComp].fDir < sizeof(pGlob->szPath) - offPath)
 
1788
        {
 
1789
            memcpy(&pGlob->szPath[offPath], &pGlob->pszPattern[pGlob->pParsed->aComps[iComp].off], cch);
 
1790
            offPath += cch;
 
1791
            pGlob->szPath[offPath] = '\0';
 
1792
 
 
1793
            /*
 
1794
             * Check if it exists.
 
1795
             */
 
1796
            int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
 
1797
            if (RT_SUCCESS(rc))
 
1798
            {
 
1799
                if (pGlob->aComps[iComp].fFinal)
 
1800
                {
 
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);
 
1805
                    break;
 
1806
                }
 
1807
 
 
1808
                if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
 
1809
                {
 
1810
                    Assert(pGlob->aComps[iComp].fDir);
 
1811
                    pGlob->szPath[offPath++] = RTPATH_SLASH;
 
1812
                    pGlob->szPath[offPath]   = '\0';
 
1813
 
 
1814
                    iComp++;
 
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);
 
1821
 
 
1822
                    /* Continue with the next plain text component. */
 
1823
                    continue;
 
1824
                }
 
1825
            }
 
1826
            /* else: file doesn't exist or something else is wrong, ignore this. */
 
1827
        }
 
1828
        else
 
1829
            pGlob->cPathOverflows++;
 
1830
        break;
 
1831
    }
 
1832
    return VINF_SUCCESS;
 
1833
}
 
1834
 
 
1835
 
 
1836
/**
 
1837
 * Recursive globbing - generic.
 
1838
 *
 
1839
 * @returns IPRT status code.
 
1840
 * @retval  VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
 
1841
 *
 
1842
 * @param   pGlob               The glob instance data.
 
1843
 * @param   offPath             The current path offset/length.
 
1844
 * @param   iComp               The current component.
 
1845
 */
 
1846
DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
 
1847
{
 
1848
    /*
 
1849
     * Enumerate entire directory and match each entry.
 
1850
     */
 
1851
    PRTDIR hDir;
 
1852
    int rc = RTDirOpen(&hDir, offPath ? pGlob->szPath : ".");
 
1853
    if (RT_SUCCESS(rc))
 
1854
    {
 
1855
        for (;;)
 
1856
        {
 
1857
            size_t cch = sizeof(pGlob->u);
 
1858
            rc = RTDirRead(hDir, &pGlob->u.DirEntry, &cch);
 
1859
            if (RT_SUCCESS(rc))
 
1860
            {
 
1861
                if (pGlob->aComps[iComp].fFinal)
 
1862
                {
 
1863
                    /*
 
1864
                     * Final component: Check if it matches the current pattern.
 
1865
                     */
 
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)
 
1870
                    {
 
1871
                        rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
 
1872
                                             &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
 
1873
                                             &pGlob->MatchCache);
 
1874
                        if (RT_SUCCESS(rc))
 
1875
                        {
 
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);
 
1881
                            else
 
1882
                            {
 
1883
                                rc = rtPathGlobAlmostAddResult(pGlob, offPath,
 
1884
                                                               pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
 
1885
                                                               (uint8_t)RTDIRENTRYTYPE_UNKNOWN);
 
1886
                                if (RT_SUCCESS(rc))
 
1887
                                {
 
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);
 
1893
                                    else
 
1894
                                        rtPathGlobRollbackResult(pGlob);
 
1895
                                }
 
1896
                            }
 
1897
                            if (rc != VINF_SUCCESS)
 
1898
                                break;
 
1899
                        }
 
1900
                        else
 
1901
                        {
 
1902
                            AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
 
1903
                            rc = VINF_SUCCESS;
 
1904
                        }
 
1905
                    }
 
1906
                }
 
1907
                /*
 
1908
                 * Intermediate component: Directories only.
 
1909
                 */
 
1910
                else if (   pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY
 
1911
                         || pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
 
1912
                {
 
1913
                    rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
 
1914
                                         &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
 
1915
                                         &pGlob->MatchCache);
 
1916
                    if (RT_SUCCESS(rc))
 
1917
                    {
 
1918
                        /* Recurse down into the alleged directory. */
 
1919
                        cch = offPath + pGlob->u.DirEntry.cbName;
 
1920
                        if (cch + 1 < sizeof(pGlob->szPath))
 
1921
                        {
 
1922
                            memcpy(&pGlob->szPath[offPath], pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName);
 
1923
                            pGlob->szPath[cch++] = RTPATH_SLASH;
 
1924
                            pGlob->szPath[cch]   = '\0';
 
1925
 
 
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);
 
1932
                            else
 
1933
                                rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
 
1934
                            if (rc != VINF_SUCCESS)
 
1935
                                return rc;
 
1936
                        }
 
1937
                        else
 
1938
                            pGlob->cPathOverflows++;
 
1939
                    }
 
1940
                    else
 
1941
                    {
 
1942
                        AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
 
1943
                        rc = VINF_SUCCESS;
 
1944
                    }
 
1945
                }
 
1946
            }
 
1947
            /*
 
1948
             * RTDirRead failure.
 
1949
             */
 
1950
            else
 
1951
            {
 
1952
                /* The end?  */
 
1953
                if (rc == VERR_NO_MORE_FILES)
 
1954
                    rc = VINF_SUCCESS;
 
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)
 
1957
                {
 
1958
                    pGlob->cPathOverflows++;
 
1959
                    rc = rtPathGlobSkipDirEntry(hDir, cch);
 
1960
                    if (RT_SUCCESS(rc))
 
1961
                        continue;
 
1962
                }
 
1963
                /* else: Any other error is unexpected and should be reported. */
 
1964
                break;
 
1965
            }
 
1966
        }
 
1967
 
 
1968
        RTDirClose(hDir);
 
1969
    }
 
1970
    /* Directory doesn't exist or something else is wrong, ignore this. */
 
1971
    else
 
1972
        rc = VINF_SUCCESS;
 
1973
    return rc;
 
1974
}
 
1975
 
 
1976
 
 
1977
/**
 
1978
 * Executes a glob search.
 
1979
 *
 
1980
 * @returns IPRT status code.
 
1981
 * @param   pGlob               The glob instance data.
 
1982
 */
 
1983
static int rtPathGlobExec(PRTPATHGLOB pGlob)
 
1984
{
 
1985
    Assert(pGlob->offFirstPath < sizeof(pGlob->szPath));
 
1986
    Assert(pGlob->szPath[pGlob->offFirstPath] == '\0');
 
1987
 
 
1988
    int rc;
 
1989
    if (RT_LIKELY(pGlob->iFirstComp < pGlob->pParsed->cComps))
 
1990
    {
 
1991
        /*
 
1992
         * Call the appropriate function.
 
1993
         */
 
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);
 
2000
        else
 
2001
            rc = rtPathGlobExecRecursiveGeneric(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
 
2002
    }
 
2003
    else
 
2004
    {
 
2005
        /*
 
2006
         * Special case where we only have a root component or tilde expansion.
 
2007
         */
 
2008
        Assert(pGlob->offFirstPath > 0);
 
2009
        rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
 
2010
        if (   RT_SUCCESS(rc)
 
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);
 
2014
        else
 
2015
            rc = VINF_SUCCESS;
 
2016
    }
 
2017
 
 
2018
    /*
 
2019
     * Adjust the status code.  Check for results, hide RTPATHGLOB_F_FIRST_ONLY
 
2020
     * status code, and add warning if necessary.
 
2021
     */
 
2022
    if (pGlob->cResults > 0)
 
2023
    {
 
2024
        if (rc == VINF_CALLBACK_RETURN)
 
2025
            rc = VINF_SUCCESS;
 
2026
        if (rc == VINF_SUCCESS)
 
2027
        {
 
2028
            if (pGlob->cPathOverflows > 0)
 
2029
                rc = VINF_BUFFER_OVERFLOW;
 
2030
        }
 
2031
    }
 
2032
    else
 
2033
        rc = VERR_FILE_NOT_FOUND;
 
2034
 
 
2035
    return rc;
 
2036
}
 
2037
 
 
2038
 
 
2039
RTDECL(int) RTPathGlob(const char *pszPattern, uint32_t fFlags, PPCRTPATHGLOBENTRY ppHead, uint32_t *pcResults)
 
2040
{
 
2041
    /*
 
2042
     * Input validation.
 
2043
     */
 
2044
    AssertPtrReturn(ppHead, VERR_INVALID_POINTER);
 
2045
    *ppHead = NULL;
 
2046
    if (pcResults)
 
2047
    {
 
2048
        AssertPtrReturn(pcResults, VERR_INVALID_POINTER);
 
2049
        *pcResults = 0;
 
2050
    }
 
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);
 
2055
 
 
2056
    /*
 
2057
     * Parse the path.
 
2058
     */
 
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)
 
2064
    {
 
2065
        cbParsed = RT_OFFSETOF(RTPATHPARSED, aComps[pParsed->cComps + 1]);
 
2066
        RTMemTmpFree(pParsed);
 
2067
        pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
 
2068
        AssertReturn(pParsed, VERR_NO_MEMORY);
 
2069
 
 
2070
        rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
 
2071
    }
 
2072
    if (RT_SUCCESS(rc))
 
2073
    {
 
2074
        /*
 
2075
         * Check dir slash vs. only/not dir flag.
 
2076
         */
 
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) ) )
 
2081
        {
 
2082
            if (pParsed->fProps & RTPATH_PROP_DIR_SLASH)
 
2083
                fFlags |= RTPATHGLOB_F_ONLY_DIRS;
 
2084
 
 
2085
            /*
 
2086
             * Allocate and initialize the glob state data structure.
 
2087
             */
 
2088
            size_t      cbGlob = RT_OFFSETOF(RTPATHGLOB, aComps[pParsed->cComps + 1]);
 
2089
            PRTPATHGLOB pGlob  = (PRTPATHGLOB)RTMemTmpAllocZ(cbGlob);
 
2090
            if (pGlob)
 
2091
            {
 
2092
                pGlob->pszPattern = pszPattern;
 
2093
                pGlob->fFlags     = fFlags;
 
2094
                pGlob->pParsed    = pParsed;
 
2095
                pGlob->ppNext     = &pGlob->pHead;
 
2096
                rc = rtPathGlobParse(pGlob, pszPattern, pParsed, fFlags);
 
2097
                if (RT_SUCCESS(rc))
 
2098
                {
 
2099
                    /*
 
2100
                     * Execute the search.
 
2101
                     */
 
2102
                    rc = rtPathGlobExec(pGlob);
 
2103
                    if (RT_SUCCESS(rc))
 
2104
                    {
 
2105
                        *ppHead = pGlob->pHead;
 
2106
                        if (pcResults)
 
2107
                            *pcResults = pGlob->cResults;
 
2108
                    }
 
2109
                    else
 
2110
                        RTPathGlobFree(pGlob->pHead);
 
2111
                }
 
2112
 
 
2113
                RTMemTmpFree(pGlob->MatchInstrAlloc.paInstructions);
 
2114
                RTMemTmpFree(pGlob);
 
2115
            }
 
2116
            else
 
2117
                rc = VERR_NO_MEMORY;
 
2118
        }
 
2119
        else
 
2120
            rc = VERR_NOT_FOUND;
 
2121
    }
 
2122
    RTMemTmpFree(pParsed);
 
2123
    return rc;
 
2124
 
 
2125
 
 
2126
}
 
2127
 
 
2128
 
 
2129
RTDECL(void) RTPathGlobFree(PCRTPATHGLOBENTRY pHead)
 
2130
{
 
2131
    PRTPATHGLOBENTRY pCur = (PRTPATHGLOBENTRY)pHead;
 
2132
    while (pCur)
 
2133
    {
 
2134
        PRTPATHGLOBENTRY pNext = pCur->pNext;
 
2135
        pCur->pNext = NULL;
 
2136
        RTMemFree(pCur);
 
2137
        pCur = pNext;
 
2138
    }
 
2139
}
 
2140