VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/path/RTPathGlob.cpp

Last change on this file was 106428, checked in by vboxsync, 3 months ago

Runtime/common/path/RTPathGlob.cpp: Include arm specific directories on Windows, bugref:10392

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 84.6 KB
Line 
1/* $Id: RTPathGlob.cpp 106428 2024-10-17 10:59:22Z vboxsync $ */
2/** @file
3 * IPRT - RTPathGlob
4 */
5
6/*
7 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include "internal/iprt.h"
42#include <iprt/path.h>
43
44#include <iprt/asm.h>
45#include <iprt/assert.h>
46#include <iprt/buildconfig.h>
47#include <iprt/ctype.h>
48#include <iprt/dir.h>
49#include <iprt/env.h>
50#include <iprt/err.h>
51#include <iprt/mem.h>
52#include <iprt/string.h>
53#include <iprt/uni.h>
54
55#if defined(RT_OS_WINDOWS)
56# include <iprt/utf16.h>
57# include <iprt/win/windows.h>
58# include "../../r3/win/internal-r3-win.h"
59
60#elif defined(RT_OS_OS2)
61# define INCL_BASE
62# include <os2.h>
63# undef RT_MAX /* collision */
64
65#endif
66
67
68/*********************************************************************************************************************************
69* Defined Constants And Macros *
70*********************************************************************************************************************************/
71/** Maximum number of results. */
72#define RTPATHGLOB_MAX_RESULTS _32K
73/** Maximum number of zero-or-more wildcards in a pattern.
74 * This limits stack usage and recursion depth, as well as execution time. */
75#define RTPATHMATCH_MAX_ZERO_OR_MORE 24
76/** Maximum number of variable items. */
77#define RTPATHMATCH_MAX_VAR_ITEMS _4K
78
79
80
81/*********************************************************************************************************************************
82* Structures and Typedefs *
83*********************************************************************************************************************************/
84/**
85 * Matching operation.
86 */
87typedef enum RTPATHMATCHOP
88{
89 RTPATHMATCHOP_INVALID = 0,
90 /** EOS: Returns a match if at end of string. */
91 RTPATHMATCHOP_RETURN_MATCH_IF_AT_END,
92 /** Asterisk: Returns a match (trailing asterisk). */
93 RTPATHMATCHOP_RETURN_MATCH,
94 /** Asterisk: Returns a match (just asterisk), unless it's '.' or '..'. */
95 RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT,
96 /** Plain text: Case sensitive string compare. */
97 RTPATHMATCHOP_STRCMP,
98 /** Plain text: Case insensitive string compare. */
99 RTPATHMATCHOP_STRICMP,
100 /** Question marks: Skips exactly one code point. */
101 RTPATHMATCHOP_SKIP_ONE_CODEPOINT,
102 /** Question marks: Skips exactly RTPATHMATCHCORE::cch code points. */
103 RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS,
104 /** Char set: Requires the next codepoint to be in the ASCII-7 set defined by
105 * RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. No ranges. */
106 RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7,
107 /** Char set: Requires the next codepoint to not be in the ASCII-7 set defined
108 * by RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. No ranges. */
109 RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7,
110 /** Char set: Requires the next codepoint to be in the extended set defined by
111 * RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. Ranges, UTF-8. */
112 RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED,
113 /** Char set: Requires the next codepoint to not be in the extended set defined
114 * by RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. Ranges, UTF-8. */
115 RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED,
116 /** Variable: Case sensitive variable value compare, RTPATHMATCHCORE::uOp2 is
117 * the variable table index. */
118 RTPATHMATCHOP_VARIABLE_VALUE_CMP,
119 /** Variable: Case insensitive variable value compare, RTPATHMATCHCORE::uOp2 is
120 * the variable table index. */
121 RTPATHMATCHOP_VARIABLE_VALUE_ICMP,
122 /** Asterisk: Match zero or more code points, there must be at least
123 * RTPATHMATCHCORE::cch code points after it. */
124 RTPATHMATCHOP_ZERO_OR_MORE,
125 /** Asterisk: Match zero or more code points, there must be at least
126 * RTPATHMATCHCORE::cch code points after it, unless it's '.' or '..'. */
127 RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT,
128 /** End of valid operations. */
129 RTPATHMATCHOP_END
130} RTPATHMATCHOP;
131
132/**
133 * Matching instruction.
134 */
135typedef struct RTPATHMATCHCORE
136{
137 /** The action to take. */
138 RTPATHMATCHOP enmOpCode;
139 /** Generic value operand. */
140 uint16_t uOp2;
141 /** Generic length operand. */
142 uint16_t cch;
143 /** Generic string pointer operand. */
144 const char *pch;
145} RTPATHMATCHCORE;
146/** Pointer to a matching instruction. */
147typedef RTPATHMATCHCORE *PRTPATHMATCHCORE;
148/** Pointer to a const matching instruction. */
149typedef RTPATHMATCHCORE const *PCRTPATHMATCHCORE;
150
151/**
152 * Path matching instruction allocator.
153 */
154typedef struct RTPATHMATCHALLOC
155{
156 /** Allocated array of instructions. */
157 PRTPATHMATCHCORE paInstructions;
158 /** Index of the next free entry in paScratch. */
159 uint32_t iNext;
160 /** Number of instructions allocated. */
161 uint32_t cAllocated;
162} RTPATHMATCHALLOC;
163/** Pointer to a matching instruction allocator. */
164typedef RTPATHMATCHALLOC *PRTPATHMATCHALLOC;
165
166/**
167 * Path matching cache, mainly intended for variables like the PATH.
168 */
169typedef struct RTPATHMATCHCACHE
170{
171 /** @todo optimize later. */
172 uint32_t iNothingYet;
173} RTPATHMATCHCACHE;
174/** Pointer to a path matching cache. */
175typedef RTPATHMATCHCACHE *PRTPATHMATCHCACHE;
176
177
178
179/** Parsed path entry.*/
180typedef struct RTPATHGLOBPPE
181{
182 /** Normal: Index into RTPATHGLOB::MatchInstrAlloc.paInstructions. */
183 uint32_t iMatchProg : 16;
184 /** Set if this is a normal entry which is matched using iMatchProg. */
185 uint32_t fNormal : 1;
186 /** !fNormal: Plain name that can be dealt with using without
187 * enumerating the whole directory, unless of course the file system is case
188 * sensitive and the globbing isn't (that needs figuring out on a per
189 * directory basis). */
190 uint32_t fPlain : 1;
191 /** !fNormal: Match zero or more subdirectories. */
192 uint32_t fStarStar : 1;
193 /** !fNormal: The whole component is a variable expansion. */
194 uint32_t fExpVariable : 1;
195
196 /** Filter: Set if it only matches directories. */
197 uint32_t fDir : 1;
198 /** Set if it's the final component. */
199 uint32_t fFinal : 1;
200
201 /** Unused bits. */
202 uint32_t fReserved : 2+8;
203} RTPATHGLOBPPE;
204
205
206typedef struct RTPATHGLOB
207{
208 /** Path buffer. */
209 char szPath[RTPATH_MAX];
210 /** Temporary buffers. */
211 union
212 {
213 /** File system object info structure. */
214 RTFSOBJINFO ObjInfo;
215 /** Directory entry buffer. */
216 RTDIRENTRY DirEntry;
217 /** Padding the buffer to an unreasonably large size. */
218 uint8_t abPadding[RTPATH_MAX + sizeof(RTDIRENTRY)];
219 } u;
220
221
222 /** Where to insert the next one.*/
223 PRTPATHGLOBENTRY *ppNext;
224 /** The head pointer. */
225 PRTPATHGLOBENTRY pHead;
226 /** Result count. */
227 uint32_t cResults;
228 /** Counts path overflows. */
229 uint32_t cPathOverflows;
230 /** The input flags. */
231 uint32_t fFlags;
232 /** Matching instruction allocator. */
233 RTPATHMATCHALLOC MatchInstrAlloc;
234 /** Matching state. */
235 RTPATHMATCHCACHE MatchCache;
236
237 /** The pattern string. */
238 const char *pszPattern;
239 /** The parsed path. */
240 PRTPATHPARSED pParsed;
241 /** The component to start with. */
242 uint16_t iFirstComp;
243 /** The corresponding path offset (previous components already present). */
244 uint16_t offFirstPath;
245 /** Path component information we need. */
246 RTPATHGLOBPPE aComps[1];
247} RTPATHGLOB;
248typedef RTPATHGLOB *PRTPATHGLOB;
249
250
251/**
252 * Matching variable lookup table.
253 * Currently so small we don't bother sorting it and doing binary lookups.
254 */
255typedef struct RTPATHMATCHVAR
256{
257 /** The variable name. */
258 const char *pszName;
259 /** The variable name length. */
260 uint16_t cchName;
261 /** Only available as the verify first component. */
262 bool fFirstOnly;
263
264 /**
265 * Queries a given variable value.
266 *
267 * @returns IPRT status code.
268 * @retval VERR_BUFFER_OVERFLOW
269 * @retval VERR_TRY_AGAIN if the caller should skip this value item and try the
270 * next one instead (e.g. env var not present).
271 * @retval VINF_EOF when retrieving the last one, if possible.
272 * @retval VERR_EOF when @a iItem is past the item space.
273 *
274 * @param iItem The variable value item to retrieve. (A variable may
275 * have more than one value, e.g. 'BothProgramFile' on a
276 * 64-bit system or 'Path'.)
277 * @param pszBuf Where to return the value.
278 * @param cbBuf The buffer size.
279 * @param pcchValue Where to return the length of the return string.
280 * @param pCache Pointer to the path matching cache. May speed up
281 * enumerating PATH items and similar.
282 */
283 DECLCALLBACKMEMBER(int, pfnQuery,(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, PRTPATHMATCHCACHE pCache));
284
285 /**
286 * Matching method, optional.
287 *
288 * @returns IPRT status code.
289 * @retval VINF_SUCCESS on match.
290 * @retval VERR_MISMATCH on mismatch.
291 *
292 * @param pszMatch String to match with (not terminated).
293 * @param cchMatch The length of what we match with.
294 * @param fIgnoreCase Whether to ignore case or not when comparing.
295 * @param pcchMatched Where to return the length of the match (value length).
296 */
297 DECLCALLBACKMEMBER(int, pfnMatch,(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, size_t *pcchMatched));
298
299} RTPATHMATCHVAR;
300
301
302/*********************************************************************************************************************************
303* Internal Functions *
304*********************************************************************************************************************************/
305static int rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp, size_t offStarStarPath);
306static int rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
307static int rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
308static int rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
309
310
311/**
312 * Implements the two variable access functions for a simple one value variable.
313 */
314#define RTPATHMATCHVAR_SIMPLE(a_Name, a_GetStrExpr) \
315 static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
316 PRTPATHMATCHCACHE pCache) \
317 { \
318 if (iItem == 0) \
319 { \
320 const char *pszValue = a_GetStrExpr; \
321 size_t cchValue = strlen(pszValue); \
322 if (cchValue + 1 <= cbBuf) \
323 { \
324 memcpy(pszBuf, pszValue, cchValue + 1); \
325 *pcchValue = cchValue; \
326 return VINF_EOF; \
327 } \
328 return VERR_BUFFER_OVERFLOW; \
329 } \
330 NOREF(pCache);\
331 return VERR_EOF; \
332 } \
333 static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
334 size_t *pcchMatched) \
335 { \
336 const char *pszValue = a_GetStrExpr; \
337 size_t cchValue = strlen(pszValue); \
338 if ( cchValue >= cchMatch \
339 && ( !fIgnoreCase \
340 ? memcmp(pszValue, pchMatch, cchValue) == 0 \
341 : RTStrNICmp(pszValue, pchMatch, cchValue) == 0) ) \
342 { \
343 *pcchMatched = cchValue; \
344 return VINF_SUCCESS; \
345 } \
346 return VERR_MISMATCH; \
347 } \
348 typedef int RT_CONCAT(DummyColonType_,a_Name)
349
350/**
351 * Implements mapping a glob variable to an environment variable.
352 */
353#define RTPATHMATCHVAR_SIMPLE_ENVVAR(a_Name, a_pszEnvVar, a_cbMaxValue) \
354 static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
355 PRTPATHMATCHCACHE pCache) \
356 { \
357 if (iItem == 0) \
358 { \
359 int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, pszBuf, cbBuf, pcchValue); \
360 if (RT_SUCCESS(rc)) \
361 return VINF_EOF; \
362 if (rc != VERR_ENV_VAR_NOT_FOUND) \
363 return rc; \
364 } \
365 NOREF(pCache);\
366 return VERR_EOF; \
367 } \
368 static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
369 size_t *pcchMatched) \
370 { \
371 char szValue[a_cbMaxValue]; \
372 size_t cchValue; \
373 int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, szValue, sizeof(szValue), &cchValue); \
374 if ( RT_SUCCESS(rc) \
375 && cchValue >= cchMatch \
376 && ( !fIgnoreCase \
377 ? memcmp(szValue, pchMatch, cchValue) == 0 \
378 : RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
379 { \
380 *pcchMatched = cchValue; \
381 return VINF_SUCCESS; \
382 } \
383 return VERR_MISMATCH; \
384 } \
385 typedef int RT_CONCAT(DummyColonType_,a_Name)
386
387/**
388 * Implements mapping a glob variable to multiple environment variable values.
389 *
390 * @param a_Name The variable name.
391 * @param a_apszVarNames Assumes to be a global variable that RT_ELEMENTS
392 * works correctly on.
393 * @param a_cbMaxValue The max expected value size.
394 */
395#define RTPATHMATCHVAR_MULTIPLE_ENVVARS(a_Name, a_apszVarNames, a_cbMaxValue) \
396 static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
397 PRTPATHMATCHCACHE pCache) \
398 { \
399 if (iItem < RT_ELEMENTS(a_apszVarNames)) \
400 { \
401 int rc = RTEnvGetEx(RTENV_DEFAULT, a_apszVarNames[iItem], pszBuf, cbBuf, pcchValue); \
402 if (RT_SUCCESS(rc)) \
403 return iItem + 1 == RT_ELEMENTS(a_apszVarNames) ? VINF_EOF : VINF_SUCCESS; \
404 if (rc == VERR_ENV_VAR_NOT_FOUND) \
405 rc = VERR_TRY_AGAIN; \
406 return rc; \
407 } \
408 NOREF(pCache);\
409 return VERR_EOF; \
410 } \
411 static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
412 size_t *pcchMatched) \
413 { \
414 for (uint32_t iItem = 0; iItem < RT_ELEMENTS(a_apszVarNames); iItem++) \
415 { \
416 char szValue[a_cbMaxValue]; \
417 size_t cchValue; \
418 int rc = RTEnvGetEx(RTENV_DEFAULT, a_apszVarNames[iItem], szValue, sizeof(szValue), &cchValue);\
419 if ( RT_SUCCESS(rc) \
420 && cchValue >= cchMatch \
421 && ( !fIgnoreCase \
422 ? memcmp(szValue, pchMatch, cchValue) == 0 \
423 : RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
424 { \
425 *pcchMatched = cchValue; \
426 return VINF_SUCCESS; \
427 } \
428 } \
429 return VERR_MISMATCH; \
430 } \
431 typedef int RT_CONCAT(DummyColonType_,a_Name)
432
433
434RTPATHMATCHVAR_SIMPLE(Arch, RTBldCfgTargetArch());
435RTPATHMATCHVAR_SIMPLE(Bits, RT_XSTR(ARCH_BITS));
436#ifdef RT_OS_WINDOWS
437RTPATHMATCHVAR_SIMPLE_ENVVAR(WinAppData, "AppData", RTPATH_MAX);
438RTPATHMATCHVAR_SIMPLE_ENVVAR(WinProgramData, "ProgramData", RTPATH_MAX);
439RTPATHMATCHVAR_SIMPLE_ENVVAR(WinProgramFiles, "ProgramFiles", RTPATH_MAX);
440RTPATHMATCHVAR_SIMPLE_ENVVAR(WinCommonProgramFiles, "CommonProgramFiles", RTPATH_MAX);
441# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
442RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherProgramFiles, "ProgramFiles(x86)", RTPATH_MAX);
443RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherCommonProgramFiles, "CommonProgramFiles(x86)", RTPATH_MAX);
444# elif defined(RT_ARCH_ARM64) || defined(RT_ARCH_ARM32)
445RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherProgramFiles, "ProgramFiles(Arm)", RTPATH_MAX);
446RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherCommonProgramFiles, "CommonProgramFiles(Arm)", RTPATH_MAX);
447# else
448# error "Port ME!"
449# endif
450static const char * const a_apszWinProgramFilesVars[] =
451{
452 "ProgramFiles",
453# ifdef RT_ARCH_AMD64
454 "ProgramFiles(x86)",
455# elif defined(RT_ARCH_ARM64) || defined(RT_ARCH_ARM32)
456 "ProgramFiles(x86)",
457 "ProgramFiles(Arm)",
458# endif
459};
460RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllProgramFiles, a_apszWinProgramFilesVars, RTPATH_MAX);
461static const char * const a_apszWinCommonProgramFilesVars[] =
462{
463 "CommonProgramFiles",
464# ifdef RT_ARCH_AMD64
465 "CommonProgramFiles(x86)",
466# elif defined(RT_ARCH_ARM64) || defined(RT_ARCH_ARM32)
467 "CommonProgramFiles(x86)",
468 "CommonProgramFiles(Arm)",
469# endif
470};
471RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllCommonProgramFiles, a_apszWinCommonProgramFilesVars, RTPATH_MAX);
472#endif
473
474
475/**
476 * @interface_method_impl{RTPATHMATCHVAR,pfnQuery, Enumerates the PATH}
477 */
478static DECLCALLBACK(int) rtPathVarQuery_Path(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
479 PRTPATHMATCHCACHE pCache)
480{
481 RT_NOREF_PV(pCache);
482
483 /*
484 * Query the PATH value.
485 */
486/** @todo cache this in pCache with iItem and offset. */
487 char *pszPathFree = NULL;
488 char *pszPath = pszBuf;
489 size_t cchActual;
490 const char *pszVarNm = "PATH";
491 int rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPath, cbBuf, &cchActual);
492#ifdef RT_OS_WINDOWS
493 if (rc == VERR_ENV_VAR_NOT_FOUND)
494 rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm = "Path", pszPath, cbBuf, &cchActual);
495#endif
496 if (rc == VERR_BUFFER_OVERFLOW)
497 {
498 for (uint32_t iTry = 0;; iTry++)
499 {
500 size_t cbPathBuf = RT_ALIGN_Z(cchActual + 1 + 64 * iTry, 64);
501 pszPathFree = (char *)RTMemTmpAlloc(cbPathBuf);
502 rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPathFree, cbPathBuf, &cchActual);
503 if (RT_SUCCESS(rc))
504 break;
505 RTMemTmpFree(pszPathFree);
506 AssertReturn(cchActual >= cbPathBuf, VERR_INTERNAL_ERROR_3);
507 if (iTry >= 9)
508 return rc;
509 }
510 pszPath = pszPathFree;
511 }
512
513 /*
514 * Spool forward to the given PATH item.
515 */
516 rc = VERR_EOF;
517#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
518 const char chSep = ';';
519#else
520 const char chSep = ':';
521#endif
522 while (*pszPath != '\0')
523 {
524 char *pchSep = strchr(pszPath, chSep);
525
526 /* We ignore empty strings, which is probably not entirely correct,
527 but works better on DOS based system with many entries added
528 without checking whether there is a trailing separator or not.
529 Thus, the current directory is only searched if a '.' is present
530 in the PATH. */
531 if (pchSep == pszPath)
532 pszPath++;
533 else if (iItem > 0)
534 {
535 /* If we didn't find a separator, the item doesn't exists. Quit. */
536 if (!pchSep)
537 break;
538
539 pszPath = pchSep + 1;
540 iItem--;
541 }
542 else
543 {
544 /* We've reached the item we wanted. */
545 size_t cchComp = pchSep ? pchSep - pszPath : strlen(pszPath);
546 if (cchComp < cbBuf)
547 {
548 if (pszBuf != pszPath)
549 memmove(pszBuf, pszPath, cchComp);
550 pszBuf[cchComp] = '\0';
551 rc = pchSep ? VINF_SUCCESS : VINF_EOF;
552 }
553 else
554 rc = VERR_BUFFER_OVERFLOW;
555 *pcchValue = cchComp;
556 break;
557 }
558 }
559
560 if (pszPathFree)
561 RTMemTmpFree(pszPathFree);
562 return rc;
563}
564
565
566#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
567/**
568 * @interface_method_impl{RTPATHMATCHVAR,pfnQuery,
569 * The system drive letter + colon.}.
570 */
571static DECLCALLBACK(int) rtPathVarQuery_DosSystemDrive(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
572 PRTPATHMATCHCACHE pCache)
573{
574 RT_NOREF_PV(pCache);
575
576 if (iItem == 0)
577 {
578 AssertReturn(cbBuf >= 3, VERR_BUFFER_OVERFLOW);
579
580# ifdef RT_OS_WINDOWS
581 /* Since this is used at the start of a pattern, we assume
582 we've got more than enough buffer space. */
583 AssertReturn(g_pfnGetSystemWindowsDirectoryW, VERR_SYMBOL_NOT_FOUND);
584 PRTUTF16 pwszTmp = (PRTUTF16)pszBuf;
585 UINT cch = g_pfnGetSystemWindowsDirectoryW(pwszTmp, (UINT)(cbBuf / sizeof(WCHAR)));
586 if (cch >= 2)
587 {
588 RTUTF16 wcDrive = pwszTmp[0];
589 if ( RT_C_IS_ALPHA(wcDrive)
590 && pwszTmp[1] == ':')
591 {
592 pszBuf[0] = wcDrive;
593 pszBuf[1] = ':';
594 pszBuf[2] = '\0';
595 *pcchValue = 2;
596 return VINF_EOF;
597 }
598 }
599# else
600 ULONG ulDrive = ~(ULONG)0;
601 APIRET rc = DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, &ulDrive, sizeof(ulDrive));
602 ulDrive--; /* 1 = 'A' */
603 if ( rc == NO_ERROR
604 && ulDrive <= (ULONG)'Z')
605 {
606 pszBuf[0] = (char)ulDrive + 'A';
607 pszBuf[1] = ':';
608 pszBuf[2] = '\0';
609 *pcchValue = 2;
610 return VINF_EOF;
611 }
612# endif
613 return VERR_INTERNAL_ERROR_4;
614 }
615 return VERR_EOF;
616}
617#endif
618
619
620#ifdef RT_OS_WINDOWS
621/**
622 * @interface_method_impl{RTPATHMATCHVAR,pfnQuery,
623 * The system root directory (C:\Windows).}.
624 */
625static DECLCALLBACK(int) rtPathVarQuery_WinSystemRoot(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
626 PRTPATHMATCHCACHE pCache)
627{
628 RT_NOREF_PV(pCache);
629
630 if (iItem == 0)
631 {
632 Assert(pszBuf); Assert(cbBuf);
633 AssertReturn(g_pfnGetSystemWindowsDirectoryW, VERR_SYMBOL_NOT_FOUND);
634 RTUTF16 wszSystemRoot[MAX_PATH];
635 UINT cchSystemRoot = g_pfnGetSystemWindowsDirectoryW(wszSystemRoot, MAX_PATH);
636 if (cchSystemRoot > 0)
637 return RTUtf16ToUtf8Ex(wszSystemRoot, cchSystemRoot, &pszBuf, cbBuf, pcchValue);
638 return RTErrConvertFromWin32(GetLastError());
639 }
640 return VERR_EOF;
641}
642#endif
643
644#undef RTPATHMATCHVAR_SIMPLE
645#undef RTPATHMATCHVAR_SIMPLE_ENVVAR
646#undef RTPATHMATCHVAR_DOUBLE_ENVVAR
647
648/**
649 * Variables.
650 */
651static RTPATHMATCHVAR const g_aVariables[] =
652{
653 { RT_STR_TUPLE("Arch"), false, rtPathVarQuery_Arch, rtPathVarMatch_Arch },
654 { RT_STR_TUPLE("Bits"), false, rtPathVarQuery_Bits, rtPathVarMatch_Bits },
655 { RT_STR_TUPLE("Path"), true, rtPathVarQuery_Path, NULL },
656#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
657 { RT_STR_TUPLE("SystemDrive"), true, rtPathVarQuery_DosSystemDrive, NULL },
658#endif
659#ifdef RT_OS_WINDOWS
660 { RT_STR_TUPLE("SystemRoot"), true, rtPathVarQuery_WinSystemRoot, NULL },
661 { RT_STR_TUPLE("AppData"), true, rtPathVarQuery_WinAppData, rtPathVarMatch_WinAppData },
662 { RT_STR_TUPLE("ProgramData"), true, rtPathVarQuery_WinProgramData, rtPathVarMatch_WinProgramData },
663 { RT_STR_TUPLE("ProgramFiles"), true, rtPathVarQuery_WinProgramFiles, rtPathVarMatch_WinProgramFiles },
664 { RT_STR_TUPLE("OtherProgramFiles"), true, rtPathVarQuery_WinOtherProgramFiles, rtPathVarMatch_WinOtherProgramFiles },
665 { RT_STR_TUPLE("AllProgramFiles"), true, rtPathVarQuery_WinAllProgramFiles, rtPathVarMatch_WinAllProgramFiles },
666 { RT_STR_TUPLE("CommonProgramFiles"), true, rtPathVarQuery_WinCommonProgramFiles, rtPathVarMatch_WinCommonProgramFiles },
667 { RT_STR_TUPLE("OtherCommonProgramFiles"), true, rtPathVarQuery_WinOtherCommonProgramFiles, rtPathVarMatch_WinOtherCommonProgramFiles },
668 { RT_STR_TUPLE("AllCommonProgramFiles"), true, rtPathVarQuery_WinAllCommonProgramFiles, rtPathVarMatch_WinAllCommonProgramFiles },
669#endif
670};
671
672
673
674/**
675 * Handles a complicated set.
676 *
677 * A complicated set is either using ranges, character classes or code points
678 * outside the ASCII-7 range.
679 *
680 * @returns VINF_SUCCESS or VERR_MISMATCH. May also return UTF-8 decoding
681 * errors as well as VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED.
682 *
683 * @param ucInput The input code point to match with.
684 * @param pchSet The start of the set specification (after caret).
685 * @param cchSet The length of the set specification.
686 */
687static int rtPathMatchExecExtendedSet(RTUNICP ucInput, const char *pchSet, size_t cchSet)
688{
689 while (cchSet > 0)
690 {
691 RTUNICP ucSet;
692 int rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet);
693 AssertRCReturn(rc, rc);
694
695 /*
696 * Check for character class, collating symbol and equvalence class.
697 */
698 if (ucSet == '[' && cchSet > 0)
699 {
700 char chNext = *pchSet;
701 if (chNext == ':')
702 {
703#define CHECK_CHAR_CLASS(a_szClassNm, a_BoolTestExpr) \
704 if ( cchSet >= sizeof(a_szClassNm) \
705 && memcmp(pchSet, a_szClassNm "]", sizeof(a_szClassNm)) == 0) \
706 { \
707 if (a_BoolTestExpr) \
708 return VINF_SUCCESS; \
709 pchSet += sizeof(a_szClassNm); \
710 cchSet -= sizeof(a_szClassNm); \
711 continue; \
712 } do { } while (0)
713
714 CHECK_CHAR_CLASS(":alpha:", RTUniCpIsAlphabetic(ucInput));
715 CHECK_CHAR_CLASS(":alnum:", RTUniCpIsAlphabetic(ucInput) || RTUniCpIsDecDigit(ucInput)); /** @todo figure what's correct here and fix uni.h */
716 CHECK_CHAR_CLASS(":blank:", ucInput == ' ' || ucInput == '\t');
717 CHECK_CHAR_CLASS(":cntrl:", ucInput < 31 || ucInput == 127);
718 CHECK_CHAR_CLASS(":digit:", RTUniCpIsDecDigit(ucInput));
719 CHECK_CHAR_CLASS(":lower:", RTUniCpIsLower(ucInput));
720 CHECK_CHAR_CLASS(":print:", RTUniCpIsAlphabetic(ucInput) || (RT_C_IS_PRINT(ucInput) && ucInput < 127)); /** @todo fixme*/
721 CHECK_CHAR_CLASS(":punct:", RT_C_IS_PRINT(ucInput) && ucInput < 127); /** @todo fixme*/
722 CHECK_CHAR_CLASS(":space:", RTUniCpIsSpace(ucInput));
723 CHECK_CHAR_CLASS(":upper:", RTUniCpIsUpper(ucInput));
724 CHECK_CHAR_CLASS(":xdigit:", RTUniCpIsHexDigit(ucInput));
725 AssertMsgFailedReturn(("Unknown or malformed char class: '%.*s'\n", cchSet + 1, pchSet - 1),
726 VERR_PATH_GLOB_UNKNOWN_CHAR_CLASS);
727#undef CHECK_CHAR_CLASS
728 }
729 /** @todo implement collating symbol and equvalence class. */
730 else if (chNext == '=' || chNext == '.')
731 AssertFailedReturn(VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
732 }
733
734 /*
735 * Check for range (leading or final dash does not constitute a range).
736 */
737 if (cchSet > 1 && *pchSet == '-')
738 {
739 pchSet++; /* skip dash */
740 cchSet--;
741
742 RTUNICP ucSet2;
743 rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet2);
744 AssertRCReturn(rc, rc);
745 Assert(ucSet < ucSet2);
746 if (ucInput >= ucSet && ucInput <= ucSet2)
747 return VINF_SUCCESS;
748 }
749 /*
750 * Single char comparison.
751 */
752 else if (ucInput == ucSet)
753 return VINF_SUCCESS;
754 }
755 return VERR_MISMATCH;
756}
757
758
759/**
760 * Variable matching fallback using the query function.
761 *
762 * This must not be inlined as it consuming a lot of stack! Which is why it's
763 * placed a couple of functions away from the recursive rtPathExecMatch.
764 *
765 * @returns VINF_SUCCESS or VERR_MISMATCH.
766 * @param pchInput The current input position.
767 * @param cchInput The amount of input left..
768 * @param idxVar The variable table index.
769 * @param fIgnoreCase Whether to ignore case when comparing.
770 * @param pcchMatched Where to return how much we actually matched up.
771 * @param pCache Pointer to the path matching cache.
772 */
773DECL_NO_INLINE(static, int) rtPathMatchExecVariableFallback(const char *pchInput, size_t cchInput, uint16_t idxVar,
774 bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
775{
776 for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
777 {
778 char szValue[RTPATH_MAX];
779 size_t cchValue;
780 int rc = g_aVariables[idxVar].pfnQuery(iItem, szValue, sizeof(szValue), &cchValue, pCache);
781 if (RT_SUCCESS(rc))
782 {
783 if (cchValue <= cchInput)
784 {
785 if ( !fIgnoreCase
786 ? memcmp(pchInput, szValue, cchValue) == 0
787 : RTStrNICmp(pchInput, szValue, cchValue) == 0)
788 {
789 *pcchMatched = cchValue;
790 return VINF_SUCCESS;
791 }
792 }
793 if (rc == VINF_EOF)
794 return VERR_MISMATCH;
795 }
796 else if (rc == VERR_EOF)
797 return VERR_MISMATCH;
798 else
799 Assert(rc == VERR_BUFFER_OVERFLOW || rc == VERR_TRY_AGAIN);
800 }
801 AssertFailed();
802 return VERR_MISMATCH;
803}
804
805
806/**
807 * Variable matching worker.
808 *
809 * @returns VINF_SUCCESS or VERR_MISMATCH.
810 * @param pchInput The current input position.
811 * @param cchInput The amount of input left..
812 * @param idxVar The variable table index.
813 * @param fIgnoreCase Whether to ignore case when comparing.
814 * @param pcchMatched Where to return how much we actually matched up.
815 * @param pCache Pointer to the path matching cache.
816 */
817static int rtPathMatchExecVariable(const char *pchInput, size_t cchInput, uint16_t idxVar,
818 bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
819{
820 Assert(idxVar < RT_ELEMENTS(g_aVariables));
821 if (g_aVariables[idxVar].pfnMatch)
822 return g_aVariables[idxVar].pfnMatch(pchInput, cchInput, fIgnoreCase, pcchMatched);
823 return rtPathMatchExecVariableFallback(pchInput, cchInput, idxVar, fIgnoreCase, pcchMatched, pCache);
824}
825
826
827/**
828 * Variable matching worker.
829 *
830 * @returns VINF_SUCCESS or VERR_MISMATCH.
831 * @param pchInput The current input position.
832 * @param cchInput The amount of input left..
833 * @param pProg The first matching program instruction.
834 * @param pCache Pointer to the path matching cache.
835 */
836static int rtPathMatchExec(const char *pchInput, size_t cchInput, PCRTPATHMATCHCORE pProg, PRTPATHMATCHCACHE pCache)
837{
838 for (;;)
839 {
840 switch (pProg->enmOpCode)
841 {
842 case RTPATHMATCHOP_RETURN_MATCH_IF_AT_END:
843 return cchInput == 0 ? VINF_SUCCESS : VERR_MISMATCH;
844
845 case RTPATHMATCHOP_RETURN_MATCH:
846 return VINF_SUCCESS;
847
848 case RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT:
849 if ( cchInput > 2
850 || cchInput < 1
851 || pchInput[0] != '.'
852 || (cchInput == 2 && pchInput[1] != '.') )
853 return VINF_SUCCESS;
854 return VERR_MISMATCH;
855
856 case RTPATHMATCHOP_STRCMP:
857 if (pProg->cch > cchInput)
858 return VERR_MISMATCH;
859 if (memcmp(pchInput, pProg->pch, pProg->cch) != 0)
860 return VERR_MISMATCH;
861 cchInput -= pProg->cch;
862 pchInput += pProg->cch;
863 break;
864
865 case RTPATHMATCHOP_STRICMP:
866 if (pProg->cch > cchInput)
867 return VERR_MISMATCH;
868 if (RTStrNICmp(pchInput, pProg->pch, pProg->cch) != 0)
869 return VERR_MISMATCH;
870 cchInput -= pProg->cch;
871 pchInput += pProg->cch;
872 break;
873
874 case RTPATHMATCHOP_SKIP_ONE_CODEPOINT:
875 {
876 if (cchInput == 0)
877 return VERR_MISMATCH;
878 RTUNICP ucInputIgnore;
879 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
880 AssertRCReturn(rc, rc);
881 break;
882 }
883
884 case RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS:
885 {
886 uint16_t cCpsLeft = pProg->cch;
887 Assert(cCpsLeft > 1);
888 if (cCpsLeft > cchInput)
889 return VERR_MISMATCH;
890 while (cCpsLeft-- > 0)
891 {
892 RTUNICP ucInputIgnore;
893 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
894 if (RT_FAILURE(rc))
895 return rc == VERR_END_OF_STRING ? VERR_MISMATCH : rc;
896 }
897 break;
898 }
899
900 case RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7:
901 {
902 if (cchInput == 0)
903 return VERR_MISMATCH;
904 RTUNICP ucInput;
905 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
906 AssertRCReturn(rc, rc);
907 if (ucInput >= 0x80)
908 return VERR_MISMATCH;
909 if (memchr(pProg->pch, (char)ucInput, pProg->cch) == NULL)
910 return VERR_MISMATCH;
911 break;
912 }
913
914 case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7:
915 {
916 if (cchInput == 0)
917 return VERR_MISMATCH;
918 RTUNICP ucInput;
919 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
920 AssertRCReturn(rc, rc);
921 if (ucInput >= 0x80)
922 break;
923 if (memchr(pProg->pch, (char)ucInput, pProg->cch) != NULL)
924 return VERR_MISMATCH;
925 break;
926 }
927
928 case RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED:
929 {
930 if (cchInput == 0)
931 return VERR_MISMATCH;
932 RTUNICP ucInput;
933 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
934 AssertRCReturn(rc, rc);
935 rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
936 if (rc == VINF_SUCCESS)
937 break;
938 return rc;
939 }
940
941 case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED:
942 {
943 if (cchInput == 0)
944 return VERR_MISMATCH;
945 RTUNICP ucInput;
946 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
947 AssertRCReturn(rc, rc);
948 rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
949 if (rc == VERR_MISMATCH)
950 break;
951 if (rc == VINF_SUCCESS)
952 rc = VERR_MISMATCH;
953 return rc;
954 }
955
956 case RTPATHMATCHOP_VARIABLE_VALUE_CMP:
957 case RTPATHMATCHOP_VARIABLE_VALUE_ICMP:
958 {
959 size_t cchMatched = 0;
960 int rc = rtPathMatchExecVariable(pchInput, cchInput, pProg->uOp2,
961 pProg->enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP, &cchMatched, pCache);
962 if (rc == VINF_SUCCESS)
963 {
964 pchInput += cchMatched;
965 cchInput -= cchMatched;
966 break;
967 }
968 return rc;
969 }
970
971 /*
972 * This is the expensive one. It always completes the program.
973 */
974 case RTPATHMATCHOP_ZERO_OR_MORE:
975 {
976 if (cchInput < pProg->cch)
977 return VERR_MISMATCH;
978 size_t cchMatched = cchInput - pProg->cch;
979 do
980 {
981 int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
982 if (RT_SUCCESS(rc))
983 return rc;
984 } while (cchMatched-- > 0);
985 return VERR_MISMATCH;
986 }
987
988 /*
989 * Variant of the above that doesn't match '.' and '..' entries.
990 */
991 case RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT:
992 {
993 if (cchInput < pProg->cch)
994 return VERR_MISMATCH;
995 if ( cchInput <= 2
996 && cchInput > 0
997 && pchInput[0] == '.'
998 && (cchInput == 1 || pchInput[1] == '.') )
999 return VERR_MISMATCH;
1000 size_t cchMatched = cchInput - pProg->cch;
1001 do
1002 {
1003 int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
1004 if (RT_SUCCESS(rc))
1005 return rc;
1006 } while (cchMatched-- > 0);
1007 return VERR_MISMATCH;
1008 }
1009
1010 default:
1011 AssertMsgFailedReturn(("enmOpCode=%d\n", pProg->enmOpCode), VERR_INTERNAL_ERROR_3);
1012 }
1013
1014 pProg++;
1015 }
1016}
1017
1018
1019
1020
1021/**
1022 * Compiles a path matching program.
1023 *
1024 * @returns IPRT status code.
1025 * @param pchPattern The pattern to compile.
1026 * @param cchPattern The length of the pattern.
1027 * @param fIgnoreCase Whether to ignore case or not when doing the
1028 * actual matching later on.
1029 * @param pAllocator Pointer to the instruction allocator & result
1030 * array. The compiled "program" starts at
1031 * PRTPATHMATCHALLOC::paInstructions[PRTPATHMATCHALLOC::iNext]
1032 * (input iNext value).
1033 *
1034 * @todo Expose this matching code and also use it for RTDirOpenFiltered
1035 */
1036static int rtPathMatchCompile(const char *pchPattern, size_t cchPattern, bool fIgnoreCase, PRTPATHMATCHALLOC pAllocator)
1037{
1038 /** @todo PORTME: big endian. */
1039 static const uint8_t s_bmMetaChars[256/8] =
1040 {
1041 0x00, 0x00, 0x00, 0x00, /* 0 thru 31 */
1042 0x10, 0x04, 0x00, 0x80, /* 32 thru 63 */
1043 0x00, 0x00, 0x00, 0x08, /* 64 thru 95 */
1044 0x00, 0x00, 0x00, 0x00, /* 96 thru 127 */
1045 /* UTF-8 multibyte: */
1046 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1047 };
1048 Assert(ASMBitTest(s_bmMetaChars, '$')); AssertCompile('$' == 0x24 /*36*/);
1049 Assert(ASMBitTest(s_bmMetaChars, '*')); AssertCompile('*' == 0x2a /*42*/);
1050 Assert(ASMBitTest(s_bmMetaChars, '?')); AssertCompile('?' == 0x3f /*63*/);
1051 Assert(ASMBitTest(s_bmMetaChars, '[')); AssertCompile('[' == 0x5b /*91*/);
1052
1053 /*
1054 * For checking for the first instruction.
1055 */
1056 uint16_t const iFirst = pAllocator->iNext;
1057
1058 /*
1059 * This is for tracking zero-or-more instructions and for calculating
1060 * the minimum amount of input required for it to be considered.
1061 */
1062 uint16_t aiZeroOrMore[RTPATHMATCH_MAX_ZERO_OR_MORE];
1063 uint8_t cZeroOrMore = 0;
1064 size_t offInput = 0;
1065
1066 /*
1067 * Loop thru the pattern and translate it into string matching instructions.
1068 */
1069 for (;;)
1070 {
1071 /*
1072 * Allocate the next instruction.
1073 */
1074 if (pAllocator->iNext >= pAllocator->cAllocated)
1075 {
1076 uint32_t cNew = pAllocator->cAllocated ? pAllocator->cAllocated * 2 : 2;
1077 void *pvNew = RTMemRealloc(pAllocator->paInstructions, cNew * sizeof(pAllocator->paInstructions[0]));
1078 AssertReturn(pvNew, VERR_NO_MEMORY);
1079 pAllocator->paInstructions = (PRTPATHMATCHCORE)pvNew;
1080 pAllocator->cAllocated = cNew;
1081 }
1082 PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[pAllocator->iNext++];
1083 pInstr->pch = pchPattern;
1084 pInstr->cch = 0;
1085 pInstr->uOp2 = 0;
1086
1087 /*
1088 * Special case: End of pattern.
1089 */
1090 if (!cchPattern)
1091 {
1092 pInstr->enmOpCode = RTPATHMATCHOP_RETURN_MATCH_IF_AT_END;
1093 break;
1094 }
1095
1096 /*
1097 * Parse the next bit of the pattern.
1098 */
1099 char ch = *pchPattern;
1100 if (ASMBitTest(s_bmMetaChars, (uint8_t)ch))
1101 {
1102 /*
1103 * Zero or more characters wildcard.
1104 */
1105 if (ch == '*')
1106 {
1107 /* Skip extra asterisks. */
1108 do
1109 {
1110 cchPattern--;
1111 pchPattern++;
1112 } while (cchPattern > 0 && *pchPattern == '*');
1113
1114 /* There is a special optimization for trailing '*'. */
1115 pInstr->cch = 1;
1116 if (cchPattern == 0)
1117 {
1118 pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
1119 ? RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_RETURN_MATCH;
1120 break;
1121 }
1122
1123 pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
1124 ? RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_ZERO_OR_MORE;
1125 pInstr->uOp2 = (uint16_t)offInput;
1126 AssertReturn(cZeroOrMore < RT_ELEMENTS(aiZeroOrMore), VERR_OUT_OF_RANGE);
1127 aiZeroOrMore[cZeroOrMore] = (uint16_t)(pInstr - pAllocator->paInstructions);
1128
1129 /* cchInput unchanged, zero-or-more matches. */
1130 continue;
1131 }
1132
1133 /*
1134 * Single character wildcard.
1135 */
1136 if (ch == '?')
1137 {
1138 /* Count them if more. */
1139 uint16_t cchQms = 1;
1140 while (cchQms < cchPattern && pchPattern[cchQms] == '?')
1141 cchQms++;
1142
1143 pInstr->cch = cchQms;
1144 pInstr->enmOpCode = cchQms == 1 ? RTPATHMATCHOP_SKIP_ONE_CODEPOINT : RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS;
1145
1146 cchPattern -= cchQms;
1147 pchPattern += cchQms;
1148 offInput += cchQms;
1149 continue;
1150 }
1151
1152 /*
1153 * Character in set.
1154 *
1155 * Note that we skip the first char in the set as that is the only place
1156 * ']' can be placed if one desires to explicitly include it in the set.
1157 * To make life a bit more interesting, [:class:] is allowed inside the
1158 * set, so we have to do the counting game to find the end.
1159 */
1160 if (ch == '[')
1161 {
1162 if ( cchPattern > 2
1163 && (const char *)memchr(pchPattern + 2, ']', cchPattern) != NULL)
1164 {
1165
1166 /* Check for not-in. */
1167 bool fInverted = false;
1168 size_t offStart = 1;
1169 if (pchPattern[offStart] == '^')
1170 {
1171 fInverted = true;
1172 offStart++;
1173 }
1174
1175 /* Special case for ']' as the first char, it doesn't indicate closing then. */
1176 size_t off = offStart;
1177 if (pchPattern[off] == ']')
1178 off++;
1179
1180 bool fExtended = false;
1181 while (off < cchPattern)
1182 {
1183 ch = pchPattern[off++];
1184 if (ch == '[')
1185 {
1186 if (off < cchPattern)
1187 {
1188 char chOpen = pchPattern[off];
1189 if ( chOpen == ':'
1190 || chOpen == '='
1191 || chOpen == '.')
1192 {
1193 off++;
1194 const char *pchFound = (const char *)memchr(&pchPattern[off], ']', cchPattern - off);
1195 if ( pchFound
1196 && pchFound[-1] == chOpen)
1197 {
1198 fExtended = true;
1199 off = pchFound - pchPattern + 1;
1200 }
1201 else
1202 AssertFailed();
1203 }
1204 }
1205 }
1206 /* Check for closing. */
1207 else if (ch == ']')
1208 break;
1209 /* Check for range expression, promote to extended if this happens. */
1210 else if ( ch == '-'
1211 && off != offStart + 1
1212 && off < cchPattern
1213 && pchPattern[off] != ']')
1214 fExtended = true;
1215 /* UTF-8 multibyte chars forces us to use the extended version too. */
1216 else if ((uint8_t)ch >= 0x80)
1217 fExtended = true;
1218 }
1219
1220 if (ch == ']')
1221 {
1222 pInstr->pch = &pchPattern[offStart];
1223 pInstr->cch = (uint16_t)(off - offStart - 1);
1224 if (!fExtended)
1225 pInstr->enmOpCode = !fInverted
1226 ? RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7 : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7;
1227 else
1228 pInstr->enmOpCode = !fInverted
1229 ? RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED
1230 : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED;
1231 pchPattern += off;
1232 cchPattern -= off;
1233 offInput += 1;
1234 continue;
1235 }
1236
1237 /* else: invalid, treat it as */
1238 AssertFailed();
1239 }
1240 }
1241 /*
1242 * Variable matching.
1243 */
1244 else if (ch == '$')
1245 {
1246 const char *pchFound;
1247 if ( cchPattern > 3
1248 && pchPattern[1] == '{'
1249 && (pchFound = (const char *)memchr(pchPattern + 2, '}', cchPattern)) != NULL
1250 && pchFound != &pchPattern[2])
1251 {
1252 /* skip to the variable name. */
1253 pchPattern += 2;
1254 cchPattern -= 2;
1255 size_t cchVarNm = pchFound - pchPattern;
1256
1257 /* Look it up. */
1258 uint32_t iVar;
1259 for (iVar = 0; iVar < RT_ELEMENTS(g_aVariables); iVar++)
1260 if ( g_aVariables[iVar].cchName == cchVarNm
1261 && memcmp(g_aVariables[iVar].pszName, pchPattern, cchVarNm) == 0)
1262 break;
1263 if (iVar < RT_ELEMENTS(g_aVariables))
1264 {
1265 pInstr->uOp2 = (uint16_t)iVar;
1266 pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_VARIABLE_VALUE_CMP : RTPATHMATCHOP_VARIABLE_VALUE_ICMP;
1267 pInstr->pch = pchPattern; /* not necessary */
1268 pInstr->cch = (uint16_t)cchPattern; /* ditto */
1269 pchPattern += cchVarNm + 1;
1270 cchPattern -= cchVarNm + 1;
1271 AssertMsgReturn(!g_aVariables[iVar].fFirstOnly || iFirst + 1U == pAllocator->iNext,
1272 ("Glob variable '%s' should be first\n", g_aVariables[iVar].pszName),
1273 VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
1274 /* cchInput unchanged, value can be empty. */
1275 continue;
1276 }
1277 AssertMsgFailedReturn(("Unknown path matching variable '%.*s'\n", cchVarNm, pchPattern),
1278 VERR_PATH_MATCH_UNKNOWN_VARIABLE);
1279 }
1280 }
1281 else
1282 AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
1283 }
1284
1285 /*
1286 * Plain text. Look for the next meta char.
1287 */
1288 uint32_t cchPlain = 1;
1289 while (cchPlain < cchPattern)
1290 {
1291 ch = pchPattern[cchPlain];
1292 if (!ASMBitTest(s_bmMetaChars, (uint8_t)ch))
1293 { /* probable */ }
1294 else if ( ch == '?'
1295 || ch == '*')
1296 break;
1297 else if (ch == '$')
1298 {
1299 const char *pchFound;
1300 if ( cchPattern > cchPlain + 3
1301 && pchPattern[cchPlain + 1] == '{'
1302 && (pchFound = (const char *)memchr(&pchPattern[cchPlain + 2], '}', cchPattern - cchPlain - 2)) != NULL
1303 && pchFound != &pchPattern[cchPlain + 2])
1304 break;
1305 }
1306 else if (ch == '[')
1307 {
1308 /* We don't put a lot of effort into getting this 100% right here,
1309 no point it complicating things for malformed expressions. */
1310 if ( cchPattern > cchPlain + 2
1311 && memchr(&pchPattern[cchPlain + 2], ']', cchPattern - cchPlain - 1) != NULL)
1312 break;
1313 }
1314 else
1315 AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
1316 cchPlain++;
1317 }
1318 pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_STRCMP : RTPATHMATCHOP_STRICMP;
1319 pInstr->cch = cchPlain;
1320 Assert(pInstr->pch == pchPattern);
1321 Assert(pInstr->uOp2 == 0);
1322 pchPattern += cchPlain;
1323 cchPattern -= cchPlain;
1324 offInput += cchPlain;
1325 }
1326
1327 /*
1328 * Optimize zero-or-more matching.
1329 */
1330 while (cZeroOrMore-- > 0)
1331 {
1332 PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[aiZeroOrMore[cZeroOrMore]];
1333 pInstr->uOp2 = (uint16_t)(offInput - pInstr->uOp2);
1334 }
1335
1336 /** @todo It's possible to use offInput to inject a instruction for checking
1337 * minimum input length at the start of the program. Not sure it's
1338 * worth it though, unless it's long a complicated expression... */
1339 return VINF_SUCCESS;
1340}
1341
1342
1343/**
1344 * Parses the glob pattern.
1345 *
1346 * This compiles filename matching programs for each component and determins the
1347 * optimal search strategy for them.
1348 *
1349 * @returns IPRT status code.
1350 * @param pGlob The glob instance data.
1351 * @param pszPattern The pattern to parse.
1352 * @param pParsed The RTPathParse output for the pattern.
1353 * @param fFlags The glob flags (same as pGlob->fFlags).
1354 */
1355static int rtPathGlobParse(PRTPATHGLOB pGlob, const char *pszPattern, PRTPATHPARSED pParsed, uint32_t fFlags)
1356{
1357 AssertReturn(pParsed->cComps > 0, VERR_INVALID_PARAMETER); /* shouldn't happen */
1358 uint32_t iComp = 0;
1359
1360 /*
1361 * If we've got a rootspec, mark it as plain. On platforms with
1362 * drive letter and/or UNC we don't allow wildcards or such in
1363 * the drive letter spec or UNC server name. (At least not yet.)
1364 */
1365 if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
1366 {
1367 AssertReturn(pParsed->aComps[0].cch < sizeof(pGlob->szPath) - 1, VERR_FILENAME_TOO_LONG);
1368 memcpy(pGlob->szPath, &pszPattern[pParsed->aComps[0].off], pParsed->aComps[0].cch);
1369 pGlob->offFirstPath = pParsed->aComps[0].cch;
1370 pGlob->iFirstComp = iComp = 1;
1371 }
1372 else
1373 {
1374 const char * const pszComp = &pszPattern[pParsed->aComps[0].off];
1375
1376 /*
1377 * The tilde is only applicable to the first component, expand it
1378 * immediately.
1379 */
1380 if ( *pszComp == '~'
1381 && !(fFlags & RTPATHGLOB_F_NO_TILDE))
1382 {
1383 if (pParsed->aComps[0].cch == 1)
1384 {
1385 int rc = RTPathUserHome(pGlob->szPath, sizeof(pGlob->szPath) - 1);
1386 AssertRCReturn(rc, rc);
1387 }
1388 else
1389 AssertMsgFailedReturn(("'%.*s' is not supported yet\n", pszComp, pParsed->aComps[0].cch),
1390 VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
1391 pGlob->offFirstPath = (uint16_t)RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
1392 pGlob->iFirstComp = iComp = 1;
1393 }
1394 }
1395
1396 /*
1397 * Process the other components.
1398 */
1399 bool fStarStar = false;
1400 for (; iComp < pParsed->cComps; iComp++)
1401 {
1402 const char *pszComp = &pszPattern[pParsed->aComps[iComp].off];
1403 uint16_t cchComp = pParsed->aComps[iComp].cch;
1404 Assert(pGlob->aComps[iComp].fNormal == false);
1405
1406 pGlob->aComps[iComp].fDir = iComp + 1 < pParsed->cComps || (fFlags & RTPATHGLOB_F_ONLY_DIRS);
1407 if ( cchComp != 2
1408 || pszComp[0] != '*'
1409 || pszComp[1] != '*'
1410 || (fFlags & RTPATHGLOB_F_NO_STARSTAR) )
1411 {
1412 /* Compile the pattern. */
1413 uint16_t const iMatchProg = pGlob->MatchInstrAlloc.iNext;
1414 pGlob->aComps[iComp].iMatchProg = iMatchProg;
1415 int rc = rtPathMatchCompile(pszComp, cchComp, RT_BOOL(fFlags & RTPATHGLOB_F_IGNORE_CASE),
1416 &pGlob->MatchInstrAlloc);
1417 if (RT_FAILURE(rc))
1418 return rc;
1419
1420 /* Check for plain text as well as full variable matching (not applicable after '**'). */
1421 uint16_t const cInstructions = pGlob->MatchInstrAlloc.iNext - iMatchProg;
1422 if ( cInstructions == 2
1423 && !fStarStar
1424 && pGlob->MatchInstrAlloc.paInstructions[iMatchProg + 1].enmOpCode == RTPATHMATCHOP_RETURN_MATCH_IF_AT_END)
1425 {
1426 if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRCMP
1427 || pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRICMP)
1428 pGlob->aComps[iComp].fPlain = true;
1429 else if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_CMP
1430 || pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP)
1431 {
1432 pGlob->aComps[iComp].fExpVariable = true;
1433 AssertMsgReturn( iComp == 0
1434 || !g_aVariables[pGlob->MatchInstrAlloc.paInstructions[iMatchProg].uOp2].fFirstOnly,
1435 ("Glob variable '%.*s' can only be used as the path component.\n", cchComp, pszComp),
1436 VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
1437 }
1438 else
1439 pGlob->aComps[iComp].fNormal = true;
1440 }
1441 else
1442 pGlob->aComps[iComp].fNormal = true;
1443 }
1444 else
1445 {
1446 /* Recursive "**" matching. */
1447 pGlob->aComps[iComp].fNormal = false;
1448 pGlob->aComps[iComp].fStarStar = true;
1449 AssertReturn(!fStarStar, VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED); /** @todo implement multiple '**' sequences in a pattern. */
1450 fStarStar = true;
1451 }
1452 }
1453 pGlob->aComps[pParsed->cComps - 1].fFinal = true;
1454
1455 return VINF_SUCCESS;
1456}
1457
1458
1459/**
1460 * This is for skipping overly long directories entries.
1461 *
1462 * Since our directory entry buffer can hold filenames of RTPATH_MAX bytes, we
1463 * can safely skip filenames that are longer. There are very few file systems
1464 * that can actually store filenames longer than 255 bytes at time of coding
1465 * (2015-09), and extremely few which can exceed 4096 (RTPATH_MAX) bytes.
1466 *
1467 * @returns IPRT status code.
1468 * @param hDir The directory handle.
1469 * @param cbNeeded The required entry size.
1470 */
1471DECL_NO_INLINE(static, int) rtPathGlobSkipDirEntry(RTDIR hDir, size_t cbNeeded)
1472{
1473 int rc = VERR_BUFFER_OVERFLOW;
1474 cbNeeded = RT_ALIGN_Z(cbNeeded, 16);
1475 PRTDIRENTRY pDirEntry = (PRTDIRENTRY)RTMemTmpAlloc(cbNeeded);
1476 if (pDirEntry)
1477 {
1478 rc = RTDirRead(hDir, pDirEntry, &cbNeeded);
1479 RTMemTmpFree(pDirEntry);
1480 }
1481 return rc;
1482}
1483
1484
1485/**
1486 * Adds a result.
1487 *
1488 * @returns IPRT status code.
1489 * @retval VINF_CALLBACK_RETURN if we can stop searching.
1490 *
1491 * @param pGlob The glob instance data.
1492 * @param cchPath The number of bytes to add from pGlob->szPath.
1493 * @param uType The RTDIRENTRYTYPE value.
1494 */
1495DECL_NO_INLINE(static, int) rtPathGlobAddResult(PRTPATHGLOB pGlob, size_t cchPath, uint8_t uType)
1496{
1497 if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
1498 {
1499 PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + 1]));
1500 if (pEntry)
1501 {
1502 pEntry->uType = uType;
1503 pEntry->cchPath = (uint16_t)cchPath;
1504 memcpy(pEntry->szPath, pGlob->szPath, cchPath);
1505 pEntry->szPath[cchPath] = '\0';
1506
1507 pEntry->pNext = NULL;
1508 *pGlob->ppNext = pEntry;
1509 pGlob->ppNext = &pEntry->pNext;
1510 pGlob->cResults++;
1511
1512 if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
1513 return VINF_SUCCESS;
1514 return VINF_CALLBACK_RETURN;
1515 }
1516 return VERR_NO_MEMORY;
1517 }
1518 return VERR_TOO_MUCH_DATA;
1519}
1520
1521
1522/**
1523 * Adds a result, constructing the path from two string.
1524 *
1525 * @returns IPRT status code.
1526 * @retval VINF_CALLBACK_RETURN if we can stop searching.
1527 *
1528 * @param pGlob The glob instance data.
1529 * @param cchPath The number of bytes to add from pGlob->szPath.
1530 * @param pchName The string (usual filename) to append to the szPath.
1531 * @param cchName The length of the string to append.
1532 * @param uType The RTDIRENTRYTYPE value.
1533 */
1534DECL_NO_INLINE(static, int) rtPathGlobAddResult2(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
1535 uint8_t uType)
1536{
1537 if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
1538 {
1539 PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
1540 if (pEntry)
1541 {
1542 pEntry->uType = uType;
1543 pEntry->cchPath = (uint16_t)(cchPath + cchName);
1544 memcpy(pEntry->szPath, pGlob->szPath, cchPath);
1545 memcpy(&pEntry->szPath[cchPath], pchName, cchName);
1546 pEntry->szPath[cchPath + cchName] = '\0';
1547
1548 pEntry->pNext = NULL;
1549 *pGlob->ppNext = pEntry;
1550 pGlob->ppNext = &pEntry->pNext;
1551 pGlob->cResults++;
1552
1553 if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
1554 return VINF_SUCCESS;
1555 return VINF_CALLBACK_RETURN;
1556 }
1557 return VERR_NO_MEMORY;
1558 }
1559 return VERR_TOO_MUCH_DATA;
1560}
1561
1562
1563/**
1564 * Prepares a result, constructing the path from two string.
1565 *
1566 * The caller must call either rtPathGlobCommitResult or
1567 * rtPathGlobRollbackResult to complete the operation.
1568 *
1569 * @returns IPRT status code.
1570 * @retval VINF_CALLBACK_RETURN if we can stop searching.
1571 *
1572 * @param pGlob The glob instance data.
1573 * @param cchPath The number of bytes to add from pGlob->szPath.
1574 * @param pchName The string (usual filename) to append to the szPath.
1575 * @param cchName The length of the string to append.
1576 * @param uType The RTDIRENTRYTYPE value.
1577 */
1578DECL_NO_INLINE(static, int) rtPathGlobAlmostAddResult(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
1579 uint8_t uType)
1580{
1581 if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
1582 {
1583 PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
1584 if (pEntry)
1585 {
1586 pEntry->uType = uType;
1587 pEntry->cchPath = (uint16_t)(cchPath + cchName);
1588 memcpy(pEntry->szPath, pGlob->szPath, cchPath);
1589 memcpy(&pEntry->szPath[cchPath], pchName, cchName);
1590 pEntry->szPath[cchPath + cchName] = '\0';
1591
1592 pEntry->pNext = NULL;
1593 *pGlob->ppNext = pEntry;
1594 /* Note! We don't update ppNext here, that is done in rtPathGlobCommitResult. */
1595
1596 if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
1597 return VINF_SUCCESS;
1598 return VINF_CALLBACK_RETURN;
1599 }
1600 return VERR_NO_MEMORY;
1601 }
1602 return VERR_TOO_MUCH_DATA;
1603}
1604
1605
1606/**
1607 * Commits a pending result from rtPathGlobAlmostAddResult.
1608 *
1609 * @param pGlob The glob instance data.
1610 * @param uType The RTDIRENTRYTYPE value.
1611 */
1612static void rtPathGlobCommitResult(PRTPATHGLOB pGlob, uint8_t uType)
1613{
1614 PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
1615 AssertPtr(pEntry);
1616 pEntry->uType = uType;
1617 pGlob->ppNext = &pEntry->pNext;
1618 pGlob->cResults++;
1619}
1620
1621
1622/**
1623 * Rolls back a pending result from rtPathGlobAlmostAddResult.
1624 *
1625 * @param pGlob The glob instance data.
1626 */
1627static void rtPathGlobRollbackResult(PRTPATHGLOB pGlob)
1628{
1629 PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
1630 AssertPtr(pEntry);
1631 RTMemFree(pEntry);
1632 *pGlob->ppNext = NULL;
1633}
1634
1635
1636
1637/**
1638 * Whether to call rtPathGlobExecRecursiveVarExp for the next component.
1639 *
1640 * @returns true / false.
1641 * @param pGlob The glob instance data.
1642 * @param offPath The next path offset/length.
1643 * @param iComp The next component.
1644 */
1645DECLINLINE(bool) rtPathGlobExecIsExpVar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1646{
1647 return pGlob->aComps[iComp].fExpVariable
1648 && ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
1649 || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
1650}
1651
1652/**
1653 * Whether to call rtPathGlobExecRecursivePlainText for the next component.
1654 *
1655 * @returns true / false.
1656 * @param pGlob The glob instance data.
1657 * @param offPath The next path offset/length.
1658 * @param iComp The next component.
1659 */
1660DECLINLINE(bool) rtPathGlobExecIsPlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1661{
1662 return pGlob->aComps[iComp].fPlain
1663 && ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
1664 || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
1665}
1666
1667
1668/**
1669 * Helper for rtPathGlobExecRecursiveVarExp and rtPathGlobExecRecursivePlainText
1670 * that compares a file mode mask with dir/no-dir wishes of the caller.
1671 *
1672 * @returns true if match, false if not.
1673 * @param pGlob The glob instance data.
1674 * @param fMode The file mode (only the type is used).
1675 */
1676DECLINLINE(bool) rtPathGlobExecIsMatchFinalWithFileMode(PRTPATHGLOB pGlob, RTFMODE fMode)
1677{
1678 if (!(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)))
1679 return true;
1680 return RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS) == RTFS_IS_DIRECTORY(fMode);
1681}
1682
1683
1684/**
1685 * Recursive globbing - star-star mode.
1686 *
1687 * @returns IPRT status code.
1688 * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1689 *
1690 * @param pGlob The glob instance data.
1691 * @param offPath The current path offset/length.
1692 * @param iStarStarComp The star-star component index.
1693 * @param offStarStarPath The offset of the star-star component in the
1694 * pattern path.
1695 */
1696DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp,
1697 size_t offStarStarPath)
1698{
1699 /** @todo implement multi subdir matching. */
1700 RT_NOREF_PV(pGlob);
1701 RT_NOREF_PV(offPath);
1702 RT_NOREF_PV(iStarStarComp);
1703 RT_NOREF_PV(offStarStarPath);
1704 return VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED;
1705}
1706
1707
1708
1709/**
1710 * Recursive globbing - variable expansion optimization.
1711 *
1712 * @returns IPRT status code.
1713 * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1714 *
1715 * @param pGlob The glob instance data.
1716 * @param offPath The current path offset/length.
1717 * @param iComp The current component.
1718 */
1719DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1720{
1721 Assert(iComp < pGlob->pParsed->cComps);
1722 Assert(pGlob->szPath[offPath] == '\0');
1723 Assert(pGlob->aComps[iComp].fExpVariable);
1724 Assert(!pGlob->aComps[iComp].fPlain);
1725 Assert(!pGlob->aComps[iComp].fStarStar);
1726 Assert(rtPathGlobExecIsExpVar(pGlob, offPath, iComp));
1727
1728 /*
1729 * Fish the variable index out of the first matching instruction.
1730 */
1731 Assert( pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1732 == RTPATHMATCHOP_VARIABLE_VALUE_CMP
1733 || pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1734 == RTPATHMATCHOP_VARIABLE_VALUE_ICMP);
1735 uint16_t const iVar = pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].uOp2;
1736
1737 /*
1738 * Enumerate all the variable, giving them the plain text treatment.
1739 */
1740 for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
1741 {
1742 size_t cch;
1743 int rcVar = g_aVariables[iVar].pfnQuery(iItem, &pGlob->szPath[offPath], sizeof(pGlob->szPath) - offPath, &cch,
1744 &pGlob->MatchCache);
1745 if (RT_SUCCESS(rcVar))
1746 {
1747 Assert(pGlob->szPath[offPath + cch] == '\0');
1748
1749 int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1750 if (RT_SUCCESS(rc))
1751 {
1752 if (pGlob->aComps[iComp].fFinal)
1753 {
1754 if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
1755 {
1756 rc = rtPathGlobAddResult(pGlob, cch,
1757 (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
1758 >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
1759 if (rc != VINF_SUCCESS)
1760 return rc;
1761 }
1762 }
1763 else if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
1764 {
1765 Assert(pGlob->aComps[iComp].fDir);
1766 cch = RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
1767 if (cch > 0)
1768 {
1769 if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
1770 rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
1771 else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
1772 rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
1773 else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1774 rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
1775 else
1776 rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
1777 if (rc != VINF_SUCCESS)
1778 return rc;
1779 }
1780 else
1781 pGlob->cPathOverflows++;
1782 }
1783 }
1784 /* else: file doesn't exist or something else is wrong, ignore this. */
1785 if (rcVar == VINF_EOF)
1786 return VINF_SUCCESS;
1787 }
1788 else if (rcVar == VERR_EOF)
1789 return VINF_SUCCESS;
1790 else if (rcVar != VERR_TRY_AGAIN)
1791 {
1792 Assert(rcVar == VERR_BUFFER_OVERFLOW);
1793 pGlob->cPathOverflows++;
1794 }
1795 }
1796 AssertFailedReturn(VINF_SUCCESS); /* Too many items returned, probably buggy query method. */
1797}
1798
1799
1800/**
1801 * Recursive globbing - plain text optimization.
1802 *
1803 * @returns IPRT status code.
1804 * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1805 *
1806 * @param pGlob The glob instance data.
1807 * @param offPath The current path offset/length.
1808 * @param iComp The current component.
1809 */
1810DECL_NO_INLINE(static, int) rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1811{
1812 /*
1813 * Instead of recursing, we loop thru adjacent plain text components.
1814 */
1815 for (;;)
1816 {
1817 /*
1818 * Preconditions.
1819 */
1820 Assert(iComp < pGlob->pParsed->cComps);
1821 Assert(pGlob->szPath[offPath] == '\0');
1822 Assert(pGlob->aComps[iComp].fPlain);
1823 Assert(!pGlob->aComps[iComp].fExpVariable);
1824 Assert(!pGlob->aComps[iComp].fStarStar);
1825 Assert(rtPathGlobExecIsPlainText(pGlob, offPath, iComp));
1826 Assert(pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1827 == RTPATHMATCHOP_STRCMP
1828 || pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1829 == RTPATHMATCHOP_STRICMP);
1830
1831 /*
1832 * Add the plain text component to the path.
1833 */
1834 size_t const cch = pGlob->pParsed->aComps[iComp].cch;
1835 if (cch + pGlob->aComps[iComp].fDir < sizeof(pGlob->szPath) - offPath)
1836 {
1837 memcpy(&pGlob->szPath[offPath], &pGlob->pszPattern[pGlob->pParsed->aComps[iComp].off], cch);
1838 offPath += cch;
1839 pGlob->szPath[offPath] = '\0';
1840
1841 /*
1842 * Check if it exists.
1843 */
1844 int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1845 if (RT_SUCCESS(rc))
1846 {
1847 if (pGlob->aComps[iComp].fFinal)
1848 {
1849 if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
1850 return rtPathGlobAddResult(pGlob, offPath,
1851 (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
1852 >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
1853 break;
1854 }
1855
1856 if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
1857 {
1858 Assert(pGlob->aComps[iComp].fDir);
1859 pGlob->szPath[offPath++] = RTPATH_SLASH;
1860 pGlob->szPath[offPath] = '\0';
1861
1862 iComp++;
1863 if (rtPathGlobExecIsExpVar(pGlob, offPath, iComp))
1864 return rtPathGlobExecRecursiveVarExp(pGlob, offPath, iComp);
1865 if (!rtPathGlobExecIsPlainText(pGlob, offPath, iComp))
1866 return rtPathGlobExecRecursiveGeneric(pGlob, offPath, iComp);
1867 if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1868 return rtPathGlobExecRecursiveStarStar(pGlob, offPath, iComp, offPath);
1869
1870 /* Continue with the next plain text component. */
1871 continue;
1872 }
1873 }
1874 /* else: file doesn't exist or something else is wrong, ignore this. */
1875 }
1876 else
1877 pGlob->cPathOverflows++;
1878 break;
1879 }
1880 return VINF_SUCCESS;
1881}
1882
1883
1884/**
1885 * Recursive globbing - generic.
1886 *
1887 * @returns IPRT status code.
1888 * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1889 *
1890 * @param pGlob The glob instance data.
1891 * @param offPath The current path offset/length.
1892 * @param iComp The current component.
1893 */
1894DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1895{
1896 /*
1897 * Enumerate entire directory and match each entry.
1898 */
1899 RTDIR hDir;
1900 int rc = RTDirOpen(&hDir, offPath ? pGlob->szPath : ".");
1901 if (RT_SUCCESS(rc))
1902 {
1903 for (;;)
1904 {
1905 size_t cch = sizeof(pGlob->u);
1906 rc = RTDirRead(hDir, &pGlob->u.DirEntry, &cch);
1907 if (RT_SUCCESS(rc))
1908 {
1909 if (pGlob->aComps[iComp].fFinal)
1910 {
1911 /*
1912 * Final component: Check if it matches the current pattern.
1913 */
1914 if ( !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS))
1915 || RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
1916 == (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
1917 || pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
1918 {
1919 rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1920 &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
1921 &pGlob->MatchCache);
1922 if (RT_SUCCESS(rc))
1923 {
1924 /* Construct the result. */
1925 if ( pGlob->u.DirEntry.enmType != RTDIRENTRYTYPE_UNKNOWN
1926 || !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) )
1927 rc = rtPathGlobAddResult2(pGlob, offPath, pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1928 (uint8_t)pGlob->u.DirEntry.enmType);
1929 else
1930 {
1931 rc = rtPathGlobAlmostAddResult(pGlob, offPath,
1932 pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1933 (uint8_t)RTDIRENTRYTYPE_UNKNOWN);
1934 if (RT_SUCCESS(rc))
1935 {
1936 RTDirQueryUnknownType((*pGlob->ppNext)->szPath, false /*fFollowSymlinks*/,
1937 &pGlob->u.DirEntry.enmType);
1938 if ( RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
1939 == (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY))
1940 rtPathGlobCommitResult(pGlob, (uint8_t)pGlob->u.DirEntry.enmType);
1941 else
1942 rtPathGlobRollbackResult(pGlob);
1943 }
1944 }
1945 if (rc != VINF_SUCCESS)
1946 break;
1947 }
1948 else
1949 {
1950 AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
1951 rc = VINF_SUCCESS;
1952 }
1953 }
1954 }
1955 /*
1956 * Intermediate component: Directories only.
1957 */
1958 else if ( pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY
1959 || pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
1960 {
1961 rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1962 &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
1963 &pGlob->MatchCache);
1964 if (RT_SUCCESS(rc))
1965 {
1966 /* Recurse down into the alleged directory. */
1967 cch = offPath + pGlob->u.DirEntry.cbName;
1968 if (cch + 1 < sizeof(pGlob->szPath))
1969 {
1970 memcpy(&pGlob->szPath[offPath], pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName);
1971 pGlob->szPath[cch++] = RTPATH_SLASH;
1972 pGlob->szPath[cch] = '\0';
1973
1974 if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
1975 rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
1976 else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
1977 rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
1978 else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1979 rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
1980 else
1981 rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
1982 if (rc != VINF_SUCCESS)
1983 return rc;
1984 }
1985 else
1986 pGlob->cPathOverflows++;
1987 }
1988 else
1989 {
1990 AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
1991 rc = VINF_SUCCESS;
1992 }
1993 }
1994 }
1995 /*
1996 * RTDirRead failure.
1997 */
1998 else
1999 {
2000 /* The end? */
2001 if (rc == VERR_NO_MORE_FILES)
2002 rc = VINF_SUCCESS;
2003 /* Try skip the entry if we end up with an overflow (szPath can't hold it either then). */
2004 else if (rc == VERR_BUFFER_OVERFLOW)
2005 {
2006 pGlob->cPathOverflows++;
2007 rc = rtPathGlobSkipDirEntry(hDir, cch);
2008 if (RT_SUCCESS(rc))
2009 continue;
2010 }
2011 /* else: Any other error is unexpected and should be reported. */
2012 break;
2013 }
2014 }
2015
2016 RTDirClose(hDir);
2017 }
2018 /* Directory doesn't exist or something else is wrong, ignore this. */
2019 else
2020 rc = VINF_SUCCESS;
2021 return rc;
2022}
2023
2024
2025/**
2026 * Executes a glob search.
2027 *
2028 * @returns IPRT status code.
2029 * @param pGlob The glob instance data.
2030 */
2031static int rtPathGlobExec(PRTPATHGLOB pGlob)
2032{
2033 Assert(pGlob->offFirstPath < sizeof(pGlob->szPath));
2034 Assert(pGlob->szPath[pGlob->offFirstPath] == '\0');
2035
2036 int rc;
2037 if (RT_LIKELY(pGlob->iFirstComp < pGlob->pParsed->cComps))
2038 {
2039 /*
2040 * Call the appropriate function.
2041 */
2042 if (rtPathGlobExecIsExpVar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
2043 rc = rtPathGlobExecRecursiveVarExp(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
2044 else if (rtPathGlobExecIsPlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
2045 rc = rtPathGlobExecRecursivePlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
2046 else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
2047 rc = rtPathGlobExecRecursiveStarStar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp, pGlob->offFirstPath);
2048 else
2049 rc = rtPathGlobExecRecursiveGeneric(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
2050 }
2051 else
2052 {
2053 /*
2054 * Special case where we only have a root component or tilde expansion.
2055 */
2056 Assert(pGlob->offFirstPath > 0);
2057 rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
2058 if ( RT_SUCCESS(rc)
2059 && rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
2060 rc = rtPathGlobAddResult(pGlob, pGlob->offFirstPath,
2061 (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK) >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
2062 else
2063 rc = VINF_SUCCESS;
2064 }
2065
2066 /*
2067 * Adjust the status code. Check for results, hide RTPATHGLOB_F_FIRST_ONLY
2068 * status code, and add warning if necessary.
2069 */
2070 if (pGlob->cResults > 0)
2071 {
2072 if (rc == VINF_CALLBACK_RETURN)
2073 rc = VINF_SUCCESS;
2074 if (rc == VINF_SUCCESS)
2075 {
2076 if (pGlob->cPathOverflows > 0)
2077 rc = VINF_BUFFER_OVERFLOW;
2078 }
2079 }
2080 else
2081 rc = VERR_FILE_NOT_FOUND;
2082
2083 return rc;
2084}
2085
2086
2087RTDECL(int) RTPathGlob(const char *pszPattern, uint32_t fFlags, PPCRTPATHGLOBENTRY ppHead, uint32_t *pcResults)
2088{
2089 /*
2090 * Input validation.
2091 */
2092 AssertPtrReturn(ppHead, VERR_INVALID_POINTER);
2093 *ppHead = NULL;
2094 if (pcResults)
2095 {
2096 AssertPtrReturn(pcResults, VERR_INVALID_POINTER);
2097 *pcResults = 0;
2098 }
2099 AssertPtrReturn(pszPattern, VERR_INVALID_POINTER);
2100 AssertReturn(!(fFlags & ~RTPATHGLOB_F_MASK), VERR_INVALID_FLAGS);
2101 AssertReturn((fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) != (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS),
2102 VERR_INVALID_FLAGS);
2103
2104 /*
2105 * Parse the path.
2106 */
2107 size_t cbParsed = RT_UOFFSETOF(RTPATHPARSED, aComps[1]); /** @todo 16 after testing */
2108 PRTPATHPARSED pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
2109 AssertReturn(pParsed, VERR_NO_MEMORY);
2110 int rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
2111 if (rc == VERR_BUFFER_OVERFLOW)
2112 {
2113 cbParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pParsed->cComps + 1]);
2114 RTMemTmpFree(pParsed);
2115 pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
2116 AssertReturn(pParsed, VERR_NO_MEMORY);
2117
2118 rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
2119 }
2120 if (RT_SUCCESS(rc))
2121 {
2122 /*
2123 * Check dir slash vs. only/not dir flag.
2124 */
2125 if ( !(fFlags & RTPATHGLOB_F_NO_DIRS)
2126 || ( !(pParsed->fProps & RTPATH_PROP_DIR_SLASH)
2127 && ( !(pParsed->fProps & (RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_UNC))
2128 || pParsed->cComps > 1) ) )
2129 {
2130 if (pParsed->fProps & RTPATH_PROP_DIR_SLASH)
2131 fFlags |= RTPATHGLOB_F_ONLY_DIRS;
2132
2133 /*
2134 * Allocate and initialize the glob state data structure.
2135 */
2136 size_t cbGlob = RT_UOFFSETOF_DYN(RTPATHGLOB, aComps[pParsed->cComps + 1]);
2137 PRTPATHGLOB pGlob = (PRTPATHGLOB)RTMemTmpAllocZ(cbGlob);
2138 if (pGlob)
2139 {
2140 pGlob->pszPattern = pszPattern;
2141 pGlob->fFlags = fFlags;
2142 pGlob->pParsed = pParsed;
2143 pGlob->ppNext = &pGlob->pHead;
2144 rc = rtPathGlobParse(pGlob, pszPattern, pParsed, fFlags);
2145 if (RT_SUCCESS(rc))
2146 {
2147 /*
2148 * Execute the search.
2149 */
2150 rc = rtPathGlobExec(pGlob);
2151 if (RT_SUCCESS(rc))
2152 {
2153 *ppHead = pGlob->pHead;
2154 if (pcResults)
2155 *pcResults = pGlob->cResults;
2156 }
2157 else
2158 RTPathGlobFree(pGlob->pHead);
2159 }
2160
2161 RTMemTmpFree(pGlob->MatchInstrAlloc.paInstructions);
2162 RTMemTmpFree(pGlob);
2163 }
2164 else
2165 rc = VERR_NO_MEMORY;
2166 }
2167 else
2168 rc = VERR_NOT_FOUND;
2169 }
2170 RTMemTmpFree(pParsed);
2171 return rc;
2172
2173
2174}
2175
2176
2177RTDECL(void) RTPathGlobFree(PCRTPATHGLOBENTRY pHead)
2178{
2179 PRTPATHGLOBENTRY pCur = (PRTPATHGLOBENTRY)pHead;
2180 while (pCur)
2181 {
2182 PRTPATHGLOBENTRY pNext = pCur->pNext;
2183 pCur->pNext = NULL;
2184 RTMemFree(pCur);
2185 pCur = pNext;
2186 }
2187}
2188
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette