]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
basic/fileio: add a mode to read_full_virtual_file() where not the whole file is...
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 1 Apr 2021 13:23:02 +0000 (15:23 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 5 May 2021 11:59:23 +0000 (13:59 +0200)
src/basic/fileio.c
src/basic/fileio.h
src/test/test-fileio.c

index df30870a1a25f90c76fb2d9d251891958349bc55..90484a98c2ac4390659e2f67416338bf28e39e35 100644 (file)
@@ -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;
index 64a30ab7bb12df0236c776366a733c5670c7da71..2a6eef3790d7212bae657679311eb1304453b203 100644 (file)
@@ -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);
index c5c8116e96d3db831925d85b1ff8b3896d66ebfb..f3859d9ffbb626003e1bc686d6a6c4fd1b9606af 100644 (file)
@@ -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;
 }