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