/* $Id: path.cpp 14324 2008-11-18 19:09:34Z vboxsync $ */ /** @file * IPRT - Path Manipulation. */ /* * Copyright (C) 2006-2007 Sun Microsystems, Inc. * * 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. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 USA or visit http://www.sun.com if you need * additional information or have any questions. */ /******************************************************************************* * Header Files * *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include "internal/fs.h" #include "internal/path.h" #include "internal/process.h" /** * Strips the filename from a path. * * @param pszPath Path which filename should be extracted from. * If only filename in the string a '.' will be returned. * */ 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 */ } /** * Strips the extension from a path. * * @param pszPath Path which extension should be stripped. */ RTDECL(void) RTPathStripExt(char *pszPath) { char *pszDot = NULL; for (;; pszPath++) { switch (*pszPath) { /* handle separators. */ #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) case ':': case '\\': #endif case '/': pszDot = NULL; break; case '.': pszDot = pszPath; break; /* the end */ case '\0': if (pszDot) *pszDot = '\0'; return; } } /* will never get here */ } /** * 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 pcbDir 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. * @param pfFlags Where to set flags returning more information about * the path. For the future. Optional. */ RTDECL(size_t) RTPathParse(const char *pszPath, size_t *pcchDir, ssize_t *poffName, ssize_t *poffSuff) { const char *psz = pszPath; ssize_t offRoot = 0; const char *pszName = pszPath; const char *pszLastDot = NULL; for (;; psz++) { switch (*psz) { /* handle separators. */ #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) case ':': pszName = psz + 1; offRoot = pszName - psz; break; case '\\': #endif case '/': pszName = psz + 1; 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) { offSuff = pszLastDot - pszPath; if (offSuff <= offName) offSuff = -1; } *poffSuff = offSuff; } if (pcchDir) { ssize_t off = offName - 1; while (off >= offRoot && RTPATH_IS_SLASH(pszPath[off])) off--; *pcchDir = RT_MAX(off, offRoot) + 1; } return psz - pszPath; } } } /* will never get here */ return 0; } /** * Finds the filename in a path. * * @returns Pointer to filename within pszPath. * @returns NULL if no filename (i.e. empty string or ends with a slash). * @param pszPath Path to find filename in. */ RTDECL(char *) RTPathFilename(const char *pszPath) { const char *psz = pszPath; const char *pszName = pszPath; for (;; psz++) { switch (*psz) { /* handle separators. */ #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) case ':': pszName = psz + 1; break; case '\\': #endif case '/': pszName = psz + 1; break; /* the end */ case '\0': if (*pszName) return (char *)(void *)pszName; return NULL; } } /* will never get here */ return NULL; } /** * Strips the trailing slashes of a path name. * * @param pszPath Path to strip. * * @todo This isn't safe for a root element! Needs fixing. */ RTDECL(void) RTPathStripTrailingSlash(char *pszPath) { char *pszEnd = strchr(pszPath, '\0'); while (pszEnd-- > pszPath) { switch (*pszEnd) { case '/': #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) case '\\': #endif *pszEnd = '\0'; break; default: return; } } return; } /** * Finds the extension part of in a path. * * @returns Pointer to extension within pszPath. * @returns NULL if no extension. * @param pszPath Path to find extension in. */ RTDECL(char *) RTPathExt(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) return (char *)(void *)pszExt; return NULL; } } /* will never get here */ return NULL; } /** * Checks if a path have an extension. * * @returns true if extension present. * @returns false if no extension. * @param pszPath Path to check. */ RTDECL(bool) RTPathHaveExt(const char *pszPath) { return RTPathExt(pszPath) != NULL; } /** * 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) RTPathHavePath(const char *pszPath) { #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) return strpbrk(pszPath, "/\\:") != NULL; #else return strpbrk(pszPath, "/") != NULL; #endif } /** * 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) PRTUNICP puszPath1; int rc = RTStrToUni(pszPath1, &puszPath1); if (RT_FAILURE(rc)) return -1; PRTUNICP puszPath2; rc = RTStrToUni(pszPath2, &puszPath2); if (RT_FAILURE(rc)) { RTUniFree(puszPath1); return 1; } int iDiff = 0; PRTUNICP puszTmpPath1 = puszPath1; PRTUNICP puszTmpPath2 = puszPath2; for (;;) { register RTUNICP uc1 = *puszTmpPath1; register RTUNICP uc2 = *puszTmpPath2; if (uc1 != uc2) { if (uc1 == '\\') uc1 = '/'; else uc1 = RTUniCpToUpper(uc1); if (uc2 == '\\') uc2 = '/'; else uc2 = RTUniCpToUpper(uc2); if (uc1 != uc2) { iDiff = uc1 > uc2 ? 1 : -1; /* (overflow/underflow paranoia) */ if (fLimit && uc2 == '\0') iDiff = 0; break; } } if (!uc1) break; puszTmpPath1++; puszTmpPath2++; } RTUniFree(puszPath2); RTUniFree(puszPath1); return iDiff; #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: *
    *
  • On DOS-like platforms, both |\| and |/| separator chars are considered * to be equal. *
  • On platforms with case-insensitive file systems, mismatching characters * are uppercased and compared again. *
* * @remark * * File system details are currently ignored. This means that you won't get * case-insentive 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-sensitve compares on a case-sensitive file system. * * @param pszPath1 Path to compare (must be an absolute path). * @param pszPath2 Path to compare (must be an absolute path). * * @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. */ 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. * * @param pszPath Path to check, must be an absolute path. * @param pszParentPath Parent path, must be an absolute path. * No trailing directory slash! * * @returns |true| when \a pszPath starts with \a pszParentPath (or when they * are identical), or |false| otherwise. * * @remark This API doesn't currently handle root directory compares in a manner * consistant with the other APIs. RTPathStartsWith(pszSomePath, "/") will * not work if pszSomePath isn't "/". */ 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); return RTPATH_IS_SLASH(pszPath[cchParentPath]) || pszPath[cchParentPath] == '\0'; } /** * 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; } /** * 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) { char szPath[RTPATH_MAX]; int rc = RTPathAbs(pszPath, szPath, sizeof(szPath)); if (RT_SUCCESS(rc)) return RTStrDup(szPath); return NULL; } /** * Returns the length of the volume name specifier of the given path. * If no such specifier zero is returned. */ size_t rtPathVolumeSpecLen(const char *pszPath) { #if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS) if (pszPath && *pszPath) { /* UTC path. */ if ( (pszPath[0] == '\\' || pszPath[0] == '/') && (pszPath[1] == '\\' || pszPath[1] == '/')) return strcspn(pszPath + 2, "\\/") + 2; /* Drive letter. */ if ( pszPath[1] == ':' && toupper(pszPath[0]) >= 'A' && toupper(pszPath[0]) <= 'Z') 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 return 0; // return pszPath && pszPath[0] == '/'; #endif } /** * Get the absolute path (no symlinks, no . or .. components), assuming the * given base path as the current directory. The resulting path doesn't have * to exist. * * @returns iprt status code. * @param pszBase The base path to act like a current directory. * When NULL, the actual cwd is used (i.e. the call * is equivalent to RTPathAbs(pszPath, ...). * @param pszPath The path to resolve. * @param pszAbsPath Where to store the absolute path. * @param cchAbsPath Size of the buffer. */ RTDECL(int) RTPathAbsEx(const char *pszBase, const char *pszPath, char *pszAbsPath, size_t cchAbsPath) { if (pszBase && pszPath && !rtPathVolumeSpecLen(pszPath)) { #if defined(RT_OS_WINDOWS) /* The format for very long paths is not supported. */ if ( (pszBase[0] == '/' || pszBase[0] == '\\') && (pszBase[1] == '/' || pszBase[1] == '\\') && pszBase[2] == '?' && (pszBase[3] == '/' || pszBase[3] == '\\')) return VERR_INVALID_NAME; #endif /** @todo there are a couple of things which isn't 100% correct, although the * current code will have to work for now - I don't have time to fix it right now. * * 1) On Windows & OS/2 we confuse '/' with an abspath spec and will * not necessarily resolve it on the right drive. * 2) A trailing slash in the base might cause UNC names to be created. * 3) The lengths total doesn't have to be less than max length * if the pszPath starts with a slash. */ size_t cchBase = strlen(pszBase); size_t cchPath = strlen(pszPath); if (cchBase + cchPath >= RTPATH_MAX) return VERR_FILENAME_TOO_LONG; bool fRootSpec = pszPath[0] == '/' #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) || pszPath[0] == '\\' #endif ; size_t cchVolSpec = rtPathVolumeSpecLen(pszBase); char szPath[RTPATH_MAX]; if (fRootSpec) { /* join the disk name from base and the path */ memcpy(szPath, pszBase, cchVolSpec); strcpy(&szPath[cchVolSpec], pszPath); } else { /* join the base path and the path */ strcpy(szPath, pszBase); szPath[cchBase] = RTPATH_DELIMITER; strcpy(&szPath[cchBase + 1], pszPath); } return RTPathAbs(szPath, pszAbsPath, cchAbsPath); } /* Fallback to the non *Ex version */ return RTPathAbs(pszPath, pszAbsPath, cchAbsPath); } /** * Same as RTPathAbsEx only the result is RTStrDup()'ed. * * @returns Pointer to the absolute path. Use RTStrFree() to free this string. * @returns NULL if RTPathAbsEx() or RTStrDup() fails. * @param pszBase The base path to act like a current directory. * When NULL, the actual cwd is used (i.e. the call * is equivalent to RTPathAbs(pszPath, ...). * @param pszPath The path to resolve. */ RTDECL(char *) RTPathAbsExDup(const char *pszBase, const char *pszPath) { char szPath[RTPATH_MAX]; int rc = RTPathAbsEx(pszBase, pszPath, szPath, sizeof(szPath)); if (RT_SUCCESS(rc)) return RTStrDup(szPath); return NULL; } #ifndef RT_MINI RTDECL(int) RTPathProgram(char *pszPath, size_t cchPath) { AssertReturn(g_szrtProcExePath[0], VERR_WRONG_ORDER); /* * Calc the length and check if there is space before copying. */ size_t cch = g_cchrtProcDir; if (cch <= cchPath) { memcpy(pszPath, g_szrtProcExePath, cch); pszPath[cch] = '\0'; return VINF_SUCCESS; } AssertMsgFailed(("Buffer too small (%zu <= %zu)\n", cchPath, cch)); return VERR_BUFFER_OVERFLOW; } /** * Gets the directory for architecture-independent application data, for * example NLS files, module sources, ... * * Linux: /usr/shared/@ * Windows: @/@ * Old path: same as RTPathProgram() * */ RTDECL(int) RTPathAppPrivateNoArch(char *pszPath, size_t cchPath) { #if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_PRIVATE) char *pszUtf8Path; int rc; rc = rtPathFromNative(&pszUtf8Path, RTPATH_APP_PRIVATE); if (RT_SUCCESS(rc)) { size_t cchPathPrivateNoArch = strlen(pszUtf8Path); if (cchPathPrivateNoArch < cchPath) memcpy(pszPath, pszUtf8Path, cchPathPrivateNoArch + 1); else rc = VERR_BUFFER_OVERFLOW; RTStrFree(pszUtf8Path); } return rc; #else return RTPathProgram(pszPath, cchPath); #endif } /** * Gets the directory for architecture-dependent application data, for * example modules which can be loaded at runtime. * * Linux: /usr/lib/@ * Windows: @/@ * Old path: same as RTPathProgram() * * @returns iprt status code. * @param pszPath Buffer where to store the path. * @param cchPath Buffer size in bytes. */ RTDECL(int) RTPathAppPrivateArch(char *pszPath, size_t cchPath) { #if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_PRIVATE_ARCH) char *pszUtf8Path; int rc; rc = rtPathFromNative(&pszUtf8Path, RTPATH_APP_PRIVATE_ARCH); if (RT_SUCCESS(rc)) { size_t cchPathPrivateArch = strlen(pszUtf8Path); if (cchPathPrivateArch < cchPath) memcpy(pszPath, pszUtf8Path, cchPathPrivateArch + 1); else rc = VERR_BUFFER_OVERFLOW; RTStrFree(pszUtf8Path); } return rc; #else return RTPathProgram(pszPath, cchPath); #endif } /** * Gets the directory of shared libraries. This is not the same as * RTPathAppPrivateArch() as Linux depends all shared libraries in * a common global directory where ld.so can found them. * * Linux: /usr/lib * Windows: @/@ * Old path: same as RTPathProgram() * * @returns iprt status code. * @param pszPath Buffer where to store the path. * @param cchPath Buffer size in bytes. */ RTDECL(int) RTPathSharedLibs(char *pszPath, size_t cchPath) { #if !defined(RT_OS_WINDOWS) && defined(RTPATH_SHARED_LIBS) char *pszUtf8Path; int rc; rc = rtPathFromNative(&pszUtf8Path, RTPATH_SHARED_LIBS); if (RT_SUCCESS(rc)) { size_t cchPathSharedLibs = strlen(pszUtf8Path); if (cchPathSharedLibs < cchPath) memcpy(pszPath, pszUtf8Path, cchPathSharedLibs + 1); else rc = VERR_BUFFER_OVERFLOW; RTStrFree(pszUtf8Path); } return rc; #else return RTPathProgram(pszPath, cchPath); #endif } /** * Gets the directory for documentation. * * Linux: /usr/share/doc/@ * Windows: @/@ * Old path: same as RTPathProgram() * * @returns iprt status code. * @param pszPath Buffer where to store the path. * @param cchPath Buffer size in bytes. */ RTDECL(int) RTPathAppDocs(char *pszPath, size_t cchPath) { #if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_DOCS) char *pszUtf8Path; int rc; rc = rtPathFromNative(&pszUtf8Path, RTPATH_APP_DOCS); if (RT_SUCCESS(rc)) { size_t cchPathAppDocs = strlen(pszUtf8Path); if (cchPathAppDocs < cchPath) memcpy(pszPath, pszUtf8Path, cchPathAppDocs + 1); else rc = VERR_BUFFER_OVERFLOW; RTStrFree(pszUtf8Path); } return rc; #else return RTPathProgram(pszPath, cchPath); #endif } #endif /* !RT_MINI */