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(const 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 *last_slash = strrchr(path, '/');
 141                if (last_slash) {
 142                        *last_slash++ = '\0';
 143                        buf = xmalloc_realpath(path);
 144                        if (buf) {
 145                                unsigned len = strlen(buf);
 146                                buf = xrealloc(buf, len + strlen(last_slash) + 2);
 147                                buf[len++] = '/';
 148                                strcpy(buf + len, last_slash);
 149                        }
 150                } else {
 151                        char *target = xmalloc_readlink(path);
 152                        if (target) {
 153                                char *cwd;
 154                                if (target[0] == '/') {
 155                                        /*
 156                                         * $ ln -s /bin/qwe symlink  # note: /bin is a link to /usr/bin
 157                                         * $ readlink -f symlink
 158                                         * /usr/bin/qwe/target_does_not_exist
 159                                         * $ realpath symlink
 160                                         * /usr/bin/qwe/target_does_not_exist
 161                                         */
 162                                        buf = xmalloc_realpath_coreutils(target);
 163                                        free(target);
 164                                        return buf;
 165                                }
 166                                /*
 167                                 * $ ln -s target_does_not_exist symlink
 168                                 * $ readlink -f symlink
 169                                 * /CURDIR/target_does_not_exist
 170                                 * $ realpath symlink
 171                                 * /CURDIR/target_does_not_exist
 172                                 */
 173                                cwd = xrealloc_getcwd_or_warn(NULL);
 174                                buf = concat_path_file(cwd, target);
 175                                free(cwd);
 176                                free(target);
 177                                return buf;
 178                        }
 179                }
 180        }
 181
 182        return buf;
 183}
 184