From: Bradford C. Smith <bradford.carl.smith@xxxxxxxxx> Added the following routines: is_absolute_path() split_path() join_path() is_valid_path() make_absolute_path() Signed-off-by: Bradford C. Smith <bradford.carl.smith@xxxxxxxxx> --- cache.h | 5 ++ path.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+), 0 deletions(-) diff --git a/cache.h b/cache.h index 53801b8..8480716 100644 --- a/cache.h +++ b/cache.h @@ -356,6 +356,11 @@ enum sharedrepo { }; int git_config_perm(const char *var, const char *value); int adjust_shared_perm(const char *path); +int is_absolute_path(char *p); +void split_path(const char *p, char *start, char *rest); +void join_path(char *p, const char *start, const char *rest); +int is_valid_path(const char *p); +char *make_absolute_path(char *p); int safe_create_leading_directories(char *path); char *enter_repo(char *path, int strict); diff --git a/path.c b/path.c index dfff41f..2d14677 100644 --- a/path.c +++ b/path.c @@ -288,3 +288,250 @@ int adjust_shared_perm(const char *path) return -2; return 0; } + + +int is_absolute_path(char *p) +{ + return p[0] == '/'; +} + +static void strip_trailing_slashes(char *p) +{ + char *r = strrchr(p, '/'); + + if (!r) + return; /* no slashes at all */ + if (*(r + 1) != '\0') + return; /* last slash is not at the end */ + /* + * last character is a slash, back up overwriting slashes with + * nulls until I find a non-null or the beginning of p, but + * don't overwrite a '/' at the beginning of p. + */ + while (r > p && *r == '/') { + *r = '\0'; + r--; + } +} + +/* + * p = path name that will fit in a PATH_MAX size buffer + * start = NULL or PATH_MAX size buffer + * rest = NULL or PATH_MAX size buffer + * + * split p on the last slash that isn't a trailing slash. + * Copy everything before the slash into start if it is not NULL. Copy + * everything after the slash, except trailing slashes, into rest if it + * is not NULL. The slash itself isn't put in either one. + * + * If p contains no non-trailing slashes, all of p will be put into + * start and rest will be an empty string. + * + * This routine is meant to be the exact reverse of join_path() as long + * as p has no trailing slashes. If p has trailing slashes, spliting + * and rejoining will cause them to disappear. + * + * NOTE: p is copied into a temporary buffer, so it is safe for start or + * rest to point into p. + * NOTE: if p is too big to fit in a PATH_MAX size buffer, it will be + * silently truncated when copied to the temporary buffer. + */ +void split_path(const char *p, char *start, char *rest) +{ + char buf[PATH_MAX]; + char * last_slash; + const char * after_slash; + + strncpy(buf, p, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + strip_trailing_slashes(buf); + last_slash = strrchr(buf, '/'); + if (last_slash) { + *last_slash = '\0'; + after_slash = last_slash + 1; + } else { + after_slash = ""; + } + if (start) { + strcpy(start, buf); + } + if (rest) { + strcpy(rest, after_slash); + } +} + +/* + * p = PATH_MAX size buffer to hold result + * start = beginning of a path (shorter than PATH_MAX) + * rest = end of a path (shorter than PATH_MAX) + * + * fill p with start + '/' + rest, removing any trailing slashes from + * the result. If the result is too big to fit in a PATH_MAX size + * buffer, it will be silently truncated. + * + * NOTE: This routine uses a temporary buffer to hold the result, so it + * is safe to have start or rest pointing into p. + */ +void join_path(char *p, const char *start, const char *rest) +{ + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "%s/%s", start, rest); + strip_trailing_slashes(buf); + strcpy(p, buf); +} + +/* + * p = path that will fit in a PATH_MAX size buffer + * + * return true if p is a valid path, false otherwise + * + * p is considered valid if + * 1. stat(p) succeeds + * OR + * 2. stat(p) fails with ENOENT and I can successfully stat() the + * directory part of p and see that it is a directory. + * + * NOTE: The caller must ensure that p will fit in a PATH_MAX size + * buffer. + */ +int is_valid_path(const char *p) +{ + char dir[PATH_MAX]; + struct stat st; + + if (stat(p, &st) == 0) { + return 1; + } + if (errno != ENOENT) { + /* + * there's something wrong with p other than it just not + * existing + */ + return 0; + } + split_path(p, dir, NULL); + if (dir[0] == '\0') { + /* path is '/something' and '/' always exists */ + return 1; + } + return (stat(dir, &st) == 0) && S_ISDIR(st.st_mode); +} + +/* + * p = PATH_MAX size buffer containing a path that may specify a symlink + * + * If p is a symlink, overwrite p with the target of the symlink. If + * the target would be too big to fit in a PATH_MAX size buffer, p will + * not be overwritten. + * + * Returns true if p is overwritten, false otherwise. + */ +static int expand_symlink(char *p) +{ + char buf[PATH_MAX]; + size_t len; + + /* don't try to expand a symlink in an invalid path */ + if (!is_valid_path(p)) { + return 0; + } + len = readlink(p, buf, sizeof(buf)); + if (len < 0) { + return 0; /* not a symlink or couldn't read it */ + } + if (len >= sizeof(buf)) { + return 0; /* link too long to expand */ + } + buf[len] = '\0'; /* readlink() doesn't null terminate */ + if (is_absolute_path(buf)) { + strcpy(p, buf); + return 1; + } else { + /* replace basename with relative symlink */ + char dir[PATH_MAX]; + + split_path(p, dir, NULL); + if ((strlen(dir) + 1 + strlen(buf)) < PATH_MAX) { + join_path(p, dir, buf); + return 1; + } else { + /* link too big to fit in p */ + return 0; + } + } +} + +/* + * p = absolute path in a PATH_MAX size buffer + * + * Attempt to replace contents of p with an equivalent absolute path + * containing no extra slashes, symlinks, '.', or '..' elements. This + * is done recursively beginning with '/'. Resolution of symlinks will + * stop at the first element in the path that doesn't exist or cannot be + * read/searched for some reason, but extra slashes, '.', and '..' + * elements will still be resolved after that point. + * + * Always returns p. + */ +static char *normalize_path(char *p) +{ + char start[PATH_MAX]; + char rest[PATH_MAX]; + + /* + * recursion stopping case: nothing to normalize in an empty + * string (represents root directory) + */ + if (*p == '\0') { + return p; + } + split_path(p, start, rest); + normalize_path(start); + if (!strcmp(rest, ".")) { + /* "self" expands to nothing */ + rest[0] = '\0'; + } + if (!strcmp(rest, "..")) { + /* + * "parent" expands to nothing and removes the last + * element from start. + */ + rest[0] = '\0'; + split_path(start, start, NULL); + } + + /* put the path back together */ + join_path(p, start, rest); + + if (expand_symlink(p)) { + /* p was a symlink, so I must normalize its expansion */ + normalize_path(p); + } + return p; +} + + +/* + * p = absolute or partial path in a PATH_MAX size buffer + * + * normalize p to an absolute path containing no symlinks and no . or .. + * directories. + * + * NOTE: If any of the path components do not exist or cannot be read/searched + * for some reason, this routine will only standardize the parts of the + * path up to the "bad" component. + * + * Always returns p. + */ +char *make_absolute_path(char *p) +{ + if (!is_absolute_path(p)) { + char cwd[PATH_MAX]; + + if (NULL == getcwd(cwd, sizeof(cwd))) + die("cannot get working directory"); + join_path(p, cwd, p); + } + return normalize_path(p); +} -- 1.5.3.rc3.9.g9ef91 - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html