Commit 65de0b89 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull fuse updates from Miklos Szeredi:

 - Improve performance of virtio-fs in mixed read/write workloads

 - Try to revalidate cache before returning EEXIST on exclusive create

 - Add a couple of miscellaneous bug fixes as well as some code cleanups

* tag 'fuse-update-5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse:
  fuse: fix bad inode
  fuse: support SB_NOSEC flag to improve write performance
  fuse: add a flag FUSE_OPEN_KILL_SUIDGID for open() request
  fuse: don't send ATTR_MODE to kill suid/sgid for handle_killpriv_v2
  fuse: setattr should set FATTR_KILL_SUIDGID
  fuse: set FUSE_WRITE_KILL_SUIDGID in cached write path
  fuse: rename FUSE_WRITE_KILL_PRIV to FUSE_WRITE_KILL_SUIDGID
  fuse: introduce the notion of FUSE_HANDLE_KILLPRIV_V2
  fuse: always revalidate if exclusive create
  virtiofs: clean up error handling in virtio_fs_get_tree()
  fuse: add fuse_sb_destroy() helper
  fuse: simplify get_fuse_conn*()
  fuse: get rid of fuse_mount refcount
  virtiofs: simplify sb setup
  virtiofs fix leak in setup
  fuse: launder page should wait for page writeback
parents ff49c86f 5d069dbe
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ struct posix_acl *fuse_get_acl(struct inode *inode, int type)
	void *value = NULL;
	struct posix_acl *acl;

	if (fuse_is_bad(inode))
		return ERR_PTR(-EIO);

	if (!fc->posix_acl || fc->no_getxattr)
		return NULL;

@@ -53,6 +56,9 @@ int fuse_set_acl(struct inode *inode, struct posix_acl *acl, int type)
	const char *name;
	int ret;

	if (fuse_is_bad(inode))
		return -EIO;

	if (!fc->posix_acl || fc->no_setxattr)
		return -EOPNOTSUPP;

+51 −9
Original line number Diff line number Diff line
@@ -202,10 +202,10 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
	int ret;

	inode = d_inode_rcu(entry);
	if (inode && is_bad_inode(inode))
	if (inode && fuse_is_bad(inode))
		goto invalid;
	else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) ||
		 (flags & LOOKUP_REVAL)) {
		 (flags & (LOOKUP_EXCL | LOOKUP_REVAL))) {
		struct fuse_entry_out outarg;
		FUSE_ARGS(args);
		struct fuse_forget_link *forget;
@@ -328,12 +328,11 @@ static struct vfsmount *fuse_dentry_automount(struct path *path)
	if (!fm)
		goto out_put_fsc;

	refcount_set(&fm->count, 1);
	fsc->s_fs_info = fm;
	sb = sget_fc(fsc, NULL, set_anon_super_fc);
	if (IS_ERR(sb)) {
		err = PTR_ERR(sb);
		fuse_mount_put(fm);
		kfree(fm);
		goto out_put_fsc;
	}
	fm->fc = fuse_conn_get(fc);
@@ -463,6 +462,9 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
	bool outarg_valid = true;
	bool locked;

	if (fuse_is_bad(dir))
		return ERR_PTR(-EIO);

	locked = fuse_lock_inode(dir);
	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,
			       &outarg, &inode);
@@ -542,6 +544,12 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
	inarg.flags = flags;
	inarg.mode = mode;
	inarg.umask = current_umask();

	if (fm->fc->handle_killpriv_v2 && (flags & O_TRUNC) &&
	    !(flags & O_EXCL) && !capable(CAP_FSETID)) {
		inarg.open_flags |= FUSE_OPEN_KILL_SUIDGID;
	}

	args.opcode = FUSE_CREATE;
	args.nodeid = get_node_id(dir);
	args.in_numargs = 2;
@@ -606,6 +614,9 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
	struct fuse_conn *fc = get_fuse_conn(dir);
	struct dentry *res = NULL;

	if (fuse_is_bad(dir))
		return -EIO;

	if (d_in_lookup(entry)) {
		res = fuse_lookup(dir, entry, 0);
		if (IS_ERR(res))
@@ -654,6 +665,9 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
	int err;
	struct fuse_forget_link *forget;

	if (fuse_is_bad(dir))
		return -EIO;

	forget = fuse_alloc_forget();
	if (!forget)
		return -ENOMEM;
@@ -781,6 +795,9 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry)
	struct fuse_mount *fm = get_fuse_mount(dir);
	FUSE_ARGS(args);

	if (fuse_is_bad(dir))
		return -EIO;

	args.opcode = FUSE_UNLINK;
	args.nodeid = get_node_id(dir);
	args.in_numargs = 1;
@@ -817,6 +834,9 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry)
	struct fuse_mount *fm = get_fuse_mount(dir);
	FUSE_ARGS(args);

	if (fuse_is_bad(dir))
		return -EIO;

	args.opcode = FUSE_RMDIR;
	args.nodeid = get_node_id(dir);
	args.in_numargs = 1;
@@ -895,6 +915,9 @@ static int fuse_rename2(struct inode *olddir, struct dentry *oldent,
	struct fuse_conn *fc = get_fuse_conn(olddir);
	int err;

	if (fuse_is_bad(olddir))
		return -EIO;

	if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT))
		return -EINVAL;

@@ -1030,7 +1053,7 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
	if (!err) {
		if (fuse_invalid_attr(&outarg.attr) ||
		    (inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
			make_bad_inode(inode);
			fuse_make_bad(inode);
			err = -EIO;
		} else {
			fuse_change_attributes(inode, &outarg.attr,
@@ -1232,6 +1255,9 @@ static int fuse_permission(struct inode *inode, int mask)
	bool refreshed = false;
	int err = 0;

	if (fuse_is_bad(inode))
		return -EIO;

	if (!fuse_allow_current_process(fc))
		return -EACCES;

@@ -1327,7 +1353,7 @@ static const char *fuse_get_link(struct dentry *dentry, struct inode *inode,
	int err;

	err = -EIO;
	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		goto out_err;

	if (fc->cache_symlinks)
@@ -1375,7 +1401,7 @@ static int fuse_dir_fsync(struct file *file, loff_t start, loff_t end,
	struct fuse_conn *fc = get_fuse_conn(inode);
	int err;

	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		return -EIO;

	if (fc->no_fsyncdir)
@@ -1649,10 +1675,20 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
		inarg.valid |= FATTR_FH;
		inarg.fh = ff->fh;
	}

	/* Kill suid/sgid for non-directory chown unconditionally */
	if (fc->handle_killpriv_v2 && !S_ISDIR(inode->i_mode) &&
	    attr->ia_valid & (ATTR_UID | ATTR_GID))
		inarg.valid |= FATTR_KILL_SUIDGID;

	if (attr->ia_valid & ATTR_SIZE) {
		/* For mandatory locking in truncate */
		inarg.valid |= FATTR_LOCKOWNER;
		inarg.lock_owner = fuse_lock_owner_id(fc, current->files);

		/* Kill suid/sgid for truncate only if no CAP_FSETID */
		if (fc->handle_killpriv_v2 && !capable(CAP_FSETID))
			inarg.valid |= FATTR_KILL_SUIDGID;
	}
	fuse_setattr_fill(fc, &args, inode, &inarg, &outarg);
	err = fuse_simple_request(fm, &args);
@@ -1664,7 +1700,7 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,

	if (fuse_invalid_attr(&outarg.attr) ||
	    (inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
		make_bad_inode(inode);
		fuse_make_bad(inode);
		err = -EIO;
		goto error;
	}
@@ -1727,6 +1763,9 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr)
	struct file *file = (attr->ia_valid & ATTR_FILE) ? attr->ia_file : NULL;
	int ret;

	if (fuse_is_bad(inode))
		return -EIO;

	if (!fuse_allow_current_process(get_fuse_conn(inode)))
		return -EACCES;

@@ -1740,7 +1779,7 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr)
		 *
		 * This should be done on write(), truncate() and chown().
		 */
		if (!fc->handle_killpriv) {
		if (!fc->handle_killpriv && !fc->handle_killpriv_v2) {
			/*
			 * ia_mode calculation may have used stale i_mode.
			 * Refresh and recalculate.
@@ -1785,6 +1824,9 @@ static int fuse_getattr(const struct path *path, struct kstat *stat,
	struct inode *inode = d_inode(path->dentry);
	struct fuse_conn *fc = get_fuse_conn(inode);

	if (fuse_is_bad(inode))
		return -EIO;

	if (!fuse_allow_current_process(fc)) {
		if (!request_mask) {
			/*
+31 −10
Original line number Diff line number Diff line
@@ -42,6 +42,12 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
	inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY);
	if (!fm->fc->atomic_o_trunc)
		inarg.flags &= ~O_TRUNC;

	if (fm->fc->handle_killpriv_v2 &&
	    (inarg.flags & O_TRUNC) && !capable(CAP_FSETID)) {
		inarg.open_flags |= FUSE_OPEN_KILL_SUIDGID;
	}

	args.opcode = opcode;
	args.nodeid = nodeid;
	args.in_numargs = 1;
@@ -226,6 +232,9 @@ int fuse_open_common(struct inode *inode, struct file *file, bool isdir)
	bool dax_truncate = (file->f_flags & O_TRUNC) &&
			  fc->atomic_o_trunc && FUSE_IS_DAX(inode);

	if (fuse_is_bad(inode))
		return -EIO;

	err = generic_file_open(inode, file);
	if (err)
		return err;
@@ -463,7 +472,7 @@ static int fuse_flush(struct file *file, fl_owner_t id)
	FUSE_ARGS(args);
	int err;

	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		return -EIO;

	err = write_inode_now(inode, 1);
@@ -535,7 +544,7 @@ static int fuse_fsync(struct file *file, loff_t start, loff_t end,
	struct fuse_conn *fc = get_fuse_conn(inode);
	int err;

	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		return -EIO;

	inode_lock(inode);
@@ -859,7 +868,7 @@ static int fuse_readpage(struct file *file, struct page *page)
	int err;

	err = -EIO;
	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		goto out;

	err = fuse_do_readpage(file, page);
@@ -952,7 +961,7 @@ static void fuse_readahead(struct readahead_control *rac)
	struct fuse_conn *fc = get_fuse_conn(inode);
	unsigned int i, max_pages, nr_pages = 0;

	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		return;

	max_pages = min_t(unsigned int, fc->max_pages,
@@ -1097,6 +1106,8 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia,

	fuse_write_args_fill(ia, ff, pos, count);
	ia->write.in.flags = fuse_write_flags(iocb);
	if (fm->fc->handle_killpriv_v2 && !capable(CAP_FSETID))
		ia->write.in.write_flags |= FUSE_WRITE_KILL_SUIDGID;

	err = fuse_simple_request(fm, &ap->args);
	if (!err && ia->write.out.size > count)
@@ -1260,17 +1271,24 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from)
	ssize_t written_buffered = 0;
	struct inode *inode = mapping->host;
	ssize_t err;
	struct fuse_conn *fc = get_fuse_conn(inode);
	loff_t endbyte = 0;

	if (get_fuse_conn(inode)->writeback_cache) {
	if (fc->writeback_cache) {
		/* Update size (EOF optimization) and mode (SUID clearing) */
		err = fuse_update_attributes(mapping->host, file);
		if (err)
			return err;

		if (fc->handle_killpriv_v2 &&
		    should_remove_suid(file_dentry(file))) {
			goto writethrough;
		}

		return generic_file_write_iter(iocb, from);
	}

writethrough:
	inode_lock(inode);

	/* We can write back this queue in page reclaim */
@@ -1451,7 +1469,7 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,

		if (write) {
			if (!capable(CAP_FSETID))
				ia->write.in.write_flags |= FUSE_WRITE_KILL_PRIV;
				ia->write.in.write_flags |= FUSE_WRITE_KILL_SUIDGID;

			nres = fuse_send_write(ia, pos, nbytes, owner);
		} else {
@@ -1555,7 +1573,7 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
	struct fuse_file *ff = file->private_data;
	struct inode *inode = file_inode(file);

	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		return -EIO;

	if (FUSE_IS_DAX(inode))
@@ -1573,7 +1591,7 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
	struct fuse_file *ff = file->private_data;
	struct inode *inode = file_inode(file);

	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		return -EIO;

	if (FUSE_IS_DAX(inode))
@@ -2172,7 +2190,7 @@ static int fuse_writepages(struct address_space *mapping,
	int err;

	err = -EIO;
	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		goto out;

	data.inode = inode;
@@ -2281,6 +2299,9 @@ static int fuse_launder_page(struct page *page)
	int err = 0;
	if (clear_page_dirty_for_io(page)) {
		struct inode *inode = page->mapping->host;

		/* Serialize with pending writeback for the same page */
		fuse_wait_on_page_writeback(inode, page->index);
		err = fuse_writepage_locked(page);
		if (!err)
			fuse_wait_on_page_writeback(inode, page->index);
@@ -2954,7 +2975,7 @@ long fuse_ioctl_common(struct file *file, unsigned int cmd,
	if (!fuse_allow_current_process(fc))
		return -EACCES;

	if (is_bad_inode(inode))
	if (fuse_is_bad(inode))
		return -EIO;

	return fuse_do_ioctl(file, cmd, arg, flags);
+22 −19
Original line number Diff line number Diff line
@@ -172,6 +172,8 @@ enum {
	FUSE_I_INIT_RDPLUS,
	/** An operation changing file size is in progress  */
	FUSE_I_SIZE_UNSTABLE,
	/* Bad inode */
	FUSE_I_BAD,
};

struct fuse_conn;
@@ -635,6 +637,14 @@ struct fuse_conn {
	/* show legacy mount options */
	unsigned int legacy_opts_show:1;

	/*
	 * fs kills suid/sgid/cap on write/chown/trunc. suid is killed on
	 * write/trunc only if caller did not have CAP_FSETID.  sgid is killed
	 * on write/truncate only if caller did not have CAP_FSETID as well as
	 * file has group execute permission.
	 */
	unsigned handle_killpriv_v2:1;

	/*
	 * The following bitfields are only for optimization purposes
	 * and hence races in setting them will not cause malfunction
@@ -801,9 +811,6 @@ struct fuse_mount {
	/* Underlying (potentially shared) connection to the FUSE server */
	struct fuse_conn *fc;

	/* Refcount */
	refcount_t count;

	/*
	 * Super block for this connection (fc->killsb must be held when
	 * accessing this).
@@ -821,9 +828,7 @@ static inline struct fuse_mount *get_fuse_mount_super(struct super_block *sb)

static inline struct fuse_conn *get_fuse_conn_super(struct super_block *sb)
{
	struct fuse_mount *fm = get_fuse_mount_super(sb);

	return fm ? fm->fc : NULL;
	return get_fuse_mount_super(sb)->fc;
}

static inline struct fuse_mount *get_fuse_mount(struct inode *inode)
@@ -833,9 +838,7 @@ static inline struct fuse_mount *get_fuse_mount(struct inode *inode)

static inline struct fuse_conn *get_fuse_conn(struct inode *inode)
{
	struct fuse_mount *fm = get_fuse_mount(inode);

	return fm ? fm->fc : NULL;
	return get_fuse_mount_super(inode->i_sb)->fc;
}

static inline struct fuse_inode *get_fuse_inode(struct inode *inode)
@@ -858,6 +861,16 @@ static inline u64 fuse_get_attr_version(struct fuse_conn *fc)
	return atomic64_read(&fc->attr_version);
}

static inline void fuse_make_bad(struct inode *inode)
{
	set_bit(FUSE_I_BAD, &get_fuse_inode(inode)->state);
}

static inline bool fuse_is_bad(struct inode *inode)
{
	return unlikely(test_bit(FUSE_I_BAD, &get_fuse_inode(inode)->state));
}

/** Device operations */
extern const struct file_operations fuse_dev_operations;

@@ -1024,16 +1037,6 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
 */
void fuse_conn_put(struct fuse_conn *fc);

/**
 * Acquire reference to fuse_mount
 */
struct fuse_mount *fuse_mount_get(struct fuse_mount *fm);

/**
 * Release reference to fuse_mount
 */
void fuse_mount_put(struct fuse_mount *fm);

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);
+29 −32
Original line number Diff line number Diff line
@@ -132,7 +132,7 @@ static void fuse_evict_inode(struct inode *inode)
			fi->forget = NULL;
		}
	}
	if (S_ISREG(inode->i_mode) && !is_bad_inode(inode)) {
	if (S_ISREG(inode->i_mode) && !fuse_is_bad(inode)) {
		WARN_ON(!list_empty(&fi->write_files));
		WARN_ON(!list_empty(&fi->queued_writes));
	}
@@ -204,6 +204,16 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
		inode->i_mode &= ~S_ISVTX;

	fi->orig_ino = attr->ino;

	/*
	 * We are refreshing inode data and it is possible that another
	 * client set suid/sgid or security.capability xattr. So clear
	 * S_NOSEC. Ideally, we could have cleared it only if suid/sgid
	 * was set or if security.capability xattr was set. But we don't
	 * know if security.capability has been set or not. So clear it
	 * anyway. Its less efficient but should be safe.
	 */
	inode->i_flags &= ~S_NOSEC;
}

void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr,
@@ -342,7 +352,7 @@ retry:
		unlock_new_inode(inode);
	} else if ((inode->i_mode ^ attr->mode) & S_IFMT) {
		/* Inode has changed type, any I/O on the old should fail */
		make_bad_inode(inode);
		fuse_make_bad(inode);
		iput(inode);
		goto retry;
	}
@@ -452,7 +462,8 @@ static void fuse_put_super(struct super_block *sb)
{
	struct fuse_mount *fm = get_fuse_mount_super(sb);

	fuse_mount_put(fm);
	fuse_conn_put(fm->fc);
	kfree(fm);
}

static void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr)
@@ -705,7 +716,6 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
	INIT_LIST_HEAD(&fc->mounts);
	list_add(&fm->fc_entry, &fc->mounts);
	fm->fc = fc;
	refcount_set(&fm->count, 1);
}
EXPORT_SYMBOL_GPL(fuse_conn_init);

@@ -732,23 +742,6 @@ struct fuse_conn *fuse_conn_get(struct fuse_conn *fc)
}
EXPORT_SYMBOL_GPL(fuse_conn_get);

void fuse_mount_put(struct fuse_mount *fm)
{
	if (refcount_dec_and_test(&fm->count)) {
		if (fm->fc)
			fuse_conn_put(fm->fc);
		kfree(fm);
	}
}
EXPORT_SYMBOL_GPL(fuse_mount_put);

struct fuse_mount *fuse_mount_get(struct fuse_mount *fm)
{
	refcount_inc(&fm->count);
	return fm;
}
EXPORT_SYMBOL_GPL(fuse_mount_get);

static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned mode)
{
	struct fuse_attr attr;
@@ -1055,6 +1048,10 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
			    !fuse_dax_check_alignment(fc, arg->map_alignment)) {
				ok = false;
			}
			if (arg->flags & FUSE_HANDLE_KILLPRIV_V2) {
				fc->handle_killpriv_v2 = 1;
				fm->sb->s_flags |= SB_NOSEC;
			}
		} else {
			ra_pages = fc->max_read / PAGE_SIZE;
			fc->no_lock = 1;
@@ -1097,7 +1094,8 @@ void fuse_send_init(struct fuse_mount *fm)
		FUSE_WRITEBACK_CACHE | FUSE_NO_OPEN_SUPPORT |
		FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL |
		FUSE_ABORT_ERROR | FUSE_MAX_PAGES | FUSE_CACHE_SYMLINKS |
		FUSE_NO_OPENDIR_SUPPORT | FUSE_EXPLICIT_INVAL_DATA;
		FUSE_NO_OPENDIR_SUPPORT | FUSE_EXPLICIT_INVAL_DATA |
		FUSE_HANDLE_KILLPRIV_V2;
#ifdef CONFIG_FUSE_DAX
	if (fm->fc->dax)
		ia->in.flags |= FUSE_MAP_ALIGNMENT;
@@ -1465,7 +1463,8 @@ static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
	return 0;

 err_put_conn:
	fuse_mount_put(fm);
	fuse_conn_put(fc);
	kfree(fm);
	sb->s_fs_info = NULL;
 err_fput:
	fput(file);
@@ -1557,7 +1556,7 @@ void fuse_conn_destroy(struct fuse_mount *fm)
}
EXPORT_SYMBOL_GPL(fuse_conn_destroy);

static void fuse_kill_sb_anon(struct super_block *sb)
static void fuse_sb_destroy(struct super_block *sb)
{
	struct fuse_mount *fm = get_fuse_mount_super(sb);
	bool last;
@@ -1567,6 +1566,11 @@ static void fuse_kill_sb_anon(struct super_block *sb)
		if (last)
			fuse_conn_destroy(fm);
	}
}

static void fuse_kill_sb_anon(struct super_block *sb)
{
	fuse_sb_destroy(sb);
	kill_anon_super(sb);
}

@@ -1583,14 +1587,7 @@ MODULE_ALIAS_FS("fuse");
#ifdef CONFIG_BLOCK
static void fuse_kill_sb_blk(struct super_block *sb)
{
	struct fuse_mount *fm = get_fuse_mount_super(sb);
	bool last;

	if (fm) {
		last = fuse_mount_remove(fm);
		if (last)
			fuse_conn_destroy(fm);
	}
	fuse_sb_destroy(sb);
	kill_block_super(sb);
}

Loading