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]>
---
 configure.ac |  2 ++
 lib/io.c     | 40 ++++++++++++++++++++++++++++++++++------
 2 files changed, 36 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..eb9d876 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"
 
@@ -55,9 +57,11 @@ void dev_close(struct erofs_sb_info *sbi)
 
 int dev_open(struct erofs_sb_info *sbi, const char *dev)
 {
+       bool again = false;
        struct stat st;
        int fd, ret;
 
+repeat:
        fd = open(dev, O_RDWR | O_CREAT | O_BINARY, 0644);
        if (fd < 0) {
                erofs_err("failed to open(%s).", dev);
@@ -82,11 +86,35 @@ 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) {
+#ifdef HAVE_SYS_STATFS_H
+                       struct statfs stfs;
+
+                       if (again)
+                               return -ENOTEMPTY;
+
+#ifdef HAVE_FSTATFS
+                       /*
+                        * 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
+#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