diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Runtime/r3/posix/path-posix.cpp | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/posix/path-posix.cpp b/src/VBox/Runtime/r3/posix/path-posix.cpp new file mode 100644 index 00000000..7cd35aac --- /dev/null +++ b/src/VBox/Runtime/r3/posix/path-posix.cpp @@ -0,0 +1,408 @@ +/* $Id: path-posix.cpp $ */ +/** @file + * IPRT - Path Manipulation, POSIX, Part 1. + */ + +/* + * 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_PATH +#include <stdlib.h> +#include <limits.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdio.h> +#include <sys/types.h> +#include <pwd.h> + +#include <iprt/path.h> +#include <iprt/env.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include "internal/path.h" +#include "internal/process.h" +#include "internal/fs.h" + + + +RTDECL(int) RTPathReal(const char *pszPath, char *pszRealPath, size_t cchRealPath) +{ + /* + * Convert input. + */ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + /* + * On POSIX platforms the API doesn't take a length parameter, which makes it + * a little bit more work. + */ + char szTmpPath[PATH_MAX + 1]; + const char *psz = realpath(pszNativePath, szTmpPath); + if (psz) + rc = rtPathFromNativeCopy(pszRealPath, cchRealPath, szTmpPath, NULL); + else + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativePath, pszPath); + } + + LogFlow(("RTPathReal(%p:{%s}, %p:{%s}, %u): returns %Rrc\n", pszPath, pszPath, + pszRealPath, RT_SUCCESS(rc) ? pszRealPath : "<failed>", cchRealPath, rc)); + return rc; +} + + +RTR3DECL(int) RTPathSetMode(const char *pszPath, RTFMODE fMode) +{ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + + int rc; + fMode = rtFsModeNormalize(fMode, pszPath, 0, 0); + if (rtFsModeIsValidPermissions(fMode)) + { + char const *pszNativePath; + rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + if (chmod(pszNativePath, fMode & RTFS_UNIX_MASK) != 0) + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativePath, pszPath); + } + } + else + { + AssertMsgFailed(("Invalid file mode! %RTfmode\n", fMode)); + rc = VERR_INVALID_FMODE; + } + return rc; +} + + +/** + * Checks if two files are the one and same file. + */ +static bool rtPathSame(const char *pszNativeSrc, const char *pszNativeDst) +{ + struct stat SrcStat; + if (lstat(pszNativeSrc, &SrcStat)) + return false; + struct stat DstStat; + if (lstat(pszNativeDst, &DstStat)) + return false; + Assert(SrcStat.st_dev && DstStat.st_dev); + Assert(SrcStat.st_ino && DstStat.st_ino); + if ( SrcStat.st_dev == DstStat.st_dev + && SrcStat.st_ino == DstStat.st_ino + && (SrcStat.st_mode & S_IFMT) == (DstStat.st_mode & S_IFMT)) + return true; + return false; +} + + +/** + * Worker for RTPathRename, RTDirRename, RTFileRename. + * + * @returns IPRT status code. + * @param pszSrc The source path. + * @param pszDst The destination path. + * @param fRename The rename flags. + * @param fFileType The filetype. We use the RTFMODE filetypes here. If it's 0, + * anything goes. If it's RTFS_TYPE_DIRECTORY we'll check that the + * source is a directory. If Its RTFS_TYPE_FILE we'll check that it's + * not a directory (we are NOT checking whether it's a file). + */ +DECLHIDDEN(int) rtPathPosixRename(const char *pszSrc, const char *pszDst, unsigned fRename, RTFMODE fFileType) +{ + /* + * Convert the paths. + */ + char const *pszNativeSrc; + int rc = rtPathToNative(&pszNativeSrc, pszSrc, NULL); + if (RT_SUCCESS(rc)) + { + char const *pszNativeDst; + rc = rtPathToNative(&pszNativeDst, pszDst, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Check that the source exists and that any types that's specified matches. + * We have to check this first to avoid getting errnous VERR_ALREADY_EXISTS + * errors from the next step. + * + * There are race conditions here (perhaps unlikely ones, but still), but I'm + * afraid there is little with can do to fix that. + */ + struct stat SrcStat; + if (lstat(pszNativeSrc, &SrcStat)) + rc = RTErrConvertFromErrno(errno); + else if (!fFileType) + rc = VINF_SUCCESS; + else if (RTFS_IS_DIRECTORY(fFileType)) + rc = S_ISDIR(SrcStat.st_mode) ? VINF_SUCCESS : VERR_NOT_A_DIRECTORY; + else + rc = S_ISDIR(SrcStat.st_mode) ? VERR_IS_A_DIRECTORY : VINF_SUCCESS; + if (RT_SUCCESS(rc)) + { + bool fSameFile = false; + + /* + * Check if the target exists, rename is rather destructive. + * We'll have to make sure we don't overwrite the source! + * Another race condition btw. + */ + struct stat DstStat; + if (lstat(pszNativeDst, &DstStat)) + rc = errno == ENOENT ? VINF_SUCCESS : RTErrConvertFromErrno(errno); + else + { + Assert(SrcStat.st_dev && DstStat.st_dev); + Assert(SrcStat.st_ino && DstStat.st_ino); + if ( SrcStat.st_dev == DstStat.st_dev + && SrcStat.st_ino == DstStat.st_ino + && (SrcStat.st_mode & S_IFMT) == (DstStat.st_mode & S_IFMT)) + { + /* + * It's likely that we're talking about the same file here. + * We should probably check paths or whatever, but for now this'll have to be enough. + */ + fSameFile = true; + } + if (fSameFile) + rc = VINF_SUCCESS; + else if (S_ISDIR(DstStat.st_mode) || !(fRename & RTPATHRENAME_FLAGS_REPLACE)) + rc = VERR_ALREADY_EXISTS; + else + rc = VINF_SUCCESS; + + } + if (RT_SUCCESS(rc)) + { + if (!rename(pszNativeSrc, pszNativeDst)) + rc = VINF_SUCCESS; + else if ( (fRename & RTPATHRENAME_FLAGS_REPLACE) + && (errno == ENOTDIR || errno == EEXIST)) + { + /* + * Check that the destination isn't a directory. + * Yet another race condition. + */ + if (rtPathSame(pszNativeSrc, pszNativeDst)) + { + rc = VINF_SUCCESS; + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): appears to be the same file... (errno=%d)\n", + pszSrc, pszDst, fRename, fFileType, errno)); + } + else + { + if (lstat(pszNativeDst, &DstStat)) + rc = errno != ENOENT ? RTErrConvertFromErrno(errno) : VINF_SUCCESS; + else if (S_ISDIR(DstStat.st_mode)) + rc = VERR_ALREADY_EXISTS; + else + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc)) + { + if (!unlink(pszNativeDst)) + { + if (!rename(pszNativeSrc, pszNativeDst)) + rc = VINF_SUCCESS; + else + { + rc = RTErrConvertFromErrno(errno); + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): rename failed rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + } + } + else + { + rc = RTErrConvertFromErrno(errno); + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): failed to unlink dst rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + } + } + else + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): dst !dir check failed rc=%Rrc\n", + pszSrc, pszDst, fRename, fFileType, rc)); + } + } + else + { + rc = RTErrConvertFromErrno(errno); + if (errno == ENOTDIR) + rc = VERR_ALREADY_EXISTS; /* unless somebody is racing us, this is the right interpretation */ + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): rename failed rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + } + } + else + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): destination check failed rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + } + else + Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): source type check failed rc=%Rrc errno=%d\n", + pszSrc, pszDst, fRename, fFileType, rc, errno)); + + rtPathFreeNative(pszNativeDst, pszDst); + } + rtPathFreeNative(pszNativeSrc, pszSrc); + } + return rc; +} + + +RTR3DECL(int) RTPathRename(const char *pszSrc, const char *pszDst, unsigned fRename) +{ + /* + * Validate input. + */ + AssertMsgReturn(VALID_PTR(pszSrc), ("%p\n", pszSrc), VERR_INVALID_POINTER); + AssertMsgReturn(VALID_PTR(pszDst), ("%p\n", pszDst), VERR_INVALID_POINTER); + AssertMsgReturn(*pszSrc, ("%p\n", pszSrc), VERR_INVALID_PARAMETER); + AssertMsgReturn(*pszDst, ("%p\n", pszDst), VERR_INVALID_PARAMETER); + AssertMsgReturn(!(fRename & ~RTPATHRENAME_FLAGS_REPLACE), ("%#x\n", fRename), VERR_INVALID_PARAMETER); + + /* + * Hand it to the worker. + */ + int rc = rtPathPosixRename(pszSrc, pszDst, fRename, 0); + + Log(("RTPathRename(%p:{%s}, %p:{%s}, %#x): returns %Rrc\n", pszSrc, pszSrc, pszDst, pszDst, fRename, rc)); + return rc; +} + + +RTR3DECL(int) RTPathUnlink(const char *pszPath, uint32_t fUnlink) +{ + RT_NOREF_PV(pszPath); RT_NOREF_PV(fUnlink); + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(bool) RTPathExists(const char *pszPath) +{ + return RTPathExistsEx(pszPath, RTPATH_F_FOLLOW_LINK); +} + + +RTDECL(bool) RTPathExistsEx(const char *pszPath, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, false); + AssertReturn(*pszPath, false); + Assert(RTPATH_F_IS_VALID(fFlags, 0)); + + /* + * Convert the path and check if it exists using stat(). + */ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + struct stat Stat; + if (fFlags & RTPATH_F_FOLLOW_LINK) + rc = stat(pszNativePath, &Stat); + else + rc = lstat(pszNativePath, &Stat); + if (!rc) + rc = VINF_SUCCESS; + else + rc = VERR_GENERAL_FAILURE; + rtPathFreeNative(pszNativePath, pszPath); + } + return RT_SUCCESS(rc); +} + + +RTDECL(int) RTPathGetCurrent(char *pszPath, size_t cchPath) +{ + /* + * Try with a reasonably sized buffer first. + */ + char szNativeCurDir[RTPATH_MAX]; + if (getcwd(szNativeCurDir, sizeof(szNativeCurDir)) != NULL) + return rtPathFromNativeCopy(pszPath, cchPath, szNativeCurDir, NULL); + + /* + * Retry a few times with really big buffers if we failed because CWD is unreasonably long. + */ + int iErr = errno; + if (iErr != ERANGE) + return RTErrConvertFromErrno(iErr); + + size_t cbNativeTmp = RTPATH_BIG_MAX; + for (;;) + { + char *pszNativeTmp = (char *)RTMemTmpAlloc(cbNativeTmp); + if (!pszNativeTmp) + return VERR_NO_TMP_MEMORY; + if (getcwd(pszNativeTmp, cbNativeTmp) != NULL) + { + int rc = rtPathFromNativeCopy(pszPath, cchPath, pszNativeTmp, NULL); + RTMemTmpFree(pszNativeTmp); + return rc; + } + iErr = errno; + RTMemTmpFree(pszNativeTmp); + if (iErr != ERANGE) + return RTErrConvertFromErrno(iErr); + + cbNativeTmp += RTPATH_BIG_MAX; + if (cbNativeTmp > RTPATH_BIG_MAX * 4) + return VERR_FILENAME_TOO_LONG; + } +} + + +RTDECL(int) RTPathSetCurrent(const char *pszPath) +{ + /* + * Validate input. + */ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(*pszPath, VERR_INVALID_PARAMETER); + + /* + * Change the directory. + */ + char const *pszNativePath; + int rc = rtPathToNative(&pszNativePath, pszPath, NULL); + if (RT_SUCCESS(rc)) + { + if (chdir(pszNativePath)) + rc = RTErrConvertFromErrno(errno); + rtPathFreeNative(pszNativePath, pszPath); + } + return rc; +} + |