All of lore.kernel.org
 help / color / mirror / Atom feed
From: Max Reitz <mreitz@redhat.com>
To: qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>,
	qemu-devel@nongnu.org, Max Reitz <mreitz@redhat.com>
Subject: [PATCH 02/18] fuse: Allow exporting BDSs via FUSE
Date: Thu, 19 Dec 2019 15:38:02 +0100	[thread overview]
Message-ID: <20191219143818.1646168-3-mreitz@redhat.com> (raw)
In-Reply-To: <20191219143818.1646168-1-mreitz@redhat.com>

fuse-export-add allows mounting block graph nodes via FUSE on some
existing regular file.  That file should then appears like a raw disk
image, and accesses to it result in accesses to the exported BDS.

Right now, we only set up the mount point and tear all mount points down
in bdrv_close_all().  We do not implement any access functions, so
accessing the mount point only results in errors.  This will be
addressed by a followup patch.

The set of exported nodes is kept in a hash table so we can later add a
fuse-export-remove that allows unmounting.

Signed-off-by: Max Reitz <mreitz@redhat.com>
---
 block.c              |   4 +
 block/Makefile.objs  |   3 +
 block/fuse.c         | 260 +++++++++++++++++++++++++++++++++++++++++++
 include/block/fuse.h |  24 ++++
 qapi/block.json      |  23 ++++
 5 files changed, 314 insertions(+)
 create mode 100644 block/fuse.c
 create mode 100644 include/block/fuse.h

diff --git a/block.c b/block.c
index c390ec6461..887c0b105e 100644
--- a/block.c
+++ b/block.c
@@ -26,6 +26,7 @@
 #include "block/trace.h"
 #include "block/block_int.h"
 #include "block/blockjob.h"
+#include "block/fuse.h"
 #include "block/nbd.h"
 #include "block/qdict.h"
 #include "qemu/error-report.h"
@@ -4077,6 +4078,9 @@ void bdrv_close_all(void)
 {
     assert(job_next(NULL) == NULL);
     nbd_export_close_all();
+#ifdef CONFIG_FUSE
+    fuse_export_close_all();
+#endif
 
     /* Drop references from requests still in flight, such as canceled block
      * jobs whose AIO context has not been polled yet */
diff --git a/block/Makefile.objs b/block/Makefile.objs
index e394fe0b6c..b02846a6e7 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -43,6 +43,7 @@ block-obj-y += crypto.o
 
 block-obj-y += aio_task.o
 block-obj-y += backup-top.o
+block-obj-$(CONFIG_FUSE) += fuse.o
 
 common-obj-y += stream.o
 
@@ -67,3 +68,5 @@ qcow.o-libs        := -lz
 linux-aio.o-libs   := -laio
 parallels.o-cflags := $(LIBXML2_CFLAGS)
 parallels.o-libs   := $(LIBXML2_LIBS)
+fuse.o-cflags      := $(FUSE_CFLAGS)
+fuse.o-libs        := $(FUSE_LIBS)
diff --git a/block/fuse.c b/block/fuse.c
new file mode 100644
index 0000000000..3a22579dca
--- /dev/null
+++ b/block/fuse.c
@@ -0,0 +1,260 @@
+/*
+ * Present a block device as a raw image through FUSE
+ *
+ * Copyright (c) 2019 Max Reitz <mreitz@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 or later of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define FUSE_USE_VERSION 31
+
+#include "qemu/osdep.h"
+#include "block/aio.h"
+#include "block/block.h"
+#include "block/fuse.h"
+#include "block/qapi.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-block.h"
+#include "sysemu/block-backend.h"
+
+#include <fuse.h>
+#include <fuse_lowlevel.h>
+
+
+typedef struct BdrvFuseSession {
+    struct fuse_session *fuse_session;
+    struct fuse_buf fuse_buf;
+    BlockBackend *blk;
+    uint64_t perm, shared_perm;
+    bool mounted, fd_handler_set_up;
+    bool writable;
+} BdrvFuseSession;
+
+static GHashTable *sessions;
+static const struct fuse_lowlevel_ops fuse_ops;
+
+static void init_fuse(void);
+static int setup_fuse_session(BdrvFuseSession *session, const char *mountpoint,
+                              Error **errp);
+static void read_from_fuse_session(void *opaque);
+static void close_fuse_session(BdrvFuseSession *session);
+static void drop_fuse_session_from_hash_table(gpointer value);
+
+static bool is_regular_file(const char *path, Error **errp);
+
+
+void qmp_fuse_export_add(const char *node_name, const char *mountpoint,
+                         bool has_writable, bool writable,
+                         Error **errp)
+{
+    BlockDriverState *bs;
+    BdrvFuseSession *session = NULL;
+
+    if (!has_writable) {
+        writable = false;
+    }
+
+    init_fuse();
+
+    /*
+     * It is important to do this check before calling is_regular_file() --
+     * that function will do a stat(), which we would have to handle if we
+     * already exported something on @mountpoint.  But we cannot, because
+     * we are currently caught up here.
+     */
+    if (g_hash_table_contains(sessions, mountpoint)) {
+        error_setg(errp, "There already is a FUSE export on '%s'", mountpoint);
+        goto fail;
+    }
+
+    if (!is_regular_file(mountpoint, errp)) {
+        goto fail;
+    }
+
+    bs = bdrv_find_node(node_name);
+    if (!bs) {
+        error_setg(errp, "Node '%s' not found", node_name);
+        goto fail;
+    }
+
+    session = g_new(BdrvFuseSession, 1);
+    *session = (BdrvFuseSession){
+        .fuse_buf = {
+            .mem = NULL,
+        },
+
+        .writable = writable,
+    };
+
+    session->perm = BLK_PERM_CONSISTENT_READ;
+    if (writable) {
+        session->perm |= BLK_PERM_WRITE;
+    }
+    session->shared_perm = BLK_PERM_ALL;
+
+    session->blk = blk_new(bdrv_get_aio_context(bs),
+                           session->perm, session->shared_perm);
+    if (blk_insert_bs(session->blk, bs, errp) < 0) {
+        goto fail;
+    }
+
+    if (setup_fuse_session(session, mountpoint, errp) < 0) {
+        goto fail;
+    }
+
+    g_hash_table_insert(sessions, g_strdup(mountpoint), session);
+    return;
+
+fail:
+    close_fuse_session(session);
+}
+
+/**
+ * Drop all FUSE exports.
+ */
+void fuse_export_close_all(void)
+{
+    if (sessions) {
+        g_hash_table_destroy(sessions);
+    }
+}
+
+
+/**
+ * Ensure that the global FUSE context is set up.
+ */
+static void init_fuse(void)
+{
+    if (sessions) {
+        return;
+    }
+
+    sessions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+                                     drop_fuse_session_from_hash_table);
+}
+
+/**
+ * Create session->fuse_session and mount it.
+ */
+static int setup_fuse_session(BdrvFuseSession *session, const char *mountpoint,
+                              Error **errp)
+{
+    const char *fuse_argv[2];
+    struct fuse_args fuse_args;
+    int ret;
+
+    fuse_argv[0] = ""; /* Dummy program name */
+    fuse_argv[1] = NULL;
+    fuse_args = (struct fuse_args)FUSE_ARGS_INIT(1, (char **)fuse_argv);
+
+    session->fuse_session = fuse_session_new(&fuse_args, &fuse_ops,
+                                             sizeof(fuse_ops), session);
+    if (!session->fuse_session) {
+        error_setg(errp, "Failed to set up FUSE session");
+        return -EIO;
+    }
+
+    ret = fuse_session_mount(session->fuse_session, mountpoint);
+    if (ret < 0) {
+        error_setg(errp, "Failed to mount FUSE session to export");
+        return -EIO;
+    }
+    session->mounted = true;
+
+    aio_set_fd_handler(blk_get_aio_context(session->blk),
+                       fuse_session_fd(session->fuse_session), true,
+                       read_from_fuse_session, NULL, NULL, session);
+    session->fd_handler_set_up = true;
+
+    return 0;
+}
+
+/**
+ * Callback to be invoked when the FUSE session FD can be read from.
+ * (This is basically the FUSE event loop.)
+ */
+static void read_from_fuse_session(void *opaque)
+{
+    BdrvFuseSession *session = opaque;
+    int ret;
+
+    ret = fuse_session_receive_buf(session->fuse_session, &session->fuse_buf);
+    if (ret < 0) {
+        return;
+    }
+
+    fuse_session_process_buf(session->fuse_session, &session->fuse_buf);
+}
+
+/**
+ * Drop a FUSE session (unmount it and free all associated resources).
+ * It is not removed from the @sessions hash table.
+ */
+static void close_fuse_session(BdrvFuseSession *session)
+{
+    if (!session) {
+        return;
+    }
+
+    if (session->fuse_session) {
+        if (session->mounted) {
+            fuse_session_unmount(session->fuse_session);
+        }
+        if (session->fd_handler_set_up) {
+            aio_set_fd_handler(blk_get_aio_context(session->blk),
+                               fuse_session_fd(session->fuse_session), true,
+                               NULL, NULL, NULL, NULL);
+        }
+        fuse_session_destroy(session->fuse_session);
+    }
+    blk_unref(session->blk);
+
+    g_free(session);
+}
+
+/**
+ * Wrapper around close_fuse_session() for use with
+ * g_hash_table_new_full().  This allows dropping sessions by removing
+ * them from the @sessions hash table.
+ */
+static void drop_fuse_session_from_hash_table(gpointer value)
+{
+    return close_fuse_session(value);
+}
+
+
+/**
+ * Check whether @path points to a regular file.  If not, put an
+ * appropriate message into *errp.
+ */
+static bool is_regular_file(const char *path, Error **errp)
+{
+    struct stat statbuf;
+    int ret;
+
+    ret = stat(path, &statbuf);
+    if (ret < 0) {
+        error_setg_errno(errp, errno, "Failed to stat '%s'", path);
+        return false;
+    }
+
+    if (!S_ISREG(statbuf.st_mode)) {
+        error_setg(errp, "'%s' is not a regular file", path);
+        return false;
+    }
+
+    return true;
+}
+
+static const struct fuse_lowlevel_ops fuse_ops = {
+};
diff --git a/include/block/fuse.h b/include/block/fuse.h
new file mode 100644
index 0000000000..1d24dded50
--- /dev/null
+++ b/include/block/fuse.h
@@ -0,0 +1,24 @@
+/*
+ * Present a block device as a raw image through FUSE
+ *
+ * Copyright (c) 2019 Max Reitz <mreitz@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 or later of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BLOCK_FUSE_H
+#define BLOCK_FUSE_H
+
+void fuse_export_close_all(void);
+
+#endif
diff --git a/qapi/block.json b/qapi/block.json
index 145c268bb6..03f8d1b537 100644
--- a/qapi/block.json
+++ b/qapi/block.json
@@ -317,6 +317,29 @@
 ##
 { 'command': 'nbd-server-stop' }
 
+##
+# @fuse-export-add:
+#
+# Exports a block graph node on some (file) mountpoint as a raw image.
+#
+# @node-name: Node to be exported
+#
+# @mountpoint: Path on which to export the block device via FUSE.
+#              This must point to an existing regular file.
+#
+# @writable: Whether clients should be able to write to the block
+#            device via the FUSE export. (default: false)
+#
+# Since: 5.0
+##
+{ 'command': 'fuse-export-add',
+  'data': {
+      'node-name': 'str',
+      'mountpoint': 'str',
+      '*writable': 'bool'
+  },
+  'if': 'defined(CONFIG_FUSE)' }
+
 ##
 # @DEVICE_TRAY_MOVED:
 #
-- 
2.23.0



  parent reply	other threads:[~2019-12-19 14:40 UTC|newest]

Thread overview: 37+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-12-19 14:38 [PATCH 00/18] block: Allow exporting BDSs via FUSE Max Reitz
2019-12-19 14:38 ` [PATCH 01/18] configure: Detect libfuse Max Reitz
2019-12-19 14:38 ` Max Reitz [this message]
2019-12-20 10:26   ` [PATCH 02/18] fuse: Allow exporting BDSs via FUSE Kevin Wolf
2019-12-20 10:48     ` Max Reitz
2019-12-20 11:24       ` Kevin Wolf
2019-12-20 12:09         ` Max Reitz
2019-12-20 12:48         ` Markus Armbruster
2019-12-20 12:58           ` Kevin Wolf
2019-12-20 13:25             ` Markus Armbruster
2019-12-20 21:18               ` Eric Blake
2019-12-20 12:49     ` Markus Armbruster
2019-12-20 13:02       ` Kevin Wolf
2019-12-20 21:15   ` Eric Blake
2020-01-06 12:00     ` Max Reitz
2019-12-19 14:38 ` [PATCH 03/18] fuse: Implement standard FUSE operations Max Reitz
2019-12-19 14:38 ` [PATCH 04/18] fuse: Add fuse-export-remove Max Reitz
2019-12-19 14:38 ` [PATCH 05/18] fuse: Allow growable exports Max Reitz
2019-12-19 14:38 ` [PATCH 06/18] fuse: (Partially) implement fallocate() Max Reitz
2019-12-19 14:38 ` [PATCH 07/18] fuse: Implement hole detection through lseek Max Reitz
2019-12-19 14:38 ` [PATCH 08/18] iotests: Do not needlessly filter _make_test_img Max Reitz
2019-12-19 14:38 ` [PATCH 09/18] iotests: Do not pipe _make_test_img Max Reitz
2019-12-19 14:38 ` [PATCH 10/18] iotests: Use convert -n in some cases Max Reitz
2019-12-19 14:38 ` [PATCH 11/18] iotests: Avoid renaming images Max Reitz
2019-12-19 14:38 ` [PATCH 12/18] iotests: Derive image names from $TEST_IMG Max Reitz
2019-12-19 14:38 ` [PATCH 13/18] iotests/091: Use _cleanup_qemu instad of "wait" Max Reitz
2019-12-19 14:38 ` [PATCH 14/18] iotests: Restrict some Python tests to file Max Reitz
2019-12-19 14:38 ` [PATCH 15/18] iotests: Let _make_test_img guess $TEST_IMG_FILE Max Reitz
2019-12-19 14:38 ` [PATCH 16/18] iotests: Allow testing FUSE exports Max Reitz
2019-12-19 14:38 ` [PATCH 17/18] iotests: Enable fuse for many tests Max Reitz
2019-12-19 14:38 ` [PATCH 18/18] iotests/281: Add test for FUSE exports Max Reitz
2019-12-19 19:05 ` [PATCH 00/18] block: Allow exporting BDSs via FUSE Max Reitz
2019-12-20 10:08 ` Stefan Hajnoczi
2019-12-20 10:30   ` Max Reitz
2019-12-20 12:50     ` Kevin Wolf
2019-12-20 21:20       ` Eric Blake
2020-01-02 11:22     ` Stefan Hajnoczi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20191219143818.1646168-3-mreitz@redhat.com \
    --to=mreitz@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.