Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1e9a4ed9 authored by Miklos Szeredi's avatar Miklos Szeredi Committed by Linus Torvalds
Browse files

[PATCH] FUSE - mount options



This patch adds miscellaneous mount options to the FUSE filesystem.

The following mount options are added:

 o default_permissions:  check permissions with generic_permission()
 o allow_other:          allow other users to access files
 o allow_root:           allow root to access files
 o kernel_cache:         don't invalidate page cache on open

Signed-off-by: default avatarMiklos Szeredi <miklos@szeredi.hu>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent b6aeaded
Loading
Loading
Loading
Loading
+59 −19
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ static inline struct fuse_conn *fuse_get_conn(struct file *file)
	struct fuse_conn *fc;
	spin_lock(&fuse_lock);
	fc = file->private_data;
	if (fc && !fc->sb)
	if (fc && !fc->mounted)
		fc = NULL;
	spin_unlock(&fuse_lock);
	return fc;
@@ -148,6 +148,17 @@ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req)
		fuse_putback_request(fc, req);
}

void fuse_release_background(struct fuse_req *req)
{
	iput(req->inode);
	iput(req->inode2);
	if (req->file)
		fput(req->file);
	spin_lock(&fuse_lock);
	list_del(&req->bg_entry);
	spin_unlock(&fuse_lock);
}

/*
 * This function is called when a request is finished.  Either a reply
 * has arrived or it was interrupted (and not yet sent) or some error
@@ -166,12 +177,10 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
	putback = atomic_dec_and_test(&req->count);
	spin_unlock(&fuse_lock);
	if (req->background) {
		if (req->inode)
			iput(req->inode);
		if (req->inode2)
			iput(req->inode2);
		if (req->file)
			fput(req->file);
		down_read(&fc->sbput_sem);
		if (fc->mounted)
			fuse_release_background(req);
		up_read(&fc->sbput_sem);
	}
	wake_up(&req->waitq);
	if (req->in.h.opcode == FUSE_INIT) {
@@ -191,11 +200,39 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
		fuse_putback_request(fc, req);
}

static void background_request(struct fuse_req *req)
/*
 * Unfortunately request interruption not just solves the deadlock
 * problem, it causes problems too.  These stem from the fact, that an
 * interrupted request is continued to be processed in userspace,
 * while all the locks and object references (inode and file) held
 * during the operation are released.
 *
 * To release the locks is exactly why there's a need to interrupt the
 * request, so there's not a lot that can be done about this, except
 * introduce additional locking in userspace.
 *
 * More important is to keep inode and file references until userspace
 * has replied, otherwise FORGET and RELEASE could be sent while the
 * inode/file is still used by the filesystem.
 *
 * For this reason the concept of "background" request is introduced.
 * An interrupted request is backgrounded if it has been already sent
 * to userspace.  Backgrounding involves getting an extra reference to
 * inode(s) or file used in the request, and adding the request to
 * fc->background list.  When a reply is received for a background
 * request, the object references are released, and the request is
 * removed from the list.  If the filesystem is unmounted while there
 * are still background requests, the list is walked and references
 * are released as if a reply was received.
 *
 * There's one more use for a background request.  The RELEASE message is
 * always sent as background, since it doesn't return an error or
 * data.
 */
static void background_request(struct fuse_conn *fc, struct fuse_req *req)
{
	/* Need to get hold of the inode(s) and/or file used in the
	   request, so FORGET and RELEASE are not sent too early */
	req->background = 1;
	list_add(&req->bg_entry, &fc->background);
	if (req->inode)
		req->inode = igrab(req->inode);
	if (req->inode2)
@@ -215,7 +252,8 @@ static int request_wait_answer_nonint(struct fuse_req *req)
}

/* Called with fuse_lock held.  Releases, and then reacquires it. */
static void request_wait_answer(struct fuse_req *req, int interruptible)
static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req,
				int interruptible)
{
	int intr;

@@ -255,7 +293,7 @@ static void request_wait_answer(struct fuse_req *req, int interruptible)
		list_del(&req->list);
		__fuse_put_request(req);
	} else if (!req->finished && req->sent)
		background_request(req);
		background_request(fc, req);
}

static unsigned len_args(unsigned numargs, struct fuse_arg *args)
@@ -297,7 +335,7 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req,
{
	req->isreply = 1;
	spin_lock(&fuse_lock);
	if (!fc->file)
	if (!fc->connected)
		req->out.h.error = -ENOTCONN;
	else if (fc->conn_error)
		req->out.h.error = -ECONNREFUSED;
@@ -307,7 +345,7 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req,
		   after request_end() */
		__fuse_get_request(req);

		request_wait_answer(req, interruptible);
		request_wait_answer(fc, req, interruptible);
	}
	spin_unlock(&fuse_lock);
}
@@ -330,7 +368,7 @@ void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req)
static void request_send_nowait(struct fuse_conn *fc, struct fuse_req *req)
{
	spin_lock(&fuse_lock);
	if (fc->file) {
	if (fc->connected) {
		queue_request(fc, req);
		spin_unlock(&fuse_lock);
	} else {
@@ -348,7 +386,9 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req)
void request_send_background(struct fuse_conn *fc, struct fuse_req *req)
{
	req->isreply = 1;
	background_request(req);
	spin_lock(&fuse_lock);
	background_request(fc, req);
	spin_unlock(&fuse_lock);
	request_send_nowait(fc, req);
}

@@ -583,7 +623,7 @@ static void request_wait(struct fuse_conn *fc)
	DECLARE_WAITQUEUE(wait, current);

	add_wait_queue_exclusive(&fc->waitq, &wait);
	while (fc->sb && list_empty(&fc->pending)) {
	while (fc->mounted && list_empty(&fc->pending)) {
		set_current_state(TASK_INTERRUPTIBLE);
		if (signal_pending(current))
			break;
@@ -622,7 +662,7 @@ static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov,
		goto err_unlock;
	request_wait(fc);
	err = -ENODEV;
	if (!fc->sb)
	if (!fc->mounted)
		goto err_unlock;
	err = -ERESTARTSYS;
	if (list_empty(&fc->pending))
@@ -839,7 +879,7 @@ static int fuse_dev_release(struct inode *inode, struct file *file)
	spin_lock(&fuse_lock);
	fc = file->private_data;
	if (fc) {
		fc->file = NULL;
		fc->connected = 0;
		end_requests(fc, &fc->pending);
		end_requests(fc, &fc->processing);
		fuse_release_conn(fc);
+32 −3
Original line number Diff line number Diff line
@@ -418,7 +418,8 @@ static int fuse_revalidate(struct dentry *entry)
	struct fuse_conn *fc = get_fuse_conn(inode);

	if (get_node_id(inode) == FUSE_ROOT_ID) {
		if (current->fsuid != fc->user_id)
		if (!(fc->flags & FUSE_ALLOW_OTHER) &&
		    current->fsuid != fc->user_id)
			return -EACCES;
	} else if (time_before_eq(jiffies, fi->i_time))
		return 0;
@@ -430,9 +431,31 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd)
{
	struct fuse_conn *fc = get_fuse_conn(inode);

	if (current->fsuid != fc->user_id)
	if (!(fc->flags & FUSE_ALLOW_OTHER) && current->fsuid != fc->user_id)
		return -EACCES;
	else {
	else if (fc->flags & FUSE_DEFAULT_PERMISSIONS) {
		int err = generic_permission(inode, mask, NULL);

		/* If permission is denied, try to refresh file
		   attributes.  This is also needed, because the root
		   node will at first have no permissions */
		if (err == -EACCES) {
		 	err = fuse_do_getattr(inode);
			if (!err)
				err = generic_permission(inode, mask, NULL);
		}

		/* FIXME: Need some mechanism to revoke permissions:
		   currently if the filesystem suddenly changes the
		   file mode, we will not be informed about it, and
		   continue to allow access to the file/directory.

		   This is actually not so grave, since the user can
		   simply keep access to the file/directory anyway by
		   keeping it open... */

		return err;
	} else {
		int mode = inode->i_mode;
		if ((mask & MAY_WRITE) && IS_RDONLY(inode) &&
                    (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))
@@ -636,6 +659,12 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr)
	int err;
	int is_truncate = 0;

	if (fc->flags & FUSE_DEFAULT_PERMISSIONS) {
		err = inode_change_ok(inode, attr);
		if (err)
			return err;
	}

	if (attr->ia_valid & ATTR_SIZE) {
		unsigned long limit;
		is_truncate = 1;
+1 −1
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ static int fuse_open(struct inode *inode, struct file *file)
	else
		request_send(fc, req);
	err = req->out.h.error;
	if (!err)
	if (!err && !(fc->flags & FUSE_KERNEL_CACHE))
		invalidate_inode_pages(inode->i_mapping);
	if (err) {
		fuse_request_free(ff->release_req);
+40 −5
Original line number Diff line number Diff line
@@ -21,6 +21,19 @@
/** If more requests are outstanding, then the operation will block */
#define FUSE_MAX_OUTSTANDING 10

/** If the FUSE_DEFAULT_PERMISSIONS flag is given, the filesystem
    module will check permissions based on the file mode.  Otherwise no
    permission checking is done in the kernel */
#define FUSE_DEFAULT_PERMISSIONS (1 << 0)

/** If the FUSE_ALLOW_OTHER flag is given, then not only the user
    doing the mount will be allowed to access the filesystem */
#define FUSE_ALLOW_OTHER         (1 << 1)

/** If the FUSE_KERNEL_CACHE flag is given, then cached data will not
    be flushed on open */
#define FUSE_KERNEL_CACHE        (1 << 2)

/** FUSE inode */
struct fuse_inode {
	/** Inode data */
@@ -109,6 +122,9 @@ struct fuse_req {
	    lists in fuse_conn */
	struct list_head list;

	/** Entry on the background list */
	struct list_head bg_entry;

	/** refcount */
	atomic_t count;

@@ -176,15 +192,15 @@ struct fuse_req {
 * unmounted.
 */
struct fuse_conn {
	/** The superblock of the mounted filesystem */
	struct super_block *sb;

	/** The opened client device */
	struct file *file;
	/** Reference count */
	int count;

	/** The user id for this mount */
	uid_t user_id;

	/** The fuse mount flags for this mount */
	unsigned flags;

	/** Readers of the connection are waiting on this */
	wait_queue_head_t waitq;

@@ -194,6 +210,10 @@ struct fuse_conn {
	/** The list of requests being processed */
	struct list_head processing;

	/** Requests put in the background (RELEASE or any other
	    interrupted request) */
	struct list_head background;

	/** Controls the maximum number of outstanding requests */
	struct semaphore outstanding_sem;

@@ -201,12 +221,21 @@ struct fuse_conn {
	    outstanding_sem would go negative */
	unsigned outstanding_debt;

	/** RW semaphore for exclusion with fuse_put_super() */
	struct rw_semaphore sbput_sem;

	/** The list of unused requests */
	struct list_head unused_list;

	/** The next unique request id */
	u64 reqctr;

	/** Mount is active */
	unsigned mounted : 1;

	/** Connection established */
	unsigned connected : 1;

	/** Connection failed (version mismatch) */
	unsigned conn_error : 1;

@@ -261,6 +290,7 @@ extern struct file_operations fuse_dev_operations;
 *  - the private_data field of the device file
 *  - the s_fs_info field of the super block
 *  - unused_list, pending, processing lists in fuse_conn
 *  - background list in fuse_conn
 *  - the unique request ID counter reqctr in fuse_conn
 *  - the sb (super_block) field in fuse_conn
 *  - the file (device file) field in fuse_conn
@@ -371,6 +401,11 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req);
 */
void request_send_background(struct fuse_conn *fc, struct fuse_req *req);

/**
 * Release inodes and file assiciated with background request
 */
void fuse_release_background(struct fuse_req *req);

/**
 * Get the attributes of a file
 */
+38 −39
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@
#include <linux/seq_file.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/parser.h>
#include <linux/statfs.h>

@@ -25,11 +24,6 @@ MODULE_LICENSE("GPL");

spinlock_t fuse_lock;
static kmem_cache_t *fuse_inode_cachep;
static int mount_count;

static int mount_max = 1000;
module_param(mount_max, int, 0644);
MODULE_PARM_DESC(mount_max, "Maximum number of FUSE mounts allowed, if -1 then unlimited (default: 1000)");

#define FUSE_SUPER_MAGIC 0x65735546

@@ -37,6 +31,7 @@ struct fuse_mount_data {
	int fd;
	unsigned rootmode;
	unsigned user_id;
	unsigned flags;
};

static struct inode *fuse_alloc_inode(struct super_block *sb)
@@ -89,8 +84,8 @@ void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req,

static void fuse_clear_inode(struct inode *inode)
{
	if (inode->i_sb->s_flags & MS_ACTIVE) {
		struct fuse_conn *fc = get_fuse_conn(inode);
	if (fc) {
		struct fuse_inode *fi = get_fuse_inode(inode);
		fuse_send_forget(fc, fi->forget_req, fi->nodeid, fi->nlookup);
		fi->forget_req = NULL;
@@ -195,14 +190,19 @@ static void fuse_put_super(struct super_block *sb)
{
	struct fuse_conn *fc = get_fuse_conn_super(sb);

	down_write(&fc->sbput_sem);
	while (!list_empty(&fc->background))
		fuse_release_background(list_entry(fc->background.next,
						   struct fuse_req, bg_entry));

	spin_lock(&fuse_lock);
	mount_count --;
	fc->sb = NULL;
	fc->mounted = 0;
	fc->user_id = 0;
	fc->flags = 0;
	/* Flush all readers on this fs */
	wake_up_all(&fc->waitq);
	up_write(&fc->sbput_sem);
	fuse_release_conn(fc);
	*get_fuse_conn_super_p(sb) = NULL;
	spin_unlock(&fuse_lock);
}

@@ -249,7 +249,6 @@ enum {
	OPT_USER_ID,
	OPT_DEFAULT_PERMISSIONS,
	OPT_ALLOW_OTHER,
	OPT_ALLOW_ROOT,
	OPT_KERNEL_CACHE,
	OPT_ERR
};
@@ -260,7 +259,6 @@ static match_table_t tokens = {
	{OPT_USER_ID,			"user_id=%u"},
	{OPT_DEFAULT_PERMISSIONS,	"default_permissions"},
	{OPT_ALLOW_OTHER,		"allow_other"},
	{OPT_ALLOW_ROOT,		"allow_root"},
	{OPT_KERNEL_CACHE,		"kernel_cache"},
	{OPT_ERR,			NULL}
};
@@ -298,6 +296,18 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d)
			d->user_id = value;
			break;

		case OPT_DEFAULT_PERMISSIONS:
			d->flags |= FUSE_DEFAULT_PERMISSIONS;
			break;

		case OPT_ALLOW_OTHER:
			d->flags |= FUSE_ALLOW_OTHER;
			break;

		case OPT_KERNEL_CACHE:
			d->flags |= FUSE_KERNEL_CACHE;
			break;

		default:
			return 0;
		}
@@ -313,6 +323,12 @@ static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt)
	struct fuse_conn *fc = get_fuse_conn_super(mnt->mnt_sb);

	seq_printf(m, ",user_id=%u", fc->user_id);
	if (fc->flags & FUSE_DEFAULT_PERMISSIONS)
		seq_puts(m, ",default_permissions");
	if (fc->flags & FUSE_ALLOW_OTHER)
		seq_puts(m, ",allow_other");
	if (fc->flags & FUSE_KERNEL_CACHE)
		seq_puts(m, ",kernel_cache");
	return 0;
}

@@ -330,7 +346,8 @@ static void free_conn(struct fuse_conn *fc)
/* Must be called with the fuse lock held */
void fuse_release_conn(struct fuse_conn *fc)
{
	if (!fc->sb && !fc->file)
	fc->count--;
	if (!fc->count)
		free_conn(fc);
}

@@ -342,14 +359,13 @@ static struct fuse_conn *new_conn(void)
	if (fc != NULL) {
		int i;
		memset(fc, 0, sizeof(*fc));
		fc->sb = NULL;
		fc->file = NULL;
		fc->user_id = 0;
		init_waitqueue_head(&fc->waitq);
		INIT_LIST_HEAD(&fc->pending);
		INIT_LIST_HEAD(&fc->processing);
		INIT_LIST_HEAD(&fc->unused_list);
		INIT_LIST_HEAD(&fc->background);
		sema_init(&fc->outstanding_sem, 0);
		init_rwsem(&fc->sbput_sem);
		for (i = 0; i < FUSE_MAX_OUTSTANDING; i++) {
			struct fuse_req *req = fuse_request_alloc();
			if (!req) {
@@ -380,8 +396,10 @@ static struct fuse_conn *get_conn(struct file *file, struct super_block *sb)
		fc = ERR_PTR(-EINVAL);
	} else {
		file->private_data = fc;
		fc->sb = sb;
		fc->file = file;
		*get_fuse_conn_super_p(sb) = fc;
		fc->mounted = 1;
		fc->connected = 1;
		fc->count = 2;
	}
	spin_unlock(&fuse_lock);
	return fc;
@@ -407,17 +425,6 @@ static struct super_operations fuse_super_operations = {
	.show_options	= fuse_show_options,
};

static int inc_mount_count(void)
{
	int success = 0;
	spin_lock(&fuse_lock);
	mount_count ++;
	if (mount_max == -1 || mount_count <= mount_max)
		success = 1;
	spin_unlock(&fuse_lock);
	return success;
}

static int fuse_fill_super(struct super_block *sb, void *data, int silent)
{
	struct fuse_conn *fc;
@@ -444,14 +451,9 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)
	if (IS_ERR(fc))
		return PTR_ERR(fc);

	fc->flags = d.flags;
	fc->user_id = d.user_id;

	*get_fuse_conn_super_p(sb) = fc;

	err = -ENFILE;
	if (!inc_mount_count() && current->uid != 0)
		goto err;

	err = -ENOMEM;
	root = get_root_inode(sb, d.rootmode);
	if (root == NULL)
@@ -467,11 +469,8 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)

 err:
	spin_lock(&fuse_lock);
	mount_count --;
	fc->sb = NULL;
	fuse_release_conn(fc);
	spin_unlock(&fuse_lock);
	*get_fuse_conn_super_p(sb) = NULL;
	return err;
}