path-posix.cpp revision afa76ab61aecc2b579dd6b5d350522895b9fa41c
/* $Id$ */
/** @file
* IPRT - Path Manipulation, POSIX.
*/
/*
* 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;
* 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 *
*******************************************************************************/
#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_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.
*/
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;
}
#ifndef RT_OS_L4
/**
* Worker for RTPathUserHome that looks up the home directory
* using the getpwuid_r api.
*
* @returns IPRT status code.
* @param pszPath The path buffer.
* @param cchPath The size of the buffer.
* @param uid The User ID to query the home directory of.
*/
{
/*
* The getpwuid_r function uses the passed in buffer to "allocate" any
* extra memory it needs. On some systems we should probably use the
* sysconf function to find the appropriate buffer size, but since it won't
* work everywhere we'll settle with a 5KB buffer and ASSUME that it'll
* suffice for even the lengthiest user descriptions...
*/
char achBuffer[5120];
if (rc != 0)
return RTErrConvertFromErrno(rc);
return VERR_PATH_NOT_FOUND;
/*
* Check that it isn't empty and that it exists.
*/
return VERR_PATH_NOT_FOUND;
/*
* Convert it to UTF-8 and copy it to the return buffer.
*/
char *pszUtf8Path;
if (RT_SUCCESS(rc))
{
else
}
return rc;
}
#endif
/**
* Worker for RTPathUserHome that looks up the home directory
* using the HOME environment variable.
*
* @returns IPRT status code.
* @param pszPath The path buffer.
* @param cchPath The size of the buffer.
*/
{
/*
* Get HOME env. var it and validate it's existance.
*/
int rc = VERR_PATH_NOT_FOUND;
if (pszHome)
{
{
/*
* Convert it to UTF-8 and copy it to the return buffer.
*/
char *pszUtf8Path;
if (RT_SUCCESS(rc))
{
else
}
}
}
return rc;
}
{
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)
else
/*
* On failure, retry using the alternative method.
* (Should perhaps restrict the retry cases a bit more here...)
*/
if ( RT_FAILURE(rc)
&& rc != VERR_BUFFER_OVERFLOW)
{
if (!uid)
else
}
#else /* RT_OS_L4 */
#endif /* RT_OS_L4 */
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);
}
{
/*
* Validate input.
*/
/*
* Change the directory.
*/
char *pszNativePath;
if (RT_SUCCESS(rc))
{
if (chdir(pszNativePath))
}
return rc;
}