From: Zbigniew Jędrzejewski-Szmek Date: Thu, 1 Apr 2021 13:23:02 +0000 (+0200) Subject: basic/fileio: add a mode to read_full_virtual_file() where not the whole file is... X-Git-Tag: v249-rc1~274^2~12 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ad0e687c07ae5580d8bedde7c632e4a4d30d849f;p=thirdparty%2Fsystemd.git basic/fileio: add a mode to read_full_virtual_file() where not the whole file is read --- diff --git a/src/basic/fileio.c b/src/basic/fileio.c index df30870a1a2..90484a98c2a 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -363,32 +363,38 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { return 1; } -int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size) { +int read_virtual_file(const char *filename, size_t max_size, char **ret_contents, size_t *ret_size) { _cleanup_free_ char *buf = NULL; _cleanup_close_ int fd = -1; - struct stat st; size_t n, size; int n_retries; assert(ret_contents); - /* Virtual filesystems such as sysfs or procfs use kernfs, and kernfs can work - * with two sorts of virtual files. One sort uses "seq_file", and the results of - * the first read are buffered for the second read. The other sort uses "raw" - * reads which always go direct to the device. In the latter case, the content of - * the virtual file must be retrieved with a single read otherwise a second read - * might get the new value instead of finding EOF immediately. That's the reason - * why the usage of fread(3) is prohibited in this case as it always performs a - * second call to read(2) looking for EOF. See issue 13585. */ + /* Virtual filesystems such as sysfs or procfs use kernfs, and kernfs can work with two sorts of + * virtual files. One sort uses "seq_file", and the results of the first read are buffered for the + * second read. The other sort uses "raw" reads which always go direct to the device. In the latter + * case, the content of the virtual file must be retrieved with a single read otherwise a second read + * might get the new value instead of finding EOF immediately. That's the reason why the usage of + * fread(3) is prohibited in this case as it always performs a second call to read(2) looking for + * EOF. See issue #13585. + * + * max_size specifies a limit on the bytes read. If max_size is SIZE_MAX, the full file is read. If + * the the full file is too large to read, an error is returned. For other values of max_size, + * *partial contents* may be returned. (Though the read is still done using one syscall.) */ fd = open(filename, O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; + assert(max_size <= READ_FULL_BYTES_MAX || max_size == SIZE_MAX); + /* Limit the number of attempts to read the number of bytes returned by fstat(). */ n_retries = 3; for (;;) { + struct stat st; + if (fstat(fd, &st) < 0) return -errno; @@ -398,13 +404,16 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re /* Be prepared for files from /proc which generally report a file size of 0. */ assert_cc(READ_FULL_BYTES_MAX < SSIZE_MAX); if (st.st_size > 0) { - if (st.st_size > READ_FULL_BYTES_MAX) + if (st.st_size > SSIZE_MAX) /* Avoid overflow with 32-bit size_t and 64-bit off_t. */ + return -EFBIG; + + size = MIN((size_t) st.st_size, max_size); + if (size > READ_FULL_BYTES_MAX) return -EFBIG; - size = st.st_size; n_retries--; } else { - size = READ_FULL_BYTES_MAX; + size = MIN(READ_FULL_BYTES_MAX, max_size); n_retries = 0; } @@ -412,7 +421,7 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re if (!buf) return -ENOMEM; /* Use a bigger allocation if we got it anyway, but not more than the limit. */ - size = MIN(malloc_usable_size(buf) - 1, READ_FULL_BYTES_MAX); + size = MIN3(malloc_usable_size(buf) - 1, max_size, READ_FULL_BYTES_MAX); for (;;) { ssize_t k; @@ -439,8 +448,14 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re * processing, let's try again either with a bigger guessed size or the new * file size. */ - if (n_retries <= 0) - return st.st_size > 0 ? -EIO : -EFBIG; + if (n_retries <= 0) { + if (max_size == SIZE_MAX) + return st.st_size > 0 ? -EIO : -EFBIG; + + /* Accept a short read, but truncate it appropropriately. */ + n = MIN(n, max_size); + break; + } if (lseek(fd, 0, SEEK_SET) < 0) return -errno; diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 64a30ab7bb1..2a6eef3790d 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -64,7 +64,12 @@ int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, 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_virtual_file(const char *filename, size_t max_size, char **ret_contents, size_t *ret_size); +static inline int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size) { + return read_virtual_file(filename, SIZE_MAX, ret_contents, ret_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); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index c5c8116e96d..f3859d9ffbb 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -965,7 +965,7 @@ static void test_read_full_file_offset_size(void) { rbuf = mfree(rbuf); } -static void test_read_full_virtual_file(void) { +static void test_read_virtual_file(size_t max_size) { const char *filename; int r; @@ -977,8 +977,8 @@ static void test_read_full_virtual_file(void) { _cleanup_free_ char *buf = NULL; size_t size = 0; - r = read_full_virtual_file(filename, &buf, &size); - log_info_errno(r, "read_full_virtual_file(\"%s\"): %m (%zu bytes)", filename, size); + r = read_virtual_file(filename, max_size, &buf, &size); + log_info_errno(r, "read_virtual_file(\"%s\", %zu): %m (%zu bytes)", filename, max_size, size); assert_se(r == 0 || ERRNO_IS_PRIVILEGE(r) || r == -ENOENT); } } @@ -1010,7 +1010,9 @@ int main(int argc, char *argv[]) { test_read_nul_string(); test_read_full_file_socket(); test_read_full_file_offset_size(); - test_read_full_virtual_file(); + test_read_virtual_file(20); + test_read_virtual_file(4096); + test_read_virtual_file(SIZE_MAX); return 0; }