diff options
Diffstat (limited to 'sha1_file.c')
-rw-r--r-- | sha1_file.c | 1422 |
1 files changed, 919 insertions, 503 deletions
diff --git a/sha1_file.c b/sha1_file.c index 191f814e09..f615fc7206 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -11,11 +11,14 @@ #include "pack.h" #include "blob.h" #include "commit.h" +#include "run-command.h" #include "tag.h" #include "tree.h" +#include "tree-walk.h" #include "refs.h" #include "pack-revindex.h" #include "sha1-lookup.h" +#include "bulk-checkin.h" #ifndef O_NOATIME #if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) @@ -25,77 +28,91 @@ #endif #endif -#ifdef NO_C99_FORMAT -#define SZ_FMT "lu" -static unsigned long sz_fmt(size_t s) { return (unsigned long)s; } -#else -#define SZ_FMT "zu" -static size_t sz_fmt(size_t s) { return s; } -#endif +#define SZ_FMT PRIuMAX +static inline uintmax_t sz_fmt(size_t s) { return s; } const unsigned char null_sha1[20]; -const signed char hexval_table[256] = { - -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ - 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ - 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ +/* + * This is meant to hold a *small* number of objects that you would + * want read_sha1_file() to be able to return, but yet you do not want + * to write them into the object store (e.g. a browse-only + * application). + */ +static struct cached_object { + unsigned char sha1[20]; + enum object_type type; + void *buf; + unsigned long size; +} *cached_objects; +static int cached_object_nr, cached_object_alloc; + +static struct cached_object empty_tree = { + EMPTY_TREE_SHA1_BIN_LITERAL, + OBJ_TREE, + "", + 0 }; -int get_sha1_hex(const char *hex, unsigned char *sha1) +static struct packed_git *last_found_pack; + +static struct cached_object *find_cached_object(const unsigned char *sha1) { int i; - for (i = 0; i < 20; i++) { - unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); - if (val & ~0xff) + struct cached_object *co = cached_objects; + + for (i = 0; i < cached_object_nr; i++, co++) { + if (!hashcmp(co->sha1, sha1)) + return co; + } + if (!hashcmp(sha1, empty_tree.sha1)) + return &empty_tree; + return NULL; +} + +int mkdir_in_gitdir(const char *path) +{ + if (mkdir(path, 0777)) { + int saved_errno = errno; + struct stat st; + struct strbuf sb = STRBUF_INIT; + + if (errno != EEXIST) + return -1; + /* + * Are we looking at a path in a symlinked worktree + * whose original repository does not yet have it? + * e.g. .git/rr-cache pointing at its original + * repository in which the user hasn't performed any + * conflict resolution yet? + */ + if (lstat(path, &st) || !S_ISLNK(st.st_mode) || + strbuf_readlink(&sb, path, st.st_size) || + !is_absolute_path(sb.buf) || + mkdir(sb.buf, 0777)) { + strbuf_release(&sb); + errno = saved_errno; return -1; - *sha1++ = val; - hex += 2; + } + strbuf_release(&sb); } - return 0; + return adjust_shared_perm(path); } int safe_create_leading_directories(char *path) { - char *pos = path; + char *pos = path + offset_1st_component(path); struct stat st; - if (is_absolute_path(path)) - pos++; - while (pos) { pos = strchr(pos, '/'); if (!pos) break; - *pos = 0; + while (*++pos == '/') + ; + if (!*pos) + break; + *--pos = '\0'; if (!stat(path, &st)) { /* path exists */ if (!S_ISDIR(st.st_mode)) { @@ -116,22 +133,13 @@ int safe_create_leading_directories(char *path) return 0; } -char *sha1_to_hex(const unsigned char *sha1) +int safe_create_leading_directories_const(const char *path) { - static int bufno; - static char hexbuffer[4][50]; - static const char hex[] = "0123456789abcdef"; - char *buffer = hexbuffer[3 & ++bufno], *buf = buffer; - int i; - - for (i = 0; i < 20; i++) { - unsigned int val = *sha1++; - *buf++ = hex[val >> 4]; - *buf++ = hex[val & 0xf]; - } - *buf = '\0'; - - return buffer; + /* path points to cache entries, so xstrdup before messing with it */ + char *buf = xstrdup(path); + int result = safe_create_leading_directories(buf); + free(buf); + return result; } static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) @@ -158,20 +166,22 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) */ char *sha1_file_name(const unsigned char *sha1) { - static char *name, *base; + static char buf[PATH_MAX]; + const char *objdir; + int len; - if (!base) { - const char *sha1_file_directory = get_object_directory(); - int len = strlen(sha1_file_directory); - base = xmalloc(len + 60); - memcpy(base, sha1_file_directory, len); - memset(base+len, 0, 60); - base[len] = '/'; - base[len+3] = '/'; - name = base + len + 1; - } - fill_sha1_path(name, sha1); - return base; + objdir = get_object_directory(); + len = strlen(objdir); + + /* '/' + sha1(2) + '/' + sha1(38) + '\0' */ + if (len + 43 > PATH_MAX) + die("insanely long object directory %s", objdir); + memcpy(buf, objdir, len); + buf[len] = '/'; + buf[len+3] = '/'; + buf[len+42] = '\0'; + fill_sha1_path(buf + len + 1, sha1); + return buf; } static char *sha1_get_pack_name(const unsigned char *sha1, @@ -218,7 +228,7 @@ char *sha1_pack_index_name(const unsigned char *sha1) struct alternate_object_database *alt_odb_list; static struct alternate_object_database **alt_odb_tail; -static void read_info_alternates(const char * alternates, int depth); +static int git_open_noatime(const char *name); /* * Prepare alternate object database registry. @@ -237,38 +247,40 @@ static void read_info_alternates(const char * alternates, int depth); */ static int link_alt_odb_entry(const char * entry, int len, const char * relative_base, int depth) { - struct stat st; const char *objdir = get_object_directory(); struct alternate_object_database *ent; struct alternate_object_database *alt; - /* 43 = 40-byte + 2 '/' + terminating NUL */ - int pfxlen = len; - int entlen = pfxlen + 43; - int base_len = -1; + int pfxlen, entlen; + struct strbuf pathbuf = STRBUF_INIT; if (!is_absolute_path(entry) && relative_base) { - /* Relative alt-odb */ - if (base_len < 0) - base_len = strlen(relative_base) + 1; - entlen += base_len; - pfxlen += base_len; + strbuf_addstr(&pathbuf, real_path(relative_base)); + strbuf_addch(&pathbuf, '/'); } - ent = xmalloc(sizeof(*ent) + entlen); + strbuf_add(&pathbuf, entry, len); - if (!is_absolute_path(entry) && relative_base) { - memcpy(ent->base, relative_base, base_len - 1); - ent->base[base_len - 1] = '/'; - memcpy(ent->base + base_len, entry, len); - } - else - memcpy(ent->base, entry, pfxlen); + normalize_path_copy(pathbuf.buf, pathbuf.buf); + + pfxlen = strlen(pathbuf.buf); + + /* + * The trailing slash after the directory name is given by + * this function at the end. Remove duplicates. + */ + while (pfxlen && pathbuf.buf[pfxlen-1] == '/') + pfxlen -= 1; + + entlen = pfxlen + 43; /* '/' + 2 hex + '/' + 38 hex + NUL */ + ent = xmalloc(sizeof(*ent) + entlen); + memcpy(ent->base, pathbuf.buf, pfxlen); + strbuf_release(&pathbuf); ent->name = ent->base + pfxlen + 1; 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)) { + if (!is_directory(ent->base)) { error("object directory %s does not exist; " "check .git/objects/info/alternates.", ent->base); @@ -340,7 +352,7 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep, } } -static void read_info_alternates(const char * relative_base, int depth) +void read_info_alternates(const char * relative_base, int depth) { char *map; size_t mapsz; @@ -353,7 +365,7 @@ static void read_info_alternates(const char * relative_base, int depth) int fd; sprintf(path, "%s/%s", relative_base, alt_file_name); - fd = open(path, O_RDONLY); + fd = git_open_noatime(path); if (fd < 0) return; if (fstat(fd, &st) || (st.st_size == 0)) { @@ -372,8 +384,8 @@ static void read_info_alternates(const char * relative_base, int depth) void add_to_alternates_file(const char *reference) { struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), 1); - char *alt = mkpath("%s/objects\n", reference); + int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR); + char *alt = mkpath("%s\n", reference); write_or_die(fd, alt, strlen(alt)); if (commit_lock_file(lock)) die("could not close alternates file"); @@ -381,6 +393,16 @@ void add_to_alternates_file(const char *reference) link_alt_odb_entries(alt, alt + strlen(alt), '\n', NULL, 0); } +void foreach_alt_odb(alt_odb_fn fn, void *cb) +{ + struct alternate_object_database *ent; + + prepare_alt_odb(); + for (ent = alt_odb_list; ent; ent = ent->next) + if (fn(ent, cb)) + return; +} + void prepare_alt_odb(void) { const char *alt; @@ -392,32 +414,41 @@ void prepare_alt_odb(void) if (!alt) alt = ""; alt_odb_tail = &alt_odb_list; - link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL, 0); + link_alt_odb_entries(alt, alt + strlen(alt), PATH_SEP, NULL, 0); read_info_alternates(get_object_directory(), 0); } -static int has_loose_object(const unsigned char *sha1) +static int has_loose_object_local(const unsigned char *sha1) { char *name = sha1_file_name(sha1); - struct alternate_object_database *alt; + return !access(name, F_OK); +} - if (!access(name, F_OK)) - return 1; +int has_loose_object_nonlocal(const unsigned char *sha1) +{ + struct alternate_object_database *alt; prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) { - name = alt->name; - fill_sha1_path(name, sha1); + fill_sha1_path(alt->name, sha1); if (!access(alt->base, F_OK)) return 1; } return 0; } +static int has_loose_object(const unsigned char *sha1) +{ + return has_loose_object_local(sha1) || + has_loose_object_nonlocal(sha1); +} + static unsigned int pack_used_ctr; static unsigned int pack_mmap_calls; static unsigned int peak_pack_open_windows; static unsigned int pack_open_windows; +static unsigned int pack_open_fds; +static unsigned int pack_max_fds; static size_t peak_pack_mapped; static size_t pack_mapped; struct packed_git *packed_git; @@ -449,7 +480,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) struct pack_idx_header *hdr; size_t idx_size; uint32_t version, nr, i, *index; - int fd = open(path, O_RDONLY); + int fd = git_open_noatime(path); struct stat st; if (fd < 0) @@ -471,7 +502,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) version = ntohl(hdr->idx_version); if (version < 2 || version > 2) { munmap(idx_map, idx_size); - return error("index file %s is version %d" + return error("index file %s is version %"PRIu32 " and is not supported by this binary" " (try upgrading GIT to a newer version)", path, version); @@ -595,8 +626,10 @@ static int unuse_one_window(struct packed_git *current, int keep_fd) lru_l->next = lru_w->next; else { lru_p->windows = lru_w->next; - if (!lru_p->windows && lru_p->pack_fd != keep_fd) { + if (!lru_p->windows && lru_p->pack_fd != -1 + && lru_p->pack_fd != keep_fd) { close(lru_p->pack_fd); + pack_open_fds--; lru_p->pack_fd = -1; } } @@ -614,6 +647,21 @@ void release_pack_memory(size_t need, int fd) ; /* nothing */ } +void *xmmap(void *start, size_t length, + int prot, int flags, int fd, off_t offset) +{ + void *ret = mmap(start, length, prot, flags, fd, offset); + if (ret == MAP_FAILED) { + if (!length) + return NULL; + release_pack_memory(length, fd); + ret = mmap(start, length, prot, flags, fd, offset); + if (ret == MAP_FAILED) + die_errno("Out of memory? mmap failed"); + } + return ret; +} + void close_pack_windows(struct packed_git *p) { while (p->windows) { @@ -639,6 +687,49 @@ void unuse_pack(struct pack_window **w_cursor) } } +void close_pack_index(struct packed_git *p) +{ + if (p->index_data) { + munmap((void *)p->index_data, p->index_size); + p->index_data = NULL; + } +} + +/* + * This is used by git-repack in case a newly created pack happens to + * contain the same set of objects as an existing one. In that case + * the resulting file might be different even if its name would be the + * same. It is best to close any reference to the old pack before it is + * replaced on disk. Of course no index pointers nor windows for given pack + * must subsist at this point. If ever objects from this pack are requested + * again, the new version of the pack will be reinitialized through + * reprepare_packed_git(). + */ +void free_pack_by_name(const char *pack_name) +{ + struct packed_git *p, **pp = &packed_git; + + while (*pp) { + p = *pp; + if (strcmp(pack_name, p->pack_name) == 0) { + clear_delta_base_cache(); + close_pack_windows(p); + if (p->pack_fd != -1) { + close(p->pack_fd); + pack_open_fds--; + } + close_pack_index(p); + free(p->bad_object_sha1); + *pp = p->next; + if (last_found_pack == p) + last_found_pack = NULL; + free(p); + return; + } + pp = &p->next; + } +} + /* * Do not call this directly as this leaks p->pack_fd on error return; * call open_packed_git() instead. @@ -654,9 +745,29 @@ static int open_packed_git_1(struct packed_git *p) if (!p->index_data && open_pack_index(p)) return error("packfile %s index unavailable", p->pack_name); - p->pack_fd = open(p->pack_name, O_RDONLY); + if (!pack_max_fds) { + struct rlimit lim; + unsigned int max_fds; + + if (getrlimit(RLIMIT_NOFILE, &lim)) + die_errno("cannot get RLIMIT_NOFILE"); + + max_fds = lim.rlim_cur; + + /* Save 3 for stdin/stdout/stderr, 22 for work */ + if (25 < max_fds) + pack_max_fds = max_fds - 25; + else + pack_max_fds = 1; + } + + while (pack_max_fds <= pack_open_fds && unuse_one_window(NULL, -1)) + ; /* nothing */ + + p->pack_fd = git_open_noatime(p->pack_name); if (p->pack_fd < 0 || fstat(p->pack_fd, &st)) return -1; + pack_open_fds++; /* If we created the struct before we had the pack we lack size. */ if (!p->pack_size) { @@ -682,14 +793,14 @@ static int open_packed_git_1(struct packed_git *p) if (hdr.hdr_signature != htonl(PACK_SIGNATURE)) return error("file %s is not a GIT packfile", p->pack_name); if (!pack_version_ok(hdr.hdr_version)) - return error("packfile %s is version %u and not supported" - " (try upgrading GIT to a newer version)", + return error("packfile %s is version %"PRIu32" and not" + " supported (try upgrading GIT to a newer version)", p->pack_name, ntohl(hdr.hdr_version)); /* Verify the pack matches its index. */ if (p->num_objects != ntohl(hdr.hdr_entries)) - return error("packfile %s claims to have %u objects" - " while index indicates %u objects", + return error("packfile %s claims to have %"PRIu32" objects" + " while index indicates %"PRIu32" objects", p->pack_name, ntohl(hdr.hdr_entries), p->num_objects); if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1) @@ -708,6 +819,7 @@ static int open_packed_git(struct packed_git *p) return 0; if (p->pack_fd != -1) { close(p->pack_fd); + pack_open_fds--; p->pack_fd = -1; } return -1; @@ -726,21 +838,20 @@ static int in_window(struct pack_window *win, off_t offset) && (offset + 20) <= (win_off + win->len); } -unsigned char* use_pack(struct packed_git *p, +unsigned char *use_pack(struct packed_git *p, struct pack_window **w_cursor, off_t offset, - unsigned int *left) + unsigned long *left) { struct pack_window *win = *w_cursor; - if (p->pack_fd == -1 && open_packed_git(p)) - die("packfile %s cannot be accessed", p->pack_name); - - /* Since packfiles end in a hash of their content and its + /* Since packfiles end in a hash of their content and it's * pointless to ask for an offset into the middle of that * hash, and the in_window function above wouldn't match * don't allow an offset too close to the end of the file. */ + if (!p->pack_size && p->pack_fd == -1 && open_packed_git(p)) + die("packfile %s cannot be accessed", p->pack_name); if (offset > (p->pack_size - 20)) die("offset beyond end of packfile (truncated pack?)"); @@ -754,6 +865,10 @@ unsigned char* use_pack(struct packed_git *p, if (!win) { size_t window_align = packed_git_window_size / 2; off_t len; + + if (p->pack_fd == -1 && open_packed_git(p)) + die("packfile %s cannot be accessed", p->pack_name); + win = xcalloc(1, sizeof(*win)); win->offset = (offset / window_align) * window_align; len = p->pack_size - win->offset; @@ -771,6 +886,12 @@ unsigned char* use_pack(struct packed_git *p, die("packfile %s cannot be mapped: %s", p->pack_name, strerror(errno)); + if (!win->offset && win->len == p->pack_size + && !p->do_not_close) { + close(p->pack_fd); + pack_open_fds--; + p->pack_fd = -1; + } pack_mmap_calls++; pack_open_windows++; if (pack_mapped > peak_pack_mapped) @@ -792,19 +913,45 @@ unsigned char* use_pack(struct packed_git *p, return win->base + offset; } +static struct packed_git *alloc_packed_git(int extra) +{ + struct packed_git *p = xmalloc(sizeof(*p) + extra); + memset(p, 0, sizeof(*p)); + p->pack_fd = -1; + return p; +} + +static void try_to_free_pack_memory(size_t size) +{ + release_pack_memory(size, -1); +} + struct packed_git *add_packed_git(const char *path, int path_len, int local) { + static int have_set_try_to_free_routine; struct stat st; - struct packed_git *p = xmalloc(sizeof(*p) + path_len + 2); + struct packed_git *p = alloc_packed_git(path_len + 2); + + if (!have_set_try_to_free_routine) { + have_set_try_to_free_routine = 1; + set_try_to_free_routine(try_to_free_pack_memory); + } /* * Make sure a corresponding .pack file exists and that * the index looks sane. */ path_len -= strlen(".idx"); - if (path_len < 1) + if (path_len < 1) { + free(p); return NULL; + } memcpy(p->pack_name, path, path_len); + + strcpy(p->pack_name + path_len, ".keep"); + if (!access(p->pack_name, F_OK)) + p->pack_keep = 1; + strcpy(p->pack_name + path_len, ".pack"); if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode)) { free(p); @@ -814,14 +961,7 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local) /* ok, it looks sane as far as we can check without * actually mapping the pack file. */ - p->index_version = 0; - p->index_data = NULL; - p->index_size = 0; - p->num_objects = 0; p->pack_size = st.st_size; - p->next = NULL; - p->windows = NULL; - p->pack_fd = -1; p->pack_local = local; p->mtime = st.st_mtime; if (path_len < 40 || get_sha1_hex(path + path_len - 40, p->sha1)) @@ -829,28 +969,26 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local) return p; } -struct packed_git *parse_pack_index(unsigned char *sha1) +struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path) { - const char *idx_path = sha1_pack_index_name(sha1); const char *path = sha1_pack_name(sha1); - struct packed_git *p = xmalloc(sizeof(*p) + strlen(path) + 2); + struct packed_git *p = alloc_packed_git(strlen(path) + 1); + strcpy(p->pack_name, path); + hashcpy(p->sha1, sha1); if (check_packed_git_idx(idx_path, p)) { free(p); return NULL; } - strcpy(p->pack_name, path); - p->pack_size = 0; - p->next = NULL; - p->windows = NULL; - p->pack_fd = -1; - hashcpy(p->sha1, sha1); return p; } void install_packed_git(struct packed_git *pack) { + if (pack->pack_fd != -1) + pack_open_fds++; + pack->next = packed_git; packed_git = pack; } @@ -978,10 +1116,35 @@ void prepare_packed_git(void) void reprepare_packed_git(void) { + discard_revindex(); prepare_packed_git_run_once = 0; prepare_packed_git(); } +static void mark_bad_packed_object(struct packed_git *p, + const unsigned char *sha1) +{ + unsigned i; + for (i = 0; i < p->num_bad_objects; i++) + if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i)) + return; + p->bad_object_sha1 = xrealloc(p->bad_object_sha1, 20 * (p->num_bad_objects + 1)); + hashcpy(p->bad_object_sha1 + 20 * p->num_bad_objects, sha1); + p->num_bad_objects++; +} + +static const struct packed_git *has_packed_and_bad(const unsigned char *sha1) +{ + struct packed_git *p; + unsigned i; + + for (p = packed_git; p; p = p->next) + for (i = 0; i < p->num_bad_objects; i++) + if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i)) + return p; + return NULL; +} + int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type) { unsigned char real_sha1[20]; @@ -992,15 +1155,20 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz static int git_open_noatime(const char *name) { static int sha1_file_open_flag = O_NOATIME; - int fd = open(name, O_RDONLY | sha1_file_open_flag); - /* Might the failure be due to O_NOATIME? */ - if (fd < 0 && errno != ENOENT && sha1_file_open_flag) { - fd = open(name, O_RDONLY); + for (;;) { + int fd = open(name, O_RDONLY | sha1_file_open_flag); if (fd >= 0) + return fd; + + /* Might the failure be due to O_NOATIME? */ + if (errno != ENOENT && sha1_file_open_flag) { sha1_file_open_flag = 0; + continue; + } + + return -1; } - return fd; } static int open_sha1_file(const unsigned char *sha1) @@ -1025,7 +1193,7 @@ static int open_sha1_file(const unsigned char *sha1) return -1; } -static void *map_sha1_file(const unsigned char *sha1, unsigned long *size) +void *map_sha1_file(const unsigned char *sha1, unsigned long *size) { void *map; int fd; @@ -1037,6 +1205,11 @@ static void *map_sha1_file(const unsigned char *sha1, unsigned long *size) if (!fstat(fd, &st)) { *size = xsize_t(st.st_size); + if (!*size) { + /* mmap() is forbidden on empty files */ + error("object file %s is empty", sha1_file_name(sha1)); + return NULL; + } map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0); } close(fd); @@ -1044,27 +1217,56 @@ static void *map_sha1_file(const unsigned char *sha1, unsigned long *size) return map; } -static int legacy_loose_object(unsigned char *map) +/* + * There used to be a second loose object header format which + * was meant to mimic the in-pack format, allowing for direct + * copy of the object data. This format turned up not to be + * really worth it and we no longer write loose objects in that + * format. + */ +static int experimental_loose_object(unsigned char *map) { unsigned int word; /* - * Is it a zlib-compressed buffer? If so, the first byte - * must be 0x78 (15-bit window size, deflated), and the - * first 16-bit word is evenly divisible by 31 + * We must determine if the buffer contains the standard + * zlib-deflated stream or the experimental format based + * on the in-pack object format. Compare the header byte + * for each format: + * + * RFC1950 zlib w/ deflate : 0www1000 : 0 <= www <= 7 + * Experimental pack-based : Stttssss : ttt = 1,2,3,4 + * + * If bit 7 is clear and bits 0-3 equal 8, the buffer MUST be + * in standard loose-object format, UNLESS it is a Git-pack + * format object *exactly* 8 bytes in size when inflated. + * + * However, RFC1950 also specifies that the 1st 16-bit word + * must be divisible by 31 - this checksum tells us our buffer + * is in the standard format, giving a false positive only if + * the 1st word of the Git-pack format object happens to be + * divisible by 31, ie: + * ((byte0 * 256) + byte1) % 31 = 0 + * => 0ttt10000www1000 % 31 = 0 + * + * As it happens, this case can only arise for www=3 & ttt=1 + * - ie, a Commit object, which would have to be 8 bytes in + * size. As no Commit can be that small, we find that the + * combination of these two criteria (bitmask & checksum) + * can always correctly determine the buffer format. */ word = (map[0] << 8) + map[1]; - if (map[0] == 0x78 && !(word % 31)) - return 1; - else + if ((map[0] & 0x8F) == 0x08 && !(word % 31)) return 0; + else + return 1; } -unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) +unsigned long unpack_object_header_buffer(const unsigned char *buf, + unsigned long len, enum object_type *type, unsigned long *sizep) { unsigned shift; - unsigned char c; - unsigned long size; + unsigned long size, c; unsigned long used = 0; c = buf[used++]; @@ -1072,10 +1274,11 @@ unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned lon size = c & 15; shift = 4; while (c & 0x80) { - if (len <= used) - return 0; - if (sizeof(long) * 8 <= shift) - return 0; + if (len <= used || bitsizeof(long) <= shift) { + error("bad object header"); + size = used = 0; + break; + } c = buf[used++]; size += (c & 0x7f) << shift; shift += 7; @@ -1084,7 +1287,7 @@ unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned lon return used; } -static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) +int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) { unsigned long size, used; static const char valid_loose_object_type[8] = { @@ -1101,40 +1304,35 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon stream->next_out = buffer; stream->avail_out = bufsiz; - if (legacy_loose_object(map)) { - inflateInit(stream); - return inflate(stream, 0); - } - - - /* - * There used to be a second loose object header format which - * was meant to mimic the in-pack format, allowing for direct - * copy of the object data. This format turned up not to be - * really worth it and we don't write it any longer. But we - * can still read it. - */ - used = unpack_object_header_gently(map, mapsize, &type, &size); - if (!used || !valid_loose_object_type[type]) - return -1; - map += used; - mapsize -= used; + if (experimental_loose_object(map)) { + /* + * The old experimental format we no longer produce; + * we can still read it. + */ + used = unpack_object_header_buffer(map, mapsize, &type, &size); + if (!used || !valid_loose_object_type[type]) + return -1; + map += used; + mapsize -= used; - /* Set up the stream for the rest.. */ - stream->next_in = map; - stream->avail_in = mapsize; - inflateInit(stream); + /* Set up the stream for the rest.. */ + stream->next_in = map; + stream->avail_in = mapsize; + git_inflate_init(stream); - /* And generate the fake traditional header */ - stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", - typename(type), size); - return 0; + /* And generate the fake traditional header */ + stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", + typename(type), size); + return 0; + } + git_inflate_init(stream); + return git_inflate(stream, 0); } -static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1) +static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long size, const unsigned char *sha1) { int bytes = strlen(buffer) + 1; - unsigned char *buf = xmalloc(1+size); + unsigned char *buf = xmallocz(size); unsigned long n; int status = Z_OK; @@ -1147,7 +1345,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size /* * The above condition must be (bytes <= size), not * (bytes < size). In other words, even though we - * expect no more output and set avail_out to zer0, + * expect no more output and set avail_out to zero, * the input zlib stream may have bytes that express * "this concludes the stream", and we *do* want to * eat that input. @@ -1160,11 +1358,10 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size stream->next_out = buf + bytes; stream->avail_out = size - bytes; while (status == Z_OK) - status = inflate(stream, Z_FINISH); + status = git_inflate(stream, Z_FINISH); } - buf[size] = 0; if (status == Z_STREAM_END && !stream->avail_in) { - inflateEnd(stream); + git_inflate_end(stream); return buf; } @@ -1182,7 +1379,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size * too permissive for what we want to check. So do an anal * object header parse by hand. */ -static int parse_sha1_header(const char *hdr, unsigned long *sizep) +int parse_sha1_header(const char *hdr, unsigned long *sizep) { char type[10]; int i; @@ -1231,7 +1428,7 @@ static int parse_sha1_header(const char *hdr, unsigned long *sizep) static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1) { int ret; - z_stream stream; + git_zstream stream; char hdr[8192]; ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)); @@ -1247,24 +1444,26 @@ unsigned long get_size_from_delta(struct packed_git *p, { const unsigned char *data; unsigned char delta_head[20], *in; - z_stream stream; + git_zstream stream; int st; memset(&stream, 0, sizeof(stream)); stream.next_out = delta_head; stream.avail_out = sizeof(delta_head); - inflateInit(&stream); + git_inflate_init(&stream); do { in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; - st = inflate(&stream, Z_FINISH); + st = git_inflate(&stream, Z_FINISH); curpos += stream.next_in - in; } while ((st == Z_OK || st == Z_BUF_ERROR) && stream.total_out < sizeof(delta_head)); - inflateEnd(&stream); - if ((st != Z_STREAM_END) && stream.total_out != sizeof(delta_head)) - die("delta data unpack-initial failed"); + git_inflate_end(&stream); + if ((st != Z_STREAM_END) && stream.total_out != sizeof(delta_head)) { + error("delta data unpack-initial failed"); + return 0; + } /* Examine the initial part of the delta to figure out * the result size. @@ -1300,20 +1499,17 @@ static off_t get_delta_base(struct packed_git *p, while (c & 128) { base_offset += 1; if (!base_offset || MSB(base_offset, 7)) - die("offset value overflow for delta base object"); + return 0; /* overflow */ c = base_info[used++]; base_offset = (base_offset << 7) + (c & 127); } base_offset = delta_obj_offset - base_offset; - if (base_offset >= delta_obj_offset) - die("delta base offset out of bound"); + if (base_offset <= 0 || base_offset >= delta_obj_offset) + return 0; /* out of bound */ *curpos += used; } else if (type == OBJ_REF_DELTA) { /* The base entry _must_ be in the same pack */ base_offset = find_pack_entry_one(base_info, p); - if (!base_offset) - die("failed to find delta-pack base object %s", - sha1_to_hex(base_info)); *curpos += 20; } else die("I am totally screwed"); @@ -1322,7 +1518,7 @@ static off_t get_delta_base(struct packed_git *p, /* forward declaration for a mutually recursive function */ static int packed_object_info(struct packed_git *p, off_t offset, - unsigned long *sizep); + unsigned long *sizep, int *rtype); static int packed_delta_info(struct packed_git *p, struct pack_window **w_curs, @@ -1334,98 +1530,64 @@ static int packed_delta_info(struct packed_git *p, off_t base_offset; base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset); - type = packed_object_info(p, base_offset, NULL); + if (!base_offset) + return OBJ_BAD; + type = packed_object_info(p, base_offset, NULL, NULL); + if (type <= OBJ_NONE) { + struct revindex_entry *revidx; + const unsigned char *base_sha1; + revidx = find_pack_revindex(p, base_offset); + if (!revidx) + return OBJ_BAD; + base_sha1 = nth_packed_object_sha1(p, revidx->nr); + mark_bad_packed_object(p, base_sha1); + type = sha1_object_info(base_sha1, NULL); + if (type <= OBJ_NONE) + return OBJ_BAD; + } /* 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 (sizep) + if (sizep) { *sizep = get_size_from_delta(p, w_curs, curpos); + if (*sizep == 0) + type = OBJ_BAD; + } return type; } -static int unpack_object_header(struct packed_git *p, - struct pack_window **w_curs, - off_t *curpos, - unsigned long *sizep) +int unpack_object_header(struct packed_git *p, + struct pack_window **w_curs, + off_t *curpos, + unsigned long *sizep) { unsigned char *base; - unsigned int left; + unsigned long left; unsigned long used; enum object_type type; /* use_pack() assures us we have [base, base + 20) available - * as a range that we can look at at. (Its actually the hash + * as a range that we can look at. (Its actually the hash * size that is assured.) With our object header encoding * the maximum deflated object size is 2^137, which is just * insane, so we know won't exceed what we have been given. */ base = use_pack(p, w_curs, *curpos, &left); - used = unpack_object_header_gently(base, left, &type, sizep); - if (!used) - die("object offset outside of pack file"); - *curpos += used; + used = unpack_object_header_buffer(base, left, &type, sizep); + if (!used) { + type = OBJ_BAD; + } else + *curpos += used; return type; } -const char *packed_object_info_detail(struct packed_git *p, - off_t obj_offset, - unsigned long *size, - unsigned long *store_size, - unsigned int *delta_chain_length, - unsigned char *base_sha1) -{ - struct pack_window *w_curs = NULL; - off_t curpos; - unsigned long dummy; - unsigned char *next_sha1; - enum object_type type; - struct revindex_entry *revidx; - - *delta_chain_length = 0; - curpos = obj_offset; - type = unpack_object_header(p, &w_curs, &curpos, size); - - revidx = find_pack_revindex(p, obj_offset); - *store_size = revidx[1].offset - obj_offset; - - for (;;) { - switch (type) { - default: - die("pack %s contains unknown object type %d", - p->pack_name, type); - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - unuse_pack(&w_curs); - return typename(type); - case OBJ_OFS_DELTA: - obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); - if (*delta_chain_length == 0) { - revidx = find_pack_revindex(p, obj_offset); - hashcpy(base_sha1, nth_packed_object_sha1(p, revidx->nr)); - } - break; - case OBJ_REF_DELTA: - next_sha1 = use_pack(p, &w_curs, curpos, NULL); - if (*delta_chain_length == 0) - hashcpy(base_sha1, next_sha1); - obj_offset = find_pack_entry_one(next_sha1, p); - break; - } - (*delta_chain_length)++; - curpos = obj_offset; - type = unpack_object_header(p, &w_curs, &curpos, &dummy); - } -} - static int packed_object_info(struct packed_git *p, off_t obj_offset, - unsigned long *sizep) + unsigned long *sizep, int *rtype) { struct pack_window *w_curs = NULL; unsigned long size; @@ -1433,6 +1595,8 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset, enum object_type type; type = unpack_object_header(p, &w_curs, &curpos, &size); + if (rtype) + *rtype = type; /* representation type */ switch (type) { case OBJ_OFS_DELTA: @@ -1448,8 +1612,9 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset, *sizep = size; break; default: - die("pack %s contains unknown object type %d", - p->pack_name, type); + error("unknown object type %i at offset %"PRIuMAX" in %s", + type, (uintmax_t)obj_offset, p->pack_name); + type = OBJ_BAD; } unuse_pack(&w_curs); return type; @@ -1461,23 +1626,24 @@ static void *unpack_compressed_entry(struct packed_git *p, unsigned long size) { int st; - z_stream stream; + git_zstream stream; unsigned char *buffer, *in; - buffer = xmalloc(size + 1); - buffer[size] = 0; + buffer = xmallocz(size); memset(&stream, 0, sizeof(stream)); stream.next_out = buffer; - stream.avail_out = size; + stream.avail_out = size + 1; - inflateInit(&stream); + git_inflate_init(&stream); do { in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; - st = inflate(&stream, Z_FINISH); + st = git_inflate(&stream, Z_FINISH); + if (!stream.avail_out) + break; /* the payload is larger than it should be */ curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR); - inflateEnd(&stream); + git_inflate_end(&stream); if ((st != Z_STREAM_END) || stream.total_out != size) { free(buffer); return NULL; @@ -1513,6 +1679,13 @@ static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset) return hash % MAX_DELTA_CACHE; } +static int in_delta_base_cache(struct packed_git *p, off_t base_offset) +{ + unsigned long hash = pack_entry_hash(p, base_offset); + struct delta_base_cache_entry *ent = delta_base_cache + hash; + return (ent->data && ent->p == p && ent->base_offset == base_offset); +} + static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset, unsigned long *base_size, enum object_type *type, int keep_cache) { @@ -1521,11 +1694,9 @@ static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset, struct delta_base_cache_entry *ent = delta_base_cache + hash; ret = ent->data; - if (ret && ent->p == p && ent->base_offset == base_offset) - goto found_cache_entry; - return unpack_entry(p, base_offset, type, base_size); + if (!ret || ent->p != p || ent->base_offset != base_offset) + return unpack_entry(p, base_offset, type, base_size); -found_cache_entry: if (!keep_cache) { ent->data = NULL; ent->lru.next->prev = ent->lru.prev; @@ -1550,6 +1721,13 @@ static inline void release_delta_base_cache(struct delta_base_cache_entry *ent) } } +void clear_delta_base_cache(void) +{ + unsigned long p; + for (p = 0; p < MAX_DELTA_CACHE; p++) + release_delta_base_cache(&delta_base_cache[p]); +} + static void add_delta_base_cache(struct packed_git *p, off_t base_offset, void *base, unsigned long base_size, enum object_type type) { @@ -1587,6 +1765,9 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, delta_base_cache_lru.prev = &ent->lru; } +static void *read_object(const unsigned char *sha1, enum object_type *type, + unsigned long *size); + static void *unpack_delta_entry(struct packed_git *p, struct pack_window **w_curs, off_t curpos, @@ -1600,17 +1781,45 @@ static void *unpack_delta_entry(struct packed_git *p, off_t base_offset; base_offset = get_delta_base(p, w_curs, &curpos, *type, obj_offset); + if (!base_offset) { + error("failed to validate delta base reference " + "at offset %"PRIuMAX" from %s", + (uintmax_t)curpos, p->pack_name); + return NULL; + } + unuse_pack(w_curs); base = cache_or_unpack_entry(p, base_offset, &base_size, type, 0); - if (!base) - die("failed to read delta base object" - " at %"PRIuMAX" from %s", - (uintmax_t)base_offset, p->pack_name); + if (!base) { + /* + * We're probably in deep shit, but let's try to fetch + * the required base anyway from another pack or loose. + * This is costly but should happen only in the presence + * of a corrupted pack, and is better than failing outright. + */ + struct revindex_entry *revidx; + const unsigned char *base_sha1; + revidx = find_pack_revindex(p, base_offset); + if (!revidx) + return NULL; + base_sha1 = nth_packed_object_sha1(p, revidx->nr); + error("failed to read delta base object %s" + " at offset %"PRIuMAX" from %s", + sha1_to_hex(base_sha1), (uintmax_t)base_offset, + p->pack_name); + mark_bad_packed_object(p, base_sha1); + base = read_object(base_sha1, type, &base_size); + if (!base) + return NULL; + } delta_data = unpack_compressed_entry(p, w_curs, curpos, delta_size); - if (!delta_data) - die("failed to unpack compressed delta" - " at %"PRIuMAX" from %s", - (uintmax_t)curpos, p->pack_name); + if (!delta_data) { + error("failed to unpack compressed delta " + "at offset %"PRIuMAX" from %s", + (uintmax_t)curpos, p->pack_name); + free(base); + return NULL; + } result = patch_delta(base, base_size, delta_data, delta_size, sizep); @@ -1621,6 +1830,26 @@ static void *unpack_delta_entry(struct packed_git *p, return result; } +static void write_pack_access_log(struct packed_git *p, off_t obj_offset) +{ + static FILE *log_file; + + if (!log_file) { + log_file = fopen(log_pack_access, "w"); + if (!log_file) { + error("cannot open pack access log '%s' for writing: %s", + log_pack_access, strerror(errno)); + log_pack_access = NULL; + return; + } + } + fprintf(log_file, "%s %"PRIuMAX"\n", + p->pack_name, (uintmax_t)obj_offset); + fflush(log_file); +} + +int do_check_packed_object_crc; + void *unpack_entry(struct packed_git *p, off_t obj_offset, enum object_type *type, unsigned long *sizep) { @@ -1628,6 +1857,23 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, off_t curpos = obj_offset; void *data; + if (log_pack_access) + write_pack_access_log(p, obj_offset); + + if (do_check_packed_object_crc && p->index_version > 1) { + struct revindex_entry *revidx = find_pack_revindex(p, obj_offset); + unsigned long len = revidx[1].offset - obj_offset; + if (check_pack_crc(p, &w_curs, obj_offset, len, revidx->nr)) { + const unsigned char *sha1 = + nth_packed_object_sha1(p, revidx->nr); + error("bad packed object CRC for %s", + sha1_to_hex(sha1)); + mark_bad_packed_object(p, sha1); + unuse_pack(&w_curs); + return NULL; + } + } + *type = unpack_object_header(p, &w_curs, &curpos, sizep); switch (*type) { case OBJ_OFS_DELTA: @@ -1642,7 +1888,9 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, data = unpack_compressed_entry(p, &w_curs, curpos, *sizep); break; default: - die("unknown object type %i in %s", *type, p->pack_name); + data = NULL; + error("unknown object type %i at offset %"PRIuMAX" in %s", + *type, (uintmax_t)obj_offset, p->pack_name); } unuse_pack(&w_curs); return data; @@ -1668,7 +1916,7 @@ const unsigned char *nth_packed_object_sha1(struct packed_git *p, } } -static off_t nth_packed_object_offset(const struct packed_git *p, uint32_t n) +off_t nth_packed_object_offset(const struct packed_git *p, uint32_t n) { const unsigned char *index = p->index_data; index += 4 * 256; @@ -1719,7 +1967,7 @@ off_t find_pack_entry_one(const unsigned char *sha1, } if (debug_lookup) - printf("%02x%02x%02x... lo %u hi %u nr %u\n", + printf("%02x%02x%02x... lo %u hi %u nr %"PRIu32"\n", sha1[0], sha1[1], sha1[2], lo, hi, p->num_objects); if (use_lookup < 0) @@ -1749,74 +1997,79 @@ off_t find_pack_entry_one(const unsigned char *sha1, return 0; } -int matches_pack_name(struct packed_git *p, const char *name) +int is_pack_valid(struct packed_git *p) { - const char *last_c, *c; - - if (!strcmp(p->pack_name, name)) + /* An already open pack is known to be valid. */ + if (p->pack_fd != -1) return 1; - for (c = p->pack_name, last_c = c; *c;) - if (*c == '/') - last_c = ++c; - else - ++c; - if (!strcmp(last_c, name)) - return 1; + /* If the pack has one window completely covering the + * file size, the pack is known to be valid even if + * the descriptor is not currently open. + */ + if (p->windows) { + struct pack_window *w = p->windows; - return 0; + if (!w->offset && w->len == p->pack_size) + return 1; + } + + /* Force the pack to open to prove its valid. */ + return !open_packed_git(p); } -static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) +static int fill_pack_entry(const unsigned char *sha1, + struct pack_entry *e, + struct packed_git *p) { - static struct packed_git *last_found = (void *)1; - struct packed_git *p; off_t offset; + if (p->num_bad_objects) { + unsigned i; + for (i = 0; i < p->num_bad_objects; i++) + if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i)) + return 0; + } + + offset = find_pack_entry_one(sha1, p); + if (!offset) + return 0; + + /* + * We are about to tell the caller where they can locate the + * requested object. We better make sure the packfile is + * still here and can be accessed before supplying that + * answer, as it may have been deleted since the index was + * loaded! + */ + if (!is_pack_valid(p)) { + warning("packfile %s cannot be accessed", p->pack_name); + return 0; + } + e->offset = offset; + e->p = p; + hashcpy(e->sha1, sha1); + return 1; +} + +static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) +{ + struct packed_git *p; + prepare_packed_git(); if (!packed_git) return 0; - p = (last_found == (void *)1) ? packed_git : last_found; - do { - if (ignore_packed) { - const char **ig; - for (ig = ignore_packed; *ig; ig++) - if (matches_pack_name(p, *ig)) - break; - if (*ig) - goto next; - } + if (last_found_pack && fill_pack_entry(sha1, e, last_found_pack)) + return 1; - offset = find_pack_entry_one(sha1, p); - if (offset) { - /* - * We are about to tell the caller where they can - * locate the requested object. We better make - * sure the packfile is still here and can be - * accessed before supplying that answer, as - * it may have been deleted since the index - * was loaded! - */ - if (p->pack_fd == -1 && open_packed_git(p)) { - error("packfile %s cannot be accessed", p->pack_name); - goto next; - } - e->offset = offset; - e->p = p; - hashcpy(e->sha1, sha1); - last_found = p; - return 1; - } + for (p = packed_git; p; p = p->next) { + if (p == last_found_pack || !fill_pack_entry(sha1, e, p)) + continue; - next: - if (p == last_found) - p = packed_git; - else - p = p->next; - if (p == last_found) - p = p->next; - } while (p); + last_found_pack = p; + return 1; + } return 0; } @@ -1838,7 +2091,7 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size int status; unsigned long mapsize, size; void *map; - z_stream stream; + git_zstream stream; char hdr[32]; map = map_sha1_file(sha1, &mapsize); @@ -1851,69 +2104,87 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size status = error("unable to parse %s header", sha1_to_hex(sha1)); else if (sizep) *sizep = size; - inflateEnd(&stream); + git_inflate_end(&stream); munmap(map, mapsize); return status; } -int sha1_object_info(const unsigned char *sha1, unsigned long *sizep) +/* returns enum object_type or negative */ +int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi) { + struct cached_object *co; struct pack_entry e; + int status, rtype; - if (!find_pack_entry(sha1, &e, NULL)) { + co = find_cached_object(sha1); + if (co) { + if (oi->sizep) + *(oi->sizep) = co->size; + oi->whence = OI_CACHED; + return co->type; + } + + if (!find_pack_entry(sha1, &e)) { + /* Most likely it's a loose object. */ + status = sha1_loose_object_info(sha1, oi->sizep); + if (status >= 0) { + oi->whence = OI_LOOSE; + return status; + } + + /* Not a loose object; someone else may have just packed it. */ reprepare_packed_git(); - if (!find_pack_entry(sha1, &e, NULL)) - return sha1_loose_object_info(sha1, sizep); + if (!find_pack_entry(sha1, &e)) + return status; } - return packed_object_info(e.p, e.offset, sizep); -} -static void *read_packed_sha1(const unsigned char *sha1, - enum object_type *type, unsigned long *size) -{ - struct pack_entry e; + status = packed_object_info(e.p, e.offset, oi->sizep, &rtype); + if (status < 0) { + mark_bad_packed_object(e.p, sha1); + status = sha1_object_info_extended(sha1, oi); + } else if (in_delta_base_cache(e.p, e.offset)) { + oi->whence = OI_DBCACHED; + } else { + oi->whence = OI_PACKED; + oi->u.packed.offset = e.offset; + oi->u.packed.pack = e.p; + oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA || + rtype == OBJ_OFS_DELTA); + } - if (!find_pack_entry(sha1, &e, NULL)) - return NULL; - else - return cache_or_unpack_entry(e.p, e.offset, size, type, 1); + return status; } -/* - * This is meant to hold a *small* number of objects that you would - * want read_sha1_file() to be able to return, but yet you do not want - * to write them into the object store (e.g. a browse-only - * application). - */ -static struct cached_object { - unsigned char sha1[20]; - enum object_type type; - void *buf; - unsigned long size; -} *cached_objects; -static int cached_object_nr, cached_object_alloc; +int sha1_object_info(const unsigned char *sha1, unsigned long *sizep) +{ + struct object_info oi; -static struct cached_object empty_tree = { - /* empty tree sha1: 4b825dc642cb6eb9a060e54bf8d69288fbee4904 */ - "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" - "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04", - OBJ_TREE, - "", - 0 -}; + oi.sizep = sizep; + return sha1_object_info_extended(sha1, &oi); +} -static struct cached_object *find_cached_object(const unsigned char *sha1) +static void *read_packed_sha1(const unsigned char *sha1, + enum object_type *type, unsigned long *size) { - int i; - struct cached_object *co = cached_objects; + struct pack_entry e; + void *data; - for (i = 0; i < cached_object_nr; i++, co++) { - if (!hashcmp(co->sha1, sha1)) - return co; + if (!find_pack_entry(sha1, &e)) + return NULL; + data = cache_or_unpack_entry(e.p, e.offset, size, type, 1); + if (!data) { + /* + * We're probably in deep shit, but let's try to fetch + * the required object anyway from another pack or loose. + * This should happen only in the presence of a corrupted + * pack, and is better than failing outright. + */ + error("failed to read object %s at offset %"PRIuMAX" from %s", + sha1_to_hex(sha1), (uintmax_t)e.offset, e.p->pack_name); + mark_bad_packed_object(e.p, sha1); + data = read_object(sha1, type, size); } - if (!hashcmp(sha1, empty_tree.sha1)) - return &empty_tree; - return NULL; + return data; } int pretend_sha1_file(void *buf, unsigned long len, enum object_type type, @@ -1939,8 +2210,8 @@ int pretend_sha1_file(void *buf, unsigned long len, enum object_type type, return 0; } -void *read_sha1_file(const unsigned char *sha1, enum object_type *type, - unsigned long *size) +static void *read_object(const unsigned char *sha1, enum object_type *type, + unsigned long *size) { unsigned long mapsize; void *map, *buf; @@ -1966,6 +2237,48 @@ void *read_sha1_file(const unsigned char *sha1, enum object_type *type, return read_packed_sha1(sha1, type, size); } +/* + * This function dies on corrupt objects; the callers who want to + * deal with them should arrange to call read_object() and give error + * messages themselves. + */ +void *read_sha1_file_extended(const unsigned char *sha1, + enum object_type *type, + unsigned long *size, + unsigned flag) +{ + void *data; + char *path; + const struct packed_git *p; + const unsigned char *repl = (flag & READ_SHA1_FILE_REPLACE) + ? lookup_replace_object(sha1) : sha1; + + errno = 0; + data = read_object(repl, type, size); + if (data) + return data; + + if (errno && errno != ENOENT) + die_errno("failed to read object %s", sha1_to_hex(sha1)); + + /* die if we replaced an object with one that does not exist */ + if (repl != sha1) + die("replacement %s not found for %s", + sha1_to_hex(repl), sha1_to_hex(sha1)); + + if (has_loose_object(repl)) { + path = sha1_file_name(sha1); + die("loose object %s (stored in %s) is corrupt", + sha1_to_hex(repl), path); + } + + if ((p = has_packed_and_bad(repl)) != NULL) + die("packed object %s (stored in %s) is corrupt", + sha1_to_hex(repl), p->pack_name); + + return NULL; +} + void *read_object_with_reference(const unsigned char *sha1, const char *required_type_name, unsigned long *size, @@ -2018,24 +2331,32 @@ static void write_sha1_file_prepare(const void *buf, unsigned long len, const char *type, unsigned char *sha1, char *hdr, int *hdrlen) { - SHA_CTX c; + git_SHA_CTX c; /* Generate the header */ *hdrlen = sprintf(hdr, "%s %lu", type, len)+1; /* Sha1.. */ - SHA1_Init(&c); - SHA1_Update(&c, hdr, *hdrlen); - SHA1_Update(&c, buf, len); - SHA1_Final(sha1, &c); + git_SHA1_Init(&c); + git_SHA1_Update(&c, hdr, *hdrlen); + git_SHA1_Update(&c, buf, len); + git_SHA1_Final(sha1, &c); } /* - * Move the just written object into its final resting place + * Move the just written object into its final resting place. + * NEEDSWORK: this should be renamed to finalize_temp_file() as + * "moving" is only a part of what it does, when no patch between + * master to pu changes the call sites of this function. */ int move_temp_to_file(const char *tmpfile, const char *filename) { - int ret = link(tmpfile, filename); + int ret = 0; + + if (object_creation_mode == OBJECT_CREATION_USES_RENAMES) + goto try_rename; + else if (link(tmpfile, filename)) + ret = errno; /* * Coda hack - coda doesn't like cross-directory links, @@ -2045,15 +2366,16 @@ int move_temp_to_file(const char *tmpfile, const char *filename) * * The same holds for FAT formatted media. * - * When this succeeds, we just return 0. We have nothing + * When this succeeds, we just return. We have nothing * left to unlink. */ if (ret && ret != EEXIST) { + try_rename: if (!rename(tmpfile, filename)) - return 0; + goto out; ret = errno; } - unlink(tmpfile); + unlink_or_warn(tmpfile); if (ret) { if (ret != EEXIST) { return error("unable to write sha1 filename %s: %s\n", filename, strerror(ret)); @@ -2061,6 +2383,9 @@ int move_temp_to_file(const char *tmpfile, const char *filename) /* FIXME!!! Collision check here ? */ } +out: + if (adjust_shared_perm(filename)) + return error("unable to set permission to '%s'", filename); return 0; } @@ -2083,10 +2408,10 @@ int hash_sha1_file(const void *buf, unsigned long len, const char *type, /* Finalize a file on disk, and close it. */ static void close_sha1_file(int fd) { - /* For safe-mode, we could fsync_or_die(fd, "sha1 file") here */ - fchmod(fd, 0444); + if (fsync_object_files) + fsync_or_die(fd, "sha1 file"); if (close(fd) != 0) - die("unable to write sha1 file"); + die_errno("error when closing sha1 file"); } /* Size of directory component, including the ending '/' */ @@ -2115,85 +2440,92 @@ static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename) } memcpy(buffer, filename, dirlen); strcpy(buffer + dirlen, "tmp_obj_XXXXXX"); - fd = mkstemp(buffer); - if (fd < 0 && dirlen) { + fd = git_mkstemp_mode(buffer, 0444); + if (fd < 0 && dirlen && errno == ENOENT) { /* Make sure the directory exists */ + memcpy(buffer, filename, dirlen); buffer[dirlen-1] = 0; if (mkdir(buffer, 0777) || adjust_shared_perm(buffer)) return -1; /* Try again */ strcpy(buffer + dirlen - 1, "/tmp_obj_XXXXXX"); - fd = mkstemp(buffer); + fd = git_mkstemp_mode(buffer, 0444); } return fd; } static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, - void *buf, unsigned long len, time_t mtime) + const void *buf, unsigned long len, time_t mtime) { - int fd, size, ret; - unsigned char *compressed; - z_stream stream; + int fd, ret; + unsigned char compressed[4096]; + git_zstream stream; + git_SHA_CTX c; + unsigned char parano_sha1[20]; char *filename; - static char tmpfile[PATH_MAX]; + static char tmp_file[PATH_MAX]; filename = sha1_file_name(sha1); - fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename); + fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename); if (fd < 0) { - if (errno == EPERM) + if (errno == EACCES) return error("insufficient permission for adding an object to repository database %s\n", get_object_directory()); else - return error("unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno)); + return error("unable to create temporary sha1 filename %s: %s\n", tmp_file, strerror(errno)); } /* Set it up */ memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); - size = 8 + deflateBound(&stream, len+hdrlen); - compressed = xmalloc(size); - - /* Compress it */ + git_deflate_init(&stream, zlib_compression_level); stream.next_out = compressed; - stream.avail_out = size; + stream.avail_out = sizeof(compressed); + git_SHA1_Init(&c); /* First header.. */ stream.next_in = (unsigned char *)hdr; stream.avail_in = hdrlen; - while (deflate(&stream, 0) == Z_OK) - /* nothing */; + while (git_deflate(&stream, 0) == Z_OK) + ; /* nothing */ + git_SHA1_Update(&c, hdr, hdrlen); /* Then the data itself.. */ - stream.next_in = buf; + stream.next_in = (void *)buf; stream.avail_in = len; - ret = deflate(&stream, Z_FINISH); + do { + unsigned char *in0 = stream.next_in; + ret = git_deflate(&stream, Z_FINISH); + git_SHA1_Update(&c, in0, stream.next_in - in0); + if (write_buffer(fd, compressed, stream.next_out - compressed) < 0) + die("unable to write sha1 file"); + stream.next_out = compressed; + stream.avail_out = sizeof(compressed); + } while (ret == Z_OK); + if (ret != Z_STREAM_END) die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret); - - ret = deflateEnd(&stream); + ret = git_deflate_end_gently(&stream); if (ret != Z_OK) die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret); + git_SHA1_Final(parano_sha1, &c); + if (hashcmp(sha1, parano_sha1) != 0) + die("confused by unstable object source data for %s", sha1_to_hex(sha1)); - size = stream.total_out; - - if (write_buffer(fd, compressed, size) < 0) - die("unable to write sha1 file"); close_sha1_file(fd); - free(compressed); if (mtime) { struct utimbuf utb; utb.actime = mtime; utb.modtime = mtime; - if (utime(tmpfile, &utb) < 0) + if (utime(tmp_file, &utb) < 0) warning("failed utime() on %s: %s", - tmpfile, strerror(errno)); + tmp_file, strerror(errno)); } - return move_temp_to_file(tmpfile, filename); + return move_temp_to_file(tmp_file, filename); } -int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) +int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *returnsha1) { unsigned char sha1[20]; char hdr[32]; @@ -2217,6 +2549,7 @@ int force_object_loose(const unsigned char *sha1, time_t mtime) enum object_type type; char hdr[32]; int hdrlen; + int ret; if (has_loose_object(sha1)) return 0; @@ -2224,7 +2557,10 @@ int force_object_loose(const unsigned char *sha1, time_t mtime) if (!buf) return error("cannot read sha1_file for %s", sha1_to_hex(sha1)); hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1; - return write_loose_object(sha1, hdr, hdrlen, buf, len, mtime); + ret = write_loose_object(sha1, hdr, hdrlen, buf, len, mtime); + free(buf); + + return ret; } int has_pack_index(const unsigned char *sha1) @@ -2235,61 +2571,55 @@ int has_pack_index(const unsigned char *sha1) 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, const char **ignore_packed) +int has_sha1_pack(const unsigned char *sha1) { struct pack_entry e; - return find_pack_entry(sha1, &e, ignore_packed); + return find_pack_entry(sha1, &e); } int has_sha1_file(const unsigned char *sha1) { struct pack_entry e; - if (find_pack_entry(sha1, &e, NULL)) + if (find_pack_entry(sha1, &e)) return 1; return has_loose_object(sha1); } -int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) +static void check_tree(const void *buf, size_t size) { - struct strbuf buf; - int ret; + struct tree_desc desc; + struct name_entry entry; - strbuf_init(&buf, 0); - if (strbuf_read(&buf, fd, 4096) < 0) { - strbuf_release(&buf); - return -1; - } + init_tree_desc(&desc, buf, size); + while (tree_entry(&desc, &entry)) + /* do nothing + * tree_entry() will die() on malformed entries */ + ; +} - if (!type) - type = blob_type; - if (write_object) - ret = write_sha1_file(buf.buf, buf.len, type, sha1); - else - ret = hash_sha1_file(buf.buf, buf.len, type, sha1); - strbuf_release(&buf); +static void check_commit(const void *buf, size_t size) +{ + struct commit c; + memset(&c, 0, sizeof(c)); + if (parse_commit_buffer(&c, buf, size)) + die("corrupt commit"); +} - return ret; +static void check_tag(const void *buf, size_t size) +{ + struct tag t; + memset(&t, 0, sizeof(t)); + if (parse_tag_buffer(&t, buf, size)) + die("corrupt tag"); } -int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, - enum object_type type, const char *path) +static int index_mem(unsigned char *sha1, void *buf, size_t size, + enum object_type type, + const char *path, unsigned flags) { - size_t size = xsize_t(st->st_size); - void *buf = NULL; int ret, re_allocated = 0; - - if (size) - buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); + int write_object = flags & HASH_WRITE_OBJECT; if (!type) type = OBJ_BLOB; @@ -2297,35 +2627,114 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, /* * Convert blobs to git internal format */ - if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) { - struct strbuf nbuf; - strbuf_init(&nbuf, 0); + if ((type == OBJ_BLOB) && path) { + struct strbuf nbuf = STRBUF_INIT; if (convert_to_git(path, buf, size, &nbuf, - write_object ? safe_crlf : 0)) { - munmap(buf, size); + write_object ? safe_crlf : SAFE_CRLF_FALSE)) { buf = strbuf_detach(&nbuf, &size); re_allocated = 1; } } + if (flags & HASH_FORMAT_CHECK) { + if (type == OBJ_TREE) + check_tree(buf, size); + if (type == OBJ_COMMIT) + check_commit(buf, size); + if (type == OBJ_TAG) + check_tag(buf, size); + } if (write_object) ret = write_sha1_file(buf, size, typename(type), sha1); else ret = hash_sha1_file(buf, size, typename(type), sha1); - if (re_allocated) { + if (re_allocated) free(buf); - return ret; - } - if (size) + return ret; +} + +static int index_pipe(unsigned char *sha1, int fd, enum object_type type, + const char *path, unsigned flags) +{ + struct strbuf sbuf = STRBUF_INIT; + int ret; + + if (strbuf_read(&sbuf, fd, 4096) >= 0) + ret = index_mem(sha1, sbuf.buf, sbuf.len, type, path, flags); + else + ret = -1; + strbuf_release(&sbuf); + return ret; +} + +#define SMALL_FILE_SIZE (32*1024) + +static int index_core(unsigned char *sha1, int fd, size_t size, + enum object_type type, const char *path, + unsigned flags) +{ + int ret; + + if (!size) { + ret = index_mem(sha1, NULL, size, type, path, flags); + } else if (size <= SMALL_FILE_SIZE) { + char *buf = xmalloc(size); + if (size == read_in_full(fd, buf, size)) + ret = index_mem(sha1, buf, size, type, path, flags); + else + ret = error("short read %s", strerror(errno)); + free(buf); + } else { + void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + ret = index_mem(sha1, buf, size, type, path, flags); munmap(buf, size); + } + return ret; +} + +/* + * This creates one packfile per large blob unless bulk-checkin + * machinery is "plugged". + * + * This also bypasses the usual "convert-to-git" dance, and that is on + * purpose. We could write a streaming version of the converting + * functions and insert that before feeding the data to fast-import + * (or equivalent in-core API described above). However, that is + * somewhat complicated, as we do not know the size of the filter + * result, which we need to know beforehand when writing a git object. + * Since the primary motivation for trying to stream from the working + * tree file and to avoid mmaping it in core is to deal with large + * binary blobs, they generally do not want to get any conversion, and + * callers should avoid this code path when filters are requested. + */ +static int index_stream(unsigned char *sha1, int fd, size_t size, + enum object_type type, const char *path, + unsigned flags) +{ + return index_bulk_checkin(sha1, fd, size, type, path, flags); +} + +int index_fd(unsigned char *sha1, int fd, struct stat *st, + enum object_type type, const char *path, unsigned flags) +{ + int ret; + size_t size = xsize_t(st->st_size); + + if (!S_ISREG(st->st_mode)) + ret = index_pipe(sha1, fd, type, path, flags); + else if (size <= big_file_threshold || type != OBJ_BLOB || + (path && would_convert_to_git(path, NULL, 0, 0))) + ret = index_core(sha1, fd, size, type, path, flags); + else + ret = index_stream(sha1, fd, size, type, path, flags); + close(fd); return ret; } -int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object) +int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags) { int fd; - char *target; - size_t len; + struct strbuf sb = STRBUF_INIT; switch (st->st_mode & S_IFMT) { case S_IFREG: @@ -2333,25 +2742,22 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write if (fd < 0) return error("open(\"%s\"): %s", path, strerror(errno)); - if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path) < 0) + if (index_fd(sha1, fd, st, OBJ_BLOB, path, flags) < 0) return error("%s: failed to insert into database", path); break; case S_IFLNK: - len = xsize_t(st->st_size); - target = xmalloc(len + 1); - if (readlink(path, target, len + 1) != st->st_size) { + if (strbuf_readlink(&sb, path, st->st_size)) { char *errstr = strerror(errno); - free(target); return error("readlink(\"%s\"): %s", path, errstr); } - if (!write_object) - hash_sha1_file(target, len, blob_type, sha1); - else if (write_sha1_file(target, len, blob_type, sha1)) + if (!(flags & HASH_WRITE_OBJECT)) + hash_sha1_file(sb.buf, sb.len, blob_type, sha1); + else if (write_sha1_file(sb.buf, sb.len, blob_type, sha1)) return error("%s: failed to insert into database", path); - free(target); + strbuf_release(&sb); break; case S_IFDIR: return resolve_gitlink_ref(path, "HEAD", sha1); @@ -2375,3 +2781,13 @@ int read_pack_header(int fd, struct pack_header *header) return PH_ERROR_PROTOCOL; return 0; } + +void assert_sha1_type(const unsigned char *sha1, enum object_type expect) +{ + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("%s is not a valid object", sha1_to_hex(sha1)); + if (type != expect) + die("%s is not a valid '%s' object", sha1_to_hex(sha1), + typename(expect)); +} |