busybox/libbb/xreadlink.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * xreadlink.c - safe implementation of readlink.
   4 * Returns a NULL on failure.
   5 *
   6 * Licensed under GPLv2, see file LICENSE in this source tree.
   7 */
   8#include "libbb.h"
   9
  10/* Some systems (eg Hurd) do not have MAXSYMLINKS definition,
  11 * set it to some reasonable value if it isn't defined */
  12#ifndef MAXSYMLINKS
  13# define MAXSYMLINKS 20
  14#endif
  15
  16/*
  17 * NOTE: This function returns a malloced char* that you will have to free
  18 * yourself.
  19 */
  20char* FAST_FUNC xmalloc_readlink(const char *path)
  21{
  22        enum { GROWBY = 80 }; /* how large we will grow strings by */
  23
  24        char *buf = NULL;
  25        int bufsize = 0, readsize = 0;
  26
  27        do {
  28                bufsize += GROWBY;
  29                buf = xrealloc(buf, bufsize);
  30                readsize = readlink(path, buf, bufsize);
  31                if (readsize == -1) {
  32                        free(buf);
  33                        return NULL;
  34                }
  35        } while (bufsize < readsize + 1);
  36
  37        buf[readsize] = '\0';
  38
  39        return buf;
  40}
  41
  42/*
  43 * This routine is not the same as realpath(), which
  44 * canonicalizes the given path completely. This routine only
  45 * follows trailing symlinks until a real file is reached and
  46 * returns its name. If the path ends in a dangling link or if
  47 * the target doesn't exist, the path is returned in any case.
  48 * Intermediate symlinks in the path are not expanded -- only
  49 * those at the tail.
  50 * A malloced char* is returned, which must be freed by the caller.
  51 */
  52char* FAST_FUNC xmalloc_follow_symlinks(const char *path)
  53{
  54        char *buf;
  55        char *lpc;
  56        char *linkpath;
  57        int bufsize;
  58        int looping = MAXSYMLINKS + 1;
  59
  60        buf = xstrdup(path);
  61        goto jump_in;
  62
  63        while (1) {
  64                linkpath = xmalloc_readlink(buf);
  65                if (!linkpath) {
  66                        /* not a symlink, or doesn't exist */
  67                        if (errno == EINVAL || errno == ENOENT)
  68                                return buf;
  69                        goto free_buf_ret_null;
  70                }
  71
  72                if (!--looping) {
  73                        free(linkpath);
  74 free_buf_ret_null:
  75                        free(buf);
  76                        return NULL;
  77                }
  78
  79                if (*linkpath != '/') {
  80                        bufsize += strlen(linkpath);
  81                        buf = xrealloc(buf, bufsize);
  82                        lpc = bb_get_last_path_component_strip(buf);
  83                        strcpy(lpc, linkpath);
  84                        free(linkpath);
  85                } else {
  86                        free(buf);
  87                        buf = linkpath;
  88 jump_in:
  89                        bufsize = strlen(buf) + 1;
  90                }
  91        }
  92}
  93
  94char* FAST_FUNC xmalloc_readlink_or_warn(const char *path)
  95{
  96        char *buf = xmalloc_readlink(path);
  97        if (!buf) {
  98                /* EINVAL => "file: Invalid argument" => puzzled user */
  99                const char *errmsg = "not a symlink";
 100                int err = errno;
 101                if (err != EINVAL)
 102                        errmsg = strerror(err);
 103                bb_error_msg("%s: cannot read link: %s", path, errmsg);
 104        }
 105        return buf;
 106}
 107
 108char* FAST_FUNC xmalloc_realpath(const char *path)
 109{
 110/* NB: uclibc also defines __GLIBC__
 111 * Therefore the test "if glibc, or uclibc >= 0.9.31" looks a bit weird:
 112 */
 113#if defined(__GLIBC__) && \
 114    (!defined(__UCLIBC__) || UCLIBC_VERSION >= KERNEL_VERSION(0, 9, 31))
 115        /* glibc provides a non-standard extension */
 116        /* new: POSIX.1-2008 specifies this behavior as well */
 117        return realpath(path, NULL);
 118#else
 119        char buf[PATH_MAX+1];
 120
 121        /* on error returns NULL (xstrdup(NULL) == NULL) */
 122        return xstrdup(realpath(path, buf));
 123#endif
 124}
 125
 126char* FAST_FUNC xmalloc_realpath_coreutils(char *path)
 127{
 128        char *buf;
 129
 130        errno = 0;
 131        buf = xmalloc_realpath(path);
 132        /*
 133         * There is one case when "readlink -f" and
 134         * "realpath" from coreutils succeed,
 135         * even though file does not exist, such as:
 136         *     /tmp/file_does_not_exist
 137         * (the directory must exist).
 138         */
 139        if (!buf && errno == ENOENT) {
 140                char *target, c, *last_slash;
 141                size_t i;
 142
 143                target = xmalloc_readlink(path);
 144                if (target) {
 145                        /*
 146                         * $ ln -s /bin/qwe symlink  # note: /bin is a link to /usr/bin
 147                         * $ readlink -f symlink
 148                         * /usr/bin/qwe
 149                         * $ realpath symlink
 150                         * /usr/bin/qwe
 151                         */
 152                        if (target[0] != '/') {
 153                                /*
 154                                 * $ ln -s target_does_not_exist symlink
 155                                 * $ readlink -f symlink
 156                                 * /CURDIR/target_does_not_exist
 157                                 * $ realpath symlink
 158                                 * /CURDIR/target_does_not_exist
 159                                 */
 160                                char *cwd = xrealloc_getcwd_or_warn(NULL);
 161                                char *tmp = concat_path_file(cwd, target);
 162                                free(cwd);
 163                                free(target);
 164                                target = tmp;
 165                        }
 166                        buf = xmalloc_realpath_coreutils(target);
 167                        free(target);
 168                        return buf;
 169                }
 170
 171                /* ignore leading and trailing slashes */
 172                while (path[0] == '/' && path[1] == '/')
 173                        ++path;
 174                i = strlen(path) - 1;
 175                while (i > 0 && path[i] == '/')
 176                        i--;
 177                c = path[i + 1];
 178                path[i + 1] = '\0';
 179
 180                last_slash = strrchr(path, '/');
 181                if (last_slash == path)
 182                        buf = xstrdup(path);
 183                else if (last_slash) {
 184                        *last_slash = '\0';
 185                        buf = xmalloc_realpath(path);
 186                        *last_slash++ = '/';
 187                        if (buf) {
 188                                unsigned len = strlen(buf);
 189                                buf = xrealloc(buf, len + strlen(last_slash) + 2);
 190                                buf[len++] = '/';
 191                                strcpy(buf + len, last_slash);
 192                        }
 193                }
 194                path[i + 1] = c;
 195        }
 196
 197        return buf;
 198}
 199