[PATCH 5/13] Filesystem in Userspace

From: Miklos Szeredi
Date: Sat Nov 20 2004 - 18:36:21 EST


This patch adds the write filesystem operations of FUSE.

The following operations are added:

o setattr
o symlink
o mknod
o mkdir
o create
o unlink
o rmdir
o rename
o link

Signed-off-by: Miklos Szeredi <miklos@xxxxxxxxxx>
--- linux-2.6.10-rc2/fs/fuse/dir.c 2004-11-20 22:56:24.000000000 +0100
+++ linux-2.6.10-rc2-fuse/fs/fuse/dir.c 2004-11-20 22:56:24.000000000 +0100
@@ -200,6 +200,289 @@ static int fuse_lookup_iget(struct inode
return 0;
}

+static void fuse_invalidate_attr(struct inode *inode)
+{
+ struct fuse_inode *fi = INO_FI(inode);
+ fi->i_time = jiffies - 1;
+}
+
+static int lookup_new_entry(struct fuse_conn *fc, struct fuse_req *req,
+ struct inode *dir, struct dentry *entry,
+ struct fuse_entry_out *outarg, int version,
+ int mode)
+{
+ struct inode *inode;
+ struct fuse_inode *fi;
+ inode = fuse_iget(dir->i_sb, outarg->nodeid, outarg->generation,
+ &outarg->attr, version);
+ if (!inode) {
+ fuse_send_forget(fc, req, outarg->nodeid, version);
+ return -ENOMEM;
+ }
+ fuse_put_request(fc, req);
+
+ /* Don't allow userspace to do really stupid things... */
+ if ((inode->i_mode ^ mode) & S_IFMT) {
+ iput(inode);
+ printk("fuse_mknod: inode has wrong type\n");
+ return -EPROTO;
+ }
+
+ entry->d_time = time_to_jiffies(outarg->entry_valid,
+ outarg->entry_valid_nsec);
+
+ fi = INO_FI(inode);
+ fi->i_time = time_to_jiffies(outarg->attr_valid,
+ outarg->attr_valid_nsec);
+
+ d_instantiate(entry, inode);
+ fuse_invalidate_attr(dir);
+ return 0;
+}
+
+static int fuse_mknod(struct inode *dir, struct dentry *entry, int mode,
+ dev_t rdev)
+{
+ struct fuse_conn *fc = INO_FC(dir);
+ struct fuse_inode *fi = INO_FI(dir);
+ struct fuse_req *req = fuse_get_request(fc);
+ struct fuse_mknod_in inarg;
+ struct fuse_entry_out outarg;
+ int err;
+
+ if (!req)
+ return -ERESTARTSYS;
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.mode = mode;
+ inarg.rdev = new_encode_dev(rdev);
+ req->in.h.opcode = FUSE_MKNOD;
+ req->in.h.nodeid = fi->nodeid;
+ req->in.numargs = 2;
+ req->in.args[0].size = sizeof(inarg);
+ req->in.args[0].value = &inarg;
+ req->in.args[1].size = entry->d_name.len + 1;
+ req->in.args[1].value = entry->d_name.name;
+ 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;
+ if (!err)
+ err = lookup_new_entry(fc, req, dir, entry, &outarg,
+ req->out.h.unique, mode);
+ else
+ fuse_put_request(fc, req);
+ return err;
+}
+
+static int fuse_create(struct inode *dir, struct dentry *entry, int mode,
+ struct nameidata *nd)
+{
+ return fuse_mknod(dir, entry, mode, 0);
+}
+
+static int fuse_mkdir(struct inode *dir, struct dentry *entry, int mode)
+{
+ struct fuse_conn *fc = INO_FC(dir);
+ struct fuse_inode *fi = INO_FI(dir);
+ struct fuse_req *req = fuse_get_request(fc);
+ struct fuse_mkdir_in inarg;
+ struct fuse_entry_out outarg;
+ int err;
+
+ if (!req)
+ return -ERESTARTSYS;
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.mode = mode;
+ req->in.h.opcode = FUSE_MKDIR;
+ req->in.h.nodeid = fi->nodeid;
+ req->in.numargs = 2;
+ req->in.args[0].size = sizeof(inarg);
+ req->in.args[0].value = &inarg;
+ req->in.args[1].size = entry->d_name.len + 1;
+ req->in.args[1].value = entry->d_name.name;
+ 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;
+ if (!err)
+ err = lookup_new_entry(fc, req, dir, entry, &outarg,
+ req->out.h.unique, S_IFDIR);
+ else
+ fuse_put_request(fc, req);
+ return err;
+}
+
+static int fuse_symlink(struct inode *dir, struct dentry *entry,
+ const char *link)
+{
+ struct fuse_conn *fc = INO_FC(dir);
+ struct fuse_inode *fi = INO_FI(dir);
+ struct fuse_req *req;
+ struct fuse_entry_out outarg;
+ unsigned int len = strlen(link) + 1;
+ int err;
+
+ if (len > FUSE_SYMLINK_MAX)
+ return -ENAMETOOLONG;
+
+ req = fuse_get_request(fc);
+ if (!req)
+ return -ERESTARTSYS;
+
+ req->in.h.opcode = FUSE_SYMLINK;
+ req->in.h.nodeid = fi->nodeid;
+ req->in.numargs = 2;
+ req->in.args[0].size = entry->d_name.len + 1;
+ req->in.args[0].value = entry->d_name.name;
+ req->in.args[1].size = len;
+ req->in.args[1].value = link;
+ 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;
+ if (!err)
+ err = lookup_new_entry(fc, req, dir, entry, &outarg,
+ req->out.h.unique, S_IFLNK);
+ else
+ fuse_put_request(fc, req);
+ return err;
+}
+
+static int fuse_unlink(struct inode *dir, struct dentry *entry)
+{
+ struct fuse_conn *fc = INO_FC(dir);
+ struct fuse_inode *fi = INO_FI(dir);
+ struct fuse_req *req = fuse_get_request(fc);
+ int err;
+
+ if (!req)
+ return -ERESTARTSYS;
+
+ req->in.h.opcode = FUSE_UNLINK;
+ req->in.h.nodeid = fi->nodeid;
+ req->in.numargs = 1;
+ req->in.args[0].size = entry->d_name.len + 1;
+ req->in.args[0].value = entry->d_name.name;
+ request_send(fc, req);
+ err = req->out.h.error;
+ if (!err) {
+ struct inode *inode = entry->d_inode;
+
+ /* Set nlink to zero so the inode can be cleared, if
+ the inode does have more links this will be
+ discovered at the next lookup/getattr */
+ inode->i_nlink = 0;
+ fuse_invalidate_attr(inode);
+ fuse_invalidate_attr(dir);
+ }
+ fuse_put_request(fc, req);
+ return err;
+}
+
+static int fuse_rmdir(struct inode *dir, struct dentry *entry)
+{
+ struct fuse_conn *fc = INO_FC(dir);
+ struct fuse_inode *fi = INO_FI(dir);
+ struct fuse_req *req = fuse_get_request(fc);
+ int err;
+
+ if (!req)
+ return -ERESTARTSYS;
+
+ req->in.h.opcode = FUSE_RMDIR;
+ req->in.h.nodeid = fi->nodeid;
+ req->in.numargs = 1;
+ req->in.args[0].size = entry->d_name.len + 1;
+ req->in.args[0].value = entry->d_name.name;
+ request_send(fc, req);
+ err = req->out.h.error;
+ if (!err) {
+ entry->d_inode->i_nlink = 0;
+ fuse_invalidate_attr(dir);
+ }
+ fuse_put_request(fc, req);
+ return err;
+}
+
+static int fuse_rename(struct inode *olddir, struct dentry *oldent,
+ struct inode *newdir, struct dentry *newent)
+{
+ struct fuse_conn *fc = INO_FC(olddir);
+ struct fuse_inode *oldfi = INO_FI(olddir);
+ struct fuse_inode *newfi = INO_FI(newdir);
+ struct fuse_req *req = fuse_get_request(fc);
+ struct fuse_rename_in inarg;
+ int err;
+
+ if (!req)
+ return -ERESTARTSYS;
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.newdir = newfi->nodeid;
+ req->in.h.opcode = FUSE_RENAME;
+ req->in.h.nodeid = oldfi->nodeid;
+ req->in.numargs = 3;
+ req->in.args[0].size = sizeof(inarg);
+ req->in.args[0].value = &inarg;
+ req->in.args[1].size = oldent->d_name.len + 1;
+ req->in.args[1].value = oldent->d_name.name;
+ req->in.args[2].size = newent->d_name.len + 1;
+ req->in.args[2].value = newent->d_name.name;
+ request_send(fc, req);
+ err = req->out.h.error;
+ fuse_put_request(fc, req);
+ if (!err) {
+ fuse_invalidate_attr(olddir);
+ if (olddir != newdir)
+ fuse_invalidate_attr(newdir);
+ }
+ return err;
+}
+
+static int fuse_link(struct dentry *entry, struct inode *newdir,
+ struct dentry *newent)
+{
+ struct inode *inode = entry->d_inode;
+ struct fuse_conn *fc = INO_FC(inode);
+ struct fuse_inode *fi = INO_FI(inode);
+ struct fuse_inode *newfi = INO_FI(newdir);
+ struct fuse_req *req = fuse_get_request(fc);
+ struct fuse_link_in inarg;
+ struct fuse_entry_out outarg;
+ int err;
+
+ if (!req)
+ return -ERESTARTSYS;
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.newdir = newfi->nodeid;
+ req->in.h.opcode = FUSE_LINK;
+ req->in.h.nodeid = fi->nodeid;
+ req->in.numargs = 2;
+ req->in.args[0].size = sizeof(inarg);
+ req->in.args[0].value = &inarg;
+ req->in.args[1].size = newent->d_name.len + 1;
+ req->in.args[1].value = newent->d_name.name;
+ 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;
+ if (!err) {
+ /* Invalidate old entry, so attributes are refreshed */
+ d_invalidate(entry);
+ err = lookup_new_entry(fc, req, newdir, newent, &outarg,
+ req->out.h.unique, inode->i_mode);
+ } else
+ fuse_put_request(fc, req);
+ return err;
+}
+
int fuse_do_getattr(struct inode *inode)
{
struct fuse_inode *fi = INO_FI(inode);
@@ -428,6 +711,86 @@ static int fuse_dir_release(struct inode
return 0;
}

+static unsigned int iattr_to_fattr(struct iattr *iattr,
+ struct fuse_attr *fattr)
+{
+ unsigned int ivalid = iattr->ia_valid;
+ unsigned int fvalid = 0;
+
+ memset(fattr, 0, sizeof(*fattr));
+
+ if (ivalid & ATTR_MODE)
+ fvalid |= FATTR_MODE, fattr->mode = iattr->ia_mode;
+ if (ivalid & ATTR_UID)
+ fvalid |= FATTR_UID, fattr->uid = iattr->ia_uid;
+ if (ivalid & ATTR_GID)
+ fvalid |= FATTR_GID, fattr->gid = iattr->ia_gid;
+ if (ivalid & ATTR_SIZE)
+ fvalid |= FATTR_SIZE, fattr->size = iattr->ia_size;
+ /* You can only _set_ these together (they may change by themselves) */
+ if ((ivalid & (ATTR_ATIME | ATTR_MTIME)) == (ATTR_ATIME | ATTR_MTIME)) {
+ fvalid |= FATTR_ATIME | FATTR_MTIME;
+ fattr->atime = iattr->ia_atime.tv_sec;
+ fattr->mtime = iattr->ia_mtime.tv_sec;
+ }
+
+ return fvalid;
+}
+
+static int fuse_setattr(struct dentry *entry, struct iattr *attr)
+{
+ struct inode *inode = entry->d_inode;
+ struct fuse_conn *fc = INO_FC(inode);
+ struct fuse_inode *fi = INO_FI(inode);
+ struct fuse_req *req;
+ struct fuse_setattr_in inarg;
+ struct fuse_attr_out outarg;
+ int err;
+ int is_truncate = 0;
+
+ if (attr->ia_valid & ATTR_SIZE) {
+ unsigned long limit;
+ is_truncate = 1;
+ limit = current->signal->rlim[RLIMIT_FSIZE].rlim_cur;
+ if (limit != RLIM_INFINITY && attr->ia_size > (loff_t) limit) {
+ send_sig(SIGXFSZ, current, 0);
+ return -EFBIG;
+ }
+ }
+
+ req = fuse_get_request(fc);
+ if (!req)
+ return -ERESTARTSYS;
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.valid = iattr_to_fattr(attr, &inarg.attr);
+ req->in.h.opcode = FUSE_SETATTR;
+ req->in.h.nodeid = fi->nodeid;
+ 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) {
+ if (is_truncate) {
+ loff_t origsize = i_size_read(inode);
+ i_size_write(inode, outarg.attr.size);
+ if (origsize > outarg.attr.size)
+ vmtruncate(inode, outarg.attr.size);
+ }
+ change_attributes(inode, &outarg.attr);
+ fi->i_time = time_to_jiffies(outarg.attr_valid,
+ outarg.attr_valid_nsec);
+ }
+
+ return err;
+}
+
static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd)
{
if (!entry->d_inode)
@@ -480,6 +843,15 @@ static struct dentry *fuse_lookup(struct

static struct inode_operations fuse_dir_inode_operations = {
.lookup = fuse_lookup,
+ .mkdir = fuse_mkdir,
+ .symlink = fuse_symlink,
+ .unlink = fuse_unlink,
+ .rmdir = fuse_rmdir,
+ .rename = fuse_rename,
+ .link = fuse_link,
+ .setattr = fuse_setattr,
+ .create = fuse_create,
+ .mknod = fuse_mknod,
.permission = fuse_permission,
.getattr = fuse_getattr,
};
@@ -492,11 +864,13 @@ static struct file_operations fuse_dir_o
};

static struct inode_operations fuse_file_inode_operations = {
+ .setattr = fuse_setattr,
.permission = fuse_permission,
.getattr = fuse_getattr,
};

static struct inode_operations fuse_symlink_inode_operations = {
+ .setattr = fuse_setattr,
.readlink = fuse_readlink,
.follow_link = fuse_follow_link,
.getattr = fuse_getattr,
--- linux-2.6.10-rc2/include/linux/fuse.h 2004-11-20 22:56:24.000000000 +0100
+++ linux-2.6.10-rc2-fuse/include/linux/fuse.h 2004-11-20 22:56:24.000000000 +0100
@@ -50,20 +50,28 @@ struct fuse_kstatfs {
unsigned int namelen;
};

+#define FATTR_MODE (1 << 0)
+#define FATTR_UID (1 << 1)
+#define FATTR_GID (1 << 2)
+#define FATTR_SIZE (1 << 3)
+#define FATTR_ATIME (1 << 4)
+#define FATTR_MTIME (1 << 5)
+#define FATTR_CTIME (1 << 6)
+
enum fuse_opcode {
FUSE_LOOKUP = 1,
FUSE_FORGET = 2, /* no reply */
FUSE_GETATTR = 3,
- /* FUSE_SETATTR = 4, */
+ FUSE_SETATTR = 4,
FUSE_READLINK = 5,
- /* FUSE_SYMLINK = 6, */
+ FUSE_SYMLINK = 6,
FUSE_GETDIR = 7,
- /* FUSE_MKNOD = 8, */
- /* FUSE_MKDIR = 9, */
- /* FUSE_UNLINK = 10, */
- /* FUSE_RMDIR = 11, */
- /* FUSE_RENAME = 12, */
- /* FUSE_LINK = 13, */
+ FUSE_MKNOD = 8,
+ FUSE_MKDIR = 9,
+ FUSE_UNLINK = 10,
+ FUSE_RMDIR = 11,
+ FUSE_RENAME = 12,
+ FUSE_LINK = 13,
/* FUSE_OPEN = 14, */
/* FUSE_READ = 15, */
/* FUSE_WRITE = 16, */
@@ -82,6 +90,7 @@ enum fuse_opcode {
#define FUSE_MAX_IN 8192

#define FUSE_NAME_MAX 1024
+#define FUSE_SYMLINK_MAX 4096

struct fuse_entry_out {
unsigned long nodeid; /* Inode ID */
@@ -108,6 +117,28 @@ struct fuse_getdir_out {
int fd;
};

+struct fuse_mknod_in {
+ unsigned int mode;
+ unsigned int rdev;
+};
+
+struct fuse_mkdir_in {
+ unsigned int mode;
+};
+
+struct fuse_rename_in {
+ unsigned long newdir;
+};
+
+struct fuse_link_in {
+ unsigned long newdir;
+};
+
+struct fuse_setattr_in {
+ struct fuse_attr attr;
+ unsigned int valid;
+};
+
struct fuse_statfs_out {
struct fuse_kstatfs st;
};
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/