Many callers of read_in_full() expect to see exactly "len" bytes, and die if that isn't the case. Since there are two interesting error cases:
1. We saw a read() error.
2. We saw EOF with fewer bytes than expected.
we would ideally distinguish between them when reporting to the user. Let's add a helper to make this easier. We can convert cases that currently lump the two conditions together (improving their error messages) and ones that already distinguish (shortening their code).
There are a number of read_in_full() calls left that we can't (or shouldn't) convert, for one or more of these reasons:
- Some are not trying to read an exact number in the first
place. So they only care about case 1, and are fine.
- Some call error() instead of die(). We could possibly
accommodate these, at the expense of making our helper a
bit more clunky.
- Some print nothing and return a failing code up the
stack, in which case somebody eventually looks at errno.
Handling these with a helper would be hard. Ideally there
would be some errno value that we could set for short
read, but there's no standard one (ENODATA is kind-of
accurate, but it's not really "no data"; it's "not enough
data", and we wouldn't want to get confused with a real
ENODATA error).
Signed-off-by: Jeff King <peff@peff.net>
---
builtin/get-tar-commit-id.c | 3 +--
bulk-checkin.c | 4 +---
cache.h | 7 +++++++
combine-diff.c | 8 +-------
csum-file.c | 6 +-----
pack-write.c | 3 +--
wrapper.c | 11 +++++++++++
7 files changed, 23 insertions(+), 19 deletions(-)
Show changes to 7 files +23 −19
builtin/get-tar-commit-id.c, bulk-checkin.c, cache.h, combine-diff.c, csum-file.c, pack-write.c, wrapper.c
diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c
index 259ad40339..0b97196afc 100644
--- a/builtin/get-tar-commit-id.c
+++ b/builtin/get-tar-commit-id.c
@@ -24,8 +24,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
if (argc != 1)
usage(builtin_get_tar_commit_id_usage);
- if (read_in_full(0, buffer, HEADERSIZE) != HEADERSIZE)
- die_errno("git get-tar-commit-id: read error");
+ xread_in_full(0, buffer, HEADERSIZE, "stdin");
if (header->typeflag[0] != 'g')
return 1;
if (!skip_prefix(content, "52 comment=", &comment))
diff --git a/bulk-checkin.c b/bulk-checkin.c
index 9a1f6c49ab..a9743785fb 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -115,9 +115,7 @@ static int stream_to_pack(struct bulk_checkin_state *state,
if (size && !s.avail_in) {
ssize_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
- if (read_in_full(fd, ibuf, rsize) != rsize)
- die("failed to read %d bytes from '%s'",
- (int)rsize, path);
+ xread_in_full(fd, ibuf, rsize, path);
offset += rsize;
if (*already_hashed_to < offset) {
size_t hsize = offset - *already_hashed_to;
diff --git a/cache.h b/cache.h
index 49b083ee0a..deec532228 100644
--- a/cache.h
+++ b/cache.h
@@ -1757,6 +1757,13 @@ extern ssize_t read_in_full(int fd, void *buf, size_t count);
extern ssize_t write_in_full(int fd, const void *buf, size_t count);
extern ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset);
+/*
+ * Like read_in_full(), but die on errors or a failure to read exactly "count"
+ * bytes. The "desc" string will be inserted into the error message as "reading
+ * %s".
+ */
+extern void xread_in_full(int fd, void *buf, size_t count, const char *desc);
+
static inline ssize_t write_str_in_full(int fd, const char *str)
{
return write_in_full(fd, str, strlen(str));
diff --git a/combine-diff.c b/combine-diff.c
index 9e163d5ada..4bdf797a10 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -1027,7 +1027,6 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
free_filespec(df);
} else if (0 <= (fd = open(elem->path, O_RDONLY))) {
size_t len = xsize_t(st.st_size);
- ssize_t done;
int is_file, i;
elem->mode = canon_mode(st.st_mode);
@@ -1042,12 +1041,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
result_size = len;
result = xmallocz(len);
-
- done = read_in_full(fd, result, len);
- if (done < 0)
- die_errno("read error '%s'", elem->path);
- else if (done < len)
- die("early EOF '%s'", elem->path);
+ xread_in_full(fd, result, len, elem->path);
/* If not a fake symlink, apply filters, e.g. autocrlf */
if (is_file) {
diff --git a/csum-file.c b/csum-file.c
index a172199e44..8a9a6075a4 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -15,12 +15,8 @@ static void flush(struct sha1file *f, const void *buf, unsigned int count)
{
if (0 <= f->check_fd && count) {
unsigned char check_buffer[8192];
- ssize_t ret = read_in_full(f->check_fd, check_buffer, count);
- if (ret < 0)
- die_errno("%s: sha1 file read error", f->name);
- if (ret < count)
- die("%s: sha1 file truncated", f->name);
+ xread_in_full(f->check_fd, check_buffer, count, f->name);
if (memcmp(buf, check_buffer, count))
die("sha1 file '%s' validation error", f->name);
}
diff --git a/pack-write.c b/pack-write.c
index a333ec6754..05a7defdd2 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -219,8 +219,7 @@ void fixup_pack_header_footer(int pack_fd,
if (lseek(pack_fd, 0, SEEK_SET) != 0)
die_errno("Failed seeking to start of '%s'", pack_name);
- if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
- die_errno("Unable to reread header of '%s'", pack_name);
+ xread_in_full(pack_fd, &hdr, sizeof(hdr), pack_name);
if (lseek(pack_fd, 0, SEEK_SET) != 0)
die_errno("Failed seeking to start of '%s'", pack_name);
git_SHA1_Update(&old_sha1_ctx, &hdr, sizeof(hdr));
diff --git a/wrapper.c b/wrapper.c
index f55debc92d..66ba4dbff3 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -329,6 +329,17 @@ ssize_t read_in_full(int fd, void *buf, size_t count)
return total;
}
+void xread_in_full(int fd, void *buf, size_t count, const char *desc)
+{
+ ssize_t ret = read_in_full(fd, buf, count);
+
+ if (ret < 0)
+ die_errno("error reading %s", desc);
+ if (ret != count)
+ die("too few bytes while reading %s (expected %"PRIuMAX", got %"PRIuMAX")",
+ desc, (uintmax_t)count, (uintmax_t)ret);
+}
+
ssize_t write_in_full(int fd, const void *buf, size_t count)
{
const char *p = buf;