diff options
Diffstat (limited to 'abspath.c')
-rw-r--r-- | abspath.c | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/abspath.c b/abspath.c new file mode 100644 index 000000000000..985798532994 --- /dev/null +++ b/abspath.c @@ -0,0 +1,270 @@ +#include "cache.h" + +/* + * Do not use this for inspecting *tracked* content. When path is a + * symlink to a directory, we do not want to say it is a directory when + * dealing with tracked content in the working tree. + */ +int is_directory(const char *path) +{ + struct stat st; + return (!stat(path, &st) && S_ISDIR(st.st_mode)); +} + +/* 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); + + /* 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) +{ + 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 + +/* + * 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 (!*path) { + if (die_on_error) + die("The empty string is not a valid path"); + else + goto error_out; + } + + 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; + } + } + + /* Iterate over the remaining path components */ + while (remaining.len > 0) { + get_next_component(&next, &remaining); + + 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; + } + + /* 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); + } + } + + retval = resolved->buf; + +error_out: + strbuf_release(&remaining); + strbuf_release(&next); + strbuf_release(&symlink); + + if (!retval) + strbuf_reset(resolved); + + return retval; +} + +/* + * Resolve `path` into an absolute, cleaned-up path. The return value + * comes from a shared buffer. + */ +const char *real_path(const char *path) +{ + static struct strbuf realpath = STRBUF_INIT; + return strbuf_realpath(&realpath, path, 1); +} + +const char *real_path_if_valid(const char *path) +{ + 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); +} |