Skip to content

Commit f6371f9

Browse files
peffgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 118e6ce commit f6371f9

File tree

2 files changed

+143
-3
lines changed

2 files changed

+143
-3
lines changed

cache.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,19 @@ extern int finalize_object_file(const char *tmpfile, const char *filename);
11391139

11401140
extern int has_sha1_pack(const unsigned char *sha1);
11411141

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+
11421155
/*
11431156
* Return true iff we have an object named sha1, whether local or in
11441157
* an alternate object database, and whether packed or loose. This

sha1_file.c

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,13 +1670,21 @@ static int open_sha1_file(const unsigned char *sha1, const char **path)
16701670
return -1;
16711671
}
16721672

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)
16741680
{
1675-
const char *path;
16761681
void *map;
16771682
int fd;
16781683

1679-
fd = open_sha1_file(sha1, &path);
1684+
if (path)
1685+
fd = git_open(path);
1686+
else
1687+
fd = open_sha1_file(sha1, &path);
16801688
map = NULL;
16811689
if (fd >= 0) {
16821690
struct stat st;
@@ -1695,6 +1703,11 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
16951703
return map;
16961704
}
16971705

1706+
void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
1707+
{
1708+
return map_sha1_file_1(NULL, sha1, size);
1709+
}
1710+
16981711
unsigned long unpack_object_header_buffer(const unsigned char *buf,
16991712
unsigned long len, enum object_type *type, unsigned long *sizep)
17001713
{
@@ -3792,3 +3805,117 @@ int for_each_packed_object(each_packed_object_fn cb, void *data, unsigned flags)
37923805
}
37933806
return r ? r : pack_errors;
37943807
}
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+
}

0 commit comments

Comments
 (0)