sha1_file: add read_loose_object() function

It's surprisingly hard to ask the sha1_file code to open a _specific_ incarnation of a loose object. Most of the functions take a sha1, and loop over the various object types (packed versus loose) and locations (local versus alternates) at a low level. However, some tools like fsck need to look at a specific file. This patch gives them a function they can use to open the loose object at a given path. The implementation unfortunately ends up repeating bits of related functions, but there's not a good way around it without some major refactoring of the whole sha1_file stack. We need to mmap the specific file, then partially read the zlib stream to know whether we're streaming or not, and then finally either stream it or copy the data to a buffer. We can do that by assembling some of the more arcane internal sha1_file functions, but we end up having to essentially reimplement unpack_sha1_file(), along with the streaming bits of check_sha1_signature(). Still, most of the ugliness is contained in the new function, and the interface is clean enough that it may be reusable (though it seems unlikely anything but git-fsck would care about opening a specific file). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Jan 13, 2017 at 12:58 UTC f6371f9210418f1beabc85b097e2a3470aeeb54d
2 files changed +143 -3
cache.h
+13
@@ -1139,6 +1139,19 @@ extern int finalize_object_file(const char *tmpfile, const char *filename);
1139
1140 extern int has_sha1_pack(const unsigned char *sha1);
1141
1142 +/*
1143 + * Open the loose object at path, check its sha1, and return the contents,
1144 + * type, and size. If the object is a blob, then "contents" may return NULL,
1145 + * to allow streaming of large blobs.
1146 + *
1147 + * Returns 0 on success, negative on error (details may be written to stderr).
1148 + */
1149 +int read_loose_object(const char *path,
1150 + const unsigned char *expected_sha1,
1151 + enum object_type *type,
1152 + unsigned long *size,
1153 + void **contents);
1154 +
1155 /*
1156 * Return true iff we have an object named sha1, whether local or in
1157 * an alternate object database, and whether packed or loose. This
sha1_file.c
+130 -3
@@ -1670,13 +1670,21 @@ static int open_sha1_file(const unsigned char *sha1, const char **path)
1670 return -1;
1671 }
1672
1673 -void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
1673 +/*
1674 + * Map the loose object at "path" if it is not NULL, or the path found by
1675 + * searching for a loose object named "sha1".
1676 + */
1677 +static void *map_sha1_file_1(const char *path,
1678 + const unsigned char *sha1,
1679 + unsigned long *size)
1680 {
1675 - const char *path;
1681 void *map;
1682 int fd;
1683
1679 - fd = open_sha1_file(sha1, &path);
1684 + if (path)
1685 + fd = git_open(path);
1686 + else
1687 + fd = open_sha1_file(sha1, &path);
1688 map = NULL;
1689 if (fd >= 0) {
1690 struct stat st;
@@ -1695,6 +1703,11 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
1703 return map;
1704 }
1705
1706 +void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
1707 +{
1708 + return map_sha1_file_1(NULL, sha1, size);
1709 +}
1710 +
1711 unsigned long unpack_object_header_buffer(const unsigned char *buf,
1712 unsigned long len, enum object_type *type, unsigned long *sizep)
1713 {
@@ -3792,3 +3805,117 @@ int for_each_packed_object(each_packed_object_fn cb, void *data, unsigned flags)
3805 }
3806 return r ? r : pack_errors;
3807 }
3808 +
3809 +static int check_stream_sha1(git_zstream *stream,
3810 + const char *hdr,
3811 + unsigned long size,
3812 + const char *path,
3813 + const unsigned char *expected_sha1)
3814 +{
3815 + git_SHA_CTX c;
3816 + unsigned char real_sha1[GIT_SHA1_RAWSZ];
3817 + unsigned char buf[4096];
3818 + unsigned long total_read;
3819 + int status = Z_OK;
3820 +
3821 + git_SHA1_Init(&c);
3822 + git_SHA1_Update(&c, hdr, stream->total_out);
3823 +
3824 + /*
3825 + * We already read some bytes into hdr, but the ones up to the NUL
3826 + * do not count against the object's content size.
3827 + */
3828 + total_read = stream->total_out - strlen(hdr) - 1;
3829 +
3830 + /*
3831 + * This size comparison must be "<=" to read the final zlib packets;
3832 + * see the comment in unpack_sha1_rest for details.
3833 + */
3834 + while (total_read <= size &&
3835 + (status == Z_OK || status == Z_BUF_ERROR)) {
3836 + stream->next_out = buf;
3837 + stream->avail_out = sizeof(buf);
3838 + if (size - total_read < stream->avail_out)
3839 + stream->avail_out = size - total_read;
3840 + status = git_inflate(stream, Z_FINISH);
3841 + git_SHA1_Update(&c, buf, stream->next_out - buf);
3842 + total_read += stream->next_out - buf;
3843 + }
3844 + git_inflate_end(stream);
3845 +
3846 + if (status != Z_STREAM_END) {
3847 + error("corrupt loose object '%s'", sha1_to_hex(expected_sha1));
3848 + return -1;
3849 + }
3850 +
3851 + git_SHA1_Final(real_sha1, &c);
3852 + if (hashcmp(expected_sha1, real_sha1)) {
3853 + error("sha1 mismatch for %s (expected %s)", path,
3854 + sha1_to_hex(expected_sha1));
3855 + return -1;
3856 + }
3857 +
3858 + return 0;
3859 +}
3860 +
3861 +int read_loose_object(const char *path,
3862 + const unsigned char *expected_sha1,
3863 + enum object_type *type,
3864 + unsigned long *size,
3865 + void **contents)
3866 +{
3867 + int ret = -1;
3868 + int fd = -1;
3869 + void *map = NULL;
3870 + unsigned long mapsize;
3871 + git_zstream stream;
3872 + char hdr[32];
3873 +
3874 + *contents = NULL;
3875 +
3876 + map = map_sha1_file_1(path, NULL, &mapsize);
3877 + if (!map) {
3878 + error_errno("unable to mmap %s", path);
3879 + goto out;
3880 + }
3881 +
3882 + if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) {
3883 + error("unable to unpack header of %s", path);
3884 + goto out;
3885 + }
3886 +
3887 + *type = parse_sha1_header(hdr, size);
3888 + if (*type < 0) {
3889 + error("unable to parse header of %s", path);
3890 + git_inflate_end(&stream);
3891 + goto out;
3892 + }
3893 +
3894 + if (*type == OBJ_BLOB) {
3895 + if (check_stream_sha1(&stream, hdr, *size, path, expected_sha1) < 0)
3896 + goto out;
3897 + } else {
3898 + *contents = unpack_sha1_rest(&stream, hdr, *size, expected_sha1);
3899 + if (!*contents) {
3900 + error("unable to unpack contents of %s", path);
3901 + git_inflate_end(&stream);
3902 + goto out;
3903 + }
3904 + if (check_sha1_signature(expected_sha1, *contents,
3905 + *size, typename(*type))) {
3906 + error("sha1 mismatch for %s (expected %s)", path,
3907 + sha1_to_hex(expected_sha1));
3908 + free(*contents);
3909 + goto out;
3910 + }
3911 + }
3912 +
3913 + ret = 0; /* everything checks out */
3914 +
3915 +out:
3916 + if (map)
3917 + munmap(map, mapsize);
3918 + if (fd >= 0)
3919 + close(fd);
3920 + return ret;
3921 +}