VirtualBox

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

Last change on this file since 102948 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

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