Commit e9bf3850 authored by Miklos Szeredi's avatar Miklos Szeredi
Browse files

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: default avatarMiklos Szeredi <mszeredi@redhat.com>
parent a8dd5f1b
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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;
+7 −2
Original line number Diff line number Diff line
@@ -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);
+8 −7
Original line number Diff line number Diff line
@@ -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;
+4 −1
Original line number Diff line number Diff line
@@ -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);

/**
+29 −6
Original line number Diff line number Diff line
@@ -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);
	/*
	 * 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)
Loading