X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=path.c;h=194e0b553f7a5c4fd99b348228112b308c5419b6;hb=e968751573a4ded76201a0e4414ec36649a92dda;hp=5b617092ad564739829d638938f36177611acf58;hpb=54f4b87454824fedc629219620ce9b7cdcab3d27;p=git.git diff --git a/path.c b/path.c index 5b617092..194e0b55 100644 --- a/path.c +++ b/path.c @@ -83,81 +83,198 @@ int git_mkstemp(char *path, size_t len, const char *template) } -char *safe_strncpy(char *dest, const char *src, size_t n) +size_t safe_strncpy(char *dest, const char *src, size_t size) { - strncpy(dest, src, n); - dest[n - 1] = '\0'; + size_t ret = strlen(src); - return dest; + if (size) { + size_t len = (ret >= size) ? size - 1 : ret; + memcpy(dest, src, len); + dest[len] = '\0'; + } + return ret; } -static char *current_dir() -{ - return getcwd(pathname, sizeof(pathname)); -} -/* Take a raw path from is_git_repo() and canonicalize it using Linus' - * idea of a blind chdir() and getcwd(). */ -static const char *canonical_path(char *path, int strict) +int validate_symref(const char *path) { - char *dir = path; + struct stat st; + char *buf, buffer[256]; + int len, fd; - if(strict && *dir != '/') - return NULL; + if (lstat(path, &st) < 0) + return -1; - if(*dir == '~') { /* user-relative path */ - struct passwd *pw; - char *slash = strchr(dir, '/'); + /* Make sure it is a "refs/.." symlink */ + if (S_ISLNK(st.st_mode)) { + len = readlink(path, buffer, sizeof(buffer)-1); + if (len >= 5 && !memcmp("refs/", buffer, 5)) + return 0; + return -1; + } - dir++; - /* '~/' and '~' (no slash) means users own home-dir */ - if(!*dir || *dir == '/') - pw = getpwuid(getuid()); - else { - if (slash) { - *slash = '\0'; - pw = getpwnam(dir); - *slash = '/'; - } - else - pw = getpwnam(dir); - } + /* + * Anything else, just open it and try to see if it is a symbolic ref. + */ + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + len = read(fd, buffer, sizeof(buffer)-1); + close(fd); - /* make sure we got something back that we can chdir() to */ - if(!pw || chdir(pw->pw_dir) < 0) - return NULL; + /* + * Is it a symbolic ref? + */ + if (len < 4 || memcmp("ref:", buffer, 4)) + return -1; + buf = buffer + 4; + len -= 4; + while (len && isspace(*buf)) + buf++, len--; + if (len >= 5 && !memcmp("refs/", buf, 5)) + return 0; + return -1; +} - if(!slash || !slash[1]) /* no path following username */ - return current_dir(); +static char *user_path(char *buf, char *path, int sz) +{ + struct passwd *pw; + char *slash; + int len, baselen; - dir = slash + 1; + if (!path || path[0] != '~') + return NULL; + path++; + slash = strchr(path, '/'); + if (path[0] == '/' || !path[0]) { + pw = getpwuid(getuid()); } - - /* ~foo/path/to/repo is now path/to/repo and we're in foo's homedir */ - if(chdir(dir) < 0) + else { + if (slash) { + *slash = 0; + pw = getpwnam(path); + *slash = '/'; + } + else + pw = getpwnam(path); + } + if (!pw || !pw->pw_dir || sz <= strlen(pw->pw_dir)) return NULL; - - return current_dir(); + 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); + } + return buf; } +/* + * First, one directory to try is determined by the following algorithm. + * + * (0) If "strict" is given, the path is used as given and no DWIM is + * done. Otherwise: + * (1) "~/path" to mean path under the running user's home directory; + * (2) "~user/path" to mean path under named user's home directory; + * (3) "relative/path" to mean cwd relative directory; or + * (4) "/absolute/path" to mean absolute directory. + * + * Unless "strict" is given, we try access() for existence of "%s.git/.git", + * "%s/.git", "%s.git", "%s" in this order. The first one that exists is + * what we try. + * + * Second, we try chdir() to that. Upon failure, we return NULL. + * + * Then, we try if the current directory is a valid git repository. + * Upon failure, we return NULL. + * + * If all goes well, we return the directory we used to chdir() (but + * before ~user is expanded), avoiding getcwd() resolving symbolic + * links. User relative paths are also returned as they are given, + * except DWIM suffixing. + */ char *enter_repo(char *path, int strict) { - if(!path) + static char used_path[PATH_MAX]; + static char validated_path[PATH_MAX]; + + if (!path) return NULL; - if(!canonical_path(path, strict)) { - if(strict || !canonical_path(mkpath("%s.git", path), strict)) + if (!strict) { + static const char *suffix[] = { + ".git/.git", "/.git", ".git", "", NULL, + }; + int len = strlen(path); + int i; + while ((1 < len) && (path[len-1] == '/')) { + path[len-1] = 0; + len--; + } + if (PATH_MAX <= len) + return NULL; + if (path[0] == '~') { + if (!user_path(used_path, path, PATH_MAX)) + return NULL; + strcpy(validated_path, path); + path = used_path; + } + else if (PATH_MAX - 10 < len) + return NULL; + else { + path = strcpy(used_path, path); + strcpy(validated_path, path); + } + len = strlen(path); + for (i = 0; suffix[i]; i++) { + strcpy(path + len, suffix[i]); + if (!access(path, F_OK)) { + strcat(validated_path, suffix[i]); + break; + } + } + if (!suffix[i] || chdir(path)) return NULL; + path = validated_path; } + else if (chdir(path)) + return NULL; - /* This is perfectly safe, and people tend to think of the directory - * where they ran git-init-db as their repository, so humour them. */ - (void)chdir(".git"); - - if(access("objects", X_OK) == 0 && access("refs", X_OK) == 0) { + if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && + validate_symref("HEAD") == 0) { putenv("GIT_DIR=."); - return current_dir(); + check_repository_format(); + return path; } return NULL; } + +int adjust_shared_perm(const char *path) +{ + struct stat st; + int mode; + + if (!shared_repository) + return 0; + if (lstat(path, &st) < 0) + return -1; + mode = st.st_mode; + if (mode & S_IRUSR) + mode |= S_IRGRP; + if (mode & S_IWUSR) + mode |= S_IWGRP; + if (mode & S_IXUSR) + mode |= S_IXGRP; + if (S_ISDIR(mode)) + mode |= S_ISGID; + if (chmod(path, mode) < 0) + return -2; + return 0; +}