Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
7
fs/fuse/Makefile
Normal file
7
fs/fuse/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# Makefile for the FUSE filesystem.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_FUSE_FS) += fuse.o
|
||||
|
||||
fuse-objs := dev.o dir.o file.o inode.o control.o
|
||||
222
fs/fuse/control.c
Normal file
222
fs/fuse/control.c
Normal file
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include "fuse_i.h"
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#define FUSE_CTL_SUPER_MAGIC 0x65735543
|
||||
|
||||
/*
|
||||
* This is non-NULL when the single instance of the control filesystem
|
||||
* exists. Protected by fuse_mutex
|
||||
*/
|
||||
static struct super_block *fuse_control_sb;
|
||||
|
||||
static struct fuse_conn *fuse_ctl_file_conn_get(struct file *file)
|
||||
{
|
||||
struct fuse_conn *fc;
|
||||
mutex_lock(&fuse_mutex);
|
||||
fc = file->f_path.dentry->d_inode->i_private;
|
||||
if (fc)
|
||||
fc = fuse_conn_get(fc);
|
||||
mutex_unlock(&fuse_mutex);
|
||||
return fc;
|
||||
}
|
||||
|
||||
static ssize_t fuse_conn_abort_write(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct fuse_conn *fc = fuse_ctl_file_conn_get(file);
|
||||
if (fc) {
|
||||
fuse_abort_conn(fc);
|
||||
fuse_conn_put(fc);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t fuse_conn_waiting_read(struct file *file, char __user *buf,
|
||||
size_t len, loff_t *ppos)
|
||||
{
|
||||
char tmp[32];
|
||||
size_t size;
|
||||
|
||||
if (!*ppos) {
|
||||
struct fuse_conn *fc = fuse_ctl_file_conn_get(file);
|
||||
if (!fc)
|
||||
return 0;
|
||||
|
||||
file->private_data=(void *)(long)atomic_read(&fc->num_waiting);
|
||||
fuse_conn_put(fc);
|
||||
}
|
||||
size = sprintf(tmp, "%ld\n", (long)file->private_data);
|
||||
return simple_read_from_buffer(buf, len, ppos, tmp, size);
|
||||
}
|
||||
|
||||
static const struct file_operations fuse_ctl_abort_ops = {
|
||||
.open = nonseekable_open,
|
||||
.write = fuse_conn_abort_write,
|
||||
};
|
||||
|
||||
static const struct file_operations fuse_ctl_waiting_ops = {
|
||||
.open = nonseekable_open,
|
||||
.read = fuse_conn_waiting_read,
|
||||
};
|
||||
|
||||
static struct dentry *fuse_ctl_add_dentry(struct dentry *parent,
|
||||
struct fuse_conn *fc,
|
||||
const char *name,
|
||||
int mode, int nlink,
|
||||
const struct inode_operations *iop,
|
||||
const struct file_operations *fop)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
struct inode *inode;
|
||||
|
||||
BUG_ON(fc->ctl_ndents >= FUSE_CTL_NUM_DENTRIES);
|
||||
dentry = d_alloc_name(parent, name);
|
||||
if (!dentry)
|
||||
return NULL;
|
||||
|
||||
fc->ctl_dentry[fc->ctl_ndents++] = dentry;
|
||||
inode = new_inode(fuse_control_sb);
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
inode->i_mode = mode;
|
||||
inode->i_uid = fc->user_id;
|
||||
inode->i_gid = fc->group_id;
|
||||
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
|
||||
/* setting ->i_op to NULL is not allowed */
|
||||
if (iop)
|
||||
inode->i_op = iop;
|
||||
inode->i_fop = fop;
|
||||
inode->i_nlink = nlink;
|
||||
inode->i_private = fc;
|
||||
d_add(dentry, inode);
|
||||
return dentry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a connection to the control filesystem (if it exists). Caller
|
||||
* must hold fuse_mutex
|
||||
*/
|
||||
int fuse_ctl_add_conn(struct fuse_conn *fc)
|
||||
{
|
||||
struct dentry *parent;
|
||||
char name[32];
|
||||
|
||||
if (!fuse_control_sb)
|
||||
return 0;
|
||||
|
||||
parent = fuse_control_sb->s_root;
|
||||
inc_nlink(parent->d_inode);
|
||||
sprintf(name, "%llu", (unsigned long long) fc->id);
|
||||
parent = fuse_ctl_add_dentry(parent, fc, name, S_IFDIR | 0500, 2,
|
||||
&simple_dir_inode_operations,
|
||||
&simple_dir_operations);
|
||||
if (!parent)
|
||||
goto err;
|
||||
|
||||
if (!fuse_ctl_add_dentry(parent, fc, "waiting", S_IFREG | 0400, 1,
|
||||
NULL, &fuse_ctl_waiting_ops) ||
|
||||
!fuse_ctl_add_dentry(parent, fc, "abort", S_IFREG | 0200, 1,
|
||||
NULL, &fuse_ctl_abort_ops))
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
fuse_ctl_remove_conn(fc);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a connection from the control filesystem (if it exists).
|
||||
* Caller must hold fuse_mutex
|
||||
*/
|
||||
void fuse_ctl_remove_conn(struct fuse_conn *fc)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!fuse_control_sb)
|
||||
return;
|
||||
|
||||
for (i = fc->ctl_ndents - 1; i >= 0; i--) {
|
||||
struct dentry *dentry = fc->ctl_dentry[i];
|
||||
dentry->d_inode->i_private = NULL;
|
||||
d_drop(dentry);
|
||||
dput(dentry);
|
||||
}
|
||||
fuse_control_sb->s_root->d_inode->i_nlink--;
|
||||
}
|
||||
|
||||
static int fuse_ctl_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct tree_descr empty_descr = {""};
|
||||
struct fuse_conn *fc;
|
||||
int err;
|
||||
|
||||
err = simple_fill_super(sb, FUSE_CTL_SUPER_MAGIC, &empty_descr);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mutex_lock(&fuse_mutex);
|
||||
BUG_ON(fuse_control_sb);
|
||||
fuse_control_sb = sb;
|
||||
list_for_each_entry(fc, &fuse_conn_list, entry) {
|
||||
err = fuse_ctl_add_conn(fc);
|
||||
if (err) {
|
||||
fuse_control_sb = NULL;
|
||||
mutex_unlock(&fuse_mutex);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&fuse_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fuse_ctl_get_sb(struct file_system_type *fs_type, int flags,
|
||||
const char *dev_name, void *raw_data,
|
||||
struct vfsmount *mnt)
|
||||
{
|
||||
return get_sb_single(fs_type, flags, raw_data,
|
||||
fuse_ctl_fill_super, mnt);
|
||||
}
|
||||
|
||||
static void fuse_ctl_kill_sb(struct super_block *sb)
|
||||
{
|
||||
struct fuse_conn *fc;
|
||||
|
||||
mutex_lock(&fuse_mutex);
|
||||
fuse_control_sb = NULL;
|
||||
list_for_each_entry(fc, &fuse_conn_list, entry)
|
||||
fc->ctl_ndents = 0;
|
||||
mutex_unlock(&fuse_mutex);
|
||||
|
||||
kill_litter_super(sb);
|
||||
}
|
||||
|
||||
static struct file_system_type fuse_ctl_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "fusectl",
|
||||
.get_sb = fuse_ctl_get_sb,
|
||||
.kill_sb = fuse_ctl_kill_sb,
|
||||
};
|
||||
|
||||
int __init fuse_ctl_init(void)
|
||||
{
|
||||
return register_filesystem(&fuse_ctl_fs_type);
|
||||
}
|
||||
|
||||
void fuse_ctl_cleanup(void)
|
||||
{
|
||||
unregister_filesystem(&fuse_ctl_fs_type);
|
||||
}
|
||||
1067
fs/fuse/dev.c
Normal file
1067
fs/fuse/dev.c
Normal file
File diff suppressed because it is too large
Load Diff
1310
fs/fuse/dir.c
Normal file
1310
fs/fuse/dir.c
Normal file
File diff suppressed because it is too large
Load Diff
831
fs/fuse/file.c
Normal file
831
fs/fuse/file.c
Normal file
@@ -0,0 +1,831 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include "fuse_i.h"
|
||||
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
static const struct file_operations fuse_direct_io_file_operations;
|
||||
|
||||
static int fuse_send_open(struct inode *inode, struct file *file, int isdir,
|
||||
struct fuse_open_out *outargp)
|
||||
{
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_open_in inarg;
|
||||
struct fuse_req *req;
|
||||
int err;
|
||||
|
||||
req = fuse_get_req(fc);
|
||||
if (IS_ERR(req))
|
||||
return PTR_ERR(req);
|
||||
|
||||
memset(&inarg, 0, sizeof(inarg));
|
||||
inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
|
||||
req->in.h.opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN;
|
||||
req->in.h.nodeid = get_node_id(inode);
|
||||
req->in.numargs = 1;
|
||||
req->in.args[0].size = sizeof(inarg);
|
||||
req->in.args[0].value = &inarg;
|
||||
req->out.numargs = 1;
|
||||
req->out.args[0].size = sizeof(*outargp);
|
||||
req->out.args[0].value = outargp;
|
||||
request_send(fc, req);
|
||||
err = req->out.h.error;
|
||||
fuse_put_request(fc, req);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
struct fuse_file *fuse_file_alloc(void)
|
||||
{
|
||||
struct fuse_file *ff;
|
||||
ff = kmalloc(sizeof(struct fuse_file), GFP_KERNEL);
|
||||
if (ff) {
|
||||
ff->reserved_req = fuse_request_alloc();
|
||||
if (!ff->reserved_req) {
|
||||
kfree(ff);
|
||||
ff = NULL;
|
||||
}
|
||||
}
|
||||
return ff;
|
||||
}
|
||||
|
||||
void fuse_file_free(struct fuse_file *ff)
|
||||
{
|
||||
fuse_request_free(ff->reserved_req);
|
||||
kfree(ff);
|
||||
}
|
||||
|
||||
void fuse_finish_open(struct inode *inode, struct file *file,
|
||||
struct fuse_file *ff, struct fuse_open_out *outarg)
|
||||
{
|
||||
if (outarg->open_flags & FOPEN_DIRECT_IO)
|
||||
file->f_op = &fuse_direct_io_file_operations;
|
||||
if (!(outarg->open_flags & FOPEN_KEEP_CACHE))
|
||||
invalidate_mapping_pages(inode->i_mapping, 0, -1);
|
||||
ff->fh = outarg->fh;
|
||||
file->private_data = ff;
|
||||
}
|
||||
|
||||
int fuse_open_common(struct inode *inode, struct file *file, int isdir)
|
||||
{
|
||||
struct fuse_open_out outarg;
|
||||
struct fuse_file *ff;
|
||||
int err;
|
||||
|
||||
/* VFS checks this, but only _after_ ->open() */
|
||||
if (file->f_flags & O_DIRECT)
|
||||
return -EINVAL;
|
||||
|
||||
err = generic_file_open(inode, file);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* If opening the root node, no lookup has been performed on
|
||||
it, so the attributes must be refreshed */
|
||||
if (get_node_id(inode) == FUSE_ROOT_ID) {
|
||||
err = fuse_do_getattr(inode);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
ff = fuse_file_alloc();
|
||||
if (!ff)
|
||||
return -ENOMEM;
|
||||
|
||||
err = fuse_send_open(inode, file, isdir, &outarg);
|
||||
if (err)
|
||||
fuse_file_free(ff);
|
||||
else {
|
||||
if (isdir)
|
||||
outarg.open_flags &= ~FOPEN_DIRECT_IO;
|
||||
fuse_finish_open(inode, file, ff, &outarg);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
struct fuse_req *fuse_release_fill(struct fuse_file *ff, u64 nodeid, int flags,
|
||||
int opcode)
|
||||
{
|
||||
struct fuse_req *req = ff->reserved_req;
|
||||
struct fuse_release_in *inarg = &req->misc.release_in;
|
||||
|
||||
inarg->fh = ff->fh;
|
||||
inarg->flags = flags;
|
||||
req->in.h.opcode = opcode;
|
||||
req->in.h.nodeid = nodeid;
|
||||
req->in.numargs = 1;
|
||||
req->in.args[0].size = sizeof(struct fuse_release_in);
|
||||
req->in.args[0].value = inarg;
|
||||
kfree(ff);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
int fuse_release_common(struct inode *inode, struct file *file, int isdir)
|
||||
{
|
||||
struct fuse_file *ff = file->private_data;
|
||||
if (ff) {
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_req *req;
|
||||
|
||||
req = fuse_release_fill(ff, get_node_id(inode), file->f_flags,
|
||||
isdir ? FUSE_RELEASEDIR : FUSE_RELEASE);
|
||||
|
||||
/* Hold vfsmount and dentry until release is finished */
|
||||
req->vfsmount = mntget(file->f_path.mnt);
|
||||
req->dentry = dget(file->f_path.dentry);
|
||||
request_send_background(fc, req);
|
||||
}
|
||||
|
||||
/* Return value is ignored by VFS */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fuse_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return fuse_open_common(inode, file, 0);
|
||||
}
|
||||
|
||||
static int fuse_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return fuse_release_common(inode, file, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Scramble the ID space with XTEA, so that the value of the files_struct
|
||||
* pointer is not exposed to userspace.
|
||||
*/
|
||||
static u64 fuse_lock_owner_id(struct fuse_conn *fc, fl_owner_t id)
|
||||
{
|
||||
u32 *k = fc->scramble_key;
|
||||
u64 v = (unsigned long) id;
|
||||
u32 v0 = v;
|
||||
u32 v1 = v >> 32;
|
||||
u32 sum = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 32; i++) {
|
||||
v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]);
|
||||
sum += 0x9E3779B9;
|
||||
v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[sum>>11 & 3]);
|
||||
}
|
||||
|
||||
return (u64) v0 + ((u64) v1 << 32);
|
||||
}
|
||||
|
||||
static int fuse_flush(struct file *file, fl_owner_t id)
|
||||
{
|
||||
struct inode *inode = file->f_path.dentry->d_inode;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_file *ff = file->private_data;
|
||||
struct fuse_req *req;
|
||||
struct fuse_flush_in inarg;
|
||||
int err;
|
||||
|
||||
if (is_bad_inode(inode))
|
||||
return -EIO;
|
||||
|
||||
if (fc->no_flush)
|
||||
return 0;
|
||||
|
||||
req = fuse_get_req_nofail(fc, file);
|
||||
memset(&inarg, 0, sizeof(inarg));
|
||||
inarg.fh = ff->fh;
|
||||
inarg.lock_owner = fuse_lock_owner_id(fc, id);
|
||||
req->in.h.opcode = FUSE_FLUSH;
|
||||
req->in.h.nodeid = get_node_id(inode);
|
||||
req->in.numargs = 1;
|
||||
req->in.args[0].size = sizeof(inarg);
|
||||
req->in.args[0].value = &inarg;
|
||||
req->force = 1;
|
||||
request_send(fc, req);
|
||||
err = req->out.h.error;
|
||||
fuse_put_request(fc, req);
|
||||
if (err == -ENOSYS) {
|
||||
fc->no_flush = 1;
|
||||
err = 0;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int fuse_fsync_common(struct file *file, struct dentry *de, int datasync,
|
||||
int isdir)
|
||||
{
|
||||
struct inode *inode = de->d_inode;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_file *ff = file->private_data;
|
||||
struct fuse_req *req;
|
||||
struct fuse_fsync_in inarg;
|
||||
int err;
|
||||
|
||||
if (is_bad_inode(inode))
|
||||
return -EIO;
|
||||
|
||||
if ((!isdir && fc->no_fsync) || (isdir && fc->no_fsyncdir))
|
||||
return 0;
|
||||
|
||||
req = fuse_get_req(fc);
|
||||
if (IS_ERR(req))
|
||||
return PTR_ERR(req);
|
||||
|
||||
memset(&inarg, 0, sizeof(inarg));
|
||||
inarg.fh = ff->fh;
|
||||
inarg.fsync_flags = datasync ? 1 : 0;
|
||||
req->in.h.opcode = isdir ? FUSE_FSYNCDIR : FUSE_FSYNC;
|
||||
req->in.h.nodeid = get_node_id(inode);
|
||||
req->in.numargs = 1;
|
||||
req->in.args[0].size = sizeof(inarg);
|
||||
req->in.args[0].value = &inarg;
|
||||
request_send(fc, req);
|
||||
err = req->out.h.error;
|
||||
fuse_put_request(fc, req);
|
||||
if (err == -ENOSYS) {
|
||||
if (isdir)
|
||||
fc->no_fsyncdir = 1;
|
||||
else
|
||||
fc->no_fsync = 1;
|
||||
err = 0;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int fuse_fsync(struct file *file, struct dentry *de, int datasync)
|
||||
{
|
||||
return fuse_fsync_common(file, de, datasync, 0);
|
||||
}
|
||||
|
||||
void fuse_read_fill(struct fuse_req *req, struct file *file,
|
||||
struct inode *inode, loff_t pos, size_t count, int opcode)
|
||||
{
|
||||
struct fuse_file *ff = file->private_data;
|
||||
struct fuse_read_in *inarg = &req->misc.read_in;
|
||||
|
||||
inarg->fh = ff->fh;
|
||||
inarg->offset = pos;
|
||||
inarg->size = count;
|
||||
req->in.h.opcode = opcode;
|
||||
req->in.h.nodeid = get_node_id(inode);
|
||||
req->in.numargs = 1;
|
||||
req->in.args[0].size = sizeof(struct fuse_read_in);
|
||||
req->in.args[0].value = inarg;
|
||||
req->out.argpages = 1;
|
||||
req->out.argvar = 1;
|
||||
req->out.numargs = 1;
|
||||
req->out.args[0].size = count;
|
||||
}
|
||||
|
||||
static size_t fuse_send_read(struct fuse_req *req, struct file *file,
|
||||
struct inode *inode, loff_t pos, size_t count)
|
||||
{
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
fuse_read_fill(req, file, inode, pos, count, FUSE_READ);
|
||||
request_send(fc, req);
|
||||
return req->out.args[0].size;
|
||||
}
|
||||
|
||||
static int fuse_readpage(struct file *file, struct page *page)
|
||||
{
|
||||
struct inode *inode = page->mapping->host;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_req *req;
|
||||
int err;
|
||||
|
||||
err = -EIO;
|
||||
if (is_bad_inode(inode))
|
||||
goto out;
|
||||
|
||||
req = fuse_get_req(fc);
|
||||
err = PTR_ERR(req);
|
||||
if (IS_ERR(req))
|
||||
goto out;
|
||||
|
||||
req->out.page_zeroing = 1;
|
||||
req->num_pages = 1;
|
||||
req->pages[0] = page;
|
||||
fuse_send_read(req, file, inode, page_offset(page), PAGE_CACHE_SIZE);
|
||||
err = req->out.h.error;
|
||||
fuse_put_request(fc, req);
|
||||
if (!err)
|
||||
SetPageUptodate(page);
|
||||
fuse_invalidate_attr(inode); /* atime changed */
|
||||
out:
|
||||
unlock_page(page);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req)
|
||||
{
|
||||
int i;
|
||||
|
||||
fuse_invalidate_attr(req->pages[0]->mapping->host); /* atime changed */
|
||||
|
||||
for (i = 0; i < req->num_pages; i++) {
|
||||
struct page *page = req->pages[i];
|
||||
if (!req->out.h.error)
|
||||
SetPageUptodate(page);
|
||||
else
|
||||
SetPageError(page);
|
||||
unlock_page(page);
|
||||
}
|
||||
fuse_put_request(fc, req);
|
||||
}
|
||||
|
||||
static void fuse_send_readpages(struct fuse_req *req, struct file *file,
|
||||
struct inode *inode)
|
||||
{
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
loff_t pos = page_offset(req->pages[0]);
|
||||
size_t count = req->num_pages << PAGE_CACHE_SHIFT;
|
||||
req->out.page_zeroing = 1;
|
||||
fuse_read_fill(req, file, inode, pos, count, FUSE_READ);
|
||||
if (fc->async_read) {
|
||||
get_file(file);
|
||||
req->file = file;
|
||||
req->end = fuse_readpages_end;
|
||||
request_send_background(fc, req);
|
||||
} else {
|
||||
request_send(fc, req);
|
||||
fuse_readpages_end(fc, req);
|
||||
}
|
||||
}
|
||||
|
||||
struct fuse_readpages_data {
|
||||
struct fuse_req *req;
|
||||
struct file *file;
|
||||
struct inode *inode;
|
||||
};
|
||||
|
||||
static int fuse_readpages_fill(void *_data, struct page *page)
|
||||
{
|
||||
struct fuse_readpages_data *data = _data;
|
||||
struct fuse_req *req = data->req;
|
||||
struct inode *inode = data->inode;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
|
||||
if (req->num_pages &&
|
||||
(req->num_pages == FUSE_MAX_PAGES_PER_REQ ||
|
||||
(req->num_pages + 1) * PAGE_CACHE_SIZE > fc->max_read ||
|
||||
req->pages[req->num_pages - 1]->index + 1 != page->index)) {
|
||||
fuse_send_readpages(req, data->file, inode);
|
||||
data->req = req = fuse_get_req(fc);
|
||||
if (IS_ERR(req)) {
|
||||
unlock_page(page);
|
||||
return PTR_ERR(req);
|
||||
}
|
||||
}
|
||||
req->pages[req->num_pages] = page;
|
||||
req->num_pages ++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fuse_readpages(struct file *file, struct address_space *mapping,
|
||||
struct list_head *pages, unsigned nr_pages)
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_readpages_data data;
|
||||
int err;
|
||||
|
||||
err = -EIO;
|
||||
if (is_bad_inode(inode))
|
||||
goto out;
|
||||
|
||||
data.file = file;
|
||||
data.inode = inode;
|
||||
data.req = fuse_get_req(fc);
|
||||
err = PTR_ERR(data.req);
|
||||
if (IS_ERR(data.req))
|
||||
goto out;
|
||||
|
||||
err = read_cache_pages(mapping, pages, fuse_readpages_fill, &data);
|
||||
if (!err) {
|
||||
if (data.req->num_pages)
|
||||
fuse_send_readpages(data.req, file, inode);
|
||||
else
|
||||
fuse_put_request(fc, data.req);
|
||||
}
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static size_t fuse_send_write(struct fuse_req *req, struct file *file,
|
||||
struct inode *inode, loff_t pos, size_t count)
|
||||
{
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_file *ff = file->private_data;
|
||||
struct fuse_write_in inarg;
|
||||
struct fuse_write_out outarg;
|
||||
|
||||
memset(&inarg, 0, sizeof(struct fuse_write_in));
|
||||
inarg.fh = ff->fh;
|
||||
inarg.offset = pos;
|
||||
inarg.size = count;
|
||||
req->in.h.opcode = FUSE_WRITE;
|
||||
req->in.h.nodeid = get_node_id(inode);
|
||||
req->in.argpages = 1;
|
||||
req->in.numargs = 2;
|
||||
req->in.args[0].size = sizeof(struct fuse_write_in);
|
||||
req->in.args[0].value = &inarg;
|
||||
req->in.args[1].size = count;
|
||||
req->out.numargs = 1;
|
||||
req->out.args[0].size = sizeof(struct fuse_write_out);
|
||||
req->out.args[0].value = &outarg;
|
||||
request_send(fc, req);
|
||||
return outarg.size;
|
||||
}
|
||||
|
||||
static int fuse_prepare_write(struct file *file, struct page *page,
|
||||
unsigned offset, unsigned to)
|
||||
{
|
||||
/* No op */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fuse_commit_write(struct file *file, struct page *page,
|
||||
unsigned offset, unsigned to)
|
||||
{
|
||||
int err;
|
||||
size_t nres;
|
||||
unsigned count = to - offset;
|
||||
struct inode *inode = page->mapping->host;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
loff_t pos = page_offset(page) + offset;
|
||||
struct fuse_req *req;
|
||||
|
||||
if (is_bad_inode(inode))
|
||||
return -EIO;
|
||||
|
||||
req = fuse_get_req(fc);
|
||||
if (IS_ERR(req))
|
||||
return PTR_ERR(req);
|
||||
|
||||
req->num_pages = 1;
|
||||
req->pages[0] = page;
|
||||
req->page_offset = offset;
|
||||
nres = fuse_send_write(req, file, inode, pos, count);
|
||||
err = req->out.h.error;
|
||||
fuse_put_request(fc, req);
|
||||
if (!err && nres != count)
|
||||
err = -EIO;
|
||||
if (!err) {
|
||||
pos += count;
|
||||
spin_lock(&fc->lock);
|
||||
if (pos > inode->i_size)
|
||||
i_size_write(inode, pos);
|
||||
spin_unlock(&fc->lock);
|
||||
|
||||
if (offset == 0 && to == PAGE_CACHE_SIZE)
|
||||
SetPageUptodate(page);
|
||||
}
|
||||
fuse_invalidate_attr(inode);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void fuse_release_user_pages(struct fuse_req *req, int write)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < req->num_pages; i++) {
|
||||
struct page *page = req->pages[i];
|
||||
if (write)
|
||||
set_page_dirty_lock(page);
|
||||
put_page(page);
|
||||
}
|
||||
}
|
||||
|
||||
static int fuse_get_user_pages(struct fuse_req *req, const char __user *buf,
|
||||
unsigned nbytes, int write)
|
||||
{
|
||||
unsigned long user_addr = (unsigned long) buf;
|
||||
unsigned offset = user_addr & ~PAGE_MASK;
|
||||
int npages;
|
||||
|
||||
/* This doesn't work with nfsd */
|
||||
if (!current->mm)
|
||||
return -EPERM;
|
||||
|
||||
nbytes = min(nbytes, (unsigned) FUSE_MAX_PAGES_PER_REQ << PAGE_SHIFT);
|
||||
npages = (nbytes + offset + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
||||
npages = min(max(npages, 1), FUSE_MAX_PAGES_PER_REQ);
|
||||
down_read(¤t->mm->mmap_sem);
|
||||
npages = get_user_pages(current, current->mm, user_addr, npages, write,
|
||||
0, req->pages, NULL);
|
||||
up_read(¤t->mm->mmap_sem);
|
||||
if (npages < 0)
|
||||
return npages;
|
||||
|
||||
req->num_pages = npages;
|
||||
req->page_offset = offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t fuse_direct_io(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *ppos, int write)
|
||||
{
|
||||
struct inode *inode = file->f_path.dentry->d_inode;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
size_t nmax = write ? fc->max_write : fc->max_read;
|
||||
loff_t pos = *ppos;
|
||||
ssize_t res = 0;
|
||||
struct fuse_req *req;
|
||||
|
||||
if (is_bad_inode(inode))
|
||||
return -EIO;
|
||||
|
||||
req = fuse_get_req(fc);
|
||||
if (IS_ERR(req))
|
||||
return PTR_ERR(req);
|
||||
|
||||
while (count) {
|
||||
size_t nres;
|
||||
size_t nbytes = min(count, nmax);
|
||||
int err = fuse_get_user_pages(req, buf, nbytes, !write);
|
||||
if (err) {
|
||||
res = err;
|
||||
break;
|
||||
}
|
||||
nbytes = (req->num_pages << PAGE_SHIFT) - req->page_offset;
|
||||
nbytes = min(count, nbytes);
|
||||
if (write)
|
||||
nres = fuse_send_write(req, file, inode, pos, nbytes);
|
||||
else
|
||||
nres = fuse_send_read(req, file, inode, pos, nbytes);
|
||||
fuse_release_user_pages(req, !write);
|
||||
if (req->out.h.error) {
|
||||
if (!res)
|
||||
res = req->out.h.error;
|
||||
break;
|
||||
} else if (nres > nbytes) {
|
||||
res = -EIO;
|
||||
break;
|
||||
}
|
||||
count -= nres;
|
||||
res += nres;
|
||||
pos += nres;
|
||||
buf += nres;
|
||||
if (nres != nbytes)
|
||||
break;
|
||||
if (count) {
|
||||
fuse_put_request(fc, req);
|
||||
req = fuse_get_req(fc);
|
||||
if (IS_ERR(req))
|
||||
break;
|
||||
}
|
||||
}
|
||||
fuse_put_request(fc, req);
|
||||
if (res > 0) {
|
||||
if (write) {
|
||||
spin_lock(&fc->lock);
|
||||
if (pos > inode->i_size)
|
||||
i_size_write(inode, pos);
|
||||
spin_unlock(&fc->lock);
|
||||
}
|
||||
*ppos = pos;
|
||||
}
|
||||
fuse_invalidate_attr(inode);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static ssize_t fuse_direct_read(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
return fuse_direct_io(file, buf, count, ppos, 0);
|
||||
}
|
||||
|
||||
static ssize_t fuse_direct_write(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct inode *inode = file->f_path.dentry->d_inode;
|
||||
ssize_t res;
|
||||
/* Don't allow parallel writes to the same file */
|
||||
mutex_lock(&inode->i_mutex);
|
||||
res = fuse_direct_io(file, buf, count, ppos, 1);
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
if ((vma->vm_flags & VM_SHARED)) {
|
||||
if ((vma->vm_flags & VM_WRITE))
|
||||
return -ENODEV;
|
||||
else
|
||||
vma->vm_flags &= ~VM_MAYWRITE;
|
||||
}
|
||||
return generic_file_mmap(file, vma);
|
||||
}
|
||||
|
||||
static int fuse_set_page_dirty(struct page *page)
|
||||
{
|
||||
printk("fuse_set_page_dirty: should not happen\n");
|
||||
dump_stack();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int convert_fuse_file_lock(const struct fuse_file_lock *ffl,
|
||||
struct file_lock *fl)
|
||||
{
|
||||
switch (ffl->type) {
|
||||
case F_UNLCK:
|
||||
break;
|
||||
|
||||
case F_RDLCK:
|
||||
case F_WRLCK:
|
||||
if (ffl->start > OFFSET_MAX || ffl->end > OFFSET_MAX ||
|
||||
ffl->end < ffl->start)
|
||||
return -EIO;
|
||||
|
||||
fl->fl_start = ffl->start;
|
||||
fl->fl_end = ffl->end;
|
||||
fl->fl_pid = ffl->pid;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
fl->fl_type = ffl->type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fuse_lk_fill(struct fuse_req *req, struct file *file,
|
||||
const struct file_lock *fl, int opcode, pid_t pid)
|
||||
{
|
||||
struct inode *inode = file->f_path.dentry->d_inode;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_file *ff = file->private_data;
|
||||
struct fuse_lk_in *arg = &req->misc.lk_in;
|
||||
|
||||
arg->fh = ff->fh;
|
||||
arg->owner = fuse_lock_owner_id(fc, fl->fl_owner);
|
||||
arg->lk.start = fl->fl_start;
|
||||
arg->lk.end = fl->fl_end;
|
||||
arg->lk.type = fl->fl_type;
|
||||
arg->lk.pid = pid;
|
||||
req->in.h.opcode = opcode;
|
||||
req->in.h.nodeid = get_node_id(inode);
|
||||
req->in.numargs = 1;
|
||||
req->in.args[0].size = sizeof(*arg);
|
||||
req->in.args[0].value = arg;
|
||||
}
|
||||
|
||||
static int fuse_getlk(struct file *file, struct file_lock *fl)
|
||||
{
|
||||
struct inode *inode = file->f_path.dentry->d_inode;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_req *req;
|
||||
struct fuse_lk_out outarg;
|
||||
int err;
|
||||
|
||||
req = fuse_get_req(fc);
|
||||
if (IS_ERR(req))
|
||||
return PTR_ERR(req);
|
||||
|
||||
fuse_lk_fill(req, file, fl, FUSE_GETLK, 0);
|
||||
req->out.numargs = 1;
|
||||
req->out.args[0].size = sizeof(outarg);
|
||||
req->out.args[0].value = &outarg;
|
||||
request_send(fc, req);
|
||||
err = req->out.h.error;
|
||||
fuse_put_request(fc, req);
|
||||
if (!err)
|
||||
err = convert_fuse_file_lock(&outarg.lk, fl);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int fuse_setlk(struct file *file, struct file_lock *fl)
|
||||
{
|
||||
struct inode *inode = file->f_path.dentry->d_inode;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_req *req;
|
||||
int opcode = (fl->fl_flags & FL_SLEEP) ? FUSE_SETLKW : FUSE_SETLK;
|
||||
pid_t pid = fl->fl_type != F_UNLCK ? current->tgid : 0;
|
||||
int err;
|
||||
|
||||
/* Unlock on close is handled by the flush method */
|
||||
if (fl->fl_flags & FL_CLOSE)
|
||||
return 0;
|
||||
|
||||
req = fuse_get_req(fc);
|
||||
if (IS_ERR(req))
|
||||
return PTR_ERR(req);
|
||||
|
||||
fuse_lk_fill(req, file, fl, opcode, pid);
|
||||
request_send(fc, req);
|
||||
err = req->out.h.error;
|
||||
/* locking is restartable */
|
||||
if (err == -EINTR)
|
||||
err = -ERESTARTSYS;
|
||||
fuse_put_request(fc, req);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int fuse_file_lock(struct file *file, int cmd, struct file_lock *fl)
|
||||
{
|
||||
struct inode *inode = file->f_path.dentry->d_inode;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
int err;
|
||||
|
||||
if (cmd == F_GETLK) {
|
||||
if (fc->no_lock) {
|
||||
if (!posix_test_lock(file, fl, fl))
|
||||
fl->fl_type = F_UNLCK;
|
||||
err = 0;
|
||||
} else
|
||||
err = fuse_getlk(file, fl);
|
||||
} else {
|
||||
if (fc->no_lock)
|
||||
err = posix_lock_file_wait(file, fl);
|
||||
else
|
||||
err = fuse_setlk(file, fl);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static sector_t fuse_bmap(struct address_space *mapping, sector_t block)
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
struct fuse_req *req;
|
||||
struct fuse_bmap_in inarg;
|
||||
struct fuse_bmap_out outarg;
|
||||
int err;
|
||||
|
||||
if (!inode->i_sb->s_bdev || fc->no_bmap)
|
||||
return 0;
|
||||
|
||||
req = fuse_get_req(fc);
|
||||
if (IS_ERR(req))
|
||||
return 0;
|
||||
|
||||
memset(&inarg, 0, sizeof(inarg));
|
||||
inarg.block = block;
|
||||
inarg.blocksize = inode->i_sb->s_blocksize;
|
||||
req->in.h.opcode = FUSE_BMAP;
|
||||
req->in.h.nodeid = get_node_id(inode);
|
||||
req->in.numargs = 1;
|
||||
req->in.args[0].size = sizeof(inarg);
|
||||
req->in.args[0].value = &inarg;
|
||||
req->out.numargs = 1;
|
||||
req->out.args[0].size = sizeof(outarg);
|
||||
req->out.args[0].value = &outarg;
|
||||
request_send(fc, req);
|
||||
err = req->out.h.error;
|
||||
fuse_put_request(fc, req);
|
||||
if (err == -ENOSYS)
|
||||
fc->no_bmap = 1;
|
||||
|
||||
return err ? 0 : outarg.block;
|
||||
}
|
||||
|
||||
static const struct file_operations fuse_file_operations = {
|
||||
.llseek = generic_file_llseek,
|
||||
.read = do_sync_read,
|
||||
.aio_read = generic_file_aio_read,
|
||||
.write = do_sync_write,
|
||||
.aio_write = generic_file_aio_write,
|
||||
.mmap = fuse_file_mmap,
|
||||
.open = fuse_open,
|
||||
.flush = fuse_flush,
|
||||
.release = fuse_release,
|
||||
.fsync = fuse_fsync,
|
||||
.lock = fuse_file_lock,
|
||||
.sendfile = generic_file_sendfile,
|
||||
};
|
||||
|
||||
static const struct file_operations fuse_direct_io_file_operations = {
|
||||
.llseek = generic_file_llseek,
|
||||
.read = fuse_direct_read,
|
||||
.write = fuse_direct_write,
|
||||
.open = fuse_open,
|
||||
.flush = fuse_flush,
|
||||
.release = fuse_release,
|
||||
.fsync = fuse_fsync,
|
||||
.lock = fuse_file_lock,
|
||||
/* no mmap and sendfile */
|
||||
};
|
||||
|
||||
static const struct address_space_operations fuse_file_aops = {
|
||||
.readpage = fuse_readpage,
|
||||
.prepare_write = fuse_prepare_write,
|
||||
.commit_write = fuse_commit_write,
|
||||
.readpages = fuse_readpages,
|
||||
.set_page_dirty = fuse_set_page_dirty,
|
||||
.bmap = fuse_bmap,
|
||||
};
|
||||
|
||||
void fuse_init_file_inode(struct inode *inode)
|
||||
{
|
||||
inode->i_fop = &fuse_file_operations;
|
||||
inode->i_data.a_ops = &fuse_file_aops;
|
||||
}
|
||||
559
fs/fuse/fuse_i.h
Normal file
559
fs/fuse/fuse_i.h
Normal file
@@ -0,0 +1,559 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include <linux/fuse.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/backing-dev.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
/** Max number of pages that can be used in a single read request */
|
||||
#define FUSE_MAX_PAGES_PER_REQ 32
|
||||
|
||||
/** Maximum number of outstanding background requests */
|
||||
#define FUSE_MAX_BACKGROUND 10
|
||||
|
||||
/** It could be as large as PATH_MAX, but would that have any uses? */
|
||||
#define FUSE_NAME_MAX 1024
|
||||
|
||||
/** Number of dentries for each connection in the control filesystem */
|
||||
#define FUSE_CTL_NUM_DENTRIES 3
|
||||
|
||||
/** 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)
|
||||
|
||||
/** List of active connections */
|
||||
extern struct list_head fuse_conn_list;
|
||||
|
||||
/** Global mutex protecting fuse_conn_list and the control filesystem */
|
||||
extern struct mutex fuse_mutex;
|
||||
|
||||
/** FUSE inode */
|
||||
struct fuse_inode {
|
||||
/** Inode data */
|
||||
struct inode inode;
|
||||
|
||||
/** Unique ID, which identifies the inode between userspace
|
||||
* and kernel */
|
||||
u64 nodeid;
|
||||
|
||||
/** Number of lookups on this inode */
|
||||
u64 nlookup;
|
||||
|
||||
/** The request used for sending the FORGET message */
|
||||
struct fuse_req *forget_req;
|
||||
|
||||
/** Time in jiffies until the file attributes are valid */
|
||||
u64 i_time;
|
||||
};
|
||||
|
||||
/** FUSE specific file data */
|
||||
struct fuse_file {
|
||||
/** Request reserved for flush and release */
|
||||
struct fuse_req *reserved_req;
|
||||
|
||||
/** File handle used by userspace */
|
||||
u64 fh;
|
||||
};
|
||||
|
||||
/** One input argument of a request */
|
||||
struct fuse_in_arg {
|
||||
unsigned size;
|
||||
const void *value;
|
||||
};
|
||||
|
||||
/** The request input */
|
||||
struct fuse_in {
|
||||
/** The request header */
|
||||
struct fuse_in_header h;
|
||||
|
||||
/** True if the data for the last argument is in req->pages */
|
||||
unsigned argpages:1;
|
||||
|
||||
/** Number of arguments */
|
||||
unsigned numargs;
|
||||
|
||||
/** Array of arguments */
|
||||
struct fuse_in_arg args[3];
|
||||
};
|
||||
|
||||
/** One output argument of a request */
|
||||
struct fuse_arg {
|
||||
unsigned size;
|
||||
void *value;
|
||||
};
|
||||
|
||||
/** The request output */
|
||||
struct fuse_out {
|
||||
/** Header returned from userspace */
|
||||
struct fuse_out_header h;
|
||||
|
||||
/*
|
||||
* The following bitfields are not changed during the request
|
||||
* processing
|
||||
*/
|
||||
|
||||
/** Last argument is variable length (can be shorter than
|
||||
arg->size) */
|
||||
unsigned argvar:1;
|
||||
|
||||
/** Last argument is a list of pages to copy data to */
|
||||
unsigned argpages:1;
|
||||
|
||||
/** Zero partially or not copied pages */
|
||||
unsigned page_zeroing:1;
|
||||
|
||||
/** Number or arguments */
|
||||
unsigned numargs;
|
||||
|
||||
/** Array of arguments */
|
||||
struct fuse_arg args[3];
|
||||
};
|
||||
|
||||
/** The request state */
|
||||
enum fuse_req_state {
|
||||
FUSE_REQ_INIT = 0,
|
||||
FUSE_REQ_PENDING,
|
||||
FUSE_REQ_READING,
|
||||
FUSE_REQ_SENT,
|
||||
FUSE_REQ_WRITING,
|
||||
FUSE_REQ_FINISHED
|
||||
};
|
||||
|
||||
struct fuse_conn;
|
||||
|
||||
/**
|
||||
* A request to the client
|
||||
*/
|
||||
struct fuse_req {
|
||||
/** This can be on either pending processing or io lists in
|
||||
fuse_conn */
|
||||
struct list_head list;
|
||||
|
||||
/** Entry on the interrupts list */
|
||||
struct list_head intr_entry;
|
||||
|
||||
/** refcount */
|
||||
atomic_t count;
|
||||
|
||||
/** Unique ID for the interrupt request */
|
||||
u64 intr_unique;
|
||||
|
||||
/*
|
||||
* The following bitfields are either set once before the
|
||||
* request is queued or setting/clearing them is protected by
|
||||
* fuse_conn->lock
|
||||
*/
|
||||
|
||||
/** True if the request has reply */
|
||||
unsigned isreply:1;
|
||||
|
||||
/** Force sending of the request even if interrupted */
|
||||
unsigned force:1;
|
||||
|
||||
/** The request was aborted */
|
||||
unsigned aborted:1;
|
||||
|
||||
/** Request is sent in the background */
|
||||
unsigned background:1;
|
||||
|
||||
/** The request has been interrupted */
|
||||
unsigned interrupted:1;
|
||||
|
||||
/** Data is being copied to/from the request */
|
||||
unsigned locked:1;
|
||||
|
||||
/** Request is counted as "waiting" */
|
||||
unsigned waiting:1;
|
||||
|
||||
/** State of the request */
|
||||
enum fuse_req_state state;
|
||||
|
||||
/** The request input */
|
||||
struct fuse_in in;
|
||||
|
||||
/** The request output */
|
||||
struct fuse_out out;
|
||||
|
||||
/** Used to wake up the task waiting for completion of request*/
|
||||
wait_queue_head_t waitq;
|
||||
|
||||
/** Data for asynchronous requests */
|
||||
union {
|
||||
struct fuse_forget_in forget_in;
|
||||
struct fuse_release_in release_in;
|
||||
struct fuse_init_in init_in;
|
||||
struct fuse_init_out init_out;
|
||||
struct fuse_read_in read_in;
|
||||
struct fuse_lk_in lk_in;
|
||||
} misc;
|
||||
|
||||
/** page vector */
|
||||
struct page *pages[FUSE_MAX_PAGES_PER_REQ];
|
||||
|
||||
/** number of pages in vector */
|
||||
unsigned num_pages;
|
||||
|
||||
/** offset of data on first page */
|
||||
unsigned page_offset;
|
||||
|
||||
/** File used in the request (or NULL) */
|
||||
struct file *file;
|
||||
|
||||
/** vfsmount used in release */
|
||||
struct vfsmount *vfsmount;
|
||||
|
||||
/** dentry used in release */
|
||||
struct dentry *dentry;
|
||||
|
||||
/** Request completion callback */
|
||||
void (*end)(struct fuse_conn *, struct fuse_req *);
|
||||
|
||||
/** Request is stolen from fuse_file->reserved_req */
|
||||
struct file *stolen_file;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Fuse connection.
|
||||
*
|
||||
* This structure is created, when the filesystem is mounted, and is
|
||||
* destroyed, when the client device is closed and the filesystem is
|
||||
* unmounted.
|
||||
*/
|
||||
struct fuse_conn {
|
||||
/** Lock protecting accessess to members of this structure */
|
||||
spinlock_t lock;
|
||||
|
||||
/** Mutex protecting against directory alias creation */
|
||||
struct mutex inst_mutex;
|
||||
|
||||
/** Refcount */
|
||||
atomic_t count;
|
||||
|
||||
/** The user id for this mount */
|
||||
uid_t user_id;
|
||||
|
||||
/** The group id for this mount */
|
||||
gid_t group_id;
|
||||
|
||||
/** The fuse mount flags for this mount */
|
||||
unsigned flags;
|
||||
|
||||
/** Maximum read size */
|
||||
unsigned max_read;
|
||||
|
||||
/** Maximum write size */
|
||||
unsigned max_write;
|
||||
|
||||
/** Readers of the connection are waiting on this */
|
||||
wait_queue_head_t waitq;
|
||||
|
||||
/** The list of pending requests */
|
||||
struct list_head pending;
|
||||
|
||||
/** The list of requests being processed */
|
||||
struct list_head processing;
|
||||
|
||||
/** The list of requests under I/O */
|
||||
struct list_head io;
|
||||
|
||||
/** Number of requests currently in the background */
|
||||
unsigned num_background;
|
||||
|
||||
/** Pending interrupts */
|
||||
struct list_head interrupts;
|
||||
|
||||
/** Flag indicating if connection is blocked. This will be
|
||||
the case before the INIT reply is received, and if there
|
||||
are too many outstading backgrounds requests */
|
||||
int blocked;
|
||||
|
||||
/** waitq for blocked connection */
|
||||
wait_queue_head_t blocked_waitq;
|
||||
|
||||
/** The next unique request id */
|
||||
u64 reqctr;
|
||||
|
||||
/** Connection established, cleared on umount, connection
|
||||
abort and device release */
|
||||
unsigned connected;
|
||||
|
||||
/** Connection failed (version mismatch). Cannot race with
|
||||
setting other bitfields since it is only set once in INIT
|
||||
reply, before any other request, and never cleared */
|
||||
unsigned conn_error : 1;
|
||||
|
||||
/** Connection successful. Only set in INIT */
|
||||
unsigned conn_init : 1;
|
||||
|
||||
/** Do readpages asynchronously? Only set in INIT */
|
||||
unsigned async_read : 1;
|
||||
|
||||
/*
|
||||
* The following bitfields are only for optimization purposes
|
||||
* and hence races in setting them will not cause malfunction
|
||||
*/
|
||||
|
||||
/** Is fsync not implemented by fs? */
|
||||
unsigned no_fsync : 1;
|
||||
|
||||
/** Is fsyncdir not implemented by fs? */
|
||||
unsigned no_fsyncdir : 1;
|
||||
|
||||
/** Is flush not implemented by fs? */
|
||||
unsigned no_flush : 1;
|
||||
|
||||
/** Is setxattr not implemented by fs? */
|
||||
unsigned no_setxattr : 1;
|
||||
|
||||
/** Is getxattr not implemented by fs? */
|
||||
unsigned no_getxattr : 1;
|
||||
|
||||
/** Is listxattr not implemented by fs? */
|
||||
unsigned no_listxattr : 1;
|
||||
|
||||
/** Is removexattr not implemented by fs? */
|
||||
unsigned no_removexattr : 1;
|
||||
|
||||
/** Are file locking primitives not implemented by fs? */
|
||||
unsigned no_lock : 1;
|
||||
|
||||
/** Is access not implemented by fs? */
|
||||
unsigned no_access : 1;
|
||||
|
||||
/** Is create not implemented by fs? */
|
||||
unsigned no_create : 1;
|
||||
|
||||
/** Is interrupt not implemented by fs? */
|
||||
unsigned no_interrupt : 1;
|
||||
|
||||
/** Is bmap not implemented by fs? */
|
||||
unsigned no_bmap : 1;
|
||||
|
||||
/** The number of requests waiting for completion */
|
||||
atomic_t num_waiting;
|
||||
|
||||
/** Negotiated minor version */
|
||||
unsigned minor;
|
||||
|
||||
/** Backing dev info */
|
||||
struct backing_dev_info bdi;
|
||||
|
||||
/** Entry on the fuse_conn_list */
|
||||
struct list_head entry;
|
||||
|
||||
/** Unique ID */
|
||||
u64 id;
|
||||
|
||||
/** Dentries in the control filesystem */
|
||||
struct dentry *ctl_dentry[FUSE_CTL_NUM_DENTRIES];
|
||||
|
||||
/** number of dentries used in the above array */
|
||||
int ctl_ndents;
|
||||
|
||||
/** O_ASYNC requests */
|
||||
struct fasync_struct *fasync;
|
||||
|
||||
/** Key for lock owner ID scrambling */
|
||||
u32 scramble_key[4];
|
||||
|
||||
/** Reserved request for the DESTROY message */
|
||||
struct fuse_req *destroy_req;
|
||||
};
|
||||
|
||||
static inline struct fuse_conn *get_fuse_conn_super(struct super_block *sb)
|
||||
{
|
||||
return sb->s_fs_info;
|
||||
}
|
||||
|
||||
static inline struct fuse_conn *get_fuse_conn(struct inode *inode)
|
||||
{
|
||||
return get_fuse_conn_super(inode->i_sb);
|
||||
}
|
||||
|
||||
static inline struct fuse_inode *get_fuse_inode(struct inode *inode)
|
||||
{
|
||||
return container_of(inode, struct fuse_inode, inode);
|
||||
}
|
||||
|
||||
static inline u64 get_node_id(struct inode *inode)
|
||||
{
|
||||
return get_fuse_inode(inode)->nodeid;
|
||||
}
|
||||
|
||||
/** Device operations */
|
||||
extern const struct file_operations fuse_dev_operations;
|
||||
|
||||
/**
|
||||
* Get a filled in inode
|
||||
*/
|
||||
struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid,
|
||||
int generation, struct fuse_attr *attr);
|
||||
|
||||
/**
|
||||
* Send FORGET command
|
||||
*/
|
||||
void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req,
|
||||
unsigned long nodeid, u64 nlookup);
|
||||
|
||||
/**
|
||||
* Initialize READ or READDIR request
|
||||
*/
|
||||
void fuse_read_fill(struct fuse_req *req, struct file *file,
|
||||
struct inode *inode, loff_t pos, size_t count, int opcode);
|
||||
|
||||
/**
|
||||
* Send OPEN or OPENDIR request
|
||||
*/
|
||||
int fuse_open_common(struct inode *inode, struct file *file, int isdir);
|
||||
|
||||
struct fuse_file *fuse_file_alloc(void);
|
||||
void fuse_file_free(struct fuse_file *ff);
|
||||
void fuse_finish_open(struct inode *inode, struct file *file,
|
||||
struct fuse_file *ff, struct fuse_open_out *outarg);
|
||||
|
||||
/** */
|
||||
struct fuse_req *fuse_release_fill(struct fuse_file *ff, u64 nodeid, int flags,
|
||||
int opcode);
|
||||
/**
|
||||
* Send RELEASE or RELEASEDIR request
|
||||
*/
|
||||
int fuse_release_common(struct inode *inode, struct file *file, int isdir);
|
||||
|
||||
/**
|
||||
* Send FSYNC or FSYNCDIR request
|
||||
*/
|
||||
int fuse_fsync_common(struct file *file, struct dentry *de, int datasync,
|
||||
int isdir);
|
||||
|
||||
/**
|
||||
* Initialize file operations on a regular file
|
||||
*/
|
||||
void fuse_init_file_inode(struct inode *inode);
|
||||
|
||||
/**
|
||||
* Initialize inode operations on regular files and special files
|
||||
*/
|
||||
void fuse_init_common(struct inode *inode);
|
||||
|
||||
/**
|
||||
* Initialize inode and file operations on a directory
|
||||
*/
|
||||
void fuse_init_dir(struct inode *inode);
|
||||
|
||||
/**
|
||||
* Initialize inode operations on a symlink
|
||||
*/
|
||||
void fuse_init_symlink(struct inode *inode);
|
||||
|
||||
/**
|
||||
* Change attributes of an inode
|
||||
*/
|
||||
void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr);
|
||||
|
||||
/**
|
||||
* Initialize the client device
|
||||
*/
|
||||
int fuse_dev_init(void);
|
||||
|
||||
/**
|
||||
* Cleanup the client device
|
||||
*/
|
||||
void fuse_dev_cleanup(void);
|
||||
|
||||
int fuse_ctl_init(void);
|
||||
void fuse_ctl_cleanup(void);
|
||||
|
||||
/**
|
||||
* Allocate a request
|
||||
*/
|
||||
struct fuse_req *fuse_request_alloc(void);
|
||||
|
||||
/**
|
||||
* Free a request
|
||||
*/
|
||||
void fuse_request_free(struct fuse_req *req);
|
||||
|
||||
/**
|
||||
* Get a request, may fail with -ENOMEM
|
||||
*/
|
||||
struct fuse_req *fuse_get_req(struct fuse_conn *fc);
|
||||
|
||||
/**
|
||||
* Gets a requests for a file operation, always succeeds
|
||||
*/
|
||||
struct fuse_req *fuse_get_req_nofail(struct fuse_conn *fc, struct file *file);
|
||||
|
||||
/**
|
||||
* Decrement reference count of a request. If count goes to zero free
|
||||
* the request.
|
||||
*/
|
||||
void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req);
|
||||
|
||||
/**
|
||||
* Send a request (synchronous)
|
||||
*/
|
||||
void request_send(struct fuse_conn *fc, struct fuse_req *req);
|
||||
|
||||
/**
|
||||
* Send a request with no reply
|
||||
*/
|
||||
void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req);
|
||||
|
||||
/**
|
||||
* Send a request in the background
|
||||
*/
|
||||
void request_send_background(struct fuse_conn *fc, struct fuse_req *req);
|
||||
|
||||
/* Abort all requests */
|
||||
void fuse_abort_conn(struct fuse_conn *fc);
|
||||
|
||||
/**
|
||||
* Get the attributes of a file
|
||||
*/
|
||||
int fuse_do_getattr(struct inode *inode);
|
||||
|
||||
/**
|
||||
* Invalidate inode attributes
|
||||
*/
|
||||
void fuse_invalidate_attr(struct inode *inode);
|
||||
|
||||
/**
|
||||
* Acquire reference to fuse_conn
|
||||
*/
|
||||
struct fuse_conn *fuse_conn_get(struct fuse_conn *fc);
|
||||
|
||||
/**
|
||||
* Release reference to fuse_conn
|
||||
*/
|
||||
void fuse_conn_put(struct fuse_conn *fc);
|
||||
|
||||
/**
|
||||
* Add connection to control filesystem
|
||||
*/
|
||||
int fuse_ctl_add_conn(struct fuse_conn *fc);
|
||||
|
||||
/**
|
||||
* Remove connection from control filesystem
|
||||
*/
|
||||
void fuse_ctl_remove_conn(struct fuse_conn *fc);
|
||||
|
||||
/**
|
||||
* Is file type valid?
|
||||
*/
|
||||
int fuse_valid_type(int m);
|
||||
805
fs/fuse/inode.c
Normal file
805
fs/fuse/inode.c
Normal file
@@ -0,0 +1,805 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include "fuse_i.h"
|
||||
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/parser.h>
|
||||
#include <linux/statfs.h>
|
||||
#include <linux/random.h>
|
||||
|
||||
MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
|
||||
MODULE_DESCRIPTION("Filesystem in Userspace");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static struct kmem_cache *fuse_inode_cachep;
|
||||
struct list_head fuse_conn_list;
|
||||
DEFINE_MUTEX(fuse_mutex);
|
||||
|
||||
#define FUSE_SUPER_MAGIC 0x65735546
|
||||
|
||||
struct fuse_mount_data {
|
||||
int fd;
|
||||
unsigned rootmode;
|
||||
unsigned user_id;
|
||||
unsigned group_id;
|
||||
unsigned fd_present : 1;
|
||||
unsigned rootmode_present : 1;
|
||||
unsigned user_id_present : 1;
|
||||
unsigned group_id_present : 1;
|
||||
unsigned flags;
|
||||
unsigned max_read;
|
||||
unsigned blksize;
|
||||
};
|
||||
|
||||
static struct inode *fuse_alloc_inode(struct super_block *sb)
|
||||
{
|
||||
struct inode *inode;
|
||||
struct fuse_inode *fi;
|
||||
|
||||
inode = kmem_cache_alloc(fuse_inode_cachep, GFP_KERNEL);
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
fi = get_fuse_inode(inode);
|
||||
fi->i_time = 0;
|
||||
fi->nodeid = 0;
|
||||
fi->nlookup = 0;
|
||||
fi->forget_req = fuse_request_alloc();
|
||||
if (!fi->forget_req) {
|
||||
kmem_cache_free(fuse_inode_cachep, inode);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return inode;
|
||||
}
|
||||
|
||||
static void fuse_destroy_inode(struct inode *inode)
|
||||
{
|
||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||
if (fi->forget_req)
|
||||
fuse_request_free(fi->forget_req);
|
||||
kmem_cache_free(fuse_inode_cachep, inode);
|
||||
}
|
||||
|
||||
static void fuse_read_inode(struct inode *inode)
|
||||
{
|
||||
/* No op */
|
||||
}
|
||||
|
||||
void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req,
|
||||
unsigned long nodeid, u64 nlookup)
|
||||
{
|
||||
struct fuse_forget_in *inarg = &req->misc.forget_in;
|
||||
inarg->nlookup = nlookup;
|
||||
req->in.h.opcode = FUSE_FORGET;
|
||||
req->in.h.nodeid = nodeid;
|
||||
req->in.numargs = 1;
|
||||
req->in.args[0].size = sizeof(struct fuse_forget_in);
|
||||
req->in.args[0].value = inarg;
|
||||
request_send_noreply(fc, 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);
|
||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||
fuse_send_forget(fc, fi->forget_req, fi->nodeid, fi->nlookup);
|
||||
fi->forget_req = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int fuse_remount_fs(struct super_block *sb, int *flags, char *data)
|
||||
{
|
||||
if (*flags & MS_MANDLOCK)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr)
|
||||
{
|
||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||
if (S_ISREG(inode->i_mode) && i_size_read(inode) != attr->size)
|
||||
invalidate_mapping_pages(inode->i_mapping, 0, -1);
|
||||
|
||||
inode->i_ino = attr->ino;
|
||||
inode->i_mode = (inode->i_mode & S_IFMT) + (attr->mode & 07777);
|
||||
inode->i_nlink = attr->nlink;
|
||||
inode->i_uid = attr->uid;
|
||||
inode->i_gid = attr->gid;
|
||||
spin_lock(&fc->lock);
|
||||
i_size_write(inode, attr->size);
|
||||
spin_unlock(&fc->lock);
|
||||
inode->i_blocks = attr->blocks;
|
||||
inode->i_atime.tv_sec = attr->atime;
|
||||
inode->i_atime.tv_nsec = attr->atimensec;
|
||||
inode->i_mtime.tv_sec = attr->mtime;
|
||||
inode->i_mtime.tv_nsec = attr->mtimensec;
|
||||
inode->i_ctime.tv_sec = attr->ctime;
|
||||
inode->i_ctime.tv_nsec = attr->ctimensec;
|
||||
}
|
||||
|
||||
static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr)
|
||||
{
|
||||
inode->i_mode = attr->mode & S_IFMT;
|
||||
inode->i_size = attr->size;
|
||||
if (S_ISREG(inode->i_mode)) {
|
||||
fuse_init_common(inode);
|
||||
fuse_init_file_inode(inode);
|
||||
} else if (S_ISDIR(inode->i_mode))
|
||||
fuse_init_dir(inode);
|
||||
else if (S_ISLNK(inode->i_mode))
|
||||
fuse_init_symlink(inode);
|
||||
else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
|
||||
S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
|
||||
fuse_init_common(inode);
|
||||
init_special_inode(inode, inode->i_mode,
|
||||
new_decode_dev(attr->rdev));
|
||||
} else
|
||||
BUG();
|
||||
}
|
||||
|
||||
static int fuse_inode_eq(struct inode *inode, void *_nodeidp)
|
||||
{
|
||||
unsigned long nodeid = *(unsigned long *) _nodeidp;
|
||||
if (get_node_id(inode) == nodeid)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fuse_inode_set(struct inode *inode, void *_nodeidp)
|
||||
{
|
||||
unsigned long nodeid = *(unsigned long *) _nodeidp;
|
||||
get_fuse_inode(inode)->nodeid = nodeid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid,
|
||||
int generation, struct fuse_attr *attr)
|
||||
{
|
||||
struct inode *inode;
|
||||
struct fuse_inode *fi;
|
||||
struct fuse_conn *fc = get_fuse_conn_super(sb);
|
||||
|
||||
retry:
|
||||
inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid);
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
if ((inode->i_state & I_NEW)) {
|
||||
inode->i_flags |= S_NOATIME|S_NOCMTIME;
|
||||
inode->i_generation = generation;
|
||||
inode->i_data.backing_dev_info = &fc->bdi;
|
||||
fuse_init_inode(inode, attr);
|
||||
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);
|
||||
iput(inode);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
fi = get_fuse_inode(inode);
|
||||
spin_lock(&fc->lock);
|
||||
fi->nlookup ++;
|
||||
spin_unlock(&fc->lock);
|
||||
fuse_change_attributes(inode, attr);
|
||||
return inode;
|
||||
}
|
||||
|
||||
static void fuse_umount_begin(struct vfsmount *vfsmnt, int flags)
|
||||
{
|
||||
if (flags & MNT_FORCE)
|
||||
fuse_abort_conn(get_fuse_conn_super(vfsmnt->mnt_sb));
|
||||
}
|
||||
|
||||
static void fuse_send_destroy(struct fuse_conn *fc)
|
||||
{
|
||||
struct fuse_req *req = fc->destroy_req;
|
||||
if (req && fc->conn_init) {
|
||||
fc->destroy_req = NULL;
|
||||
req->in.h.opcode = FUSE_DESTROY;
|
||||
req->force = 1;
|
||||
request_send(fc, req);
|
||||
fuse_put_request(fc, req);
|
||||
}
|
||||
}
|
||||
|
||||
static void fuse_put_super(struct super_block *sb)
|
||||
{
|
||||
struct fuse_conn *fc = get_fuse_conn_super(sb);
|
||||
|
||||
fuse_send_destroy(fc);
|
||||
spin_lock(&fc->lock);
|
||||
fc->connected = 0;
|
||||
fc->blocked = 0;
|
||||
spin_unlock(&fc->lock);
|
||||
/* Flush all readers on this fs */
|
||||
kill_fasync(&fc->fasync, SIGIO, POLL_IN);
|
||||
wake_up_all(&fc->waitq);
|
||||
wake_up_all(&fc->blocked_waitq);
|
||||
mutex_lock(&fuse_mutex);
|
||||
list_del(&fc->entry);
|
||||
fuse_ctl_remove_conn(fc);
|
||||
mutex_unlock(&fuse_mutex);
|
||||
fuse_conn_put(fc);
|
||||
}
|
||||
|
||||
static void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr)
|
||||
{
|
||||
stbuf->f_type = FUSE_SUPER_MAGIC;
|
||||
stbuf->f_bsize = attr->bsize;
|
||||
stbuf->f_frsize = attr->frsize;
|
||||
stbuf->f_blocks = attr->blocks;
|
||||
stbuf->f_bfree = attr->bfree;
|
||||
stbuf->f_bavail = attr->bavail;
|
||||
stbuf->f_files = attr->files;
|
||||
stbuf->f_ffree = attr->ffree;
|
||||
stbuf->f_namelen = attr->namelen;
|
||||
/* fsid is left zero */
|
||||
}
|
||||
|
||||
static int fuse_statfs(struct dentry *dentry, struct kstatfs *buf)
|
||||
{
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
struct fuse_conn *fc = get_fuse_conn_super(sb);
|
||||
struct fuse_req *req;
|
||||
struct fuse_statfs_out outarg;
|
||||
int err;
|
||||
|
||||
req = fuse_get_req(fc);
|
||||
if (IS_ERR(req))
|
||||
return PTR_ERR(req);
|
||||
|
||||
memset(&outarg, 0, sizeof(outarg));
|
||||
req->in.numargs = 0;
|
||||
req->in.h.opcode = FUSE_STATFS;
|
||||
req->in.h.nodeid = get_node_id(dentry->d_inode);
|
||||
req->out.numargs = 1;
|
||||
req->out.args[0].size =
|
||||
fc->minor < 4 ? FUSE_COMPAT_STATFS_SIZE : sizeof(outarg);
|
||||
req->out.args[0].value = &outarg;
|
||||
request_send(fc, req);
|
||||
err = req->out.h.error;
|
||||
if (!err)
|
||||
convert_fuse_statfs(buf, &outarg.st);
|
||||
fuse_put_request(fc, req);
|
||||
return err;
|
||||
}
|
||||
|
||||
enum {
|
||||
OPT_FD,
|
||||
OPT_ROOTMODE,
|
||||
OPT_USER_ID,
|
||||
OPT_GROUP_ID,
|
||||
OPT_DEFAULT_PERMISSIONS,
|
||||
OPT_ALLOW_OTHER,
|
||||
OPT_MAX_READ,
|
||||
OPT_BLKSIZE,
|
||||
OPT_ERR
|
||||
};
|
||||
|
||||
static match_table_t tokens = {
|
||||
{OPT_FD, "fd=%u"},
|
||||
{OPT_ROOTMODE, "rootmode=%o"},
|
||||
{OPT_USER_ID, "user_id=%u"},
|
||||
{OPT_GROUP_ID, "group_id=%u"},
|
||||
{OPT_DEFAULT_PERMISSIONS, "default_permissions"},
|
||||
{OPT_ALLOW_OTHER, "allow_other"},
|
||||
{OPT_MAX_READ, "max_read=%u"},
|
||||
{OPT_BLKSIZE, "blksize=%u"},
|
||||
{OPT_ERR, NULL}
|
||||
};
|
||||
|
||||
static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev)
|
||||
{
|
||||
char *p;
|
||||
memset(d, 0, sizeof(struct fuse_mount_data));
|
||||
d->max_read = ~0;
|
||||
d->blksize = 512;
|
||||
|
||||
while ((p = strsep(&opt, ",")) != NULL) {
|
||||
int token;
|
||||
int value;
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
if (!*p)
|
||||
continue;
|
||||
|
||||
token = match_token(p, tokens, args);
|
||||
switch (token) {
|
||||
case OPT_FD:
|
||||
if (match_int(&args[0], &value))
|
||||
return 0;
|
||||
d->fd = value;
|
||||
d->fd_present = 1;
|
||||
break;
|
||||
|
||||
case OPT_ROOTMODE:
|
||||
if (match_octal(&args[0], &value))
|
||||
return 0;
|
||||
if (!fuse_valid_type(value))
|
||||
return 0;
|
||||
d->rootmode = value;
|
||||
d->rootmode_present = 1;
|
||||
break;
|
||||
|
||||
case OPT_USER_ID:
|
||||
if (match_int(&args[0], &value))
|
||||
return 0;
|
||||
d->user_id = value;
|
||||
d->user_id_present = 1;
|
||||
break;
|
||||
|
||||
case OPT_GROUP_ID:
|
||||
if (match_int(&args[0], &value))
|
||||
return 0;
|
||||
d->group_id = value;
|
||||
d->group_id_present = 1;
|
||||
break;
|
||||
|
||||
case OPT_DEFAULT_PERMISSIONS:
|
||||
d->flags |= FUSE_DEFAULT_PERMISSIONS;
|
||||
break;
|
||||
|
||||
case OPT_ALLOW_OTHER:
|
||||
d->flags |= FUSE_ALLOW_OTHER;
|
||||
break;
|
||||
|
||||
case OPT_MAX_READ:
|
||||
if (match_int(&args[0], &value))
|
||||
return 0;
|
||||
d->max_read = value;
|
||||
break;
|
||||
|
||||
case OPT_BLKSIZE:
|
||||
if (!is_bdev || match_int(&args[0], &value))
|
||||
return 0;
|
||||
d->blksize = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!d->fd_present || !d->rootmode_present ||
|
||||
!d->user_id_present || !d->group_id_present)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
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);
|
||||
seq_printf(m, ",group_id=%u", fc->group_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->max_read != ~0)
|
||||
seq_printf(m, ",max_read=%u", fc->max_read);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct fuse_conn *new_conn(void)
|
||||
{
|
||||
struct fuse_conn *fc;
|
||||
|
||||
fc = kzalloc(sizeof(*fc), GFP_KERNEL);
|
||||
if (fc) {
|
||||
spin_lock_init(&fc->lock);
|
||||
mutex_init(&fc->inst_mutex);
|
||||
atomic_set(&fc->count, 1);
|
||||
init_waitqueue_head(&fc->waitq);
|
||||
init_waitqueue_head(&fc->blocked_waitq);
|
||||
INIT_LIST_HEAD(&fc->pending);
|
||||
INIT_LIST_HEAD(&fc->processing);
|
||||
INIT_LIST_HEAD(&fc->io);
|
||||
INIT_LIST_HEAD(&fc->interrupts);
|
||||
atomic_set(&fc->num_waiting, 0);
|
||||
fc->bdi.ra_pages = (VM_MAX_READAHEAD * 1024) / PAGE_CACHE_SIZE;
|
||||
fc->bdi.unplug_io_fn = default_unplug_io_fn;
|
||||
fc->reqctr = 0;
|
||||
fc->blocked = 1;
|
||||
get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key));
|
||||
}
|
||||
return fc;
|
||||
}
|
||||
|
||||
void fuse_conn_put(struct fuse_conn *fc)
|
||||
{
|
||||
if (atomic_dec_and_test(&fc->count)) {
|
||||
if (fc->destroy_req)
|
||||
fuse_request_free(fc->destroy_req);
|
||||
mutex_destroy(&fc->inst_mutex);
|
||||
kfree(fc);
|
||||
}
|
||||
}
|
||||
|
||||
struct fuse_conn *fuse_conn_get(struct fuse_conn *fc)
|
||||
{
|
||||
atomic_inc(&fc->count);
|
||||
return fc;
|
||||
}
|
||||
|
||||
static struct inode *get_root_inode(struct super_block *sb, unsigned mode)
|
||||
{
|
||||
struct fuse_attr attr;
|
||||
memset(&attr, 0, sizeof(attr));
|
||||
|
||||
attr.mode = mode;
|
||||
attr.ino = FUSE_ROOT_ID;
|
||||
return fuse_iget(sb, 1, 0, &attr);
|
||||
}
|
||||
|
||||
static const struct super_operations fuse_super_operations = {
|
||||
.alloc_inode = fuse_alloc_inode,
|
||||
.destroy_inode = fuse_destroy_inode,
|
||||
.read_inode = fuse_read_inode,
|
||||
.clear_inode = fuse_clear_inode,
|
||||
.remount_fs = fuse_remount_fs,
|
||||
.put_super = fuse_put_super,
|
||||
.umount_begin = fuse_umount_begin,
|
||||
.statfs = fuse_statfs,
|
||||
.show_options = fuse_show_options,
|
||||
};
|
||||
|
||||
static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req)
|
||||
{
|
||||
struct fuse_init_out *arg = &req->misc.init_out;
|
||||
|
||||
if (req->out.h.error || arg->major != FUSE_KERNEL_VERSION)
|
||||
fc->conn_error = 1;
|
||||
else {
|
||||
unsigned long ra_pages;
|
||||
|
||||
if (arg->minor >= 6) {
|
||||
ra_pages = arg->max_readahead / PAGE_CACHE_SIZE;
|
||||
if (arg->flags & FUSE_ASYNC_READ)
|
||||
fc->async_read = 1;
|
||||
if (!(arg->flags & FUSE_POSIX_LOCKS))
|
||||
fc->no_lock = 1;
|
||||
} else {
|
||||
ra_pages = fc->max_read / PAGE_CACHE_SIZE;
|
||||
fc->no_lock = 1;
|
||||
}
|
||||
|
||||
fc->bdi.ra_pages = min(fc->bdi.ra_pages, ra_pages);
|
||||
fc->minor = arg->minor;
|
||||
fc->max_write = arg->minor < 5 ? 4096 : arg->max_write;
|
||||
fc->conn_init = 1;
|
||||
}
|
||||
fuse_put_request(fc, req);
|
||||
fc->blocked = 0;
|
||||
wake_up_all(&fc->blocked_waitq);
|
||||
}
|
||||
|
||||
static void fuse_send_init(struct fuse_conn *fc, struct fuse_req *req)
|
||||
{
|
||||
struct fuse_init_in *arg = &req->misc.init_in;
|
||||
|
||||
arg->major = FUSE_KERNEL_VERSION;
|
||||
arg->minor = FUSE_KERNEL_MINOR_VERSION;
|
||||
arg->max_readahead = fc->bdi.ra_pages * PAGE_CACHE_SIZE;
|
||||
arg->flags |= FUSE_ASYNC_READ | FUSE_POSIX_LOCKS;
|
||||
req->in.h.opcode = FUSE_INIT;
|
||||
req->in.numargs = 1;
|
||||
req->in.args[0].size = sizeof(*arg);
|
||||
req->in.args[0].value = arg;
|
||||
req->out.numargs = 1;
|
||||
/* Variable length arguement used for backward compatibility
|
||||
with interface version < 7.5. Rest of init_out is zeroed
|
||||
by do_get_request(), so a short reply is not a problem */
|
||||
req->out.argvar = 1;
|
||||
req->out.args[0].size = sizeof(struct fuse_init_out);
|
||||
req->out.args[0].value = &req->misc.init_out;
|
||||
req->end = process_init_reply;
|
||||
request_send_background(fc, req);
|
||||
}
|
||||
|
||||
static u64 conn_id(void)
|
||||
{
|
||||
static u64 ctr = 1;
|
||||
return ctr++;
|
||||
}
|
||||
|
||||
static int fuse_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct fuse_conn *fc;
|
||||
struct inode *root;
|
||||
struct fuse_mount_data d;
|
||||
struct file *file;
|
||||
struct dentry *root_dentry;
|
||||
struct fuse_req *init_req;
|
||||
int err;
|
||||
int is_bdev = sb->s_bdev != NULL;
|
||||
|
||||
if (sb->s_flags & MS_MANDLOCK)
|
||||
return -EINVAL;
|
||||
|
||||
if (!parse_fuse_opt((char *) data, &d, is_bdev))
|
||||
return -EINVAL;
|
||||
|
||||
if (is_bdev) {
|
||||
#ifdef CONFIG_BLOCK
|
||||
if (!sb_set_blocksize(sb, d.blksize))
|
||||
return -EINVAL;
|
||||
#endif
|
||||
} else {
|
||||
sb->s_blocksize = PAGE_CACHE_SIZE;
|
||||
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
|
||||
}
|
||||
sb->s_magic = FUSE_SUPER_MAGIC;
|
||||
sb->s_op = &fuse_super_operations;
|
||||
sb->s_maxbytes = MAX_LFS_FILESIZE;
|
||||
|
||||
file = fget(d.fd);
|
||||
if (!file)
|
||||
return -EINVAL;
|
||||
|
||||
if (file->f_op != &fuse_dev_operations)
|
||||
return -EINVAL;
|
||||
|
||||
fc = new_conn();
|
||||
if (!fc)
|
||||
return -ENOMEM;
|
||||
|
||||
fc->flags = d.flags;
|
||||
fc->user_id = d.user_id;
|
||||
fc->group_id = d.group_id;
|
||||
fc->max_read = d.max_read;
|
||||
|
||||
/* Used by get_root_inode() */
|
||||
sb->s_fs_info = fc;
|
||||
|
||||
err = -ENOMEM;
|
||||
root = get_root_inode(sb, d.rootmode);
|
||||
if (!root)
|
||||
goto err;
|
||||
|
||||
root_dentry = d_alloc_root(root);
|
||||
if (!root_dentry) {
|
||||
iput(root);
|
||||
goto err;
|
||||
}
|
||||
|
||||
init_req = fuse_request_alloc();
|
||||
if (!init_req)
|
||||
goto err_put_root;
|
||||
|
||||
if (is_bdev) {
|
||||
fc->destroy_req = fuse_request_alloc();
|
||||
if (!fc->destroy_req)
|
||||
goto err_put_root;
|
||||
}
|
||||
|
||||
mutex_lock(&fuse_mutex);
|
||||
err = -EINVAL;
|
||||
if (file->private_data)
|
||||
goto err_unlock;
|
||||
|
||||
fc->id = conn_id();
|
||||
err = fuse_ctl_add_conn(fc);
|
||||
if (err)
|
||||
goto err_unlock;
|
||||
|
||||
list_add_tail(&fc->entry, &fuse_conn_list);
|
||||
sb->s_root = root_dentry;
|
||||
fc->connected = 1;
|
||||
file->private_data = fuse_conn_get(fc);
|
||||
mutex_unlock(&fuse_mutex);
|
||||
/*
|
||||
* atomic_dec_and_test() in fput() provides the necessary
|
||||
* memory barrier for file->private_data to be visible on all
|
||||
* CPUs after this
|
||||
*/
|
||||
fput(file);
|
||||
|
||||
fuse_send_init(fc, init_req);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unlock:
|
||||
mutex_unlock(&fuse_mutex);
|
||||
fuse_request_free(init_req);
|
||||
err_put_root:
|
||||
dput(root_dentry);
|
||||
err:
|
||||
fput(file);
|
||||
fuse_conn_put(fc);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int fuse_get_sb(struct file_system_type *fs_type,
|
||||
int flags, const char *dev_name,
|
||||
void *raw_data, struct vfsmount *mnt)
|
||||
{
|
||||
return get_sb_nodev(fs_type, flags, raw_data, fuse_fill_super, mnt);
|
||||
}
|
||||
|
||||
static struct file_system_type fuse_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "fuse",
|
||||
.get_sb = fuse_get_sb,
|
||||
.kill_sb = kill_anon_super,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_BLOCK
|
||||
static int fuse_get_sb_blk(struct file_system_type *fs_type,
|
||||
int flags, const char *dev_name,
|
||||
void *raw_data, struct vfsmount *mnt)
|
||||
{
|
||||
return get_sb_bdev(fs_type, flags, dev_name, raw_data, fuse_fill_super,
|
||||
mnt);
|
||||
}
|
||||
|
||||
static struct file_system_type fuseblk_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "fuseblk",
|
||||
.get_sb = fuse_get_sb_blk,
|
||||
.kill_sb = kill_block_super,
|
||||
.fs_flags = FS_REQUIRES_DEV,
|
||||
};
|
||||
|
||||
static inline int register_fuseblk(void)
|
||||
{
|
||||
return register_filesystem(&fuseblk_fs_type);
|
||||
}
|
||||
|
||||
static inline void unregister_fuseblk(void)
|
||||
{
|
||||
unregister_filesystem(&fuseblk_fs_type);
|
||||
}
|
||||
#else
|
||||
static inline int register_fuseblk(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void unregister_fuseblk(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static decl_subsys(fuse, NULL, NULL);
|
||||
static decl_subsys(connections, NULL, NULL);
|
||||
|
||||
static void fuse_inode_init_once(void *foo, struct kmem_cache *cachep,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct inode * inode = foo;
|
||||
|
||||
if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
|
||||
SLAB_CTOR_CONSTRUCTOR)
|
||||
inode_init_once(inode);
|
||||
}
|
||||
|
||||
static int __init fuse_fs_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = register_filesystem(&fuse_fs_type);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = register_fuseblk();
|
||||
if (err)
|
||||
goto out_unreg;
|
||||
|
||||
fuse_inode_cachep = kmem_cache_create("fuse_inode",
|
||||
sizeof(struct fuse_inode),
|
||||
0, SLAB_HWCACHE_ALIGN,
|
||||
fuse_inode_init_once, NULL);
|
||||
err = -ENOMEM;
|
||||
if (!fuse_inode_cachep)
|
||||
goto out_unreg2;
|
||||
|
||||
return 0;
|
||||
|
||||
out_unreg2:
|
||||
unregister_fuseblk();
|
||||
out_unreg:
|
||||
unregister_filesystem(&fuse_fs_type);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void fuse_fs_cleanup(void)
|
||||
{
|
||||
unregister_filesystem(&fuse_fs_type);
|
||||
unregister_fuseblk();
|
||||
kmem_cache_destroy(fuse_inode_cachep);
|
||||
}
|
||||
|
||||
static int fuse_sysfs_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
kset_set_kset_s(&fuse_subsys, fs_subsys);
|
||||
err = subsystem_register(&fuse_subsys);
|
||||
if (err)
|
||||
goto out_err;
|
||||
|
||||
kset_set_kset_s(&connections_subsys, fuse_subsys);
|
||||
err = subsystem_register(&connections_subsys);
|
||||
if (err)
|
||||
goto out_fuse_unregister;
|
||||
|
||||
return 0;
|
||||
|
||||
out_fuse_unregister:
|
||||
subsystem_unregister(&fuse_subsys);
|
||||
out_err:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void fuse_sysfs_cleanup(void)
|
||||
{
|
||||
subsystem_unregister(&connections_subsys);
|
||||
subsystem_unregister(&fuse_subsys);
|
||||
}
|
||||
|
||||
static int __init fuse_init(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
printk("fuse init (API version %i.%i)\n",
|
||||
FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
|
||||
|
||||
INIT_LIST_HEAD(&fuse_conn_list);
|
||||
res = fuse_fs_init();
|
||||
if (res)
|
||||
goto err;
|
||||
|
||||
res = fuse_dev_init();
|
||||
if (res)
|
||||
goto err_fs_cleanup;
|
||||
|
||||
res = fuse_sysfs_init();
|
||||
if (res)
|
||||
goto err_dev_cleanup;
|
||||
|
||||
res = fuse_ctl_init();
|
||||
if (res)
|
||||
goto err_sysfs_cleanup;
|
||||
|
||||
return 0;
|
||||
|
||||
err_sysfs_cleanup:
|
||||
fuse_sysfs_cleanup();
|
||||
err_dev_cleanup:
|
||||
fuse_dev_cleanup();
|
||||
err_fs_cleanup:
|
||||
fuse_fs_cleanup();
|
||||
err:
|
||||
return res;
|
||||
}
|
||||
|
||||
static void __exit fuse_exit(void)
|
||||
{
|
||||
printk(KERN_DEBUG "fuse exit\n");
|
||||
|
||||
fuse_ctl_cleanup();
|
||||
fuse_sysfs_cleanup();
|
||||
fuse_fs_cleanup();
|
||||
fuse_dev_cleanup();
|
||||
}
|
||||
|
||||
module_init(fuse_init);
|
||||
module_exit(fuse_exit);
|
||||
Reference in New Issue
Block a user