diff options
Diffstat (limited to 'src/lib/path-util.c')
-rw-r--r-- | src/lib/path-util.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/src/lib/path-util.c b/src/lib/path-util.c new file mode 100644 index 0000000..90e1e46 --- /dev/null +++ b/src/lib/path-util.c @@ -0,0 +1,398 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "path-util.h" + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +#define PATH_UTIL_MAX_PATH 8*1024 +#define PATH_UTIL_MAX_SYMLINKS 80 + +static int t_getcwd_noalloc(char **dir_r, size_t *asize_r, + const char **error_r) ATTR_NULL(2) +{ + /* @UNSAFE */ + char *dir; + size_t asize = 128; + + dir = t_buffer_get(asize); + while (getcwd(dir, asize) == NULL) { + if (errno != ERANGE) { + *error_r = t_strdup_printf("getcwd() failed: %m"); + return -1; + } + asize = nearest_power(asize+1); + dir = t_buffer_get(asize); + } + if (asize_r != NULL) + *asize_r = asize; + *dir_r = dir; + return 0; +} + +static int path_normalize(const char *path, bool resolve_links, + const char **npath_r, const char **error_r) +{ + /* @UNSAFE */ + unsigned int link_count = 0; + char *npath, *npath_pos; + const char *p; + size_t asize; + + i_assert(path != NULL); + i_assert(npath_r != NULL); + i_assert(error_r != NULL); + + if (path[0] != '/') { + /* relative; initialize npath with current directory */ + if (t_getcwd_noalloc(&npath, &asize, error_r) < 0) + return -1; + npath_pos = npath + strlen(npath); + i_assert(npath[0] == '/'); + } else { + /* absolute; initialize npath with root */ + asize = 128; + npath = t_buffer_get(asize); + npath[0] = '/'; + npath_pos = npath + 1; + } + + p = path; + while (*p != '\0') { + struct stat st; + ptrdiff_t seglen; + const char *segend; + + /* skip duplicate slashes */ + while (*p == '/') + p++; + + /* find end of path segment */ + for (segend = p; *segend != '\0' && *segend != '/'; segend++); + + if (segend == p) + break; /* '\0' */ + seglen = segend - p; + if (seglen == 1 && p[0] == '.') { + /* a reference to this segment; nothing to do */ + } else if (seglen == 2 && p[0] == '.' && p[1] == '.') { + /* a reference to parent segment; back up to previous + * slash */ + i_assert(npath_pos >= npath); + if ((npath_pos - npath) > 1) { + if (*(npath_pos-1) == '/') + npath_pos--; + for (; *(npath_pos-1) != '/'; npath_pos--); + } + } else { + /* allocate space if necessary */ + i_assert(npath_pos >= npath); + if ((size_t)((npath_pos - npath) + seglen + 1) >= asize) { + ptrdiff_t npath_offset = npath_pos - npath; + asize = nearest_power(npath_offset + seglen + 2); + npath = t_buffer_reget(npath, asize); + npath_pos = npath + npath_offset; + } + + /* make sure npath now ends in slash */ + i_assert(npath_pos > npath); + if (*(npath_pos-1) != '/') { + i_assert((size_t)((npath_pos - npath) + 1) < asize); + *(npath_pos++) = '/'; + } + + /* copy segment to normalized path */ + i_assert(npath_pos >= npath); + i_assert((size_t)((npath_pos - npath) + seglen) < asize); + memmove(npath_pos, p, seglen); + npath_pos += seglen; + } + + if (resolve_links) { + /* stat path up to here (segend points to tail) */ + *npath_pos = '\0'; + if (lstat(npath, &st) < 0) { + *error_r = t_strdup_printf("lstat() failed: %m"); + return -1; + } + + if (S_ISLNK (st.st_mode)) { + /* symlink */ + char *npath_link; + size_t lsize = 128, tlen = strlen(segend), espace; + size_t ltlen = (link_count == 0 ? 0 : tlen); + ssize_t ret; + + /* limit link dereferences */ + if (++link_count > PATH_UTIL_MAX_SYMLINKS) { + errno = ELOOP; + *error_r = "Too many symlink dereferences"; + return -1; + } + + /* allocate space for preserving tail of previous symlink and + first attempt at reading symlink with room for the tail + + buffer will look like this: + [npath][0][preserved tail][link buffer][room for tail][0] + */ + espace = ltlen + tlen + 2; + i_assert(npath_pos >= npath); + if ((size_t)((npath_pos - npath) + espace + lsize) >= asize) { + ptrdiff_t npath_offset = npath_pos - npath; + asize = nearest_power((npath_offset + espace + lsize) + 1); + lsize = asize - (npath_offset + espace); + npath = t_buffer_reget(npath, asize); + npath_pos = npath + npath_offset; + } + + if (ltlen > 0) { + /* preserve tail just after end of npath */ + i_assert(npath_pos >= npath); + i_assert((size_t)((npath_pos + 1 - npath) + ltlen) < asize); + memmove(npath_pos + 1, segend, ltlen); + } + + /* read the symlink after the preserved tail */ + for (;;) { + npath_link = (npath_pos + 1) + ltlen; + + i_assert(npath_link >= npath_pos); + i_assert((size_t)((npath_link - npath) + lsize) < asize); + + /* attempt to read the link */ + if ((ret=readlink(npath, npath_link, lsize)) < 0) { + *error_r = t_strdup_printf("readlink() failed: %m"); + return -1; + } + if ((size_t)ret < lsize) { + /* POSIX doesn't guarantee the presence of a NIL */ + npath_link[ret] = '\0'; + break; + } + + /* sum of new symlink content length + * and path tail length may not + exceed maximum */ + if ((size_t)(ret + tlen) >= PATH_UTIL_MAX_PATH) { + errno = ENAMETOOLONG; + *error_r = "Resulting path is too long"; + return -1; + } + + /* try again with bigger buffer, + we need to allocate more space as well if lsize == ret, + because the returned link may have gotten truncated */ + espace = ltlen + tlen + 2; + i_assert(npath_pos >= npath); + if ((size_t)((npath_pos - npath) + espace + lsize) >= asize || + lsize == (size_t)ret) { + ptrdiff_t npath_offset = npath_pos - npath; + asize = nearest_power((npath_offset + espace + lsize) + 1); + lsize = asize - (npath_offset + espace); + npath = t_buffer_reget(npath, asize); + npath_pos = npath + npath_offset; + } + } + + /* add tail of previous path at end of symlink */ + i_assert(npath_link >= npath); + if (ltlen > 0) { + i_assert(npath_pos >= npath); + i_assert((size_t)((npath_pos - npath) + 1 + tlen) < asize); + i_assert((size_t)((npath_link - npath) + ret + tlen) < asize); + memcpy(npath_link + ret, npath_pos + 1, tlen); + } else { + i_assert((size_t)((npath_link - npath) + ret + tlen) < asize); + memcpy(npath_link + ret, segend, tlen); + } + *(npath_link+ret+tlen) = '\0'; + + /* use as new source path */ + path = segend = npath_link; + + if (path[0] == '/') { + /* absolute symlink; start over at root */ + npath_pos = npath + 1; + } else { + /* relative symlink; back up to previous segment */ + i_assert(npath_pos >= npath); + if ((npath_pos - npath) > 1) { + if (*(npath_pos-1) == '/') + npath_pos--; + for (; *(npath_pos-1) != '/'; npath_pos--); + } + } + + } else if (*segend != '\0' && !S_ISDIR (st.st_mode)) { + /* not last segment, but not a directory either */ + errno = ENOTDIR; + *error_r = t_strdup_printf("Not a directory: %s", npath); + return -1; + } + } + + p = segend; + } + + i_assert(npath_pos >= npath); + i_assert((size_t)(npath_pos - npath) < asize); + + /* remove any trailing slash */ + if ((npath_pos - npath) > 1 && *(npath_pos-1) == '/') + npath_pos--; + *npath_pos = '\0'; + + t_buffer_alloc(npath_pos - npath + 1); + *npath_r = npath; + return 0; +} + +int t_normpath(const char *path, const char **npath_r, const char **error_r) +{ + return path_normalize(path, FALSE, npath_r, error_r); +} + +int t_normpath_to(const char *path, const char *root, const char **npath_r, + const char **error_r) +{ + i_assert(path != NULL); + i_assert(root != NULL); + i_assert(npath_r != NULL); + + if (*path == '/') + return t_normpath(path, npath_r, error_r); + + return t_normpath(t_strconcat(root, "/", path, NULL), npath_r, error_r); +} + +int t_realpath(const char *path, const char **npath_r, const char **error_r) +{ + return path_normalize(path, TRUE, npath_r, error_r); +} + +int t_realpath_to(const char *path, const char *root, const char **npath_r, + const char **error_r) +{ + i_assert(path != NULL); + i_assert(root != NULL); + i_assert(npath_r != NULL); + + if (*path == '/') + return t_realpath(path, npath_r, error_r); + + return t_realpath(t_strconcat(root, "/", path, NULL), npath_r, error_r); +} + +int t_abspath(const char *path, const char **abspath_r, const char **error_r) +{ + i_assert(path != NULL); + i_assert(abspath_r != NULL); + i_assert(error_r != NULL); + + if (*path == '/') { + *abspath_r = path; + return 0; + } + + const char *dir, *error; + if (t_get_working_dir(&dir, &error) < 0) { + *error_r = t_strconcat("Failed to get working directory: ", + error, NULL); + return -1; + } + *abspath_r = t_strconcat(dir, "/", path, NULL); + return 0; +} + +const char *t_abspath_to(const char *path, const char *root) +{ + i_assert(path != NULL); + i_assert(root != NULL); + + if (*path == '/') + return path; + + return t_strconcat(root, "/", path, NULL); +} + +int t_get_working_dir(const char **dir_r, const char **error_r) +{ + char *dir; + + i_assert(dir_r != NULL); + i_assert(error_r != NULL); + if (t_getcwd_noalloc(&dir, NULL, error_r) < 0) + return -1; + + t_buffer_alloc(strlen(dir) + 1); + *dir_r = dir; + return 0; +} + +int t_readlink(const char *path, const char **dest_r, const char **error_r) +{ + i_assert(error_r != NULL); + + /* @UNSAFE */ + ssize_t ret; + char *dest; + size_t size = 128; + + dest = t_buffer_get(size); + while ((ret = readlink(path, dest, size)) >= (ssize_t)size) { + size = nearest_power(size+1); + dest = t_buffer_get(size); + } + if (ret < 0) { + *error_r = t_strdup_printf("readlink() failed: %m"); + return -1; + } + + dest[ret] = '\0'; + t_buffer_alloc(ret + 1); + *dest_r = dest; + return 0; +} + +bool t_binary_abspath(const char **binpath, const char **error_r) +{ + const char *path_env, *const *paths; + string_t *path; + + if (**binpath == '/') { + /* already have absolute path */ + return TRUE; + } else if (strchr(*binpath, '/') != NULL) { + /* relative to current directory */ + const char *error; + if (t_abspath(*binpath, binpath, &error) < 0) { + *error_r = t_strdup_printf("t_abspath(%s) failed: %s", + *binpath, error); + return FALSE; + } + return TRUE; + } else if ((path_env = getenv("PATH")) != NULL) { + /* we have to find our executable from path */ + path = t_str_new(256); + paths = t_strsplit(path_env, ":"); + for (; *paths != NULL; paths++) { + str_append(path, *paths); + str_append_c(path, '/'); + str_append(path, *binpath); + if (access(str_c(path), X_OK) == 0) { + *binpath = str_c(path); + return TRUE; + } + str_truncate(path, 0); + } + *error_r = "Could not find the wanted executable from PATH"; + return FALSE; + } else { + *error_r = "PATH environment variable undefined"; + return FALSE; + } +} |