mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
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>
This commit is contained in:
parent
a8dd5f1b73
commit
e9bf38500e
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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 (fc) {
|
||||
if (!refcount_dec_and_test(&fud->ref))
|
||||
return;
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user