[RFC PATCH v2 7/9] fuse: add fuse device ioctl(FUSE_DEV_IOC_REINIT)
From: Alexander Mikhalitsyn
Date: Mon Apr 03 2023 - 10:47:48 EST
This ioctl aborts fuse connection and then reinitializes it,
sends FUSE_INIT request to allow a new userspace daemon
to pick up the fuse connection.
Cc: Miklos Szeredi <mszeredi@xxxxxxxxxx>
Cc: Al Viro <viro@xxxxxxxxxxxxxxxxxx>
Cc: Amir Goldstein <amir73il@xxxxxxxxx>
Cc: Stéphane Graber <stgraber@xxxxxxxxxx>
Cc: Seth Forshee <sforshee@xxxxxxxxxx>
Cc: Christian Brauner <brauner@xxxxxxxxxx>
Cc: Andrei Vagin <avagin@xxxxxxxxx>
Cc: Pavel Tikhomirov <ptikhomirov@xxxxxxxxxxxxx>
Cc: Bernd Schubert <bschubert@xxxxxxx>
Cc: linux-fsdevel@xxxxxxxxxxxxxxx
Cc: linux-kernel@xxxxxxxxxxxxxxx
Cc: criu@xxxxxxxxxx
Signed-off-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@xxxxxxxxxxxxx>
---
fs/fuse/dev.c | 152 ++++++++++++++++++++++++++++++++++++++
fs/fuse/fuse_i.h | 3 +
include/uapi/linux/fuse.h | 1 +
3 files changed, 156 insertions(+)
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index b4501a10c379..93a457c90b49 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -2187,6 +2187,132 @@ void fuse_abort_conn(struct fuse_conn *fc)
}
EXPORT_SYMBOL_GPL(fuse_abort_conn);
+static int fuse_reinit_conn(struct fuse_conn *fc)
+{
+ struct fuse_iqueue *fiq = &fc->iq;
+ struct fuse_dev *fud;
+ unsigned int i;
+
+ spin_lock(&fc->lock);
+ if (fc->reinit_in_progress) {
+ spin_unlock(&fc->lock);
+ return -EBUSY;
+ }
+
+ if (fc->conn_gen + 1 < fc->conn_gen) {
+ spin_unlock(&fc->lock);
+ return -EOVERFLOW;
+ }
+
+ fc->reinit_in_progress = true;
+ spin_unlock(&fc->lock);
+
+ /*
+ * Unsets fc->connected and fiq->connected and
+ * ensures that no new requests can be queued
+ */
+ fuse_abort_conn(fc);
+ fuse_wait_aborted(fc);
+
+ spin_lock(&fc->lock);
+ if (fc->connected) {
+ fc->reinit_in_progress = false;
+ spin_unlock(&fc->lock);
+ return -EINVAL;
+ }
+
+ fc->conn_gen++;
+
+ spin_lock(&fiq->lock);
+ if (request_pending(fiq) || fiq->forget_list_tail != &fiq->forget_list_head) {
+ spin_unlock(&fiq->lock);
+ fc->reinit_in_progress = false;
+ spin_unlock(&fc->lock);
+ return -EINVAL;
+ }
+
+ if (&fuse_dev_fiq_ops != fiq->ops) {
+ spin_unlock(&fiq->lock);
+ fc->reinit_in_progress = false;
+ spin_unlock(&fc->lock);
+ return -EOPNOTSUPP;
+ }
+
+ fiq->connected = 1;
+ spin_unlock(&fiq->lock);
+
+ spin_lock(&fc->bg_lock);
+ if (!list_empty(&fc->bg_queue)) {
+ spin_unlock(&fc->bg_lock);
+ fc->reinit_in_progress = false;
+ spin_unlock(&fc->lock);
+ return -EINVAL;
+ }
+
+ fc->blocked = 0;
+ fc->max_background = FUSE_DEFAULT_MAX_BACKGROUND;
+ spin_unlock(&fc->bg_lock);
+
+ list_for_each_entry(fud, &fc->devices, entry) {
+ struct fuse_pqueue *fpq = &fud->pq;
+
+ spin_lock(&fpq->lock);
+ if (!list_empty(&fpq->io)) {
+ spin_unlock(&fpq->lock);
+ fc->reinit_in_progress = false;
+ spin_unlock(&fc->lock);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) {
+ if (!list_empty(&fpq->processing[i])) {
+ spin_unlock(&fpq->lock);
+ fc->reinit_in_progress = false;
+ spin_unlock(&fc->lock);
+ return -EINVAL;
+ }
+ }
+
+ fpq->connected = 1;
+ spin_unlock(&fpq->lock);
+ }
+
+ fuse_set_initialized(fc);
+
+ /* Background queuing checks fc->connected under bg_lock */
+ spin_lock(&fc->bg_lock);
+ fc->connected = 1;
+ spin_unlock(&fc->bg_lock);
+
+ fc->aborted = false;
+ fc->abort_err = 0;
+
+ /* nullify all the flags */
+ memset(&fc->flags, 0, sizeof(struct fuse_conn_flags));
+
+ spin_unlock(&fc->lock);
+
+ down_read(&fc->killsb);
+ if (!list_empty(&fc->mounts)) {
+ struct fuse_mount *fm;
+
+ fm = list_first_entry(&fc->mounts, struct fuse_mount, fc_entry);
+ if (!fm->sb) {
+ up_read(&fc->killsb);
+ return -EINVAL;
+ }
+
+ fuse_send_init(fm);
+ }
+ up_read(&fc->killsb);
+
+ spin_lock(&fc->lock);
+ fc->reinit_in_progress = false;
+ spin_unlock(&fc->lock);
+
+ return 0;
+}
+
void fuse_wait_aborted(struct fuse_conn *fc)
{
/* matches implicit memory barrier in fuse_drop_waiting() */
@@ -2282,6 +2408,32 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
}
}
break;
+ case FUSE_DEV_IOC_REINIT:
+ struct fuse_conn *fc;
+
+ if (!checkpoint_restore_ns_capable(file->f_cred->user_ns))
+ return -EPERM;
+
+ res = -EINVAL;
+ fud = fuse_get_dev(file);
+
+ /*
+ * Only fuse mounts with an already initialized fuse
+ * connection are supported
+ */
+ if (file->f_op == &fuse_dev_operations && fud) {
+ mutex_lock(&fuse_mutex);
+ fc = fud->fc;
+ if (fc)
+ fc = fuse_conn_get(fc);
+ mutex_unlock(&fuse_mutex);
+
+ if (fc) {
+ res = fuse_reinit_conn(fc);
+ fuse_conn_put(fc);
+ }
+ }
+ break;
default:
res = -ENOTTY;
break;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 90c5b3459864..8f2c0f969f6f 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -752,6 +752,9 @@ struct fuse_conn {
/** Connection aborted via sysfs */
bool aborted;
+ /** Connection reinit in progress */
+ bool reinit_in_progress;
+
/** Connection failed (version mismatch). Cannot race with
setting other bitfields since it is only set once in INIT
reply, before any other request, and never cleared */
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index b3fcab13fcd3..325da23431ef 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -992,6 +992,7 @@ struct fuse_notify_retrieve_in {
/* Device ioctls: */
#define FUSE_DEV_IOC_MAGIC 229
#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+#define FUSE_DEV_IOC_REINIT _IO(FUSE_DEV_IOC_MAGIC, 0)
struct fuse_lseek_in {
uint64_t fh;
--
2.34.1