summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@redhat.com>2026-03-12 00:05:17 +0300
committerMiklos Szeredi <mszeredi@redhat.com>2026-04-02 21:43:24 +0300
commite9bf38500ed9aec7cfdf9219c75d353645b41168 (patch)
treed531d968cfd670f784d61f90ad4905d6ffb84c51
parenta8dd5f1b73bc533e1192d38c82fc144595d3ce9a (diff)
downloadlinux-e9bf38500ed9aec7cfdf9219c75d353645b41168.tar.xz
fuse: add refcount to fuse_dev
This will make it possible to grab the fuse_dev and subsequently release the file that it came from. In the above case, fud->fc will be set to FUSE_DEV_FC_DISCONNECTED to indicate that this is no longer a functional device. When trying to assign an fc to such a disconnected fuse_dev, the fc is set to the disconnected state. Use atomic operations xchg() and cmpxchg() to prevent races. Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
-rw-r--r--fs/fuse/cuse.c2
-rw-r--r--fs/fuse/dev.c9
-rw-r--r--fs/fuse/fuse_dev_i.h15
-rw-r--r--fs/fuse/fuse_i.h5
-rw-r--r--fs/fuse/inode.c35
-rw-r--r--fs/fuse/virtio_fs.c2
6 files changed, 50 insertions, 18 deletions
diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
index dfcb98a654d8..174333633471 100644
--- a/fs/fuse/cuse.c
+++ b/fs/fuse/cuse.c
@@ -527,7 +527,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file)
cc->fc.initialized = 1;
rc = cuse_send_init(cc);
if (rc) {
- fuse_dev_free(fud);
+ fuse_dev_put(fud);
return rc;
}
file->private_data = fud;
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 0c03bbb21c12..3d96e7a16103 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -2540,7 +2540,8 @@ void fuse_wait_aborted(struct fuse_conn *fc)
int fuse_dev_release(struct inode *inode, struct file *file)
{
struct fuse_dev *fud = fuse_file_to_fud(file);
- struct fuse_conn *fc = fuse_dev_fc_get(fud);
+ /* Pairs with cmpxchg() in fuse_dev_install() */
+ struct fuse_conn *fc = xchg(&fud->fc, FUSE_DEV_FC_DISCONNECTED);
if (fc) {
struct fuse_pqueue *fpq = &fud->pq;
@@ -2560,8 +2561,12 @@ int fuse_dev_release(struct inode *inode, struct file *file)
WARN_ON(fc->iq.fasync != NULL);
fuse_abort_conn(fc);
}
+ spin_lock(&fc->lock);
+ list_del(&fud->entry);
+ spin_unlock(&fc->lock);
+ fuse_conn_put(fc);
}
- fuse_dev_free(fud);
+ fuse_dev_put(fud);
return 0;
}
EXPORT_SYMBOL_GPL(fuse_dev_release);
diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h
index 522b2012cd1f..910f883cd090 100644
--- a/fs/fuse/fuse_dev_i.h
+++ b/fs/fuse/fuse_dev_i.h
@@ -39,22 +39,23 @@ struct fuse_copy_state {
} ring;
};
+/* fud->fc gets assigned to this value when /dev/fuse is closed */
+#define FUSE_DEV_FC_DISCONNECTED ((struct fuse_conn *) 1)
+
/*
* Lockless access is OK, because fud->fc is set once during mount and is valid
* until the file is released.
+ *
+ * fud->fc is set to FUSE_DEV_FC_DISCONNECTED only after the containing file is
+ * released, so result is safe to dereference in most cases. Exceptions are:
+ * fuse_dev_put() and fuse_fill_super_common().
*/
static inline struct fuse_conn *fuse_dev_fc_get(struct fuse_dev *fud)
{
- /* Pairs with smp_store_release() in fuse_dev_fc_set() */
+ /* Pairs with xchg() in fuse_dev_install() */
return smp_load_acquire(&fud->fc);
}
-static inline void fuse_dev_fc_set(struct fuse_dev *fud, struct fuse_conn *fc)
-{
- /* Pairs with smp_load_acquire() in fuse_dev_fc_get() */
- smp_store_release(&fud->fc, fc);
-}
-
static inline struct fuse_dev *fuse_file_to_fud(struct file *file)
{
return file->private_data;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 94b49384a2f7..339e57a90159 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -577,6 +577,9 @@ struct fuse_pqueue {
* Fuse device instance
*/
struct fuse_dev {
+ /** Reference count of this object */
+ refcount_t ref;
+
/** Issue FUSE_INIT synchronously */
bool sync_init;
@@ -1344,7 +1347,7 @@ void fuse_conn_put(struct fuse_conn *fc);
struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc);
struct fuse_dev *fuse_dev_alloc(void);
void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc);
-void fuse_dev_free(struct fuse_dev *fud);
+void fuse_dev_put(struct fuse_dev *fud);
int fuse_send_init(struct fuse_mount *fm);
/**
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index d34a7fbf849c..fe72ef2a416c 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1626,6 +1626,7 @@ struct fuse_dev *fuse_dev_alloc(void)
if (!fud)
return NULL;
+ refcount_set(&fud->ref, 1);
pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE);
if (!pq) {
kfree(fud);
@@ -1641,9 +1642,26 @@ EXPORT_SYMBOL_GPL(fuse_dev_alloc);
void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc)
{
- fuse_dev_fc_set(fud, fuse_conn_get(fc));
+ struct fuse_conn *old_fc;
+
spin_lock(&fc->lock);
- list_add_tail(&fud->entry, &fc->devices);
+ /*
+ * Pairs with:
+ * - xchg() in fuse_dev_release()
+ * - smp_load_acquire() in fuse_dev_fc_get()
+ */
+ old_fc = cmpxchg(&fud->fc, NULL, fc);
+ if (old_fc) {
+ /*
+ * failed to set fud->fc because
+ * - it was already set to a different fc
+ * - it was set to disconneted
+ */
+ fc->connected = 0;
+ } else {
+ list_add_tail(&fud->entry, &fc->devices);
+ fuse_conn_get(fc);
+ }
spin_unlock(&fc->lock);
}
EXPORT_SYMBOL_GPL(fuse_dev_install);
@@ -1661,11 +1679,16 @@ struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc)
}
EXPORT_SYMBOL_GPL(fuse_dev_alloc_install);
-void fuse_dev_free(struct fuse_dev *fud)
+void fuse_dev_put(struct fuse_dev *fud)
{
- struct fuse_conn *fc = fuse_dev_fc_get(fud);
+ struct fuse_conn *fc;
+
+ if (!refcount_dec_and_test(&fud->ref))
+ return;
- if (fc) {
+ fc = fuse_dev_fc_get(fud);
+ if (fc && fc != FUSE_DEV_FC_DISCONNECTED) {
+ /* This is the virtiofs case (fuse_dev_release() not called) */
spin_lock(&fc->lock);
list_del(&fud->entry);
spin_unlock(&fc->lock);
@@ -1675,7 +1698,7 @@ void fuse_dev_free(struct fuse_dev *fud)
kfree(fud->pq.processing);
kfree(fud);
}
-EXPORT_SYMBOL_GPL(fuse_dev_free);
+EXPORT_SYMBOL_GPL(fuse_dev_put);
static void fuse_fill_attr_from_inode(struct fuse_attr *attr,
const struct fuse_inode *fi)
diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c
index f685916754ad..12300651a0f1 100644
--- a/fs/fuse/virtio_fs.c
+++ b/fs/fuse/virtio_fs.c
@@ -486,7 +486,7 @@ static void virtio_fs_free_devs(struct virtio_fs *fs)
if (!fsvq->fud)
continue;
- fuse_dev_free(fsvq->fud);
+ fuse_dev_put(fsvq->fud);
fsvq->fud = NULL;
}
}