X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=sha1_file.c;h=942b673dc3c7fa9f057c5c452e3a1b73eaeb8707;hb=06cd3b94b27c285fc9877e7c6d9e1f35fbc0a7a4;hp=7b7b65d2eb3757ceb83f35a60be07e8d705dc0fb;hpb=f4913f91a960ebcfbc9c6a09dc909ce638cb5c66;p=git.git diff --git a/sha1_file.c b/sha1_file.c index 7b7b65d2..942b673d 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -7,9 +7,18 @@ * creation etc. */ #include +#include #include "cache.h" -const char *sha1_file_directory = NULL; +#ifndef O_NOATIME +#if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) +#define O_NOATIME 01000000 +#else +#define O_NOATIME 0 +#endif +#endif + +static unsigned int sha1_file_open_flag = O_NOATIME; static unsigned hexval(char c) { @@ -35,6 +44,81 @@ int get_sha1_hex(const char *hex, unsigned char *sha1) return 0; } +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; +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_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_index_file(void) +{ + if (!git_index_file) + setup_git_env(); + return git_index_file; +} + +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; + } + + return -1; +} + char * sha1_to_hex(const unsigned char *sha1) { static char buffer[50]; @@ -50,36 +134,130 @@ char * sha1_to_hex(const unsigned char *sha1) return buffer; } +static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) +{ + int i; + for (i = 0; i < 20; i++) { + static char hex[] = "0123456789abcdef"; + unsigned int val = sha1[i]; + char *pos = pathbuf + i*2 + (i > 0); + *pos++ = hex[val >> 4]; + *pos = hex[val & 0xf]; + } +} + /* * NOTE! This returns a statically allocated buffer, so you have to be * careful about using it. Do a "strdup()" if you need to save the * filename. + * + * Also note that this returns the location for creating. Reading + * SHA1 file can happen from any alternate directory listed in the + * DB_ENVIRONMENT environment variable if it is not found in + * the primary object database. */ char *sha1_file_name(const unsigned char *sha1) { - int i; static char *name, *base; if (!base) { - char *sha1_file_directory = getenv(DB_ENVIRONMENT) ? : DEFAULT_DB_ENVIRONMENT; + const char *sha1_file_directory = get_object_directory(); int len = strlen(sha1_file_directory); - base = malloc(len + 60); + base = xmalloc(len + 60); memcpy(base, sha1_file_directory, len); memset(base+len, 0, 60); base[len] = '/'; base[len+3] = '/'; name = base + len + 1; } - for (i = 0; i < 20; i++) { - static char hex[] = "0123456789abcdef"; - unsigned int val = sha1[i]; - char *pos = name + i*2 + (i > 0); - *pos++ = hex[val >> 4]; - *pos = hex[val & 0xf]; - } + fill_sha1_path(name, sha1); return base; } +static struct alternate_object_database { + char *base; + char *name; +} *alt_odb; + +/* + * 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. + */ +static void prepare_alt_odb(void) +{ + int pass, totlen, i; + const char *cp, *last; + char *op = 0; + 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 == ':') + 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 = 0; + op = (char*)(&alt_odb[i+1]); + } +} + +static char *find_sha1_file(const unsigned char *sha1, struct stat *st) +{ + int i; + char *name = sha1_file_name(sha1); + + if (!stat(name, st)) + return name; + if (!alt_odb) + prepare_alt_odb(); + for (i = 0; (name = alt_odb[i].name) != NULL; i++) { + fill_sha1_path(name, sha1); + if (!stat(alt_odb[i].base, st)) + return alt_odb[i].base; + } + return NULL; +} + int check_sha1_signature(unsigned char *sha1, void *map, unsigned long size, const char *type) { char header[100]; @@ -95,18 +273,32 @@ int check_sha1_signature(unsigned char *sha1, void *map, unsigned long size, con void *map_sha1_file(const unsigned char *sha1, unsigned long *size) { - char *filename = sha1_file_name(sha1); - int fd = open(filename, O_RDONLY); struct stat st; void *map; + int fd; + char *filename = find_sha1_file(sha1, &st); - if (fd < 0) { - perror(filename); + if (!filename) { + error("cannot map sha1 file %s", sha1_to_hex(sha1)); return NULL; } - if (fstat(fd, &st) < 0) { - close(fd); - return NULL; + + fd = open(filename, O_RDONLY | sha1_file_open_flag); + if (fd < 0) { + /* See if it works without O_NOATIME */ + switch (sha1_file_open_flag) { + default: + fd = open(filename, O_RDONLY); + if (fd >= 0) + break; + /* Fallthrough */ + case 0: + perror(filename); + return NULL; + } + + /* If it failed once, it will probably fail again. Stop using O_NOATIME */ + sha1_file_open_flag = 0; } map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); @@ -132,13 +324,13 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l inflateInit(&stream); ret = inflate(&stream, 0); + if (ret < Z_OK) + return NULL; if (sscanf(buffer, "%10s %lu", type, size) != 2) return NULL; bytes = strlen(buffer) + 1; - buf = malloc(*size); - if (!buf) - return NULL; + buf = xmalloc(*size); memcpy(buf, buffer + bytes, stream.total_out - bytes); bytes = stream.total_out - bytes; @@ -166,47 +358,52 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size return NULL; } -void *read_tree_with_tree_or_commit_sha1(const unsigned char *sha1, - unsigned long *size, - unsigned char *tree_sha1_return) +void *read_object_with_reference(const unsigned char *sha1, + const unsigned char *required_type, + unsigned long *size, + unsigned char *actual_sha1_return) { char type[20]; void *buffer; unsigned long isize; - int was_commit = 0; - char tree_sha1[20]; - - buffer = read_sha1_file(sha1, type, &isize); - - /* - * We might have read a commit instead of a tree, in which case - * we parse out the tree_sha1 and attempt to read from there. - * (buffer + 5) is because the tree sha1 is always at offset 5 - * in a commit record ("tree "). - */ - if (buffer && - !strcmp(type, "commit") && - !get_sha1_hex(buffer + 5, tree_sha1)) { - free(buffer); - buffer = read_sha1_file(tree_sha1, type, &isize); - was_commit = 1; - } - - /* - * Now do we have something and if so is it a tree? - */ - if (!buffer || strcmp(type, "tree")) { - free(buffer); - return; + unsigned char actual_sha1[20]; + + memcpy(actual_sha1, sha1, 20); + while (1) { + int ref_length = -1; + const char *ref_type = NULL; + + buffer = read_sha1_file(actual_sha1, type, &isize); + if (!buffer) + return NULL; + if (!strcmp(type, required_type)) { + *size = isize; + if (actual_sha1_return) + memcpy(actual_sha1_return, actual_sha1, 20); + return buffer; + } + /* Handle references */ + else if (!strcmp(type, "commit")) + ref_type = "tree "; + else if (!strcmp(type, "tag")) + ref_type = "object "; + else { + free(buffer); + return NULL; + } + ref_length = strlen(ref_type); + + if (memcmp(buffer, ref_type, ref_length) || + get_sha1_hex(buffer + ref_length, actual_sha1)) { + free(buffer); + return NULL; + } + /* Now we have the ID of the referred-to object in + * actual_sha1. Check again. */ } - - *size = isize; - if (tree_sha1_return) - memcpy(tree_sha1_return, was_commit ? tree_sha1 : sha1, 20); - return buffer; } -int write_sha1_file(char *buf, unsigned len, unsigned char *returnsha1) +int write_sha1_file(char *buf, unsigned long len, const char *type, unsigned char *returnsha1) { int size; char *compressed; @@ -214,10 +411,16 @@ int write_sha1_file(char *buf, unsigned len, unsigned char *returnsha1) unsigned char sha1[20]; SHA_CTX c; char *filename; - int fd; + static char tmpfile[PATH_MAX]; + char hdr[50]; + int fd, hdrlen, ret; + + /* 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); @@ -225,29 +428,48 @@ int write_sha1_file(char *buf, unsigned len, unsigned char *returnsha1) memcpy(returnsha1, sha1, 20); filename = sha1_file_name(sha1); - fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (fd < 0) { - if (errno != EEXIST) - return -1; - + fd = open(filename, O_RDONLY); + if (fd >= 0) { /* - * We might do collision checking here, but we'd need to - * uncompress the old file and check it. Later. + * FIXME!!! We might do collision checking here, but we'd + * need to uncompress the old file and check it. Later. */ + close(fd); return 0; } + if (errno != ENOENT) { + fprintf(stderr, "sha1 file %s: %s", filename, strerror(errno)); + return -1; + } + + snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory()); + + fd = mkstemp(tmpfile); + if (fd < 0) { + fprintf(stderr, "unable to create temporary sha1 filename %s: %s", tmpfile, strerror(errno)); + return -1; + } + /* Set it up */ memset(&stream, 0, sizeof(stream)); deflateInit(&stream, Z_BEST_COMPRESSION); - size = deflateBound(&stream, len); - compressed = malloc(size); + size = deflateBound(&stream, len+hdrlen); + compressed = xmalloc(size); /* Compress it */ - stream.next_in = buf; - stream.avail_in = len; stream.next_out = compressed; stream.avail_out = size; + + /* First header.. */ + stream.next_in = hdr; + stream.avail_in = hdrlen; + while (deflate(&stream, 0) == Z_OK) + /* nothing */; + + /* Then the data itself.. */ + stream.next_in = buf; + stream.avail_in = len; while (deflate(&stream, Z_FINISH) == Z_OK) /* nothing */; deflateEnd(&stream); @@ -255,53 +477,121 @@ int write_sha1_file(char *buf, unsigned len, unsigned char *returnsha1) if (write(fd, compressed, size) != size) die("unable to write file"); + fchmod(fd, 0444); close(fd); - + free(compressed); + + ret = link(tmpfile, filename); + if (ret < 0) { + ret = errno; + + /* + * Coda hack - coda doesn't like cross-directory links, + * so we fall back to a rename, which will mean that it + * won't be able to check collisions, but that's not a + * big deal. + * + * When this succeeds, we just return 0. We have nothing + * left to unlink. + */ + if (ret == EXDEV && !rename(tmpfile, filename)) + return 0; + } + unlink(tmpfile); + if (ret) { + if (ret != EEXIST) { + fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret)); + return -1; + } + /* FIXME!!! Collision check here ? */ + } + return 0; } -static inline int collision_check(char *filename, void *buf, unsigned int size) +int write_sha1_from_fd(const unsigned char *sha1, int fd) { -#ifdef COLLISION_CHECK - void *map; - int fd = open(filename, O_RDONLY); - struct stat st; - int cmp; + char *filename = sha1_file_name(sha1); - /* Unreadable object, or object went away? Strange. */ - if (fd < 0) - return -1; + int local; + z_stream stream; + unsigned char real_sha1[20]; + char buf[4096]; + char discard[4096]; + int ret; + SHA_CTX c; - if (fstat(fd, &st) < 0 || size != st.st_size) - return -1; + local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666); - map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); - if (map == MAP_FAILED) - return -1; - cmp = memcmp(buf, map, size); - munmap(map, size); - if (cmp) - return -1; -#endif + if (local < 0) + return error("Couldn't open %s\n", filename); + + memset(&stream, 0, sizeof(stream)); + + inflateInit(&stream); + + SHA1_Init(&c); + + do { + ssize_t size; + size = read(fd, buf, 4096); + if (size <= 0) { + close(local); + unlink(filename); + 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); + inflateEnd(&stream); + + close(local); + SHA1_Final(real_sha1, &c); + if (ret != Z_STREAM_END) { + unlink(filename); + return error("File %s corrupted", sha1_to_hex(sha1)); + } + if (memcmp(sha1, real_sha1, 20)) { + unlink(filename); + return error("File %s has bad hash\n", sha1_to_hex(sha1)); + } + return 0; } -int write_sha1_buffer(const unsigned char *sha1, void *buf, unsigned int size) +int has_sha1_file(const unsigned char *sha1) { - char *filename = sha1_file_name(sha1); - int fd; + struct stat st; + return !!find_sha1_file(sha1, &st); +} - fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (fd < 0) { - if (errno != EEXIST) - return -1; - if (collision_check(filename, buf, size)) - return error("SHA1 collision detected!" - " This is bad, bad, BAD!\a\n"); - return 0; - } - write(fd, buf, size); +int index_fd(unsigned char *sha1, int fd, struct stat *st) +{ + unsigned long size = st->st_size; + void *buf; + int ret; + + buf = ""; + if (size) + buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - return 0; + if ((int)(long)buf == -1) + return -1; + + ret = write_sha1_file(buf, size, "blob", sha1); + if (size) + munmap(buf, size); + return ret; }