X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=sha1_file.c;h=aa09b4646abff2f82d30c9968ae277d35db8c438;hb=72fdfb50f721460e4cdff16fbe9c72d4ce6c668c;hp=2d109f928bef6fe74f2a27af1419d9e82a7b9b33;hpb=da27f4f3f4c23d0276a372726ed050ed13e45168;p=git.git diff --git a/sha1_file.c b/sha1_file.c index 2d109f92..aa09b464 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -6,8 +6,6 @@ * This handles basic git sha1 object files - packing, unpacking, * creation etc. */ -#include -#include #include "cache.h" #include "delta.h" #include "pack.h" @@ -20,6 +18,8 @@ #endif #endif +const unsigned char null_sha1[20] = { 0, }; + static unsigned int sha1_file_open_flag = O_NOATIME; static unsigned hexval(char c) @@ -46,72 +46,57 @@ int get_sha1_hex(const char *hex, unsigned char *sha1) return 0; } -static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, - *git_graft_file; -static void setup_git_env(void) -{ - git_dir = gitenv(GIT_DIR_ENVIRONMENT); - if (!git_dir) - git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; - git_object_dir = gitenv(DB_ENVIRONMENT); - if (!git_object_dir) { - git_object_dir = xmalloc(strlen(git_dir) + 9); - sprintf(git_object_dir, "%s/objects", git_dir); - } - git_refs_dir = xmalloc(strlen(git_dir) + 6); - sprintf(git_refs_dir, "%s/refs", git_dir); - git_index_file = gitenv(INDEX_ENVIRONMENT); - if (!git_index_file) { - git_index_file = xmalloc(strlen(git_dir) + 7); - sprintf(git_index_file, "%s/index", git_dir); - } - git_graft_file = gitenv(GRAFT_ENVIRONMENT); - if (!git_graft_file) - git_graft_file = strdup(git_path("info/grafts")); -} - -char *get_object_directory(void) -{ - if (!git_object_dir) - setup_git_env(); - return git_object_dir; -} - -char *get_refs_directory(void) +int adjust_shared_perm(const char *path) { - if (!git_refs_dir) - setup_git_env(); - return git_refs_dir; -} - -char *get_index_file(void) -{ - if (!git_index_file) - setup_git_env(); - return git_index_file; -} + struct stat st; + int mode; -char *get_graft_file(void) -{ - if (!git_graft_file) - setup_git_env(); - return git_graft_file; + 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; } int safe_create_leading_directories(char *path) { char *pos = path; + struct stat st; + + if (*pos == '/') + pos++; while (pos) { pos = strchr(pos, '/'); if (!pos) break; *pos = 0; - if (mkdir(path, 0777) < 0) - if (errno != EEXIST) { + if (!stat(path, &st)) { + /* path exists */ + if (!S_ISDIR(st.st_mode)) { *pos = '/'; - return -1; + return -3; } + } + else if (mkdir(path, 0777)) { + *pos = '/'; + return -1; + } + else if (adjust_shared_perm(path)) { + *pos = '/'; + return -2; + } *pos++ = '/'; } return 0; @@ -129,6 +114,8 @@ char * sha1_to_hex(const unsigned char *sha1) *buf++ = hex[val >> 4]; *buf++ = hex[val & 0xf]; } + *buf = '\0'; + return buffer; } @@ -232,58 +219,110 @@ static struct alternate_object_database **alt_odb_tail; * alternate_object_database. The elements on this list come from * non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT * environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates, - * whose contents is exactly in the same format as that environment - * variable. Its base points at a statically allocated buffer that + * whose contents is similar to that environment variable but can be + * LF separated. Its base points at a statically allocated buffer that * contains "/the/directory/corresponding/to/.git/objects/...", while * its name points just after the slash at the end of ".git/objects/" * in the example above, and has enough space to hold 40-byte hex * SHA1, an extra slash for the first level indirection, and the * terminating NUL. */ -static void link_alt_odb_entries(const char *alt, const char *ep) +static void link_alt_odb_entries(const char *alt, const char *ep, int sep, + const char *relative_base) { const char *cp, *last; struct alternate_object_database *ent; + const char *objdir = get_object_directory(); + int base_len = -1; last = alt; - do { - for (cp = last; cp < ep && *cp != ':'; cp++) + while (last < ep) { + cp = last; + if (cp < ep && *cp == '#') { + while (cp < ep && *cp != sep) + cp++; + last = cp + 1; + continue; + } + for ( ; cp < ep && *cp != sep; cp++) ; if (last != cp) { + struct stat st; + struct alternate_object_database *alt; /* 43 = 40-byte + 2 '/' + terminating NUL */ int pfxlen = cp - last; int entlen = pfxlen + 43; + if (*last != '/' && relative_base) { + /* Relative alt-odb */ + if (base_len < 0) + base_len = strlen(relative_base) + 1; + entlen += base_len; + pfxlen += base_len; + } ent = xmalloc(sizeof(*ent) + entlen); - *alt_odb_tail = ent; - alt_odb_tail = &(ent->next); - ent->next = NULL; - memcpy(ent->base, last, pfxlen); + if (*last != '/' && relative_base) { + memcpy(ent->base, relative_base, base_len - 1); + ent->base[base_len - 1] = '/'; + memcpy(ent->base + base_len, + last, cp - last); + } + else + memcpy(ent->base, last, pfxlen); + ent->name = ent->base + pfxlen + 1; - ent->base[pfxlen] = ent->base[pfxlen + 3] = '/'; - ent->base[entlen-1] = 0; + ent->base[pfxlen + 3] = '/'; + ent->base[pfxlen] = ent->base[entlen-1] = 0; + + /* Detect cases where alternate disappeared */ + if (stat(ent->base, &st) || !S_ISDIR(st.st_mode)) { + error("object directory %s does not exist; " + "check .git/objects/info/alternates.", + ent->base); + goto bad; + } + ent->base[pfxlen] = '/'; + + /* Prevent the common mistake of listing the same + * thing twice, or object directory itself. + */ + for (alt = alt_odb_list; alt; alt = alt->next) + if (!memcmp(ent->base, alt->base, pfxlen)) + goto bad; + if (!memcmp(ent->base, objdir, pfxlen)) { + bad: + free(ent); + } + else { + *alt_odb_tail = ent; + alt_odb_tail = &(ent->next); + ent->next = NULL; + } } - while (cp < ep && *cp == ':') + while (cp < ep && *cp == sep) cp++; last = cp; - } while (cp < ep); + } } void prepare_alt_odb(void) { char path[PATH_MAX]; - char *map, *ep; + char *map; int fd; struct stat st; - char *alt = gitenv(ALTERNATE_DB_ENVIRONMENT) ? : ""; + char *alt; + + alt = getenv(ALTERNATE_DB_ENVIRONMENT); + if (!alt) alt = ""; - sprintf(path, "%s/info/alternates", get_object_directory()); if (alt_odb_tail) return; alt_odb_tail = &alt_odb_list; - link_alt_odb_entries(alt, alt + strlen(alt)); + link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL); + sprintf(path, "%s/info/alternates", get_object_directory()); fd = open(path, O_RDONLY); if (fd < 0) return; @@ -296,10 +335,8 @@ void prepare_alt_odb(void) if (map == MAP_FAILED) return; - /* Remove the trailing newline */ - for (ep = map + st.st_size - 1; map < ep && ep[-1] == '\n'; ep--) - ; - link_alt_odb_entries(map, ep); + link_alt_odb_entries(map, map + st.st_size, '\n', + get_object_directory()); munmap(map, st.st_size); } @@ -443,12 +480,13 @@ int use_packed_git(struct packed_git *p) return 0; } -struct packed_git *add_packed_git(char *path, int path_len) +struct packed_git *add_packed_git(char *path, int path_len, int local) { struct stat st; struct packed_git *p; unsigned long idx_size; void *idx_map; + unsigned char sha1[20]; if (check_packed_git_idx(path, &idx_size, &idx_map)) return NULL; @@ -471,17 +509,26 @@ struct packed_git *add_packed_git(char *path, int path_len) p->pack_base = NULL; p->pack_last_used = 0; p->pack_use_cnt = 0; + p->pack_local = local; + if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1)) + memcpy(p->sha1, sha1, 20); return p; } struct packed_git *parse_pack_index(unsigned char *sha1) { + char *path = sha1_pack_index_name(sha1); + return parse_pack_index_file(sha1, path); +} + +struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_path) +{ struct packed_git *p; unsigned long idx_size; void *idx_map; - char *path = sha1_pack_index_name(sha1); + char *path; - if (check_packed_git_idx(path, &idx_size, &idx_map)) + if (check_packed_git_idx(idx_path, &idx_size, &idx_map)) return NULL; path = sha1_pack_name(sha1); @@ -505,7 +552,7 @@ void install_packed_git(struct packed_git *pack) packed_git = pack; } -static void prepare_packed_git_one(char *objdir) +static void prepare_packed_git_one(char *objdir, int local) { char path[PATH_MAX]; int len; @@ -515,8 +562,12 @@ static void prepare_packed_git_one(char *objdir) sprintf(path, "%s/pack", objdir); len = strlen(path); dir = opendir(path); - if (!dir) + if (!dir) { + if (errno != ENOENT) + error("unable to open object pack directory: %s: %s", + path, strerror(errno)); return; + } path[len++] = '/'; while ((de = readdir(dir)) != NULL) { int namelen = strlen(de->d_name); @@ -527,7 +578,7 @@ static void prepare_packed_git_one(char *objdir) /* we have .idx. Is it a file we can map? */ strcpy(path + len, de->d_name); - p = add_packed_git(path, len + namelen); + p = add_packed_git(path, len + namelen, local); if (!p) continue; p->next = packed_git; @@ -543,11 +594,12 @@ void prepare_packed_git(void) if (run_once) return; - prepare_packed_git_one(get_object_directory()); + prepare_packed_git_one(get_object_directory(), 1); prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) { - alt->name[0] = 0; - prepare_packed_git_one(alt->base); + alt->name[-1] = 0; + prepare_packed_git_one(alt->base, 0); + alt->name[-1] = '/'; } run_once = 1; } @@ -789,6 +841,25 @@ static unsigned long unpack_object_header(struct packed_git *p, unsigned long of return offset; } +int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, + unsigned char *base, unsigned long *sizep, + enum object_type *kindp) +{ + unsigned long ptr; + int status = -1; + + use_packed_git(p); + ptr = offset; + ptr = unpack_object_header(p, ptr, kindp, sizep); + if (*kindp != OBJ_DELTA) + goto done; + memcpy(base, p->pack_base + ptr, 20); + status = 0; + done: + unuse_packed_git(p); + return status; +} + void packed_object_info_detail(struct pack_entry *e, char *type, unsigned long *size, @@ -835,7 +906,8 @@ void packed_object_info_detail(struct pack_entry *e, strcpy(type, "tag"); break; default: - die("corrupted pack file"); + die("corrupted pack file %s containing object of kind %d", + p->pack_name, kind); } *store_size = 0; /* notyet */ } @@ -874,7 +946,8 @@ static int packed_object_info(struct pack_entry *entry, strcpy(type, "tag"); break; default: - die("corrupted pack file"); + die("corrupted pack file %s containing object of kind %d", + p->pack_name, kind); } if (sizep) *sizep = size; @@ -974,7 +1047,7 @@ static void *unpack_entry(struct pack_entry *entry, retval = unpack_entry_gently(entry, type, sizep); unuse_packed_git(p); if (!retval) - die("corrupted pack file"); + die("corrupted pack file %s", p->pack_name); return retval; } @@ -1208,6 +1281,79 @@ char *write_sha1_file_prepare(void *buf, return sha1_file_name(sha1); } +/* + * Link the tempfile to the final place, possibly creating the + * last directory level as you do so. + * + * Returns the errno on failure, 0 on success. + */ +static int link_temp_to_file(const char *tmpfile, char *filename) +{ + int ret; + + if (!link(tmpfile, filename)) + return 0; + + /* + * Try to mkdir the last path component if that failed + * with an ENOENT. + * + * Re-try the "link()" regardless of whether the mkdir + * succeeds, since a race might mean that somebody + * else succeeded. + */ + ret = errno; + if (ret == ENOENT) { + char *dir = strrchr(filename, '/'); + if (dir) { + *dir = 0; + mkdir(filename, 0777); + if (adjust_shared_perm(filename)) + return -2; + *dir = '/'; + if (!link(tmpfile, filename)) + return 0; + ret = errno; + } + } + return ret; +} + +/* + * Move the just written object into its final resting place + */ +int move_temp_to_file(const char *tmpfile, char *filename) +{ + int ret = link_temp_to_file(tmpfile, filename); + + /* + * Coda hack - coda doesn't like cross-directory links, + * so we fall back to a rename, which will mean that it + * won't be able to check collisions, but that's not a + * big deal. + * + * The same holds for FAT formatted media. + * + * When this succeeds, we just return 0. We have nothing + * left to unlink. + */ + if (ret && ret != EEXIST) { + if (!rename(tmpfile, filename)) + return 0; + ret = errno; + } + unlink(tmpfile); + if (ret) { + if (ret != EEXIST) { + fprintf(stderr, "unable to write sha1 filename %s: %s\n", filename, strerror(ret)); + return -1; + } + /* FIXME!!! Collision check here ? */ + } + + return 0; +} + int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) { int size; @@ -1217,7 +1363,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha char *filename; static char tmpfile[PATH_MAX]; unsigned char hdr[50]; - int fd, hdrlen, ret; + int fd, hdrlen; /* Normally if we have it in the pack then we do not bother writing * it out into .git/objects/??/?{38} file. @@ -1238,7 +1384,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha } if (errno != ENOENT) { - fprintf(stderr, "sha1 file %s: %s", filename, strerror(errno)); + fprintf(stderr, "sha1 file %s: %s\n", filename, strerror(errno)); return -1; } @@ -1246,7 +1392,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha fd = mkstemp(tmpfile); if (fd < 0) { - fprintf(stderr, "unable to create temporary sha1 filename %s: %s", tmpfile, strerror(errno)); + fprintf(stderr, "unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno)); return -1; } @@ -1280,32 +1426,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha close(fd); free(compressed); - ret = link(tmpfile, filename); - if (ret < 0) { - ret = errno; - - /* - * Coda hack - coda doesn't like cross-directory links, - * so we fall back to a rename, which will mean that it - * won't be able to check collisions, but that's not a - * big deal. - * - * When this succeeds, we just return 0. We have nothing - * left to unlink. - */ - if (ret == EXDEV && !rename(tmpfile, filename)) - return 0; - } - unlink(tmpfile); - if (ret) { - if (ret != EEXIST) { - fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret)); - return -1; - } - /* FIXME!!! Collision check here ? */ - } - - return 0; + return move_temp_to_file(tmpfile, filename); } int write_sha1_to_fd(int fd, const unsigned char *sha1) @@ -1360,7 +1481,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1) size = write(fd, buf + posn, objsize - posn); if (size <= 0) { if (!size) { - fprintf(stderr, "write closed"); + fprintf(stderr, "write closed\n"); } else { perror("write "); } @@ -1380,8 +1501,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1) int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, size_t bufsize, size_t *bufposn) { - char *filename = sha1_file_name(sha1); - + char tmpfile[PATH_MAX]; int local; z_stream stream; unsigned char real_sha1[20]; @@ -1389,10 +1509,12 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, int ret; SHA_CTX c; - local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666); + snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory()); + local = mkstemp(tmpfile); if (local < 0) - return error("Couldn't open %s\n", filename); + return error("Couldn't open %s for %s", + tmpfile, sha1_to_hex(sha1)); memset(&stream, 0, sizeof(stream)); @@ -1422,7 +1544,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, size = read(fd, buffer + *bufposn, bufsize - *bufposn); if (size <= 0) { close(local); - unlink(filename); + unlink(tmpfile); if (!size) return error("Connection closed?"); perror("Reading from connection"); @@ -1435,15 +1557,15 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, close(local); SHA1_Final(real_sha1, &c); if (ret != Z_STREAM_END) { - unlink(filename); + unlink(tmpfile); return error("File %s corrupted", sha1_to_hex(sha1)); } if (memcmp(sha1, real_sha1, 20)) { - unlink(filename); - return error("File %s has bad hash\n", sha1_to_hex(sha1)); + unlink(tmpfile); + return error("File %s has bad hash", sha1_to_hex(sha1)); } - - return 0; + + return move_temp_to_file(tmpfile, sha1_file_name(sha1)); } int has_pack_index(const unsigned char *sha1) @@ -1478,6 +1600,40 @@ int has_sha1_file(const unsigned char *sha1) return find_sha1_file(sha1, &st) ? 1 : 0; } +int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) +{ + unsigned long size = 4096; + char *buf = malloc(size); + int iret, ret; + unsigned long off = 0; + unsigned char hdr[50]; + int hdrlen; + do { + iret = read(fd, buf + off, size - off); + if (iret > 0) { + off += iret; + if (off == size) { + size *= 2; + buf = realloc(buf, size); + } + } + } while (iret > 0); + if (iret < 0) { + free(buf); + return -1; + } + if (!type) + type = "blob"; + if (write_object) + ret = write_sha1_file(buf, off, type, sha1); + else { + write_sha1_file_prepare(buf, off, type, sha1, hdr, &hdrlen); + ret = 0; + } + free(buf); + return ret; +} + int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type) { unsigned long size = st->st_size; @@ -1505,3 +1661,42 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con munmap(buf, size); return ret; } + +int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object) +{ + int fd; + char *target; + + switch (st->st_mode & S_IFMT) { + case S_IFREG: + fd = open(path, O_RDONLY); + if (fd < 0) + return error("open(\"%s\"): %s", path, + strerror(errno)); + if (index_fd(sha1, fd, st, write_object, NULL) < 0) + return error("%s: failed to insert into database", + path); + break; + case S_IFLNK: + target = xmalloc(st->st_size+1); + if (readlink(path, target, st->st_size+1) != st->st_size) { + char *errstr = strerror(errno); + free(target); + return error("readlink(\"%s\"): %s", path, + errstr); + } + if (!write_object) { + unsigned char hdr[50]; + int hdrlen; + write_sha1_file_prepare(target, st->st_size, "blob", + sha1, hdr, &hdrlen); + } else if (write_sha1_file(target, st->st_size, "blob", sha1)) + return error("%s: failed to insert into database", + path); + free(target); + break; + default: + return error("%s: unsupported file type", path); + } + return 0; +}