summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/path/RTPathGlob.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/common/path/RTPathGlob.cpp')
-rw-r--r--src/VBox/Runtime/common/path/RTPathGlob.cpp2167
1 files changed, 2167 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/path/RTPathGlob.cpp b/src/VBox/Runtime/common/path/RTPathGlob.cpp
new file mode 100644
index 00000000..df7a8a29
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathGlob.cpp
@@ -0,0 +1,2167 @@
+/* $Id: RTPathGlob.cpp $ */
+/** @file
+ * IPRT - RTPathGlob
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#if defined(RT_OS_WINDOWS)
+# include <iprt/utf16.h>
+# include <iprt/win/windows.h>
+# include "../../r3/win/internal-r3-win.h"
+
+#elif defined(RT_OS_OS2)
+# define INCL_BASE
+# include <os2.h>
+# undef RT_MAX /* collision */
+
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum number of results. */
+#define RTPATHGLOB_MAX_RESULTS _32K
+/** Maximum number of zero-or-more wildcards in a pattern.
+ * This limits stack usage and recursion depth, as well as execution time. */
+#define RTPATHMATCH_MAX_ZERO_OR_MORE 24
+/** Maximum number of variable items. */
+#define RTPATHMATCH_MAX_VAR_ITEMS _4K
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Matching operation.
+ */
+typedef enum RTPATHMATCHOP
+{
+ RTPATHMATCHOP_INVALID = 0,
+ /** EOS: Returns a match if at end of string. */
+ RTPATHMATCHOP_RETURN_MATCH_IF_AT_END,
+ /** Asterisk: Returns a match (trailing asterisk). */
+ RTPATHMATCHOP_RETURN_MATCH,
+ /** Asterisk: Returns a match (just asterisk), unless it's '.' or '..'. */
+ RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT,
+ /** Plain text: Case sensitive string compare. */
+ RTPATHMATCHOP_STRCMP,
+ /** Plain text: Case insensitive string compare. */
+ RTPATHMATCHOP_STRICMP,
+ /** Question marks: Skips exactly one code point. */
+ RTPATHMATCHOP_SKIP_ONE_CODEPOINT,
+ /** Question marks: Skips exactly RTPATHMATCHCORE::cch code points. */
+ RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS,
+ /** Char set: Requires the next codepoint to be in the ASCII-7 set defined by
+ * RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. No ranges. */
+ RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7,
+ /** Char set: Requires the next codepoint to not be in the ASCII-7 set defined
+ * by RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. No ranges. */
+ RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7,
+ /** Char set: Requires the next codepoint to be in the extended set defined by
+ * RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. Ranges, UTF-8. */
+ RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED,
+ /** Char set: Requires the next codepoint to not be in the extended set defined
+ * by RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. Ranges, UTF-8. */
+ RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED,
+ /** Variable: Case sensitive variable value compare, RTPATHMATCHCORE::uOp2 is
+ * the variable table index. */
+ RTPATHMATCHOP_VARIABLE_VALUE_CMP,
+ /** Variable: Case insensitive variable value compare, RTPATHMATCHCORE::uOp2 is
+ * the variable table index. */
+ RTPATHMATCHOP_VARIABLE_VALUE_ICMP,
+ /** Asterisk: Match zero or more code points, there must be at least
+ * RTPATHMATCHCORE::cch code points after it. */
+ RTPATHMATCHOP_ZERO_OR_MORE,
+ /** Asterisk: Match zero or more code points, there must be at least
+ * RTPATHMATCHCORE::cch code points after it, unless it's '.' or '..'. */
+ RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT,
+ /** End of valid operations. */
+ RTPATHMATCHOP_END
+} RTPATHMATCHOP;
+
+/**
+ * Matching instruction.
+ */
+typedef struct RTPATHMATCHCORE
+{
+ /** The action to take. */
+ RTPATHMATCHOP enmOpCode;
+ /** Generic value operand. */
+ uint16_t uOp2;
+ /** Generic length operand. */
+ uint16_t cch;
+ /** Generic string pointer operand. */
+ const char *pch;
+} RTPATHMATCHCORE;
+/** Pointer to a matching instruction. */
+typedef RTPATHMATCHCORE *PRTPATHMATCHCORE;
+/** Pointer to a const matching instruction. */
+typedef RTPATHMATCHCORE const *PCRTPATHMATCHCORE;
+
+/**
+ * Path matching instruction allocator.
+ */
+typedef struct RTPATHMATCHALLOC
+{
+ /** Allocated array of instructions. */
+ PRTPATHMATCHCORE paInstructions;
+ /** Index of the next free entry in paScratch. */
+ uint32_t iNext;
+ /** Number of instructions allocated. */
+ uint32_t cAllocated;
+} RTPATHMATCHALLOC;
+/** Pointer to a matching instruction allocator. */
+typedef RTPATHMATCHALLOC *PRTPATHMATCHALLOC;
+
+/**
+ * Path matching cache, mainly intended for variables like the PATH.
+ */
+typedef struct RTPATHMATCHCACHE
+{
+ /** @todo optimize later. */
+ uint32_t iNothingYet;
+} RTPATHMATCHCACHE;
+/** Pointer to a path matching cache. */
+typedef RTPATHMATCHCACHE *PRTPATHMATCHCACHE;
+
+
+
+/** Parsed path entry.*/
+typedef struct RTPATHGLOBPPE
+{
+ /** Normal: Index into RTPATHGLOB::MatchInstrAlloc.paInstructions. */
+ uint32_t iMatchProg : 16;
+ /** Set if this is a normal entry which is matched using iMatchProg. */
+ uint32_t fNormal : 1;
+ /** !fNormal: Plain name that can be dealt with using without
+ * enumerating the whole directory, unless of course the file system is case
+ * sensitive and the globbing isn't (that needs figuring out on a per
+ * directory basis). */
+ uint32_t fPlain : 1;
+ /** !fNormal: Match zero or more subdirectories. */
+ uint32_t fStarStar : 1;
+ /** !fNormal: The whole component is a variable expansion. */
+ uint32_t fExpVariable : 1;
+
+ /** Filter: Set if it only matches directories. */
+ uint32_t fDir : 1;
+ /** Set if it's the final component. */
+ uint32_t fFinal : 1;
+
+ /** Unused bits. */
+ uint32_t fReserved : 2+8;
+} RTPATHGLOBPPE;
+
+
+typedef struct RTPATHGLOB
+{
+ /** Path buffer. */
+ char szPath[RTPATH_MAX];
+ /** Temporary buffers. */
+ union
+ {
+ /** File system object info structure. */
+ RTFSOBJINFO ObjInfo;
+ /** Directory entry buffer. */
+ RTDIRENTRY DirEntry;
+ /** Padding the buffer to an unreasonably large size. */
+ uint8_t abPadding[RTPATH_MAX + sizeof(RTDIRENTRY)];
+ } u;
+
+
+ /** Where to insert the next one.*/
+ PRTPATHGLOBENTRY *ppNext;
+ /** The head pointer. */
+ PRTPATHGLOBENTRY pHead;
+ /** Result count. */
+ uint32_t cResults;
+ /** Counts path overflows. */
+ uint32_t cPathOverflows;
+ /** The input flags. */
+ uint32_t fFlags;
+ /** Matching instruction allocator. */
+ RTPATHMATCHALLOC MatchInstrAlloc;
+ /** Matching state. */
+ RTPATHMATCHCACHE MatchCache;
+
+ /** The pattern string. */
+ const char *pszPattern;
+ /** The parsed path. */
+ PRTPATHPARSED pParsed;
+ /** The component to start with. */
+ uint16_t iFirstComp;
+ /** The corresponding path offset (previous components already present). */
+ uint16_t offFirstPath;
+ /** Path component information we need. */
+ RTPATHGLOBPPE aComps[1];
+} RTPATHGLOB;
+typedef RTPATHGLOB *PRTPATHGLOB;
+
+
+/**
+ * Matching variable lookup table.
+ * Currently so small we don't bother sorting it and doing binary lookups.
+ */
+typedef struct RTPATHMATCHVAR
+{
+ /** The variable name. */
+ const char *pszName;
+ /** The variable name length. */
+ uint16_t cchName;
+ /** Only available as the verify first component. */
+ bool fFirstOnly;
+
+ /**
+ * Queries a given variable value.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_BUFFER_OVERFLOW
+ * @retval VERR_TRY_AGAIN if the caller should skip this value item and try the
+ * next one instead (e.g. env var not present).
+ * @retval VINF_EOF when retrieving the last one, if possible.
+ * @retval VERR_EOF when @a iItem is past the item space.
+ *
+ * @param iItem The variable value item to retrieve. (A variable may
+ * have more than one value, e.g. 'BothProgramFile' on a
+ * 64-bit system or 'Path'.)
+ * @param pszBuf Where to return the value.
+ * @param cbBuf The buffer size.
+ * @param pcchValue Where to return the length of the return string.
+ * @param pCache Pointer to the path matching cache. May speed up
+ * enumerating PATH items and similar.
+ */
+ DECLCALLBACKMEMBER(int, pfnQuery)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, PRTPATHMATCHCACHE pCache);
+
+ /**
+ * Matching method, optional.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS on match.
+ * @retval VERR_MISMATCH on mismatch.
+ *
+ * @param pszMatch String to match with (not terminated).
+ * @param cchMatch The length of what we match with.
+ * @param fIgnoreCase Whether to ignore case or not when comparing.
+ * @param pcchMatched Where to return the length of the match (value length).
+ */
+ DECLCALLBACKMEMBER(int, pfnMatch)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, size_t *pcchMatched);
+
+} RTPATHMATCHVAR;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp, size_t offStarStarPath);
+static int rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
+static int rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
+static int rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
+
+
+/**
+ * Implements the two variable access functions for a simple one value variable.
+ */
+#define RTPATHMATCHVAR_SIMPLE(a_Name, a_GetStrExpr) \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
+ PRTPATHMATCHCACHE pCache) \
+ { \
+ if (iItem == 0) \
+ { \
+ const char *pszValue = a_GetStrExpr; \
+ size_t cchValue = strlen(pszValue); \
+ if (cchValue + 1 <= cbBuf) \
+ { \
+ memcpy(pszBuf, pszValue, cchValue + 1); \
+ *pcchValue = cchValue; \
+ return VINF_EOF; \
+ } \
+ return VERR_BUFFER_OVERFLOW; \
+ } \
+ NOREF(pCache);\
+ return VERR_EOF; \
+ } \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
+ size_t *pcchMatched) \
+ { \
+ const char *pszValue = a_GetStrExpr; \
+ size_t cchValue = strlen(pszValue); \
+ if ( cchValue >= cchMatch \
+ && ( !fIgnoreCase \
+ ? memcmp(pszValue, pchMatch, cchValue) == 0 \
+ : RTStrNICmp(pszValue, pchMatch, cchValue) == 0) ) \
+ { \
+ *pcchMatched = cchValue; \
+ return VINF_SUCCESS; \
+ } \
+ return VERR_MISMATCH; \
+ } \
+ typedef int RT_CONCAT(DummyColonType_,a_Name)
+
+/**
+ * Implements mapping a glob variable to an environment variable.
+ */
+#define RTPATHMATCHVAR_SIMPLE_ENVVAR(a_Name, a_pszEnvVar, a_cbMaxValue) \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
+ PRTPATHMATCHCACHE pCache) \
+ { \
+ if (iItem == 0) \
+ { \
+ int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, pszBuf, cbBuf, pcchValue); \
+ if (RT_SUCCESS(rc)) \
+ return VINF_EOF; \
+ if (rc != VERR_ENV_VAR_NOT_FOUND) \
+ return rc; \
+ } \
+ NOREF(pCache);\
+ return VERR_EOF; \
+ } \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
+ size_t *pcchMatched) \
+ { \
+ char szValue[a_cbMaxValue]; \
+ size_t cchValue; \
+ int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, szValue, sizeof(szValue), &cchValue); \
+ if ( RT_SUCCESS(rc) \
+ && cchValue >= cchMatch \
+ && ( !fIgnoreCase \
+ ? memcmp(szValue, pchMatch, cchValue) == 0 \
+ : RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
+ { \
+ *pcchMatched = cchValue; \
+ return VINF_SUCCESS; \
+ } \
+ return VERR_MISMATCH; \
+ } \
+ typedef int RT_CONCAT(DummyColonType_,a_Name)
+
+/**
+ * Implements mapping a glob variable to multiple environment variable values.
+ *
+ * @param a_Name The variable name.
+ * @param a_apszVarNames Assumes to be a global variable that RT_ELEMENTS
+ * works correctly on.
+ * @param a_cbMaxValue The max expected value size.
+ */
+#define RTPATHMATCHVAR_MULTIPLE_ENVVARS(a_Name, a_apszVarNames, a_cbMaxValue) \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
+ PRTPATHMATCHCACHE pCache) \
+ { \
+ if (iItem < RT_ELEMENTS(a_apszVarNames)) \
+ { \
+ int rc = RTEnvGetEx(RTENV_DEFAULT, a_apszVarNames[iItem], pszBuf, cbBuf, pcchValue); \
+ if (RT_SUCCESS(rc)) \
+ return iItem + 1 == RT_ELEMENTS(a_apszVarNames) ? VINF_EOF : VINF_SUCCESS; \
+ if (rc == VERR_ENV_VAR_NOT_FOUND) \
+ rc = VERR_TRY_AGAIN; \
+ return rc; \
+ } \
+ NOREF(pCache);\
+ return VERR_EOF; \
+ } \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
+ size_t *pcchMatched) \
+ { \
+ for (uint32_t iItem = 0; iItem < RT_ELEMENTS(a_apszVarNames); iItem++) \
+ { \
+ char szValue[a_cbMaxValue]; \
+ size_t cchValue; \
+ int rc = RTEnvGetEx(RTENV_DEFAULT, a_apszVarNames[iItem], szValue, sizeof(szValue), &cchValue);\
+ if ( RT_SUCCESS(rc) \
+ && cchValue >= cchMatch \
+ && ( !fIgnoreCase \
+ ? memcmp(szValue, pchMatch, cchValue) == 0 \
+ : RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
+ { \
+ *pcchMatched = cchValue; \
+ return VINF_SUCCESS; \
+ } \
+ } \
+ return VERR_MISMATCH; \
+ } \
+ typedef int RT_CONCAT(DummyColonType_,a_Name)
+
+
+RTPATHMATCHVAR_SIMPLE(Arch, RTBldCfgTargetArch());
+RTPATHMATCHVAR_SIMPLE(Bits, RT_XSTR(ARCH_BITS));
+#ifdef RT_OS_WINDOWS
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinAppData, "AppData", RTPATH_MAX);
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinProgramData, "ProgramData", RTPATH_MAX);
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinProgramFiles, "ProgramFiles", RTPATH_MAX);
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinCommonProgramFiles, "CommonProgramFiles", RTPATH_MAX);
+# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherProgramFiles, "ProgramFiles(x86)", RTPATH_MAX);
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherCommonProgramFiles, "CommonProgramFiles(x86)", RTPATH_MAX);
+# else
+# error "Port ME!"
+# endif
+static const char * const a_apszWinProgramFilesVars[] =
+{
+ "ProgramFiles",
+# ifdef RT_ARCH_AMD64
+ "ProgramFiles(x86)",
+# endif
+};
+RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllProgramFiles, a_apszWinProgramFilesVars, RTPATH_MAX);
+static const char * const a_apszWinCommonProgramFilesVars[] =
+{
+ "CommonProgramFiles",
+# ifdef RT_ARCH_AMD64
+ "CommonProgramFiles(x86)",
+# endif
+};
+RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllCommonProgramFiles, a_apszWinCommonProgramFilesVars, RTPATH_MAX);
+#endif
+
+
+/**
+ * @interface_method_impl{RTPATHMATCHVAR,pfnQuery, Enumerates the PATH}
+ */
+static DECLCALLBACK(int) rtPathVarQuery_Path(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
+ PRTPATHMATCHCACHE pCache)
+{
+ RT_NOREF_PV(pCache);
+
+ /*
+ * Query the PATH value.
+ */
+/** @todo cache this in pCache with iItem and offset. */
+ char *pszPathFree = NULL;
+ char *pszPath = pszBuf;
+ size_t cchActual;
+ const char *pszVarNm = "PATH";
+ int rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPath, cbBuf, &cchActual);
+#ifdef RT_OS_WINDOWS
+ if (rc == VERR_ENV_VAR_NOT_FOUND)
+ rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm = "Path", pszPath, cbBuf, &cchActual);
+#endif
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ for (uint32_t iTry = 0; iTry < 10; iTry++)
+ {
+ size_t cbPathBuf = RT_ALIGN_Z(cchActual + 1 + 64 * iTry, 64);
+ pszPathFree = (char *)RTMemTmpAlloc(cbPathBuf);
+ rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPathFree, cbPathBuf, &cchActual);
+ if (RT_SUCCESS(rc))
+ break;
+ RTMemTmpFree(pszPathFree);
+ AssertReturn(cchActual >= cbPathBuf, VERR_INTERNAL_ERROR_3);
+ }
+ pszPath = pszPathFree;
+ }
+
+ /*
+ * Spool forward to the given PATH item.
+ */
+ rc = VERR_EOF;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ const char chSep = ';';
+#else
+ const char chSep = ':';
+#endif
+ while (*pszPath != '\0')
+ {
+ char *pchSep = strchr(pszPath, chSep);
+
+ /* We ignore empty strings, which is probably not entirely correct,
+ but works better on DOS based system with many entries added
+ without checking whether there is a trailing separator or not.
+ Thus, the current directory is only searched if a '.' is present
+ in the PATH. */
+ if (pchSep == pszPath)
+ pszPath++;
+ else if (iItem > 0)
+ {
+ /* If we didn't find a separator, the item doesn't exists. Quit. */
+ if (!pchSep)
+ break;
+
+ pszPath = pchSep + 1;
+ iItem--;
+ }
+ else
+ {
+ /* We've reached the item we wanted. */
+ size_t cchComp = pchSep ? pchSep - pszPath : strlen(pszPath);
+ if (cchComp < cbBuf)
+ {
+ if (pszBuf != pszPath)
+ memmove(pszBuf, pszPath, cchComp);
+ pszBuf[cchComp] = '\0';
+ rc = pchSep ? VINF_SUCCESS : VINF_EOF;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ *pcchValue = cchComp;
+ break;
+ }
+ }
+
+ if (pszPathFree)
+ RTMemTmpFree(pszPathFree);
+ return rc;
+}
+
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+/**
+ * @interface_method_impl{RTPATHMATCHVAR,pfnQuery,
+ * The system drive letter + colon.}.
+ */
+static DECLCALLBACK(int) rtPathVarQuery_DosSystemDrive(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
+ PRTPATHMATCHCACHE pCache)
+{
+ RT_NOREF_PV(pCache);
+
+ if (iItem == 0)
+ {
+ AssertReturn(cbBuf >= 3, VERR_BUFFER_OVERFLOW);
+
+# ifdef RT_OS_WINDOWS
+ /* Since this is used at the start of a pattern, we assume
+ we've got more than enough buffer space. */
+ AssertReturn(g_pfnGetSystemWindowsDirectoryW, VERR_SYMBOL_NOT_FOUND);
+ PRTUTF16 pwszTmp = (PRTUTF16)pszBuf;
+ UINT cch = g_pfnGetSystemWindowsDirectoryW(pwszTmp, (UINT)(cbBuf / sizeof(WCHAR)));
+ if (cch >= 2)
+ {
+ RTUTF16 wcDrive = pwszTmp[0];
+ if ( RT_C_IS_ALPHA(wcDrive)
+ && pwszTmp[1] == ':')
+ {
+ pszBuf[0] = wcDrive;
+ pszBuf[1] = ':';
+ pszBuf[2] = '\0';
+ *pcchValue = 2;
+ return VINF_EOF;
+ }
+ }
+# else
+ ULONG ulDrive = ~(ULONG)0;
+ APIRET rc = DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, &ulDrive, sizeof(ulDrive));
+ ulDrive--; /* 1 = 'A' */
+ if ( rc == NO_ERROR
+ && ulDrive <= (ULONG)'Z')
+ {
+ pszBuf[0] = (char)ulDrive + 'A';
+ pszBuf[1] = ':';
+ pszBuf[2] = '\0';
+ *pcchValue = 2;
+ return VINF_EOF;
+ }
+# endif
+ return VERR_INTERNAL_ERROR_4;
+ }
+ return VERR_EOF;
+}
+#endif
+
+
+#ifdef RT_OS_WINDOWS
+/**
+ * @interface_method_impl{RTPATHMATCHVAR,pfnQuery,
+ * The system root directory (C:\Windows).}.
+ */
+static DECLCALLBACK(int) rtPathVarQuery_WinSystemRoot(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
+ PRTPATHMATCHCACHE pCache)
+{
+ RT_NOREF_PV(pCache);
+
+ if (iItem == 0)
+ {
+ Assert(pszBuf); Assert(cbBuf);
+ AssertReturn(g_pfnGetSystemWindowsDirectoryW, VERR_SYMBOL_NOT_FOUND);
+ RTUTF16 wszSystemRoot[MAX_PATH];
+ UINT cchSystemRoot = g_pfnGetSystemWindowsDirectoryW(wszSystemRoot, MAX_PATH);
+ if (cchSystemRoot > 0)
+ return RTUtf16ToUtf8Ex(wszSystemRoot, cchSystemRoot, &pszBuf, cbBuf, pcchValue);
+ return RTErrConvertFromWin32(GetLastError());
+ }
+ return VERR_EOF;
+}
+#endif
+
+#undef RTPATHMATCHVAR_SIMPLE
+#undef RTPATHMATCHVAR_SIMPLE_ENVVAR
+#undef RTPATHMATCHVAR_DOUBLE_ENVVAR
+
+/**
+ * Variables.
+ */
+static RTPATHMATCHVAR const g_aVariables[] =
+{
+ { RT_STR_TUPLE("Arch"), false, rtPathVarQuery_Arch, rtPathVarMatch_Arch },
+ { RT_STR_TUPLE("Bits"), false, rtPathVarQuery_Bits, rtPathVarMatch_Bits },
+ { RT_STR_TUPLE("Path"), true, rtPathVarQuery_Path, NULL },
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ { RT_STR_TUPLE("SystemDrive"), true, rtPathVarQuery_DosSystemDrive, NULL },
+#endif
+#ifdef RT_OS_WINDOWS
+ { RT_STR_TUPLE("SystemRoot"), true, rtPathVarQuery_WinSystemRoot, NULL },
+ { RT_STR_TUPLE("AppData"), true, rtPathVarQuery_WinAppData, rtPathVarMatch_WinAppData },
+ { RT_STR_TUPLE("ProgramData"), true, rtPathVarQuery_WinProgramData, rtPathVarMatch_WinProgramData },
+ { RT_STR_TUPLE("ProgramFiles"), true, rtPathVarQuery_WinProgramFiles, rtPathVarMatch_WinProgramFiles },
+ { RT_STR_TUPLE("OtherProgramFiles"), true, rtPathVarQuery_WinOtherProgramFiles, rtPathVarMatch_WinOtherProgramFiles },
+ { RT_STR_TUPLE("AllProgramFiles"), true, rtPathVarQuery_WinAllProgramFiles, rtPathVarMatch_WinAllProgramFiles },
+ { RT_STR_TUPLE("CommonProgramFiles"), true, rtPathVarQuery_WinCommonProgramFiles, rtPathVarMatch_WinCommonProgramFiles },
+ { RT_STR_TUPLE("OtherCommonProgramFiles"), true, rtPathVarQuery_WinOtherCommonProgramFiles, rtPathVarMatch_WinOtherCommonProgramFiles },
+ { RT_STR_TUPLE("AllCommonProgramFiles"), true, rtPathVarQuery_WinAllCommonProgramFiles, rtPathVarMatch_WinAllCommonProgramFiles },
+#endif
+};
+
+
+
+/**
+ * Handles a complicated set.
+ *
+ * A complicated set is either using ranges, character classes or code points
+ * outside the ASCII-7 range.
+ *
+ * @returns VINF_SUCCESS or VERR_MISMATCH. May also return UTF-8 decoding
+ * errors as well as VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED.
+ *
+ * @param ucInput The input code point to match with.
+ * @param pchSet The start of the set specification (after caret).
+ * @param cchSet The length of the set specification.
+ */
+static int rtPathMatchExecExtendedSet(RTUNICP ucInput, const char *pchSet, size_t cchSet)
+{
+ while (cchSet > 0)
+ {
+ RTUNICP ucSet;
+ int rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Check for character class, collating symbol and equvalence class.
+ */
+ if (ucSet == '[' && cchSet > 0)
+ {
+ char chNext = *pchSet;
+ if (chNext == ':')
+ {
+#define CHECK_CHAR_CLASS(a_szClassNm, a_BoolTestExpr) \
+ if ( cchSet >= sizeof(a_szClassNm) \
+ && memcmp(pchSet, a_szClassNm "]", sizeof(a_szClassNm)) == 0) \
+ { \
+ if (a_BoolTestExpr) \
+ return VINF_SUCCESS; \
+ pchSet += sizeof(a_szClassNm); \
+ cchSet -= sizeof(a_szClassNm); \
+ continue; \
+ } do { } while (0)
+
+ CHECK_CHAR_CLASS(":alpha:", RTUniCpIsAlphabetic(ucInput));
+ CHECK_CHAR_CLASS(":alnum:", RTUniCpIsAlphabetic(ucInput) || RTUniCpIsDecDigit(ucInput)); /** @todo figure what's correct here and fix uni.h */
+ CHECK_CHAR_CLASS(":blank:", ucInput == ' ' || ucInput == '\t');
+ CHECK_CHAR_CLASS(":cntrl:", ucInput < 31 || ucInput == 127);
+ CHECK_CHAR_CLASS(":digit:", RTUniCpIsDecDigit(ucInput));
+ CHECK_CHAR_CLASS(":lower:", RTUniCpIsLower(ucInput));
+ CHECK_CHAR_CLASS(":print:", RTUniCpIsAlphabetic(ucInput) || (RT_C_IS_PRINT(ucInput) && ucInput < 127)); /** @todo fixme*/
+ CHECK_CHAR_CLASS(":punct:", RT_C_IS_PRINT(ucInput) && ucInput < 127); /** @todo fixme*/
+ CHECK_CHAR_CLASS(":space:", RTUniCpIsSpace(ucInput));
+ CHECK_CHAR_CLASS(":upper:", RTUniCpIsUpper(ucInput));
+ CHECK_CHAR_CLASS(":xdigit:", RTUniCpIsHexDigit(ucInput));
+ AssertMsgFailedReturn(("Unknown or malformed char class: '%.*s'\n", cchSet + 1, pchSet - 1),
+ VERR_PATH_GLOB_UNKNOWN_CHAR_CLASS);
+#undef CHECK_CHAR_CLASS
+ }
+ /** @todo implement collating symbol and equvalence class. */
+ else if (chNext == '=' || chNext == '.')
+ AssertFailedReturn(VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
+ }
+
+ /*
+ * Check for range (leading or final dash does not constitute a range).
+ */
+ if (cchSet > 1 && *pchSet == '-')
+ {
+ pchSet++; /* skip dash */
+ cchSet--;
+
+ RTUNICP ucSet2;
+ rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet2);
+ AssertRCReturn(rc, rc);
+ Assert(ucSet < ucSet2);
+ if (ucInput >= ucSet && ucInput <= ucSet2)
+ return VINF_SUCCESS;
+ }
+ /*
+ * Single char comparison.
+ */
+ else if (ucInput == ucSet)
+ return VINF_SUCCESS;
+ }
+ return VERR_MISMATCH;
+}
+
+
+/**
+ * Variable matching fallback using the query function.
+ *
+ * This must not be inlined as it consuming a lot of stack! Which is why it's
+ * placed a couple of functions away from the recursive rtPathExecMatch.
+ *
+ * @returns VINF_SUCCESS or VERR_MISMATCH.
+ * @param pchInput The current input position.
+ * @param cchInput The amount of input left..
+ * @param idxVar The variable table index.
+ * @param fIgnoreCase Whether to ignore case when comparing.
+ * @param pcchMatched Where to return how much we actually matched up.
+ * @param pCache Pointer to the path matching cache.
+ */
+DECL_NO_INLINE(static, int) rtPathMatchExecVariableFallback(const char *pchInput, size_t cchInput, uint16_t idxVar,
+ bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
+{
+ for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
+ {
+ char szValue[RTPATH_MAX];
+ size_t cchValue;
+ int rc = g_aVariables[idxVar].pfnQuery(iItem, szValue, sizeof(szValue), &cchValue, pCache);
+ if (RT_SUCCESS(rc))
+ {
+ if (cchValue <= cchInput)
+ {
+ if ( !fIgnoreCase
+ ? memcmp(pchInput, szValue, cchValue) == 0
+ : RTStrNICmp(pchInput, szValue, cchValue) == 0)
+ {
+ *pcchMatched = cchValue;
+ return VINF_SUCCESS;
+ }
+ }
+ if (rc == VINF_EOF)
+ return VERR_MISMATCH;
+ }
+ else if (rc == VERR_EOF)
+ return VERR_MISMATCH;
+ else
+ Assert(rc == VERR_BUFFER_OVERFLOW || rc == VERR_TRY_AGAIN);
+ }
+ AssertFailed();
+ return VERR_MISMATCH;
+}
+
+
+/**
+ * Variable matching worker.
+ *
+ * @returns VINF_SUCCESS or VERR_MISMATCH.
+ * @param pchInput The current input position.
+ * @param cchInput The amount of input left..
+ * @param idxVar The variable table index.
+ * @param fIgnoreCase Whether to ignore case when comparing.
+ * @param pcchMatched Where to return how much we actually matched up.
+ * @param pCache Pointer to the path matching cache.
+ */
+static int rtPathMatchExecVariable(const char *pchInput, size_t cchInput, uint16_t idxVar,
+ bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
+{
+ Assert(idxVar < RT_ELEMENTS(g_aVariables));
+ if (g_aVariables[idxVar].pfnMatch)
+ return g_aVariables[idxVar].pfnMatch(pchInput, cchInput, fIgnoreCase, pcchMatched);
+ return rtPathMatchExecVariableFallback(pchInput, cchInput, idxVar, fIgnoreCase, pcchMatched, pCache);
+}
+
+
+/**
+ * Variable matching worker.
+ *
+ * @returns VINF_SUCCESS or VERR_MISMATCH.
+ * @param pchInput The current input position.
+ * @param cchInput The amount of input left..
+ * @param pProg The first matching program instruction.
+ * @param pCache Pointer to the path matching cache.
+ */
+static int rtPathMatchExec(const char *pchInput, size_t cchInput, PCRTPATHMATCHCORE pProg, PRTPATHMATCHCACHE pCache)
+{
+ for (;;)
+ {
+ switch (pProg->enmOpCode)
+ {
+ case RTPATHMATCHOP_RETURN_MATCH_IF_AT_END:
+ return cchInput == 0 ? VINF_SUCCESS : VERR_MISMATCH;
+
+ case RTPATHMATCHOP_RETURN_MATCH:
+ return VINF_SUCCESS;
+
+ case RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT:
+ if ( cchInput > 2
+ || cchInput < 1
+ || pchInput[0] != '.'
+ || (cchInput == 2 && pchInput[1] != '.') )
+ return VINF_SUCCESS;
+ return VERR_MISMATCH;
+
+ case RTPATHMATCHOP_STRCMP:
+ if (pProg->cch > cchInput)
+ return VERR_MISMATCH;
+ if (memcmp(pchInput, pProg->pch, pProg->cch) != 0)
+ return VERR_MISMATCH;
+ cchInput -= pProg->cch;
+ pchInput += pProg->cch;
+ break;
+
+ case RTPATHMATCHOP_STRICMP:
+ if (pProg->cch > cchInput)
+ return VERR_MISMATCH;
+ if (RTStrNICmp(pchInput, pProg->pch, pProg->cch) != 0)
+ return VERR_MISMATCH;
+ cchInput -= pProg->cch;
+ pchInput += pProg->cch;
+ break;
+
+ case RTPATHMATCHOP_SKIP_ONE_CODEPOINT:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInputIgnore;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
+ AssertRCReturn(rc, rc);
+ break;
+ }
+
+ case RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS:
+ {
+ uint16_t cCpsLeft = pProg->cch;
+ Assert(cCpsLeft > 1);
+ if (cCpsLeft > cchInput)
+ return VERR_MISMATCH;
+ while (cCpsLeft-- > 0)
+ {
+ RTUNICP ucInputIgnore;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
+ if (RT_FAILURE(rc))
+ return rc == VERR_END_OF_STRING ? VERR_MISMATCH : rc;
+ }
+ break;
+ }
+
+ case RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInput;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
+ AssertRCReturn(rc, rc);
+ if (ucInput >= 0x80)
+ return VERR_MISMATCH;
+ if (memchr(pProg->pch, (char)ucInput, pProg->cch) == NULL)
+ return VERR_MISMATCH;
+ break;
+ }
+
+ case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInput;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
+ AssertRCReturn(rc, rc);
+ if (ucInput >= 0x80)
+ break;
+ if (memchr(pProg->pch, (char)ucInput, pProg->cch) != NULL)
+ return VERR_MISMATCH;
+ break;
+ }
+
+ case RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInput;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
+ AssertRCReturn(rc, rc);
+ rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
+ if (rc == VINF_SUCCESS)
+ break;
+ return rc;
+ }
+
+ case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInput;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
+ AssertRCReturn(rc, rc);
+ rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
+ if (rc == VERR_MISMATCH)
+ break;
+ if (rc == VINF_SUCCESS)
+ rc = VERR_MISMATCH;
+ return rc;
+ }
+
+ case RTPATHMATCHOP_VARIABLE_VALUE_CMP:
+ case RTPATHMATCHOP_VARIABLE_VALUE_ICMP:
+ {
+ size_t cchMatched = 0;
+ int rc = rtPathMatchExecVariable(pchInput, cchInput, pProg->uOp2,
+ pProg->enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP, &cchMatched, pCache);
+ if (rc == VINF_SUCCESS)
+ {
+ pchInput += cchMatched;
+ cchInput -= cchMatched;
+ break;
+ }
+ return rc;
+ }
+
+ /*
+ * This is the expensive one. It always completes the program.
+ */
+ case RTPATHMATCHOP_ZERO_OR_MORE:
+ {
+ if (cchInput < pProg->cch)
+ return VERR_MISMATCH;
+ size_t cchMatched = cchInput - pProg->cch;
+ do
+ {
+ int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
+ if (RT_SUCCESS(rc))
+ return rc;
+ } while (cchMatched-- > 0);
+ return VERR_MISMATCH;
+ }
+
+ /*
+ * Variant of the above that doesn't match '.' and '..' entries.
+ */
+ case RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT:
+ {
+ if (cchInput < pProg->cch)
+ return VERR_MISMATCH;
+ if ( cchInput <= 2
+ && cchInput > 0
+ && pchInput[0] == '.'
+ && (cchInput == 1 || pchInput[1] == '.') )
+ return VERR_MISMATCH;
+ size_t cchMatched = cchInput - pProg->cch;
+ do
+ {
+ int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
+ if (RT_SUCCESS(rc))
+ return rc;
+ } while (cchMatched-- > 0);
+ return VERR_MISMATCH;
+ }
+
+ default:
+ AssertMsgFailedReturn(("enmOpCode=%d\n", pProg->enmOpCode), VERR_INTERNAL_ERROR_3);
+ }
+
+ pProg++;
+ }
+}
+
+
+
+
+/**
+ * Compiles a path matching program.
+ *
+ * @returns IPRT status code.
+ * @param pchPattern The pattern to compile.
+ * @param cchPattern The length of the pattern.
+ * @param fIgnoreCase Whether to ignore case or not when doing the
+ * actual matching later on.
+ * @param pAllocator Pointer to the instruction allocator & result
+ * array. The compiled "program" starts at
+ * PRTPATHMATCHALLOC::paInstructions[PRTPATHMATCHALLOC::iNext]
+ * (input iNext value).
+ *
+ * @todo Expose this matching code and also use it for RTDirOpenFiltered
+ */
+static int rtPathMatchCompile(const char *pchPattern, size_t cchPattern, bool fIgnoreCase, PRTPATHMATCHALLOC pAllocator)
+{
+ /** @todo PORTME: big endian. */
+ static const uint8_t s_bmMetaChars[256/8] =
+ {
+ 0x00, 0x00, 0x00, 0x00, /* 0 thru 31 */
+ 0x10, 0x04, 0x00, 0x80, /* 32 thru 63 */
+ 0x00, 0x00, 0x00, 0x08, /* 64 thru 95 */
+ 0x00, 0x00, 0x00, 0x00, /* 96 thru 127 */
+ /* UTF-8 multibyte: */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ Assert(ASMBitTest(s_bmMetaChars, '$')); AssertCompile('$' == 0x24 /*36*/);
+ Assert(ASMBitTest(s_bmMetaChars, '*')); AssertCompile('*' == 0x2a /*42*/);
+ Assert(ASMBitTest(s_bmMetaChars, '?')); AssertCompile('?' == 0x3f /*63*/);
+ Assert(ASMBitTest(s_bmMetaChars, '[')); AssertCompile('[' == 0x5b /*91*/);
+
+ /*
+ * For checking for the first instruction.
+ */
+ uint16_t const iFirst = pAllocator->iNext;
+
+ /*
+ * This is for tracking zero-or-more instructions and for calculating
+ * the minimum amount of input required for it to be considered.
+ */
+ uint16_t aiZeroOrMore[RTPATHMATCH_MAX_ZERO_OR_MORE];
+ uint8_t cZeroOrMore = 0;
+ size_t offInput = 0;
+
+ /*
+ * Loop thru the pattern and translate it into string matching instructions.
+ */
+ for (;;)
+ {
+ /*
+ * Allocate the next instruction.
+ */
+ if (pAllocator->iNext >= pAllocator->cAllocated)
+ {
+ uint32_t cNew = pAllocator->cAllocated ? pAllocator->cAllocated * 2 : 2;
+ void *pvNew = RTMemRealloc(pAllocator->paInstructions, cNew * sizeof(pAllocator->paInstructions[0]));
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pAllocator->paInstructions = (PRTPATHMATCHCORE)pvNew;
+ pAllocator->cAllocated = cNew;
+ }
+ PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[pAllocator->iNext++];
+ pInstr->pch = pchPattern;
+ pInstr->cch = 0;
+ pInstr->uOp2 = 0;
+
+ /*
+ * Special case: End of pattern.
+ */
+ if (!cchPattern)
+ {
+ pInstr->enmOpCode = RTPATHMATCHOP_RETURN_MATCH_IF_AT_END;
+ break;
+ }
+
+ /*
+ * Parse the next bit of the pattern.
+ */
+ char ch = *pchPattern;
+ if (ASMBitTest(s_bmMetaChars, (uint8_t)ch))
+ {
+ /*
+ * Zero or more characters wildcard.
+ */
+ if (ch == '*')
+ {
+ /* Skip extra asterisks. */
+ do
+ {
+ cchPattern--;
+ pchPattern++;
+ } while (cchPattern > 0 && *pchPattern == '*');
+
+ /* There is a special optimization for trailing '*'. */
+ pInstr->cch = 1;
+ if (cchPattern == 0)
+ {
+ pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
+ ? RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_RETURN_MATCH;
+ break;
+ }
+
+ pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
+ ? RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_ZERO_OR_MORE;
+ pInstr->uOp2 = (uint16_t)offInput;
+ AssertReturn(cZeroOrMore < RT_ELEMENTS(aiZeroOrMore), VERR_OUT_OF_RANGE);
+ aiZeroOrMore[cZeroOrMore] = (uint16_t)(pInstr - pAllocator->paInstructions);
+
+ /* cchInput unchanged, zero-or-more matches. */
+ continue;
+ }
+
+ /*
+ * Single character wildcard.
+ */
+ if (ch == '?')
+ {
+ /* Count them if more. */
+ uint16_t cchQms = 1;
+ while (cchQms < cchPattern && pchPattern[cchQms] == '?')
+ cchQms++;
+
+ pInstr->cch = cchQms;
+ pInstr->enmOpCode = cchQms == 1 ? RTPATHMATCHOP_SKIP_ONE_CODEPOINT : RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS;
+
+ cchPattern -= cchQms;
+ pchPattern += cchQms;
+ offInput += cchQms;
+ continue;
+ }
+
+ /*
+ * Character in set.
+ *
+ * Note that we skip the first char in the set as that is the only place
+ * ']' can be placed if one desires to explicitly include it in the set.
+ * To make life a bit more interesting, [:class:] is allowed inside the
+ * set, so we have to do the counting game to find the end.
+ */
+ if (ch == '[')
+ {
+ if ( cchPattern > 2
+ && (const char *)memchr(pchPattern + 2, ']', cchPattern) != NULL)
+ {
+
+ /* Check for not-in. */
+ bool fInverted = false;
+ size_t offStart = 1;
+ if (pchPattern[offStart] == '^')
+ {
+ fInverted = true;
+ offStart++;
+ }
+
+ /* Special case for ']' as the first char, it doesn't indicate closing then. */
+ size_t off = offStart;
+ if (pchPattern[off] == ']')
+ off++;
+
+ bool fExtended = false;
+ while (off < cchPattern)
+ {
+ ch = pchPattern[off++];
+ if (ch == '[')
+ {
+ if (off < cchPattern)
+ {
+ char chOpen = pchPattern[off];
+ if ( chOpen == ':'
+ || chOpen == '='
+ || chOpen == '.')
+ {
+ off++;
+ const char *pchFound = (const char *)memchr(&pchPattern[off], ']', cchPattern - off);
+ if ( pchFound
+ && pchFound[-1] == chOpen)
+ {
+ fExtended = true;
+ off = pchFound - pchPattern + 1;
+ }
+ else
+ AssertFailed();
+ }
+ }
+ }
+ /* Check for closing. */
+ else if (ch == ']')
+ break;
+ /* Check for range expression, promote to extended if this happens. */
+ else if ( ch == '-'
+ && off != offStart + 1
+ && off < cchPattern
+ && pchPattern[off] != ']')
+ fExtended = true;
+ /* UTF-8 multibyte chars forces us to use the extended version too. */
+ else if ((uint8_t)ch >= 0x80)
+ fExtended = true;
+ }
+
+ if (ch == ']')
+ {
+ pInstr->pch = &pchPattern[offStart];
+ pInstr->cch = (uint16_t)(off - offStart - 1);
+ if (!fExtended)
+ pInstr->enmOpCode = !fInverted
+ ? RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7 : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7;
+ else
+ pInstr->enmOpCode = !fInverted
+ ? RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED
+ : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED;
+ pchPattern += off;
+ cchPattern -= off;
+ offInput += 1;
+ continue;
+ }
+
+ /* else: invalid, treat it as */
+ AssertFailed();
+ }
+ }
+ /*
+ * Variable matching.
+ */
+ else if (ch == '$')
+ {
+ const char *pchFound;
+ if ( cchPattern > 3
+ && pchPattern[1] == '{'
+ && (pchFound = (const char *)memchr(pchPattern + 2, '}', cchPattern)) != NULL
+ && pchFound != &pchPattern[2])
+ {
+ /* skip to the variable name. */
+ pchPattern += 2;
+ cchPattern -= 2;
+ size_t cchVarNm = pchFound - pchPattern;
+
+ /* Look it up. */
+ uint32_t iVar;
+ for (iVar = 0; iVar < RT_ELEMENTS(g_aVariables); iVar++)
+ if ( g_aVariables[iVar].cchName == cchVarNm
+ && memcmp(g_aVariables[iVar].pszName, pchPattern, cchVarNm) == 0)
+ break;
+ if (iVar < RT_ELEMENTS(g_aVariables))
+ {
+ pInstr->uOp2 = (uint16_t)iVar;
+ pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_VARIABLE_VALUE_CMP : RTPATHMATCHOP_VARIABLE_VALUE_ICMP;
+ pInstr->pch = pchPattern; /* not necessary */
+ pInstr->cch = (uint16_t)cchPattern; /* ditto */
+ pchPattern += cchVarNm + 1;
+ cchPattern -= cchVarNm + 1;
+ AssertMsgReturn(!g_aVariables[iVar].fFirstOnly || iFirst + 1U == pAllocator->iNext,
+ ("Glob variable '%s' should be first\n", g_aVariables[iVar].pszName),
+ VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
+ /* cchInput unchanged, value can be empty. */
+ continue;
+ }
+ AssertMsgFailedReturn(("Unknown path matching variable '%.*s'\n", cchVarNm, pchPattern),
+ VERR_PATH_MATCH_UNKNOWN_VARIABLE);
+ }
+ }
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
+ }
+
+ /*
+ * Plain text. Look for the next meta char.
+ */
+ uint32_t cchPlain = 1;
+ while (cchPlain < cchPattern)
+ {
+ ch = pchPattern[cchPlain];
+ if (!ASMBitTest(s_bmMetaChars, (uint8_t)ch))
+ { /* probable */ }
+ else if ( ch == '?'
+ || ch == '*')
+ break;
+ else if (ch == '$')
+ {
+ const char *pchFound;
+ if ( cchPattern > cchPlain + 3
+ && pchPattern[cchPlain + 1] == '{'
+ && (pchFound = (const char *)memchr(&pchPattern[cchPlain + 2], '}', cchPattern - cchPlain - 2)) != NULL
+ && pchFound != &pchPattern[cchPlain + 2])
+ break;
+ }
+ else if (ch == '[')
+ {
+ /* We don't put a lot of effort into getting this 100% right here,
+ no point it complicating things for malformed expressions. */
+ if ( cchPattern > cchPlain + 2
+ && memchr(&pchPattern[cchPlain + 2], ']', cchPattern - cchPlain - 1) != NULL)
+ break;
+ }
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
+ cchPlain++;
+ }
+ pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_STRCMP : RTPATHMATCHOP_STRICMP;
+ pInstr->cch = cchPlain;
+ Assert(pInstr->pch == pchPattern);
+ Assert(pInstr->uOp2 == 0);
+ pchPattern += cchPlain;
+ cchPattern -= cchPlain;
+ offInput += cchPlain;
+ }
+
+ /*
+ * Optimize zero-or-more matching.
+ */
+ while (cZeroOrMore-- > 0)
+ {
+ PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[aiZeroOrMore[cZeroOrMore]];
+ pInstr->uOp2 = (uint16_t)(offInput - pInstr->uOp2);
+ }
+
+ /** @todo It's possible to use offInput to inject a instruction for checking
+ * minimum input length at the start of the program. Not sure it's
+ * worth it though, unless it's long a complicated expression... */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses the glob pattern.
+ *
+ * This compiles filename matching programs for each component and determins the
+ * optimal search strategy for them.
+ *
+ * @returns IPRT status code.
+ * @param pGlob The glob instance data.
+ * @param pszPattern The pattern to parse.
+ * @param pParsed The RTPathParse output for the pattern.
+ * @param fFlags The glob flags (same as pGlob->fFlags).
+ */
+static int rtPathGlobParse(PRTPATHGLOB pGlob, const char *pszPattern, PRTPATHPARSED pParsed, uint32_t fFlags)
+{
+ AssertReturn(pParsed->cComps > 0, VERR_INVALID_PARAMETER); /* shouldn't happen */
+ uint32_t iComp = 0;
+
+ /*
+ * If we've got a rootspec, mark it as plain. On platforms with
+ * drive letter and/or UNC we don't allow wildcards or such in
+ * the drive letter spec or UNC server name. (At least not yet.)
+ */
+ if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
+ {
+ AssertReturn(pParsed->aComps[0].cch < sizeof(pGlob->szPath) - 1, VERR_FILENAME_TOO_LONG);
+ memcpy(pGlob->szPath, &pszPattern[pParsed->aComps[0].off], pParsed->aComps[0].cch);
+ pGlob->offFirstPath = pParsed->aComps[0].cch;
+ pGlob->iFirstComp = iComp = 1;
+ }
+ else
+ {
+ const char * const pszComp = &pszPattern[pParsed->aComps[0].off];
+
+ /*
+ * The tilde is only applicable to the first component, expand it
+ * immediately.
+ */
+ if ( *pszComp == '~'
+ && !(fFlags & RTPATHGLOB_F_NO_TILDE))
+ {
+ if (pParsed->aComps[0].cch == 1)
+ {
+ int rc = RTPathUserHome(pGlob->szPath, sizeof(pGlob->szPath) - 1);
+ AssertRCReturn(rc, rc);
+ }
+ else
+ AssertMsgFailedReturn(("'%.*s' is not supported yet\n", pszComp, pParsed->aComps[0].cch),
+ VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
+ pGlob->offFirstPath = (uint32_t)RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
+ pGlob->iFirstComp = iComp = 1;
+ }
+ }
+
+ /*
+ * Process the other components.
+ */
+ bool fStarStar = false;
+ for (; iComp < pParsed->cComps; iComp++)
+ {
+ const char *pszComp = &pszPattern[pParsed->aComps[iComp].off];
+ uint16_t cchComp = pParsed->aComps[iComp].cch;
+ Assert(pGlob->aComps[iComp].fNormal == false);
+
+ pGlob->aComps[iComp].fDir = iComp + 1 < pParsed->cComps || (fFlags & RTPATHGLOB_F_ONLY_DIRS);
+ if ( cchComp != 2
+ || pszComp[0] != '*'
+ || pszComp[1] != '*'
+ || (fFlags & RTPATHGLOB_F_NO_STARSTAR) )
+ {
+ /* Compile the pattern. */
+ uint16_t const iMatchProg = pGlob->MatchInstrAlloc.iNext;
+ pGlob->aComps[iComp].iMatchProg = iMatchProg;
+ int rc = rtPathMatchCompile(pszComp, cchComp, RT_BOOL(fFlags & RTPATHGLOB_F_IGNORE_CASE),
+ &pGlob->MatchInstrAlloc);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Check for plain text as well as full variable matching (not applicable after '**'). */
+ uint16_t const cInstructions = pGlob->MatchInstrAlloc.iNext - iMatchProg;
+ if ( cInstructions == 2
+ && !fStarStar
+ && pGlob->MatchInstrAlloc.paInstructions[iMatchProg + 1].enmOpCode == RTPATHMATCHOP_RETURN_MATCH_IF_AT_END)
+ {
+ if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRCMP
+ || pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRICMP)
+ pGlob->aComps[iComp].fPlain = true;
+ else if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_CMP
+ || pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP)
+ {
+ pGlob->aComps[iComp].fExpVariable = true;
+ AssertMsgReturn( iComp == 0
+ || !g_aVariables[pGlob->MatchInstrAlloc.paInstructions[iMatchProg].uOp2].fFirstOnly,
+ ("Glob variable '%.*s' can only be used as the path component.\n", cchComp, pszComp),
+ VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
+ }
+ else
+ pGlob->aComps[iComp].fNormal = true;
+ }
+ else
+ pGlob->aComps[iComp].fNormal = true;
+ }
+ else
+ {
+ /* Recursive "**" matching. */
+ pGlob->aComps[iComp].fNormal = false;
+ pGlob->aComps[iComp].fStarStar = true;
+ AssertReturn(!fStarStar, VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED); /** @todo implement multiple '**' sequences in a pattern. */
+ fStarStar = true;
+ }
+ }
+ pGlob->aComps[pParsed->cComps - 1].fFinal = true;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * This is for skipping overly long directories entries.
+ *
+ * Since our directory entry buffer can hold filenames of RTPATH_MAX bytes, we
+ * can safely skip filenames that are longer. There are very few file systems
+ * that can actually store filenames longer than 255 bytes at time of coding
+ * (2015-09), and extremely few which can exceed 4096 (RTPATH_MAX) bytes.
+ *
+ * @returns IPRT status code.
+ * @param hDir The directory handle.
+ * @param cbNeeded The required entry size.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobSkipDirEntry(RTDIR hDir, size_t cbNeeded)
+{
+ int rc = VERR_BUFFER_OVERFLOW;
+ cbNeeded = RT_ALIGN_Z(cbNeeded, 16);
+ PRTDIRENTRY pDirEntry = (PRTDIRENTRY)RTMemTmpAlloc(cbNeeded);
+ if (pDirEntry)
+ {
+ rc = RTDirRead(hDir, pDirEntry, &cbNeeded);
+ RTMemTmpFree(pDirEntry);
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a result.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN if we can stop searching.
+ *
+ * @param pGlob The glob instance data.
+ * @param cchPath The number of bytes to add from pGlob->szPath.
+ * @param uType The RTDIRENTRYTYPE value.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobAddResult(PRTPATHGLOB pGlob, size_t cchPath, uint8_t uType)
+{
+ if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
+ {
+ PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + 1]));
+ if (pEntry)
+ {
+ pEntry->uType = uType;
+ pEntry->cchPath = (uint16_t)cchPath;
+ memcpy(pEntry->szPath, pGlob->szPath, cchPath);
+ pEntry->szPath[cchPath] = '\0';
+
+ pEntry->pNext = NULL;
+ *pGlob->ppNext = pEntry;
+ pGlob->ppNext = &pEntry->pNext;
+ pGlob->cResults++;
+
+ if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
+ return VINF_SUCCESS;
+ return VINF_CALLBACK_RETURN;
+ }
+ return VERR_NO_MEMORY;
+ }
+ return VERR_TOO_MUCH_DATA;
+}
+
+
+/**
+ * Adds a result, constructing the path from two string.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN if we can stop searching.
+ *
+ * @param pGlob The glob instance data.
+ * @param cchPath The number of bytes to add from pGlob->szPath.
+ * @param pchName The string (usual filename) to append to the szPath.
+ * @param cchName The length of the string to append.
+ * @param uType The RTDIRENTRYTYPE value.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobAddResult2(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
+ uint8_t uType)
+{
+ if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
+ {
+ PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
+ if (pEntry)
+ {
+ pEntry->uType = uType;
+ pEntry->cchPath = (uint16_t)(cchPath + cchName);
+ memcpy(pEntry->szPath, pGlob->szPath, cchPath);
+ memcpy(&pEntry->szPath[cchPath], pchName, cchName);
+ pEntry->szPath[cchPath + cchName] = '\0';
+
+ pEntry->pNext = NULL;
+ *pGlob->ppNext = pEntry;
+ pGlob->ppNext = &pEntry->pNext;
+ pGlob->cResults++;
+
+ if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
+ return VINF_SUCCESS;
+ return VINF_CALLBACK_RETURN;
+ }
+ return VERR_NO_MEMORY;
+ }
+ return VERR_TOO_MUCH_DATA;
+}
+
+
+/**
+ * Prepares a result, constructing the path from two string.
+ *
+ * The caller must call either rtPathGlobCommitResult or
+ * rtPathGlobRollbackResult to complete the operation.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN if we can stop searching.
+ *
+ * @param pGlob The glob instance data.
+ * @param cchPath The number of bytes to add from pGlob->szPath.
+ * @param pchName The string (usual filename) to append to the szPath.
+ * @param cchName The length of the string to append.
+ * @param uType The RTDIRENTRYTYPE value.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobAlmostAddResult(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
+ uint8_t uType)
+{
+ if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
+ {
+ PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
+ if (pEntry)
+ {
+ pEntry->uType = uType;
+ pEntry->cchPath = (uint16_t)(cchPath + cchName);
+ memcpy(pEntry->szPath, pGlob->szPath, cchPath);
+ memcpy(&pEntry->szPath[cchPath], pchName, cchName);
+ pEntry->szPath[cchPath + cchName] = '\0';
+
+ pEntry->pNext = NULL;
+ *pGlob->ppNext = pEntry;
+ /* Note! We don't update ppNext here, that is done in rtPathGlobCommitResult. */
+
+ if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
+ return VINF_SUCCESS;
+ return VINF_CALLBACK_RETURN;
+ }
+ return VERR_NO_MEMORY;
+ }
+ return VERR_TOO_MUCH_DATA;
+}
+
+
+/**
+ * Commits a pending result from rtPathGlobAlmostAddResult.
+ *
+ * @param pGlob The glob instance data.
+ * @param uType The RTDIRENTRYTYPE value.
+ */
+static void rtPathGlobCommitResult(PRTPATHGLOB pGlob, uint8_t uType)
+{
+ PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
+ AssertPtr(pEntry);
+ pEntry->uType = uType;
+ pGlob->ppNext = &pEntry->pNext;
+ pGlob->cResults++;
+}
+
+
+/**
+ * Rolls back a pending result from rtPathGlobAlmostAddResult.
+ *
+ * @param pGlob The glob instance data.
+ */
+static void rtPathGlobRollbackResult(PRTPATHGLOB pGlob)
+{
+ PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
+ AssertPtr(pEntry);
+ RTMemFree(pEntry);
+ *pGlob->ppNext = NULL;
+}
+
+
+
+/**
+ * Whether to call rtPathGlobExecRecursiveVarExp for the next component.
+ *
+ * @returns true / false.
+ * @param pGlob The glob instance data.
+ * @param offPath The next path offset/length.
+ * @param iComp The next component.
+ */
+DECLINLINE(bool) rtPathGlobExecIsExpVar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ return pGlob->aComps[iComp].fExpVariable
+ && ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
+ || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
+}
+
+/**
+ * Whether to call rtPathGlobExecRecursivePlainText for the next component.
+ *
+ * @returns true / false.
+ * @param pGlob The glob instance data.
+ * @param offPath The next path offset/length.
+ * @param iComp The next component.
+ */
+DECLINLINE(bool) rtPathGlobExecIsPlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ return pGlob->aComps[iComp].fPlain
+ && ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
+ || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
+}
+
+
+/**
+ * Helper for rtPathGlobExecRecursiveVarExp and rtPathGlobExecRecursivePlainText
+ * that compares a file mode mask with dir/no-dir wishes of the caller.
+ *
+ * @returns true if match, false if not.
+ * @param pGlob The glob instance data.
+ * @param fMode The file mode (only the type is used).
+ */
+DECLINLINE(bool) rtPathGlobExecIsMatchFinalWithFileMode(PRTPATHGLOB pGlob, RTFMODE fMode)
+{
+ if (!(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)))
+ return true;
+ return RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS) == RTFS_IS_DIRECTORY(fMode);
+}
+
+
+/**
+ * Recursive globbing - star-star mode.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
+ *
+ * @param pGlob The glob instance data.
+ * @param offPath The current path offset/length.
+ * @param iStarStarComp The star-star component index.
+ * @param offStarStarPath The offset of the star-star component in the
+ * pattern path.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp,
+ size_t offStarStarPath)
+{
+ /** @todo implement multi subdir matching. */
+ RT_NOREF_PV(pGlob);
+ RT_NOREF_PV(offPath);
+ RT_NOREF_PV(iStarStarComp);
+ RT_NOREF_PV(offStarStarPath);
+ return VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED;
+}
+
+
+
+/**
+ * Recursive globbing - variable expansion optimization.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
+ *
+ * @param pGlob The glob instance data.
+ * @param offPath The current path offset/length.
+ * @param iComp The current component.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ Assert(iComp < pGlob->pParsed->cComps);
+ Assert(pGlob->szPath[offPath] == '\0');
+ Assert(pGlob->aComps[iComp].fExpVariable);
+ Assert(!pGlob->aComps[iComp].fPlain);
+ Assert(!pGlob->aComps[iComp].fStarStar);
+ Assert(rtPathGlobExecIsExpVar(pGlob, offPath, iComp));
+
+ /*
+ * Fish the variable index out of the first matching instruction.
+ */
+ Assert( pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
+ == RTPATHMATCHOP_VARIABLE_VALUE_CMP
+ || pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
+ == RTPATHMATCHOP_VARIABLE_VALUE_ICMP);
+ uint16_t const iVar = pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].uOp2;
+
+ /*
+ * Enumerate all the variable, giving them the plain text treatment.
+ */
+ for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
+ {
+ size_t cch;
+ int rcVar = g_aVariables[iVar].pfnQuery(iItem, &pGlob->szPath[offPath], sizeof(pGlob->szPath) - offPath, &cch,
+ &pGlob->MatchCache);
+ if (RT_SUCCESS(rcVar))
+ {
+ Assert(pGlob->szPath[offPath + cch] == '\0');
+
+ int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ if (pGlob->aComps[iComp].fFinal)
+ {
+ if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
+ {
+ rc = rtPathGlobAddResult(pGlob, cch,
+ (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ }
+ else if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
+ {
+ Assert(pGlob->aComps[iComp].fDir);
+ cch = RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
+ if (cch > 0)
+ {
+ if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
+ rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
+ else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
+ rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
+ else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
+ rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
+ else
+ rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ else
+ pGlob->cPathOverflows++;
+ }
+ }
+ /* else: file doesn't exist or something else is wrong, ignore this. */
+ if (rcVar == VINF_EOF)
+ return VINF_SUCCESS;
+ }
+ else if (rcVar == VERR_EOF)
+ return VINF_SUCCESS;
+ else if (rcVar != VERR_TRY_AGAIN)
+ {
+ Assert(rcVar == VERR_BUFFER_OVERFLOW);
+ pGlob->cPathOverflows++;
+ }
+ }
+ AssertFailedReturn(VINF_SUCCESS); /* Too many items returned, probably buggy query method. */
+}
+
+
+/**
+ * Recursive globbing - plain text optimization.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
+ *
+ * @param pGlob The glob instance data.
+ * @param offPath The current path offset/length.
+ * @param iComp The current component.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ /*
+ * Instead of recursing, we loop thru adjacent plain text components.
+ */
+ for (;;)
+ {
+ /*
+ * Preconditions.
+ */
+ Assert(iComp < pGlob->pParsed->cComps);
+ Assert(pGlob->szPath[offPath] == '\0');
+ Assert(pGlob->aComps[iComp].fPlain);
+ Assert(!pGlob->aComps[iComp].fExpVariable);
+ Assert(!pGlob->aComps[iComp].fStarStar);
+ Assert(rtPathGlobExecIsPlainText(pGlob, offPath, iComp));
+ Assert(pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
+ == RTPATHMATCHOP_STRCMP
+ || pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
+ == RTPATHMATCHOP_STRICMP);
+
+ /*
+ * Add the plain text component to the path.
+ */
+ size_t const cch = pGlob->pParsed->aComps[iComp].cch;
+ if (cch + pGlob->aComps[iComp].fDir < sizeof(pGlob->szPath) - offPath)
+ {
+ memcpy(&pGlob->szPath[offPath], &pGlob->pszPattern[pGlob->pParsed->aComps[iComp].off], cch);
+ offPath += cch;
+ pGlob->szPath[offPath] = '\0';
+
+ /*
+ * Check if it exists.
+ */
+ int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ if (pGlob->aComps[iComp].fFinal)
+ {
+ if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
+ return rtPathGlobAddResult(pGlob, offPath,
+ (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
+ break;
+ }
+
+ if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
+ {
+ Assert(pGlob->aComps[iComp].fDir);
+ pGlob->szPath[offPath++] = RTPATH_SLASH;
+ pGlob->szPath[offPath] = '\0';
+
+ iComp++;
+ if (rtPathGlobExecIsExpVar(pGlob, offPath, iComp))
+ return rtPathGlobExecRecursiveVarExp(pGlob, offPath, iComp);
+ if (!rtPathGlobExecIsPlainText(pGlob, offPath, iComp))
+ return rtPathGlobExecRecursiveGeneric(pGlob, offPath, iComp);
+ if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
+ return rtPathGlobExecRecursiveStarStar(pGlob, offPath, iComp, offPath);
+
+ /* Continue with the next plain text component. */
+ continue;
+ }
+ }
+ /* else: file doesn't exist or something else is wrong, ignore this. */
+ }
+ else
+ pGlob->cPathOverflows++;
+ break;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Recursive globbing - generic.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
+ *
+ * @param pGlob The glob instance data.
+ * @param offPath The current path offset/length.
+ * @param iComp The current component.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ /*
+ * Enumerate entire directory and match each entry.
+ */
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, offPath ? pGlob->szPath : ".");
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ size_t cch = sizeof(pGlob->u);
+ rc = RTDirRead(hDir, &pGlob->u.DirEntry, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ if (pGlob->aComps[iComp].fFinal)
+ {
+ /*
+ * Final component: Check if it matches the current pattern.
+ */
+ if ( !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS))
+ || RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
+ == (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
+ || pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
+ {
+ rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
+ &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
+ &pGlob->MatchCache);
+ if (RT_SUCCESS(rc))
+ {
+ /* Construct the result. */
+ if ( pGlob->u.DirEntry.enmType != RTDIRENTRYTYPE_UNKNOWN
+ || !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) )
+ rc = rtPathGlobAddResult2(pGlob, offPath, pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
+ (uint8_t)pGlob->u.DirEntry.enmType);
+ else
+ {
+ rc = rtPathGlobAlmostAddResult(pGlob, offPath,
+ pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
+ (uint8_t)RTDIRENTRYTYPE_UNKNOWN);
+ if (RT_SUCCESS(rc))
+ {
+ RTDirQueryUnknownType((*pGlob->ppNext)->szPath, false /*fFollowSymlinks*/,
+ &pGlob->u.DirEntry.enmType);
+ if ( RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
+ == (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY))
+ rtPathGlobCommitResult(pGlob, (uint8_t)pGlob->u.DirEntry.enmType);
+ else
+ rtPathGlobRollbackResult(pGlob);
+ }
+ }
+ if (rc != VINF_SUCCESS)
+ break;
+ }
+ else
+ {
+ AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+ /*
+ * Intermediate component: Directories only.
+ */
+ else if ( pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY
+ || pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
+ {
+ rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
+ &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
+ &pGlob->MatchCache);
+ if (RT_SUCCESS(rc))
+ {
+ /* Recurse down into the alleged directory. */
+ cch = offPath + pGlob->u.DirEntry.cbName;
+ if (cch + 1 < sizeof(pGlob->szPath))
+ {
+ memcpy(&pGlob->szPath[offPath], pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName);
+ pGlob->szPath[cch++] = RTPATH_SLASH;
+ pGlob->szPath[cch] = '\0';
+
+ if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
+ rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
+ else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
+ rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
+ else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
+ rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
+ else
+ rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ else
+ pGlob->cPathOverflows++;
+ }
+ else
+ {
+ AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+ /*
+ * RTDirRead failure.
+ */
+ else
+ {
+ /* The end? */
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ /* Try skip the entry if we end up with an overflow (szPath can't hold it either then). */
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ pGlob->cPathOverflows++;
+ rc = rtPathGlobSkipDirEntry(hDir, cch);
+ if (RT_SUCCESS(rc))
+ continue;
+ }
+ /* else: Any other error is unexpected and should be reported. */
+ break;
+ }
+ }
+
+ RTDirClose(hDir);
+ }
+ /* Directory doesn't exist or something else is wrong, ignore this. */
+ else
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+/**
+ * Executes a glob search.
+ *
+ * @returns IPRT status code.
+ * @param pGlob The glob instance data.
+ */
+static int rtPathGlobExec(PRTPATHGLOB pGlob)
+{
+ Assert(pGlob->offFirstPath < sizeof(pGlob->szPath));
+ Assert(pGlob->szPath[pGlob->offFirstPath] == '\0');
+
+ int rc;
+ if (RT_LIKELY(pGlob->iFirstComp < pGlob->pParsed->cComps))
+ {
+ /*
+ * Call the appropriate function.
+ */
+ if (rtPathGlobExecIsExpVar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
+ rc = rtPathGlobExecRecursiveVarExp(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
+ else if (rtPathGlobExecIsPlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
+ rc = rtPathGlobExecRecursivePlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
+ else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
+ rc = rtPathGlobExecRecursiveStarStar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp, pGlob->offFirstPath);
+ else
+ rc = rtPathGlobExecRecursiveGeneric(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
+ }
+ else
+ {
+ /*
+ * Special case where we only have a root component or tilde expansion.
+ */
+ Assert(pGlob->offFirstPath > 0);
+ rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if ( RT_SUCCESS(rc)
+ && rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
+ rc = rtPathGlobAddResult(pGlob, pGlob->offFirstPath,
+ (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK) >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
+ else
+ rc = VINF_SUCCESS;
+ }
+
+ /*
+ * Adjust the status code. Check for results, hide RTPATHGLOB_F_FIRST_ONLY
+ * status code, and add warning if necessary.
+ */
+ if (pGlob->cResults > 0)
+ {
+ if (rc == VINF_CALLBACK_RETURN)
+ rc = VINF_SUCCESS;
+ if (rc == VINF_SUCCESS)
+ {
+ if (pGlob->cPathOverflows > 0)
+ rc = VINF_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+
+ return rc;
+}
+
+
+RTDECL(int) RTPathGlob(const char *pszPattern, uint32_t fFlags, PPCRTPATHGLOBENTRY ppHead, uint32_t *pcResults)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(ppHead, VERR_INVALID_POINTER);
+ *ppHead = NULL;
+ if (pcResults)
+ {
+ AssertPtrReturn(pcResults, VERR_INVALID_POINTER);
+ *pcResults = 0;
+ }
+ AssertPtrReturn(pszPattern, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTPATHGLOB_F_MASK), VERR_INVALID_FLAGS);
+ AssertReturn((fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) != (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS),
+ VERR_INVALID_FLAGS);
+
+ /*
+ * Parse the path.
+ */
+ size_t cbParsed = RT_UOFFSETOF(RTPATHPARSED, aComps[1]); /** @todo 16 after testing */
+ PRTPATHPARSED pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
+ AssertReturn(pParsed, VERR_NO_MEMORY);
+ int rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pParsed->cComps + 1]);
+ RTMemTmpFree(pParsed);
+ pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
+ AssertReturn(pParsed, VERR_NO_MEMORY);
+
+ rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check dir slash vs. only/not dir flag.
+ */
+ if ( !(fFlags & RTPATHGLOB_F_NO_DIRS)
+ || ( !(pParsed->fProps & RTPATH_PROP_DIR_SLASH)
+ && ( !(pParsed->fProps & (RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_UNC))
+ || pParsed->cComps > 1) ) )
+ {
+ if (pParsed->fProps & RTPATH_PROP_DIR_SLASH)
+ fFlags |= RTPATHGLOB_F_ONLY_DIRS;
+
+ /*
+ * Allocate and initialize the glob state data structure.
+ */
+ size_t cbGlob = RT_UOFFSETOF_DYN(RTPATHGLOB, aComps[pParsed->cComps + 1]);
+ PRTPATHGLOB pGlob = (PRTPATHGLOB)RTMemTmpAllocZ(cbGlob);
+ if (pGlob)
+ {
+ pGlob->pszPattern = pszPattern;
+ pGlob->fFlags = fFlags;
+ pGlob->pParsed = pParsed;
+ pGlob->ppNext = &pGlob->pHead;
+ rc = rtPathGlobParse(pGlob, pszPattern, pParsed, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Execute the search.
+ */
+ rc = rtPathGlobExec(pGlob);
+ if (RT_SUCCESS(rc))
+ {
+ *ppHead = pGlob->pHead;
+ if (pcResults)
+ *pcResults = pGlob->cResults;
+ }
+ else
+ RTPathGlobFree(pGlob->pHead);
+ }
+
+ RTMemTmpFree(pGlob->MatchInstrAlloc.paInstructions);
+ RTMemTmpFree(pGlob);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ RTMemTmpFree(pParsed);
+ return rc;
+
+
+}
+
+
+RTDECL(void) RTPathGlobFree(PCRTPATHGLOBENTRY pHead)
+{
+ PRTPATHGLOBENTRY pCur = (PRTPATHGLOBENTRY)pHead;
+ while (pCur)
+ {
+ PRTPATHGLOBENTRY pNext = pCur->pNext;
+ pCur->pNext = NULL;
+ RTMemFree(pCur);
+ pCur = pNext;
+ }
+}
+