From 986311c2da873ea24ee05e59e97c60f5584e4226 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 4 Nov 2020 20:25:06 +0100 Subject: [PATCH] fileio: teach read_full_file_full() to read from offset/with maximum size --- src/basic/fileio.c | 56 +++++++++++++++++------- src/basic/fileio.h | 12 ++--- src/core/execute.c | 2 +- src/journal-remote/journal-gatewayd.c | 14 ++++-- src/journal-remote/journal-remote-main.c | 14 ++++-- src/network/netdev/macsec.c | 2 +- src/network/netdev/wireguard.c | 2 +- src/nspawn/nspawn.c | 5 ++- src/partition/repart.c | 2 +- src/shared/json.c | 2 +- src/test/test-fileio.c | 49 ++++++++++++++++++++- src/veritysetup/veritysetup.c | 6 ++- 12 files changed, 130 insertions(+), 36 deletions(-) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 973756c891..f4708bc05f 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -472,12 +472,13 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re int read_full_stream_full( FILE *f, const char *filename, + uint64_t offset, + size_t size, ReadFullFileFlags flags, char **ret_contents, size_t *ret_size) { _cleanup_free_ char *buf = NULL; - struct stat st; size_t n, n_next, l; int fd, r; @@ -485,32 +486,45 @@ int read_full_stream_full( assert(ret_contents); assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)); - n_next = LINE_MAX; /* Start size */ + if (offset != UINT64_MAX && offset > LONG_MAX) + return -ERANGE; + + n_next = size != SIZE_MAX ? size : LINE_MAX; /* Start size */ fd = fileno(f); - if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen()), let's - * optimize our buffering */ + if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see + * fmemopen()), let's optimize our buffering */ + struct stat st; if (fstat(fd, &st) < 0) return -errno; if (S_ISREG(st.st_mode)) { + if (size == SIZE_MAX) { + uint64_t rsize = + LESS_BY((uint64_t) st.st_size, offset == UINT64_MAX ? 0 : offset); - /* Safety check */ - if (st.st_size > READ_FULL_BYTES_MAX) - return -E2BIG; + /* Safety check */ + if (rsize > READ_FULL_BYTES_MAX) + return -E2BIG; - /* Start with the right file size. Note that we increase the size - * to read here by one, so that the first read attempt already - * makes us notice the EOF. */ - if (st.st_size > 0) - n_next = st.st_size + 1; + /* Start with the right file size. Note that we increase the size to read + * here by one, so that the first read attempt already makes us notice the + * EOF. If the reported size of the file is zero, we avoid this logic + * however, since quite likely it might be a virtual file in procfs that all + * report a zero file size. */ + if (st.st_size > 0) + n_next = rsize + 1; + } if (flags & READ_FULL_FILE_WARN_WORLD_READABLE) (void) warn_file_is_world_accessible(filename, &st, NULL, 0); } } + if (offset != UINT64_MAX && fseek(f, offset, SEEK_SET) < 0) + return -errno; + n = l = 0; for (;;) { char *t; @@ -547,6 +561,11 @@ int read_full_stream_full( if (feof(f)) break; + if (size != SIZE_MAX) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */ + assert(l == size); + break; + } + assert(k > 0); /* we can't have read zero bytes because that would have been EOF */ /* Safety check */ @@ -605,15 +624,18 @@ finalize: int read_full_file_full( int dir_fd, const char *filename, + uint64_t offset, + size_t size, ReadFullFileFlags flags, const char *bind_name, - char **contents, size_t *size) { + char **ret_contents, + size_t *ret_size) { _cleanup_fclose_ FILE *f = NULL; int r; assert(filename); - assert(contents); + assert(ret_contents); r = xfopenat(dir_fd, filename, "re", 0, &f); if (r < 0) { @@ -628,6 +650,10 @@ int read_full_file_full( if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET)) return -ENXIO; + /* Seeking is not supported on AF_UNIX sockets */ + if (offset != UINT64_MAX) + return -ESPIPE; + if (dir_fd == AT_FDCWD) r = sockaddr_un_set_path(&sa.un, filename); else { @@ -681,7 +707,7 @@ int read_full_file_full( (void) __fsetlocking(f, FSETLOCKING_BYCALLER); - return read_full_stream_full(f, filename, flags, contents, size); + return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size); } int executable_is_script(const char *path, char **interpreter) { diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 0886354cbc..498e880354 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -60,14 +60,14 @@ static inline int write_string_file(const char *fn, const char *line, WriteStrin int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4); int read_one_line_file(const char *filename, char **line); -int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, const char *bind_name, char **contents, size_t *size); -static inline int read_full_file(const char *filename, char **contents, size_t *size) { - return read_full_file_full(AT_FDCWD, filename, 0, NULL, contents, size); +int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size); +static inline int read_full_file(const char *filename, char **ret_contents, size_t *ret_size) { + return read_full_file_full(AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); } int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size); -int read_full_stream_full(FILE *f, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size); -static inline int read_full_stream(FILE *f, char **contents, size_t *size) { - return read_full_stream_full(f, NULL, 0, contents, size); +int read_full_stream_full(FILE *f, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, char **ret_contents, size_t *ret_size); +static inline int read_full_stream(FILE *f, char **ret_contents, size_t *ret_size) { + return read_full_stream_full(f, NULL, UINT64_MAX, SIZE_MAX, 0, ret_contents, ret_size); } int verify_file(const char *fn, const char *blob, bool accept_extra_nl); diff --git a/src/core/execute.c b/src/core/execute.c index f1f3744191..56793c5a43 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2576,7 +2576,7 @@ static int acquire_credentials( if (source) - r = read_full_file_full(AT_FDCWD, source, flags, bindname, &data, &size); + r = read_full_file_full(AT_FDCWD, source, UINT64_MAX, SIZE_MAX, flags, bindname, &data, &size); else r = -ENOENT; if (r == -ENOENT && diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 362c61203a..534de51b0f 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -898,7 +898,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key file specified twice"); r = read_full_file_full( - AT_FDCWD, optarg, + AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_key_pem, NULL); @@ -911,7 +911,11 @@ static int parse_argv(int argc, char *argv[]) { if (arg_cert_pem) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Certificate file specified twice"); - r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_cert_pem, NULL); + r = read_full_file_full( + AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &arg_cert_pem, NULL); if (r < 0) return log_error_errno(r, "Failed to read certificate file: %m"); assert(arg_cert_pem); @@ -922,7 +926,11 @@ static int parse_argv(int argc, char *argv[]) { if (arg_trust_pem) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "CA certificate file specified twice"); - r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_trust_pem, NULL); + r = read_full_file_full( + AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &arg_trust_pem, NULL); if (r < 0) return log_error_errno(r, "Failed to read CA certificate file: %m"); assert(arg_trust_pem); diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index a78896c14f..af8cde7eef 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -1079,7 +1079,7 @@ static int load_certificates(char **key, char **cert, char **trust) { int r; r = read_full_file_full( - AT_FDCWD, arg_key ?: PRIV_KEY_FILE, + AT_FDCWD, arg_key ?: PRIV_KEY_FILE, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, NULL, key, NULL); @@ -1087,7 +1087,11 @@ static int load_certificates(char **key, char **cert, char **trust) { return log_error_errno(r, "Failed to read key from file '%s': %m", arg_key ?: PRIV_KEY_FILE); - r = read_full_file_full(AT_FDCWD, arg_cert ?: CERT_FILE, READ_FULL_FILE_CONNECT_SOCKET, NULL, cert, NULL); + r = read_full_file_full( + AT_FDCWD, arg_cert ?: CERT_FILE, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + cert, NULL); if (r < 0) return log_error_errno(r, "Failed to read certificate from file '%s': %m", arg_cert ?: CERT_FILE); @@ -1095,7 +1099,11 @@ static int load_certificates(char **key, char **cert, char **trust) { if (arg_trust_all) log_info("Certificate checking disabled."); else { - r = read_full_file_full(AT_FDCWD, arg_trust ?: TRUST_FILE, READ_FULL_FILE_CONNECT_SOCKET, NULL, trust, NULL); + r = read_full_file_full( + AT_FDCWD, arg_trust ?: TRUST_FILE, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + trust, NULL); if (r < 0) return log_error_errno(r, "Failed to read CA certificate file '%s': %m", arg_trust ?: TRUST_FILE); diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c index 313277ca16..27fc2fd5e5 100644 --- a/src/network/netdev/macsec.c +++ b/src/network/netdev/macsec.c @@ -986,7 +986,7 @@ static int macsec_read_key_file(NetDev *netdev, SecurityAssociation *sa) { (void) warn_file_is_world_accessible(sa->key_file, NULL, NULL, 0); r = read_full_file_full( - AT_FDCWD, sa->key_file, + AT_FDCWD, sa->key_file, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET, NULL, (char **) &key, &key_len); if (r < 0) diff --git a/src/network/netdev/wireguard.c b/src/network/netdev/wireguard.c index 416e9b92d1..76444bdd7c 100644 --- a/src/network/netdev/wireguard.c +++ b/src/network/netdev/wireguard.c @@ -869,7 +869,7 @@ static int wireguard_read_key_file(const char *filename, uint8_t dest[static WG_ (void) warn_file_is_world_accessible(filename, NULL, NULL, 0); r = read_full_file_full( - AT_FDCWD, filename, + AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET, NULL, &key, &key_len); if (r < 0) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 0842731c18..ad2f572869 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1589,7 +1589,10 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } - r = read_full_file_full(AT_FDCWD, j ?: p, flags, NULL, &data, &size); + r = read_full_file_full(AT_FDCWD, j ?: p, UINT64_MAX, SIZE_MAX, + flags, + NULL, + &data, &size); if (r < 0) return log_error_errno(r, "Failed to read credential '%s': %m", j ?: p); diff --git a/src/partition/repart.c b/src/partition/repart.c index 58cacab244..ddb9476cec 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3622,7 +3622,7 @@ static int parse_argv(int argc, char *argv[]) { size_t n = 0; r = read_full_file_full( - AT_FDCWD, optarg, + AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, NULL, &k, &n); diff --git a/src/shared/json.c b/src/shared/json.c index ddf6dcb663..28fe482749 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -3195,7 +3195,7 @@ int json_parse_file_at(FILE *f, int dir_fd, const char *path, JsonParseFlags fla if (f) r = read_full_stream(f, &text, NULL); else if (path) - r = read_full_file_full(dir_fd, path, 0, NULL, &text, NULL); + r = read_full_file_full(dir_fd, path, UINT64_MAX, SIZE_MAX, 0, NULL, &text, NULL); else return -EINVAL; if (r < 0) diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 431aea07ef..a5834eba36 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -911,8 +911,8 @@ static void test_read_full_file_socket(void) { _exit(EXIT_SUCCESS); } - assert_se(read_full_file_full(AT_FDCWD, j, 0, NULL, &data, &size) == -ENXIO); - assert_se(read_full_file_full(AT_FDCWD, j, READ_FULL_FILE_CONNECT_SOCKET, clientname, &data, &size) >= 0); + assert_se(read_full_file_full(AT_FDCWD, j, UINT64_MAX, SIZE_MAX, 0, NULL, &data, &size) == -ENXIO); + assert_se(read_full_file_full(AT_FDCWD, j, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, clientname, &data, &size) >= 0); assert_se(size == strlen(TEST_STR)); assert_se(streq(data, TEST_STR)); @@ -920,6 +920,50 @@ static void test_read_full_file_socket(void) { #undef TEST_STR } +static void test_read_full_file_offset_size(void) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_(unlink_and_freep) char *fn = NULL; + _cleanup_free_ char *rbuf = NULL; + size_t rbuf_size; + uint8_t buf[4711]; + + random_bytes(buf, sizeof(buf)); + + assert_se(tempfn_random_child(NULL, NULL, &fn) >= 0); + assert_se(f = fopen(fn, "we")); + assert_se(fwrite(buf, 1, sizeof(buf), f) == sizeof(buf)); + assert_se(fflush_and_check(f) >= 0); + + assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, SIZE_MAX, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == sizeof(buf)); + assert_se(memcmp(buf, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, 128, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == 128); + assert_se(memcmp(buf, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, 1234, SIZE_MAX, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == sizeof(buf) - 1234); + assert_se(memcmp(buf + 1234, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, 2345, 777, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == 777); + assert_se(memcmp(buf + 2345, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, 4700, 20, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == 11); + assert_se(memcmp(buf + 4700, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, 10000, 99, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == 0); + rbuf = mfree(rbuf); +} + int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); @@ -946,6 +990,7 @@ int main(int argc, char *argv[]) { test_read_line4(); test_read_nul_string(); test_read_full_file_socket(); + test_read_full_file_offset_size(); return 0; } diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index 558e9510ff..9b8bca11f2 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -100,7 +100,11 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to parse root hash signature '%s': %m", argv[6]); } else { - r = read_full_file_full(AT_FDCWD, argv[6], READ_FULL_FILE_CONNECT_SOCKET, NULL, &hash_sig, &hash_sig_size); + r = read_full_file_full( + AT_FDCWD, argv[6], UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &hash_sig, &hash_sig_size); if (r < 0) return log_error_errno(r, "Failed to read root hash signature: %m"); }