pack-objects: rework check_delta_limit usage
[gitweb.git] / index-pack.c
index 866a0540560c085cd9feadb277b5b5e1e7891310..7aad261d485f209f7459362dbe5a7cf5f6a9cf42 100644 (file)
@@ -6,17 +6,16 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
-#include <sys/time.h>
-#include <signal.h>
 
 static const char index_pack_usage[] =
-"git-index-pack [-v] [-o <index-file>] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
+"git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
 
 struct object_entry
 {
-       unsigned long offset;
+       off_t offset;
        unsigned long size;
        unsigned int hdr_size;
+       uint32_t crc32;
        enum object_type type;
        enum object_type real_type;
        unsigned char sha1[20];
@@ -24,7 +23,7 @@ struct object_entry
 
 union delta_base {
        unsigned char sha1[20];
-       unsigned long offset;
+       off_t offset;
 };
 
 /*
@@ -85,18 +84,20 @@ static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc)
 
 /* We always read in 4kB chunks. */
 static unsigned char input_buffer[4096];
-static unsigned long input_offset, input_len, consumed_bytes;
+static unsigned int input_offset, input_len;
+static off_t consumed_bytes;
 static SHA_CTX input_ctx;
-static int input_fd, output_fd, mmap_fd;
+static uint32_t input_crc32;
+static int input_fd, output_fd, pack_fd;
 
 /* Discard current buffer used content. */
-static void flush()
+static void flush(void)
 {
        if (input_offset) {
                if (output_fd >= 0)
                        write_or_die(output_fd, input_buffer, input_offset);
                SHA1_Update(&input_ctx, input_buffer, input_offset);
-               memcpy(input_buffer, input_buffer + input_offset, input_len);
+               memmove(input_buffer, input_buffer + input_offset, input_len);
                input_offset = 0;
        }
 }
@@ -129,8 +130,13 @@ static void use(int bytes)
 {
        if (bytes > input_len)
                die("used more bytes than were available");
+       input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
        input_len -= bytes;
        input_offset += bytes;
+
+       /* make sure off_t is sufficiently large not to wrap */
+       if (consumed_bytes > consumed_bytes + bytes)
+               die("pack too large for current definition of off_t");
        consumed_bytes += bytes;
 }
 
@@ -141,21 +147,21 @@ static const char *open_pack_file(const char *pack_name)
                if (!pack_name) {
                        static char tmpfile[PATH_MAX];
                        snprintf(tmpfile, sizeof(tmpfile),
-                                "%s/pack_XXXXXX", get_object_directory());
+                                "%s/tmp_pack_XXXXXX", get_object_directory());
                        output_fd = mkstemp(tmpfile);
                        pack_name = xstrdup(tmpfile);
                } else
                        output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
                if (output_fd < 0)
                        die("unable to create %s: %s\n", pack_name, strerror(errno));
-               mmap_fd = output_fd;
+               pack_fd = output_fd;
        } else {
                input_fd = open(pack_name, O_RDONLY);
                if (input_fd < 0)
                        die("cannot open packfile '%s': %s",
                            pack_name, strerror(errno));
                output_fd = -1;
-               mmap_fd = input_fd;
+               pack_fd = input_fd;
        }
        SHA1_Init(&input_ctx);
        return pack_name;
@@ -218,10 +224,13 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size)
 static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
 {
        unsigned char *p, c;
-       unsigned long size, base_offset;
+       unsigned long size;
+       off_t base_offset;
        unsigned shift;
+       void *data;
 
        obj->offset = consumed_bytes;
+       input_crc32 = crc32(0, Z_NULL, 0);
 
        p = fill(1);
        c = *p;
@@ -251,7 +260,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
                base_offset = c & 127;
                while (c & 128) {
                        base_offset += 1;
-                       if (!base_offset || base_offset & ~(~0UL >> 7))
+                       if (!base_offset || MSB(base_offset, 7))
                                bad_object(obj->offset, "offset value overflow for delta base object");
                        p = fill(1);
                        c = *p;
@@ -268,38 +277,44 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        case OBJ_TAG:
                break;
        default:
-               bad_object(obj->offset, "bad object type %d", obj->type);
+               bad_object(obj->offset, "unknown object type %d", obj->type);
        }
        obj->hdr_size = consumed_bytes - obj->offset;
 
-       return unpack_entry_data(obj->offset, obj->size);
+       data = unpack_entry_data(obj->offset, obj->size);
+       obj->crc32 = input_crc32;
+       return data;
 }
 
 static void *get_data_from_pack(struct object_entry *obj)
 {
        unsigned long from = obj[0].offset + obj[0].hdr_size;
        unsigned long len = obj[1].offset - from;
-       unsigned pg_offset = from % getpagesize();
-       unsigned char *map, *data;
+       unsigned long rdy = 0;
+       unsigned char *src, *data;
        z_stream stream;
        int st;
 
-       map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE,
-                  mmap_fd, from - pg_offset);
-       if (map == MAP_FAILED)
-               die("cannot mmap pack file: %s", strerror(errno));
+       src = xmalloc(len);
+       data = src;
+       do {
+               ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy);
+               if (n <= 0)
+                       die("cannot pread pack file: %s", strerror(errno));
+               rdy += n;
+       } while (rdy < len);
        data = xmalloc(obj->size);
        memset(&stream, 0, sizeof(stream));
        stream.next_out = data;
        stream.avail_out = obj->size;
-       stream.next_in = map + pg_offset;
+       stream.next_in = src;
        stream.avail_in = len;
        inflateInit(&stream);
        while ((st = inflate(&stream, Z_FINISH)) == Z_OK);
        inflateEnd(&stream);
        if (st != Z_STREAM_END || stream.total_out != obj->size)
                die("serious inflate inconsistency");
-       munmap(map, len + pg_offset);
+       free(src);
        return data;
 }
 
@@ -345,26 +360,19 @@ static int find_delta_children(const union delta_base *base,
 static void sha1_object(const void *data, unsigned long size,
                        enum object_type type, unsigned char *sha1)
 {
-       SHA_CTX ctx;
-       char header[50];
-       int header_size;
-       const char *type_str;
-
-       switch (type) {
-       case OBJ_COMMIT: type_str = commit_type; break;
-       case OBJ_TREE:   type_str = tree_type; break;
-       case OBJ_BLOB:   type_str = blob_type; break;
-       case OBJ_TAG:    type_str = tag_type; break;
-       default:
-               die("bad type %d", type);
+       hash_sha1_file(data, size, typename(type), sha1);
+       if (has_sha1_file(sha1)) {
+               void *has_data;
+               enum object_type has_type;
+               unsigned long has_size;
+               has_data = read_sha1_file(sha1, &has_type, &has_size);
+               if (!has_data)
+                       die("cannot read existing object %s", sha1_to_hex(sha1));
+               if (size != has_size || type != has_type ||
+                   memcmp(data, has_data, size) != 0)
+                       die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+               free(has_data);
        }
-
-       header_size = sprintf(header, "%s %lu", type_str, size) + 1;
-
-       SHA1_Init(&ctx);
-       SHA1_Update(&ctx, header, header_size);
-       SHA1_Update(&ctx, data, size);
-       SHA1_Final(sha1, &ctx);
 }
 
 static void resolve_delta(struct object_entry *delta_obj, void *base_data,
@@ -461,7 +469,8 @@ static void parse_pack_objects(unsigned char *sha1)
        /* If input_fd is a file, we should have reached its end now. */
        if (fstat(input_fd, &st))
                die("cannot fstat packfile: %s", strerror(errno));
-       if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes)
+       if (S_ISREG(st.st_mode) &&
+                       lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
                die("pack has junk at the end");
 
        if (!nr_deltas)
@@ -519,7 +528,7 @@ static void parse_pack_objects(unsigned char *sha1)
                fputc('\n', stderr);
 }
 
-static int write_compressed(int fd, void *in, unsigned int size)
+static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_crc)
 {
        z_stream stream;
        unsigned long maxsize;
@@ -540,11 +549,12 @@ static int write_compressed(int fd, void *in, unsigned int size)
 
        size = stream.total_out;
        write_or_die(fd, out, size);
+       *obj_crc = crc32(*obj_crc, out, size);
        free(out);
        return size;
 }
 
-static void append_obj_to_pack(void *buf,
+static void append_obj_to_pack(const unsigned char *sha1, void *buf,
                               unsigned long size, enum object_type type)
 {
        struct object_entry *obj = &objects[nr_objects++];
@@ -560,9 +570,11 @@ static void append_obj_to_pack(void *buf,
        }
        header[n++] = c;
        write_or_die(output_fd, header, n);
+       obj[0].crc32 = crc32(0, Z_NULL, 0);
+       obj[0].crc32 = crc32(obj[0].crc32, header, n);
        obj[1].offset = obj[0].offset + n;
-       obj[1].offset += write_compressed(output_fd, buf, size);
-       sha1_object(buf, size, type, obj->sha1);
+       obj[1].offset += write_compressed(output_fd, buf, size, &obj[0].crc32);
+       hashcpy(obj->sha1, sha1);
 }
 
 static int delta_pos_compare(const void *_a, const void *_b)
@@ -599,30 +611,25 @@ static void fix_unresolved_deltas(int nr_unresolved)
                struct delta_entry *d = sorted_by_pos[i];
                void *data;
                unsigned long size;
-               char type[10];
-               enum object_type obj_type;
+               enum object_type type;
                int j, first, last;
 
                if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
                        continue;
-               data = read_sha1_file(d->base.sha1, type, &size);
+               data = read_sha1_file(d->base.sha1, &type, &size);
                if (!data)
                        continue;
-               if      (!strcmp(type, blob_type))   obj_type = OBJ_BLOB;
-               else if (!strcmp(type, tree_type))   obj_type = OBJ_TREE;
-               else if (!strcmp(type, commit_type)) obj_type = OBJ_COMMIT;
-               else if (!strcmp(type, tag_type))    obj_type = OBJ_TAG;
-               else die("base object %s is of type '%s'",
-                        sha1_to_hex(d->base.sha1), type);
 
                find_delta_children(&d->base, &first, &last);
                for (j = first; j <= last; j++) {
                        struct object_entry *child = objects + deltas[j].obj_no;
                        if (child->real_type == OBJ_REF_DELTA)
-                               resolve_delta(child, data, size, obj_type);
+                               resolve_delta(child, data, size, type);
                }
 
-               append_obj_to_pack(data, size, obj_type);
+               if (check_sha1_signature(d->base.sha1, data, size, typename(type)))
+                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+               append_obj_to_pack(d->base.sha1, data, size, type);
                free(data);
                if (verbose)
                        percent = display_progress(nr_resolved_deltas,
@@ -642,7 +649,7 @@ static void readjust_pack_header_and_sha1(unsigned char *sha1)
        /* Rewrite pack header with updated object number */
        if (lseek(output_fd, 0, SEEK_SET) != 0)
                die("cannot seek back: %s", strerror(errno));
-       if (xread(output_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+       if (read_in_full(output_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
                die("cannot read pack header back: %s", strerror(errno));
        hdr.hdr_entries = htonl(nr_objects);
        if (lseek(output_fd, 0, SEEK_SET) != 0)
@@ -664,6 +671,9 @@ static void readjust_pack_header_and_sha1(unsigned char *sha1)
        write_or_die(output_fd, sha1, 20);
 }
 
+static uint32_t index_default_version = 1;
+static uint32_t index_off32_limit = 0x7fffffff;
+
 static int sha1_compare(const void *_a, const void *_b)
 {
        struct object_entry *a = *(struct object_entry **)_a;
@@ -679,9 +689,10 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
 {
        struct sha1file *f;
        struct object_entry **sorted_by_sha, **list, **last;
-       unsigned int array[256];
+       uint32_t array[256];
        int i, fd;
        SHA_CTX ctx;
+       uint32_t index_version;
 
        if (nr_objects) {
                sorted_by_sha =
@@ -692,7 +703,6 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
                        sorted_by_sha[i] = &objects[i];
                qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
                      sha1_compare);
-
        }
        else
                sorted_by_sha = list = last = NULL;
@@ -700,7 +710,7 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
        if (!index_name) {
                static char tmpfile[PATH_MAX];
                snprintf(tmpfile, sizeof(tmpfile),
-                        "%s/index_XXXXXX", get_object_directory());
+                        "%s/tmp_idx_XXXXXX", get_object_directory());
                fd = mkstemp(tmpfile);
                index_name = xstrdup(tmpfile);
        } else {
@@ -711,6 +721,17 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
                die("unable to create %s: %s", index_name, strerror(errno));
        f = sha1fd(fd, index_name);
 
+       /* if last object's offset is >= 2^31 we should use index V2 */
+       index_version = (objects[nr_objects-1].offset >> 31) ? 2 : index_default_version;
+
+       /* index versions 2 and above need a header */
+       if (index_version >= 2) {
+               struct pack_idx_header hdr;
+               hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
+               hdr.idx_version = htonl(index_version);
+               sha1write(f, &hdr, sizeof(hdr));
+       }
+
        /*
         * Write the first-level table (the list is sorted,
         * but we use a 256-entry lookup to be able to avoid
@@ -727,24 +748,61 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
                array[i] = htonl(next - sorted_by_sha);
                list = next;
        }
-       sha1write(f, array, 256 * sizeof(int));
+       sha1write(f, array, 256 * 4);
 
-       /* recompute the SHA1 hash of sorted object names.
-        * currently pack-objects does not do this, but that
-        * can be fixed.
-        */
+       /* compute the SHA1 hash of sorted object names. */
        SHA1_Init(&ctx);
+
        /*
         * Write the actual SHA1 entries..
         */
        list = sorted_by_sha;
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = *list++;
-               unsigned int offset = htonl(obj->offset);
-               sha1write(f, &offset, 4);
+               if (index_version < 2) {
+                       uint32_t offset = htonl(obj->offset);
+                       sha1write(f, &offset, 4);
+               }
                sha1write(f, obj->sha1, 20);
                SHA1_Update(&ctx, obj->sha1, 20);
        }
+
+       if (index_version >= 2) {
+               unsigned int nr_large_offset = 0;
+
+               /* write the crc32 table */
+               list = sorted_by_sha;
+               for (i = 0; i < nr_objects; i++) {
+                       struct object_entry *obj = *list++;
+                       uint32_t crc32_val = htonl(obj->crc32);
+                       sha1write(f, &crc32_val, 4);
+               }
+
+               /* write the 32-bit offset table */
+               list = sorted_by_sha;
+               for (i = 0; i < nr_objects; i++) {
+                       struct object_entry *obj = *list++;
+                       uint32_t offset = (obj->offset <= index_off32_limit) ?
+                               obj->offset : (0x80000000 | nr_large_offset++);
+                       offset = htonl(offset);
+                       sha1write(f, &offset, 4);
+               }
+
+               /* write the large offset table */
+               list = sorted_by_sha;
+               while (nr_large_offset) {
+                       struct object_entry *obj = *list++;
+                       uint64_t offset = obj->offset;
+                       if (offset > index_off32_limit) {
+                               uint32_t split[2];
+                               split[0]        = htonl(offset >> 32);
+                               split[1] = htonl(offset & 0xffffffff);
+                               sha1write(f, split, 8);
+                               nr_large_offset--;
+                       }
+               }
+       }
+
        sha1write(f, sha1, 20);
        sha1close(f, NULL, 1);
        free(sorted_by_sha);
@@ -754,8 +812,10 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
 
 static void final(const char *final_pack_name, const char *curr_pack_name,
                  const char *final_index_name, const char *curr_index_name,
+                 const char *keep_name, const char *keep_msg,
                  unsigned char *sha1)
 {
+       const char *report = "pack";
        char name[PATH_MAX];
        int err;
 
@@ -766,17 +826,26 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                if (err)
                        die("error while closing pack file: %s", strerror(errno));
                chmod(curr_pack_name, 0444);
+       }
 
-               /*
-                * Let's just mimic git-unpack-objects here and write
-                * the last part of the buffer to stdout.
-                */
-               while (input_len) {
-                       err = xwrite(1, input_buffer + input_offset, input_len);
-                       if (err <= 0)
-                               break;
-                       input_len -= err;
-                       input_offset += err;
+       if (keep_msg) {
+               int keep_fd, keep_msg_len = strlen(keep_msg);
+               if (!keep_name) {
+                       snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
+                                get_object_directory(), sha1_to_hex(sha1));
+                       keep_name = name;
+               }
+               keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+               if (keep_fd < 0) {
+                       if (errno != EEXIST)
+                               die("cannot write keep file");
+               } else {
+                       if (keep_msg_len > 0) {
+                               write_or_die(keep_fd, keep_msg, keep_msg_len);
+                               write_or_die(keep_fd, "\n", 1);
+                       }
+                       close(keep_fd);
+                       report = "keep";
                }
        }
 
@@ -800,6 +869,27 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                if (move_temp_to_file(curr_index_name, final_index_name))
                        die("cannot store index file");
        }
+
+       if (!from_stdin) {
+               printf("%s\n", sha1_to_hex(sha1));
+       } else {
+               char buf[48];
+               int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
+                                  report, sha1_to_hex(sha1));
+               write_or_die(1, buf, len);
+
+               /*
+                * Let's just mimic git-unpack-objects here and write
+                * the last part of the input buffer to stdout.
+                */
+               while (input_len) {
+                       err = xwrite(1, input_buffer + input_offset, input_len);
+                       if (err <= 0)
+                               break;
+                       input_len -= err;
+                       input_offset += err;
+               }
+       }
 }
 
 int main(int argc, char **argv)
@@ -807,7 +897,8 @@ int main(int argc, char **argv)
        int i, fix_thin_pack = 0;
        const char *curr_pack, *pack_name = NULL;
        const char *curr_index, *index_name = NULL;
-       char *index_name_buf = NULL;
+       const char *keep_name = NULL, *keep_msg = NULL;
+       char *index_name_buf = NULL, *keep_name_buf = NULL;
        unsigned char sha1[20];
 
        for (i = 1; i < argc; i++) {
@@ -818,12 +909,38 @@ int main(int argc, char **argv)
                                from_stdin = 1;
                        } else if (!strcmp(arg, "--fix-thin")) {
                                fix_thin_pack = 1;
+                       } else if (!strcmp(arg, "--keep")) {
+                               keep_msg = "";
+                       } else if (!prefixcmp(arg, "--keep=")) {
+                               keep_msg = arg + 7;
+                       } else if (!prefixcmp(arg, "--pack_header=")) {
+                               struct pack_header *hdr;
+                               char *c;
+
+                               hdr = (struct pack_header *)input_buffer;
+                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
+                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+                               if (*c != ',')
+                                       die("bad %s", arg);
+                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+                               if (*c)
+                                       die("bad %s", arg);
+                               input_len = sizeof(*hdr);
                        } else if (!strcmp(arg, "-v")) {
                                verbose = 1;
                        } else if (!strcmp(arg, "-o")) {
                                if (index_name || (i+1) >= argc)
                                        usage(index_pack_usage);
                                index_name = argv[++i];
+                       } else if (!prefixcmp(arg, "--index-version=")) {
+                               char *c;
+                               index_default_version = strtoul(arg + 16, &c, 10);
+                               if (index_default_version > 2)
+                                       die("bad %s", arg);
+                               if (*c == ',')
+                                       index_off32_limit = strtoul(c+1, &c, 0);
+                               if (*c || index_off32_limit & 0x80000000)
+                                       die("bad %s", arg);
                        } else
                                usage(index_pack_usage);
                        continue;
@@ -848,6 +965,16 @@ int main(int argc, char **argv)
                strcpy(index_name_buf + len - 5, ".idx");
                index_name = index_name_buf;
        }
+       if (keep_msg && !keep_name && pack_name) {
+               int len = strlen(pack_name);
+               if (!has_extension(pack_name, ".pack"))
+                       die("packfile name '%s' does not end with '.pack'",
+                           pack_name);
+               keep_name_buf = xmalloc(len);
+               memcpy(keep_name_buf, pack_name, len - 5);
+               strcpy(keep_name_buf + len - 5, ".keep");
+               keep_name = keep_name_buf;
+       }
 
        curr_pack = open_pack_file(pack_name);
        parse_pack_header();
@@ -880,12 +1007,13 @@ int main(int argc, char **argv)
        }
        free(deltas);
        curr_index = write_index_file(index_name, sha1);
-       final(pack_name, curr_pack, index_name, curr_index, sha1);
+       final(pack_name, curr_pack,
+               index_name, curr_index,
+               keep_name, keep_msg,
+               sha1);
        free(objects);
        free(index_name_buf);
-
-       if (!from_stdin)
-               printf("%s\n", sha1_to_hex(sha1));
+       free(keep_name_buf);
 
        return 0;
 }