Changeset 3925


Ignore:
Timestamp:
Oct 26, 2014, 2:06:31 AM (11 years ago)
Author:
bird
Message:

trunk: Reimplemented _abspath to deal with path-rewriters, chroot environment, special /pipe/ and /dev/ dirs, UNC stuff, and extra slashes. Fixes #267.

Location:
trunk/libc
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/libc/src/libc/misc/abspath.c

    r3879 r3925  
    1 /* abspath.c (emx+gcc) -- Copyright (c) 1992-1998 by Eberhard Mattes */
    2 
     1/* $Id$ */
     2/** @file
     3 * kLibC - Implementation of _abspath().
     4 *
     5 * @copyright   Copyright (C) 2014 knut st. osmundsen <bird-klibc-spam-xiv@anduin.net>
     6 * @licenses    MIT, BSD2, BSD3, BSD4, LGPLv2.1, LGPLv3, LGPLvFuture.
     7 */
     8
     9
     10/*******************************************************************************
     11*   Header Files                                                               *
     12*******************************************************************************/
    313#include "libc-alias.h"
    414#include <stdlib.h>
     15
     16#include <assert.h>
     17#include <errno.h>
     18#include <stdbool.h>
    519#include <string.h>
    620#include <alloca.h>
    7 #include <errno.h>
     21#include <sys/cdefs.h>
    822#include <sys/param.h>
    9 #include <emx/syscalls.h>
    1023#include <InnoTekLIBC/libc.h>
    1124#include <InnoTekLIBC/locale.h>
    1225#include <InnoTekLIBC/backend.h>
    13 
    14 #define FALSE   0
    15 #define TRUE    1
    16 #define IS_PATH_DELIM(c) ((c)=='\\' || (c)=='/')
    17 
    18 int _abspath (char *dst, const char *src, int size)
     26#ifdef __OS2__
     27# include <InnoTekLIBC/pathrewrite.h>
     28extern int  __libc_gcchUnixRoot; /* fs.c  / b_fs.h */
     29#endif
     30
     31
     32/** Macro that advance the a_pszSrc variable past all leading slashes. */
     33#define _ABSPATH_SKIP_SLASHES(a_pszSrc) \
     34    do { \
     35        char ch = *(a_pszSrc); \
     36        while (__KLIBC_PATH_IS_SLASH(ch)) \
     37            ch = *++(a_pszSrc); \
     38    } while (0)
     39
     40
     41/**
     42 * Sets errno to ERANGE and returns -1.
     43 *
     44 * Convenience for dropping three lines of code for each overflow check and
     45 * return.
     46 *
     47 * @returns -1
     48 */
     49static int _abspath_overflow(void)
    1950{
    20   char drive, dir[MAXPATHLEN+1], src_drive, *s, *p;
    21   int i, j, rel, server, trail, mbcl;
    22   char chSlash = '/';
    23 
    24   s = alloca (strlen (src) + 1);
    25   strcpy (s, src);
    26   src_drive = _fngetdrive (s);
    27   if (src_drive == 0)
    28     drive = _getdrive ();
    29   else
    30     {
    31       drive = src_drive;
    32       s += 2;
    33     }
    34   dir[0] = 0; rel = FALSE; server = FALSE; trail = FALSE;
    35   if (IS_PATH_DELIM (*s))
    36     {
    37       ++s;
    38       if (IS_PATH_DELIM (*s))
    39         {
    40           ++s; server = TRUE;
    41         }
    42     }
    43   else if (__libc_Back_fsDirCurrentGet (dir, sizeof(dir), src_drive, __LIBC_BACK_FSCWD_NO_DRIVE | __LIBC_BACK_FSCWD_NO_ROOT_SLASH) == 0)
    44     _fnslashify (dir);
    45   else
    46     {
    47       dir[0] = 0;
    48       rel = TRUE;
    49     }
    50 
    51   while (*s != 0)
    52     {
    53       if (s[0] == '.' && (IS_PATH_DELIM (s[1]) || s[1] == 0))
    54         ++s;
    55       else if (s[0] == '.' && s[1] == '.' && (IS_PATH_DELIM (s[2]) ||
    56                                               s[2] == 0))
    57         {
    58           s += 2;
    59           if (*dir == 0)
    60             strcpy (dir, "..");
    61           else
    62             {
    63               p = _getname (dir);
    64               if (p == dir)
    65                 p[0] = 0;
    66               else
    67                 p[-1] = 0;
    68             }
    69         }
    70       else
    71         {
    72           i = strlen (dir);
    73           if (i < sizeof (dir) - 1)
    74             dir[i++] = chSlash;
    75           while (*s != 0)
    76             {
    77               if (CHK_MBCS_PREFIX (&__libc_GLocaleCtype, *s, mbcl) && s[1] != 0)
     51    errno = ERANGE;
     52    return -1;
     53}
     54
     55
     56/**
     57 * Figures out the length of the directory component @a pszSrc points to.
     58 *
     59 * @returns Length in bytes.
     60 * @param   pszSrc      Pointer to the path component which length we're
     61 *                      interested in.  This should not point to a slash but may
     62 *                      point to the zero terminator character.
     63 */
     64static int _abspath_component_length(const char *pszSrc)
     65{
     66    char const *psz = pszSrc;
     67    char        ch;
     68    assert(!__KLIBC_PATH_IS_SLASH(*psz));
     69
     70    if (__libc_GLocaleCtype.mbcs)
     71    {
     72        /* May contain multibyte chars. */
     73        int cchMultibyteCodepoint;
     74        while ((ch = *psz) != '\0')
     75        {
     76            if (   CHK_MBCS_PREFIX(&__libc_GLocaleCtype, ch, cchMultibyteCodepoint)
     77                && psz[1] != '\0')
     78                psz += cchMultibyteCodepoint;
     79            else if (__KLIBC_PATH_IS_SLASH(ch))
     80                break;
     81            else
     82                psz++;
     83        }
     84    }
     85    else
     86    {
     87        /* All chars are single byte. */
     88        while ((ch = *psz) != '\0' && !__KLIBC_PATH_IS_SLASH(ch))
     89            psz++;
     90    }
     91    return (int)(psz - pszSrc);
     92}
     93
     94
     95/**
     96 * Fixes the slashes of a current directory we got from the backend.
     97 *
     98 * @returns Length of the current returned directory.
     99 * @param   pszDst      The destination buffer which slashes we should fix.
     100 * @param   chSlash     The prefereed slash.
     101 */
     102static int _abspath_slashify(char *pszDst, char chSlash)
     103{
     104#ifdef __KLIBC_PATH_SLASH_ALT
     105    char *psz = pszDst;
     106    char  ch;
     107    if (__libc_GLocaleCtype.mbcs)
     108    {
     109        /* May contain multibyte chars. */
     110        int cchMultibyteCodepoint;
     111        while ((ch = *psz) != '\0')
     112        {
     113            if (   CHK_MBCS_PREFIX(&__libc_GLocaleCtype, ch, cchMultibyteCodepoint)
     114                && psz[1] != '\0')
     115                psz += cchMultibyteCodepoint;
     116            else if (__KLIBC_PATH_IS_SLASH(ch))
     117                *psz++ = chSlash;
     118            else
     119                psz++;
     120        }
     121    }
     122    else
     123    {
     124        /* All chars are single byte. */
     125        while ((ch = *psz) != '\0')
     126        {
     127            if (__KLIBC_PATH_IS_SLASH(ch))
     128                *psz = chSlash;
     129            psz++;
     130        }
     131    }
     132    return (int)(psz - pszDst);
     133#else
     134    return strlen(pszDst);
     135#endif
     136}
     137
     138
     139#ifdef __OS2__
     140/**
     141 * Checks if @a pszSrc points something that could be an OS/2 device name.
     142 *
     143 * @returns 1 if likely OS/2 device name, 0 if not.
     144 * @param   pszSrc          Pointer to the char after "/DEV/".
     145 */
     146static int _abspath_is_likely_device_name(const char *pszSrc)
     147{
     148    _ABSPATH_SKIP_SLASHES(pszSrc);
     149    size_t cch = _abspath_component_length(pszSrc);
     150    if (cch > 0 && cch <= 8 && !pszSrc[cch])
     151    {
     152        if (cch == 1)
     153            return pszSrc[0] != '.';
     154        if (cch == 2)
     155            return pszSrc[0] != '.' || pszSrc[1] != '.';
     156        return 1;
     157    }
     158    return 0;
     159}
     160#endif
     161
     162int _abspath(char *pszDst, const char *pszSrc, int cbDst)
     163{
     164    /*
     165     * Note! This code must mimick fsResolveUnix and fsResolveOS2
     166     */
     167#ifdef __KLIBC_PATH_SLASH_ALT
     168    char const chSlash = !__libc_gfNoUnix ? __KLIBC_PATH_SLASH_ALT : __KLIBC_PATH_SLASH;
     169#else
     170    char const chSlash = __KLIBC_PATH_SLASH;
     171#endif
     172#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
     173    char chDrive;
     174#endif
     175
     176    /*
     177     * Make a copy of the input if it overlaps the destination buffer
     178     * OR if the
     179     */
     180    size_t cchSrc = strlen(pszSrc);
     181    if (   (uintptr_t)pszDst <= (uintptr_t)pszSrc + cchSrc
     182        && (uintptr_t)pszDst + (uintptr_t)cbDst > (uintptr_t)pszSrc)
     183    {
     184        char *pszSrcCopy = (char *)alloca(cchSrc + 1);
     185        memcpy(pszSrcCopy, pszSrc, cchSrc + 1);
     186        pszSrc = pszSrcCopy;
     187    }
     188
     189    /*
     190     * Deal with the start of the path. This will skip
     191     */
     192    int cch;
     193    int offDst     = 0;
     194#define _ABSPATH_ERANGE_RETURN_CHECK(a_cbNeeded) if (cbDst <= (a_cbNeeded)) return _abspath_overflow(); else do {} while (0)
     195    int offDstRoot = 0;
     196    /* Root slash? */
     197    if (__KLIBC_PATH_IS_SLASH(*pszSrc))
     198    {
     199#ifdef __KLIBC_PATH_HAVE_UNC
     200        /*
     201         * Check for and deal with UNC.
     202         * Note! We do not allow '..' to advance higher than the share name,
     203         *       i.e. "//server/share/.." becomes "//server/share/".
     204         */
     205        /** @todo Should "//server/share/.." become "//server/share" rather than "//server/share/" ?? */
     206        if (   __KLIBC_PATH_IS_SLASH(pszSrc[1])
     207            && !__KLIBC_PATH_IS_SLASH(pszSrc[2])
     208            && pszSrc[2])
     209        {
     210            /* The server part. */
     211            cch = _abspath_component_length(&pszSrc[2]);
     212            _ABSPATH_ERANGE_RETURN_CHECK(2 + cch);
     213            pszDst[0] = chSlash;
     214            pszDst[1] = chSlash;
     215            memcpy(&pszDst[2], &pszSrc[2], cch);
     216            offDst = 2 + cch;
     217            pszSrc += 2 + cch;
     218
     219            /* The share/resource too, if present. */
     220            if (*pszSrc)
     221            {
     222                pszSrc++;
     223                _ABSPATH_SKIP_SLASHES(pszSrc);
     224                cch = _abspath_component_length(pszSrc);
     225                _ABSPATH_ERANGE_RETURN_CHECK(offDst + 1 + cch);
     226                pszDst[offDst++] = chSlash;
     227                memcpy(&pszDst[offDst], pszSrc, cch);
     228                offDst += cch;
     229                pszSrc += cch;
     230                if (*pszSrc)
    78231                {
    79                   if (i < sizeof (dir) - mbcl)
    80                     {
    81                       memcpy (dir + i, s, mbcl);
    82                       i += mbcl;
    83                     }
    84                   s += mbcl;
     232                    _ABSPATH_ERANGE_RETURN_CHECK(offDst + 1);
     233                    pszDst[offDst++] = chSlash;
     234                    pszSrc++;
     235                    _ABSPATH_SKIP_SLASHES(pszSrc);
    85236                }
    86               else if (IS_PATH_DELIM (*s))
     237            }
     238            offDstRoot = offDst;
     239        }
     240        else
     241#endif
     242        {
     243            /* Skip leading slashes before continuing. */
     244            pszSrc++;
     245            _ABSPATH_SKIP_SLASHES(pszSrc);
     246
     247            if (0)
     248            { /* nothing */ }
     249#ifdef __OS2__
     250            /*
     251             * The \pipe\ path is special on OS/2, all named pipes are invisibly
     252             * living under it.  May include subdirectories.  Unsure how '..' is
     253             * handled, but assuming the caller wants to straighten those out.
     254             */
     255            else if (   (pszSrc[0] == 'p' || pszSrc[0] == 'P')
     256                     && (pszSrc[1] == 'i' || pszSrc[1] == 'I')
     257                     && (pszSrc[2] == 'p' || pszSrc[2] == 'P')
     258                     && (pszSrc[3] == 'e' || pszSrc[3] == 'E')
     259                     && __KLIBC_PATH_IS_SLASH(pszSrc[4]) )
     260            {
     261                _ABSPATH_ERANGE_RETURN_CHECK(6);
     262                pszDst[0] = chSlash;
     263                pszDst[1] = 'P';
     264                pszDst[2] = 'I';
     265                pszDst[3] = 'P';
     266                pszDst[4] = 'E';
     267                pszDst[5] = chSlash;
     268                offDst = 6;
     269                pszSrc += 5;
     270                _ABSPATH_SKIP_SLASHES(pszSrc);
     271            }
     272            /*
     273             * The \dev\ path is also special on OS/2, all devices are visibly
     274             * (via stat but not readdir) living under it.  Names are limited
     275             * to 8 chars and there should not be any subdirs AFAIK.  Since we
     276             * may misidentify kNIX pseudo device path here, we do not
     277             * uppercase the prefix like we did for the pipes.
     278             */
     279            else if (   (pszSrc[0] == 'd' || pszSrc[0] == 'D')
     280                     && (pszSrc[1] == 'e' || pszSrc[1] == 'E')
     281                     && (pszSrc[2] == 'v' || pszSrc[2] == 'V')
     282                     && __KLIBC_PATH_IS_SLASH(pszSrc[3])
     283                     && _abspath_is_likely_device_name(&pszSrc[4]))
     284            {
     285                _ABSPATH_ERANGE_RETURN_CHECK(5);
     286                pszDst[0] = chSlash;
     287                pszDst[1] = pszSrc[0];
     288                pszDst[2] = pszSrc[1];
     289                pszDst[3] = pszSrc[2];
     290                pszDst[4] = chSlash;
     291                offDst = 5;
     292                pszSrc += 4;
     293                _ABSPATH_SKIP_SLASHES(pszSrc);
     294            }
     295            /*
     296             * If the path rewriter triggers on the path, we will not prefix
     297             * it with the current driver letter.
     298             */
     299            else if (__libc_PathRewrite(pszSrc - 1, NULL, 0) > 0)
     300            {
     301                _ABSPATH_ERANGE_RETURN_CHECK(1);
     302                pszDst[0] = chSlash;
     303                offDst = 1;
     304            }
     305#endif /* __OS2__ */
     306#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
     307            /*
     308             * Add the current drive letter before the root slash, unless we've
     309             * done chroot() or similar that means '/' refers to the unixroot
     310             * instead of the root of the current drive.
     311             */
     312            else if (   __libc_gcchUnixRoot == 0
     313                     && (chDrive = _getdrive()) > 0) /** @todo UNC CWD */
     314            {
     315                _ABSPATH_ERANGE_RETURN_CHECK(3);
     316                pszDst[0] = chDrive;
     317                pszDst[1] = ':';
     318                pszDst[2] = chSlash;
     319                offDst = 3;
     320            }
     321#endif
     322            /*
     323             * Unix root slash.
     324             */
     325            else
     326            {
     327                _ABSPATH_ERANGE_RETURN_CHECK(1);
     328                pszDst[0] = chSlash;
     329                offDst = 1;
     330            }
     331            offDstRoot = offDst;
     332        }
     333    }
     334#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
     335    /*
     336     * Drive letter?
     337     */
     338    else if (   pszSrc[0] != '\0'
     339             && pszSrc[1] == ':'
     340             && (chDrive = _fngetdrive(pszSrc)) )
     341    {
     342        if (__KLIBC_PATH_IS_SLASH(pszSrc[2]))
     343        {
     344            /*
     345             * Absolute path, great :-).
     346             */
     347            _ABSPATH_ERANGE_RETURN_CHECK(3);
     348            pszDst[0] = chDrive;
     349            pszDst[1] = ':';
     350            pszDst[2] = chSlash;
     351            offDstRoot = offDst = 3;
     352            pszSrc += 3;
     353            _ABSPATH_SKIP_SLASHES(pszSrc);
     354        }
     355        else
     356        {
     357            /*
     358             * Drive letter relative path.  Try add the current directory for
     359             * that drive.  If we cannot get it (drive letter exists or
     360             * something, return the relative path.
     361             */
     362            pszSrc += 2;
     363            int rc = __libc_Back_fsDirCurrentGet(pszDst, cbDst, chDrive, 0 /*fFlags*/);
     364            if (rc == -ERANGE)
     365                return _abspath_overflow();
     366            if (rc == 0)
     367            {
     368                offDst = _abspath_slashify(pszDst, chSlash);
     369                if (*pszSrc && pszDst[offDst - 1] != chSlash)
     370                {
     371                    _ABSPATH_ERANGE_RETURN_CHECK(offDst + 1);
     372                    pszDst[offDst++] = chSlash;
     373                }
     374                offDstRoot = 3;
     375            }
     376            else
     377            {
     378                pszDst[0] = chDrive;
     379                pszDst[1] = ':';
     380                offDstRoot = offDst = 2;
     381            }
     382        }
     383    }
     384#endif
     385    /*
     386     * The path is relative to the current directory, so try add it.
     387     */
     388    else if (*pszSrc)
     389    {
     390        int rc = __libc_Back_fsDirCurrentGet(pszDst, cbDst, '\0' /* current drive */, 0 /*fFlags*/);
     391        if (rc == -ERANGE)
     392            return _abspath_overflow();
     393        if (rc == 0)
     394        {
     395            offDst = _abspath_slashify(pszDst, chSlash);
     396            if (pszDst[offDst - 1] != chSlash)
     397            {
     398                _ABSPATH_ERANGE_RETURN_CHECK(offDst + 1);
     399                pszDst[offDst++] = chSlash;
     400            }
     401
     402#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
     403            if (_fngetdrive(pszDst))
     404                offDstRoot = __KLIBC_PATH_IS_SLASH(pszDst[2]) ? 3 : 2;
     405            else
     406#endif
     407#ifdef __KLIBC_PATH_HAVE_UNC
     408            if (   __KLIBC_PATH_IS_SLASH(pszDst[0])
     409                && __KLIBC_PATH_IS_SLASH(pszDst[1]))
     410            {
     411                offDstRoot = 2 + _abspath_component_length(&pszSrc[2]);
     412                if (pszDst[offDstRoot])
     413                    offDstRoot += 1 + _abspath_component_length(&pszSrc[offDstRoot]);
     414            }
     415            else
     416#endif
     417                offDstRoot = 1;
     418        }
     419        else
     420            offDstRoot = offDst = 0;
     421    }
     422    /*
     423     * Input is empty. Return empty path and failure.
     424     */
     425    else
     426    {
     427        if (cbDst)
     428            *pszDst = '\0';
     429        errno = EINVAL;
     430        return -1;
     431    }
     432
     433    /*
     434     * Straighten out '.', '..' and slashes.  There shall be no slash at the
     435     * head of the input string at this point, that way we can more easily
     436     * handle the trailing directory slash correctly.  It is also assumed that
     437     * offDstRoot - 1 is a slash or similar, so we can back up all the way to
     438     * offDstRoot without needing to thing about slashes.
     439     */
     440    int offParentDir = -1;
     441    for (;;)
     442    {
     443        assert(!__KLIBC_PATH_IS_SLASH(*pszSrc));
     444        cch = _abspath_component_length(pszSrc);
     445        if (!cch)
     446            break;
     447
     448        /*
     449         * "." - No change.
     450         */
     451        if (cch == 1 && pszSrc[0] == '.')
     452        {
     453            pszSrc++;
     454            if (!*pszSrc)
     455            {
     456                if (offDst > offDstRoot)
     457                    offDst--; /* No trailing slash. */
    87458                break;
    88               else
     459            }
     460            pszSrc++;
     461        }
     462        /*
     463         * ".." - Drop the last directory in the destination path (unless we're
     464         * constructing a relative path because we couldn't for some reason or
     465         * other resolve the current directory).
     466         */
     467        else if (cch == 2 && pszSrc[0] == '.' && pszSrc[1] == '.' && offDstRoot != 0)
     468        {
     469            if (offDst > offDstRoot)
     470            {
     471                if (offParentDir > 0)
     472                    offDst = offParentDir;
     473                else
    89474                {
    90                   if (i < sizeof (dir) - 1)
    91                     dir[i++] = *s;
    92                   ++s;
     475                    pszDst[offDst - 1] = '\0'; /* terminate and drop trailing slash. */
     476                    offDst = (int)(_getname(&pszDst[offDstRoot]) - pszDst);
    93477                }
    94478            }
    95           dir[i] = 0;
    96         }
    97       if (*s != 0)
    98         {
    99           ++s;
    100           if (*s == 0)
    101             trail = 1;
    102         }
    103     }
    104   i = 0;
    105   if (i+1 < size && (src_drive != 0 || !server))
    106     {
    107       dst[i++] = drive;
    108       dst[i++] = ':';
    109     }
    110   if (i < size && server && src_drive == 0)
    111     dst[i++] = chSlash;
    112   if (i < size && !rel && dir[0] != chSlash)
    113     dst[i++] = chSlash;
    114   j = 0;
    115   if (rel && dir[j] == chSlash)
    116     ++j;
    117   while (i < size && dir[j] != 0)
    118     dst[i++] = dir[j++];
    119   if (trail && i < size && (i == 0 || dst[i-1] != chSlash))
    120     dst[i++] = chSlash;
    121   if (i >= size)
    122     {
    123       dir[size-1] = 0;
    124       errno = ERANGE;
    125       return -1;
    126     }
    127   dst[i] = 0;
    128   return 0;
     479            /* else: Hit the root, it's ".." link doesn't go anywhere. So ignore it. */
     480            offParentDir = -1;
     481
     482            pszSrc += 2;
     483            if (!*pszSrc)
     484            {
     485                if (offDst > offDstRoot)
     486                    offDst--; /* No trailing slash. */
     487                break;
     488            }
     489            pszSrc++;
     490        }
     491        /*
     492         * Append the component and trailing slash, if any.
     493         */
     494        else
     495        {
     496            offParentDir = offDst; /* Cache the last dir offset for efficient '..' handling. */
     497            if (!pszSrc[cch])
     498            {
     499                _ABSPATH_ERANGE_RETURN_CHECK(offDst + cch);
     500                memcpy(&pszDst[offDst], pszSrc, cch);
     501                offDst += cch;
     502                break;
     503            }
     504
     505            _ABSPATH_ERANGE_RETURN_CHECK(offDst + cch + 1);
     506            memcpy(&pszDst[offDst], pszSrc, cch);
     507            offDst += cch;
     508            pszDst[offDst++] = chSlash;
     509            pszSrc += cch + 1;
     510        }
     511
     512        /* Skip extra slashes before we go on to the next component or reach
     513           the end of the string. */
     514        _ABSPATH_SKIP_SLASHES(pszSrc);
     515    }
     516
     517    /*
     518     * Add the terminator and return.
     519     */
     520    if (offDst < cbDst)
     521    {
     522        pszDst[offDst] = '\0';
     523        return 0;
     524    }
     525    return _abspath_overflow();
    129526}
     527
  • trunk/libc/tests/libc/Makefile

    r3914 r3925  
    5858SMOKETESTS := \
    5959        smoketests/64bitio-1.c \
     60        smoketests/abspath-1.c \
    6061        smoketests/access-1.c \
    6162        smoketests/alloca-1.c \
Note: See TracChangeset for help on using the changeset viewer.