summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/path
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/common/path')
-rw-r--r--src/VBox/Runtime/common/path/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/path/RTPathAbsDup.cpp49
-rw-r--r--src/VBox/Runtime/common/path/RTPathAbsEx.cpp699
-rw-r--r--src/VBox/Runtime/common/path/RTPathAbsExDup.cpp82
-rw-r--r--src/VBox/Runtime/common/path/RTPathAppend.cpp51
-rw-r--r--src/VBox/Runtime/common/path/RTPathAppendEx.cpp97
-rw-r--r--src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h166
-rw-r--r--src/VBox/Runtime/common/path/RTPathCalcRelative.cpp248
-rw-r--r--src/VBox/Runtime/common/path/RTPathChangeToDosSlashes.cpp74
-rw-r--r--src/VBox/Runtime/common/path/RTPathChangeToUnixSlashes.cpp74
-rw-r--r--src/VBox/Runtime/common/path/RTPathCopyComponents.cpp94
-rw-r--r--src/VBox/Runtime/common/path/RTPathCountComponents.cpp62
-rw-r--r--src/VBox/Runtime/common/path/RTPathEnsureTrailingSeparator.cpp108
-rw-r--r--src/VBox/Runtime/common/path/RTPathExt.cpp79
-rw-r--r--src/VBox/Runtime/common/path/RTPathFilename.cpp109
-rw-r--r--src/VBox/Runtime/common/path/RTPathFilenameUtf16.cpp109
-rw-r--r--src/VBox/Runtime/common/path/RTPathFindCommon.cpp127
-rw-r--r--src/VBox/Runtime/common/path/RTPathFindCommon.cpp.h267
-rw-r--r--src/VBox/Runtime/common/path/RTPathGlob.cpp2177
-rw-r--r--src/VBox/Runtime/common/path/RTPathHasExt.cpp50
-rw-r--r--src/VBox/Runtime/common/path/RTPathHasPath.cpp61
-rw-r--r--src/VBox/Runtime/common/path/RTPathJoin.cpp67
-rw-r--r--src/VBox/Runtime/common/path/RTPathJoinA.cpp83
-rw-r--r--src/VBox/Runtime/common/path/RTPathJoinEx.cpp70
-rw-r--r--src/VBox/Runtime/common/path/RTPathParentLength.cpp95
-rw-r--r--src/VBox/Runtime/common/path/RTPathParentLength.cpp.h68
-rw-r--r--src/VBox/Runtime/common/path/RTPathParse.cpp85
-rw-r--r--src/VBox/Runtime/common/path/RTPathParse.cpp.h256
-rw-r--r--src/VBox/Runtime/common/path/RTPathParseSimple.cpp144
-rw-r--r--src/VBox/Runtime/common/path/RTPathParsedReassemble.cpp161
-rw-r--r--src/VBox/Runtime/common/path/RTPathPurgeFilename.cpp131
-rw-r--r--src/VBox/Runtime/common/path/RTPathRealDup.cpp57
-rw-r--r--src/VBox/Runtime/common/path/RTPathRmCmd.cpp658
-rw-r--r--src/VBox/Runtime/common/path/RTPathSkipRootSpec.cpp51
-rw-r--r--src/VBox/Runtime/common/path/RTPathSplit.cpp143
-rw-r--r--src/VBox/Runtime/common/path/RTPathSplitA.cpp101
-rw-r--r--src/VBox/Runtime/common/path/RTPathSplitReassemble.cpp130
-rw-r--r--src/VBox/Runtime/common/path/RTPathStartsWithRoot.cpp51
-rw-r--r--src/VBox/Runtime/common/path/RTPathStripExt.cpp53
-rw-r--r--src/VBox/Runtime/common/path/RTPathStripFilename.cpp99
-rw-r--r--src/VBox/Runtime/common/path/RTPathStripTrailingSlash.cpp74
-rw-r--r--src/VBox/Runtime/common/path/RTPathTraverseList.cpp90
-rw-r--r--src/VBox/Runtime/common/path/comparepaths.cpp146
-rw-r--r--src/VBox/Runtime/common/path/nocrt-access.cpp78
-rw-r--r--src/VBox/Runtime/common/path/nocrt-unlink.cpp59
-rw-r--r--src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp103
-rw-r--r--src/VBox/Runtime/common/path/rtPathVolumeSpecLen.cpp80
-rw-r--r--src/VBox/Runtime/common/path/rtpath-expand-template.cpp.h92
-rw-r--r--src/VBox/Runtime/common/path/rtpath-root-length-template.cpp.h80
49 files changed, 8088 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/path/Makefile.kup b/src/VBox/Runtime/common/path/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/path/Makefile.kup
diff --git a/src/VBox/Runtime/common/path/RTPathAbsDup.cpp b/src/VBox/Runtime/common/path/RTPathAbsDup.cpp
new file mode 100644
index 00000000..80a79733
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAbsDup.cpp
@@ -0,0 +1,49 @@
+/* $Id: RTPathAbsDup.cpp $ */
+/** @file
+ * IPRT - RTPathAbsDup
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+RTDECL(char *) RTPathAbsDup(const char *pszPath)
+{
+ return RTPathAbsExDup(NULL, pszPath, RTPATH_STR_F_STYLE_HOST);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAbsEx.cpp b/src/VBox/Runtime/common/path/RTPathAbsEx.cpp
new file mode 100644
index 00000000..a93e244f
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAbsEx.cpp
@@ -0,0 +1,699 @@
+/* $Id: RTPathAbsEx.cpp $ */
+/** @file
+ * IPRT - RTPathAbsEx and RTPathAbs.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_PATH
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include "internal/path.h"
+
+
+
+/**
+ * Ensures that the drive letter is capitalized (prereq: RTPATH_PROP_VOLUME).
+ */
+DECLINLINE(void) rtPathAbsExUpperCaseDriveLetter(char *pszAbsPath)
+{
+ AssertReturnVoid(pszAbsPath[1] == ':');
+ char ch = *pszAbsPath;
+ AssertReturnVoid(RT_C_IS_ALPHA(ch));
+ *pszAbsPath = RT_C_TO_UPPER(ch);
+}
+
+
+/**
+ * Common worker for relative paths.
+ *
+ * Uses RTPATHABS_F_STOP_AT_BASE for RTPATHABS_F_STOP_AT_CWD.
+ */
+static int rtPathAbsExWithCwdOrBaseCommon(const char *pszBase, size_t cchBaseInPlace, PRTPATHPARSED pBaseParsed,
+ const char *pszPath, PRTPATHPARSED pParsed, uint32_t fFlags,
+ char *pszAbsPath, size_t *pcbAbsPath)
+{
+ AssertReturn(pBaseParsed->cComps > 0, VERR_INVALID_PARAMETER);
+
+ /*
+ * Clean up the base path first if necessary.
+ *
+ * Note! UNC tries to preserve the first two elements in the base path,
+ * unless it's a \\.\ or \\?\ prefix.
+ */
+ uint32_t const iBaseStop = (pBaseParsed->fProps & (RTPATH_PROP_UNC | RTPATH_PROP_SPECIAL_UNC)) != RTPATH_PROP_UNC
+ || pBaseParsed->cComps < 2 ? 0 : 1;
+ uint32_t iBaseLast = iBaseStop;
+ if (pBaseParsed->fProps & (RTPATH_PROP_DOT_REFS | RTPATH_PROP_DOTDOT_REFS))
+ {
+ uint32_t const cComps = pBaseParsed->cComps;
+ uint32_t i = iBaseStop + 1;
+ while (i < cComps)
+ {
+ uint32_t const cchComp = pBaseParsed->aComps[i].cch;
+ if ( cchComp > 2
+ || pszPath[pBaseParsed->aComps[i].off] != '.'
+ || (cchComp == 2 && pszPath[pBaseParsed->aComps[i].off + 1] != '.') )
+ iBaseLast = i;
+ else
+ {
+ Assert(cchComp == 1 || cchComp == 2);
+ pBaseParsed->aComps[i].cch = 0;
+ if (cchComp == 2)
+ {
+ while (iBaseLast > 0 && pBaseParsed->aComps[iBaseLast].cch == 0)
+ iBaseLast--;
+ if (iBaseLast > iBaseStop)
+ {
+ Assert(pBaseParsed->aComps[iBaseLast].cch != 0);
+ pBaseParsed->aComps[iBaseLast].cch = 0;
+ iBaseLast--;
+ }
+ }
+ }
+ i++;
+ }
+ Assert(iBaseLast < cComps);
+ }
+ else
+ iBaseLast = pBaseParsed->cComps - 1;
+
+ /*
+ * Clean up the path next if needed.
+ */
+ int32_t iLast = -1; /* Is signed here! */
+ if (pParsed->fProps & (RTPATH_PROP_DOT_REFS | RTPATH_PROP_DOTDOT_REFS))
+ {
+ uint32_t const cComps = pParsed->cComps;
+ uint32_t i = 0;
+
+ /* If we have a volume specifier, take it from the base path. */
+ if (pParsed->fProps & RTPATH_PROP_VOLUME)
+ pParsed->aComps[i++].cch = 0;
+
+ while (i < cComps)
+ {
+ uint32_t const cchComp = pParsed->aComps[i].cch;
+ if ( cchComp > 2
+ || pszPath[pParsed->aComps[i].off] != '.'
+ || (cchComp == 2 && pszPath[pParsed->aComps[i].off + 1] != '.') )
+ iLast = i;
+ else
+ {
+ Assert(cchComp == 1 || cchComp == 2);
+ pParsed->aComps[i].cch = 0;
+ if (cchComp == 2)
+ {
+ while (iLast >= 0 && pParsed->aComps[iLast].cch == 0)
+ iLast--;
+ if (iLast >= 0)
+ {
+ Assert(pParsed->aComps[iLast].cch != 0);
+ pParsed->aComps[iLast].cch = 0;
+ iLast--;
+ }
+ else if ( iBaseLast > iBaseStop
+ && !(fFlags & RTPATHABS_F_STOP_AT_BASE))
+ {
+ while (iBaseLast > iBaseStop && pBaseParsed->aComps[iBaseLast].cch == 0)
+ iBaseLast--;
+ if (iBaseLast > iBaseStop)
+ {
+ Assert(pBaseParsed->aComps[iBaseLast].cch != 0);
+ pBaseParsed->aComps[iBaseLast].cch = 0;
+ iBaseLast--;
+ }
+ }
+ }
+ }
+ i++;
+ }
+ Assert(iLast < (int32_t)cComps);
+ }
+ else
+ {
+ /* If we have a volume specifier, take it from the base path. */
+ iLast = pParsed->cComps - 1;
+ if (pParsed->fProps & RTPATH_PROP_VOLUME)
+ {
+ pParsed->aComps[0].cch = 0;
+ if (iLast == 0)
+ iLast = -1;
+ }
+ }
+
+ /*
+ * Do we need a trailing slash in the base?
+ * If nothing is taken from pszPath, preserve its trailing slash,
+ * otherwise make sure there is a slash for joining the two.
+ */
+ Assert(!(pParsed->fProps & RTPATH_PROP_ROOT_SLASH));
+ if (pBaseParsed->cComps == 1)
+ {
+ AssertReturn(pBaseParsed->fProps & RTPATH_PROP_ROOT_SLASH, VERR_PATH_DOES_NOT_START_WITH_ROOT);
+ Assert(!(pBaseParsed->fProps & RTPATH_PROP_DIR_SLASH));
+ }
+ else
+ {
+ Assert(pBaseParsed->cComps > 1);
+ if ( iLast >= 0
+ || (pParsed->fProps & RTPATH_PROP_DIR_SLASH)
+ || (fFlags & RTPATHABS_F_ENSURE_TRAILING_SLASH) )
+ pBaseParsed->fProps |= RTPATH_PROP_DIR_SLASH;
+ else
+ pBaseParsed->fProps &= ~RTPATH_PROP_DIR_SLASH;
+ }
+
+ /* Apply the trailing flash flag to the input path: */
+ if ( iLast >= 0
+ && (fFlags & RTPATHABS_F_ENSURE_TRAILING_SLASH))
+ pParsed->fProps |= RTPATH_PROP_DIR_SLASH;
+
+ /*
+ * Combine the two. RTPathParsedReassemble can handle in place stuff, as
+ * long as the path doesn't grow.
+ */
+ int rc = RTPathParsedReassemble(pszBase, pBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, *pcbAbsPath);
+ if (RT_SUCCESS(rc))
+ {
+ if (pBaseParsed->fProps & RTPATH_PROP_VOLUME)
+ rtPathAbsExUpperCaseDriveLetter(pszAbsPath);
+
+ cchBaseInPlace = pBaseParsed->cchPath;
+ Assert(cchBaseInPlace == strlen(pszAbsPath));
+ if (iLast >= 0)
+ {
+ rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK,
+ &pszAbsPath[cchBaseInPlace], *pcbAbsPath - cchBaseInPlace);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbAbsPath = cchBaseInPlace + pParsed->cchPath;
+ Assert(*pcbAbsPath == strlen(pszAbsPath));
+ }
+ else
+ *pcbAbsPath = cchBaseInPlace + pParsed->cchPath + 1;
+ }
+ else
+ *pcbAbsPath = cchBaseInPlace;
+ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ if (iLast >= 0)
+ {
+ RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, 0);
+ *pcbAbsPath = pBaseParsed->cchPath + pParsed->cchPath + 1;
+ }
+ else
+ *pcbAbsPath = pBaseParsed->cchPath + 1;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Handles the no-root-path scenario where we do CWD prefixing.
+ */
+static int rtPathAbsExWithCwd(const char *pszPath, PRTPATHPARSED pParsed, uint32_t fFlags, char *pszAbsPath, size_t *pcbAbsPath)
+{
+ /*
+ * Get the current directory and place it in the output buffer.
+ */
+ size_t cchInPlace;
+ size_t cbCwd = *pcbAbsPath;
+ char *pszCwdFree = NULL;
+ char *pszCwd = pszAbsPath;
+ int rc;
+ if ( !(fFlags & RTPATH_STR_F_STYLE_DOS)
+ || (pParsed->fProps & (RTPATH_PROP_VOLUME | RTPATH_PROP_ROOT_SLASH)) != RTPATH_PROP_VOLUME )
+ rc = RTPathGetCurrent(pszCwd, cbCwd);
+ else
+ rc = RTPathGetCurrentOnDrive(pszPath[0], pszCwd, cbCwd);
+ if (RT_SUCCESS(rc))
+ cchInPlace = strlen(pszCwd);
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* Allocate a big temporary buffer so we can return the correct length
+ (the destination buffer might even be big enough if pszPath includes
+ sufficient '..' entries). */
+ cchInPlace = 0;
+ cbCwd = RT_MAX(cbCwd * 4, RTPATH_BIG_MAX);
+ pszCwdFree = pszCwd = (char *)RTMemTmpAlloc(cbCwd);
+ if (pszCwdFree)
+ {
+ if ( !(fFlags & RTPATH_STR_F_STYLE_DOS)
+ || (pParsed->fProps & (RTPATH_PROP_VOLUME | RTPATH_PROP_ROOT_SLASH)) != RTPATH_PROP_VOLUME )
+ rc = RTPathGetCurrent(pszCwd, cbCwd);
+ else
+ rc = RTPathGetCurrentOnDrive(pszPath[0], pszCwd, cbCwd);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ rc = VERR_FILENAME_TOO_LONG;
+ RTMemTmpFree(pszCwdFree);
+ return rc;
+ }
+ }
+ else
+ {
+ *pcbAbsPath = cbCwd + 1 + pParsed->cchPath + 1;
+ return rc;
+ }
+ }
+ else
+ return rc;
+
+ /*
+ * Parse the path.
+ */
+ union
+ {
+ RTPATHPARSED Parsed;
+ uint8_t abPadding[1024];
+ } uCwd;
+ PRTPATHPARSED pCwdParsedFree = NULL;
+ PRTPATHPARSED pCwdParsed = &uCwd.Parsed;
+ size_t cbCwdParsed = sizeof(uCwd);
+ rc = RTPathParse(pszCwd, pCwdParsed, cbCwdParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbCwdParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pCwdParsed->cComps + 2]);
+ pCwdParsedFree = pCwdParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbCwdParsed);
+ AssertReturnStmt(pCwdParsed, RTMemTmpFree(pszCwdFree), VERR_NO_TMP_MEMORY);
+ rc = RTPathParse(pszCwd, pCwdParsed, cbCwdParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pCwdParsedFree); RTMemTmpFree(pszCwdFree), rc);
+ }
+ else
+ AssertMsgFailedReturn(("rc=%Rrc '%s'\n", rc, pszPath), rc);
+
+ /*
+ * Join paths with the base-path code.
+ */
+ if (fFlags & RTPATHABS_F_STOP_AT_CWD)
+ fFlags |= RTPATHABS_F_STOP_AT_BASE;
+ else
+ fFlags &= ~RTPATHABS_F_STOP_AT_BASE;
+ rc = rtPathAbsExWithCwdOrBaseCommon(pszCwd, cchInPlace, pCwdParsed, pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ if (pCwdParsedFree)
+ RTMemTmpFree(pCwdParsedFree);
+ if (pszCwdFree)
+ RTMemTmpFree(pszCwdFree);
+ return rc;
+}
+
+
+/**
+ * Handles the no-root-path scenario where we've got a base path.
+ */
+static int rtPathAbsExWithBase(const char *pszBase, const char *pszPath, PRTPATHPARSED pParsed, uint32_t fFlags,
+ char *pszAbsPath, size_t *pcbAbsPath)
+{
+ /*
+ * Parse the base path.
+ */
+ union
+ {
+ RTPATHPARSED Parsed;
+ uint8_t abPadding[1024];
+ } uBase;
+ PRTPATHPARSED pBaseParsedFree = NULL;
+ PRTPATHPARSED pBaseParsed = &uBase.Parsed;
+ size_t cbBaseParsed = sizeof(uBase);
+ int rc = RTPathParse(pszBase, pBaseParsed, cbBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbBaseParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pBaseParsed->cComps + 2]);
+ pBaseParsedFree = pBaseParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbBaseParsed);
+ AssertReturn(pBaseParsed, VERR_NO_TMP_MEMORY);
+ rc = RTPathParse(pszBase, pBaseParsed, cbBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pBaseParsedFree), rc);
+ }
+ else
+ AssertMsgFailedReturn(("rc=%Rrc '%s'\n", rc, pszPath), rc);
+
+ /*
+ * If the base path isn't absolute, we need to deal with that.
+ */
+ size_t cchInPlace = 0;
+ if ((pBaseParsed->fProps & (RTPATH_PROP_ABSOLUTE | RTPATH_PROP_EXTRA_SLASHES | RTPATH_PROP_DOT_REFS)) == RTPATH_PROP_ABSOLUTE)
+ { /* likely */ }
+ else
+ {
+ cchInPlace = *pcbAbsPath;
+ rc = RTPathAbsEx(NULL, pszBase, fFlags, pszAbsPath, &cchInPlace);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(strlen(pszAbsPath) == cchInPlace);
+ Assert(cchInPlace > 0);
+ }
+ else
+ {
+/** @todo Allocate temp buffer like we do for CWD? */
+ /* This is over generious, but don't want to put too much effort into it yet. */
+ if (rc == VERR_BUFFER_OVERFLOW)
+ *pcbAbsPath = cchInPlace + 1 + pParsed->cchPath + 1;
+ return rc;
+ }
+
+ /*
+ * Reparse it.
+ */
+ rc = RTPathParse(pszAbsPath, pBaseParsed, cbBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ if (pBaseParsedFree)
+ RTMemTmpFree(pBaseParsedFree);
+ cbBaseParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pBaseParsed->cComps + 2]);
+ pBaseParsedFree = pBaseParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbBaseParsed);
+ AssertReturn(pBaseParsed, VERR_NO_TMP_MEMORY);
+ rc = RTPathParse(pszAbsPath, pBaseParsed, cbBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pBaseParsedFree), rc);
+ }
+ else
+ AssertMsgFailedReturn(("rc=%Rrc '%s'\n", rc, pszPath), rc);
+ }
+
+ /*
+ * Join paths with the CWD code.
+ */
+ rc = rtPathAbsExWithCwdOrBaseCommon(cchInPlace ? pszAbsPath : pszBase, cchInPlace, pBaseParsed,
+ pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ if (pBaseParsedFree)
+ RTMemTmpFree(pBaseParsedFree);
+ return rc;
+}
+
+
+/**
+ * Handles the RTPATH_PROP_ROOT_SLASH case.
+ */
+static int rtPathAbsExRootSlash(const char *pszBase, const char *pszPath, PRTPATHPARSED pParsed,
+ uint32_t fFlags, char *pszAbsPath, size_t *pcbAbsPath)
+{
+ /*
+ * Eliminate dot and dot-dot components.
+ * Note! aComp[0] is the root stuff and must never be dropped.
+ */
+ uint32_t const cComps = pParsed->cComps;
+ uint32_t const iStop = (pParsed->fProps & (RTPATH_PROP_UNC | RTPATH_PROP_SPECIAL_UNC)) != RTPATH_PROP_UNC
+ || pParsed->cComps < 2 ? 0 : 1;
+ uint32_t iLast = iStop;
+ uint32_t i = iStop + 1;
+ while (i < cComps)
+ {
+ uint32_t const cchComp = pParsed->aComps[i].cch;
+ if ( cchComp > 2
+ || pszPath[pParsed->aComps[i].off] != '.'
+ || (cchComp == 2 && pszPath[pParsed->aComps[i].off + 1] != '.') )
+ iLast = i;
+ else
+ {
+ Assert(cchComp == 1 || cchComp == 2);
+ pParsed->aComps[i].cch = 0;
+ if (cchComp == 2)
+ {
+ while (iLast > iStop && pParsed->aComps[iLast].cch == 0)
+ iLast--;
+ if (iLast > iStop)
+ {
+ Assert(pParsed->aComps[iLast].cch > 0);
+ pParsed->aComps[iLast].cch = 0;
+ iLast--;
+ }
+ }
+ }
+ i++;
+ }
+
+ /*
+ * Before we continue, ensure trailing slash if requested.
+ */
+ if ( (fFlags & RTPATHABS_F_ENSURE_TRAILING_SLASH)
+ && iLast > 0)
+ pParsed->fProps |= RTPATH_PROP_DIR_SLASH;
+
+ /*
+ * DOS-style: Do we need to supply a drive letter or UNC root?
+ */
+ size_t cchRootPrefix = 0;
+ if ( (fFlags & RTPATH_STR_F_STYLE_DOS)
+ && !(pParsed->fProps & (RTPATH_PROP_VOLUME | RTPATH_PROP_UNC)) )
+ {
+ /* Use the drive/UNC from the base path if we have one and it has such a component: */
+ if (pszBase)
+ {
+ union
+ {
+ RTPATHPARSED Parsed;
+ uint8_t abPadding[sizeof(RTPATHPARSED) + sizeof(pParsed->aComps[0]) * 2];
+ } uBase;
+ int rc = RTPathParse(pszBase, &uBase.Parsed, sizeof(uBase), fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW, ("%Rrc - '%s'\n", rc, pszBase), rc);
+
+ if (uBase.Parsed.fProps & RTPATH_PROP_VOLUME)
+ {
+ /* get the drive letter. */
+ Assert(uBase.Parsed.aComps[0].cch == 2 || uBase.Parsed.aComps[0].cch == 3);
+ cchRootPrefix = RT_MIN(uBase.Parsed.aComps[0].cch, 2);
+ if (cchRootPrefix < *pcbAbsPath)
+ memcpy(pszAbsPath, &pszBase[uBase.Parsed.aComps[0].off], cchRootPrefix);
+ else
+ {
+ rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, 0);
+ Assert(rc == VERR_BUFFER_OVERFLOW);
+
+ *pcbAbsPath = cchRootPrefix + pParsed->cchPath + 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ rtPathAbsExUpperCaseDriveLetter(pszAbsPath);
+ }
+ else if (uBase.Parsed.fProps & RTPATH_PROP_UNC)
+ {
+ /* Include the share if we've got one. */
+ cchRootPrefix = uBase.Parsed.aComps[0].cch;
+ if (uBase.Parsed.cComps >= 2 && !(uBase.Parsed.fProps & RTPATH_PROP_SPECIAL_UNC))
+ cchRootPrefix += uBase.Parsed.aComps[1].cch;
+ else if (uBase.Parsed.fProps & RTPATH_PROP_ROOT_SLASH)
+ cchRootPrefix--;
+ if (cchRootPrefix < *pcbAbsPath)
+ {
+ if (uBase.Parsed.cComps < 2 || (uBase.Parsed.fProps & RTPATH_PROP_SPECIAL_UNC))
+ memcpy(pszAbsPath, &pszBase[uBase.Parsed.aComps[0].off], cchRootPrefix);
+ else
+ {
+ size_t cchFirst = uBase.Parsed.aComps[0].cch;
+ memcpy(pszAbsPath, &pszBase[uBase.Parsed.aComps[0].off], cchFirst);
+ memcpy(&pszAbsPath[cchFirst], &pszBase[uBase.Parsed.aComps[1].off], uBase.Parsed.aComps[1].cch);
+ }
+ }
+ else
+ {
+ rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, 0);
+ Assert(rc == VERR_BUFFER_OVERFLOW);
+
+ *pcbAbsPath = cchRootPrefix + pParsed->cchPath + 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ pszBase = NULL;
+ }
+
+ /* Otherwise, query the current drive: */
+ if (!pszBase)
+ {
+ int rc = RTPathGetCurrentDrive(pszAbsPath, *pcbAbsPath);
+ if (RT_SUCCESS(rc))
+ cchRootPrefix = strlen(pszAbsPath);
+ else
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ int rc2 = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, 0);
+ Assert(rc2 == VERR_BUFFER_OVERFLOW);
+
+ char *pszTmp = (char *)RTMemTmpAlloc(RTPATH_BIG_MAX);
+ if (pszTmp)
+ {
+ rc2 = RTPathGetCurrentDrive(pszTmp, RTPATH_BIG_MAX);
+ if (RT_SUCCESS(rc2))
+ *pcbAbsPath = strlen(pszTmp) + pParsed->cchPath + 1;
+ else
+ *pcbAbsPath = RT_MAX(*pcbAbsPath * 2, (size_t)RTPATH_BIG_MAX * 3 + pParsed->cchPath + 1);
+ RTMemTmpFree(pszTmp);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ return rc;
+ }
+ }
+ }
+
+ /*
+ * Reassemble the path and return.
+ */
+ int rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK,
+ pszAbsPath + cchRootPrefix, *pcbAbsPath - cchRootPrefix);
+ *pcbAbsPath = cchRootPrefix + pParsed->cchPath + (rc == VERR_BUFFER_OVERFLOW);
+ return rc;
+}
+
+
+/**
+ * Handles the RTPATH_PROP_ABSOLUTE case.
+ */
+static int rtPathAbsExAbsolute(const char *pszPath, PRTPATHPARSED pParsed, uint32_t fFlags, char *pszAbsPath, size_t *pcbAbsPath)
+{
+ if (pParsed->fProps & RTPATH_PROP_DOT_REFS)
+ {
+ uint32_t i = pParsed->cComps;
+ while (i-- > 0)
+ if ( pParsed->aComps[i].cch == 1
+ && pszPath[pParsed->aComps[i].off] == '.')
+ pParsed->aComps[i].cch = 0;
+ }
+
+ if ( (fFlags & RTPATHABS_F_ENSURE_TRAILING_SLASH)
+ && pParsed->cComps > 1)
+ pParsed->fProps |= RTPATH_PROP_DIR_SLASH;
+
+ int rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, *pcbAbsPath);
+ *pcbAbsPath = pParsed->cchPath + (rc == VERR_BUFFER_OVERFLOW);
+ if (RT_SUCCESS(rc) && (pParsed->fProps & RTPATH_PROP_VOLUME))
+ rtPathAbsExUpperCaseDriveLetter(pszAbsPath);
+ return rc;
+}
+
+
+RTDECL(int) RTPathAbsEx(const char *pszBase, const char *pszPath, uint32_t fFlags, char *pszAbsPath, size_t *pcbAbsPath)
+{
+ LogFlow(("RTPathAbsEx: pszBase=%s pszPath=%s fFlags=%#x\n", pszBase, pszPath, fFlags));
+
+ /*
+ * Some input validation.
+ */
+ AssertPtr(pszPath);
+ AssertPtr(pszAbsPath);
+ AssertPtr(pcbAbsPath);
+ AssertReturn(*pszPath != '\0', VERR_PATH_ZERO_LENGTH);
+
+ AssertCompile(RTPATH_STR_F_STYLE_HOST == 0);
+ AssertReturn( RTPATH_STR_F_IS_VALID(fFlags, RTPATHABS_F_STOP_AT_BASE | RTPATHABS_F_STOP_AT_CWD | RTPATHABS_F_ENSURE_TRAILING_SLASH)
+ && !(fFlags & RTPATH_STR_F_MIDDLE), VERR_INVALID_FLAGS);
+ if ((fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_HOST)
+ fFlags |= RTPATH_STYLE;
+
+ /*
+ * Parse the path we're to straigthen out.
+ */
+ union
+ {
+ RTPATHPARSED Parsed;
+ uint8_t abPadding[1024];
+ } uBuf;
+ PRTPATHPARSED pParsedFree = NULL;
+ PRTPATHPARSED pParsed = &uBuf.Parsed;
+ size_t cbParsed = sizeof(uBuf);
+ int rc = RTPathParse(pszPath, pParsed, cbParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pParsed->cComps + 2]);
+ pParsedFree = pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
+ AssertReturn(pParsed, VERR_NO_TMP_MEMORY);
+ rc = RTPathParse(pszPath, pParsed, cbParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pParsedFree), rc);
+ }
+ else
+ AssertMsgFailedReturn(("rc=%Rrc '%s'\n", rc, pszPath), rc);
+
+ /*
+ * Check if the input is more or less perfect as it is.
+ */
+ if (pParsed->fProps & RTPATH_PROP_ABSOLUTE)
+ rc = rtPathAbsExAbsolute(pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ /*
+ * What about relative but with a root slash.
+ */
+ else if (pParsed->fProps & RTPATH_PROP_ROOT_SLASH)
+ rc = rtPathAbsExRootSlash(pszBase, pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ /*
+ * Not exactly perfect. No root slash.
+ *
+ * If we have a base path, we use it unless we're into drive letters and
+ * pszPath refers to a different drive letter.
+ */
+ else if ( pszBase
+ && ( !(fFlags & RTPATH_STR_F_STYLE_DOS)
+ /** @todo add flag for skipping this and always using the base path? */
+ || !(pParsed->fProps & RTPATH_PROP_VOLUME)
+ || ( RT_C_IS_ALPHA(pszBase[0])
+ && pszBase[1] == ':'
+ && RT_C_TO_UPPER(pszBase[0]) == RT_C_TO_UPPER(pszPath[0])
+ )
+ )
+ )
+ rc = rtPathAbsExWithBase(pszBase, pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ else
+ rc = rtPathAbsExWithCwd(pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+
+ if (pParsedFree)
+ RTMemTmpFree(pParsedFree);
+ LogFlow(("RTPathAbsEx: returns %Rrc *pcbAbsPath=%#zx\n", rc, *pcbAbsPath));
+ return rc;
+}
+
+
+RTDECL(int) RTPathAbs(const char *pszPath, char *pszAbsPath, size_t cbAbsPath)
+{
+ return RTPathAbsEx(NULL, pszPath, RTPATH_STR_F_STYLE_HOST, pszAbsPath, &cbAbsPath);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAbsExDup.cpp b/src/VBox/Runtime/common/path/RTPathAbsExDup.cpp
new file mode 100644
index 00000000..6d658092
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAbsExDup.cpp
@@ -0,0 +1,82 @@
+/* $Id: RTPathAbsExDup.cpp $ */
+/** @file
+ * IPRT - RTPathAbsExDup
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/errcore.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(char *) RTPathAbsExDup(const char *pszBase, const char *pszPath, uint32_t fFlags)
+{
+ unsigned cTries = 16;
+ size_t cbAbsPath = RTPATH_MAX / 2;
+ for (;;)
+ {
+ char *pszAbsPath = RTStrAlloc(cbAbsPath);
+ if (pszAbsPath)
+ {
+ size_t cbActual = cbAbsPath;
+ int rc = RTPathAbsEx(pszBase, pszPath, fFlags, pszAbsPath, &cbActual);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbActual < cbAbsPath / 2)
+ RTStrRealloc(&pszAbsPath, cbActual + 1);
+ return pszAbsPath;
+ }
+
+ RTStrFree(pszAbsPath);
+
+ if (rc != VERR_BUFFER_OVERFLOW)
+ break;
+
+ if (--cTries == 0)
+ break;
+
+ cbAbsPath = RT_MAX(RT_ALIGN_Z(cbActual + 16, 64), cbAbsPath + 256);
+ }
+ else
+ break;
+ }
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAppend.cpp b/src/VBox/Runtime/common/path/RTPathAppend.cpp
new file mode 100644
index 00000000..18297225
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAppend.cpp
@@ -0,0 +1,51 @@
+/* $Id: RTPathAppend.cpp $ */
+/** @file
+ * IPRT - RTPathAppend
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/string.h>
+
+
+RTDECL(int) RTPathAppend(char *pszPath, size_t cbPathDst, const char *pszAppend)
+{
+ return RTPathAppendEx(pszPath, cbPathDst, pszAppend, RTSTR_MAX, RTPATH_STR_F_STYLE_HOST);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAppendEx.cpp b/src/VBox/Runtime/common/path/RTPathAppendEx.cpp
new file mode 100644
index 00000000..2d936f5c
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAppendEx.cpp
@@ -0,0 +1,97 @@
+/* $Id: RTPathAppendEx.cpp $ */
+/** @file
+ * IPRT - RTPathAppendEx
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#define RTPATH_TEMPLATE_CPP_H "RTPathAppendEx.cpp.h"
+#include "rtpath-expand-template.cpp.h"
+
+
+RTDECL(int) RTPathAppendEx(char *pszPath, size_t cbPathDst, const char *pszAppend, size_t cchAppendMax, uint32_t fFlags)
+{
+ char *pszPathEnd = RTStrEnd(pszPath, cbPathDst);
+ AssertReturn(pszPathEnd, VERR_INVALID_PARAMETER);
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0));
+
+ /*
+ * Special cases.
+ */
+ if (!pszAppend)
+ return VINF_SUCCESS;
+ size_t cchAppend = RTStrNLen(pszAppend, cchAppendMax);
+ if (!cchAppend)
+ return VINF_SUCCESS;
+ if (pszPathEnd == pszPath)
+ {
+ if (cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pszPath, pszAppend, cchAppend);
+ pszPath[cchAppend] = '\0';
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Go to path style specific code now.
+ */
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_DOS:
+ return rtPathAppendExStyleDos(pszPath, cbPathDst, pszPathEnd, pszAppend, cchAppend);
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_UNIX:
+ return rtPathAppendExStyleUnix(pszPath, cbPathDst, pszPathEnd, pszAppend, cchAppend);
+
+ default:
+ AssertFailedReturn(VERR_INVALID_FLAGS); /* impossible */
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h b/src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h
new file mode 100644
index 00000000..f51f335d
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h
@@ -0,0 +1,166 @@
+/* $Id: RTPathAppendEx.cpp.h $ */
+/** @file
+ * IPRT - rtPathAppendEx - Code Template.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/**
+ * Figures the length of the root part of the path.
+ *
+ * @returns length of the root specifier.
+ * @retval 0 if none.
+ *
+ * @param pszPath The path to investigate.
+ *
+ * @remarks Unnecessary root slashes will not be counted. The caller will have
+ * to deal with it where it matters. (Unlike rtPathRootSpecLen which
+ * counts them.)
+ */
+DECLINLINE(size_t) RTPATH_STYLE_FN(rtPathRootSpecLen2)(const char *pszPath)
+{
+ /* fend of wildlife. */
+ if (!pszPath)
+ return 0;
+
+ /* Root slash? */
+ if (RTPATH_IS_SLASH(pszPath[0]))
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ /* UNC? */
+ if ( RTPATH_IS_SLASH(pszPath[1])
+ && pszPath[2] != '\0'
+ && !RTPATH_IS_SLASH(pszPath[2]))
+ {
+ /* Find the end of the server name. */
+ const char *pszEnd = pszPath + 2;
+ pszEnd += 2;
+ while ( *pszEnd != '\0'
+ && !RTPATH_IS_SLASH(*pszEnd))
+ pszEnd++;
+ if (RTPATH_IS_SLASH(*pszEnd))
+ {
+ pszEnd++;
+ while (RTPATH_IS_SLASH(*pszEnd))
+ pszEnd++;
+
+ /* Find the end of the share name */
+ while ( *pszEnd != '\0'
+ && !RTPATH_IS_SLASH(*pszEnd))
+ pszEnd++;
+ if (RTPATH_IS_SLASH(*pszEnd))
+ pszEnd++;
+ return pszPath - pszEnd;
+ }
+ }
+#endif
+ return 1;
+ }
+
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ /* Drive specifier? */
+ if ( pszPath[0] != '\0'
+ && pszPath[1] == ':'
+ && RT_C_IS_ALPHA(pszPath[0]))
+ {
+ if (RTPATH_IS_SLASH(pszPath[2]))
+ return 3;
+ return 2;
+ }
+#endif
+ return 0;
+}
+
+
+/** Internal worker for RTPathAppendEx. */
+DECLINLINE(int) RTPATH_STYLE_FN(rtPathAppendEx)(char *pszPath, size_t cbPathDst, char *pszPathEnd,
+ const char *pszAppend, size_t cchAppend)
+{
+ /*
+ * Balance slashes and check for buffer overflow.
+ */
+ if (!RTPATH_IS_SLASH(pszPathEnd[-1]))
+ {
+ if (!RTPATH_IS_SLASH(pszAppend[0]))
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ if ( (size_t)(pszPathEnd - pszPath) == 2
+ && pszPath[1] == ':'
+ && RT_C_IS_ALPHA(pszPath[0]))
+ {
+ if ((size_t)(pszPathEnd - pszPath) + cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ }
+ else
+#endif
+ {
+ if ((size_t)(pszPathEnd - pszPath) + 1 + cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ *pszPathEnd++ = RTPATH_SLASH;
+ }
+ }
+ else
+ {
+ /* One slash is sufficient at this point. */
+ while (cchAppend > 1 && RTPATH_IS_SLASH(pszAppend[1]))
+ pszAppend++, cchAppend--;
+
+ if ((size_t)(pszPathEnd - pszPath) + cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ {
+ /* No slashes needed in the appended bit. */
+ while (cchAppend && RTPATH_IS_SLASH(*pszAppend))
+ pszAppend++, cchAppend--;
+
+ /* In the leading path we can skip unnecessary trailing slashes, but
+ be sure to leave one. */
+ size_t const cchRoot = RTPATH_STYLE_FN(rtPathRootSpecLen2)(pszPath);
+ while ( (size_t)(pszPathEnd - pszPath) > RT_MAX(1, cchRoot)
+ && RTPATH_IS_SLASH(pszPathEnd[-2]))
+ pszPathEnd--;
+
+ if ((size_t)(pszPathEnd - pszPath) + cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /*
+ * What remains now is the just the copying.
+ */
+ memcpy(pszPathEnd, pszAppend, cchAppend);
+ pszPathEnd[cchAppend] = '\0';
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathCalcRelative.cpp b/src/VBox/Runtime/common/path/RTPathCalcRelative.cpp
new file mode 100644
index 00000000..a1666957
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathCalcRelative.cpp
@@ -0,0 +1,248 @@
+/* $Id: RTPathCalcRelative.cpp $ */
+/** @file
+ * IPRT - RTPathCreateRelative.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+# include <iprt/uni.h>
+#endif
+#include "internal/path.h"
+
+
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+/** Helper for doing case insensitive comparison of a mismatching codepoint. */
+DECLINLINE(bool) rtPathCalcRelativeEqualICaseCodepoint(const char *pszPathFromStart, const char *pszPathFrom,
+ const char *pszPathToStart, const char *pszPathTo)
+{
+ RTUNICP ucFrom = RTStrGetCp(RTStrPrevCp(pszPathFromStart, pszPathFrom));
+ RTUNICP ucTo = RTStrGetCp(RTStrPrevCp(pszPathToStart, pszPathTo));
+ return ucFrom == ucTo
+ || RTUniCpToLower(ucFrom) == RTUniCpToLower(ucTo)
+ || RTUniCpToUpper(ucFrom) == RTUniCpToUpper(ucTo);
+}
+#endif
+
+
+RTDECL(int) RTPathCalcRelative(char *pszPathDst, size_t cbPathDst,
+ const char *pszPathFrom, bool fFromFile,
+ const char *pszPathTo)
+{
+ AssertPtrReturn(pszPathDst, VERR_INVALID_POINTER);
+ AssertReturn(cbPathDst, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszPathFrom, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPathTo, VERR_INVALID_POINTER);
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ const char * const pszPathFromStart = pszPathFrom;
+ const char * const pszPathToStart = pszPathTo;
+#endif
+
+ /*
+ * Check for different root specifiers (drive letters), creating a relative path doesn't work here.
+ */
+ size_t offRootFrom = rtPathRootSpecLen(pszPathFrom);
+ AssertReturn(offRootFrom > 0, VERR_INVALID_PARAMETER);
+
+ size_t offRootTo = rtPathRootSpecLen(pszPathTo);
+ AssertReturn(offRootTo > 0, VERR_INVALID_PARAMETER);
+
+
+ /** @todo correctly deal with extra root slashes! */
+ if (offRootFrom != offRootTo)
+ return VERR_NOT_SUPPORTED;
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_DOS
+ if (RTStrNCmp(pszPathFrom, pszPathTo, offRootFrom))
+ return VERR_NOT_SUPPORTED;
+#else
+ if (RTStrNICmp(pszPathFrom, pszPathTo, offRootFrom))
+ {
+ const char *pszFromCursor = pszPathFrom;
+ const char *pszToCursor = pszPathTo;
+ while ((size_t)(pszFromCursor - pszPathFrom) < offRootFrom)
+ {
+ RTUNICP ucFrom;
+ int rc = RTStrGetCpEx(&pszFromCursor, &ucFrom);
+ AssertRCReturn(rc, rc);
+
+ RTUNICP ucTo;
+ rc = RTStrGetCpEx(&pszToCursor, &ucTo);
+ AssertRCReturn(rc, rc);
+ if ( ucFrom != ucTo
+ && RTUniCpToLower(ucFrom) != RTUniCpToLower(ucTo)
+ && RTUniCpToUpper(ucFrom) != RTUniCpToUpper(ucTo)
+ && (!RTPATH_IS_SLASH(ucFrom) || !RTPATH_IS_SLASH(ucTo)) )
+ return VERR_NOT_SUPPORTED;
+ }
+ }
+#endif
+
+ pszPathFrom += offRootFrom;
+ pszPathTo += offRootTo;
+
+ /*
+ * Skip out the part of the path which is equal to both.
+ */
+ const char *pszStartOfFromComp = pszPathFrom;
+ for (;;)
+ {
+ char const chFrom = *pszPathFrom;
+ char const chTo = *pszPathTo;
+ if (!RTPATH_IS_SLASH(chFrom))
+ {
+ if (chFrom == chTo)
+ {
+ if (chFrom)
+ { /* likely */ }
+ else
+ {
+ /* Special case: The two paths are equal. */
+ if (fFromFile)
+ {
+ size_t cchComp = pszPathFrom - pszStartOfFromComp;
+ if (cchComp < cbPathDst)
+ {
+ memcpy(pszPathDst, pszStartOfFromComp, cchComp);
+ pszPathDst[cchComp] = '\0';
+ return VINF_SUCCESS;
+ }
+ }
+ else if (sizeof(".") <= cbPathDst)
+ {
+ memcpy(pszPathDst, ".", sizeof("."));
+ return VINF_SUCCESS;
+ }
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if (rtPathCalcRelativeEqualICaseCodepoint(pszPathFromStart, pszPathFrom + 1, pszPathToStart, pszPathTo + 1))
+ { /* if not likely, then simpler code structure wise. */ }
+#endif
+ else if (chFrom != '\0' || !RTPATH_IS_SLASH(chTo) || fFromFile)
+ break;
+ else
+ {
+ pszStartOfFromComp = pszPathFrom;
+ do
+ pszPathTo++;
+ while (RTPATH_IS_SLASH(*pszPathTo));
+ break;
+ }
+ pszPathFrom++;
+ pszPathTo++;
+ }
+ else if (RTPATH_IS_SLASH(chTo))
+ {
+ /* Both have slashes. Skip any additional ones before taking down
+ the start of the component for rewinding purposes. */
+ do
+ pszPathTo++;
+ while (RTPATH_IS_SLASH(*pszPathTo));
+ do
+ pszPathFrom++;
+ while (RTPATH_IS_SLASH(*pszPathFrom));
+ pszStartOfFromComp = pszPathFrom;
+ }
+ else
+ break;
+ }
+
+ /* Rewind to the start of the current component. */
+ pszPathTo -= pszPathFrom - pszStartOfFromComp;
+ pszPathFrom = pszStartOfFromComp;
+
+ /* Paths point to the first non equal component now. */
+
+ /*
+ * Constructure the relative path.
+ */
+
+ /* Create the part to go up from pszPathFrom. */
+ unsigned offDst = 0;
+
+ if (!fFromFile && *pszPathFrom != '\0')
+ {
+ if (offDst + 3 < cbPathDst)
+ {
+ pszPathDst[offDst++] = '.';
+ pszPathDst[offDst++] = '.';
+ pszPathDst[offDst++] = RTPATH_SLASH;
+ }
+ else
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ while (*pszPathFrom != '\0')
+ {
+ char ch;
+ while ( (ch = *pszPathFrom) != '\0'
+ && !RTPATH_IS_SLASH(*pszPathFrom))
+ pszPathFrom++;
+ while ( (ch = *pszPathFrom) != '\0'
+ && RTPATH_IS_SLASH(ch))
+ pszPathFrom++;
+ if (!ch)
+ break;
+
+ if (offDst + 3 < cbPathDst)
+ {
+ pszPathDst[offDst++] = '.';
+ pszPathDst[offDst++] = '.';
+ pszPathDst[offDst++] = RTPATH_SLASH;
+ }
+ else
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Now append the rest of pszPathTo to the final path. */
+ size_t cchTo = strlen(pszPathTo);
+ if (offDst + cchTo <= cbPathDst)
+ {
+ memcpy(&pszPathDst[offDst], pszPathTo, cchTo);
+ pszPathDst[offDst + cchTo] = '\0';
+ return VINF_SUCCESS;
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathChangeToDosSlashes.cpp b/src/VBox/Runtime/common/path/RTPathChangeToDosSlashes.cpp
new file mode 100644
index 00000000..770ee4b5
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathChangeToDosSlashes.cpp
@@ -0,0 +1,74 @@
+/* $Id: RTPathChangeToDosSlashes.cpp $ */
+/** @file
+ * IPRT - RTPathChangeToDosSlashes
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+/**
+ * Changes all the slashes in the specified path to DOS style.
+ *
+ * Unless @a fForce is set, nothing will be done when on a UNIX flavored system
+ * since paths wont work with DOS style slashes there.
+ *
+ * @returns @a pszPath.
+ * @param pszPath The path to modify.
+ * @param fForce Whether to force the conversion on non-DOS OSes.
+ */
+RTDECL(char *) RTPathChangeToDosSlashes(char *pszPath, bool fForce)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ RT_NOREF_PV(fForce);
+#else
+ if (fForce)
+#endif
+ {
+ char ch;
+ char *psz = pszPath;
+ while ((ch = *psz) != '\0')
+ {
+ if (ch == '/')
+ *psz = '\\';
+ psz++;
+ }
+ }
+ return pszPath;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathChangeToUnixSlashes.cpp b/src/VBox/Runtime/common/path/RTPathChangeToUnixSlashes.cpp
new file mode 100644
index 00000000..8649b792
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathChangeToUnixSlashes.cpp
@@ -0,0 +1,74 @@
+/* $Id: RTPathChangeToUnixSlashes.cpp $ */
+/** @file
+ * IPRT - RTPathChangeToUnixSlashes
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+/**
+ * Changes all the slashes in the specified path to unix style.
+ *
+ * Unless @a fForce is set, nothing will be done when on a UNIX flavored system
+ * since paths wont work with DOS style slashes there.
+ *
+ * @returns @a pszPath.
+ * @param pszPath The path to modify.
+ * @param fForce Whether to force the conversion on non-DOS OSes.
+ */
+RTDECL(char *) RTPathChangeToUnixSlashes(char *pszPath, bool fForce)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ RT_NOREF_PV(fForce);
+#else
+ if (fForce)
+#endif
+ {
+ char ch;
+ char *psz = pszPath;
+ while ((ch = *psz) != '\0')
+ {
+ if (ch == '\\')
+ *psz = '/';
+ psz++;
+ }
+ }
+ return pszPath;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathCopyComponents.cpp b/src/VBox/Runtime/common/path/RTPathCopyComponents.cpp
new file mode 100644
index 00000000..ea202507
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathCopyComponents.cpp
@@ -0,0 +1,94 @@
+/* $Id: RTPathCopyComponents.cpp $ */
+/** @file
+ * IPRT - RTPathCountComponents
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include "internal/path.h"
+
+
+RTDECL(int) RTPathCopyComponents(char *pszDst, size_t cbDst, const char *pszSrc, size_t cComponents)
+{
+ /*
+ * Quick input validation.
+ */
+ AssertPtr(pszDst);
+ AssertPtr(pszSrc);
+ if (cbDst == 0)
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Fend of the simple case where nothing is wanted.
+ */
+ if (cComponents == 0)
+ {
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Parse into the path until we've counted the desired number of objects
+ * or hit the end.
+ */
+ size_t off = rtPathRootSpecLen(pszSrc);
+ size_t c = off != 0;
+ while (c < cComponents && pszSrc[off])
+ {
+ c++;
+ while (!RTPATH_IS_SLASH(pszSrc[off]) && pszSrc[off])
+ off++;
+ while (RTPATH_IS_SLASH(pszSrc[off]))
+ off++;
+ }
+
+ /*
+ * Copy up to but not including 'off'.
+ */
+ if (off >= cbDst)
+ return VERR_BUFFER_OVERFLOW;
+
+ memcpy(pszDst, pszSrc, off);
+ pszDst[off] = '\0';
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathCountComponents.cpp b/src/VBox/Runtime/common/path/RTPathCountComponents.cpp
new file mode 100644
index 00000000..27b4a298
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathCountComponents.cpp
@@ -0,0 +1,62 @@
+/* $Id: RTPathCountComponents.cpp $ */
+/** @file
+ * IPRT - RTPathCountComponents
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include "internal/path.h"
+
+
+RTDECL(size_t) RTPathCountComponents(const char *pszPath)
+{
+ size_t off = rtPathRootSpecLen(pszPath);
+ size_t c = off != 0;
+ while (pszPath[off])
+ {
+ c++;
+ while (!RTPATH_IS_SLASH(pszPath[off]) && pszPath[off])
+ off++;
+ while (RTPATH_IS_SLASH(pszPath[off]))
+ off++;
+ }
+ return c;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathEnsureTrailingSeparator.cpp b/src/VBox/Runtime/common/path/RTPathEnsureTrailingSeparator.cpp
new file mode 100644
index 00000000..b957234a
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathEnsureTrailingSeparator.cpp
@@ -0,0 +1,108 @@
+/* $Id: RTPathEnsureTrailingSeparator.cpp $ */
+/** @file
+ * IPRT - RTPathEnsureTrailingSeparator & RTPathEnsureTrailingSeparatorEx
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Slash character indexed by path style. */
+static char g_achSlashes[] =
+{
+ /*[RTPATH_STR_F_STYLE_HOST] =*/ RTPATH_SLASH,
+ /*[RTPATH_STR_F_STYLE_DOS] =*/ '\\',
+ /*[RTPATH_STR_F_STYLE_UNIX] =*/ '/',
+ /*[RTPATH_STR_F_STYLE_RESERVED] =*/ '!',
+};
+AssertCompile(RTPATH_STR_F_STYLE_HOST == 0);
+AssertCompile(RTPATH_STR_F_STYLE_DOS == 1);
+AssertCompile(RTPATH_STR_F_STYLE_UNIX == 2);
+
+
+RTDECL(size_t) RTPathEnsureTrailingSeparatorEx(char *pszPath, size_t cbPath, uint32_t fFlags)
+{
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0));
+
+ size_t off = strlen(pszPath);
+ if (off > 0)
+ {
+ char ch = pszPath[off - 1];
+ if (ch == '/')
+ return off;
+ if ( (ch == ':' || ch == '\\')
+ && ( (fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_DOS
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ || (fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_HOST
+#endif
+ ))
+ return off;
+
+ if (off + 2 <= cbPath)
+ {
+ pszPath[off++] = g_achSlashes[fFlags & RTPATH_STR_F_STYLE_MASK];
+ pszPath[off] = '\0';
+ return off;
+ }
+ }
+ else if (off + 3 <= cbPath)
+ {
+ pszPath[off++] = '.';
+ pszPath[off++] = g_achSlashes[fFlags & RTPATH_STR_F_STYLE_MASK];
+ pszPath[off] = '\0';
+ return off;
+ }
+
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTPathEnsureTrailingSeparatorEx);
+
+
+RTDECL(size_t) RTPathEnsureTrailingSeparator(char *pszPath, size_t cbPath)
+{
+ return RTPathEnsureTrailingSeparatorEx(pszPath, cbPath, RTPATH_STR_F_STYLE_HOST);
+}
+RT_EXPORT_SYMBOL(RTPathEnsureTrailingSeparator);
+
diff --git a/src/VBox/Runtime/common/path/RTPathExt.cpp b/src/VBox/Runtime/common/path/RTPathExt.cpp
new file mode 100644
index 00000000..54669270
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathExt.cpp
@@ -0,0 +1,79 @@
+/* $Id: RTPathExt.cpp $ */
+/** @file
+ * IPRT - RTPathExt
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+RTDECL(char *) RTPathSuffix(const char *pszPath)
+{
+ const char *psz = pszPath;
+ const char *pszExt = NULL;
+
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ /* handle separators. */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ case ':':
+ pszExt = NULL;
+ break;
+
+ case '\\':
+#endif
+ case '/':
+ pszExt = NULL;
+ break;
+ case '.':
+ pszExt = psz;
+ break;
+
+ /* the end */
+ case '\0':
+ if (pszExt && pszExt != pszPath && pszExt[1])
+ return (char *)(void *)pszExt;
+ return NULL;
+ }
+ }
+
+ /* not reached */
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathFilename.cpp b/src/VBox/Runtime/common/path/RTPathFilename.cpp
new file mode 100644
index 00000000..699ff8ec
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathFilename.cpp
@@ -0,0 +1,109 @@
+/* $Id: RTPathFilename.cpp $ */
+/** @file
+ * IPRT - RTPathFilename
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+
+
+
+RTDECL(char *) RTPathFilename(const char *pszPath)
+{
+ return RTPathFilenameEx(pszPath, RTPATH_STYLE);
+}
+RT_EXPORT_SYMBOL(RTPathFilename);
+
+
+RTDECL(char *) RTPathFilenameEx(const char *pszPath, uint32_t fFlags)
+{
+ const char *psz = pszPath;
+ const char *pszName = pszPath;
+
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0 /*no extra flags*/));
+ fFlags &= RTPATH_STR_F_STYLE_MASK;
+ if (fFlags == RTPATH_STR_F_STYLE_HOST)
+ fFlags = RTPATH_STYLE;
+ if (fFlags == RTPATH_STR_F_STYLE_DOS)
+ {
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ /* handle separators. */
+ case ':':
+ case '\\':
+ case '/':
+ pszName = psz + 1;
+ break;
+
+ /* the end */
+ case '\0':
+ if (*pszName)
+ return (char *)(void *)pszName;
+ return NULL;
+ }
+ }
+ }
+ else
+ {
+ Assert(fFlags == RTPATH_STR_F_STYLE_UNIX);
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ /* handle separators. */
+ case '/':
+ pszName = psz + 1;
+ break;
+
+ /* the end */
+ case '\0':
+ if (*pszName)
+ return (char *)(void *)pszName;
+ return NULL;
+ }
+ }
+ }
+
+ /* not reached */
+}
+RT_EXPORT_SYMBOL(RTPathFilenameEx);
+
diff --git a/src/VBox/Runtime/common/path/RTPathFilenameUtf16.cpp b/src/VBox/Runtime/common/path/RTPathFilenameUtf16.cpp
new file mode 100644
index 00000000..711ba7b4
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathFilenameUtf16.cpp
@@ -0,0 +1,109 @@
+/* $Id: RTPathFilenameUtf16.cpp $ */
+/** @file
+ * IPRT - RTPathFilenameUtf16
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+
+
+
+RTDECL(PRTUTF16) RTPathFilenameUtf16(PCRTUTF16 pwszPath)
+{
+ return RTPathFilenameExUtf16(pwszPath, RTPATH_STYLE);
+}
+RT_EXPORT_SYMBOL(RTPathFilenameUtf16);
+
+
+RTDECL(PRTUTF16) RTPathFilenameExUtf16(PCRTUTF16 pwszPath, uint32_t fFlags)
+{
+ PCRTUTF16 pwsz = pwszPath;
+ PCRTUTF16 pwszName = pwszPath;
+
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0 /*no extra flags*/));
+ fFlags &= RTPATH_STR_F_STYLE_MASK;
+ if (fFlags == RTPATH_STR_F_STYLE_HOST)
+ fFlags = RTPATH_STYLE;
+ if (fFlags == RTPATH_STR_F_STYLE_DOS)
+ {
+ for (;; pwsz++)
+ {
+ switch (*pwsz)
+ {
+ /* handle separators. */
+ case ':':
+ case '\\':
+ case '/':
+ pwszName = pwsz + 1;
+ break;
+
+ /* the end */
+ case '\0':
+ if (*pwszName)
+ return (PRTUTF16)(void *)pwszName;
+ return NULL;
+ }
+ }
+ }
+ else
+ {
+ Assert(fFlags == RTPATH_STR_F_STYLE_UNIX);
+ for (;; pwsz++)
+ {
+ switch (*pwsz)
+ {
+ /* handle separators. */
+ case '/':
+ pwszName = pwsz + 1;
+ break;
+
+ /* the end */
+ case '\0':
+ if (*pwszName)
+ return (PRTUTF16)(void *)pwszName;
+ return NULL;
+ }
+ }
+ }
+
+ /* not reached */
+}
+RT_EXPORT_SYMBOL(RTPathFilenameExUtf16);
+
diff --git a/src/VBox/Runtime/common/path/RTPathFindCommon.cpp b/src/VBox/Runtime/common/path/RTPathFindCommon.cpp
new file mode 100644
index 00000000..626ac181
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathFindCommon.cpp
@@ -0,0 +1,127 @@
+/* $Id: RTPathFindCommon.cpp $ */
+/** @file
+ * IPRT - RTPathFindCommon implementations.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+
+#define RTPATH_TEMPLATE_CPP_H "RTPathFindCommon.cpp.h"
+#include "rtpath-expand-template.cpp.h"
+
+
+RTDECL(size_t) RTPathFindCommonEx(size_t cPaths, const char * const *papszPaths, uint32_t fFlags)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, RTPATHFINDCOMMON_F_IGNORE_DOTDOT), 0);
+ AssertReturn(cPaths > 0, 0);
+ AssertPtrReturn(papszPaths, 0);
+ size_t i = cPaths;
+ while (i-- > 0)
+ AssertPtrReturn(papszPaths[i], 0);
+
+ /*
+ * Duplicate papszPaths so we can have individual positions in each path.
+ * Use the stack if we haven't got too many paths.
+ */
+ void *pvFree;
+ const char **papszCopy;
+ size_t cbNeeded = cPaths * sizeof(papszCopy[0]);
+ if (cbNeeded <= _2K)
+ {
+ pvFree = NULL;
+ papszCopy = (const char **)alloca(cbNeeded);
+ }
+ else
+ {
+ pvFree = RTMemTmpAlloc(cbNeeded);
+ papszCopy = (const char **)pvFree;
+ }
+ AssertReturn(papszCopy, 0);
+ memcpy(papszCopy, papszPaths, cbNeeded);
+
+ /*
+ * Invoke the worker for the selected path style.
+ */
+ size_t cchRet;
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_DOS:
+ cchRet= rtPathFindCommonStyleDos(cPaths, papszCopy, fFlags);
+ break;
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_UNIX:
+ cchRet = rtPathFindCommonStyleUnix(cPaths, papszCopy, fFlags);
+ break;
+
+ default:
+ AssertFailedStmt(cchRet = 0); /* impossible */
+ }
+
+ /*
+ * Clean up and return.
+ */
+ if (pvFree)
+ RTMemTmpFree(pvFree);
+ return cchRet;
+}
+RT_EXPORT_SYMBOL(RTPathFindCommonEx);
+
+
+RTDECL(size_t) RTPathFindCommon(size_t cPaths, const char * const *papszPaths)
+{
+ return RTPathFindCommonEx(cPaths, papszPaths, RTPATH_STR_F_STYLE_HOST);
+}
+RT_EXPORT_SYMBOL(RTPathFindCommon);
+
diff --git a/src/VBox/Runtime/common/path/RTPathFindCommon.cpp.h b/src/VBox/Runtime/common/path/RTPathFindCommon.cpp.h
new file mode 100644
index 00000000..196a26a0
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathFindCommon.cpp.h
@@ -0,0 +1,267 @@
+/* $Id: RTPathFindCommon.cpp.h $ */
+/** @file
+ * IPRT - RTPathFindCommon - Code Template.
+ *
+ * This file included multiple times with different path style macros.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include "rtpath-root-length-template.cpp.h"
+
+
+/** Helper for skipping slashes, given a pointer to the first one. */
+DECLINLINE(const char *) RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(const char *pszSlash)
+{
+ for (;;)
+ {
+ char ch;
+ do
+ ch = *++pszSlash;
+ while (RTPATH_IS_SLASH(ch));
+
+ /* Also skip '/./' sequences. */
+ if ( ch != '.'
+ || !RTPATH_IS_SLASH(pszSlash[1]))
+ break;
+ pszSlash++;
+ }
+ return pszSlash;
+}
+
+
+static size_t RTPATH_STYLE_FN(rtPathFindCommon)(size_t cPaths, const char **papszPaths, uint32_t fFlags)
+{
+ /*
+ * Check for '..' elements before we start doing anything.
+ *
+ * They are currently not supported at all (lazy) and we shun them for
+ * security reasons. Iff we want to support them properly, we'd have to:
+ * 1. Note down exactly where the root specification ends for each of
+ * the paths so we can prevent '..' from messing with it.
+ * 2. When encountering '..', we'd have to ascend all paths.
+ * 3. When encountering a difference, we'd have to see if it's eliminated
+ * by a following '..' sequence.
+ * 4. When returning anything, we'd have to see if it could be affected by
+ * a '..' sequence later in any of the paths.
+ *
+ * We could kind of RTAbsPath the secondary paths, however it wouldn't work
+ * for the primary path we use as reference.
+ *
+ * Summa summarum: Annoyingly tedious, so just forget it.
+ */
+ if (!(fFlags & RTPATHFINDCOMMON_F_IGNORE_DOTDOT))
+ for (size_t i = 0; i < cPaths; i++)
+ {
+ const char * const psz = papszPaths[i];
+ const char *pszDot = strchr(psz, '.');
+ while (pszDot)
+ {
+ if ( pszDot[1] == '.'
+ && (RTPATH_IS_SLASH(pszDot[2]) || pszDot[2] == '\0')
+ && ( pszDot == psz
+ || RTPATH_IS_SLASH(pszDot[-1])
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ || (pszDot[-1] == ':' && psz + 2 == pszDot && !(fFlags & RTPATH_STR_F_NO_START))
+#endif
+ )
+ )
+ return 0;
+ pszDot = strchr(pszDot + 1, '.');
+ }
+ }
+
+ /*
+ * We use the first path as the reference for the return length.
+ */
+ const char * pszPath0 = papszPaths[0];
+ const char * pszPath0EndLastComp = pszPath0;
+ const char * const pszPath0Start = pszPath0;
+
+ /*
+ * Deal with root stuff as appropriate.
+ */
+ if (fFlags & RTPATH_STR_F_NO_START)
+ {
+ /* We ignore leading slashes when RTPATH_STR_F_NO_START is specified: */
+ for (size_t i = 0; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ papszPaths[i] = RTPATH_IS_SLASH(*psz) ? RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(psz) : psz;
+ }
+ pszPath0EndLastComp = pszPath0 = papszPaths[0];
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if (RTPATH_IS_SLASH(pszPath0[0]))
+ {
+ /* UNC requires a little bit of special magic to make sure we have
+ exactly two slashes in each path and don't mix things up. */
+ char ch;
+ if ( RTPATH_IS_SLASH(pszPath0[1])
+ && (ch = pszPath0[2]) != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ {
+ pszPath0 += 2;
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ if ( RTPATH_IS_SLASH(psz[0])
+ && RTPATH_IS_SLASH(psz[1])
+ && (ch = psz[2]) != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ papszPaths[i] = psz + 2;
+ else
+ return 0;
+ }
+ }
+ else
+ {
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ if ( RTPATH_IS_SLASH(psz[0])
+ && RTPATH_IS_SLASH(psz[1])
+ && (ch = psz[2]) != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ return 0;
+ if (!RTPATH_IS_SLASH(psz[0]))
+ return 0;
+ papszPaths[i] = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(psz);
+ }
+ pszPath0EndLastComp = pszPath0 = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(pszPath0);
+ }
+ }
+ /* Skip past the drive letter if there is one, as that eliminates the need
+ to handle ':' in the main loop below. */
+ else if ( RT_C_IS_ALPHA(pszPath0[0])
+ && pszPath0[1] == ':')
+ {
+ /* Drive letter part first: */
+ char const chDrv = RT_C_TO_UPPER(pszPath0[0]);
+ pszPath0 += 2;
+ pszPath0EndLastComp = pszPath0;
+
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ if ( ( psz[0] != chDrv
+ && RT_C_TO_UPPER(psz[0]) != chDrv)
+ || psz[1] != ':')
+ return 0;
+ papszPaths[i] = psz + 2;
+ }
+
+ /* Subsequent slashes or lack thereof. */
+ if (RTPATH_IS_SLASH(*pszPath0))
+ {
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ if (!RTPATH_IS_SLASH(*psz))
+ return pszPath0EndLastComp - pszPath0Start;
+ papszPaths[i] = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(psz);
+ }
+ pszPath0EndLastComp = pszPath0 = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(pszPath0);
+ }
+ else
+ for (size_t i = 1; i < cPaths; i++)
+ if (RTPATH_IS_SLASH(*papszPaths[i]))
+ return pszPath0EndLastComp - pszPath0Start;
+ }
+#endif
+
+ /*
+ * Main compare loop.
+ */
+ for (;;)
+ {
+ RTUNICP uc0;
+ int rc = RTStrGetCpEx(&pszPath0, &uc0);
+ AssertRCReturn(rc, 0);
+ if (!RTPATH_IS_SLASH(uc0))
+ {
+ if (uc0 != 0)
+ {
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ RTUNICP uc;
+ rc = RTStrGetCpEx(&papszPaths[i], &uc);
+ AssertRCReturn(rc, 0);
+ if (uc == uc0)
+ { /* likely */}
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if ( RTUniCpToUpper(uc) == RTUniCpToUpper(uc0)
+ || RTUniCpToLower(uc) == RTUniCpToLower(uc0))
+ { /* less likely */}
+#endif
+ else
+ return pszPath0EndLastComp - pszPath0Start;
+ }
+ }
+ else
+ {
+ /* pszPath0 is at an end. Check the state of the others as we must
+ return the whole pszPath0 length if their are also at the end of
+ at a slash. */
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ char ch = *papszPaths[i];
+ if ( ch != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ return pszPath0EndLastComp - pszPath0Start;
+ }
+ return pszPath0 - 1 - pszPath0Start;
+ }
+ }
+ else
+ {
+ /* pszPath0 is at a slash. Check whether all the other are too or are at
+ the end of the string. If any other string ends here, we can return
+ the length up to but not including the slash. */
+ bool fDone = false;
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ char ch = *papszPaths[i];
+ if (ch == '\0')
+ fDone = true;
+ else if (RTPATH_IS_SLASH(ch))
+ papszPaths[i] = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(papszPaths[i]);
+ else
+ return pszPath0EndLastComp - pszPath0Start;
+ }
+ if (fDone)
+ return pszPath0 - pszPath0Start;
+ pszPath0EndLastComp = pszPath0 = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(pszPath0 - 1);
+ }
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathGlob.cpp b/src/VBox/Runtime/common/path/RTPathGlob.cpp
new file mode 100644
index 00000000..46a634b2
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathGlob.cpp
@@ -0,0 +1,2177 @@
+/* $Id: RTPathGlob.cpp $ */
+/** @file
+ * IPRT - RTPathGlob
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* 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 = (uint16_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;
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathHasExt.cpp b/src/VBox/Runtime/common/path/RTPathHasExt.cpp
new file mode 100644
index 00000000..049f7d24
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathHasExt.cpp
@@ -0,0 +1,50 @@
+/* $Id: RTPathHasExt.cpp $ */
+/** @file
+ * IPRT - RTPathHasExt
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+
+RTDECL(bool) RTPathHasSuffix(const char *pszPath)
+{
+ return RTPathSuffix(pszPath) != NULL;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathHasPath.cpp b/src/VBox/Runtime/common/path/RTPathHasPath.cpp
new file mode 100644
index 00000000..77c1923e
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathHasPath.cpp
@@ -0,0 +1,61 @@
+/* $Id: RTPathHasPath.cpp $ */
+/** @file
+ * IPRT - RTPathHasPath
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/**
+ * Checks if a path includes more than a filename.
+ *
+ * @returns true if path present.
+ * @returns false if no path.
+ * @param pszPath Path to check.
+ */
+RTDECL(bool) RTPathHasPath(const char *pszPath)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ return strpbrk(pszPath, "/\\:") != NULL;
+#else
+ return strpbrk(pszPath, "/") != NULL;
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathJoin.cpp b/src/VBox/Runtime/common/path/RTPathJoin.cpp
new file mode 100644
index 00000000..a7b30ba8
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathJoin.cpp
@@ -0,0 +1,67 @@
+/* $Id: RTPathJoin.cpp $ */
+/** @file
+ * IPRT - RTPathJoin.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+
+
+RTDECL(int) RTPathJoin(char *pszPathDst, size_t cbPathDst, const char *pszPathSrc,
+ const char *pszAppend)
+{
+ AssertPtr(pszPathDst);
+ AssertPtr(pszPathSrc);
+ AssertPtr(pszAppend);
+
+ /*
+ * The easy way: Copy the path into the buffer and call RTPathAppend.
+ */
+ size_t cchPathSrc = strlen(pszPathSrc);
+ if (cchPathSrc >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pszPathDst, pszPathSrc, cchPathSrc + 1);
+
+ return RTPathAppend(pszPathDst, cbPathDst, pszAppend);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathJoinA.cpp b/src/VBox/Runtime/common/path/RTPathJoinA.cpp
new file mode 100644
index 00000000..04fe6787
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathJoinA.cpp
@@ -0,0 +1,83 @@
+/* $Id: RTPathJoinA.cpp $ */
+/** @file
+ * IPRT - RTPathJoinA.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+
+
+RTDECL(char *) RTPathJoinA(const char *pszPathSrc, const char *pszAppend)
+{
+ AssertPtr(pszAppend);
+ AssertPtr(pszPathSrc);
+
+ /*
+ * The easy way: Allocate a buffer and call RTPathAppend till it succeeds.
+ */
+ size_t cchPathSrc = strlen(pszPathSrc);
+ size_t cchAppend = strlen(pszAppend);
+ size_t cbPathDst = cchPathSrc + cchAppend + 4;
+ char *pszPathDst = RTStrAlloc(cbPathDst);
+ if (pszPathDst)
+ {
+ memcpy(pszPathDst, pszPathSrc, cchPathSrc + 1);
+ int rc = RTPathAppend(pszPathDst, cbPathDst, pszAppend);
+ if (RT_FAILURE(rc))
+ {
+ /* This shouldn't happen, but if it does try again with a larger buffer... */
+ AssertRC(rc);
+
+ rc = RTStrRealloc(&pszPathDst, cbPathDst * 2);
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(pszPathDst, cbPathDst, pszAppend);
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(pszPathDst);
+ pszPathDst = NULL;
+ }
+ }
+ }
+ return pszPathDst;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathJoinEx.cpp b/src/VBox/Runtime/common/path/RTPathJoinEx.cpp
new file mode 100644
index 00000000..c10c5934
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathJoinEx.cpp
@@ -0,0 +1,70 @@
+/* $Id: RTPathJoinEx.cpp $ */
+/** @file
+ * IPRT - RTPathJoinEx.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+
+
+RTDECL(int) RTPathJoinEx(char *pszPathDst, size_t cbPathDst,
+ const char *pszPathSrc, size_t cchPathSrcMax,
+ const char *pszAppend, size_t cchAppendMax, uint32_t fFlags)
+{
+ AssertPtr(pszPathDst);
+ AssertPtr(pszPathSrc);
+ AssertPtr(pszAppend);
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0));
+
+ /*
+ * The easy way: Copy the path into the buffer and call RTPathAppend.
+ */
+ size_t cchPathSrc = RTStrNLen(pszPathSrc, cchPathSrcMax);
+ if (cchPathSrc >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pszPathDst, pszPathSrc, cchPathSrc);
+ pszPathDst[cchPathSrc] = '\0';
+
+ return RTPathAppendEx(pszPathDst, cbPathDst, pszAppend, cchAppendMax, fFlags);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParentLength.cpp b/src/VBox/Runtime/common/path/RTPathParentLength.cpp
new file mode 100644
index 00000000..813e779e
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParentLength.cpp
@@ -0,0 +1,95 @@
+/* $Id: RTPathParentLength.cpp $ */
+/** @file
+ * IPRT - RTPathParentLength
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#define RTPATH_TEMPLATE_CPP_H "RTPathParentLength.cpp.h"
+#include "rtpath-expand-template.cpp.h"
+
+
+
+RTDECL(size_t) RTPathParentLengthEx(const char *pszPath, uint32_t fFlags)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(pszPath, 0);
+ AssertReturn(*pszPath, 0);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0), 0);
+ Assert(!(fFlags & RTPATH_STR_F_NO_END));
+
+ /*
+ * Invoke the worker for the selected path style.
+ */
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_DOS:
+ return rtPathParentLengthStyleDos(pszPath, fFlags);
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_UNIX:
+ return rtPathParentLengthStyleUnix(pszPath, fFlags);
+
+ default:
+ AssertFailedReturn(0); /* impossible */
+ }
+}
+
+
+RTDECL(size_t) RTPathParentLength(const char *pszPath)
+{
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ return rtPathParentLengthStyleDos(pszPath, 0);
+#else
+ return rtPathParentLengthStyleUnix(pszPath, 0);
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParentLength.cpp.h b/src/VBox/Runtime/common/path/RTPathParentLength.cpp.h
new file mode 100644
index 00000000..aebf823b
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParentLength.cpp.h
@@ -0,0 +1,68 @@
+/* $Id: RTPathParentLength.cpp.h $ */
+/** @file
+ * IPRT - RTPathParentLength - Code Template.
+ *
+ * This file included multiple times with different path style macros.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include "rtpath-root-length-template.cpp.h"
+
+/**
+ * @copydoc RTPathParentLengthEx
+ */
+static size_t RTPATH_STYLE_FN(rtPathParentLength)(const char *pszPath, uint32_t fFlags)
+{
+ /*
+ * Determin the length of the root component so we can make sure
+ * we don't try ascend higher than it.
+ */
+ size_t const cchRoot = RTPATH_STYLE_FN(rtPathRootLengthEx)(pszPath, fFlags);
+
+ /*
+ * Rewind to the start of the final component.
+ */
+ size_t cch = strlen(pszPath);
+
+ /* Trailing slashes: */
+ while (cch > cchRoot && RTPATH_IS_SLASH(pszPath[cch - 1]))
+ cch--;
+
+ /* The component: */
+ while (cch > cchRoot && !RTPATH_IS_SEP(pszPath[cch - 1]))
+ cch--;
+
+ /* Done! */
+ return cch;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParse.cpp b/src/VBox/Runtime/common/path/RTPathParse.cpp
new file mode 100644
index 00000000..cb400033
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParse.cpp
@@ -0,0 +1,85 @@
+/* $Id: RTPathParse.cpp $ */
+/** @file
+ * IPRT - RTPathParse
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#define RTPATH_TEMPLATE_CPP_H "RTPathParse.cpp.h"
+#include "rtpath-expand-template.cpp.h"
+
+
+RTDECL(int) RTPathParse(const char *pszPath, PRTPATHPARSED pParsed, size_t cbParsed, uint32_t fFlags)
+{
+ /*
+ * Input validation.
+ */
+ AssertReturn(cbParsed >= RT_UOFFSETOF(RTPATHPARSED, aComps), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pParsed, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(*pszPath, VERR_PATH_ZERO_LENGTH);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0), VERR_INVALID_FLAGS);
+
+ /*
+ * Invoke the worker for the selected path style.
+ */
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_DOS:
+ return rtPathParseStyleDos(pszPath, pParsed, cbParsed, fFlags);
+
+#if !defined(RT_OS_OS2) && !defined(RT_OS_WINDOWS)
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_UNIX:
+ return rtPathParseStyleUnix(pszPath, pParsed, cbParsed, fFlags);
+
+ default:
+ AssertFailedReturn(VERR_INVALID_FLAGS); /* impossible */
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParse.cpp.h b/src/VBox/Runtime/common/path/RTPathParse.cpp.h
new file mode 100644
index 00000000..083a2c98
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParse.cpp.h
@@ -0,0 +1,256 @@
+/* $Id: RTPathParse.cpp.h $ */
+/** @file
+ * IPRT - RTPathParse - Code Template.
+ *
+ * This file included multiple times with different path style macros.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+
+/**
+ * @copydoc RTPathParse
+ */
+static int RTPATH_STYLE_FN(rtPathParse)(const char *pszPath, PRTPATHPARSED pParsed, size_t cbParsed, uint32_t fFlags)
+{
+ /*
+ * Parse the root specification if present and initialize the parser state
+ * (keep it on the stack for speed).
+ */
+ uint32_t const cMaxComps = cbParsed < RT_UOFFSETOF(RTPATHPARSED, aComps[0xfff0])
+ ? (uint32_t)((cbParsed - RT_UOFFSETOF(RTPATHPARSED, aComps)) / sizeof(pParsed->aComps[0]))
+ : 0xfff0;
+ uint32_t idxComp = 0;
+ uint32_t cchPath;
+ uint32_t offCur;
+ uint16_t fProps;
+
+ if (RTPATH_IS_SLASH(pszPath[0]))
+ {
+ if (fFlags & RTPATH_STR_F_NO_START)
+ {
+ offCur = 1;
+ while (RTPATH_IS_SLASH(pszPath[offCur]))
+ offCur++;
+ if (!pszPath[offCur])
+ return VERR_PATH_ZERO_LENGTH;
+ fProps = RTPATH_PROP_RELATIVE | RTPATH_PROP_EXTRA_SLASHES;
+ cchPath = 0;
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if (RTPATH_IS_SLASH(pszPath[1]))
+ {
+ /* UNC - there are exactly two prefix slashes followed by a namespace
+ or computer name, which can be empty on windows. */
+ offCur = 2;
+ while (!RTPATH_IS_SLASH(pszPath[offCur]) && pszPath[offCur])
+ offCur++;
+
+ /* Special fun for windows. */
+ fProps = RTPATH_PROP_UNC | RTPATH_PROP_ABSOLUTE;
+ if ( offCur == 3
+ && (pszPath[2] == '.' || pszPath[2] == '?'))
+ fProps |= RTPATH_PROP_SPECIAL_UNC;
+
+ if (RTPATH_IS_SLASH(pszPath[offCur]))
+ {
+ fProps |= RTPATH_PROP_ROOT_SLASH;
+ offCur++;
+ }
+ cchPath = offCur;
+ }
+#endif
+ else
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ fProps = RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_RELATIVE;
+#else
+ fProps = RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_ABSOLUTE;
+#endif
+ offCur = 1;
+ cchPath = 1;
+ }
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if (RT_C_IS_ALPHA(pszPath[0]) && pszPath[1] == ':')
+ {
+ if (!RTPATH_IS_SLASH(pszPath[2]))
+ {
+ fProps = RTPATH_PROP_VOLUME | RTPATH_PROP_RELATIVE;
+ offCur = 2;
+ }
+ else
+ {
+ fProps = RTPATH_PROP_VOLUME | RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_ABSOLUTE;
+ offCur = 3;
+ }
+ cchPath = offCur;
+ }
+#endif
+ else
+ {
+ fProps = RTPATH_PROP_RELATIVE;
+ offCur = 0;
+ cchPath = 0;
+ }
+
+ /* Add it to the component array . */
+ if (offCur && !(fFlags & RTPATH_STR_F_NO_START))
+ {
+ cchPath = offCur;
+ if (idxComp < cMaxComps)
+ {
+ pParsed->aComps[idxComp].off = 0;
+ pParsed->aComps[idxComp].cch = offCur;
+ }
+ idxComp++;
+
+ /* Skip unnecessary slashes following the root-spec. */
+ if (RTPATH_IS_SLASH(pszPath[offCur]))
+ {
+ fProps |= RTPATH_PROP_EXTRA_SLASHES;
+ do
+ offCur++;
+ while (RTPATH_IS_SLASH(pszPath[offCur]));
+ }
+ }
+
+ /*
+ * Parse the rest.
+ */
+ if (pszPath[offCur])
+ {
+ for (;;)
+ {
+ Assert(!RTPATH_IS_SLASH(pszPath[offCur]));
+
+ /* Find the end of the component. */
+ uint32_t offStart = offCur;
+ char ch;
+ while ((ch = pszPath[offCur]) != '\0' && !RTPATH_IS_SLASH(ch))
+ offCur++;
+ if (offCur >= _64K)
+ return VERR_FILENAME_TOO_LONG;
+
+ /* Add it. */
+ uint32_t cchComp = offCur - offStart;
+ if (idxComp < cMaxComps)
+ {
+ pParsed->aComps[idxComp].off = offStart;
+ pParsed->aComps[idxComp].cch = cchComp;
+ }
+ idxComp++;
+ cchPath += cchComp;
+
+ /* Look for '.' and '..' references. */
+ if (cchComp == 1 && pszPath[offCur - 1] == '.')
+ fProps |= RTPATH_PROP_DOT_REFS;
+ else if (cchComp == 2 && pszPath[offCur - 1] == '.' && pszPath[offCur - 2] == '.')
+ {
+ fProps &= ~RTPATH_PROP_ABSOLUTE;
+ fProps |= RTPATH_PROP_DOTDOT_REFS | RTPATH_PROP_RELATIVE;
+ }
+
+ /* Skip unnecessary slashes. Leave ch unchanged! */
+ char ch2 = ch;
+ if (ch2)
+ {
+ ch2 = pszPath[++offCur];
+ if (RTPATH_IS_SLASH(ch2))
+ {
+ fProps |= RTPATH_PROP_EXTRA_SLASHES;
+ do
+ ch2 = pszPath[++offCur];
+ while (RTPATH_IS_SLASH(ch2));
+ }
+ }
+
+ /* The end? */
+ if (ch2 == '\0')
+ {
+ pParsed->offSuffix = offCur;
+ pParsed->cchSuffix = 0;
+ if (ch)
+ {
+ if (!(fFlags & RTPATH_STR_F_NO_END))
+ {
+ fProps |= RTPATH_PROP_DIR_SLASH; /* (not counted in component, but in cchPath) */
+ cchPath++;
+ }
+ else
+ fProps |= RTPATH_PROP_EXTRA_SLASHES;
+ }
+ else if (!(fFlags & RTPATH_STR_F_NO_END))
+ {
+ fProps |= RTPATH_PROP_FILENAME;
+
+ /* Look for a suffix: */
+ uint32_t offSuffix = offStart + cchComp;
+ while (--offSuffix > offStart)
+ if (pszPath[offSuffix] == '.')
+ {
+ uint32_t cchSuffix = offStart + cchComp - offSuffix;
+ if (cchSuffix > 1)
+ {
+ pParsed->cchSuffix = cchSuffix;
+ pParsed->offSuffix = offSuffix;
+ fProps |= RTPATH_PROP_SUFFIX;
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ /* No, not the end. Account for an separator before we restart the loop. */
+ cchPath += sizeof(RTPATH_SLASH_STR) - 1;
+ }
+ }
+ else
+ {
+ pParsed->offSuffix = offCur;
+ pParsed->cchSuffix = 0;
+ }
+ if (offCur >= _64K)
+ return VERR_FILENAME_TOO_LONG;
+
+ /*
+ * Store the remainder of the state and we're done.
+ */
+ pParsed->fProps = fProps;
+ pParsed->cchPath = cchPath;
+ pParsed->cComps = idxComp;
+
+ return idxComp <= cMaxComps ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParseSimple.cpp b/src/VBox/Runtime/common/path/RTPathParseSimple.cpp
new file mode 100644
index 00000000..ba3cd17d
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParseSimple.cpp
@@ -0,0 +1,144 @@
+/* $Id: RTPathParseSimple.cpp $ */
+/** @file
+ * IPRT - RTPathParseSimple
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+
+
+RTDECL(size_t) RTPathParseSimple(const char *pszPath, size_t *pcchDir, ssize_t *poffName, ssize_t *poffSuff)
+{
+ /*
+ * First deal with the root as it is always more fun that you'd think.
+ */
+ const char *psz = pszPath;
+ size_t cchRoot = 0;
+
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ if (RT_C_IS_ALPHA(*psz) && RTPATH_IS_VOLSEP(psz[1]))
+ {
+ /* Volume specifier. */
+ cchRoot = 2;
+ psz += 2;
+ }
+ else if (RTPATH_IS_SLASH(*psz) && RTPATH_IS_SLASH(psz[1]))
+ {
+ /* UNC - there are exactly two prefix slashes followed by a namespace
+ or computer name, which can be empty on windows. */
+ cchRoot = 2;
+ psz += 2;
+ while (!RTPATH_IS_SLASH(*psz) && *psz)
+ {
+ cchRoot++;
+ psz++;
+ }
+ }
+#endif
+ while (RTPATH_IS_SLASH(*psz))
+ {
+ cchRoot++;
+ psz++;
+ }
+
+ /*
+ * Do the remainder.
+ */
+ const char *pszName = psz;
+ const char *pszLastDot = NULL;
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ default:
+ break;
+
+ /* handle separators. */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ case '\\':
+#endif
+ case '/':
+ pszName = psz + 1;
+ pszLastDot = NULL;
+ break;
+
+ case '.':
+ pszLastDot = psz;
+ break;
+
+ /*
+ * The end. Complete the results.
+ */
+ case '\0':
+ {
+ ssize_t offName = *pszName != '\0' ? pszName - pszPath : -1;
+ if (poffName)
+ *poffName = offName;
+
+ if (poffSuff)
+ {
+ ssize_t offSuff = -1;
+ if ( pszLastDot
+ && pszLastDot != pszName
+ && pszLastDot[1] != '\0')
+ {
+ offSuff = pszLastDot - pszPath;
+ Assert(offSuff > offName);
+ }
+ *poffSuff = offSuff;
+ }
+
+ if (pcchDir)
+ {
+ size_t cch = offName < 0 ? psz - pszPath : offName - 1 < (ssize_t)cchRoot ? cchRoot : offName - 1;
+ while (cch > cchRoot && RTPATH_IS_SLASH(pszPath[cch - 1]))
+ cch--;
+ *pcchDir = cch;
+ }
+
+ return psz - pszPath;
+ }
+ }
+ }
+
+ /* will never get here */
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParsedReassemble.cpp b/src/VBox/Runtime/common/path/RTPathParsedReassemble.cpp
new file mode 100644
index 00000000..4a814943
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParsedReassemble.cpp
@@ -0,0 +1,161 @@
+/* $Id: RTPathParsedReassemble.cpp $ */
+/** @file
+ * IPRT - RTPathParsedReassemble.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTPathParsedReassemble(const char *pszSrcPath, PRTPATHPARSED pParsed, uint32_t fFlags,
+ char *pszDstPath, size_t cbDstPath)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(pszSrcPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(pParsed, VERR_INVALID_POINTER);
+ AssertReturn(pParsed->cComps > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0) && !(fFlags & RTPATH_STR_F_MIDDLE), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszDstPath, VERR_INVALID_POINTER);
+
+ /*
+ * Recalculate the length.
+ */
+ uint32_t const cComps = pParsed->cComps;
+ uint32_t idxComp = 0;
+ uint32_t cchPath = 0;
+ if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
+ {
+ cchPath = pParsed->aComps[0].cch;
+ idxComp++;
+ }
+ bool fNeedSlash = false;
+ while (idxComp < cComps)
+ {
+ uint32_t const cchComp = pParsed->aComps[idxComp].cch;
+ idxComp++;
+ if (cchComp > 0)
+ {
+ cchPath += cchComp + fNeedSlash;
+ fNeedSlash = true;
+ }
+ }
+ if ((pParsed->fProps & RTPATH_PROP_DIR_SLASH) && fNeedSlash)
+ cchPath += 1;
+ pParsed->cchPath = cchPath;
+ if (cbDstPath > cchPath)
+ { /* likely */ }
+ else
+ {
+ if (cbDstPath)
+ *pszDstPath = '\0';
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /*
+ * Figure which slash to use.
+ */
+ char chSlash;
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+ case RTPATH_STR_F_STYLE_HOST:
+ chSlash = RTPATH_SLASH;
+ break;
+
+ case RTPATH_STR_F_STYLE_DOS:
+ chSlash = '\\';
+ break;
+
+ case RTPATH_STR_F_STYLE_UNIX:
+ chSlash = '/';
+ break;
+
+ default:
+ AssertFailedReturn(VERR_INVALID_FLAGS); /* impossible */
+ }
+
+ /*
+ * Do the joining.
+ * Note! Using memmove here as we want to support pszSrcPath == pszDstPath.
+ */
+ char *pszDst = pszDstPath;
+ idxComp = 0;
+ fNeedSlash = false;
+
+ if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
+ {
+ uint32_t cchComp = pParsed->aComps[0].cch;
+ memmove(pszDst, &pszSrcPath[pParsed->aComps[0].off], cchComp);
+
+ /* fix the slashes (harmless for unix style) */
+ char chOtherSlash = chSlash == '\\' ? '/' : '\\';
+ while (cchComp-- > 0)
+ {
+ if (*pszDst == chOtherSlash)
+ *pszDst = chSlash;
+ pszDst++;
+ }
+ idxComp = 1;
+ }
+
+ while (idxComp < cComps)
+ {
+ uint32_t const cchComp = pParsed->aComps[idxComp].cch;
+ if (cchComp > 0)
+ {
+ if (fNeedSlash)
+ *pszDst++ = chSlash;
+ fNeedSlash = true;
+ memmove(pszDst, &pszSrcPath[pParsed->aComps[idxComp].off], cchComp);
+ pszDst += cchComp;
+ }
+ idxComp++;
+ }
+
+ if ((pParsed->fProps & RTPATH_PROP_DIR_SLASH) && fNeedSlash)
+ *pszDst++ = chSlash;
+ *pszDst = '\0';
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathPurgeFilename.cpp b/src/VBox/Runtime/common/path/RTPathPurgeFilename.cpp
new file mode 100644
index 00000000..9f343c9a
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathPurgeFilename.cpp
@@ -0,0 +1,131 @@
+/* $Id: RTPathPurgeFilename.cpp $ */
+/** @file
+ * IPRT - RTPathPurgeFilename
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Character mappings for translating a string into a valid Windows or OS/2 filename. */
+static const unsigned char g_auchWinOs2Map[256] =
+{
+ 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 32, 33, 95, 35, 36, 37, 38, 39, 40, 41, 95, 43, 44, 45, 46, 95, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 95, 59, 95, 61, 95, 95,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 95, 93, 94, 95,
+ 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123, 95,125,126, 95,
+ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
+ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
+ 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
+};
+
+/* Program for generating the table:
+#include <stdio.h>
+#include <string.h>
+int main()
+{
+ int i;
+ printf("static const unsigned char g_auchWinOs2Map[256] =\n"
+ "{");
+ for (i = 0; i < 256; i++)
+ {
+ int ch = i;
+ if ( i <= 31
+ || i == 127
+ || (i < 127 && strchr("/\\*:<>?|\"", (char)i) != NULL))
+ ch = i > 0 ? '_' : 0;
+ if (i == 0)
+ printf("\n %3d", ch);
+ else if ((i % 32) == 0)
+ printf(",\n %3d", ch);
+ else
+ printf(",%3d", ch);
+ }
+ printf("\n"
+ "};\n");
+ return 0;
+}
+*/
+
+
+RTDECL(char *) RTPathPurgeFilename(char *pszString, uint32_t fFlags)
+{
+ AssertPtrReturn(pszString, NULL);
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0));
+
+ /*
+ * Take action according to the style after first resolving the host style.
+ */
+ if ((fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_HOST)
+ fFlags = (fFlags & ~RTPATH_STR_F_STYLE_MASK) | RTPATH_STYLE;
+ if ((fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_DOS)
+ {
+ /*
+ * Produce a filename valid on Windows and OS/2.
+ * Here all control characters (including tab) and path separators needs to
+ * be replaced, in addition to a scattering of other ones.
+ */
+ unsigned char *puch = (unsigned char *)pszString;
+ uintptr_t uch;
+ while ((uch = *puch))
+ *puch++ = g_auchWinOs2Map[uch];
+ }
+ else
+ {
+ /*
+ * Produce a filename valid on a (typical) Unix system.
+ * Here only the '/' needs to be replaced.
+ */
+ Assert((fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_UNIX);
+ char *pszSlash = strchr(pszString, '/');
+ while (pszSlash != NULL)
+ {
+ *pszSlash = '_';
+ pszSlash = strchr(pszSlash + 1, '/');
+ }
+ }
+ return pszString;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathRealDup.cpp b/src/VBox/Runtime/common/path/RTPathRealDup.cpp
new file mode 100644
index 00000000..66964c39
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathRealDup.cpp
@@ -0,0 +1,57 @@
+/* $Id: RTPathRealDup.cpp $ */
+/** @file
+ * IPRT - RTPathRealDup
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/errcore.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(char *) RTPathRealDup(const char *pszPath)
+{
+ char szPath[RTPATH_MAX];
+ int rc = RTPathReal(pszPath, szPath, sizeof(szPath));
+ if (RT_SUCCESS(rc))
+ return RTStrDup(szPath);
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathRmCmd.cpp b/src/VBox/Runtime/common/path/RTPathRmCmd.cpp
new file mode 100644
index 00000000..c5184b1e
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathRmCmd.cpp
@@ -0,0 +1,658 @@
+/* $Id: RTPathRmCmd.cpp $ */
+/** @file
+ * IPRT - RM Command.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/path.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/dir.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/symlink.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTPATHRMCMD_OPT_INTERACTIVE 1000
+#define RTPATHRMCMD_OPT_ONE_FILE_SYSTEM 1001
+#define RTPATHRMCMD_OPT_PRESERVE_ROOT 1002
+#define RTPATHRMCMD_OPT_NO_PRESERVE_ROOT 1003
+#define RTPATHRMCMD_OPT_MACHINE_READABLE 1004
+
+/** The max directory entry size. */
+#define RTPATHRM_DIR_MAX_ENTRY_SIZE (sizeof(RTDIRENTRYEX) + 4096)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Interactive option. */
+typedef enum
+{
+ RTPATHRMCMDINTERACTIVE_NONE = 1,
+ RTPATHRMCMDINTERACTIVE_ALL,
+ RTPATHRMCMDINTERACTIVE_ONCE
+ /** @todo possible that we should by default prompt if removing read-only
+ * files or files owned by someone else. We currently don't. */
+} RTPATHRMCMDINTERACTIVE;
+
+/**
+ * IPRT rm option structure.
+ */
+typedef struct RTPATHRMCMDOPTS
+{
+ /** Whether to delete recursively. */
+ bool fRecursive;
+ /** Whether to delete directories as well as other kinds of files. */
+ bool fDirsAndOther;
+ /** Whether to remove files without prompting and ignoring non-existing
+ * files. */
+ bool fForce;
+ /** Machine readable output. */
+ bool fMachineReadable;
+ /** Don't try remove root ('/') if set, otherwise don't treat root specially. */
+ bool fPreserveRoot;
+ /** Whether to keep to one file system. */
+ bool fOneFileSystem;
+ /** Whether to safely delete files (overwrite 3x before unlinking). */
+ bool fSafeDelete;
+ /** Whether to be verbose about the operation. */
+ bool fVerbose;
+ /** The interactive setting. */
+ RTPATHRMCMDINTERACTIVE enmInteractive;
+} RTPATHRMCMDOPTS;
+/** Pointer to the IPRT rm options. */
+typedef RTPATHRMCMDOPTS *PRTPATHRMCMDOPTS;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** A bunch of zeros. */
+static uint8_t const g_abZeros[16384] = { 0 };
+/** A bunch of 0xFF bytes. (lazy init) */
+static uint8_t g_ab0xFF[16384];
+
+
+static void rtPathRmVerbose(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ if (!pOpts->fMachineReadable)
+ RTPrintf("%s\n", pszPath);
+}
+
+
+static int rtPathRmError(PRTPATHRMCMDOPTS pOpts, const char *pszPath, int rc,
+ const char *pszFormat, ...)
+{
+ if (pOpts->fMachineReadable)
+ RTPrintf("fname=%s%crc=%d%c", pszPath, 0, rc, 0);
+ else
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ }
+ return rc;
+}
+
+
+/**
+ * Worker that removes a symbolic link.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the symbolic link.
+ */
+static int rtPathRmOneSymlink(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ if (pOpts->fVerbose)
+ rtPathRmVerbose(pOpts, pszPath);
+ int rc = RTSymlinkDelete(pszPath, 0);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Error removing symbolic link '%s': %Rrc\n", pszPath, rc);
+ return rc;
+}
+
+
+/**
+ * Worker that removes a file.
+ *
+ * Currently used to delete both regular and special files.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the file.
+ * @param pObjInfo The FS object info for the file.
+ */
+static int rtPathRmOneFile(PRTPATHRMCMDOPTS pOpts, const char *pszPath, PRTFSOBJINFO pObjInfo)
+{
+ int rc;
+ if (pOpts->fVerbose)
+ rtPathRmVerbose(pOpts, pszPath);
+
+ /*
+ * Wipe the file if requested and possible.
+ */
+ if (pOpts->fSafeDelete && RTFS_IS_FILE(pObjInfo->Attr.fMode))
+ {
+ /* Lazy init of the 0xff buffer. */
+ if (g_ab0xFF[0] != 0xff || g_ab0xFF[sizeof(g_ab0xFF) - 1] != 0xff)
+ memset(g_ab0xFF, 0xff, sizeof(g_ab0xFF));
+
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszPath, RTFILE_O_WRITE);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Opening '%s' for overwriting: %Rrc\n", pszPath, rc);
+
+ for (unsigned iPass = 0; iPass < 3; iPass++)
+ {
+ uint8_t const *pabFiller = iPass == 1 ? g_abZeros : g_ab0xFF;
+ size_t const cbFiller = iPass == 1 ? sizeof(g_abZeros) : sizeof(g_ab0xFF);
+
+ rc = RTFileSeek(hFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = rtPathRmError(pOpts, pszPath, rc, "Error seeking to start of '%s': %Rrc\n", pszPath, rc);
+ break;
+ }
+ for (RTFOFF cbLeft = pObjInfo->cbObject; cbLeft > 0; cbLeft -= cbFiller)
+ {
+ size_t cbToWrite = cbFiller;
+ if (cbLeft < (RTFOFF)cbToWrite)
+ cbToWrite = (size_t)cbLeft;
+ rc = RTFileWrite(hFile, pabFiller, cbToWrite, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = rtPathRmError(pOpts, pszPath, rc, "Error writing to '%s': %Rrc\n", pszPath, rc);
+ break;
+ }
+ }
+ }
+
+ int rc2 = RTFileClose(hFile);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ return rtPathRmError(pOpts, pszPath, rc2, "Closing '%s' failed: %Rrc\n", pszPath, rc);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Remove the file.
+ */
+ rc = RTFileDelete(pszPath);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc,
+ RTFS_IS_FILE(pObjInfo->Attr.fMode)
+ ? "Error removing regular file '%s': %Rrc\n"
+ : "Error removing special file '%s': %Rrc\n",
+ pszPath, rc);
+ return rc;
+}
+
+
+/**
+ * Deletes one directory (if it's empty).
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the directory.
+ */
+static int rtPathRmOneDir(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ if (pOpts->fVerbose)
+ rtPathRmVerbose(pOpts, pszPath);
+
+ int rc = RTDirRemove(pszPath);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Error removing directory '%s': %Rrc", pszPath, rc);
+ return rc;
+}
+
+
+/**
+ * Recursively delete a directory.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath Pointer to a writable buffer holding the path to
+ * the directory.
+ * @param cchPath The length of the path (avoid strlen).
+ * @param pDirEntry Pointer to a directory entry buffer that is
+ * RTPATHRM_DIR_MAX_ENTRY_SIZE bytes big.
+ */
+static int rtPathRmRecursive(PRTPATHRMCMDOPTS pOpts, char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry)
+{
+ /*
+ * Make sure the path ends with a slash.
+ */
+ if (!cchPath || !RTPATH_IS_SLASH(pszPath[cchPath - 1]))
+ {
+ if (cchPath + 1 >= RTPATH_MAX)
+ return rtPathRmError(pOpts, pszPath, VERR_BUFFER_OVERFLOW, "Buffer overflow fixing up '%s'.\n", pszPath);
+ pszPath[cchPath++] = RTPATH_SLASH;
+ pszPath[cchPath] = '\0';
+ }
+
+ /*
+ * Traverse the directory.
+ */
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, pszPath);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Error opening directory '%s': %Rrc", pszPath, rc);
+ int rcRet = VINF_SUCCESS;
+ for (;;)
+ {
+ /*
+ * Read the next entry, constructing an full path for it.
+ */
+ size_t cbEntry = RTPATHRM_DIR_MAX_ENTRY_SIZE;
+ rc = RTDirReadEx(hDir, pDirEntry, &cbEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (rc == VERR_NO_MORE_FILES)
+ {
+ /*
+ * Reached the end of the directory.
+ */
+ pszPath[cchPath] = '\0';
+ rc = RTDirClose(hDir);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Error closing directory '%s': %Rrc", pszPath, rc);
+
+ /* Delete the directory. */
+ int rc2 = rtPathRmOneDir(pOpts, pszPath);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rcRet))
+ return rc2;
+ return rcRet;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ rc = rtPathRmError(pOpts, pszPath, rc, "Error reading directory '%s': %Rrc", pszPath, rc);
+ break;
+ }
+
+ /* Skip '.' and '..'. */
+ if ( pDirEntry->szName[0] == '.'
+ && ( pDirEntry->cbName == 1
+ || ( pDirEntry->cbName == 2
+ && pDirEntry->szName[1] == '.')))
+ continue;
+
+ /* Construct full path. */
+ if (cchPath + pDirEntry->cbName >= RTPATH_MAX)
+ {
+ pszPath[cchPath] = '\0';
+ rc = rtPathRmError(pOpts, pszPath, VERR_BUFFER_OVERFLOW, "Path buffer overflow in directory '%s'.", pszPath);
+ break;
+ }
+ memcpy(pszPath + cchPath, pDirEntry->szName, pDirEntry->cbName + 1);
+
+ /*
+ * Take action according to the type.
+ */
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ rc = rtPathRmOneFile(pOpts, pszPath, &pDirEntry->Info);
+ break;
+
+ case RTFS_TYPE_DIRECTORY:
+ rc = rtPathRmRecursive(pOpts, pszPath, cchPath + pDirEntry->cbName, pDirEntry);
+ break;
+
+ case RTFS_TYPE_SYMLINK:
+ rc = rtPathRmOneSymlink(pOpts, pszPath);
+ break;
+
+ case RTFS_TYPE_FIFO:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_SOCKET:
+ rc = rtPathRmOneFile(pOpts, pszPath, &pDirEntry->Info);
+ break;
+
+ case RTFS_TYPE_WHITEOUT:
+ default:
+ rc = rtPathRmError(pOpts, pszPath, VERR_UNEXPECTED_FS_OBJ_TYPE,
+ "Object '%s' has an unknown file type: %o\n",
+ pszPath, pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK);
+ break;
+ }
+ if (RT_FAILURE(rc) && RT_SUCCESS(rcRet))
+ rcRet = rc;
+ }
+
+ /*
+ * Some error occured, close and return.
+ */
+ RTDirClose(hDir);
+ return rc;
+}
+
+/**
+ * Validates the specified file or directory.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the file, directory, whatever.
+ */
+static int rtPathRmOneValidate(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ /*
+ * RTPathFilename doesn't do the trailing slash thing the way we need it to.
+ * E.g. both '..' and '../' should be rejected.
+ */
+ size_t cchPath = strlen(pszPath);
+ while (cchPath > 0 && RTPATH_IS_SLASH(pszPath[cchPath - 1]))
+ cchPath--;
+
+ if ( ( cchPath == 0
+ || 0 /** @todo drive letter + UNC crap */)
+ && pOpts->fPreserveRoot)
+ return rtPathRmError(pOpts, pszPath, VERR_CANT_DELETE_DIRECTORY, "Cannot remove root directory ('%s').\n", pszPath);
+
+ size_t offLast = cchPath - 1;
+ while (offLast > 0 && !RTPATH_IS_SEP(pszPath[offLast - 1]))
+ offLast--;
+
+ size_t cchLast = cchPath - offLast;
+ if ( pszPath[offLast] == '.'
+ && ( cchLast == 1
+ || (cchLast == 2 && pszPath[offLast + 1] == '.')))
+ return rtPathRmError(pOpts, pszPath, VERR_CANT_DELETE_DIRECTORY, "Cannot remove special directory '%s'.\n", pszPath);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Remove one user specified file or directory.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the file, directory, whatever.
+ */
+static int rtPathRmOne(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ /*
+ * RM refuses to delete some directories.
+ */
+ int rc = rtPathRmOneValidate(pOpts, pszPath);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Query file system object info.
+ */
+ RTFSOBJINFO ObjInfo;
+ rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ {
+ if (pOpts->fForce && (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND))
+ return VINF_SUCCESS;
+ return rtPathRmError(pOpts, pszPath, rc, "Error deleting '%s': %Rrc", pszPath, rc);
+ }
+
+ /*
+ * Take type specific action.
+ */
+ switch (ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ return rtPathRmOneFile(pOpts, pszPath, &ObjInfo);
+
+ case RTFS_TYPE_DIRECTORY:
+ if (pOpts->fRecursive)
+ {
+ char szPath[RTPATH_MAX];
+ rc = RTPathAbs(pszPath, szPath, sizeof(szPath));
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "RTPathAbs failed on '%s': %Rrc\n", pszPath, rc);
+
+ union
+ {
+ RTDIRENTRYEX Core;
+ uint8_t abPadding[RTPATHRM_DIR_MAX_ENTRY_SIZE];
+ } DirEntry;
+
+ return rtPathRmRecursive(pOpts, szPath, strlen(szPath), &DirEntry.Core);
+ }
+ if (pOpts->fDirsAndOther)
+ return rtPathRmOneDir(pOpts, pszPath);
+ return rtPathRmError(pOpts, pszPath, VERR_IS_A_DIRECTORY, "Cannot remove '%s': %Rrc\n", pszPath, VERR_IS_A_DIRECTORY);
+
+ case RTFS_TYPE_SYMLINK:
+ return rtPathRmOneSymlink(pOpts, pszPath);
+
+ case RTFS_TYPE_FIFO:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_SOCKET:
+ return rtPathRmOneFile(pOpts, pszPath, &ObjInfo);
+
+ case RTFS_TYPE_WHITEOUT:
+ default:
+ return rtPathRmError(pOpts, pszPath, VERR_UNEXPECTED_FS_OBJ_TYPE,
+ "Object '%s' has an unknown file type: %o\n", pszPath, ObjInfo.Attr.fMode & RTFS_TYPE_MASK);
+
+ }
+}
+
+
+RTDECL(RTEXITCODE) RTPathRmCmd(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--dirs-and-more", 'd', RTGETOPT_REQ_NOTHING },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ { "--prompt", 'i', RTGETOPT_REQ_NOTHING },
+ { "--prompt-once", 'I', RTGETOPT_REQ_NOTHING },
+ { "--interactive", RTPATHRMCMD_OPT_INTERACTIVE, RTGETOPT_REQ_STRING },
+ { "--one-file-system", RTPATHRMCMD_OPT_ONE_FILE_SYSTEM, RTGETOPT_REQ_NOTHING },
+ { "--preserve-root", RTPATHRMCMD_OPT_PRESERVE_ROOT, RTGETOPT_REQ_NOTHING },
+ { "--no-preserve-root", RTPATHRMCMD_OPT_NO_PRESERVE_ROOT, RTGETOPT_REQ_NOTHING },
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--recursive", 'r', RTGETOPT_REQ_NOTHING },
+ { "--safe-delete", 'P', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+
+ /* IPRT extensions */
+ { "--machine-readable", RTPATHRMCMD_OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--machinereadable", RTPATHRMCMD_OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, /* bad long option style */
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTPATHRMCMDOPTS Opts;
+ RT_ZERO(Opts);
+ Opts.fPreserveRoot = true;
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_NONE;
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'd':
+ Opts.fDirsAndOther = true;
+ break;
+
+ case 'f':
+ Opts.fForce = true;
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_NONE;
+ break;
+
+ case 'i':
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_ALL;
+ break;
+
+ case 'I':
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_ONCE;
+ break;
+
+ case RTPATHRMCMD_OPT_INTERACTIVE:
+ if (!strcmp(ValueUnion.psz, "always"))
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_ALL;
+ else if (!strcmp(ValueUnion.psz, "once"))
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_ONCE;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown --interactive option value: '%s'\n", ValueUnion.psz);
+ break;
+
+ case RTPATHRMCMD_OPT_ONE_FILE_SYSTEM:
+ Opts.fOneFileSystem = true;
+ break;
+
+ case RTPATHRMCMD_OPT_PRESERVE_ROOT:
+ Opts.fPreserveRoot = true;
+ break;
+
+ case RTPATHRMCMD_OPT_NO_PRESERVE_ROOT:
+ Opts.fPreserveRoot = false;
+ break;
+
+ case 'R':
+ case 'r':
+ Opts.fRecursive = true;
+ Opts.fDirsAndOther = true;
+ break;
+
+ case 'P':
+ Opts.fSafeDelete = true;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case RTPATHRMCMD_OPT_MACHINE_READABLE:
+ Opts.fMachineReadable = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ if (RT_C_IS_PRINT(s_aOptions[i].iShort))
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ else
+ RTPrintf(" %s\n", s_aOptions[i].pszLong);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Options we don't support.
+ */
+ if (Opts.fOneFileSystem)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The --one-file-system option is not yet implemented.\n");
+ if (Opts.enmInteractive != RTPATHRMCMDINTERACTIVE_NONE)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The -i, -I and --interactive options are not implemented yet.\n");
+
+ /*
+ * No files means error.
+ */
+ if (rc != VINF_GETOPT_NOT_OPTION && !Opts.fForce)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No files or directories specified.\n");
+
+ /*
+ * Machine readable init + header.
+ */
+ if (Opts.fMachineReadable)
+ {
+ int rc2 = RTStrmSetMode(g_pStdOut, true /*fBinary*/, false /*fCurrentCodeSet*/);
+ if (RT_FAILURE(rc2))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTStrmSetMode failed: %Rrc.\n", rc2);
+ static const char s_achHeader[] = "hdr_id=rm\0hdr_ver=1";
+ RTStrmWrite(g_pStdOut, s_achHeader, sizeof(s_achHeader));
+ }
+
+ /*
+ * Delete the specified files/dirs/whatever.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ while (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ rc = rtPathRmOne(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ /* next */
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+ if (rc != 0)
+ rcExit = RTGetOptPrintError(rc, &ValueUnion);
+
+ /*
+ * Terminate the machine readable stuff.
+ */
+ if (Opts.fMachineReadable)
+ {
+ RTStrmWrite(g_pStdOut, "\0\0\0", 4);
+ rc = RTStrmFlush(g_pStdOut);
+ if (RT_FAILURE(rc) && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathSkipRootSpec.cpp b/src/VBox/Runtime/common/path/RTPathSkipRootSpec.cpp
new file mode 100644
index 00000000..999ec147
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathSkipRootSpec.cpp
@@ -0,0 +1,51 @@
+/* $Id: RTPathSkipRootSpec.cpp $ */
+/** @file
+ * IPRT - RTPathSkipRootSpec
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include "internal/path.h"
+
+
+RTDECL(char *) RTPathSkipRootSpec(const char *pszPath)
+{
+ return (char *)&pszPath[rtPathRootSpecLen(pszPath)];
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathSplit.cpp b/src/VBox/Runtime/common/path/RTPathSplit.cpp
new file mode 100644
index 00000000..a3253ed3
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathSplit.cpp
@@ -0,0 +1,143 @@
+/* $Id: RTPathSplit.cpp $ */
+/** @file
+ * IPRT - RTPathSplit
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(int) RTPathSplit(const char *pszPath, PRTPATHSPLIT pSplit, size_t cbSplit, uint32_t fFlags)
+{
+ /*
+ * Input validation.
+ */
+ AssertReturn(cbSplit >= RT_UOFFSETOF(RTPATHSPLIT, apszComps), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pSplit, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(*pszPath, VERR_PATH_ZERO_LENGTH);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0), VERR_INVALID_FLAGS);
+
+ /*
+ * Use RTPathParse to do the parsing.
+ * - This makes the ASSUMPTION that the output of this function is greater
+ * or equal to that of RTPathParsed.
+ * - We're aliasing the buffer here, so use volatile to avoid issues due to
+ * compiler optimizations.
+ */
+ RTPATHPARSED volatile *pParsedVolatile = (RTPATHPARSED volatile *)pSplit;
+ RTPATHSPLIT volatile *pSplitVolatile = (RTPATHSPLIT volatile *)pSplit;
+
+ AssertCompile(sizeof(*pParsedVolatile) <= sizeof(*pSplitVolatile));
+ AssertCompile(sizeof(pParsedVolatile->aComps[0]) <= sizeof(pSplitVolatile->apszComps[0]));
+
+ int rc = RTPathParse(pszPath, (PRTPATHPARSED)pParsedVolatile, cbSplit, fFlags);
+ if (RT_FAILURE(rc) && rc != VERR_BUFFER_OVERFLOW)
+ return rc;
+
+ /*
+ * Calculate the required buffer space.
+ */
+ uint16_t const cComps = pParsedVolatile->cComps;
+ uint16_t const fProps = pParsedVolatile->fProps;
+ uint16_t const cchPath = pParsedVolatile->cchPath;
+ uint16_t const offSuffix = pParsedVolatile->offSuffix;
+ uint32_t cbNeeded = RT_UOFFSETOF_DYN(RTPATHSPLIT, apszComps[cComps])
+ + cchPath
+ + RTPATH_PROP_FIRST_NEEDS_NO_SLASH(fProps) /* zero terminator for root spec. */
+ - RT_BOOL(fProps & RTPATH_PROP_DIR_SLASH) /* counted by cchPath, not included in the comp str. */
+ + 1; /* zero terminator. */
+ if (cbNeeded > cbSplit)
+ {
+ pSplitVolatile->cbNeeded = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ Assert(RT_SUCCESS(rc));
+
+ /*
+ * Convert the array and copy the strings, both backwards.
+ */
+ char *psz = (char *)pSplit + cbNeeded;
+ uint32_t idxComp = cComps - 1;
+
+ /* the final component first (because of suffix handling). */
+ uint16_t offComp = pParsedVolatile->aComps[idxComp].off;
+ uint16_t cchComp = pParsedVolatile->aComps[idxComp].cch;
+
+ *--psz = '\0';
+ psz -= cchComp;
+ memcpy(psz, &pszPath[offComp], cchComp);
+ pSplitVolatile->apszComps[idxComp] = psz;
+
+ char *pszSuffix;
+ if (offSuffix >= offComp + cchComp)
+ pszSuffix = &psz[cchComp];
+ else
+ pszSuffix = &psz[offSuffix - offComp];
+
+ /* the remainder */
+ while (idxComp-- > 0)
+ {
+ offComp = pParsedVolatile->aComps[idxComp].off;
+ cchComp = pParsedVolatile->aComps[idxComp].cch;
+ *--psz = '\0';
+ psz -= cchComp;
+ memcpy(psz, &pszPath[offComp], cchComp);
+ pSplitVolatile->apszComps[idxComp] = psz;
+ }
+
+ /*
+ * Store / reshuffle the non-array bits. This MUST be done after finishing
+ * the array processing because there may be members in RTPATHSPLIT
+ * overlapping the array of RTPATHPARSED.
+ */
+ AssertCompileMembersSameSizeAndOffset(RTPATHPARSED, cComps, RTPATHSPLIT, cComps); Assert(pSplitVolatile->cComps == cComps);
+ AssertCompileMembersSameSizeAndOffset(RTPATHPARSED, fProps, RTPATHSPLIT, fProps); Assert(pSplitVolatile->fProps == fProps);
+ AssertCompileMembersSameSizeAndOffset(RTPATHPARSED, cchPath, RTPATHSPLIT, cchPath); Assert(pSplitVolatile->cchPath == cchPath);
+ pSplitVolatile->u16Reserved = 0;
+ pSplitVolatile->cbNeeded = cbNeeded;
+ pSplitVolatile->pszSuffix = pszSuffix;
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathSplitA.cpp b/src/VBox/Runtime/common/path/RTPathSplitA.cpp
new file mode 100644
index 00000000..ec82cfea
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathSplitA.cpp
@@ -0,0 +1,101 @@
+/* $Id: RTPathSplitA.cpp $ */
+/** @file
+ * IPRT - RTPathSplitA and RTPathSplitFree.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(int) RTPathSplitATag(const char *pszPath, PRTPATHSPLIT *ppSplit, uint32_t fFlags, const char *pszTag)
+{
+ AssertPtrReturn(ppSplit, VERR_INVALID_POINTER);
+ *ppSplit = NULL;
+
+ /*
+ * Try estimate a reasonable buffer size based on the path length.
+ * Note! No point in trying very hard to get it right.
+ */
+ size_t cbSplit = strlen(pszPath);
+ cbSplit += RT_UOFFSETOF_DYN(RTPATHSPLIT, apszComps[cbSplit / 8]) + cbSplit / 8 + 8;
+ cbSplit = RT_ALIGN(cbSplit, 64);
+ PRTPATHSPLIT pSplit = (PRTPATHSPLIT)RTMemAllocTag(cbSplit, pszTag);
+ if (pSplit == NULL)
+ return VERR_NO_MEMORY;
+
+ /*
+ * First try. If it fails due to buffer, reallocate the buffer and try again.
+ */
+ int rc = RTPathSplit(pszPath, pSplit, cbSplit, fFlags);
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbSplit = RT_ALIGN(pSplit->cbNeeded, 64);
+ RTMemFree(pSplit);
+
+ pSplit = (PRTPATHSPLIT)RTMemAllocTag(cbSplit, pszTag);
+ if (pSplit == NULL)
+ return VERR_NO_MEMORY;
+ rc = RTPathSplit(pszPath, pSplit, cbSplit, fFlags);
+ }
+
+ /*
+ * Done (one way or the other).
+ */
+ if (RT_SUCCESS(rc))
+ *ppSplit = pSplit;
+ else
+ RTMemFree(pSplit);
+ return rc;
+}
+
+
+RTDECL(void) RTPathSplitFree(PRTPATHSPLIT pSplit)
+{
+ if (pSplit)
+ {
+ Assert(pSplit->u16Reserved = UINT16_C(0xbeef));
+ RTMemFree(pSplit);
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathSplitReassemble.cpp b/src/VBox/Runtime/common/path/RTPathSplitReassemble.cpp
new file mode 100644
index 00000000..9987d2b9
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathSplitReassemble.cpp
@@ -0,0 +1,130 @@
+/* $Id: RTPathSplitReassemble.cpp $ */
+/** @file
+ * IPRT - RTPathSplitReassemble.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTPathSplitReassemble(PRTPATHSPLIT pSplit, uint32_t fFlags, char *pszDstPath, size_t cbDstPath)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(pSplit, VERR_INVALID_POINTER);
+ AssertReturn(pSplit->cComps > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0) && !(fFlags & RTPATH_STR_F_MIDDLE), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszDstPath, VERR_INVALID_POINTER);
+ AssertReturn(cbDstPath > pSplit->cchPath, VERR_BUFFER_OVERFLOW);
+
+ /*
+ * Figure which slash to use.
+ */
+ char chSlash;
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+ case RTPATH_STR_F_STYLE_HOST:
+ chSlash = RTPATH_SLASH;
+ break;
+
+ case RTPATH_STR_F_STYLE_DOS:
+ chSlash = '\\';
+ break;
+
+ case RTPATH_STR_F_STYLE_UNIX:
+ chSlash = '/';
+ break;
+
+ default:
+ AssertFailedReturn(VERR_INVALID_FLAGS); /* impossible */
+ }
+
+ /*
+ * Do the joining.
+ */
+ uint32_t const cchOrgPath = pSplit->cchPath;
+ size_t cchDstPath = 0;
+ uint32_t const cComps = pSplit->cComps;
+ uint32_t idxComp = 0;
+ char *pszDst = pszDstPath;
+ size_t cchComp;
+
+ if (RTPATH_PROP_HAS_ROOT_SPEC(pSplit->fProps))
+ {
+ cchComp = strlen(pSplit->apszComps[0]);
+ cchDstPath += cchComp;
+ AssertReturn(cchDstPath <= cchOrgPath, VERR_INVALID_PARAMETER);
+ memcpy(pszDst, pSplit->apszComps[0], cchComp);
+
+ /* fix the slashes */
+ char chOtherSlash = chSlash == '\\' ? '/' : '\\';
+ while (cchComp-- > 0)
+ {
+ if (*pszDst == chOtherSlash)
+ *pszDst = chSlash;
+ pszDst++;
+ }
+ idxComp = 1;
+ }
+
+ while (idxComp < cComps)
+ {
+ cchComp = strlen(pSplit->apszComps[idxComp]);
+ cchDstPath += cchComp;
+ AssertReturn(cchDstPath <= cchOrgPath, VERR_INVALID_PARAMETER);
+ memcpy(pszDst, pSplit->apszComps[idxComp], cchComp);
+ pszDst += cchComp;
+ idxComp++;
+ if (idxComp != cComps || (pSplit->fProps & RTPATH_PROP_DIR_SLASH))
+ {
+ cchDstPath++;
+ AssertReturn(cchDstPath <= cchOrgPath, VERR_INVALID_PARAMETER);
+ *pszDst++ = chSlash;
+ }
+ }
+
+ *pszDst = '\0';
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathStartsWithRoot.cpp b/src/VBox/Runtime/common/path/RTPathStartsWithRoot.cpp
new file mode 100644
index 00000000..3ac7c426
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathStartsWithRoot.cpp
@@ -0,0 +1,51 @@
+/* $Id: RTPathStartsWithRoot.cpp $ */
+/** @file
+ * IPRT - RTPathStartsWithRoot
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include "internal/path.h"
+
+
+RTDECL(bool) RTPathStartsWithRoot(const char *pszPath)
+{
+ return rtPathRootSpecLen(pszPath) > 0;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathStripExt.cpp b/src/VBox/Runtime/common/path/RTPathStripExt.cpp
new file mode 100644
index 00000000..08421f28
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathStripExt.cpp
@@ -0,0 +1,53 @@
+/* $Id: RTPathStripExt.cpp $ */
+/** @file
+ * IPRT - RTPathStripSuffix
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(void) RTPathStripSuffix(char *pszPath)
+{
+ char *pszSuffix = RTPathSuffix(pszPath);
+ if (pszSuffix)
+ *pszSuffix = '\0';
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathStripFilename.cpp b/src/VBox/Runtime/common/path/RTPathStripFilename.cpp
new file mode 100644
index 00000000..6d6aa332
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathStripFilename.cpp
@@ -0,0 +1,99 @@
+/* $Id: RTPathStripFilename.cpp $ */
+/** @file
+ * IPRT - RTPathStripFilename
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/ctype.h>
+
+
+/**
+ * Strips the filename from a path. Truncates the given string in-place by overwriting the
+ * last path separator character with a null byte in a platform-neutral way.
+ *
+ * @param pszPath Path from which filename should be extracted, will be truncated.
+ * If the string contains no path separator, it will be changed to a "." string.
+ */
+RTDECL(void) RTPathStripFilename(char *pszPath)
+{
+ char *psz = pszPath;
+ char *pszLastSep = NULL;
+
+
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ /* handle separators. */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ case ':':
+ pszLastSep = psz + 1;
+ if (RTPATH_IS_SLASH(psz[1]))
+ pszPath = psz + 1;
+ else
+ pszPath = psz;
+ break;
+
+ case '\\':
+#endif
+ case '/':
+ pszLastSep = psz;
+ break;
+
+ /* the end */
+ case '\0':
+ if (!pszLastSep)
+ {
+ /* no directory component */
+ pszPath[0] = '.';
+ pszPath[1] = '\0';
+ }
+ else if (pszLastSep == pszPath)
+ {
+ /* only root. */
+ pszLastSep[1] = '\0';
+ }
+ else
+ pszLastSep[0] = '\0';
+ return;
+ }
+ }
+ /* will never get here */
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathStripTrailingSlash.cpp b/src/VBox/Runtime/common/path/RTPathStripTrailingSlash.cpp
new file mode 100644
index 00000000..c75c5103
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathStripTrailingSlash.cpp
@@ -0,0 +1,74 @@
+/* $Id: RTPathStripTrailingSlash.cpp $ */
+/** @file
+ * IPRT - RTPathSTripTrailingSlash
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+
+
+
+RTDECL(size_t) RTPathStripTrailingSlash(char *pszPath)
+{
+ size_t off = strlen(pszPath);
+ while (off > 1)
+ {
+ off--;
+ switch (pszPath[off])
+ {
+ case '/':
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ case '\\':
+ if ( off == 2
+ && pszPath[1] == ':'
+ && RT_C_IS_ALPHA(pszPath[0]))
+ return off + 1;
+#endif
+ pszPath[off] = '\0';
+ break;
+
+ default:
+ return off + 1;
+ }
+ }
+
+ return 1;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathTraverseList.cpp b/src/VBox/Runtime/common/path/RTPathTraverseList.cpp
new file mode 100644
index 00000000..25439c01
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathTraverseList.cpp
@@ -0,0 +1,90 @@
+/* $Id: RTPathTraverseList.cpp $ */
+/** @file
+ * IPRT - RTPathTraverseList
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTPathTraverseList(const char *pszPathList, char chSep, PFNRTPATHTRAVERSER pfnCallback, void *pvUser1, void *pvUser2)
+{
+ AssertPtrNull(pszPathList);
+ Assert((unsigned int)chSep <= 127);
+
+ if (!pszPathList)
+ return VERR_END_OF_STRING;
+
+ /*
+ * Walk the path list.
+ */
+ const char *psz = pszPathList;
+ while (*psz)
+ {
+ /* Skip leading blanks - no directories with leading spaces, thank you. */
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+
+ /* Find the end of this element. */
+ const char *pszNext;
+ const char *pszEnd = strchr(psz, chSep);
+ if (!pszEnd)
+ pszEnd = pszNext = strchr(psz, '\0');
+ else
+ pszNext = pszEnd + 1;
+ if (pszEnd != psz)
+ {
+ size_t const cch = pszEnd - psz;
+ int rc = pfnCallback(psz, cch, pvUser1, pvUser2);
+ if (rc != VERR_TRY_AGAIN)
+ return rc;
+ }
+
+ /* advance */
+ psz = pszNext;
+ }
+
+ return VERR_END_OF_STRING;
+}
+RT_EXPORT_SYMBOL(RTPathTraverseList);
+
diff --git a/src/VBox/Runtime/common/path/comparepaths.cpp b/src/VBox/Runtime/common/path/comparepaths.cpp
new file mode 100644
index 00000000..159f60d2
--- /dev/null
+++ b/src/VBox/Runtime/common/path/comparepaths.cpp
@@ -0,0 +1,146 @@
+/* $Id: comparepaths.cpp $ */
+/** @file
+ * IPRT - Path Comparison.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+
+/**
+ * Helper for RTPathCompare() and RTPathStartsWith().
+ *
+ * @returns similar to strcmp.
+ * @param pszPath1 Path to compare.
+ * @param pszPath2 Path to compare.
+ * @param fLimit Limit the comparison to the length of \a pszPath2
+ * @internal
+ */
+static int rtPathCompare(const char *pszPath1, const char *pszPath2, bool fLimit)
+{
+ if (pszPath1 == pszPath2)
+ return 0;
+ if (!pszPath1)
+ return -1;
+ if (!pszPath2)
+ return 1;
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ for (;;)
+ {
+ RTUNICP uc1;
+ int rc = RTStrGetCpEx(&pszPath1, &uc1);
+ if (RT_SUCCESS(rc))
+ {
+ RTUNICP uc2;
+ rc = RTStrGetCpEx(&pszPath2, &uc2);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc1 == uc2)
+ {
+ if (uc1)
+ { /* likely */ }
+ else
+ return 0;
+ }
+ else
+ {
+ if (uc1 == '\\')
+ uc1 = '/';
+ else
+ uc1 = RTUniCpToUpper(uc1);
+ if (uc2 == '\\')
+ uc2 = '/';
+ else
+ uc2 = RTUniCpToUpper(uc2);
+ if (uc1 != uc2)
+ {
+ if (fLimit && uc2 == '\0')
+ return 0;
+ return uc1 > uc2 ? 1 : -1; /* (overflow/underflow paranoia) */
+ }
+ }
+ }
+ else
+ return 1;
+ }
+ else
+ return -1;
+ }
+#else
+ if (!fLimit)
+ return strcmp(pszPath1, pszPath2);
+ return strncmp(pszPath1, pszPath2, strlen(pszPath2));
+#endif
+}
+
+
+RTDECL(int) RTPathCompare(const char *pszPath1, const char *pszPath2)
+{
+ return rtPathCompare(pszPath1, pszPath2, false /* full path lengths */);
+}
+
+
+RTDECL(bool) RTPathStartsWith(const char *pszPath, const char *pszParentPath)
+{
+ if (pszPath == pszParentPath)
+ return true;
+ if (!pszPath || !pszParentPath)
+ return false;
+
+ if (rtPathCompare(pszPath, pszParentPath, true /* limited by path 2 */) != 0)
+ return false;
+
+ const size_t cchParentPath = strlen(pszParentPath);
+ if (RTPATH_IS_SLASH(pszPath[cchParentPath]))
+ return true;
+ if (pszPath[cchParentPath] == '\0')
+ return true;
+
+ /* Deal with pszParentPath = root (or having a trailing slash). */
+ if ( cchParentPath > 0
+ && RTPATH_IS_SLASH(pszParentPath[cchParentPath - 1])
+ && RTPATH_IS_SLASH(pszPath[cchParentPath - 1]))
+ return true;
+
+ return false;
+}
+
diff --git a/src/VBox/Runtime/common/path/nocrt-access.cpp b/src/VBox/Runtime/common/path/nocrt-access.cpp
new file mode 100644
index 00000000..16c554e5
--- /dev/null
+++ b/src/VBox/Runtime/common/path/nocrt-access.cpp
@@ -0,0 +1,78 @@
+/* $Id: nocrt-access.cpp $ */
+/** @file
+ * IPRT - No-CRT - access().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/unistd.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+
+
+#undef access
+int RT_NOCRT(access)(const char *pszPath, int fFlags) RT_NOEXCEPT
+{
+ RTFSOBJINFO ObjInfo;
+ int rc = RTPathQueryInfo(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(rc))
+ {
+ if (fFlags == F_OK)
+ return 0;
+ rc = 0;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ /** @todo A proper answer on windows would require reading the security
+ * attributes and stuff. Faking it for now. */
+ if ((fFlags & W_OK) && (ObjInfo.Attr.fMode & RTFS_DOS_READONLY) && !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ rc = -1;
+ else if ((fFlags & X_OK) && !(ObjInfo.Attr.fMode & (RTFS_UNIX_IXOTH | RTFS_UNIX_IXGRP | RTFS_UNIX_IXUSR)))
+ rc = -1;
+#else
+# error "port me"
+#endif
+ if (!rc)
+ return rc;
+ errno = EACCES;
+ }
+ else
+ errno = RTErrConvertToErrno(rc);
+ return -1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(access);
+
diff --git a/src/VBox/Runtime/common/path/nocrt-unlink.cpp b/src/VBox/Runtime/common/path/nocrt-unlink.cpp
new file mode 100644
index 00000000..df2fb8b3
--- /dev/null
+++ b/src/VBox/Runtime/common/path/nocrt-unlink.cpp
@@ -0,0 +1,59 @@
+/* $Id: nocrt-unlink.cpp $ */
+/** @file
+ * IPRT - No-CRT - unlink().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/unistd.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+
+
+#undef unlink
+int RT_NOCRT(unlink)(const char *pszPath) RT_NOEXCEPT
+{
+ int rc = RTPathUnlink(pszPath, 0 /*fUnlink*/);
+ if (RT_SUCCESS(rc))
+ return 0;
+ errno = RTErrConvertToErrno(rc);
+ return -1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(unlink);
+
diff --git a/src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp b/src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp
new file mode 100644
index 00000000..47c140f3
--- /dev/null
+++ b/src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp
@@ -0,0 +1,103 @@
+/* $Id: rtPathRootSpecLen.cpp $ */
+/** @file
+ * IPRT - rtPathRootSpecLen (internal).
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include "internal/path.h"
+
+/**
+ * Figures out the length of the root (or drive) specifier in @a pszPath.
+ *
+ * For UNC names, we consider the root specifier to include both the server and
+ * share names.
+ *
+ * @returns The length including all slashes. 0 if relative path.
+ *
+ * @param pszPath The path to examine.
+ */
+DECLHIDDEN(size_t) rtPathRootSpecLen(const char *pszPath)
+{
+ /*
+ * If it's an absolute path, threat the root or volume specification as
+ * component 0. UNC is making this extra fun on OS/2 and Windows as usual.
+ */
+ size_t off = 0;
+ if (RTPATH_IS_SLASH(pszPath[0]))
+ {
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ if ( RTPATH_IS_SLASH(pszPath[1])
+ && !RTPATH_IS_SLASH(pszPath[2])
+ && pszPath[2])
+ {
+ /* UNC server name */
+ off = 2;
+ while (!RTPATH_IS_SLASH(pszPath[off]) && pszPath[off])
+ off++;
+ while (RTPATH_IS_SLASH(pszPath[off]))
+ off++;
+
+ /* UNC share */
+ while (!RTPATH_IS_SLASH(pszPath[off]) && pszPath[off])
+ off++;
+ }
+ else
+#endif
+ {
+ off = 1;
+ }
+ while (RTPATH_IS_SLASH(pszPath[off]))
+ off++;
+ }
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ else if (RT_C_IS_ALPHA(pszPath[0]) && pszPath[1] == ':')
+ {
+ off = 2;
+ while (RTPATH_IS_SLASH(pszPath[off]))
+ off++;
+ }
+#endif
+ Assert(!RTPATH_IS_SLASH(pszPath[off]));
+
+ return off;
+}
+
diff --git a/src/VBox/Runtime/common/path/rtPathVolumeSpecLen.cpp b/src/VBox/Runtime/common/path/rtPathVolumeSpecLen.cpp
new file mode 100644
index 00000000..8c8000f7
--- /dev/null
+++ b/src/VBox/Runtime/common/path/rtPathVolumeSpecLen.cpp
@@ -0,0 +1,80 @@
+/* $Id: rtPathVolumeSpecLen.cpp $ */
+/** @file
+ * IPRT - rtPathVolumeSpecLen
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+#include "internal/path.h"
+
+
+
+/**
+ * Returns the length of the volume name specifier of the given path.
+ * If no such specifier zero is returned.
+ */
+DECLHIDDEN(size_t) rtPathVolumeSpecLen(const char *pszPath)
+{
+#if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS)
+ if (pszPath && *pszPath)
+ {
+ /* UNC path. */
+ /** @todo r=bird: it's UNC and we have to check that the next char isn't a
+ * slash, then skip both the server and the share name. */
+ if ( (pszPath[0] == '\\' || pszPath[0] == '/')
+ && (pszPath[1] == '\\' || pszPath[1] == '/'))
+ return strcspn(pszPath + 2, "\\/") + 2;
+
+ /* Drive letter. */
+ if ( pszPath[1] == ':'
+ && RT_C_IS_ALPHA(pszPath[0]))
+ return 2;
+ }
+ return 0;
+
+#else
+ /* This isn't quite right when looking at the above stuff, but it works assuming that '//' does not mean UNC. */
+ /// @todo (dmik) well, it's better to consider there's no volume name
+ // at all on *nix systems
+ NOREF(pszPath);
+ return 0;
+// return pszPath && pszPath[0] == '/';
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/path/rtpath-expand-template.cpp.h b/src/VBox/Runtime/common/path/rtpath-expand-template.cpp.h
new file mode 100644
index 00000000..68fb1399
--- /dev/null
+++ b/src/VBox/Runtime/common/path/rtpath-expand-template.cpp.h
@@ -0,0 +1,92 @@
+/* $Id: rtpath-expand-template.cpp.h $ */
+/** @file
+ * IPRT - RTPath - Internal header that includes RTPATH_TEMPLATE_CPP_H multiple
+ * times to expand the code for different path styles.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#undef RTPATH_DELIMITER
+
+/*
+ * DOS style
+ */
+#undef RTPATH_STYLE
+#undef RTPATH_SLASH
+#undef RTPATH_SLASH_STR
+#undef RTPATH_IS_SLASH
+#undef RTPATH_IS_VOLSEP
+#undef RTPATH_STYLE_FN
+
+#define RTPATH_STYLE RTPATH_STR_F_STYLE_DOS
+#define RTPATH_SLASH '\\'
+#define RTPATH_SLASH_STR "\\"
+#define RTPATH_IS_SLASH(a_ch) ( (a_ch) == '\\' || (a_ch) == '/' )
+#define RTPATH_IS_VOLSEP(a_ch) ( (a_ch) == ':' )
+#define RTPATH_STYLE_FN(a_Name) a_Name ## StyleDos
+#include RTPATH_TEMPLATE_CPP_H
+
+/*
+ * Unix style.
+ */
+#undef RTPATH_STYLE
+#undef RTPATH_SLASH
+#undef RTPATH_SLASH_STR
+#undef RTPATH_IS_SLASH
+#undef RTPATH_IS_VOLSEP
+#undef RTPATH_STYLE_FN
+
+#define RTPATH_STYLE RTPATH_STR_F_STYLE_UNIX
+#define RTPATH_SLASH '/'
+#define RTPATH_SLASH_STR "/"
+#define RTPATH_IS_SLASH(a_ch) ( (a_ch) == '/' )
+#define RTPATH_IS_VOLSEP(a_ch) ( false )
+#define RTPATH_STYLE_FN(a_Name) a_Name ## StyleUnix
+#include RTPATH_TEMPLATE_CPP_H
+
+/*
+ * Clean up and restore the host style.
+ */
+#undef RTPATH_STYLE_FN
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+# undef RTPATH_STYLE
+# undef RTPATH_SLASH
+# undef RTPATH_SLASH_STR
+# undef RTPATH_IS_SLASH
+# undef RTPATH_IS_VOLSEP
+# define RTPATH_STYLE RTPATH_STR_F_STYLE_DOS
+# define RTPATH_SLASH '\\'
+# define RTPATH_SLASH_STR "\\"
+# define RTPATH_IS_SLASH(a_ch) ( (a_ch) == '\\' || (a_ch) == '/' )
+# define RTPATH_IS_VOLSEP(a_ch) ( (a_ch) == ':' )
+#endif
+
diff --git a/src/VBox/Runtime/common/path/rtpath-root-length-template.cpp.h b/src/VBox/Runtime/common/path/rtpath-root-length-template.cpp.h
new file mode 100644
index 00000000..f223b857
--- /dev/null
+++ b/src/VBox/Runtime/common/path/rtpath-root-length-template.cpp.h
@@ -0,0 +1,80 @@
+/* $Id: rtpath-root-length-template.cpp.h $ */
+/** @file
+ * IPRT - rtPathRootLengthEx - Code Template.
+ *
+ * Include this from a path-style template file.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/ctype.h>
+
+
+/**
+ * Inlined helper for determining the length of the root component.
+ */
+DECLINLINE(size_t) RTPATH_STYLE_FN(rtPathRootLengthEx)(const char *pszPath, uint32_t fFlags)
+{
+ if (RTPATH_IS_SLASH(pszPath[0]))
+ {
+ if (fFlags & RTPATH_STR_F_NO_START)
+ return 0;
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ if (RTPATH_IS_SLASH(pszPath[1]))
+ {
+ /* UNC - there are exactly two prefix slashes followed by a namespace
+ or computer name, which can be empty on windows. */
+ size_t cchRoot = 2;
+ while (!RTPATH_IS_SLASH(pszPath[cchRoot]) && pszPath[cchRoot])
+ cchRoot++;
+ if (RTPATH_IS_SLASH(pszPath[cchRoot]))
+ cchRoot++;
+ return cchRoot;
+ }
+#endif
+ return 1;
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ if (RT_C_IS_ALPHA(pszPath[0]) && pszPath[1] == ':')
+ {
+ if (!RTPATH_IS_SLASH(pszPath[2]))
+ return 2;
+ return 3;
+ }
+#endif
+ return 0;
+}
+