[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: Miklos Szeredi <miklos@szeredi.hu>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index e8f3170..ca6fc0e 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -26,7 +26,7 @@
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 @@
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 @@
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 @@
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 @@
}
/* 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 @@
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 @@
{
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 @@
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 @@
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_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 @@
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 @@
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 @@
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);
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 8adc1ee..0950455 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -418,7 +418,8 @@
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 @@
{
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 @@
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;
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index de8c9c7..96ea302 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -70,7 +70,7 @@
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);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index b4aa8f7..c8e6c87 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -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 @@
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 @@
* 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 @@
/** 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 @@
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 @@
* - 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
@@ -372,6 +402,11 @@
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
*/
int fuse_do_getattr(struct inode *inode);
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index f229d69..458c62c 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -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 @@
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 @@
int fd;
unsigned rootmode;
unsigned user_id;
+ unsigned flags;
};
static struct inode *fuse_alloc_inode(struct super_block *sb)
@@ -89,8 +84,8 @@
static void fuse_clear_inode(struct inode *inode)
{
- struct fuse_conn *fc = get_fuse_conn(inode);
- if (fc) {
+ if (inode->i_sb->s_flags & MS_ACTIVE) {
+ struct fuse_conn *fc = get_fuse_conn(inode);
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 @@
{
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 @@
OPT_USER_ID,
OPT_DEFAULT_PERMISSIONS,
OPT_ALLOW_OTHER,
- OPT_ALLOW_ROOT,
OPT_KERNEL_CACHE,
OPT_ERR
};
@@ -260,7 +259,6 @@
{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 @@
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 @@
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 @@
/* 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 @@
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 @@
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 @@
.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 @@
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 @@
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;
}