path-posix.cpp revision b3c81894719ac55f8214cd9371f9abd393df8949
/* $Id$ */
/** @file
* innotek Portable Runtime - Path Manipulation, POSIX.
*/
/*
* Copyright (C) 2006-2007 innotek GmbH
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License 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.
*
* If you received this file as part of a commercial VirtualBox
* distribution, then only the terms of your commercial VirtualBox
* license agreement apply instead of the previous paragraph.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP RTLOGGROUP_PATH
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <pwd.h>
#ifdef RT_OS_DARWIN
#endif
#ifdef RT_OS_L4
#endif
{
/*
* Convert input.
*/
char *pszNativePath;
if (RT_SUCCESS(rc))
{
/*
* On POSIX platforms the API doesn't take a length parameter, which makes it
* a little bit more work.
*/
if (psz)
{
/*
* Convert result and copy it to the return buffer.
*/
char *pszUtf8RealPath;
if (RT_SUCCESS(rc))
{
if (cch <= cchRealPath)
else
}
}
else
}
return rc;
}
/**
* Cleans up a path specifier a little bit.
* This includes removing duplicate slashes, uncessary single dots, and
* trailing slashes. Also, replaces all RTPATH_SLASH characters with '/'.
*
* @returns Number of bytes in the clean path.
* @param pszPath The path to cleanup.
* @remark Borrowed from innotek libc.
*/
static int fsCleanPath(char *pszPath)
{
/*
* Change to '/' and remove duplicates.
*/
#ifdef HAVE_UNC
int fUnc = 0;
if ( RTPATH_IS_SLASH(pszPath[0])
{ /* Skip first slash in a unc path. */
pszSrc++;
*pszTrg++ = '/';
fUnc = 1;
}
#endif
for (;;)
{
if (RTPATH_IS_SLASH(ch))
{
*pszTrg++ = '/';
for (;;)
{
while (RTPATH_IS_SLASH(ch));
/* Remove '/./' and '/.'. */
break;
}
}
if (!ch)
break;
pszTrg++;
}
/*
* Remove trailing slash if the path may be pointing to a directory.
*/
if ( cch > 1
#ifdef HAVE_DRIVE
#endif
return cch;
}
{
/*
* Convert input.
*/
char *pszNativePath;
if (RT_FAILURE(rc))
{
return rc;
}
/*
* On POSIX platforms the API doesn't take a length parameter, which makes it
* a little bit more work.
*/
if (!psz)
{
#ifdef RT_OS_OS2
/// @todo realpath() returns EIO for non-existent UNC paths like
// a share). We should either fix realpath() in libc or remove
// this todo.
#endif
)
{
{
/*
* Iterate the path bit by bit an apply realpath to it.
*/
#ifdef HAVE_DRIVE
{
cch = 2;
pszCur += 3;
}
#ifdef HAVE_UNC
else
{
pszCur += 2;
{
}
else
/* we've got just "//server" or "//" */
/// @todo (r=dmik) not 100% sure we should fail, but the
// above cases are just invalid (incomplete) paths,
// no matter that Win32 returns these paths as is.
}
#endif
#else
if (*pszCur == '/')
{
pszCur++;
}
#endif
else
{
/* get the cwd */
if (psz)
{
#ifdef HAVE_DRIVE
if (*pszCur == '/')
{
cch = 2;
pszCur++;
}
else
#endif
}
else
}
if (psz)
{
bool fResolveSymlinks = true;
/* make sure strrchr() will work correctly */
while (*pszCur)
{
{
break;
}
{
#ifdef HAVE_UNC
#else
if (pszLastSlash)
#endif
{
}
/* else: We've reached the root and the parent of
* the root is the root. */
}
else
{
cch += cchElement;
if (fResolveSymlinks)
{
/* resolve possible symlinks */
: szTmpPath);
if (psz2)
{
}
else
{
#ifdef RT_OS_OS2
/// @todo see above
#endif
)
{
break;
}
/* no more need to resolve symlinks */
fResolveSymlinks = false;
}
}
}
pszCur += cchElement;
/* skip the slash */
if (*pszCur)
++pszCur;
}
#ifdef HAVE_DRIVE
/* check if we're at the root */
#else
/* if the length is zero here, then we're at the root */
if (!cch)
#endif
{
}
}
}
else
}
else
}
{
/*
* Convert result and copy it to the return buffer.
*/
char *pszUtf8AbsPath;
if (RT_FAILURE(rc))
{
return rc;
}
/* replace '/' back with native RTPATH_SLASH */
if (*psz == '/')
*psz = RTPATH_SLASH;
if (cch <= cchAbsPath)
else
}
cchAbsPath, rc));
return rc;
}
{
/*
* First time only.
*/
if (!g_szrtProgramPath[0])
{
/*
* Linux have no API for obtaining the executable path, but provides a symbolic link
* in the proc file system. Note that readlink is one of the weirdest Unix apis around.
*
* OS/2 have an api for getting the program file name.
*/
/** @todo use RTProcGetExecutableName() */
# ifdef RT_OS_LINUX
# elif defined(RT_OS_SOLARIS)
# else /* RT_OS_FREEBSD: */
# endif
{
return rc;
}
#elif defined(RT_OS_DARWIN)
const char *pszImageName = _dyld_get_image_name(0);
if (cchImageName >= sizeof(g_szrtProgramPath))
#else
#endif
/*
* Convert to UTF-8 and strip of the filename.
*/
if (RT_FAILURE(rc))
{
return rc;
}
if (cch >= sizeof(g_szrtProgramPath))
{
return VERR_BUFFER_OVERFLOW;
}
}
/*
* Calc the length and check if there is space before copying.
*/
{
return VINF_SUCCESS;
}
return VERR_BUFFER_OVERFLOW;
}
{
int rc;
#ifndef RT_OS_L4
/*
* We make an exception for the root user and use the system call
* getpwuid_r to determine their initial home path instead of
* reading it from the $HOME variable. This is because the $HOME
* variable does not get changed by sudo (and possibly su and others)
* which can cause root-owned files to appear in user's home folders.
*/
if (uid == 0)
{
/* The getpwuid_r function "allocates" any pointer memory it
needs from here. In theory one should use the sysconf
function to find the appropriate size, but sysconf is
unreliable by design. This should definitely be enough. */
char buffer[5120];
if (rc != 0)
return RTErrConvertFromErrno(rc);
/*
* Convert it to UTF-8 and copy it to the return buffer.
*/
char *pszUtf8Path;
if (RT_SUCCESS(rc))
{
else
}
return rc;
}
#endif
/*
* Get HOME env. var it and validate it's existance.
*/
struct stat s;
if (pszHome)
{
{
/*
* Convert it to UTF-8 and copy it to the return buffer.
*/
char *pszUtf8Path;
if (RT_SUCCESS(rc))
{
else
}
}
else
}
else
return rc;
}
RTR3DECL(int) RTPathQueryInfo(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs)
{
/*
* Validate input.
*/
("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs),
/*
* Convert the filename.
*/
char *pszNativePath;
if (RT_SUCCESS(rc))
{
{
switch (enmAdditionalAttribs)
{
case RTFSOBJATTRADD_EASIZE:
/** @todo Use SGI extended attribute interface to query EA info. */
break;
case RTFSOBJATTRADD_NOTHING:
case RTFSOBJATTRADD_UNIX:
break;
default:
AssertMsgFailed(("Impossible!\n"));
return VERR_INTERNAL_ERROR;
}
}
else
}
LogFlow(("RTPathQueryInfo(%p:{%s}, pObjInfo=%p, %d): returns %Rrc\n",
return rc;
}
RTR3DECL(int) RTPathSetTimes(const char *pszPath, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
{
/*
* Validate input.
*/
AssertMsgReturn(!pAccessTime || VALID_PTR(pAccessTime), ("%p\n", pAccessTime), VERR_INVALID_POINTER);
AssertMsgReturn(!pModificationTime || VALID_PTR(pModificationTime), ("%p\n", pModificationTime), VERR_INVALID_POINTER);
AssertMsgReturn(!pChangeTime || VALID_PTR(pChangeTime), ("%p\n", pChangeTime), VERR_INVALID_POINTER);
/*
* Convert the paths.
*/
char *pszNativePath;
if (RT_SUCCESS(rc))
{
/*
* If it's a no-op, we'll only verify the existance of the file.
*/
if (!pAccessTime && !pModificationTime)
{
rc = VINF_SUCCESS;
else
{
}
}
else
{
/*
* Convert the input to timeval, getting the missing one if necessary,
* and call the API which does the change.
*/
if (pAccessTime && pModificationTime)
{
}
else
{
if (RT_SUCCESS(rc))
{
RTTimeSpecGetTimeval(pModificationTime ? pModificationTime : &ObjInfo.ModificationTime, &aTimevals[1]);
}
else
Log(("RTPathSetTimes('%s',%p,%p,,): RTPathQueryInfo failed with %Rrc\n",
}
if (RT_SUCCESS(rc))
{
{
Log(("RTPathSetTimes('%s',%p,%p,,): failed with %Rrc and errno=%d\n",
}
}
}
}
LogFlow(("RTPathSetTimes(%p:{%s}, %p:{%RDtimespec}, %p:{%RDtimespec}, %p:{%RDtimespec}, %p:{%RDtimespec}): return %Rrc\n",
return rc;
}
/**
* Checks if two files are the one and same file.
*/
{
return false;
return false;
return true;
return false;
}
/**
* Worker for RTPathRename, RTDirRename, RTFileRename.
*
* @returns IPRT status code.
* @param pszSrc The source path.
* @param pszDst The destintation 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).
*/
{
/*
* Convert the paths.
*/
char *pszNativeSrc;
if (RT_SUCCESS(rc))
{
char *pszNativeDst;
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 unlikly ones but still), but I'm
* afraid there is little with can do to fix that.
*/
else if (!fFileType)
rc = VINF_SUCCESS;
else if (RTFS_IS_DIRECTORY(fFileType))
else
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.
*/
else
{
{
/*
* 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
rc = VINF_SUCCESS;
}
if (RT_SUCCESS(rc))
{
rc = VINF_SUCCESS;
else if ( (fRename & RTPATHRENAME_FLAGS_REPLACE)
{
/*
* Check that the destination isn't a directory.
* Yet another race condition.
*/
{
rc = VINF_SUCCESS;
Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): appears to be the same file... (errno=%d)\n",
}
else
{
else
rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
if (!unlink(pszNativeDst))
{
rc = VINF_SUCCESS;
else
{
Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): rename failed rc=%Rrc errno=%d\n",
}
}
else
{
Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): failed to unlink dst rc=%Rrc errno=%d\n",
}
}
else
Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): dst !dir check failed rc=%Rrc\n",
}
}
else
{
Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): rename failed rc=%Rrc errno=%d\n",
}
}
else
Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): destination check failed rc=%Rrc errno=%d\n",
}
else
Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): source type check failed rc=%Rrc errno=%d\n",
}
}
return rc;
}
{
/*
* Validate input.
*/
AssertMsgReturn(!(fRename & ~RTPATHRENAME_FLAGS_REPLACE), ("%#x\n", fRename), VERR_INVALID_PARAMETER);
/*
* Hand it to the worker.
*/
Log(("RTPathRename(%p:{%s}, %p:{%s}, %#x): returns %Rrc\n", pszSrc, pszSrc, pszDst, pszDst, fRename, rc));
return rc;
}
{
/*
* Validate input.
*/
AssertPtrReturn(pszPath, false);
AssertReturn(*pszPath, false);
/*
* Convert the path and check if it exists using stat().
*/
char *pszNativePath;
if (RT_SUCCESS(rc))
{
rc = VINF_SUCCESS;
else
}
return RT_SUCCESS(rc);
}