X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=refs.c;h=97506a4ebdfbd164abad6be76d7160db171d9148;hb=69779a562a171d9b70bbb17b89e076bd6b16ae6c;hp=d4f3612487c0b9775d1baa382549b019beeef262;hpb=ca8db1424d1808a1f78bc9905efd267f7c154d8e;p=git.git diff --git a/refs.c b/refs.c index d4f36124..97506a4e 100644 --- a/refs.c +++ b/refs.c @@ -2,45 +2,156 @@ #include "cache.h" #include -#include /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -int read_ref(const char *filename, unsigned char *sha1) +#ifndef USE_SYMLINK_HEAD +#define USE_SYMLINK_HEAD 1 +#endif + +int validate_symref(const char *path) { - int depth = 0; - int ret = -1, fd; + struct stat st; + char *buf, buffer[256]; + int len, fd; + + if (lstat(path, &st) < 0) + return -1; + + /* 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; + } - while ((fd = open(filename, O_RDONLY)) >= 0) { - char buffer[256]; - int len = read(fd, buffer, sizeof(buffer)-1); + /* + * 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); + /* + * 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; +} + +const char *resolve_ref(const char *path, unsigned char *sha1, int reading) +{ + int depth = MAXDEPTH, len; + char buffer[256]; + + for (;;) { + struct stat st; + char *buf; + int fd; + + if (--depth < 0) + return NULL; + + /* Special case: non-existing file. + * Not having the refs/heads/new-branch is OK + * if we are writing into it, so is .git/HEAD + * that points at refs/heads/master still to be + * born. It is NOT OK if we are resolving for + * reading. + */ + if (lstat(path, &st) < 0) { + if (reading || errno != ENOENT) + return NULL; + memset(sha1, 0, 20); + return path; + } + + /* Follow "normalized" - ie "refs/.." symlinks by hand */ + if (S_ISLNK(st.st_mode)) { + len = readlink(path, buffer, sizeof(buffer)-1); + if (len >= 5 && !memcmp("refs/", buffer, 5)) { + path = git_path("%.*s", len, buffer); + continue; + } + } + + /* + * Anything else, just open it and try to use it as + * a ref + */ + fd = open(path, O_RDONLY); + if (fd < 0) + return NULL; + len = read(fd, buffer, sizeof(buffer)-1); close(fd); - if (len < 0) + + /* + * Is it a symbolic ref? + */ + if (len < 4 || memcmp("ref:", buffer, 4)) break; + buf = buffer + 4; + len -= 4; + while (len && isspace(*buf)) + buf++, len--; + while (len && isspace(buf[len-1])) + buf[--len] = 0; + path = git_path("%.*s", len, buf); + } + if (len < 40 || get_sha1_hex(buffer, sha1)) + return NULL; + return path; +} - buffer[len] = 0; - while (len && isspace(buffer[len-1])) - buffer[--len] = 0; +int create_symref(const char *git_HEAD, const char *refs_heads_master) +{ +#if USE_SYMLINK_HEAD + unlink(git_HEAD); + return symlink(refs_heads_master, git_HEAD); +#else + const char *lockpath; + char ref[1000]; + int fd, len, written; - if (!strncmp(buffer, "ref:", 4)) { - char *buf; - if (depth > MAXDEPTH) - break; - depth++; - buf = buffer + 4; - len -= 4; - while (len && isspace(*buf)) - buf++, len--; - filename = git_path("%.*s", len, buf); - continue; - } - if (len >= 40) - ret = get_sha1_hex(buffer, sha1); - break; + len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); + if (sizeof(ref) <= len) { + error("refname too long: %s", refs_heads_master); + return -1; } - return ret; + lockpath = mkpath("%s.lock", git_HEAD); + fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); + written = write(fd, ref, len); + close(fd); + if (written != len) { + unlink(lockpath); + error("Unable to write to %s", lockpath); + return -2; + } + if (rename(lockpath, git_HEAD) < 0) { + unlink(lockpath); + error("Unable to create %s", git_HEAD); + return -3; + } + return 0; +#endif +} + +int read_ref(const char *filename, unsigned char *sha1) +{ + if (resolve_ref(filename, sha1, 1)) + return 0; + return -1; } static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1)) @@ -223,17 +334,54 @@ int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1) return retval; } +/* + * Make sure "ref" is something reasonable to have under ".git/refs/"; + * We do not like it if: + * + * - any path component of it begins with ".", or + * - it has double dots "..", or + * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or + * - it ends with a "/". + */ + +static inline int bad_ref_char(int ch) +{ + return (((unsigned) ch) <= ' ' || + ch == '~' || ch == '^' || ch == ':'); +} + int check_ref_format(const char *ref) { - char *middle; - if (ref[0] == '.' || ref[0] == '/') - return -1; - middle = strchr(ref, '/'); - if (!middle || !middle[1]) - return -1; - if (strchr(middle + 1, '/')) - return -1; - return 0; + int ch, level; + const char *cp = ref; + + level = 0; + while (1) { + while ((ch = *cp++) == '/') + ; /* tolerate duplicated slashes */ + if (!ch) + return -1; /* should not end with slashes */ + + /* we are at the beginning of the path component */ + if (ch == '.' || bad_ref_char(ch)) + return -1; + + /* scan the rest of the path component */ + while ((ch = *cp++) != 0) { + if (bad_ref_char(ch)) + return -1; + if (ch == '/') + break; + if (ch == '.' && *cp == '.') + return -1; + } + level++; + if (!ch) { + if (level < 2) + return -1; /* at least of form "heads/blah" */ + return 0; + } + } } int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)