X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=sha1_file.c;h=7e4a3df3ad9f7be666256e3219651a5296da7aca;hb=f49fb35d0d58271dc7c93c2752ced0d743fdf12e;hp=a2ba4c81dba1b55b119d9ec3c42a7e4ce4ca1df5;hpb=65c2e0c349aa5c7f605defb52dc67f1b3658a1b9;p=git.git diff --git a/sha1_file.c b/sha1_file.c index a2ba4c81..7e4a3df3 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -6,6 +6,8 @@ * This handles basic git sha1 object files - packing, unpacking, * creation etc. */ +#include +#include #include "cache.h" #include "delta.h" @@ -58,7 +60,7 @@ static int get_sha1_file(const char *path, unsigned char *result) return get_sha1_hex(buffer, result); } -static char *git_dir, *git_object_dir, *git_index_file; +static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir; static void setup_git_env(void) { git_dir = gitenv(GIT_DIR_ENVIRONMENT); @@ -69,6 +71,8 @@ static void setup_git_env(void) 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); @@ -83,6 +87,13 @@ char *get_object_directory(void) return git_object_dir; } +char *get_refs_directory(void) +{ + if (!git_refs_dir) + setup_git_env(); + return git_refs_dir; +} + char *get_index_file(void) { if (!git_index_file) @@ -257,7 +268,185 @@ static char *find_sha1_file(const unsigned char *sha1, struct stat *st) return NULL; } -int check_sha1_signature(unsigned char *sha1, void *map, unsigned long size, const char *type) +#define PACK_MAX_SZ (1<<26) +static int pack_used_ctr; +static unsigned long pack_mapped; +static struct packed_git { + struct packed_git *next; + unsigned long index_size; + unsigned long pack_size; + unsigned int *index_base; + void *pack_base; + unsigned int pack_last_used; + char pack_name[0]; /* something like ".git/objects/pack/xxxxx.pack" */ +} *packed_git; + +struct pack_entry { + unsigned int offset; + unsigned char sha1[20]; + struct packed_git *p; +}; + +static int check_packed_git_idx(const char *path, unsigned long *idx_size_, + void **idx_map_) +{ + void *idx_map; + unsigned int *index; + unsigned long idx_size; + int nr, i; + int fd = open(path, O_RDONLY); + struct stat st; + if (fd < 0) + return -1; + if (fstat(fd, &st)) { + close(fd); + return -1; + } + idx_size = st.st_size; + idx_map = mmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (idx_map == MAP_FAILED) + return -1; + + index = idx_map; + + /* check index map */ + if (idx_size < 4*256 + 20) + return error("index file too small"); + nr = 0; + for (i = 0; i < 256; i++) { + unsigned int n = ntohl(index[i]); + if (n < nr) + return error("non-monotonic index"); + nr = n; + } + + /* + * Total size: + * - 256 index entries 4 bytes each + * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + */ + if (idx_size != 4*256 + nr * 24 + 20 + 20) + return error("wrong index file size"); + + *idx_map_ = idx_map; + *idx_size_ = idx_size; + return 0; +} + +static void unuse_one_packed_git(void) +{ + /* NOTYET */ +} + +static int use_packed_git(struct packed_git *p) +{ + if (!p->pack_base) { + int fd; + struct stat st; + void *map; + + pack_mapped += p->pack_size; + while (PACK_MAX_SZ < pack_mapped) + unuse_one_packed_git(); + fd = open(p->pack_name, O_RDONLY); + if (fd < 0) + return -1; + if (fstat(fd, &st)) { + close(fd); + return -1; + } + if (st.st_size != p->pack_size) + return -1; + map = mmap(NULL, p->pack_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (map == MAP_FAILED) + return -1; + p->pack_base = map; + } + p->pack_last_used = pack_used_ctr++; + return 0; +} + +static struct packed_git *add_packed_git(char *path, int path_len) +{ + struct stat st; + struct packed_git *p; + unsigned long idx_size; + void *idx_map; + + if (check_packed_git_idx(path, &idx_size, &idx_map)) + return NULL; + + /* do we have a corresponding .pack file? */ + strcpy(path + path_len - 4, ".pack"); + if (stat(path, &st) || !S_ISREG(st.st_mode)) { + munmap(idx_map, idx_size); + return NULL; + } + /* ok, it looks sane as far as we can check without + * actually mapping the pack file. + */ + p = xmalloc(sizeof(*p) + path_len + 2); + strcpy(p->pack_name, path); + p->index_size = idx_size; + p->pack_size = st.st_size; + p->index_base = idx_map; + p->next = NULL; + p->pack_last_used = 0; + return p; +} + +static void prepare_packed_git_one(char *objdir) +{ + char path[PATH_MAX]; + int len; + DIR *dir; + struct dirent *de; + + sprintf(path, "%s/pack", objdir); + len = strlen(path); + dir = opendir(path); + if (!dir) + return; + path[len++] = '/'; + while ((de = readdir(dir)) != NULL) { + int namelen = strlen(de->d_name); + struct packed_git *p; + + if (strcmp(de->d_name + namelen - 4, ".idx")) + continue; + + /* we have .idx. Is it a file we can map? */ + strcpy(path + len, de->d_name); + p = add_packed_git(path, len + namelen); + if (!p) + continue; + p->next = packed_git; + packed_git = p; + } +} + +static void prepare_packed_git(void) +{ + int i; + static int run_once = 0; + + if (run_once++) + return; + + prepare_packed_git_one(get_object_directory()); + if (!alt_odb) + prepare_alt_odb(); + for (i = 0; alt_odb[i].base != NULL; i++) { + alt_odb[i].name[0] = 0; + prepare_packed_git_one(alt_odb[i].base); + } +} + +int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type) { char header[100]; unsigned char real_sha1[20]; @@ -270,7 +459,9 @@ int check_sha1_signature(unsigned char *sha1, void *map, unsigned long size, con return memcmp(sha1, real_sha1, 20) ? -1 : 0; } -void *map_sha1_file(const unsigned char *sha1, unsigned long *size) +static void *map_sha1_file_internal(const unsigned char *sha1, + unsigned long *size, + int say_error) { struct stat st; void *map; @@ -278,7 +469,8 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) char *filename = find_sha1_file(sha1, &st); if (!filename) { - error("cannot map sha1 file %s", sha1_to_hex(sha1)); + if (say_error) + error("cannot map sha1 file %s", sha1_to_hex(sha1)); return NULL; } @@ -292,11 +484,14 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) break; /* Fallthrough */ case 0: - perror(filename); + if (say_error) + perror(filename); return NULL; } - /* If it failed once, it will probably fail again. Stop using O_NOATIME */ + /* If it failed once, it will probably fail again. + * Stop using O_NOATIME + */ sha1_file_open_flag = 0; } map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); @@ -307,6 +502,11 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) return map; } +void *map_sha1_file(const unsigned char *sha1, unsigned long *size) +{ + return map_sha1_file_internal(sha1, size, 1); +} + int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size) { /* Get the data stream */ @@ -323,7 +523,7 @@ int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size) { int bytes = strlen(buffer) + 1; - char *buf = xmalloc(1+size); + unsigned char *buf = xmalloc(1+size); memcpy(buf, buffer + bytes, stream->total_out - bytes); bytes = stream->total_out - bytes; @@ -401,122 +601,249 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l return unpack_sha1_rest(&stream, hdr, *size); } -int sha1_delta_base(const unsigned char *sha1, unsigned char *base_sha1) +/* Returns 0 on fast-path success, returns 1 on deltified + * and need to unpack to see info. + */ +static int packed_object_info(struct pack_entry *entry, + char *type, unsigned long *sizep) { - int ret; - unsigned long mapsize, size; - void *map; + struct packed_git *p = entry->p; + unsigned long offset, size, left; + unsigned char *pack; + + offset = entry->offset; + if (p->pack_size - 5 < offset) + die("object offset outside of pack file"); + pack = p->pack_base + offset; + size = (pack[1] << 24) + (pack[2] << 16) + (pack[3] << 8) + pack[4]; + left = p->pack_size - offset - 5; + switch (*pack) { + case 'D': + return 1; + break; + case 'C': + strcpy(type, "commit"); + break; + case 'T': + strcpy(type, "tree"); + break; + case 'B': + strcpy(type, "blob"); + break; + default: + die("corrupted pack file"); + } + *sizep = size; + return 0; +} + +/* forward declaration for a mutually recursive function */ +static void *unpack_entry(struct pack_entry *, char *, unsigned long *); + +static void *unpack_delta_entry(unsigned char *base_sha1, + unsigned long delta_size, + unsigned long left, + char *type, + unsigned long *sizep) +{ + void *data, *delta_data, *result, *base; + unsigned long data_size, result_size, base_size; z_stream stream; - char hdr[64], type[20]; - void *delta_data_head; + int st; - map = map_sha1_file(sha1, &mapsize); - if (!map) - return -1; - ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)); - if (ret < Z_OK || parse_sha1_header(hdr, type, &size) < 0) { - ret = -1; - goto out; - } - if (strcmp(type, "delta")) { - ret = 0; - goto out; - } + if (left < 20) + die("truncated pack file"); + data = base_sha1 + 20; + data_size = left - 20; + delta_data = xmalloc(delta_size); + + memset(&stream, 0, sizeof(stream)); - delta_data_head = hdr + strlen(hdr) + 1; - ret = 1; - memcpy(base_sha1, delta_data_head, 20); - out: + stream.next_in = data; + stream.avail_in = data_size; + stream.next_out = delta_data; + stream.avail_out = delta_size; + + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); inflateEnd(&stream); - munmap(map, mapsize); - return ret; + if ((st != Z_STREAM_END) || stream.total_out != delta_size) + die("delta data unpack failed"); + + /* This may recursively unpack the base, which is what we want */ + base = read_sha1_file(base_sha1, type, &base_size); + if (!base) + die("failed to read delta-pack base object %s", + sha1_to_hex(base_sha1)); + result = patch_delta(base, base_size, + delta_data, delta_size, + &result_size); + if (!result) + die("failed to apply delta"); + free(delta_data); + free(base); + *sizep = result_size; + return result; } -int sha1_file_size(const unsigned char *sha1, unsigned long *sizep) +static void *unpack_non_delta_entry(unsigned char *data, + unsigned long size, + unsigned long left) { - int ret, status; - unsigned long mapsize, size; - void *map; + int st; z_stream stream; - char hdr[64], type[20]; - const unsigned char *data; - unsigned char cmd; - int i; + char *buffer; - map = map_sha1_file(sha1, &mapsize); - if (!map) - return -1; - ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)); - status = -1; - if (ret < Z_OK || parse_sha1_header(hdr, type, &size) < 0) - goto out; - if (strcmp(type, "delta")) { - *sizep = size; - status = 0; - goto out; + buffer = xmalloc(size + 1); + buffer[size] = 0; + memset(&stream, 0, sizeof(stream)); + stream.next_in = data; + stream.avail_in = left; + stream.next_out = buffer; + stream.avail_out = size; + + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + if ((st != Z_STREAM_END) || stream.total_out != size) { + free(buffer); + return NULL; } - /* We are dealing with a delta object. Inflated, the first - * 20 bytes hold the base object SHA1, and delta data follows - * immediately after it. - * - * The initial part of the delta starts at delta_data_head + - * 20. Borrow code from patch-delta to read the result size. - */ - data = hdr + strlen(hdr) + 1 + 20; + return buffer; +} - /* Skip over the source size; we are not interested in - * it and we cannot verify it because we do not want - * to read the base object. - */ - cmd = *data++; - while (cmd) { - if (cmd & 1) - data++; - cmd >>= 1; - } - /* Read the result size */ - size = i = 0; - cmd = *data++; - while (cmd) { - if (cmd & 1) - size |= *data++ << i; - i += 8; - cmd >>= 1; +static void *unpack_entry(struct pack_entry *entry, + char *type, unsigned long *sizep) +{ + struct packed_git *p = entry->p; + unsigned long offset, size, left; + unsigned char *pack; + + offset = entry->offset; + if (p->pack_size - 5 < offset) + die("object offset outside of pack file"); + + if (use_packed_git(p)) + die("cannot map packed file"); + + pack = p->pack_base + offset; + size = (pack[1] << 24) + (pack[2] << 16) + (pack[3] << 8) + pack[4]; + left = p->pack_size - offset - 5; + switch (*pack) { + case 'D': + return unpack_delta_entry(pack+5, size, left, type, sizep); + case 'C': + strcpy(type, "commit"); + break; + case 'T': + strcpy(type, "tree"); + break; + case 'B': + strcpy(type, "blob"); + break; + default: + die("corrupted pack file"); } *sizep = size; - status = 0; - out: + return unpack_non_delta_entry(pack+5, size, left); +} + +static int find_pack_entry_1(const unsigned char *sha1, + struct pack_entry *e, struct packed_git *p) +{ + int *level1_ofs = p->index_base; + int hi = ntohl(level1_ofs[*sha1]); + int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1])); + void *index = p->index_base + 256; + + do { + int mi = (lo + hi) / 2; + int cmp = memcmp(index + 24 * mi + 4, sha1, 20); + if (!cmp) { + e->offset = ntohl(*((int*)(index + 24 * mi))); + memcpy(e->sha1, sha1, 20); + e->p = p; + return 1; + } + if (cmp > 0) + hi = mi; + else + lo = mi+1; + } while (lo < hi); + return 0; +} + +static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) +{ + struct packed_git *p; + prepare_packed_git(); + + for (p = packed_git; p; p = p->next) { + if (find_pack_entry_1(sha1, e, p)) + return 1; + } + return 0; +} + +int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep) +{ + int status; + unsigned long mapsize, size; + void *map; + z_stream stream; + char hdr[128]; + + map = map_sha1_file_internal(sha1, &mapsize, 0); + if (!map) { + struct pack_entry e; + + if (!find_pack_entry(sha1, &e)) + return error("unable to find %s", sha1_to_hex(sha1)); + if (!packed_object_info(&e, type, sizep)) + return 0; + /* sheesh */ + map = unpack_entry(&e, type, sizep); + free(map); + return (map == NULL) ? 0 : -1; + } + if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) + status = error("unable to unpack %s header", + sha1_to_hex(sha1)); + if (parse_sha1_header(hdr, type, &size) < 0) + status = error("unable to parse %s header", sha1_to_hex(sha1)); + else { + status = 0; + *sizep = size; + } inflateEnd(&stream); munmap(map, mapsize); return status; } +static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size) +{ + struct pack_entry e; + + if (!find_pack_entry(sha1, &e)) { + error("cannot read sha1_file for %s", sha1_to_hex(sha1)); + return NULL; + } + return unpack_entry(&e, type, size); +} + void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size) { unsigned long mapsize; void *map, *buf; - map = map_sha1_file(sha1, &mapsize); + map = map_sha1_file_internal(sha1, &mapsize, 0); if (map) { buf = unpack_sha1_file(map, mapsize, type, size); munmap(map, mapsize); - if (buf && !strcmp(type, "delta")) { - void *ref = NULL, *delta = buf; - unsigned long ref_size, delta_size = *size; - buf = NULL; - if (delta_size > 20) - ref = read_sha1_file(delta, type, &ref_size); - if (ref) - buf = patch_delta(ref, ref_size, - delta+20, delta_size-20, - size); - free(delta); - free(ref); - } return buf; } - return NULL; + return read_packed_sha1(sha1, type, size); } void *read_object_with_reference(const unsigned char *sha1, @@ -735,7 +1062,11 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd) int has_sha1_file(const unsigned char *sha1) { struct stat st; - return !!find_sha1_file(sha1, &st); + struct pack_entry e; + + if (find_sha1_file(sha1, &st)) + return 1; + return find_pack_entry(sha1, &e); } int index_fd(unsigned char *sha1, int fd, struct stat *st)