VirtualBox

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

Last change on this file was 104260, checked in by vboxsync, 7 weeks ago

IPRT/RTPathGlob: Nits.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 84.2 KB
Line 
1/* $Id: RTPathGlob.cpp 104260 2024-04-09 23:38:53Z vboxsync $ */
2/** @file
3 * IPRT - RTPathGlob
4 */
5
6/*
7 * Copyright (C) 2006-2023 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# else
445# error "Port ME!"
446# endif
447static const char * const a_apszWinProgramFilesVars[] =
448{
449 "ProgramFiles",
450# ifdef RT_ARCH_AMD64
451 "ProgramFiles(x86)",
452# endif
453};
454RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllProgramFiles, a_apszWinProgramFilesVars, RTPATH_MAX);
455static const char * const a_apszWinCommonProgramFilesVars[] =
456{
457 "CommonProgramFiles",
458# ifdef RT_ARCH_AMD64
459 "CommonProgramFiles(x86)",
460# endif
461};
462RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllCommonProgramFiles, a_apszWinCommonProgramFilesVars, RTPATH_MAX);
463#endif
464
465
466/**
467 * @interface_method_impl{RTPATHMATCHVAR,pfnQuery, Enumerates the PATH}
468 */
469static DECLCALLBACK(int) rtPathVarQuery_Path(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
470 PRTPATHMATCHCACHE pCache)
471{
472 RT_NOREF_PV(pCache);
473
474 /*
475 * Query the PATH value.
476 */
477/** @todo cache this in pCache with iItem and offset. */
478 char *pszPathFree = NULL;
479 char *pszPath = pszBuf;
480 size_t cchActual;
481 const char *pszVarNm = "PATH";
482 int rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPath, cbBuf, &cchActual);
483#ifdef RT_OS_WINDOWS
484 if (rc == VERR_ENV_VAR_NOT_FOUND)
485 rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm = "Path", pszPath, cbBuf, &cchActual);
486#endif
487 if (rc == VERR_BUFFER_OVERFLOW)
488 {
489 for (uint32_t iTry = 0;; iTry++)
490 {
491 size_t cbPathBuf = RT_ALIGN_Z(cchActual + 1 + 64 * iTry, 64);
492 pszPathFree = (char *)RTMemTmpAlloc(cbPathBuf);
493 rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPathFree, cbPathBuf, &cchActual);
494 if (RT_SUCCESS(rc))
495 break;
496 RTMemTmpFree(pszPathFree);
497 AssertReturn(cchActual >= cbPathBuf, VERR_INTERNAL_ERROR_3);
498 if (iTry >= 9)
499 return rc;
500 }
501 pszPath = pszPathFree;
502 }
503
504 /*
505 * Spool forward to the given PATH item.
506 */
507 rc = VERR_EOF;
508#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
509 const char chSep = ';';
510#else
511 const char chSep = ':';
512#endif
513 while (*pszPath != '\0')
514 {
515 char *pchSep = strchr(pszPath, chSep);
516
517 /* We ignore empty strings, which is probably not entirely correct,
518 but works better on DOS based system with many entries added
519 without checking whether there is a trailing separator or not.
520 Thus, the current directory is only searched if a '.' is present
521 in the PATH. */
522 if (pchSep == pszPath)
523 pszPath++;
524 else if (iItem > 0)
525 {
526 /* If we didn't find a separator, the item doesn't exists. Quit. */
527 if (!pchSep)
528 break;
529
530 pszPath = pchSep + 1;
531 iItem--;
532 }
533 else
534 {
535 /* We've reached the item we wanted. */
536 size_t cchComp = pchSep ? pchSep - pszPath : strlen(pszPath);
537 if (cchComp < cbBuf)
538 {
539 if (pszBuf != pszPath)
540 memmove(pszBuf, pszPath, cchComp);
541 pszBuf[cchComp] = '\0';
542 rc = pchSep ? VINF_SUCCESS : VINF_EOF;
543 }
544 else
545 rc = VERR_BUFFER_OVERFLOW;
546 *pcchValue = cchComp;
547 break;
548 }
549 }
550
551 if (pszPathFree)
552 RTMemTmpFree(pszPathFree);
553 return rc;
554}
555
556
557#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
558/**
559 * @interface_method_impl{RTPATHMATCHVAR,pfnQuery,
560 * The system drive letter + colon.}.
561 */
562static DECLCALLBACK(int) rtPathVarQuery_DosSystemDrive(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
563 PRTPATHMATCHCACHE pCache)
564{
565 RT_NOREF_PV(pCache);
566
567 if (iItem == 0)
568 {
569 AssertReturn(cbBuf >= 3, VERR_BUFFER_OVERFLOW);
570
571# ifdef RT_OS_WINDOWS
572 /* Since this is used at the start of a pattern, we assume
573 we've got more than enough buffer space. */
574 AssertReturn(g_pfnGetSystemWindowsDirectoryW, VERR_SYMBOL_NOT_FOUND);
575 PRTUTF16 pwszTmp = (PRTUTF16)pszBuf;
576 UINT cch = g_pfnGetSystemWindowsDirectoryW(pwszTmp, (UINT)(cbBuf / sizeof(WCHAR)));
577 if (cch >= 2)
578 {
579 RTUTF16 wcDrive = pwszTmp[0];
580 if ( RT_C_IS_ALPHA(wcDrive)
581 && pwszTmp[1] == ':')
582 {
583 pszBuf[0] = wcDrive;
584 pszBuf[1] = ':';
585 pszBuf[2] = '\0';
586 *pcchValue = 2;
587 return VINF_EOF;
588 }
589 }
590# else
591 ULONG ulDrive = ~(ULONG)0;
592 APIRET rc = DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, &ulDrive, sizeof(ulDrive));
593 ulDrive--; /* 1 = 'A' */
594 if ( rc == NO_ERROR
595 && ulDrive <= (ULONG)'Z')
596 {
597 pszBuf[0] = (char)ulDrive + 'A';
598 pszBuf[1] = ':';
599 pszBuf[2] = '\0';
600 *pcchValue = 2;
601 return VINF_EOF;
602 }
603# endif
604 return VERR_INTERNAL_ERROR_4;
605 }
606 return VERR_EOF;
607}
608#endif
609
610
611#ifdef RT_OS_WINDOWS
612/**
613 * @interface_method_impl{RTPATHMATCHVAR,pfnQuery,
614 * The system root directory (C:\Windows).}.
615 */
616static DECLCALLBACK(int) rtPathVarQuery_WinSystemRoot(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
617 PRTPATHMATCHCACHE pCache)
618{
619 RT_NOREF_PV(pCache);
620
621 if (iItem == 0)
622 {
623 Assert(pszBuf); Assert(cbBuf);
624 AssertReturn(g_pfnGetSystemWindowsDirectoryW, VERR_SYMBOL_NOT_FOUND);
625 RTUTF16 wszSystemRoot[MAX_PATH];
626 UINT cchSystemRoot = g_pfnGetSystemWindowsDirectoryW(wszSystemRoot, MAX_PATH);
627 if (cchSystemRoot > 0)
628 return RTUtf16ToUtf8Ex(wszSystemRoot, cchSystemRoot, &pszBuf, cbBuf, pcchValue);
629 return RTErrConvertFromWin32(GetLastError());
630 }
631 return VERR_EOF;
632}
633#endif
634
635#undef RTPATHMATCHVAR_SIMPLE
636#undef RTPATHMATCHVAR_SIMPLE_ENVVAR
637#undef RTPATHMATCHVAR_DOUBLE_ENVVAR
638
639/**
640 * Variables.
641 */
642static RTPATHMATCHVAR const g_aVariables[] =
643{
644 { RT_STR_TUPLE("Arch"), false, rtPathVarQuery_Arch, rtPathVarMatch_Arch },
645 { RT_STR_TUPLE("Bits"), false, rtPathVarQuery_Bits, rtPathVarMatch_Bits },
646 { RT_STR_TUPLE("Path"), true, rtPathVarQuery_Path, NULL },
647#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
648 { RT_STR_TUPLE("SystemDrive"), true, rtPathVarQuery_DosSystemDrive, NULL },
649#endif
650#ifdef RT_OS_WINDOWS
651 { RT_STR_TUPLE("SystemRoot"), true, rtPathVarQuery_WinSystemRoot, NULL },
652 { RT_STR_TUPLE("AppData"), true, rtPathVarQuery_WinAppData, rtPathVarMatch_WinAppData },
653 { RT_STR_TUPLE("ProgramData"), true, rtPathVarQuery_WinProgramData, rtPathVarMatch_WinProgramData },
654 { RT_STR_TUPLE("ProgramFiles"), true, rtPathVarQuery_WinProgramFiles, rtPathVarMatch_WinProgramFiles },
655 { RT_STR_TUPLE("OtherProgramFiles"), true, rtPathVarQuery_WinOtherProgramFiles, rtPathVarMatch_WinOtherProgramFiles },
656 { RT_STR_TUPLE("AllProgramFiles"), true, rtPathVarQuery_WinAllProgramFiles, rtPathVarMatch_WinAllProgramFiles },
657 { RT_STR_TUPLE("CommonProgramFiles"), true, rtPathVarQuery_WinCommonProgramFiles, rtPathVarMatch_WinCommonProgramFiles },
658 { RT_STR_TUPLE("OtherCommonProgramFiles"), true, rtPathVarQuery_WinOtherCommonProgramFiles, rtPathVarMatch_WinOtherCommonProgramFiles },
659 { RT_STR_TUPLE("AllCommonProgramFiles"), true, rtPathVarQuery_WinAllCommonProgramFiles, rtPathVarMatch_WinAllCommonProgramFiles },
660#endif
661};
662
663
664
665/**
666 * Handles a complicated set.
667 *
668 * A complicated set is either using ranges, character classes or code points
669 * outside the ASCII-7 range.
670 *
671 * @returns VINF_SUCCESS or VERR_MISMATCH. May also return UTF-8 decoding
672 * errors as well as VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED.
673 *
674 * @param ucInput The input code point to match with.
675 * @param pchSet The start of the set specification (after caret).
676 * @param cchSet The length of the set specification.
677 */
678static int rtPathMatchExecExtendedSet(RTUNICP ucInput, const char *pchSet, size_t cchSet)
679{
680 while (cchSet > 0)
681 {
682 RTUNICP ucSet;
683 int rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet);
684 AssertRCReturn(rc, rc);
685
686 /*
687 * Check for character class, collating symbol and equvalence class.
688 */
689 if (ucSet == '[' && cchSet > 0)
690 {
691 char chNext = *pchSet;
692 if (chNext == ':')
693 {
694#define CHECK_CHAR_CLASS(a_szClassNm, a_BoolTestExpr) \
695 if ( cchSet >= sizeof(a_szClassNm) \
696 && memcmp(pchSet, a_szClassNm "]", sizeof(a_szClassNm)) == 0) \
697 { \
698 if (a_BoolTestExpr) \
699 return VINF_SUCCESS; \
700 pchSet += sizeof(a_szClassNm); \
701 cchSet -= sizeof(a_szClassNm); \
702 continue; \
703 } do { } while (0)
704
705 CHECK_CHAR_CLASS(":alpha:", RTUniCpIsAlphabetic(ucInput));
706 CHECK_CHAR_CLASS(":alnum:", RTUniCpIsAlphabetic(ucInput) || RTUniCpIsDecDigit(ucInput)); /** @todo figure what's correct here and fix uni.h */
707 CHECK_CHAR_CLASS(":blank:", ucInput == ' ' || ucInput == '\t');
708 CHECK_CHAR_CLASS(":cntrl:", ucInput < 31 || ucInput == 127);
709 CHECK_CHAR_CLASS(":digit:", RTUniCpIsDecDigit(ucInput));
710 CHECK_CHAR_CLASS(":lower:", RTUniCpIsLower(ucInput));
711 CHECK_CHAR_CLASS(":print:", RTUniCpIsAlphabetic(ucInput) || (RT_C_IS_PRINT(ucInput) && ucInput < 127)); /** @todo fixme*/
712 CHECK_CHAR_CLASS(":punct:", RT_C_IS_PRINT(ucInput) && ucInput < 127); /** @todo fixme*/
713 CHECK_CHAR_CLASS(":space:", RTUniCpIsSpace(ucInput));
714 CHECK_CHAR_CLASS(":upper:", RTUniCpIsUpper(ucInput));
715 CHECK_CHAR_CLASS(":xdigit:", RTUniCpIsHexDigit(ucInput));
716 AssertMsgFailedReturn(("Unknown or malformed char class: '%.*s'\n", cchSet + 1, pchSet - 1),
717 VERR_PATH_GLOB_UNKNOWN_CHAR_CLASS);
718#undef CHECK_CHAR_CLASS
719 }
720 /** @todo implement collating symbol and equvalence class. */
721 else if (chNext == '=' || chNext == '.')
722 AssertFailedReturn(VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
723 }
724
725 /*
726 * Check for range (leading or final dash does not constitute a range).
727 */
728 if (cchSet > 1 && *pchSet == '-')
729 {
730 pchSet++; /* skip dash */
731 cchSet--;
732
733 RTUNICP ucSet2;
734 rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet2);
735 AssertRCReturn(rc, rc);
736 Assert(ucSet < ucSet2);
737 if (ucInput >= ucSet && ucInput <= ucSet2)
738 return VINF_SUCCESS;
739 }
740 /*
741 * Single char comparison.
742 */
743 else if (ucInput == ucSet)
744 return VINF_SUCCESS;
745 }
746 return VERR_MISMATCH;
747}
748
749
750/**
751 * Variable matching fallback using the query function.
752 *
753 * This must not be inlined as it consuming a lot of stack! Which is why it's
754 * placed a couple of functions away from the recursive rtPathExecMatch.
755 *
756 * @returns VINF_SUCCESS or VERR_MISMATCH.
757 * @param pchInput The current input position.
758 * @param cchInput The amount of input left..
759 * @param idxVar The variable table index.
760 * @param fIgnoreCase Whether to ignore case when comparing.
761 * @param pcchMatched Where to return how much we actually matched up.
762 * @param pCache Pointer to the path matching cache.
763 */
764DECL_NO_INLINE(static, int) rtPathMatchExecVariableFallback(const char *pchInput, size_t cchInput, uint16_t idxVar,
765 bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
766{
767 for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
768 {
769 char szValue[RTPATH_MAX];
770 size_t cchValue;
771 int rc = g_aVariables[idxVar].pfnQuery(iItem, szValue, sizeof(szValue), &cchValue, pCache);
772 if (RT_SUCCESS(rc))
773 {
774 if (cchValue <= cchInput)
775 {
776 if ( !fIgnoreCase
777 ? memcmp(pchInput, szValue, cchValue) == 0
778 : RTStrNICmp(pchInput, szValue, cchValue) == 0)
779 {
780 *pcchMatched = cchValue;
781 return VINF_SUCCESS;
782 }
783 }
784 if (rc == VINF_EOF)
785 return VERR_MISMATCH;
786 }
787 else if (rc == VERR_EOF)
788 return VERR_MISMATCH;
789 else
790 Assert(rc == VERR_BUFFER_OVERFLOW || rc == VERR_TRY_AGAIN);
791 }
792 AssertFailed();
793 return VERR_MISMATCH;
794}
795
796
797/**
798 * Variable matching worker.
799 *
800 * @returns VINF_SUCCESS or VERR_MISMATCH.
801 * @param pchInput The current input position.
802 * @param cchInput The amount of input left..
803 * @param idxVar The variable table index.
804 * @param fIgnoreCase Whether to ignore case when comparing.
805 * @param pcchMatched Where to return how much we actually matched up.
806 * @param pCache Pointer to the path matching cache.
807 */
808static int rtPathMatchExecVariable(const char *pchInput, size_t cchInput, uint16_t idxVar,
809 bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
810{
811 Assert(idxVar < RT_ELEMENTS(g_aVariables));
812 if (g_aVariables[idxVar].pfnMatch)
813 return g_aVariables[idxVar].pfnMatch(pchInput, cchInput, fIgnoreCase, pcchMatched);
814 return rtPathMatchExecVariableFallback(pchInput, cchInput, idxVar, fIgnoreCase, pcchMatched, pCache);
815}
816
817
818/**
819 * Variable matching worker.
820 *
821 * @returns VINF_SUCCESS or VERR_MISMATCH.
822 * @param pchInput The current input position.
823 * @param cchInput The amount of input left..
824 * @param pProg The first matching program instruction.
825 * @param pCache Pointer to the path matching cache.
826 */
827static int rtPathMatchExec(const char *pchInput, size_t cchInput, PCRTPATHMATCHCORE pProg, PRTPATHMATCHCACHE pCache)
828{
829 for (;;)
830 {
831 switch (pProg->enmOpCode)
832 {
833 case RTPATHMATCHOP_RETURN_MATCH_IF_AT_END:
834 return cchInput == 0 ? VINF_SUCCESS : VERR_MISMATCH;
835
836 case RTPATHMATCHOP_RETURN_MATCH:
837 return VINF_SUCCESS;
838
839 case RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT:
840 if ( cchInput > 2
841 || cchInput < 1
842 || pchInput[0] != '.'
843 || (cchInput == 2 && pchInput[1] != '.') )
844 return VINF_SUCCESS;
845 return VERR_MISMATCH;
846
847 case RTPATHMATCHOP_STRCMP:
848 if (pProg->cch > cchInput)
849 return VERR_MISMATCH;
850 if (memcmp(pchInput, pProg->pch, pProg->cch) != 0)
851 return VERR_MISMATCH;
852 cchInput -= pProg->cch;
853 pchInput += pProg->cch;
854 break;
855
856 case RTPATHMATCHOP_STRICMP:
857 if (pProg->cch > cchInput)
858 return VERR_MISMATCH;
859 if (RTStrNICmp(pchInput, pProg->pch, pProg->cch) != 0)
860 return VERR_MISMATCH;
861 cchInput -= pProg->cch;
862 pchInput += pProg->cch;
863 break;
864
865 case RTPATHMATCHOP_SKIP_ONE_CODEPOINT:
866 {
867 if (cchInput == 0)
868 return VERR_MISMATCH;
869 RTUNICP ucInputIgnore;
870 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
871 AssertRCReturn(rc, rc);
872 break;
873 }
874
875 case RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS:
876 {
877 uint16_t cCpsLeft = pProg->cch;
878 Assert(cCpsLeft > 1);
879 if (cCpsLeft > cchInput)
880 return VERR_MISMATCH;
881 while (cCpsLeft-- > 0)
882 {
883 RTUNICP ucInputIgnore;
884 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
885 if (RT_FAILURE(rc))
886 return rc == VERR_END_OF_STRING ? VERR_MISMATCH : rc;
887 }
888 break;
889 }
890
891 case RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7:
892 {
893 if (cchInput == 0)
894 return VERR_MISMATCH;
895 RTUNICP ucInput;
896 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
897 AssertRCReturn(rc, rc);
898 if (ucInput >= 0x80)
899 return VERR_MISMATCH;
900 if (memchr(pProg->pch, (char)ucInput, pProg->cch) == NULL)
901 return VERR_MISMATCH;
902 break;
903 }
904
905 case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7:
906 {
907 if (cchInput == 0)
908 return VERR_MISMATCH;
909 RTUNICP ucInput;
910 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
911 AssertRCReturn(rc, rc);
912 if (ucInput >= 0x80)
913 break;
914 if (memchr(pProg->pch, (char)ucInput, pProg->cch) != NULL)
915 return VERR_MISMATCH;
916 break;
917 }
918
919 case RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED:
920 {
921 if (cchInput == 0)
922 return VERR_MISMATCH;
923 RTUNICP ucInput;
924 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
925 AssertRCReturn(rc, rc);
926 rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
927 if (rc == VINF_SUCCESS)
928 break;
929 return rc;
930 }
931
932 case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED:
933 {
934 if (cchInput == 0)
935 return VERR_MISMATCH;
936 RTUNICP ucInput;
937 int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
938 AssertRCReturn(rc, rc);
939 rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
940 if (rc == VERR_MISMATCH)
941 break;
942 if (rc == VINF_SUCCESS)
943 rc = VERR_MISMATCH;
944 return rc;
945 }
946
947 case RTPATHMATCHOP_VARIABLE_VALUE_CMP:
948 case RTPATHMATCHOP_VARIABLE_VALUE_ICMP:
949 {
950 size_t cchMatched = 0;
951 int rc = rtPathMatchExecVariable(pchInput, cchInput, pProg->uOp2,
952 pProg->enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP, &cchMatched, pCache);
953 if (rc == VINF_SUCCESS)
954 {
955 pchInput += cchMatched;
956 cchInput -= cchMatched;
957 break;
958 }
959 return rc;
960 }
961
962 /*
963 * This is the expensive one. It always completes the program.
964 */
965 case RTPATHMATCHOP_ZERO_OR_MORE:
966 {
967 if (cchInput < pProg->cch)
968 return VERR_MISMATCH;
969 size_t cchMatched = cchInput - pProg->cch;
970 do
971 {
972 int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
973 if (RT_SUCCESS(rc))
974 return rc;
975 } while (cchMatched-- > 0);
976 return VERR_MISMATCH;
977 }
978
979 /*
980 * Variant of the above that doesn't match '.' and '..' entries.
981 */
982 case RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT:
983 {
984 if (cchInput < pProg->cch)
985 return VERR_MISMATCH;
986 if ( cchInput <= 2
987 && cchInput > 0
988 && pchInput[0] == '.'
989 && (cchInput == 1 || pchInput[1] == '.') )
990 return VERR_MISMATCH;
991 size_t cchMatched = cchInput - pProg->cch;
992 do
993 {
994 int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
995 if (RT_SUCCESS(rc))
996 return rc;
997 } while (cchMatched-- > 0);
998 return VERR_MISMATCH;
999 }
1000
1001 default:
1002 AssertMsgFailedReturn(("enmOpCode=%d\n", pProg->enmOpCode), VERR_INTERNAL_ERROR_3);
1003 }
1004
1005 pProg++;
1006 }
1007}
1008
1009
1010
1011
1012/**
1013 * Compiles a path matching program.
1014 *
1015 * @returns IPRT status code.
1016 * @param pchPattern The pattern to compile.
1017 * @param cchPattern The length of the pattern.
1018 * @param fIgnoreCase Whether to ignore case or not when doing the
1019 * actual matching later on.
1020 * @param pAllocator Pointer to the instruction allocator & result
1021 * array. The compiled "program" starts at
1022 * PRTPATHMATCHALLOC::paInstructions[PRTPATHMATCHALLOC::iNext]
1023 * (input iNext value).
1024 *
1025 * @todo Expose this matching code and also use it for RTDirOpenFiltered
1026 */
1027static int rtPathMatchCompile(const char *pchPattern, size_t cchPattern, bool fIgnoreCase, PRTPATHMATCHALLOC pAllocator)
1028{
1029 /** @todo PORTME: big endian. */
1030 static const uint8_t s_bmMetaChars[256/8] =
1031 {
1032 0x00, 0x00, 0x00, 0x00, /* 0 thru 31 */
1033 0x10, 0x04, 0x00, 0x80, /* 32 thru 63 */
1034 0x00, 0x00, 0x00, 0x08, /* 64 thru 95 */
1035 0x00, 0x00, 0x00, 0x00, /* 96 thru 127 */
1036 /* UTF-8 multibyte: */
1037 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1038 };
1039 Assert(ASMBitTest(s_bmMetaChars, '$')); AssertCompile('$' == 0x24 /*36*/);
1040 Assert(ASMBitTest(s_bmMetaChars, '*')); AssertCompile('*' == 0x2a /*42*/);
1041 Assert(ASMBitTest(s_bmMetaChars, '?')); AssertCompile('?' == 0x3f /*63*/);
1042 Assert(ASMBitTest(s_bmMetaChars, '[')); AssertCompile('[' == 0x5b /*91*/);
1043
1044 /*
1045 * For checking for the first instruction.
1046 */
1047 uint16_t const iFirst = pAllocator->iNext;
1048
1049 /*
1050 * This is for tracking zero-or-more instructions and for calculating
1051 * the minimum amount of input required for it to be considered.
1052 */
1053 uint16_t aiZeroOrMore[RTPATHMATCH_MAX_ZERO_OR_MORE];
1054 uint8_t cZeroOrMore = 0;
1055 size_t offInput = 0;
1056
1057 /*
1058 * Loop thru the pattern and translate it into string matching instructions.
1059 */
1060 for (;;)
1061 {
1062 /*
1063 * Allocate the next instruction.
1064 */
1065 if (pAllocator->iNext >= pAllocator->cAllocated)
1066 {
1067 uint32_t cNew = pAllocator->cAllocated ? pAllocator->cAllocated * 2 : 2;
1068 void *pvNew = RTMemRealloc(pAllocator->paInstructions, cNew * sizeof(pAllocator->paInstructions[0]));
1069 AssertReturn(pvNew, VERR_NO_MEMORY);
1070 pAllocator->paInstructions = (PRTPATHMATCHCORE)pvNew;
1071 pAllocator->cAllocated = cNew;
1072 }
1073 PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[pAllocator->iNext++];
1074 pInstr->pch = pchPattern;
1075 pInstr->cch = 0;
1076 pInstr->uOp2 = 0;
1077
1078 /*
1079 * Special case: End of pattern.
1080 */
1081 if (!cchPattern)
1082 {
1083 pInstr->enmOpCode = RTPATHMATCHOP_RETURN_MATCH_IF_AT_END;
1084 break;
1085 }
1086
1087 /*
1088 * Parse the next bit of the pattern.
1089 */
1090 char ch = *pchPattern;
1091 if (ASMBitTest(s_bmMetaChars, (uint8_t)ch))
1092 {
1093 /*
1094 * Zero or more characters wildcard.
1095 */
1096 if (ch == '*')
1097 {
1098 /* Skip extra asterisks. */
1099 do
1100 {
1101 cchPattern--;
1102 pchPattern++;
1103 } while (cchPattern > 0 && *pchPattern == '*');
1104
1105 /* There is a special optimization for trailing '*'. */
1106 pInstr->cch = 1;
1107 if (cchPattern == 0)
1108 {
1109 pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
1110 ? RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_RETURN_MATCH;
1111 break;
1112 }
1113
1114 pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
1115 ? RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_ZERO_OR_MORE;
1116 pInstr->uOp2 = (uint16_t)offInput;
1117 AssertReturn(cZeroOrMore < RT_ELEMENTS(aiZeroOrMore), VERR_OUT_OF_RANGE);
1118 aiZeroOrMore[cZeroOrMore] = (uint16_t)(pInstr - pAllocator->paInstructions);
1119
1120 /* cchInput unchanged, zero-or-more matches. */
1121 continue;
1122 }
1123
1124 /*
1125 * Single character wildcard.
1126 */
1127 if (ch == '?')
1128 {
1129 /* Count them if more. */
1130 uint16_t cchQms = 1;
1131 while (cchQms < cchPattern && pchPattern[cchQms] == '?')
1132 cchQms++;
1133
1134 pInstr->cch = cchQms;
1135 pInstr->enmOpCode = cchQms == 1 ? RTPATHMATCHOP_SKIP_ONE_CODEPOINT : RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS;
1136
1137 cchPattern -= cchQms;
1138 pchPattern += cchQms;
1139 offInput += cchQms;
1140 continue;
1141 }
1142
1143 /*
1144 * Character in set.
1145 *
1146 * Note that we skip the first char in the set as that is the only place
1147 * ']' can be placed if one desires to explicitly include it in the set.
1148 * To make life a bit more interesting, [:class:] is allowed inside the
1149 * set, so we have to do the counting game to find the end.
1150 */
1151 if (ch == '[')
1152 {
1153 if ( cchPattern > 2
1154 && (const char *)memchr(pchPattern + 2, ']', cchPattern) != NULL)
1155 {
1156
1157 /* Check for not-in. */
1158 bool fInverted = false;
1159 size_t offStart = 1;
1160 if (pchPattern[offStart] == '^')
1161 {
1162 fInverted = true;
1163 offStart++;
1164 }
1165
1166 /* Special case for ']' as the first char, it doesn't indicate closing then. */
1167 size_t off = offStart;
1168 if (pchPattern[off] == ']')
1169 off++;
1170
1171 bool fExtended = false;
1172 while (off < cchPattern)
1173 {
1174 ch = pchPattern[off++];
1175 if (ch == '[')
1176 {
1177 if (off < cchPattern)
1178 {
1179 char chOpen = pchPattern[off];
1180 if ( chOpen == ':'
1181 || chOpen == '='
1182 || chOpen == '.')
1183 {
1184 off++;
1185 const char *pchFound = (const char *)memchr(&pchPattern[off], ']', cchPattern - off);
1186 if ( pchFound
1187 && pchFound[-1] == chOpen)
1188 {
1189 fExtended = true;
1190 off = pchFound - pchPattern + 1;
1191 }
1192 else
1193 AssertFailed();
1194 }
1195 }
1196 }
1197 /* Check for closing. */
1198 else if (ch == ']')
1199 break;
1200 /* Check for range expression, promote to extended if this happens. */
1201 else if ( ch == '-'
1202 && off != offStart + 1
1203 && off < cchPattern
1204 && pchPattern[off] != ']')
1205 fExtended = true;
1206 /* UTF-8 multibyte chars forces us to use the extended version too. */
1207 else if ((uint8_t)ch >= 0x80)
1208 fExtended = true;
1209 }
1210
1211 if (ch == ']')
1212 {
1213 pInstr->pch = &pchPattern[offStart];
1214 pInstr->cch = (uint16_t)(off - offStart - 1);
1215 if (!fExtended)
1216 pInstr->enmOpCode = !fInverted
1217 ? RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7 : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7;
1218 else
1219 pInstr->enmOpCode = !fInverted
1220 ? RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED
1221 : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED;
1222 pchPattern += off;
1223 cchPattern -= off;
1224 offInput += 1;
1225 continue;
1226 }
1227
1228 /* else: invalid, treat it as */
1229 AssertFailed();
1230 }
1231 }
1232 /*
1233 * Variable matching.
1234 */
1235 else if (ch == '$')
1236 {
1237 const char *pchFound;
1238 if ( cchPattern > 3
1239 && pchPattern[1] == '{'
1240 && (pchFound = (const char *)memchr(pchPattern + 2, '}', cchPattern)) != NULL
1241 && pchFound != &pchPattern[2])
1242 {
1243 /* skip to the variable name. */
1244 pchPattern += 2;
1245 cchPattern -= 2;
1246 size_t cchVarNm = pchFound - pchPattern;
1247
1248 /* Look it up. */
1249 uint32_t iVar;
1250 for (iVar = 0; iVar < RT_ELEMENTS(g_aVariables); iVar++)
1251 if ( g_aVariables[iVar].cchName == cchVarNm
1252 && memcmp(g_aVariables[iVar].pszName, pchPattern, cchVarNm) == 0)
1253 break;
1254 if (iVar < RT_ELEMENTS(g_aVariables))
1255 {
1256 pInstr->uOp2 = (uint16_t)iVar;
1257 pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_VARIABLE_VALUE_CMP : RTPATHMATCHOP_VARIABLE_VALUE_ICMP;
1258 pInstr->pch = pchPattern; /* not necessary */
1259 pInstr->cch = (uint16_t)cchPattern; /* ditto */
1260 pchPattern += cchVarNm + 1;
1261 cchPattern -= cchVarNm + 1;
1262 AssertMsgReturn(!g_aVariables[iVar].fFirstOnly || iFirst + 1U == pAllocator->iNext,
1263 ("Glob variable '%s' should be first\n", g_aVariables[iVar].pszName),
1264 VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
1265 /* cchInput unchanged, value can be empty. */
1266 continue;
1267 }
1268 AssertMsgFailedReturn(("Unknown path matching variable '%.*s'\n", cchVarNm, pchPattern),
1269 VERR_PATH_MATCH_UNKNOWN_VARIABLE);
1270 }
1271 }
1272 else
1273 AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
1274 }
1275
1276 /*
1277 * Plain text. Look for the next meta char.
1278 */
1279 uint32_t cchPlain = 1;
1280 while (cchPlain < cchPattern)
1281 {
1282 ch = pchPattern[cchPlain];
1283 if (!ASMBitTest(s_bmMetaChars, (uint8_t)ch))
1284 { /* probable */ }
1285 else if ( ch == '?'
1286 || ch == '*')
1287 break;
1288 else if (ch == '$')
1289 {
1290 const char *pchFound;
1291 if ( cchPattern > cchPlain + 3
1292 && pchPattern[cchPlain + 1] == '{'
1293 && (pchFound = (const char *)memchr(&pchPattern[cchPlain + 2], '}', cchPattern - cchPlain - 2)) != NULL
1294 && pchFound != &pchPattern[cchPlain + 2])
1295 break;
1296 }
1297 else if (ch == '[')
1298 {
1299 /* We don't put a lot of effort into getting this 100% right here,
1300 no point it complicating things for malformed expressions. */
1301 if ( cchPattern > cchPlain + 2
1302 && memchr(&pchPattern[cchPlain + 2], ']', cchPattern - cchPlain - 1) != NULL)
1303 break;
1304 }
1305 else
1306 AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
1307 cchPlain++;
1308 }
1309 pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_STRCMP : RTPATHMATCHOP_STRICMP;
1310 pInstr->cch = cchPlain;
1311 Assert(pInstr->pch == pchPattern);
1312 Assert(pInstr->uOp2 == 0);
1313 pchPattern += cchPlain;
1314 cchPattern -= cchPlain;
1315 offInput += cchPlain;
1316 }
1317
1318 /*
1319 * Optimize zero-or-more matching.
1320 */
1321 while (cZeroOrMore-- > 0)
1322 {
1323 PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[aiZeroOrMore[cZeroOrMore]];
1324 pInstr->uOp2 = (uint16_t)(offInput - pInstr->uOp2);
1325 }
1326
1327 /** @todo It's possible to use offInput to inject a instruction for checking
1328 * minimum input length at the start of the program. Not sure it's
1329 * worth it though, unless it's long a complicated expression... */
1330 return VINF_SUCCESS;
1331}
1332
1333
1334/**
1335 * Parses the glob pattern.
1336 *
1337 * This compiles filename matching programs for each component and determins the
1338 * optimal search strategy for them.
1339 *
1340 * @returns IPRT status code.
1341 * @param pGlob The glob instance data.
1342 * @param pszPattern The pattern to parse.
1343 * @param pParsed The RTPathParse output for the pattern.
1344 * @param fFlags The glob flags (same as pGlob->fFlags).
1345 */
1346static int rtPathGlobParse(PRTPATHGLOB pGlob, const char *pszPattern, PRTPATHPARSED pParsed, uint32_t fFlags)
1347{
1348 AssertReturn(pParsed->cComps > 0, VERR_INVALID_PARAMETER); /* shouldn't happen */
1349 uint32_t iComp = 0;
1350
1351 /*
1352 * If we've got a rootspec, mark it as plain. On platforms with
1353 * drive letter and/or UNC we don't allow wildcards or such in
1354 * the drive letter spec or UNC server name. (At least not yet.)
1355 */
1356 if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
1357 {
1358 AssertReturn(pParsed->aComps[0].cch < sizeof(pGlob->szPath) - 1, VERR_FILENAME_TOO_LONG);
1359 memcpy(pGlob->szPath, &pszPattern[pParsed->aComps[0].off], pParsed->aComps[0].cch);
1360 pGlob->offFirstPath = pParsed->aComps[0].cch;
1361 pGlob->iFirstComp = iComp = 1;
1362 }
1363 else
1364 {
1365 const char * const pszComp = &pszPattern[pParsed->aComps[0].off];
1366
1367 /*
1368 * The tilde is only applicable to the first component, expand it
1369 * immediately.
1370 */
1371 if ( *pszComp == '~'
1372 && !(fFlags & RTPATHGLOB_F_NO_TILDE))
1373 {
1374 if (pParsed->aComps[0].cch == 1)
1375 {
1376 int rc = RTPathUserHome(pGlob->szPath, sizeof(pGlob->szPath) - 1);
1377 AssertRCReturn(rc, rc);
1378 }
1379 else
1380 AssertMsgFailedReturn(("'%.*s' is not supported yet\n", pszComp, pParsed->aComps[0].cch),
1381 VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
1382 pGlob->offFirstPath = (uint16_t)RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
1383 pGlob->iFirstComp = iComp = 1;
1384 }
1385 }
1386
1387 /*
1388 * Process the other components.
1389 */
1390 bool fStarStar = false;
1391 for (; iComp < pParsed->cComps; iComp++)
1392 {
1393 const char *pszComp = &pszPattern[pParsed->aComps[iComp].off];
1394 uint16_t cchComp = pParsed->aComps[iComp].cch;
1395 Assert(pGlob->aComps[iComp].fNormal == false);
1396
1397 pGlob->aComps[iComp].fDir = iComp + 1 < pParsed->cComps || (fFlags & RTPATHGLOB_F_ONLY_DIRS);
1398 if ( cchComp != 2
1399 || pszComp[0] != '*'
1400 || pszComp[1] != '*'
1401 || (fFlags & RTPATHGLOB_F_NO_STARSTAR) )
1402 {
1403 /* Compile the pattern. */
1404 uint16_t const iMatchProg = pGlob->MatchInstrAlloc.iNext;
1405 pGlob->aComps[iComp].iMatchProg = iMatchProg;
1406 int rc = rtPathMatchCompile(pszComp, cchComp, RT_BOOL(fFlags & RTPATHGLOB_F_IGNORE_CASE),
1407 &pGlob->MatchInstrAlloc);
1408 if (RT_FAILURE(rc))
1409 return rc;
1410
1411 /* Check for plain text as well as full variable matching (not applicable after '**'). */
1412 uint16_t const cInstructions = pGlob->MatchInstrAlloc.iNext - iMatchProg;
1413 if ( cInstructions == 2
1414 && !fStarStar
1415 && pGlob->MatchInstrAlloc.paInstructions[iMatchProg + 1].enmOpCode == RTPATHMATCHOP_RETURN_MATCH_IF_AT_END)
1416 {
1417 if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRCMP
1418 || pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRICMP)
1419 pGlob->aComps[iComp].fPlain = true;
1420 else if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_CMP
1421 || pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP)
1422 {
1423 pGlob->aComps[iComp].fExpVariable = true;
1424 AssertMsgReturn( iComp == 0
1425 || !g_aVariables[pGlob->MatchInstrAlloc.paInstructions[iMatchProg].uOp2].fFirstOnly,
1426 ("Glob variable '%.*s' can only be used as the path component.\n", cchComp, pszComp),
1427 VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
1428 }
1429 else
1430 pGlob->aComps[iComp].fNormal = true;
1431 }
1432 else
1433 pGlob->aComps[iComp].fNormal = true;
1434 }
1435 else
1436 {
1437 /* Recursive "**" matching. */
1438 pGlob->aComps[iComp].fNormal = false;
1439 pGlob->aComps[iComp].fStarStar = true;
1440 AssertReturn(!fStarStar, VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED); /** @todo implement multiple '**' sequences in a pattern. */
1441 fStarStar = true;
1442 }
1443 }
1444 pGlob->aComps[pParsed->cComps - 1].fFinal = true;
1445
1446 return VINF_SUCCESS;
1447}
1448
1449
1450/**
1451 * This is for skipping overly long directories entries.
1452 *
1453 * Since our directory entry buffer can hold filenames of RTPATH_MAX bytes, we
1454 * can safely skip filenames that are longer. There are very few file systems
1455 * that can actually store filenames longer than 255 bytes at time of coding
1456 * (2015-09), and extremely few which can exceed 4096 (RTPATH_MAX) bytes.
1457 *
1458 * @returns IPRT status code.
1459 * @param hDir The directory handle.
1460 * @param cbNeeded The required entry size.
1461 */
1462DECL_NO_INLINE(static, int) rtPathGlobSkipDirEntry(RTDIR hDir, size_t cbNeeded)
1463{
1464 int rc = VERR_BUFFER_OVERFLOW;
1465 cbNeeded = RT_ALIGN_Z(cbNeeded, 16);
1466 PRTDIRENTRY pDirEntry = (PRTDIRENTRY)RTMemTmpAlloc(cbNeeded);
1467 if (pDirEntry)
1468 {
1469 rc = RTDirRead(hDir, pDirEntry, &cbNeeded);
1470 RTMemTmpFree(pDirEntry);
1471 }
1472 return rc;
1473}
1474
1475
1476/**
1477 * Adds a result.
1478 *
1479 * @returns IPRT status code.
1480 * @retval VINF_CALLBACK_RETURN if we can stop searching.
1481 *
1482 * @param pGlob The glob instance data.
1483 * @param cchPath The number of bytes to add from pGlob->szPath.
1484 * @param uType The RTDIRENTRYTYPE value.
1485 */
1486DECL_NO_INLINE(static, int) rtPathGlobAddResult(PRTPATHGLOB pGlob, size_t cchPath, uint8_t uType)
1487{
1488 if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
1489 {
1490 PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + 1]));
1491 if (pEntry)
1492 {
1493 pEntry->uType = uType;
1494 pEntry->cchPath = (uint16_t)cchPath;
1495 memcpy(pEntry->szPath, pGlob->szPath, cchPath);
1496 pEntry->szPath[cchPath] = '\0';
1497
1498 pEntry->pNext = NULL;
1499 *pGlob->ppNext = pEntry;
1500 pGlob->ppNext = &pEntry->pNext;
1501 pGlob->cResults++;
1502
1503 if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
1504 return VINF_SUCCESS;
1505 return VINF_CALLBACK_RETURN;
1506 }
1507 return VERR_NO_MEMORY;
1508 }
1509 return VERR_TOO_MUCH_DATA;
1510}
1511
1512
1513/**
1514 * Adds a result, constructing the path from two string.
1515 *
1516 * @returns IPRT status code.
1517 * @retval VINF_CALLBACK_RETURN if we can stop searching.
1518 *
1519 * @param pGlob The glob instance data.
1520 * @param cchPath The number of bytes to add from pGlob->szPath.
1521 * @param pchName The string (usual filename) to append to the szPath.
1522 * @param cchName The length of the string to append.
1523 * @param uType The RTDIRENTRYTYPE value.
1524 */
1525DECL_NO_INLINE(static, int) rtPathGlobAddResult2(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
1526 uint8_t uType)
1527{
1528 if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
1529 {
1530 PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
1531 if (pEntry)
1532 {
1533 pEntry->uType = uType;
1534 pEntry->cchPath = (uint16_t)(cchPath + cchName);
1535 memcpy(pEntry->szPath, pGlob->szPath, cchPath);
1536 memcpy(&pEntry->szPath[cchPath], pchName, cchName);
1537 pEntry->szPath[cchPath + cchName] = '\0';
1538
1539 pEntry->pNext = NULL;
1540 *pGlob->ppNext = pEntry;
1541 pGlob->ppNext = &pEntry->pNext;
1542 pGlob->cResults++;
1543
1544 if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
1545 return VINF_SUCCESS;
1546 return VINF_CALLBACK_RETURN;
1547 }
1548 return VERR_NO_MEMORY;
1549 }
1550 return VERR_TOO_MUCH_DATA;
1551}
1552
1553
1554/**
1555 * Prepares a result, constructing the path from two string.
1556 *
1557 * The caller must call either rtPathGlobCommitResult or
1558 * rtPathGlobRollbackResult to complete the operation.
1559 *
1560 * @returns IPRT status code.
1561 * @retval VINF_CALLBACK_RETURN if we can stop searching.
1562 *
1563 * @param pGlob The glob instance data.
1564 * @param cchPath The number of bytes to add from pGlob->szPath.
1565 * @param pchName The string (usual filename) to append to the szPath.
1566 * @param cchName The length of the string to append.
1567 * @param uType The RTDIRENTRYTYPE value.
1568 */
1569DECL_NO_INLINE(static, int) rtPathGlobAlmostAddResult(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
1570 uint8_t uType)
1571{
1572 if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
1573 {
1574 PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
1575 if (pEntry)
1576 {
1577 pEntry->uType = uType;
1578 pEntry->cchPath = (uint16_t)(cchPath + cchName);
1579 memcpy(pEntry->szPath, pGlob->szPath, cchPath);
1580 memcpy(&pEntry->szPath[cchPath], pchName, cchName);
1581 pEntry->szPath[cchPath + cchName] = '\0';
1582
1583 pEntry->pNext = NULL;
1584 *pGlob->ppNext = pEntry;
1585 /* Note! We don't update ppNext here, that is done in rtPathGlobCommitResult. */
1586
1587 if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
1588 return VINF_SUCCESS;
1589 return VINF_CALLBACK_RETURN;
1590 }
1591 return VERR_NO_MEMORY;
1592 }
1593 return VERR_TOO_MUCH_DATA;
1594}
1595
1596
1597/**
1598 * Commits a pending result from rtPathGlobAlmostAddResult.
1599 *
1600 * @param pGlob The glob instance data.
1601 * @param uType The RTDIRENTRYTYPE value.
1602 */
1603static void rtPathGlobCommitResult(PRTPATHGLOB pGlob, uint8_t uType)
1604{
1605 PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
1606 AssertPtr(pEntry);
1607 pEntry->uType = uType;
1608 pGlob->ppNext = &pEntry->pNext;
1609 pGlob->cResults++;
1610}
1611
1612
1613/**
1614 * Rolls back a pending result from rtPathGlobAlmostAddResult.
1615 *
1616 * @param pGlob The glob instance data.
1617 */
1618static void rtPathGlobRollbackResult(PRTPATHGLOB pGlob)
1619{
1620 PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
1621 AssertPtr(pEntry);
1622 RTMemFree(pEntry);
1623 *pGlob->ppNext = NULL;
1624}
1625
1626
1627
1628/**
1629 * Whether to call rtPathGlobExecRecursiveVarExp for the next component.
1630 *
1631 * @returns true / false.
1632 * @param pGlob The glob instance data.
1633 * @param offPath The next path offset/length.
1634 * @param iComp The next component.
1635 */
1636DECLINLINE(bool) rtPathGlobExecIsExpVar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1637{
1638 return pGlob->aComps[iComp].fExpVariable
1639 && ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
1640 || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
1641}
1642
1643/**
1644 * Whether to call rtPathGlobExecRecursivePlainText for the next component.
1645 *
1646 * @returns true / false.
1647 * @param pGlob The glob instance data.
1648 * @param offPath The next path offset/length.
1649 * @param iComp The next component.
1650 */
1651DECLINLINE(bool) rtPathGlobExecIsPlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1652{
1653 return pGlob->aComps[iComp].fPlain
1654 && ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
1655 || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
1656}
1657
1658
1659/**
1660 * Helper for rtPathGlobExecRecursiveVarExp and rtPathGlobExecRecursivePlainText
1661 * that compares a file mode mask with dir/no-dir wishes of the caller.
1662 *
1663 * @returns true if match, false if not.
1664 * @param pGlob The glob instance data.
1665 * @param fMode The file mode (only the type is used).
1666 */
1667DECLINLINE(bool) rtPathGlobExecIsMatchFinalWithFileMode(PRTPATHGLOB pGlob, RTFMODE fMode)
1668{
1669 if (!(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)))
1670 return true;
1671 return RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS) == RTFS_IS_DIRECTORY(fMode);
1672}
1673
1674
1675/**
1676 * Recursive globbing - star-star mode.
1677 *
1678 * @returns IPRT status code.
1679 * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1680 *
1681 * @param pGlob The glob instance data.
1682 * @param offPath The current path offset/length.
1683 * @param iStarStarComp The star-star component index.
1684 * @param offStarStarPath The offset of the star-star component in the
1685 * pattern path.
1686 */
1687DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp,
1688 size_t offStarStarPath)
1689{
1690 /** @todo implement multi subdir matching. */
1691 RT_NOREF_PV(pGlob);
1692 RT_NOREF_PV(offPath);
1693 RT_NOREF_PV(iStarStarComp);
1694 RT_NOREF_PV(offStarStarPath);
1695 return VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED;
1696}
1697
1698
1699
1700/**
1701 * Recursive globbing - variable expansion optimization.
1702 *
1703 * @returns IPRT status code.
1704 * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1705 *
1706 * @param pGlob The glob instance data.
1707 * @param offPath The current path offset/length.
1708 * @param iComp The current component.
1709 */
1710DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1711{
1712 Assert(iComp < pGlob->pParsed->cComps);
1713 Assert(pGlob->szPath[offPath] == '\0');
1714 Assert(pGlob->aComps[iComp].fExpVariable);
1715 Assert(!pGlob->aComps[iComp].fPlain);
1716 Assert(!pGlob->aComps[iComp].fStarStar);
1717 Assert(rtPathGlobExecIsExpVar(pGlob, offPath, iComp));
1718
1719 /*
1720 * Fish the variable index out of the first matching instruction.
1721 */
1722 Assert( pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1723 == RTPATHMATCHOP_VARIABLE_VALUE_CMP
1724 || pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1725 == RTPATHMATCHOP_VARIABLE_VALUE_ICMP);
1726 uint16_t const iVar = pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].uOp2;
1727
1728 /*
1729 * Enumerate all the variable, giving them the plain text treatment.
1730 */
1731 for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
1732 {
1733 size_t cch;
1734 int rcVar = g_aVariables[iVar].pfnQuery(iItem, &pGlob->szPath[offPath], sizeof(pGlob->szPath) - offPath, &cch,
1735 &pGlob->MatchCache);
1736 if (RT_SUCCESS(rcVar))
1737 {
1738 Assert(pGlob->szPath[offPath + cch] == '\0');
1739
1740 int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1741 if (RT_SUCCESS(rc))
1742 {
1743 if (pGlob->aComps[iComp].fFinal)
1744 {
1745 if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
1746 {
1747 rc = rtPathGlobAddResult(pGlob, cch,
1748 (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
1749 >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
1750 if (rc != VINF_SUCCESS)
1751 return rc;
1752 }
1753 }
1754 else if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
1755 {
1756 Assert(pGlob->aComps[iComp].fDir);
1757 cch = RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
1758 if (cch > 0)
1759 {
1760 if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
1761 rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
1762 else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
1763 rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
1764 else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1765 rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
1766 else
1767 rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
1768 if (rc != VINF_SUCCESS)
1769 return rc;
1770 }
1771 else
1772 pGlob->cPathOverflows++;
1773 }
1774 }
1775 /* else: file doesn't exist or something else is wrong, ignore this. */
1776 if (rcVar == VINF_EOF)
1777 return VINF_SUCCESS;
1778 }
1779 else if (rcVar == VERR_EOF)
1780 return VINF_SUCCESS;
1781 else if (rcVar != VERR_TRY_AGAIN)
1782 {
1783 Assert(rcVar == VERR_BUFFER_OVERFLOW);
1784 pGlob->cPathOverflows++;
1785 }
1786 }
1787 AssertFailedReturn(VINF_SUCCESS); /* Too many items returned, probably buggy query method. */
1788}
1789
1790
1791/**
1792 * Recursive globbing - plain text optimization.
1793 *
1794 * @returns IPRT status code.
1795 * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1796 *
1797 * @param pGlob The glob instance data.
1798 * @param offPath The current path offset/length.
1799 * @param iComp The current component.
1800 */
1801DECL_NO_INLINE(static, int) rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1802{
1803 /*
1804 * Instead of recursing, we loop thru adjacent plain text components.
1805 */
1806 for (;;)
1807 {
1808 /*
1809 * Preconditions.
1810 */
1811 Assert(iComp < pGlob->pParsed->cComps);
1812 Assert(pGlob->szPath[offPath] == '\0');
1813 Assert(pGlob->aComps[iComp].fPlain);
1814 Assert(!pGlob->aComps[iComp].fExpVariable);
1815 Assert(!pGlob->aComps[iComp].fStarStar);
1816 Assert(rtPathGlobExecIsPlainText(pGlob, offPath, iComp));
1817 Assert(pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1818 == RTPATHMATCHOP_STRCMP
1819 || pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
1820 == RTPATHMATCHOP_STRICMP);
1821
1822 /*
1823 * Add the plain text component to the path.
1824 */
1825 size_t const cch = pGlob->pParsed->aComps[iComp].cch;
1826 if (cch + pGlob->aComps[iComp].fDir < sizeof(pGlob->szPath) - offPath)
1827 {
1828 memcpy(&pGlob->szPath[offPath], &pGlob->pszPattern[pGlob->pParsed->aComps[iComp].off], cch);
1829 offPath += cch;
1830 pGlob->szPath[offPath] = '\0';
1831
1832 /*
1833 * Check if it exists.
1834 */
1835 int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
1836 if (RT_SUCCESS(rc))
1837 {
1838 if (pGlob->aComps[iComp].fFinal)
1839 {
1840 if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
1841 return rtPathGlobAddResult(pGlob, offPath,
1842 (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
1843 >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
1844 break;
1845 }
1846
1847 if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
1848 {
1849 Assert(pGlob->aComps[iComp].fDir);
1850 pGlob->szPath[offPath++] = RTPATH_SLASH;
1851 pGlob->szPath[offPath] = '\0';
1852
1853 iComp++;
1854 if (rtPathGlobExecIsExpVar(pGlob, offPath, iComp))
1855 return rtPathGlobExecRecursiveVarExp(pGlob, offPath, iComp);
1856 if (!rtPathGlobExecIsPlainText(pGlob, offPath, iComp))
1857 return rtPathGlobExecRecursiveGeneric(pGlob, offPath, iComp);
1858 if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1859 return rtPathGlobExecRecursiveStarStar(pGlob, offPath, iComp, offPath);
1860
1861 /* Continue with the next plain text component. */
1862 continue;
1863 }
1864 }
1865 /* else: file doesn't exist or something else is wrong, ignore this. */
1866 }
1867 else
1868 pGlob->cPathOverflows++;
1869 break;
1870 }
1871 return VINF_SUCCESS;
1872}
1873
1874
1875/**
1876 * Recursive globbing - generic.
1877 *
1878 * @returns IPRT status code.
1879 * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
1880 *
1881 * @param pGlob The glob instance data.
1882 * @param offPath The current path offset/length.
1883 * @param iComp The current component.
1884 */
1885DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
1886{
1887 /*
1888 * Enumerate entire directory and match each entry.
1889 */
1890 RTDIR hDir;
1891 int rc = RTDirOpen(&hDir, offPath ? pGlob->szPath : ".");
1892 if (RT_SUCCESS(rc))
1893 {
1894 for (;;)
1895 {
1896 size_t cch = sizeof(pGlob->u);
1897 rc = RTDirRead(hDir, &pGlob->u.DirEntry, &cch);
1898 if (RT_SUCCESS(rc))
1899 {
1900 if (pGlob->aComps[iComp].fFinal)
1901 {
1902 /*
1903 * Final component: Check if it matches the current pattern.
1904 */
1905 if ( !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS))
1906 || RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
1907 == (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
1908 || pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
1909 {
1910 rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1911 &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
1912 &pGlob->MatchCache);
1913 if (RT_SUCCESS(rc))
1914 {
1915 /* Construct the result. */
1916 if ( pGlob->u.DirEntry.enmType != RTDIRENTRYTYPE_UNKNOWN
1917 || !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) )
1918 rc = rtPathGlobAddResult2(pGlob, offPath, pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1919 (uint8_t)pGlob->u.DirEntry.enmType);
1920 else
1921 {
1922 rc = rtPathGlobAlmostAddResult(pGlob, offPath,
1923 pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1924 (uint8_t)RTDIRENTRYTYPE_UNKNOWN);
1925 if (RT_SUCCESS(rc))
1926 {
1927 RTDirQueryUnknownType((*pGlob->ppNext)->szPath, false /*fFollowSymlinks*/,
1928 &pGlob->u.DirEntry.enmType);
1929 if ( RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
1930 == (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY))
1931 rtPathGlobCommitResult(pGlob, (uint8_t)pGlob->u.DirEntry.enmType);
1932 else
1933 rtPathGlobRollbackResult(pGlob);
1934 }
1935 }
1936 if (rc != VINF_SUCCESS)
1937 break;
1938 }
1939 else
1940 {
1941 AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
1942 rc = VINF_SUCCESS;
1943 }
1944 }
1945 }
1946 /*
1947 * Intermediate component: Directories only.
1948 */
1949 else if ( pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY
1950 || pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
1951 {
1952 rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
1953 &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
1954 &pGlob->MatchCache);
1955 if (RT_SUCCESS(rc))
1956 {
1957 /* Recurse down into the alleged directory. */
1958 cch = offPath + pGlob->u.DirEntry.cbName;
1959 if (cch + 1 < sizeof(pGlob->szPath))
1960 {
1961 memcpy(&pGlob->szPath[offPath], pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName);
1962 pGlob->szPath[cch++] = RTPATH_SLASH;
1963 pGlob->szPath[cch] = '\0';
1964
1965 if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
1966 rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
1967 else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
1968 rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
1969 else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
1970 rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
1971 else
1972 rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
1973 if (rc != VINF_SUCCESS)
1974 return rc;
1975 }
1976 else
1977 pGlob->cPathOverflows++;
1978 }
1979 else
1980 {
1981 AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
1982 rc = VINF_SUCCESS;
1983 }
1984 }
1985 }
1986 /*
1987 * RTDirRead failure.
1988 */
1989 else
1990 {
1991 /* The end? */
1992 if (rc == VERR_NO_MORE_FILES)
1993 rc = VINF_SUCCESS;
1994 /* Try skip the entry if we end up with an overflow (szPath can't hold it either then). */
1995 else if (rc == VERR_BUFFER_OVERFLOW)
1996 {
1997 pGlob->cPathOverflows++;
1998 rc = rtPathGlobSkipDirEntry(hDir, cch);
1999 if (RT_SUCCESS(rc))
2000 continue;
2001 }
2002 /* else: Any other error is unexpected and should be reported. */
2003 break;
2004 }
2005 }
2006
2007 RTDirClose(hDir);
2008 }
2009 /* Directory doesn't exist or something else is wrong, ignore this. */
2010 else
2011 rc = VINF_SUCCESS;
2012 return rc;
2013}
2014
2015
2016/**
2017 * Executes a glob search.
2018 *
2019 * @returns IPRT status code.
2020 * @param pGlob The glob instance data.
2021 */
2022static int rtPathGlobExec(PRTPATHGLOB pGlob)
2023{
2024 Assert(pGlob->offFirstPath < sizeof(pGlob->szPath));
2025 Assert(pGlob->szPath[pGlob->offFirstPath] == '\0');
2026
2027 int rc;
2028 if (RT_LIKELY(pGlob->iFirstComp < pGlob->pParsed->cComps))
2029 {
2030 /*
2031 * Call the appropriate function.
2032 */
2033 if (rtPathGlobExecIsExpVar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
2034 rc = rtPathGlobExecRecursiveVarExp(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
2035 else if (rtPathGlobExecIsPlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
2036 rc = rtPathGlobExecRecursivePlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
2037 else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
2038 rc = rtPathGlobExecRecursiveStarStar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp, pGlob->offFirstPath);
2039 else
2040 rc = rtPathGlobExecRecursiveGeneric(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
2041 }
2042 else
2043 {
2044 /*
2045 * Special case where we only have a root component or tilde expansion.
2046 */
2047 Assert(pGlob->offFirstPath > 0);
2048 rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
2049 if ( RT_SUCCESS(rc)
2050 && rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
2051 rc = rtPathGlobAddResult(pGlob, pGlob->offFirstPath,
2052 (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK) >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
2053 else
2054 rc = VINF_SUCCESS;
2055 }
2056
2057 /*
2058 * Adjust the status code. Check for results, hide RTPATHGLOB_F_FIRST_ONLY
2059 * status code, and add warning if necessary.
2060 */
2061 if (pGlob->cResults > 0)
2062 {
2063 if (rc == VINF_CALLBACK_RETURN)
2064 rc = VINF_SUCCESS;
2065 if (rc == VINF_SUCCESS)
2066 {
2067 if (pGlob->cPathOverflows > 0)
2068 rc = VINF_BUFFER_OVERFLOW;
2069 }
2070 }
2071 else
2072 rc = VERR_FILE_NOT_FOUND;
2073
2074 return rc;
2075}
2076
2077
2078RTDECL(int) RTPathGlob(const char *pszPattern, uint32_t fFlags, PPCRTPATHGLOBENTRY ppHead, uint32_t *pcResults)
2079{
2080 /*
2081 * Input validation.
2082 */
2083 AssertPtrReturn(ppHead, VERR_INVALID_POINTER);
2084 *ppHead = NULL;
2085 if (pcResults)
2086 {
2087 AssertPtrReturn(pcResults, VERR_INVALID_POINTER);
2088 *pcResults = 0;
2089 }
2090 AssertPtrReturn(pszPattern, VERR_INVALID_POINTER);
2091 AssertReturn(!(fFlags & ~RTPATHGLOB_F_MASK), VERR_INVALID_FLAGS);
2092 AssertReturn((fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) != (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS),
2093 VERR_INVALID_FLAGS);
2094
2095 /*
2096 * Parse the path.
2097 */
2098 size_t cbParsed = RT_UOFFSETOF(RTPATHPARSED, aComps[1]); /** @todo 16 after testing */
2099 PRTPATHPARSED pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
2100 AssertReturn(pParsed, VERR_NO_MEMORY);
2101 int rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
2102 if (rc == VERR_BUFFER_OVERFLOW)
2103 {
2104 cbParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pParsed->cComps + 1]);
2105 RTMemTmpFree(pParsed);
2106 pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
2107 AssertReturn(pParsed, VERR_NO_MEMORY);
2108
2109 rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
2110 }
2111 if (RT_SUCCESS(rc))
2112 {
2113 /*
2114 * Check dir slash vs. only/not dir flag.
2115 */
2116 if ( !(fFlags & RTPATHGLOB_F_NO_DIRS)
2117 || ( !(pParsed->fProps & RTPATH_PROP_DIR_SLASH)
2118 && ( !(pParsed->fProps & (RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_UNC))
2119 || pParsed->cComps > 1) ) )
2120 {
2121 if (pParsed->fProps & RTPATH_PROP_DIR_SLASH)
2122 fFlags |= RTPATHGLOB_F_ONLY_DIRS;
2123
2124 /*
2125 * Allocate and initialize the glob state data structure.
2126 */
2127 size_t cbGlob = RT_UOFFSETOF_DYN(RTPATHGLOB, aComps[pParsed->cComps + 1]);
2128 PRTPATHGLOB pGlob = (PRTPATHGLOB)RTMemTmpAllocZ(cbGlob);
2129 if (pGlob)
2130 {
2131 pGlob->pszPattern = pszPattern;
2132 pGlob->fFlags = fFlags;
2133 pGlob->pParsed = pParsed;
2134 pGlob->ppNext = &pGlob->pHead;
2135 rc = rtPathGlobParse(pGlob, pszPattern, pParsed, fFlags);
2136 if (RT_SUCCESS(rc))
2137 {
2138 /*
2139 * Execute the search.
2140 */
2141 rc = rtPathGlobExec(pGlob);
2142 if (RT_SUCCESS(rc))
2143 {
2144 *ppHead = pGlob->pHead;
2145 if (pcResults)
2146 *pcResults = pGlob->cResults;
2147 }
2148 else
2149 RTPathGlobFree(pGlob->pHead);
2150 }
2151
2152 RTMemTmpFree(pGlob->MatchInstrAlloc.paInstructions);
2153 RTMemTmpFree(pGlob);
2154 }
2155 else
2156 rc = VERR_NO_MEMORY;
2157 }
2158 else
2159 rc = VERR_NOT_FOUND;
2160 }
2161 RTMemTmpFree(pParsed);
2162 return rc;
2163
2164
2165}
2166
2167
2168RTDECL(void) RTPathGlobFree(PCRTPATHGLOBENTRY pHead)
2169{
2170 PRTPATHGLOBENTRY pCur = (PRTPATHGLOBENTRY)pHead;
2171 while (pCur)
2172 {
2173 PRTPATHGLOBENTRY pNext = pCur->pNext;
2174 pCur->pNext = NULL;
2175 RTMemFree(pCur);
2176 pCur = pNext;
2177 }
2178}
2179
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use