diff options
Diffstat (limited to 'path.c')
-rw-r--r-- | path.c | 585 |
1 files changed, 515 insertions, 70 deletions
@@ -11,6 +11,7 @@ * which is what it's designed for. */ #include "cache.h" +#include "strbuf.h" static char bad_path[] = "/bad-path/"; @@ -32,6 +33,60 @@ static char *cleanup_path(char *path) return path; } +char *mksnpath(char *buf, size_t n, const char *fmt, ...) +{ + va_list args; + unsigned len; + + va_start(args, fmt); + len = vsnprintf(buf, n, fmt, args); + va_end(args); + if (len >= n) { + strlcpy(buf, bad_path, n); + return buf; + } + return cleanup_path(buf); +} + +static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args) +{ + const char *git_dir = get_git_dir(); + size_t len; + + len = strlen(git_dir); + if (n < len + 1) + goto bad; + memcpy(buf, git_dir, len); + if (len && !is_dir_sep(git_dir[len-1])) + buf[len++] = '/'; + len += vsnprintf(buf + len, n - len, fmt, args); + if (len >= n) + goto bad; + return cleanup_path(buf); +bad: + strlcpy(buf, bad_path, n); + return buf; +} + +char *git_snpath(char *buf, size_t n, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + (void)git_vsnpath(buf, n, fmt, args); + va_end(args); + return buf; +} + +char *git_pathdup(const char *fmt, ...) +{ + char path[PATH_MAX]; + va_list args; + va_start(args, fmt); + (void)git_vsnpath(path, sizeof(path), fmt, args); + va_end(args); + return xstrdup(path); +} + char *mkpath(const char *fmt, ...) { va_list args; @@ -71,31 +126,123 @@ char *git_path(const char *fmt, ...) /* git_mkstemp() - create tmp file honoring TMPDIR variable */ int git_mkstemp(char *path, size_t len, const char *template) { - char *env, *pch = path; + const char *tmp; + size_t n; + + tmp = getenv("TMPDIR"); + if (!tmp) + tmp = "/tmp"; + n = snprintf(path, len, "%s/%s", tmp, template); + if (len <= n) { + errno = ENAMETOOLONG; + return -1; + } + return mkstemp(path); +} - if ((env = getenv("TMPDIR")) == NULL) { - strcpy(pch, "/tmp/"); - len -= 5; - pch += 5; - } else { - size_t n = snprintf(pch, len, "%s/", env); +/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */ +int git_mkstemps(char *path, size_t len, const char *template, int suffix_len) +{ + const char *tmp; + size_t n; + + tmp = getenv("TMPDIR"); + if (!tmp) + tmp = "/tmp"; + n = snprintf(path, len, "%s/%s", tmp, template); + if (len <= n) { + errno = ENAMETOOLONG; + return -1; + } + return mkstemps(path, suffix_len); +} + +/* Adapted from libiberty's mkstemp.c. */ + +#undef TMP_MAX +#define TMP_MAX 16384 - len -= n; - pch += n; +int git_mkstemps_mode(char *pattern, int suffix_len, int mode) +{ + static const char letters[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + static const int num_letters = 62; + uint64_t value; + struct timeval tv; + char *template; + size_t len; + int fd, count; + + len = strlen(pattern); + + if (len < 6 + suffix_len) { + errno = EINVAL; + return -1; } - strlcpy(pch, template, len); + if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) { + errno = EINVAL; + return -1; + } - return mkstemp(path); + /* + * Replace pattern's XXXXXX characters with randomness. + * Try TMP_MAX different filenames. + */ + gettimeofday(&tv, NULL); + value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid(); + template = &pattern[len - 6 - suffix_len]; + for (count = 0; count < TMP_MAX; ++count) { + uint64_t v = value; + /* Fill in the random bits. */ + template[0] = letters[v % num_letters]; v /= num_letters; + template[1] = letters[v % num_letters]; v /= num_letters; + template[2] = letters[v % num_letters]; v /= num_letters; + template[3] = letters[v % num_letters]; v /= num_letters; + template[4] = letters[v % num_letters]; v /= num_letters; + template[5] = letters[v % num_letters]; v /= num_letters; + + fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode); + if (fd > 0) + return fd; + /* + * Fatal error (EPERM, ENOSPC etc). + * It doesn't make sense to loop. + */ + if (errno != EEXIST) + break; + /* + * This is a random value. It is only necessary that + * the next TMP_MAX values generated by adding 7777 to + * VALUE are different with (module 2^32). + */ + value += 7777; + } + /* We return the null string if we can't find a unique file name. */ + pattern[0] = '\0'; + return -1; } +int git_mkstemp_mode(char *pattern, int mode) +{ + /* mkstemp is just mkstemps with no suffix */ + return git_mkstemps_mode(pattern, 0, mode); +} + +int gitmkstemps(char *pattern, int suffix_len) +{ + return git_mkstemps_mode(pattern, suffix_len, 0600); +} int validate_headref(const char *path) { struct stat st; char *buf, buffer[256]; unsigned char sha1[20]; - int len, fd; + int fd; + ssize_t len; if (lstat(path, &st) < 0) return -1; @@ -140,43 +287,51 @@ int validate_headref(const char *path) return -1; } -static char *user_path(char *buf, char *path, int sz) +static struct passwd *getpw_str(const char *username, size_t len) { struct passwd *pw; - char *slash; - int len, baselen; + char *username_z = xmalloc(len + 1); + memcpy(username_z, username, len); + username_z[len] = '\0'; + pw = getpwnam(username_z); + free(username_z); + return pw; +} - if (!path || path[0] != '~') - return NULL; - path++; - slash = strchr(path, '/'); - if (path[0] == '/' || !path[0]) { - pw = getpwuid(getuid()); - } - else { - if (slash) { - *slash = 0; - pw = getpwnam(path); - *slash = '/'; +/* + * Return a string with ~ and ~user expanded via getpw*. If buf != NULL, + * then it is a newly allocated string. Returns NULL on getpw failure or + * if path is NULL. + */ +char *expand_user_path(const char *path) +{ + struct strbuf user_path = STRBUF_INIT; + const char *first_slash = strchrnul(path, '/'); + const char *to_copy = path; + + if (path == NULL) + goto return_null; + if (path[0] == '~') { + const char *username = path + 1; + size_t username_len = first_slash - username; + if (username_len == 0) { + const char *home = getenv("HOME"); + if (!home) + goto return_null; + strbuf_add(&user_path, home, strlen(home)); + } else { + struct passwd *pw = getpw_str(username, username_len); + if (!pw) + goto return_null; + strbuf_add(&user_path, pw->pw_dir, strlen(pw->pw_dir)); } - else - pw = getpwnam(path); - } - if (!pw || !pw->pw_dir || sz <= strlen(pw->pw_dir)) - return NULL; - baselen = strlen(pw->pw_dir); - memcpy(buf, pw->pw_dir, baselen); - while ((1 < baselen) && (buf[baselen-1] == '/')) { - buf[baselen-1] = 0; - baselen--; - } - if (slash && slash[1]) { - len = strlen(slash); - if (sz <= baselen + len) - return NULL; - memcpy(buf + baselen, slash, len + 1); + to_copy = first_slash; } - return buf; + strbuf_add(&user_path, to_copy, strlen(to_copy)); + return strbuf_detach(&user_path, NULL); +return_null: + strbuf_release(&user_path); + return NULL; } /* @@ -224,8 +379,18 @@ char *enter_repo(char *path, int strict) if (PATH_MAX <= len) return NULL; if (path[0] == '~') { - if (!user_path(used_path, path, PATH_MAX)) + char *newpath = expand_user_path(path); + if (!newpath || (PATH_MAX - 10 < strlen(newpath))) { + free(newpath); return NULL; + } + /* + * Copy back into the static buffer. A pity + * since newpath was not bounded, but other + * branches of the if are limited by PATH_MAX + * anyway. + */ + strcpy(used_path, newpath); free(newpath); strcpy(validated_path, path); path = used_path; } @@ -252,7 +417,7 @@ char *enter_repo(char *path, int strict) if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && validate_headref("HEAD") == 0) { - setenv("GIT_DIR", ".", 1); + set_git_dir("."); check_repository_format(); return path; } @@ -260,35 +425,315 @@ char *enter_repo(char *path, int strict) return NULL; } -int adjust_shared_perm(const char *path) +int set_shared_perm(const char *path, int mode) { struct stat st; - int mode; + int tweak, shared, orig_mode; - if (!shared_repository) + if (!shared_repository) { + if (mode) + return chmod(path, mode & ~S_IFMT); return 0; - if (lstat(path, &st) < 0) - return -1; - mode = st.st_mode; - if (mode & S_IRUSR) - mode |= (shared_repository == PERM_GROUP - ? S_IRGRP - : (shared_repository == PERM_EVERYBODY - ? (S_IRGRP|S_IROTH) - : 0)); - - if (mode & S_IWUSR) - mode |= S_IWGRP; - + } + if (!mode) { + if (lstat(path, &st) < 0) + return -1; + mode = st.st_mode; + orig_mode = mode; + } else + orig_mode = 0; + if (shared_repository < 0) + shared = -shared_repository; + else + shared = shared_repository; + tweak = shared; + + if (!(mode & S_IWUSR)) + tweak &= ~0222; if (mode & S_IXUSR) - mode |= (shared_repository == PERM_GROUP - ? S_IXGRP - : (shared_repository == PERM_EVERYBODY - ? (S_IXGRP|S_IXOTH) - : 0)); - if (S_ISDIR(mode)) - mode |= S_ISGID; - if ((mode & st.st_mode) != mode && chmod(path, mode) < 0) + /* Copy read bits to execute bits */ + tweak |= (tweak & 0444) >> 2; + if (shared_repository < 0) + mode = (mode & ~0777) | tweak; + else + mode |= tweak; + + if (S_ISDIR(mode)) { + /* Copy read bits to execute bits */ + mode |= (shared & 0444) >> 2; + mode |= FORCE_DIR_SET_GID; + } + + if (((shared_repository < 0 + ? (orig_mode & (FORCE_DIR_SET_GID | 0777)) + : (orig_mode & mode)) != mode) && + chmod(path, (mode & ~S_IFMT)) < 0) return -2; return 0; } + +const char *make_relative_path(const char *abs, const char *base) +{ + static char buf[PATH_MAX + 1]; + int i = 0, j = 0; + + if (!base || !base[0]) + return abs; + while (base[i]) { + if (is_dir_sep(base[i])) { + if (!is_dir_sep(abs[j])) + return abs; + while (is_dir_sep(base[i])) + i++; + while (is_dir_sep(abs[j])) + j++; + continue; + } else if (abs[j] != base[i]) { + return abs; + } + i++; + j++; + } + if ( + /* "/foo" is a prefix of "/foo" */ + abs[j] && + /* "/foo" is not a prefix of "/foobar" */ + !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j]) + ) + return abs; + while (is_dir_sep(abs[j])) + j++; + if (!abs[j]) + strcpy(buf, "."); + else + strcpy(buf, abs + j); + return buf; +} + +/* + * It is okay if dst == src, but they should not overlap otherwise. + * + * Performs the following normalizations on src, storing the result in dst: + * - Ensures that components are separated by '/' (Windows only) + * - Squashes sequences of '/'. + * - Removes "." components. + * - Removes ".." components, and the components the precede them. + * Returns failure (non-zero) if a ".." component appears as first path + * component anytime during the normalization. Otherwise, returns success (0). + * + * Note that this function is purely textual. It does not follow symlinks, + * verify the existence of the path, or make any system calls. + */ +int normalize_path_copy(char *dst, const char *src) +{ + char *dst0; + + if (has_dos_drive_prefix(src)) { + *dst++ = *src++; + *dst++ = *src++; + } + dst0 = dst; + + if (is_dir_sep(*src)) { + *dst++ = '/'; + while (is_dir_sep(*src)) + src++; + } + + for (;;) { + char c = *src; + + /* + * A path component that begins with . could be + * special: + * (1) "." and ends -- ignore and terminate. + * (2) "./" -- ignore them, eat slash and continue. + * (3) ".." and ends -- strip one and terminate. + * (4) "../" -- strip one, eat slash and continue. + */ + if (c == '.') { + if (!src[1]) { + /* (1) */ + src++; + } else if (is_dir_sep(src[1])) { + /* (2) */ + src += 2; + while (is_dir_sep(*src)) + src++; + continue; + } else if (src[1] == '.') { + if (!src[2]) { + /* (3) */ + src += 2; + goto up_one; + } else if (is_dir_sep(src[2])) { + /* (4) */ + src += 3; + while (is_dir_sep(*src)) + src++; + goto up_one; + } + } + } + + /* copy up to the next '/', and eat all '/' */ + while ((c = *src++) != '\0' && !is_dir_sep(c)) + *dst++ = c; + if (is_dir_sep(c)) { + *dst++ = '/'; + while (is_dir_sep(c)) + c = *src++; + src--; + } else if (!c) + break; + continue; + + up_one: + /* + * dst0..dst is prefix portion, and dst[-1] is '/'; + * go up one level. + */ + dst--; /* go to trailing '/' */ + if (dst <= dst0) + return -1; + /* Windows: dst[-1] cannot be backslash anymore */ + while (dst0 < dst && dst[-1] != '/') + dst--; + } + *dst = '\0'; + return 0; +} + +/* + * path = Canonical absolute path + * prefix_list = Colon-separated list of absolute paths + * + * Determines, for each path in prefix_list, whether the "prefix" really + * is an ancestor directory of path. Returns the length of the longest + * ancestor directory, excluding any trailing slashes, or -1 if no prefix + * is an ancestor. (Note that this means 0 is returned if prefix_list is + * "/".) "/foo" is not considered an ancestor of "/foobar". Directories + * are not considered to be their own ancestors. path must be in a + * canonical form: empty components, or "." or ".." components are not + * allowed. prefix_list may be null, which is like "". + */ +int longest_ancestor_length(const char *path, const char *prefix_list) +{ + char buf[PATH_MAX+1]; + const char *ceil, *colon; + int len, max_len = -1; + + if (prefix_list == NULL || !strcmp(path, "/")) + return -1; + + for (colon = ceil = prefix_list; *colon; ceil = colon+1) { + for (colon = ceil; *colon && *colon != PATH_SEP; colon++); + len = colon - ceil; + if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil)) + continue; + strlcpy(buf, ceil, len+1); + if (normalize_path_copy(buf, buf) < 0) + continue; + len = strlen(buf); + if (len > 0 && buf[len-1] == '/') + buf[--len] = '\0'; + + if (!strncmp(path, buf, len) && + path[len] == '/' && + len > max_len) { + max_len = len; + } + } + + return max_len; +} + +/* strip arbitrary amount of directory separators at end of path */ +static inline int chomp_trailing_dir_sep(const char *path, int len) +{ + while (len && is_dir_sep(path[len - 1])) + len--; + return len; +} + +/* + * If path ends with suffix (complete path components), returns the + * part before suffix (sans trailing directory separators). + * Otherwise returns NULL. + */ +char *strip_path_suffix(const char *path, const char *suffix) +{ + int path_len = strlen(path), suffix_len = strlen(suffix); + + while (suffix_len) { + if (!path_len) + return NULL; + + if (is_dir_sep(path[path_len - 1])) { + if (!is_dir_sep(suffix[suffix_len - 1])) + return NULL; + path_len = chomp_trailing_dir_sep(path, path_len); + suffix_len = chomp_trailing_dir_sep(suffix, suffix_len); + } + else if (path[--path_len] != suffix[--suffix_len]) + return NULL; + } + + if (path_len && !is_dir_sep(path[path_len - 1])) + return NULL; + return xstrndup(path, chomp_trailing_dir_sep(path, path_len)); +} + +int daemon_avoid_alias(const char *p) +{ + int sl, ndot; + + /* + * This resurrects the belts and suspenders paranoia check by HPA + * done in <435560F7.4080006@zytor.com> thread, now enter_repo() + * does not do getcwd() based path canonicalization. + * + * sl becomes true immediately after seeing '/' and continues to + * be true as long as dots continue after that without intervening + * non-dot character. + */ + if (!p || (*p != '/' && *p != '~')) + return -1; + sl = 1; ndot = 0; + p++; + + while (1) { + char ch = *p++; + if (sl) { + if (ch == '.') + ndot++; + else if (ch == '/') { + if (ndot < 3) + /* reject //, /./ and /../ */ + return -1; + ndot = 0; + } + else if (ch == 0) { + if (0 < ndot && ndot < 3) + /* reject /.$ and /..$ */ + return -1; + return 0; + } + else + sl = ndot = 0; + } + else if (ch == 0) + return 0; + else if (ch == '/') { + sl = 1; + ndot = 0; + } + } +} + +int offset_1st_component(const char *path) +{ + if (has_dos_drive_prefix(path)) + return 2 + is_dir_sep(path[2]); + return is_dir_sep(path[0]); +} |