Traditionally, truncating to small sizes will trigger some
flush-on-close semantics to avoid the notorious NULL files.

I'm not sure if it's our use case since:
  1) we're creating new image files instead of reusing old ones;
  2) it kills end-to-end performance in practice;
  3) other programs like GNU TAR doesn't work as this too for
     such meaningless comparsion;

Let's work around it now.

Signed-off-by: Gao Xiang <[email protected]>
---
change since v1:
 - fix macOS build error:

 configure.ac |  2 ++
 lib/io.c     | 41 +++++++++++++++++++++++++++++++++++------
 2 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/configure.ac b/configure.ac
index a8cecd0..51ace67 100644
--- a/configure.ac
+++ b/configure.ac
@@ -190,6 +190,7 @@ AC_CHECK_HEADERS(m4_flatten([
        sys/mman.h
        sys/random.h
        sys/stat.h
+       sys/statfs.h
        sys/sysmacros.h
        sys/time.h
        unistd.h
@@ -249,6 +250,7 @@ AC_CHECK_FUNCS(m4_flatten([
        ftello64
        pread64
        pwrite64
+       fstatfs
        strdup
        strerror
        strrchr
diff --git a/lib/io.c b/lib/io.c
index 1545436..602ac68 100644
--- a/lib/io.c
+++ b/lib/io.c
@@ -20,7 +20,9 @@
 #ifdef HAVE_LINUX_FALLOC_H
 #include <linux/falloc.h>
 #endif
-
+#ifdef HAVE_SYS_STATFS_H
+#include <sys/statfs.h>
+#endif
 #define EROFS_MODNAME  "erofs_io"
 #include "erofs/print.h"
 
@@ -58,6 +60,11 @@ int dev_open(struct erofs_sb_info *sbi, const char *dev)
        struct stat st;
        int fd, ret;
 
+#if defined(HAVE_SYS_STATFS_H) && defined(HAVE_FSTATFS)
+       bool again = false;
+
+repeat:
+#endif
        fd = open(dev, O_RDWR | O_CREAT | O_BINARY, 0644);
        if (fd < 0) {
                erofs_err("failed to open(%s).", dev);
@@ -82,11 +89,33 @@ int dev_open(struct erofs_sb_info *sbi, const char *dev)
                sbi->devsz = round_down(sbi->devsz, erofs_blksiz(sbi));
                break;
        case S_IFREG:
-               ret = ftruncate(fd, 0);
-               if (ret) {
-                       erofs_err("failed to ftruncate(%s).", dev);
-                       close(fd);
-                       return -errno;
+               if (st.st_size) {
+#if defined(HAVE_SYS_STATFS_H) && defined(HAVE_FSTATFS)
+                       struct statfs stfs;
+
+                       if (again)
+                               return -ENOTEMPTY;
+
+                       /*
+                        * fses like EXT4 and BTRFS will flush dirty blocks
+                        * after truncate(0) even after the writeback happens
+                        * (see kernel commit 7d8f9f7d150d and ccd2506bd431),
+                        * which is NOT our intention.  Let's work around this.
+                        */
+                       if (!fstatfs(fd, &stfs) && (stfs.f_type == 0xEF53 ||
+                                       stfs.f_type == 0x9123683E)) {
+                               close(fd);
+                               unlink(dev);
+                               again = true;
+                               goto repeat;
+                       }
+#endif
+                       ret = ftruncate(fd, 0);
+                       if (ret) {
+                               erofs_err("failed to ftruncate(%s).", dev);
+                               close(fd);
+                               return -errno;
+                       }
                }
                /* INT64_MAX is the limit of kernel vfs */
                sbi->devsz = INT64_MAX;
-- 
2.39.3

Reply via email to