[libglnx/wip/smcv/copy-pseudo-files] fdio: Use a read/write loop until EOF if st_size is zero




commit 586bdfe1c316ce5e129978d266c0f532e9d6ce5a
Author: Simon McVittie <smcv debian org>
Date:   Fri Jan 28 11:39:31 2022 +0000

    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 <https://lwn.net/Articles/846403/>), 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 <smcv debian org>

 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);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]