Skip to content

Commit

Permalink
lib: Add Pigeonhole realpath functions to path-util
Browse files Browse the repository at this point in the history
  • Loading branch information
mrannanj authored and GitLab committed Jan 30, 2017
1 parent f0913ba commit f6925c4
Show file tree
Hide file tree
Showing 2 changed files with 300 additions and 17 deletions.
283 changes: 266 additions & 17 deletions src/lib/path-util.c
Expand Up @@ -5,6 +5,270 @@
#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_alloc(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_alloc(&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 shashes */
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 */
if (npath_pos > npath + 1) {
if (*(npath_pos-1) == '/')
npath_pos--;
for (; *(npath_pos-1) != '/'; npath_pos--);
}
} else {
/* make sure npath now ends in slash */
if (*(npath_pos-1) != '/') {
i_assert(npath_pos + 1 < npath + asize);
*(npath_pos++) = '/';
}

/* allocate space if necessary */
if ((npath_pos + seglen + 1) >= (npath + 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;
}

/* copy segment to normalized path */
i_assert(p + seglen < npath + asize);
(void)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;
if ((npath_pos + espace + lsize) >= (npath + 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 */
(void)memmove(npath_pos + 1, segend, ltlen);
}

/* read the symlink after the preserved tail */
for (;;) {
npath_link = (npath_pos + 1) + ltlen;

i_assert(npath_link + lsize < npath + 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) {
/* make static analyzers happy */
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 */
espace = ltlen + tlen + 2;
if ((npath_pos + espace + lsize) >= (npath + 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;
}
}

/* add tail of previous path at end of symlink */
if (ltlen > 0) {
i_assert(npath_pos + 1 + tlen < npath + asize);
(void)memcpy(npath_link + ret, npath_pos + 1, tlen);
} else {
i_assert(segend + tlen < npath + asize);
(void)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 */
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 + 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);
}

const char *t_abspath(const char *path)
{
Expand Down Expand Up @@ -32,24 +296,9 @@ const char *t_abspath_to(const char *path, const char *root)

int t_get_working_dir(const char **dir_r, const char **error_r)
{
i_assert(dir_r != NULL);
i_assert(error_r != NULL);

/* @UNSAFE */
char *dir;
size_t size = 128;

dir = t_buffer_get(size);
while (getcwd(dir, size) == NULL) {
if (errno != ERANGE) {
*error_r = t_strdup_printf("getcwd() failed: %m");
return -1;
}
size = nearest_power(size+1);
dir = t_buffer_get(size);
}
t_buffer_alloc(strlen(dir) + 1);
*dir_r = dir;
return 0;
return t_getcwd_alloc((char**)dir_r, NULL, error_r);
}

int t_readlink(const char *path, const char **dest_r, const char **error_r)
Expand Down
34 changes: 34 additions & 0 deletions src/lib/path-util.h
@@ -1,6 +1,40 @@
#ifndef PATH_UTIL_H
#define PATH_UTIL_H

/* Returns path as the normalized absolute path, which means that './'
* and '../' components are resolved, and that duplicate and trailing
* slashes are removed. If it's not already the absolute path, it's
* assumed to be relative to the current working directory.
*
* NOTE: Be careful with this function. The resolution of '../' components
* with the parent component as if it were a normal directory is not valid
* if the path contains symbolic links.
*
* Returns 0 on success, and -1 on failure. errno and error_r are set on
* failure, and error_r cannot be NULL.
*/
int t_normpath(const char *path, const char **npath_r, const char **error_r);
/* Like t_normpath(), but path is relative to given root. */
int t_normpath_to(const char *path, const char *root, const char **npath_r,
const char **error_r);

/* Returns path as the real normalized absolute path, which means that all
* symbolic links in the path are resolved, that './' and '../' components
* are resolved, and that duplicate and trailing slashes are removed. If it's
* not already the absolute path, it's assumed to be relative to the current
* working directory.
*
* NOTE: This function calls stat() for each path component and more when
* there are symbolic links (just like POSIX realpath()).
*
* Returns 0 on success, and -1 on failure. errno and error_r are set on
* failure, and error_r cannot be NULL.
*/
int t_realpath(const char *path, const char **npath_r, const char **error_r);
/* Like t_realpath(), but path is relative to given root. */
int t_realpath_to(const char *path, const char *root, const char **npath_r,
const char **error_r);

/* Returns path as absolute path. If it's not already absolute path,
* it's assumed to be relative to current working directory.
*
Expand Down

0 comments on commit f6925c4

Please sign in to comment.