diff options
Diffstat (limited to 'abspath.c')
-rw-r--r-- | abspath.c | 324 |
1 files changed, 236 insertions, 88 deletions
@@ -11,112 +11,260 @@ int is_directory(const char *path) return (!stat(path, &st) && S_ISDIR(st.st_mode)); } -/* We allow "recursive" symbolic links. Only within reason, though. */ -#define MAXDEPTH 5 +/* removes the last path component from 'path' except if 'path' is root */ +static void strip_last_component(struct strbuf *path) +{ + size_t offset = offset_1st_component(path->buf); + size_t len = path->len; + + /* Find start of the last component */ + while (offset < len && !is_dir_sep(path->buf[len - 1])) + len--; + /* Skip sequences of multiple path-separators */ + while (offset < len && is_dir_sep(path->buf[len - 1])) + len--; + + strbuf_setlen(path, len); +} + +/* get (and remove) the next component in 'remaining' and place it in 'next' */ +static void get_next_component(struct strbuf *next, struct strbuf *remaining) +{ + char *start = NULL; + char *end = NULL; + + strbuf_reset(next); -const char *make_absolute_path(const char *path) + /* look for the next component */ + /* Skip sequences of multiple path-separators */ + for (start = remaining->buf; is_dir_sep(*start); start++) + ; /* nothing */ + /* Find end of the path component */ + for (end = start; *end && !is_dir_sep(*end); end++) + ; /* nothing */ + + strbuf_add(next, start, end - start); + /* remove the component from 'remaining' */ + strbuf_remove(remaining, 0, end - remaining->buf); +} + +/* copies root part from remaining to resolved, canonicalizing it on the way */ +static void get_root_part(struct strbuf *resolved, struct strbuf *remaining) { - static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1]; - char cwd[1024] = ""; - int buf_index = 1; + int offset = offset_1st_component(remaining->buf); + + strbuf_reset(resolved); + strbuf_add(resolved, remaining->buf, offset); +#ifdef GIT_WINDOWS_NATIVE + convert_slashes(resolved->buf); +#endif + strbuf_remove(remaining, 0, offset); +} + +/* We allow "recursive" symbolic links. Only within reason, though. */ +#ifndef MAXSYMLINKS +#define MAXSYMLINKS 32 +#endif - int depth = MAXDEPTH; - char *last_elem = NULL; +/* + * Return the real path (i.e., absolute path, with symlinks resolved + * and extra slashes removed) equivalent to the specified path. (If + * you want an absolute path but don't mind links, use + * absolute_path().) Places the resolved realpath in the provided strbuf. + * + * The directory part of path (i.e., everything up to the last + * dir_sep) must denote a valid, existing directory, but the last + * component need not exist. If die_on_error is set, then die with an + * informative error message if there is a problem. Otherwise, return + * NULL on errors (without generating any output). + */ +char *strbuf_realpath(struct strbuf *resolved, const char *path, + int die_on_error) +{ + struct strbuf remaining = STRBUF_INIT; + struct strbuf next = STRBUF_INIT; + struct strbuf symlink = STRBUF_INIT; + char *retval = NULL; + int num_symlinks = 0; struct stat st; - if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) - die ("Too long path: %.*s", 60, path); + if (!*path) { + if (die_on_error) + die("The empty string is not a valid path"); + else + goto error_out; + } - while (depth--) { - if (!is_directory(buf)) { - char *last_slash = strrchr(buf, '/'); - if (last_slash) { - *last_slash = '\0'; - last_elem = xstrdup(last_slash + 1); - } else { - last_elem = xstrdup(buf); - *buf = '\0'; - } + strbuf_addstr(&remaining, path); + get_root_part(resolved, &remaining); + + if (!resolved->len) { + /* relative path; can use CWD as the initial resolved path */ + if (strbuf_getcwd(resolved)) { + if (die_on_error) + die_errno("unable to get current working directory"); + else + goto error_out; } + } - if (*buf) { - if (!*cwd && !getcwd(cwd, sizeof(cwd))) - die_errno ("Could not get current working directory"); + /* Iterate over the remaining path components */ + while (remaining.len > 0) { + get_next_component(&next, &remaining); - if (chdir(buf)) - die_errno ("Could not switch to '%s'", buf); - } - if (!getcwd(buf, PATH_MAX)) - die_errno ("Could not get current working directory"); - - if (last_elem) { - size_t len = strlen(buf); - if (len + strlen(last_elem) + 2 > PATH_MAX) - die ("Too long path name: '%s/%s'", - buf, last_elem); - if (len && buf[len-1] != '/') - buf[len++] = '/'; - strcpy(buf + len, last_elem); - free(last_elem); - last_elem = NULL; + if (next.len == 0) { + continue; /* empty component */ + } else if (next.len == 1 && !strcmp(next.buf, ".")) { + continue; /* '.' component */ + } else if (next.len == 2 && !strcmp(next.buf, "..")) { + /* '..' component; strip the last path component */ + strip_last_component(resolved); + continue; } - if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) { - ssize_t len = readlink(buf, next_buf, PATH_MAX); - if (len < 0) - die_errno ("Invalid symlink '%s'", buf); - if (PATH_MAX <= len) - die("symbolic link too long: %s", buf); - next_buf[len] = '\0'; - buf = next_buf; - buf_index = 1 - buf_index; - next_buf = bufs[buf_index]; - } else - break; + /* append the next component and resolve resultant path */ + if (!is_dir_sep(resolved->buf[resolved->len - 1])) + strbuf_addch(resolved, '/'); + strbuf_addbuf(resolved, &next); + + if (lstat(resolved->buf, &st)) { + /* error out unless this was the last component */ + if (errno != ENOENT || remaining.len) { + if (die_on_error) + die_errno("Invalid path '%s'", + resolved->buf); + else + goto error_out; + } + } else if (S_ISLNK(st.st_mode)) { + ssize_t len; + strbuf_reset(&symlink); + + if (num_symlinks++ > MAXSYMLINKS) { + errno = ELOOP; + + if (die_on_error) + die("More than %d nested symlinks " + "on path '%s'", MAXSYMLINKS, path); + else + goto error_out; + } + + len = strbuf_readlink(&symlink, resolved->buf, + st.st_size); + if (len < 0) { + if (die_on_error) + die_errno("Invalid symlink '%s'", + resolved->buf); + else + goto error_out; + } + + if (is_absolute_path(symlink.buf)) { + /* absolute symlink; set resolved to root */ + get_root_part(resolved, &symlink); + } else { + /* + * relative symlink + * strip off the last component since it will + * be replaced with the contents of the symlink + */ + strip_last_component(resolved); + } + + /* + * if there are still remaining components to resolve + * then append them to symlink + */ + if (remaining.len) { + strbuf_addch(&symlink, '/'); + strbuf_addbuf(&symlink, &remaining); + } + + /* + * use the symlink as the remaining components that + * need to be resolved + */ + strbuf_swap(&symlink, &remaining); + } } - if (*cwd && chdir(cwd)) - die_errno ("Could not change back to '%s'", cwd); + retval = resolved->buf; + +error_out: + strbuf_release(&remaining); + strbuf_release(&next); + strbuf_release(&symlink); + + if (!retval) + strbuf_reset(resolved); - return buf; + return retval; } -static const char *get_pwd_cwd(void) +/* + * Resolve `path` into an absolute, cleaned-up path. The return value + * comes from a shared buffer. + */ +const char *real_path(const char *path) { - static char cwd[PATH_MAX + 1]; - char *pwd; - struct stat cwd_stat, pwd_stat; - if (getcwd(cwd, PATH_MAX) == NULL) - return NULL; - pwd = getenv("PWD"); - if (pwd && strcmp(pwd, cwd)) { - stat(cwd, &cwd_stat); - if (!stat(pwd, &pwd_stat) && - pwd_stat.st_dev == cwd_stat.st_dev && - pwd_stat.st_ino == cwd_stat.st_ino) { - strlcpy(cwd, pwd, PATH_MAX); - } - } - return cwd; + static struct strbuf realpath = STRBUF_INIT; + return strbuf_realpath(&realpath, path, 1); } -const char *make_nonrelative_path(const char *path) +const char *real_path_if_valid(const char *path) { - static char buf[PATH_MAX + 1]; - - if (is_absolute_path(path)) { - if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) - die("Too long path: %.*s", 60, path); - } else { - size_t len; - const char *fmt; - const char *cwd = get_pwd_cwd(); - if (!cwd) - die_errno("Cannot determine the current working directory"); - len = strlen(cwd); - fmt = (len > 0 && is_dir_sep(cwd[len-1])) ? "%s%s" : "%s/%s"; - if (snprintf(buf, PATH_MAX, fmt, cwd, path) >= PATH_MAX) - die("Too long path: %.*s", 60, path); - } - return buf; + static struct strbuf realpath = STRBUF_INIT; + return strbuf_realpath(&realpath, path, 0); +} + +char *real_pathdup(const char *path, int die_on_error) +{ + struct strbuf realpath = STRBUF_INIT; + char *retval = NULL; + + if (strbuf_realpath(&realpath, path, die_on_error)) + retval = strbuf_detach(&realpath, NULL); + + strbuf_release(&realpath); + + return retval; +} + +/* + * Use this to get an absolute path from a relative one. If you want + * to resolve links, you should use real_path. + */ +const char *absolute_path(const char *path) +{ + static struct strbuf sb = STRBUF_INIT; + strbuf_reset(&sb); + strbuf_add_absolute_path(&sb, path); + return sb.buf; +} + +char *absolute_pathdup(const char *path) +{ + struct strbuf sb = STRBUF_INIT; + strbuf_add_absolute_path(&sb, path); + return strbuf_detach(&sb, NULL); +} + +char *prefix_filename(const char *pfx, const char *arg) +{ + struct strbuf path = STRBUF_INIT; + size_t pfx_len = pfx ? strlen(pfx) : 0; + + if (!pfx_len) + ; /* nothing to prefix */ + else if (is_absolute_path(arg)) + pfx_len = 0; + else + strbuf_add(&path, pfx, pfx_len); + + strbuf_addstr(&path, arg); +#ifdef GIT_WINDOWS_NATIVE + convert_slashes(path.buf + pfx_len); +#endif + return strbuf_detach(&path, NULL); } |