/* $Id$ */
/** @file
* IPRT - RTPathAbs, generic implementation.
*/
/*
* Copyright (C) 2006-2014 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 <iprt/path.h>
#include <iprt/assert.h>
#include <iprt/ctype.h>
#include <iprt/err.h>
#include <iprt/log.h>
#include <iprt/string.h>
#include "internal/path.h"
#include "internal/fs.h"
static char *rtPathSkipRootSpec(char *pszCur)
{
#ifdef HAVE_DRIVE
if (pszCur[0] && RTPATH_IS_VOLSEP(pszCur[1]) && pszCur[2] == RTPATH_SLASH)
pszCur += 3;
# ifdef HAVE_UNC
else if (pszCur[0] == RTPATH_SLASH && pszCur[1] == RTPATH_SLASH)
{
pszCur += 2;
while (*pszCur == RTPATH_SLASH)
pszCur++;
if (*pszCur)
{
while (*pszCur != RTPATH_SLASH && *pszCur)
pszCur++;
if (*pszCur == RTPATH_SLASH)
{
pszCur++;
while (*pszCur != RTPATH_SLASH && *pszCur)
pszCur++;
if (*pszCur == RTPATH_SLASH)
pszCur++;
}
}
}
# endif
#else
if (pszCur[0] == RTPATH_SLASH)
pszCur += 1;
#endif
return pszCur;
}
/**
* Cleans up a path specifier a little bit.
*
* This includes removing duplicate slashes, unnecessary single dots, and
* trailing slashes. Also, replaces all slash characters with RTPATH_SLASH.
*
* @returns Length of the cleaned path (in chars).
* @param pszPath The path to cleanup.
*/
static int fsCleanPath(char *pszPath)
{
char *pszSrc = pszPath;
char *pszTrg = pszPath;
/*
* On windows, you either use on or two slashes at the head of a path,
* seems like it treats additional slashes as part of the UNC server name.
* Just change slashes to RTPATH_SLASH and skip them.
*/
/** @todo check how OS/2 treats unnecessary leading slashes */
int cchIgnoreLeading = 0;
#ifdef HAVE_UNC
if ( RTPATH_IS_SLASH(pszSrc[0])
&& RTPATH_IS_SLASH(pszSrc[1]))
{
pszTrg[0] = RTPATH_SLASH;
pszTrg[1] = RTPATH_SLASH;
pszTrg += 2;
pszSrc += 2;
cchIgnoreLeading = 1;
while (RTPATH_IS_SLASH(*pszSrc))
{
cchIgnoreLeading++;
pszSrc++;
*pszTrg++ = RTPATH_SLASH;
}
}
#endif
/*
* Change slashes to RTPATH_SLASH and remove duplicates.
*/
for (;;)
{
char ch = *pszSrc++;
if (RTPATH_IS_SLASH(ch))
{
*pszTrg++ = RTPATH_SLASH;
for (;;)
{
do
ch = *pszSrc++;
while (RTPATH_IS_SLASH(ch));
/* Remove '/./' and '/.'. */
if ( ch != '.'
|| (*pszSrc && !RTPATH_IS_SLASH(*pszSrc)))
break;
}
}
*pszTrg = ch;
if (!ch)
break;
pszTrg++;
}
return pszTrg - pszPath;
}
RTDECL(int) RTPathAbs(const char *pszPath, char *pszAbsPath, size_t cchAbsPath)
{
int rc;
/*
* Validation.
*/
AssertPtr(pszAbsPath);
AssertPtr(pszPath);
if (RT_UNLIKELY(!*pszPath))
return VERR_INVALID_PARAMETER; //VERR_INVALID_NAME;
/*
* Make a clean working copy of the input.
*/
size_t cchPath = strlen(pszPath);
if (cchPath >= RTPATH_MAX)
{
LogFlow(("RTPathAbs(%p:{%s}, %p, %d): returns %Rrc\n", pszPath, pszPath, pszAbsPath, cchAbsPath, VERR_FILENAME_TOO_LONG));
return VERR_FILENAME_TOO_LONG;
}
char szTmpPath[RTPATH_MAX];
memcpy(szTmpPath, pszPath, cchPath + 1);
size_t cchTmpPath = fsCleanPath(szTmpPath);
/*
* Handle "." specially (fsCleanPath does).
*/
if (szTmpPath[0] == '.')
{
if ( cchTmpPath == 1
|| (cchTmpPath == 2 && szTmpPath[1] == RTPATH_SLASH))
{
rc = RTPathGetCurrent(pszAbsPath, cchAbsPath);
if (RT_SUCCESS(rc))
{
size_t cch = fsCleanPath(pszAbsPath);
char *pszTop = rtPathSkipRootSpec(pszAbsPath);
#if 1
if ((uintptr_t)&pszAbsPath[cch] > (uintptr_t)pszTop && pszAbsPath[cch - 1] == RTPATH_SLASH)
pszAbsPath[cch - 1] = '\0';
#else
if ( cchTmpPath == 2
&& (uintptr_t)&pszAbsPath[cch - 1] > (uintptr_t)pszTop && pszAbsPath[cch - 1] != RTPATH_SLASH)
{
if (cch + 1 < cchAbsPath)
{
pszAbsPath[cch++] = RTPATH_SLASH;
pszAbsPath[cch] = '\0';
}
else
rc = VERR_BUFFER_OVERFLOW;
}
#endif
}
return rc;
}
}
/*
* Do we have an incomplete root spec? Supply the missing bits.
*/
#ifdef HAVE_DRIVE
if ( !(szTmpPath[0] && RTPATH_IS_VOLSEP(szTmpPath[1]) && szTmpPath[2] == RTPATH_SLASH)
# ifdef HAVE_UNC
&& !(szTmpPath[0] == RTPATH_SLASH && szTmpPath[1] == RTPATH_SLASH)
# endif
)
#else
if (szTmpPath[0] != RTPATH_SLASH)
#endif
{
char szCurDir[RTPATH_MAX];
size_t cchCurDir;
int offApplyAt;
bool fNeedSlash;
#ifdef HAVE_DRIVE
if (szTmpPath[0] && RTPATH_IS_VOLSEP(szTmpPath[1]) && szTmpPath[2] != RTPATH_SLASH)
{
/*
* Relative to drive specific current directory.
*/
rc = RTPathGetCurrentOnDrive(szTmpPath[0], szCurDir, sizeof(szCurDir));
fNeedSlash = true;
offApplyAt = 2;
}
# ifdef HAVE_UNC
else if (szTmpPath[0] == RTPATH_SLASH && szTmpPath[1] != RTPATH_SLASH)
# else
else if (szTmpPath[0] == RTPATH_SLASH)
# endif
{
/*
* Root of current drive. This may return a UNC root if we're not
* standing on a drive but on a UNC share.
*/
rc = RTPathGetCurrentDrive(szCurDir, sizeof(szCurDir));
fNeedSlash = false;
offApplyAt = 0;
}
else
#endif
{
/*
* Relative to current directory.
*/
rc = RTPathGetCurrent(szCurDir, sizeof(szCurDir));
fNeedSlash = true;
offApplyAt = 0;
}
if (RT_SUCCESS(rc))
{
cchCurDir = fsCleanPath(szCurDir);
if (fNeedSlash && cchCurDir > 0 && szCurDir[cchCurDir - 1] == RTPATH_SLASH)
fNeedSlash = false;
if (cchCurDir + fNeedSlash + cchTmpPath - offApplyAt <= RTPATH_MAX)
{
memmove(szTmpPath + cchCurDir + fNeedSlash, szTmpPath + offApplyAt, cchTmpPath + 1 - offApplyAt);
memcpy(szTmpPath, szCurDir, cchCurDir);
if (fNeedSlash)
szTmpPath[cchCurDir] = RTPATH_SLASH;
}
else
rc = VERR_FILENAME_TOO_LONG;
}
if (RT_FAILURE(rc))
{
LogFlow(("RTPathAbs(%p:{%s}, %p, %d): returns %Rrc\n", pszPath, pszPath, pszAbsPath, cchAbsPath, rc));
return rc;
}
}
/*
* Skip past the root spec.
*/
char *pszCur = rtPathSkipRootSpec(szTmpPath);
AssertMsgReturn(pszCur != &szTmpPath[0], ("pszCur=%s\n", pszCur), VERR_INTERNAL_ERROR);
char * const pszTop = pszCur;
/*
* Get rid of double dot path components by evaluating them.
*/
for (;;)
{
if ( pszCur[0] == '.'
&& pszCur[1] == '.'
&& (!pszCur[2] || pszCur[2] == RTPATH_SLASH))
{
/* rewind to the previous component if any */
char *pszPrev = pszCur;
if ((uintptr_t)pszPrev > (uintptr_t)pszTop)
{
pszPrev--;
while ( (uintptr_t)pszPrev > (uintptr_t)pszTop
&& pszPrev[-1] != RTPATH_SLASH)
pszPrev--;
}
if (!pszCur[2])
{
if (pszPrev != pszTop)
pszPrev[-1] = '\0';
else
*pszPrev = '\0';
break;
}
Assert(pszPrev[-1] == RTPATH_SLASH);
memmove(pszPrev, pszCur + 3, strlen(pszCur + 3) + 1);
pszCur = pszPrev - 1;
}
else
{
/* advance to end of component. */
while (*pszCur && *pszCur != RTPATH_SLASH)
pszCur++;
}
if (!*pszCur)
break;
/* skip the slash */
++pszCur;
}
cchTmpPath = pszCur - szTmpPath;
#if 1
/*
* Strip trailing slash if that's what's desired.
*/
if ((uintptr_t)&szTmpPath[cchTmpPath] > (uintptr_t)pszTop && szTmpPath[cchTmpPath - 1] == RTPATH_SLASH)
szTmpPath[--cchTmpPath] = '\0';
#endif
/*
* Copy the result to the user buffer.
*/
if (cchTmpPath < cchAbsPath)
{
memcpy(pszAbsPath, szTmpPath, cchTmpPath + 1);
rc = VINF_SUCCESS;
}
else
rc = VERR_BUFFER_OVERFLOW;
LogFlow(("RTPathAbs(%p:{%s}, %p:{%s}, %d): returns %Rrc\n", pszPath, pszPath, pszAbsPath,
RT_SUCCESS(rc) ? pszAbsPath : "<failed>", cchAbsPath, rc));
return rc;
}