X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=sha1_file.c;h=946a35346bdaf21432b7f463712f5bf8881fee39;hb=67ffdf4c0af47679b8a4ac2b70e722e2f65fc4f9;hp=7e4a3df3ad9f7be666256e3219651a5296da7aca;hpb=85c1f337be49eaa9a22e42a1c9958deef5ab57c3;p=git.git diff --git a/sha1_file.c b/sha1_file.c index 7e4a3df3..946a3534 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -10,6 +10,7 @@ #include #include "cache.h" #include "delta.h" +#include "pack.h" #ifndef O_NOATIME #if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) @@ -19,6 +20,8 @@ #endif #endif +const unsigned char null_sha1[20] = { 0, }; + static unsigned int sha1_file_open_flag = O_NOATIME; static unsigned hexval(char c) @@ -45,88 +48,25 @@ int get_sha1_hex(const char *hex, unsigned char *sha1) return 0; } -static int get_sha1_file(const char *path, unsigned char *result) -{ - char buffer[60]; - int fd = open(path, O_RDONLY); - int len; - - if (fd < 0) - return -1; - len = read(fd, buffer, sizeof(buffer)); - close(fd); - if (len < 40) - return -1; - return get_sha1_hex(buffer, result); -} - -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); - 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); - } -} - -char *get_object_directory(void) -{ - if (!git_object_dir) - setup_git_env(); - 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) +int safe_create_leading_directories(char *path) { - if (!git_index_file) - setup_git_env(); - return git_index_file; -} + char *pos = path; + if (*pos == '/') + pos++; -int get_sha1(const char *str, unsigned char *sha1) -{ - static char pathname[PATH_MAX]; - static const char *prefix[] = { - "", - "refs", - "refs/tags", - "refs/heads", - "refs/snap", - NULL - }; - const char **p; - - if (!get_sha1_hex(str, sha1)) - return 0; - - if (!git_dir) - setup_git_env(); - for (p = prefix; *p; p++) { - snprintf(pathname, sizeof(pathname), "%s/%s/%s", - git_dir, *p, str); - if (!get_sha1_file(pathname, sha1)) - return 0; + while (pos) { + pos = strchr(pos, '/'); + if (!pos) + break; + *pos = 0; + if (mkdir(path, 0777) < 0) + if (errno != EEXIST) { + *pos = '/'; + return -1; + } + *pos++ = '/'; } - - return -1; + return 0; } char * sha1_to_hex(const unsigned char *sha1) @@ -184,86 +124,173 @@ char *sha1_file_name(const unsigned char *sha1) return base; } -static struct alternate_object_database { - char *base; - char *name; -} *alt_odb; +char *sha1_pack_name(const unsigned char *sha1) +{ + static const char hex[] = "0123456789abcdef"; + static char *name, *base, *buf; + int i; + + if (!base) { + const char *sha1_file_directory = get_object_directory(); + int len = strlen(sha1_file_directory); + base = xmalloc(len + 60); + sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory); + name = base + len + 11; + } + + buf = name; + + for (i = 0; i < 20; i++) { + unsigned int val = *sha1++; + *buf++ = hex[val >> 4]; + *buf++ = hex[val & 0xf]; + } + + return base; +} + +char *sha1_pack_index_name(const unsigned char *sha1) +{ + static const char hex[] = "0123456789abcdef"; + static char *name, *base, *buf; + int i; + + if (!base) { + const char *sha1_file_directory = get_object_directory(); + int len = strlen(sha1_file_directory); + base = xmalloc(len + 60); + sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.idx", sha1_file_directory); + name = base + len + 11; + } + + buf = name; + + for (i = 0; i < 20; i++) { + unsigned int val = *sha1++; + *buf++ = hex[val >> 4]; + *buf++ = hex[val & 0xf]; + } + + return base; +} + +struct alternate_object_database *alt_odb_list; +static struct alternate_object_database **alt_odb_tail; /* * Prepare alternate object database registry. - * alt_odb points at an array of struct alternate_object_database. - * This array is terminated with an element that has both its base - * and name set to NULL. alt_odb[n] comes from n'th non-empty - * element from colon separated ALTERNATE_DB_ENVIRONMENT environment - * variable, and 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. - * This function allocates the alt_odb array and all the strings - * pointed by base fields of the array elements with one xmalloc(); - * the string pool immediately follows the array. + * + * The variable alt_odb_list points at the list of struct + * 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 + * 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 prepare_alt_odb(void) +static void link_alt_odb_entries(const char *alt, const char *ep, int sep, + const char *relative_base) { - int pass, totlen, i; const char *cp, *last; - char *op = NULL; - const char *alt = gitenv(ALTERNATE_DB_ENVIRONMENT) ? : ""; - - /* The first pass counts how large an area to allocate to - * hold the entire alt_odb structure, including array of - * structs and path buffers for them. The second pass fills - * the structure and prepares the path buffers for use by - * fill_sha1_path(). - */ - for (totlen = pass = 0; pass < 2; pass++) { - last = alt; - i = 0; - do { - cp = strchr(last, ':') ? : last + strlen(last); - if (last != cp) { - /* 43 = 40-byte + 2 '/' + terminating NUL */ - int pfxlen = cp - last; - int entlen = pfxlen + 43; - if (pass == 0) - totlen += entlen; - else { - alt_odb[i].base = op; - alt_odb[i].name = op + pfxlen + 1; - memcpy(op, last, pfxlen); - op[pfxlen] = op[pfxlen + 3] = '/'; - op[entlen-1] = 0; - op += entlen; - } - i++; - } - while (*cp && *cp == ':') + struct alternate_object_database *ent; + int base_len = -1; + + last = alt; + while (last < ep) { + cp = last; + if (cp < ep && *cp == '#') { + while (cp < ep && *cp != sep) cp++; - last = cp; - } while (*cp); - if (pass) - break; - alt_odb = xmalloc(sizeof(*alt_odb) * (i + 1) + totlen); - alt_odb[i].base = alt_odb[i].name = NULL; - op = (char*)(&alt_odb[i+1]); + last = cp + 1; + continue; + } + for ( ; cp < ep && *cp != sep; cp++) + ; + if (last != cp) { + /* 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; + 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; + } + while (cp < ep && *cp == sep) + cp++; + last = cp; } } +void prepare_alt_odb(void) +{ + char path[PATH_MAX]; + char *map; + int fd; + struct stat st; + char *alt; + + alt = getenv(ALTERNATE_DB_ENVIRONMENT); + if (!alt) alt = ""; + + if (alt_odb_tail) + return; + alt_odb_tail = &alt_odb_list; + 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; + if (fstat(fd, &st) || (st.st_size == 0)) { + close(fd); + return; + } + map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (map == MAP_FAILED) + return; + + link_alt_odb_entries(map, map + st.st_size, '\n', + get_object_directory()); + munmap(map, st.st_size); +} + static char *find_sha1_file(const unsigned char *sha1, struct stat *st) { - int i; char *name = sha1_file_name(sha1); + struct alternate_object_database *alt; if (!stat(name, st)) return name; - if (!alt_odb) - prepare_alt_odb(); - for (i = 0; (name = alt_odb[i].name) != NULL; i++) { + prepare_alt_odb(); + for (alt = alt_odb_list; alt; alt = alt->next) { + name = alt->name; fill_sha1_path(name, sha1); - if (!stat(alt_odb[i].base, st)) - return alt_odb[i].base; + if (!stat(alt->base, st)) + return alt->base; } return NULL; } @@ -271,21 +298,7 @@ static char *find_sha1_file(const unsigned char *sha1, struct stat *st) #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; -}; +struct packed_git *packed_git; static int check_packed_git_idx(const char *path, unsigned long *idx_size_, void **idx_map_) @@ -309,9 +322,11 @@ static int check_packed_git_idx(const char *path, unsigned long *idx_size_, return -1; index = idx_map; + *idx_map_ = idx_map; + *idx_size_ = idx_size; /* check index map */ - if (idx_size < 4*256 + 20) + if (idx_size < 4*256 + 20 + 20) return error("index file too small"); nr = 0; for (i = 0; i < 256; i++) { @@ -331,46 +346,79 @@ static int check_packed_git_idx(const char *path, unsigned long *idx_size_, 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) +static int unuse_one_packed_git(void) { - /* NOTYET */ + struct packed_git *p, *lru = NULL; + + for (p = packed_git; p; p = p->next) { + if (p->pack_use_cnt || !p->pack_base) + continue; + if (!lru || p->pack_last_used < lru->pack_last_used) + lru = p; + } + if (!lru) + return 0; + munmap(lru->pack_base, lru->pack_size); + lru->pack_base = NULL; + return 1; } -static int use_packed_git(struct packed_git *p) +void unuse_packed_git(struct packed_git *p) { + p->pack_use_cnt--; +} + +int use_packed_git(struct packed_git *p) +{ + if (!p->pack_size) { + struct stat st; + // We created the struct before we had the pack + stat(p->pack_name, &st); + if (!S_ISREG(st.st_mode)) + die("packfile %s not a regular file", p->pack_name); + p->pack_size = st.st_size; + } 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(); + while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git()) + ; /* nothing */ fd = open(p->pack_name, O_RDONLY); if (fd < 0) - return -1; + die("packfile %s cannot be opened", p->pack_name); if (fstat(fd, &st)) { close(fd); - return -1; + die("packfile %s cannot be opened", p->pack_name); } if (st.st_size != p->pack_size) - return -1; + die("packfile %s size mismatch.", p->pack_name); map = mmap(NULL, p->pack_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (map == MAP_FAILED) - return -1; + die("packfile %s cannot be mapped.", p->pack_name); p->pack_base = map; + + /* Check if the pack file matches with the index file. + * this is cheap. + */ + if (memcmp((char*)(p->index_base) + p->index_size - 40, + p->pack_base + p->pack_size - 20, 20)) { + + die("packfile %s does not match index.", p->pack_name); + } } p->pack_last_used = pack_used_ctr++; + p->pack_use_cnt++; return 0; } -static 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; @@ -395,11 +443,51 @@ static struct packed_git *add_packed_git(char *path, int path_len) p->pack_size = st.st_size; p->index_base = idx_map; p->next = NULL; + p->pack_base = NULL; + p->pack_last_used = 0; + p->pack_use_cnt = 0; + p->pack_local = local; + 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; + + if (check_packed_git_idx(idx_path, &idx_size, &idx_map)) + return NULL; + + path = sha1_pack_name(sha1); + + p = xmalloc(sizeof(*p) + strlen(path) + 2); + strcpy(p->pack_name, path); + p->index_size = idx_size; + p->pack_size = 0; + p->index_base = idx_map; + p->next = NULL; + p->pack_base = NULL; p->pack_last_used = 0; + p->pack_use_cnt = 0; + memcpy(p->sha1, sha1, 20); return p; } -static void prepare_packed_git_one(char *objdir) +void install_packed_git(struct packed_git *pack) +{ + pack->next = packed_git; + packed_git = pack; +} + +static void prepare_packed_git_one(char *objdir, int local) { char path[PATH_MAX]; int len; @@ -421,29 +509,29 @@ 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; packed_git = p; } + closedir(dir); } -static void prepare_packed_git(void) +void prepare_packed_git(void) { - int i; static int run_once = 0; + struct alternate_object_database *alt; - if (run_once++) + 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); + 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, 0); } + run_once = 1; } int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type) @@ -460,8 +548,7 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz } static void *map_sha1_file_internal(const unsigned char *sha1, - unsigned long *size, - int say_error) + unsigned long *size) { struct stat st; void *map; @@ -469,8 +556,6 @@ static void *map_sha1_file_internal(const unsigned char *sha1, char *filename = find_sha1_file(sha1, &st); if (!filename) { - if (say_error) - error("cannot map sha1 file %s", sha1_to_hex(sha1)); return NULL; } @@ -484,8 +569,6 @@ static void *map_sha1_file_internal(const unsigned char *sha1, break; /* Fallthrough */ case 0: - if (say_error) - perror(filename); return NULL; } @@ -496,17 +579,12 @@ static void *map_sha1_file_internal(const unsigned char *sha1, } map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - if (-1 == (int)(long)map) + if (map == MAP_FAILED) return NULL; *size = st.st_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 */ @@ -520,7 +598,7 @@ int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void return inflate(stream, 0); } -void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size) +static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size) { int bytes = strlen(buffer) + 1; unsigned char *buf = xmalloc(1+size); @@ -601,39 +679,190 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l return unpack_sha1_rest(&stream, hdr, *size); } -/* Returns 0 on fast-path success, returns 1 on deltified - * and need to unpack to see info. - */ +/* forward declaration for a mutually recursive function */ +static int packed_object_info(struct pack_entry *entry, + char *type, unsigned long *sizep); + +static int packed_delta_info(unsigned char *base_sha1, + unsigned long delta_size, + unsigned long left, + char *type, + unsigned long *sizep, + struct packed_git *p) +{ + struct pack_entry base_ent; + + if (left < 20) + die("truncated pack file"); + + /* The base entry _must_ be in the same pack */ + if (!find_pack_entry_one(base_sha1, &base_ent, p)) + die("failed to find delta-pack base object %s", + sha1_to_hex(base_sha1)); + + /* We choose to only get the type of the base object and + * ignore potentially corrupt pack file that expects the delta + * based on a base with a wrong size. This saves tons of + * inflate() calls. + */ + + if (packed_object_info(&base_ent, type, NULL)) + die("cannot get info for delta-pack base"); + + if (sizep) { + const unsigned char *data; + unsigned char delta_head[64]; + unsigned long result_size; + z_stream stream; + int st; + + memset(&stream, 0, sizeof(stream)); + + data = stream.next_in = base_sha1 + 20; + stream.avail_in = left - 20; + stream.next_out = delta_head; + stream.avail_out = sizeof(delta_head); + + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + if ((st != Z_STREAM_END) && + stream.total_out != sizeof(delta_head)) + die("delta data unpack-initial failed"); + + /* Examine the initial part of the delta to figure out + * the result size. + */ + data = delta_head; + get_delta_hdr_size(&data); /* ignore base size */ + + /* Read the result size */ + result_size = get_delta_hdr_size(&data); + *sizep = result_size; + } + return 0; +} + +static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset, + enum object_type *type, unsigned long *sizep) +{ + unsigned shift; + unsigned char *pack, c; + unsigned long size; + + if (offset >= p->pack_size) + die("object offset outside of pack file"); + + pack = p->pack_base + offset; + c = *pack++; + offset++; + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + if (offset >= p->pack_size) + die("object offset outside of pack file"); + c = *pack++; + offset++; + size += (c & 0x7f) << shift; + shift += 7; + } + *sizep = size; + return offset; +} + +void packed_object_info_detail(struct pack_entry *e, + char *type, + unsigned long *size, + unsigned long *store_size, + int *delta_chain_length, + unsigned char *base_sha1) +{ + struct packed_git *p = e->p; + unsigned long offset, left; + unsigned char *pack; + enum object_type kind; + + offset = unpack_object_header(p, e->offset, &kind, size); + pack = p->pack_base + offset; + left = p->pack_size - offset; + if (kind != OBJ_DELTA) + *delta_chain_length = 0; + else { + int chain_length = 0; + memcpy(base_sha1, pack, 20); + do { + struct pack_entry base_ent; + unsigned long junk; + + find_pack_entry_one(pack, &base_ent, p); + offset = unpack_object_header(p, base_ent.offset, + &kind, &junk); + pack = p->pack_base + offset; + chain_length++; + } while (kind == OBJ_DELTA); + *delta_chain_length = chain_length; + } + switch (kind) { + case OBJ_COMMIT: + strcpy(type, "commit"); + break; + case OBJ_TREE: + strcpy(type, "tree"); + break; + case OBJ_BLOB: + strcpy(type, "blob"); + break; + case OBJ_TAG: + strcpy(type, "tag"); + break; + default: + die("corrupted pack file %s containing object of kind %d", + p->pack_name, kind); + } + *store_size = 0; /* notyet */ +} + static int packed_object_info(struct pack_entry *entry, char *type, unsigned long *sizep) { struct packed_git *p = entry->p; unsigned long offset, size, left; unsigned char *pack; + enum object_type kind; + int retval; - 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"); + + offset = unpack_object_header(p, entry->offset, &kind, &size); 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': + left = p->pack_size - offset; + + switch (kind) { + case OBJ_DELTA: + retval = packed_delta_info(pack, size, left, type, sizep, p); + unuse_packed_git(p); + return retval; + case OBJ_COMMIT: strcpy(type, "commit"); break; - case 'T': + case OBJ_TREE: strcpy(type, "tree"); break; - case 'B': + case OBJ_BLOB: strcpy(type, "blob"); break; + case OBJ_TAG: + strcpy(type, "tag"); + break; default: - die("corrupted pack file"); + die("corrupted pack file %s containing object of kind %d", + p->pack_name, kind); } - *sizep = size; + if (sizep) + *sizep = size; + unuse_packed_git(p); return 0; } @@ -644,8 +873,10 @@ static void *unpack_delta_entry(unsigned char *base_sha1, unsigned long delta_size, unsigned long left, char *type, - unsigned long *sizep) + unsigned long *sizep, + struct packed_git *p) { + struct pack_entry base_ent; void *data, *delta_data, *result, *base; unsigned long data_size, result_size, base_size; z_stream stream; @@ -670,8 +901,11 @@ static void *unpack_delta_entry(unsigned char *base_sha1, 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); + /* The base entry _must_ be in the same pack */ + if (!find_pack_entry_one(base_sha1, &base_ent, p)) + die("failed to find delta-pack base object %s", + sha1_to_hex(base_sha1)); + base = unpack_entry_gently(&base_ent, type, &base_size); if (!base) die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); @@ -692,7 +926,7 @@ static void *unpack_non_delta_entry(unsigned char *data, { int st; z_stream stream; - char *buffer; + unsigned char *buffer; buffer = xmalloc(size + 1); buffer[size] = 0; @@ -717,42 +951,74 @@ 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"); + void *retval; if (use_packed_git(p)) die("cannot map packed file"); + retval = unpack_entry_gently(entry, type, sizep); + unuse_packed_git(p); + if (!retval) + die("corrupted pack file %s", p->pack_name); + return retval; +} +/* The caller is responsible for use_packed_git()/unuse_packed_git() pair */ +void *unpack_entry_gently(struct pack_entry *entry, + char *type, unsigned long *sizep) +{ + struct packed_git *p = entry->p; + unsigned long offset, size, left; + unsigned char *pack; + enum object_type kind; + void *retval; + + offset = unpack_object_header(p, entry->offset, &kind, &size); 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': + left = p->pack_size - offset; + switch (kind) { + case OBJ_DELTA: + retval = unpack_delta_entry(pack, size, left, type, sizep, p); + return retval; + case OBJ_COMMIT: strcpy(type, "commit"); break; - case 'T': + case OBJ_TREE: strcpy(type, "tree"); break; - case 'B': + case OBJ_BLOB: strcpy(type, "blob"); break; + case OBJ_TAG: + strcpy(type, "tag"); + break; default: - die("corrupted pack file"); + return NULL; } *sizep = size; - return unpack_non_delta_entry(pack+5, size, left); + retval = unpack_non_delta_entry(pack, size, left); + return retval; +} + +int num_packed_objects(const struct packed_git *p) +{ + /* See check_packed_git_idx() */ + return (p->index_size - 20 - 20 - 4*256) / 24; } -static int find_pack_entry_1(const unsigned char *sha1, - struct pack_entry *e, struct packed_git *p) +int nth_packed_object_sha1(const struct packed_git *p, int n, + unsigned char* sha1) { - int *level1_ofs = p->index_base; + void *index = p->index_base + 256; + if (n < 0 || num_packed_objects(p) <= n) + return -1; + memcpy(sha1, (index + 24 * n + 4), 20); + return 0; +} + +int find_pack_entry_one(const unsigned char *sha1, + struct pack_entry *e, struct packed_git *p) +{ + unsigned 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; @@ -780,12 +1046,26 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) prepare_packed_git(); for (p = packed_git; p; p = p->next) { - if (find_pack_entry_1(sha1, e, p)) + if (find_pack_entry_one(sha1, e, p)) return 1; } return 0; } +struct packed_git *find_sha1_pack(const unsigned char *sha1, + struct packed_git *packs) +{ + struct packed_git *p; + struct pack_entry e; + + for (p = packs; p; p = p->next) { + if (find_pack_entry_one(sha1, &e, p)) + return p; + } + return NULL; + +} + int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep) { int status; @@ -794,18 +1074,13 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep z_stream stream; char hdr[128]; - map = map_sha1_file_internal(sha1, &mapsize, 0); + map = map_sha1_file_internal(sha1, &mapsize); 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; + return packed_object_info(&e, type, sizep); } if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) status = error("unable to unpack %s header", @@ -814,7 +1089,8 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep status = error("unable to parse %s header", sha1_to_hex(sha1)); else { status = 0; - *sizep = size; + if (sizep) + *sizep = size; } inflateEnd(&stream); munmap(map, mapsize); @@ -836,14 +1112,17 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size { unsigned long mapsize; void *map, *buf; + struct pack_entry e; - map = map_sha1_file_internal(sha1, &mapsize, 0); + if (find_pack_entry(sha1, &e)) + return read_packed_sha1(sha1, type, size); + map = map_sha1_file_internal(sha1, &mapsize); if (map) { buf = unpack_sha1_file(map, mapsize, type, size); munmap(map, mapsize); return buf; } - return read_packed_sha1(sha1, type, size); + return NULL; } void *read_object_with_reference(const unsigned char *sha1, @@ -886,36 +1165,123 @@ void *read_object_with_reference(const unsigned char *sha1, free(buffer); return NULL; } + free(buffer); /* Now we have the ID of the referred-to object in * actual_sha1. Check again. */ } } -int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) +char *write_sha1_file_prepare(void *buf, + unsigned long len, + const char *type, + unsigned char *sha1, + unsigned char *hdr, + int *hdrlen) { - int size; - unsigned char *compressed; - z_stream stream; - unsigned char sha1[20]; SHA_CTX c; - char *filename; - static char tmpfile[PATH_MAX]; - unsigned char hdr[50]; - int fd, hdrlen, ret; /* Generate the header */ - hdrlen = sprintf((char *)hdr, "%s %lu", type, len)+1; + *hdrlen = sprintf((char *)hdr, "%s %lu", type, len)+1; /* Sha1.. */ SHA1_Init(&c); - SHA1_Update(&c, hdr, hdrlen); + SHA1_Update(&c, hdr, *hdrlen); SHA1_Update(&c, buf, len); SHA1_Final(sha1, &c); + 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); + *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", 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; + unsigned char *compressed; + z_stream stream; + unsigned char sha1[20]; + char *filename; + static char tmpfile[PATH_MAX]; + unsigned char hdr[50]; + int fd, hdrlen; + + /* Normally if we have it in the pack then we do not bother writing + * it out into .git/objects/??/?{38} file. + */ + filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); if (returnsha1) memcpy(returnsha1, sha1, 20); - - filename = sha1_file_name(sha1); + if (has_sha1_file(sha1)) + return 0; fd = open(filename, O_RDONLY); if (fd >= 0) { /* @@ -969,50 +1335,94 @@ 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; + return move_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. - * - * When this succeeds, we just return 0. We have nothing - * left to unlink. - */ - if (ret == EXDEV && !rename(tmpfile, filename)) - return 0; +int write_sha1_to_fd(int fd, const unsigned char *sha1) +{ + ssize_t size; + unsigned long objsize; + int posn = 0; + void *map = map_sha1_file_internal(sha1, &objsize); + void *buf = map; + void *temp_obj = NULL; + z_stream stream; + + if (!buf) { + unsigned char *unpacked; + unsigned long len; + char type[20]; + char hdr[50]; + int hdrlen; + // need to unpack and recompress it by itself + unpacked = read_packed_sha1(sha1, type, &len); + + hdrlen = sprintf(hdr, "%s %lu", type, len) + 1; + + /* Set it up */ + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, Z_BEST_COMPRESSION); + size = deflateBound(&stream, len + hdrlen); + temp_obj = buf = xmalloc(size); + + /* Compress it */ + stream.next_out = buf; + stream.avail_out = size; + + /* First header.. */ + stream.next_in = (void *)hdr; + stream.avail_in = hdrlen; + while (deflate(&stream, 0) == Z_OK) + /* nothing */; + + /* Then the data itself.. */ + stream.next_in = unpacked; + stream.avail_in = len; + while (deflate(&stream, Z_FINISH) == Z_OK) + /* nothing */; + deflateEnd(&stream); + free(unpacked); + + objsize = stream.total_out; } - unlink(tmpfile); - if (ret) { - if (ret != EEXIST) { - fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret)); + + do { + size = write(fd, buf + posn, objsize - posn); + if (size <= 0) { + if (!size) { + fprintf(stderr, "write closed"); + } else { + perror("write "); + } return -1; } - /* FIXME!!! Collision check here ? */ - } + posn += size; + } while (posn < objsize); + + if (map) + munmap(map, objsize); + if (temp_obj) + free(temp_obj); return 0; } -int write_sha1_from_fd(const unsigned char *sha1, int fd) +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]; - unsigned char buf[4096]; unsigned char discard[4096]; 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\n", tmpfile, sha1_to_hex(sha1)); memset(&stream, 0, sizeof(stream)); @@ -1022,41 +1432,70 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd) do { ssize_t size; - size = read(fd, buf, 4096); + if (*bufposn) { + stream.avail_in = *bufposn; + stream.next_in = (unsigned char *) buffer; + do { + stream.next_out = discard; + stream.avail_out = sizeof(discard); + ret = inflate(&stream, Z_SYNC_FLUSH); + SHA1_Update(&c, discard, sizeof(discard) - + stream.avail_out); + } while (stream.avail_in && ret == Z_OK); + write(local, buffer, *bufposn - stream.avail_in); + memmove(buffer, buffer + *bufposn - stream.avail_in, + stream.avail_in); + *bufposn = stream.avail_in; + if (ret != Z_OK) + break; + } + 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"); return -1; } - write(local, buf, size); - stream.avail_in = size; - stream.next_in = buf; - do { - stream.next_out = discard; - stream.avail_out = sizeof(discard); - ret = inflate(&stream, Z_SYNC_FLUSH); - SHA1_Update(&c, discard, sizeof(discard) - - stream.avail_out); - } while (stream.avail_in && ret == Z_OK); - - } while (ret == Z_OK); + *bufposn += size; + } while (1); inflateEnd(&stream); 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); + unlink(tmpfile); return error("File %s has bad hash\n", sha1_to_hex(sha1)); } - - return 0; + + return move_temp_to_file(tmpfile, sha1_file_name(sha1)); +} + +int has_pack_index(const unsigned char *sha1) +{ + struct stat st; + if (stat(sha1_pack_index_name(sha1), &st)) + return 0; + return 1; +} + +int has_pack_file(const unsigned char *sha1) +{ + struct stat st; + if (stat(sha1_pack_name(sha1), &st)) + return 0; + return 1; +} + +int has_sha1_pack(const unsigned char *sha1) +{ + struct pack_entry e; + return find_pack_entry(sha1, &e); } int has_sha1_file(const unsigned char *sha1) @@ -1064,26 +1503,74 @@ int has_sha1_file(const unsigned char *sha1) struct stat st; struct pack_entry e; - if (find_sha1_file(sha1, &st)) + if (find_pack_entry(sha1, &e)) return 1; - return find_pack_entry(sha1, &e); + return find_sha1_file(sha1, &st) ? 1 : 0; } -int index_fd(unsigned char *sha1, int fd, struct stat *st) +int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type) { unsigned long size = st->st_size; void *buf; int ret; + unsigned char hdr[50]; + int hdrlen; buf = ""; if (size) buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - if ((int)(long)buf == -1) + if (buf == MAP_FAILED) return -1; - ret = write_sha1_file(buf, size, "blob", sha1); + if (!type) + type = "blob"; + if (write_object) + ret = write_sha1_file(buf, size, type, sha1); + else { + write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen); + ret = 0; + } if (size) 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; +}