From 586bdfe1c316ce5e129978d266c0f532e9d6ce5a Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 28 Jan 2022 11:39:31 +0000 Subject: fdio: Use a read/write loop until EOF if st_size is zero Some pseudo-files in /proc and /sys list a size of 0 bytes in stat() results, but do in fact have content. For these pseudo-files, the only way to find out the true file size is to try read() until EOF, so leave the max_bytes set to -1. copy_file_range() and sendfile() don't work for such files in some kernel versions (see ), so skip the fast-paths and use a read() and write() loop. For pseudo-files, we expect to be able to copy the whole content in one read() in any case. Signed-off-by: Simon McVittie --- glnx-fdio.c | 8 +++++--- tests/test-libglnx-fdio.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/glnx-fdio.c b/glnx-fdio.c index 3fa73b5..c6d130b 100644 --- a/glnx-fdio.c +++ b/glnx-fdio.c @@ -805,7 +805,9 @@ glnx_regfile_copy_bytes (int fdf, int fdt, off_t max_bytes) */ if (fstat (fdf, &stbuf) < 0) return -1; - max_bytes = stbuf.st_size; + + if (stbuf.st_size > 0) + max_bytes = stbuf.st_size; } while (TRUE) @@ -816,7 +818,7 @@ glnx_regfile_copy_bytes (int fdf, int fdt, off_t max_bytes) * try_copy_file_range() from systemd upstream, which works better since * we use POSIX errno style. */ - if (try_cfr) + if (try_cfr && max_bytes != (off_t) -1) { n = copy_file_range (fdf, NULL, fdt, NULL, max_bytes, 0u); if (n < 0) @@ -855,7 +857,7 @@ glnx_regfile_copy_bytes (int fdf, int fdt, off_t max_bytes) /* Next try sendfile(); this version is also changed from systemd upstream * to match the same logic we have for copy_file_range(). */ - if (try_sendfile) + if (try_sendfile && max_bytes != (off_t) -1) { n = sendfile (fdt, fdf, NULL, max_bytes); if (n < 0) diff --git a/tests/test-libglnx-fdio.c b/tests/test-libglnx-fdio.c index 84ebb14..be4448f 100644 --- a/tests/test-libglnx-fdio.c +++ b/tests/test-libglnx-fdio.c @@ -235,6 +235,54 @@ test_filecopy (void) g_assert (S_ISREG (stbuf.st_mode)); } +static void +test_filecopy_procfs (void) +{ + const char * const pseudo_files[] = + { + /* A file in /proc that stat()s as empty (at least on Linux 5.15) */ + "/proc/version", + /* A file in /sys that stat()s as empty (at least on Linux 5.15) */ + "/sys/fs/cgroup/cgroup.controllers", + /* A file in /sys that stat()s as non-empty (at least on Linux 5.15) */ + "/sys/fs/ext4/features/meta_bg_resize", + }; + gsize i; + + for (i = 0; i < G_N_ELEMENTS (pseudo_files); i++) + { + _GLNX_TEST_DECLARE_ERROR(local_error, error); + g_autofree char *contents = NULL; + g_autofree char *contents_of_copy = NULL; + gsize len; + gsize len_copy; + + if (!g_file_get_contents (pseudo_files[i], &contents, &len, error)) + { + g_test_message ("Not testing %s: %s", + pseudo_files[i], local_error->message); + g_clear_error (&local_error); + continue; + } + + if (!glnx_file_copy_at (AT_FDCWD, pseudo_files[i], NULL, + AT_FDCWD, "copy", + GLNX_FILE_COPY_OVERWRITE | GLNX_FILE_COPY_NOCHOWN, + NULL, error)) + return; + + g_assert_no_error (local_error); + + if (!g_file_get_contents ("copy", &contents_of_copy, &len_copy, error)) + return; + + g_assert_no_error (local_error); + + g_assert_cmpstr (contents, ==, contents_of_copy); + g_assert_cmpuint (len, ==, len_copy); + } +} + int main (int argc, char **argv) { _GLNX_TEST_SCOPED_TEMP_DIR; @@ -245,6 +293,7 @@ int main (int argc, char **argv) g_test_add_func ("/tmpfile", test_tmpfile); g_test_add_func ("/stdio-file", test_stdio_file); g_test_add_func ("/filecopy", test_filecopy); + g_test_add_func ("/filecopy-procfs", test_filecopy_procfs); g_test_add_func ("/renameat2-noreplace", test_renameat2_noreplace); g_test_add_func ("/renameat2-exchange", test_renameat2_exchange); g_test_add_func ("/fstat", test_fstatat); -- cgit v1.2.1