diff mbox series

[v2,06/20] fuse: Implement hole detection through lseek

Message ID 20200922104932.46384-7-mreitz@redhat.com
State New
Headers show
Series block/export: Allow exporting BDSs via FUSE | expand

Commit Message

Max Reitz Sept. 22, 2020, 10:49 a.m. UTC
This is a relatively new feature in libfuse (available since 3.8.0,
which was released in November 2019), so we have to let configure check
whether it is available before making use of it.

Signed-off-by: Max Reitz <mreitz@redhat.com>
---
 configure           | 32 +++++++++++++++++++
 block/export/fuse.c | 77 +++++++++++++++++++++++++++++++++++++++++++++
 meson.build         |  1 +
 3 files changed, 110 insertions(+)
diff mbox series

Patch

diff --git a/configure b/configure
index 21c31e4694..6b9184b62a 100755
--- a/configure
+++ b/configure
@@ -6226,11 +6226,39 @@  EOF
   fuse_libs=$(pkg-config --libs fuse3)
   if compile_prog "$fuse_cflags" "$fuse_libs"; then
     fuse=yes
+
+    cat > $TMPC <<EOF
+#define FUSE_USE_VERSION 31
+#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+static void fuse_lseek(fuse_req_t req, fuse_ino_t inode, off_t offset,
+                       int whence, struct fuse_file_info *fi)
+{
+    if (whence == SEEK_DATA || whence == SEEK_HOLE) {
+        fuse_reply_lseek(req, offset);
+    } else {
+        fuse_reply_err(req, EINVAL);
+    }
+}
+const struct fuse_lowlevel_ops fuse_ops = {
+    .lseek = fuse_lseek,
+};
+int main(void) { return 0; }
+EOF
+    if compile_prog "$fuse_cflags" "$fuse_libs"; then
+      fuse_lseek=yes
+    else
+      fuse_lseek=no
+    fi
   else
     if test "$fuse" = "yes"; then
       feature_not_found "fuse"
     fi
     fuse=no
+    fuse_lseek=no
   fi
 fi
 
@@ -7425,6 +7453,10 @@  if test "$fuse" = "yes"; then
   echo "CONFIG_FUSE=y" >> $config_host_mak
   echo "FUSE_CFLAGS=$fuse_cflags" >> $config_host_mak
   echo "FUSE_LIBS=$fuse_libs" >> $config_host_mak
+
+  if test "$fuse_lseek" = "yes"; then
+    echo "CONFIG_FUSE_LSEEK=y" >> $config_host_mak
+  fi
 fi
 
 if test "$tcg_interpreter" = "yes"; then
diff --git a/block/export/fuse.c b/block/export/fuse.c
index 7dfb4eb280..ee90f5a185 100644
--- a/block/export/fuse.c
+++ b/block/export/fuse.c
@@ -548,6 +548,80 @@  static void fuse_flush(fuse_req_t req, fuse_ino_t inode,
     fuse_reply_err(req, ret < 0 ? -ret : 0);
 }
 
+#ifdef CONFIG_FUSE_LSEEK
+/**
+ * Let clients inquire allocation status.
+ */
+static void fuse_lseek(fuse_req_t req, fuse_ino_t inode, off_t offset,
+                       int whence, struct fuse_file_info *fi)
+{
+    FuseExport *exp = fuse_req_userdata(req);
+
+    if (whence != SEEK_HOLE && whence != SEEK_DATA) {
+        fuse_reply_err(req, EINVAL);
+        return;
+    }
+
+    while (true) {
+        int64_t pnum;
+        int ret;
+
+        ret = bdrv_block_status_above(blk_bs(exp->common.blk), NULL,
+                                      offset, INT64_MAX, &pnum, NULL, NULL);
+        if (ret < 0) {
+            fuse_reply_err(req, -ret);
+            return;
+        }
+
+        if (!pnum && (ret & BDRV_BLOCK_EOF)) {
+            int64_t blk_len;
+
+            /*
+             * If blk_getlength() rounds (e.g. by sectors), then the
+             * export length will be rounded, too.  However,
+             * bdrv_block_status_above() may return EOF at unaligned
+             * offsets.  We must not let this become visible and thus
+             * always simulate a hole between @offset (the real EOF)
+             * and @blk_len (the client-visible EOF).
+             */
+
+            blk_len = blk_getlength(exp->common.blk);
+            if (blk_len < 0) {
+                fuse_reply_err(req, -blk_len);
+                return;
+            }
+
+            if (offset > blk_len || whence == SEEK_DATA) {
+                fuse_reply_err(req, ENXIO);
+            } else {
+                fuse_reply_lseek(req, offset);
+            }
+            return;
+        }
+
+        if (ret & BDRV_BLOCK_DATA) {
+            if (whence == SEEK_DATA) {
+                fuse_reply_lseek(req, offset);
+                return;
+            }
+        } else {
+            if (whence == SEEK_HOLE) {
+                fuse_reply_lseek(req, offset);
+                return;
+            }
+        }
+
+        /* Safety check against infinite loops */
+        if (!pnum) {
+            fuse_reply_err(req, ENXIO);
+            return;
+        }
+
+        offset += pnum;
+    }
+}
+#endif
+
 static const struct fuse_lowlevel_ops fuse_ops = {
     .lookup     = fuse_lookup,
     .getattr    = fuse_getattr,
@@ -557,6 +631,9 @@  static const struct fuse_lowlevel_ops fuse_ops = {
     .write      = fuse_write,
     .fallocate  = fuse_fallocate,
     .flush      = fuse_flush,
+#ifdef CONFIG_FUSE_LSEEK
+    .lseek      = fuse_lseek,
+#endif
 };
 
 const BlockExportDriver blk_exp_fuse = {
diff --git a/meson.build b/meson.build
index 85addd8562..1db6a46d14 100644
--- a/meson.build
+++ b/meson.build
@@ -1537,6 +1537,7 @@  summary_info += {'thread sanitizer':  config_host.has_key('CONFIG_TSAN')}
 summary_info += {'rng-none':          config_host.has_key('CONFIG_RNG_NONE')}
 summary_info += {'Linux keyring':     config_host.has_key('CONFIG_SECRET_KEYRING')}
 summary_info += {'fuse exports':      config_host.has_key('CONFIG_FUSE')}
+summary_info += {'fuse lseek':        config_host.has_key('CONFIG_FUSE_LSEEK')}
 summary(summary_info, bool_yn: true)
 
 if not supported_cpus.contains(cpu)