diff options
Diffstat (limited to 'fs/fuse')
-rw-r--r-- | fs/fuse/dev.c | 13 | ||||
-rw-r--r-- | fs/fuse/dir.c | 212 | ||||
-rw-r--r-- | fs/fuse/file.c | 136 | ||||
-rw-r--r-- | fs/fuse/fuse_i.h | 29 |
4 files changed, 306 insertions, 84 deletions
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index d4c869c6d01b..8f873e621f41 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -151,9 +151,9 @@ void fuse_release_background(struct fuse_req *req) /* * 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 - * occured during communication with userspace, or the device file was - * closed. It decreases the referece count for the request. In case - * of a background request the referece to the stored objects are + * occurred during communication with userspace, or the device file was + * closed. It decreases the reference count for the request. In case + * of a background request the reference to the stored objects are * released. The requester thread is woken up (if still waiting), and * finally the request is either freed or put on the unused_list * @@ -184,6 +184,13 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req) fuse_putback_request() */ for (i = 1; i < FUSE_MAX_OUTSTANDING; i++) up(&fc->outstanding_sem); + } else if (req->in.h.opcode == FUSE_RELEASE && req->inode == NULL) { + /* Special case for failed iget in CREATE */ + u64 nodeid = req->in.h.nodeid; + __fuse_get_request(req); + fuse_reset_request(req); + fuse_send_forget(fc, req, nodeid, 1); + putback = 0; } if (putback) fuse_putback_request(fc, req); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index e79e49b3eec7..51f5da652771 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -13,6 +13,7 @@ #include <linux/gfp.h> #include <linux/sched.h> #include <linux/namei.h> +#include <linux/mount.h> static inline unsigned long time_to_jiffies(unsigned long sec, unsigned long nsec) @@ -73,6 +74,24 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) return 1; } +static int dir_alias(struct inode *inode) +{ + if (S_ISDIR(inode->i_mode)) { + /* Don't allow creating an alias to a directory */ + struct dentry *alias = d_find_alias(inode); + if (alias) { + dput(alias); + return 1; + } + } + return 0; +} + +static inline int invalid_nodeid(u64 nodeid) +{ + return !nodeid || nodeid == FUSE_ROOT_ID; +} + static struct dentry_operations fuse_dentry_operations = { .d_revalidate = fuse_dentry_revalidate, }; @@ -96,6 +115,8 @@ static int fuse_lookup_iget(struct inode *dir, struct dentry *entry, fuse_lookup_init(req, dir, entry, &outarg); request_send(fc, req); err = req->out.h.error; + if (!err && invalid_nodeid(outarg.nodeid)) + err = -EIO; if (!err) { inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, &outarg.attr); @@ -132,6 +153,101 @@ static void fuse_invalidate_entry(struct dentry *entry) entry->d_time = jiffies - 1; } +static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode, + struct nameidata *nd) +{ + int err; + struct inode *inode; + struct fuse_conn *fc = get_fuse_conn(dir); + struct fuse_req *req; + struct fuse_open_in inarg; + struct fuse_open_out outopen; + struct fuse_entry_out outentry; + struct fuse_inode *fi; + struct fuse_file *ff; + struct file *file; + int flags = nd->intent.open.flags - 1; + + err = -ENOSYS; + if (fc->no_create) + goto out; + + err = -ENAMETOOLONG; + if (entry->d_name.len > FUSE_NAME_MAX) + goto out; + + err = -EINTR; + req = fuse_get_request(fc); + if (!req) + goto out; + + ff = fuse_file_alloc(); + if (!ff) + goto out_put_request; + + flags &= ~O_NOCTTY; + memset(&inarg, 0, sizeof(inarg)); + inarg.flags = flags; + inarg.mode = mode; + req->in.h.opcode = FUSE_CREATE; + req->in.h.nodeid = get_node_id(dir); + req->inode = dir; + 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 = 2; + req->out.args[0].size = sizeof(outentry); + req->out.args[0].value = &outentry; + req->out.args[1].size = sizeof(outopen); + req->out.args[1].value = &outopen; + request_send(fc, req); + err = req->out.h.error; + if (err) { + if (err == -ENOSYS) + fc->no_create = 1; + goto out_free_ff; + } + + err = -EIO; + if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid)) + goto out_free_ff; + + inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation, + &outentry.attr); + err = -ENOMEM; + if (!inode) { + flags &= ~(O_CREAT | O_EXCL | O_TRUNC); + ff->fh = outopen.fh; + fuse_send_release(fc, ff, outentry.nodeid, NULL, flags, 0); + goto out_put_request; + } + fuse_put_request(fc, req); + entry->d_time = time_to_jiffies(outentry.entry_valid, + outentry.entry_valid_nsec); + fi = get_fuse_inode(inode); + fi->i_time = time_to_jiffies(outentry.attr_valid, + outentry.attr_valid_nsec); + + d_instantiate(entry, inode); + file = lookup_instantiate_filp(nd, entry, generic_file_open); + if (IS_ERR(file)) { + ff->fh = outopen.fh; + fuse_send_release(fc, ff, outentry.nodeid, inode, flags, 0); + return PTR_ERR(file); + } + fuse_finish_open(inode, file, ff, &outopen); + return 0; + + out_free_ff: + fuse_file_free(ff); + out_put_request: + fuse_put_request(fc, req); + out: + return err; +} + static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, struct inode *dir, struct dentry *entry, int mode) @@ -152,6 +268,10 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, fuse_put_request(fc, req); return err; } + if (invalid_nodeid(outarg.nodeid)) { + fuse_put_request(fc, req); + return -EIO; + } inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, &outarg.attr); if (!inode) { @@ -161,7 +281,7 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, fuse_put_request(fc, req); /* Don't allow userspace to do really stupid things... */ - if ((inode->i_mode ^ mode) & S_IFMT) { + if (((inode->i_mode ^ mode) & S_IFMT) || dir_alias(inode)) { iput(inode); return -EIO; } @@ -202,6 +322,12 @@ static int fuse_mknod(struct inode *dir, struct dentry *entry, int mode, static int fuse_create(struct inode *dir, struct dentry *entry, int mode, struct nameidata *nd) { + if (nd && (nd->flags & LOOKUP_CREATE)) { + int err = fuse_create_open(dir, entry, mode, nd); + if (err != -ENOSYS) + return err; + /* Fall back on mknod */ + } return fuse_mknod(dir, entry, mode, 0); } @@ -455,6 +581,38 @@ static int fuse_revalidate(struct dentry *entry) return fuse_do_getattr(inode); } +static int fuse_access(struct inode *inode, int mask) +{ + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_req *req; + struct fuse_access_in inarg; + int err; + + if (fc->no_access) + return 0; + + req = fuse_get_request(fc); + if (!req) + return -EINTR; + + memset(&inarg, 0, sizeof(inarg)); + inarg.mask = mask; + req->in.h.opcode = FUSE_ACCESS; + req->in.h.nodeid = get_node_id(inode); + req->inode = 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) { + fc->no_access = 1; + err = 0; + } + return err; +} + static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) { struct fuse_conn *fc = get_fuse_conn(inode); @@ -485,11 +643,11 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) return err; } else { int mode = inode->i_mode; - if ((mask & MAY_WRITE) && IS_RDONLY(inode) && - (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - return -EROFS; if ((mask & MAY_EXEC) && !S_ISDIR(mode) && !(mode & S_IXUGO)) return -EACCES; + + if (nd && (nd->flags & LOOKUP_ACCESS)) + return fuse_access(inode, mask); return 0; } } @@ -623,29 +781,29 @@ static int fuse_dir_fsync(struct file *file, struct dentry *de, int datasync) return file ? fuse_fsync_common(file, de, datasync, 1) : 0; } -static unsigned iattr_to_fattr(struct iattr *iattr, struct fuse_attr *fattr) +static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg) { unsigned ivalid = iattr->ia_valid; - unsigned fvalid = 0; - - memset(fattr, 0, sizeof(*fattr)); if (ivalid & ATTR_MODE) - fvalid |= FATTR_MODE, fattr->mode = iattr->ia_mode; + arg->valid |= FATTR_MODE, arg->mode = iattr->ia_mode; if (ivalid & ATTR_UID) - fvalid |= FATTR_UID, fattr->uid = iattr->ia_uid; + arg->valid |= FATTR_UID, arg->uid = iattr->ia_uid; if (ivalid & ATTR_GID) - fvalid |= FATTR_GID, fattr->gid = iattr->ia_gid; + arg->valid |= FATTR_GID, arg->gid = iattr->ia_gid; if (ivalid & ATTR_SIZE) - fvalid |= FATTR_SIZE, fattr->size = iattr->ia_size; + arg->valid |= FATTR_SIZE, arg->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; + arg->valid |= FATTR_ATIME | FATTR_MTIME; + arg->atime = iattr->ia_atime.tv_sec; + arg->mtime = iattr->ia_mtime.tv_sec; + } + if (ivalid & ATTR_FILE) { + struct fuse_file *ff = iattr->ia_file->private_data; + arg->valid |= FATTR_FH; + arg->fh = ff->fh; } - - return fvalid; } static int fuse_setattr(struct dentry *entry, struct iattr *attr) @@ -680,7 +838,7 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) return -EINTR; memset(&inarg, 0, sizeof(inarg)); - inarg.valid = iattr_to_fattr(attr, &inarg.attr); + iattr_to_fattr(attr, &inarg); req->in.h.opcode = FUSE_SETATTR; req->in.h.nodeid = get_node_id(inode); req->inode = inode; @@ -729,19 +887,17 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, struct nameidata *nd) { struct inode *inode; - int err = fuse_lookup_iget(dir, entry, &inode); + int err; + + err = fuse_lookup_iget(dir, entry, &inode); if (err) return ERR_PTR(err); - if (inode && S_ISDIR(inode->i_mode)) { - /* Don't allow creating an alias to a directory */ - struct dentry *alias = d_find_alias(inode); - if (alias && !(alias->d_flags & DCACHE_DISCONNECTED)) { - dput(alias); - iput(inode); - return ERR_PTR(-EIO); - } + if (inode && dir_alias(inode)) { + iput(inode); + return ERR_PTR(-EIO); } - return d_splice_alias(inode, entry); + d_add(entry, inode); + return NULL; } static int fuse_setxattr(struct dentry *entry, const char *name, diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 6454022b0536..2ca86141d13a 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -14,42 +14,18 @@ static struct file_operations fuse_direct_io_file_operations; -int fuse_open_common(struct inode *inode, struct file *file, int isdir) +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_req *req; struct fuse_open_in inarg; - struct fuse_open_out outarg; - struct fuse_file *ff; + struct fuse_req *req; int err; - 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) { - int err = fuse_do_getattr(inode); - if (err) - return err; - } - req = fuse_get_request(fc); if (!req) return -EINTR; - err = -ENOMEM; - ff = kmalloc(sizeof(struct fuse_file), GFP_KERNEL); - if (!ff) - goto out_put_request; - - ff->release_req = fuse_request_alloc(); - if (!ff->release_req) { - kfree(ff); - goto out_put_request; - } - 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; @@ -59,44 +35,110 @@ int fuse_open_common(struct inode *inode, struct file *file, int isdir) 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; + req->out.args[0].size = sizeof(*outargp); + req->out.args[0].value = outargp; request_send(fc, req); err = req->out.h.error; - if (err) { - fuse_request_free(ff->release_req); - kfree(ff); - } else { - if (!isdir && (outarg.open_flags & FOPEN_DIRECT_IO)) - file->f_op = &fuse_direct_io_file_operations; - if (!(outarg.open_flags & FOPEN_KEEP_CACHE)) - invalidate_inode_pages(inode->i_mapping); - ff->fh = outarg.fh; - file->private_data = ff; + 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->release_req = fuse_request_alloc(); + if (!ff->release_req) { + kfree(ff); + ff = NULL; + } + } + return ff; +} + +void fuse_file_free(struct fuse_file *ff) +{ + fuse_request_free(ff->release_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_inode_pages(inode->i_mapping); + 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); } - out_put_request: - fuse_put_request(fc, req); return err; } -int fuse_release_common(struct inode *inode, struct file *file, int isdir) +void fuse_send_release(struct fuse_conn *fc, struct fuse_file *ff, + u64 nodeid, struct inode *inode, int flags, int isdir) { - struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_file *ff = file->private_data; - struct fuse_req *req = ff->release_req; + struct fuse_req * req = ff->release_req; struct fuse_release_in *inarg = &req->misc.release_in; inarg->fh = ff->fh; - inarg->flags = file->f_flags & ~O_EXCL; + inarg->flags = flags; req->in.h.opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE; - req->in.h.nodeid = get_node_id(inode); + req->in.h.nodeid = nodeid; req->inode = inode; req->in.numargs = 1; req->in.args[0].size = sizeof(struct fuse_release_in); req->in.args[0].value = inarg; request_send_background(fc, req); kfree(ff); +} + +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); + u64 nodeid = get_node_id(inode); + fuse_send_release(fc, ff, nodeid, inode, file->f_flags, isdir); + } /* Return value is ignored by VFS */ return 0; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 24d761518d86..0ea5301f86be 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -266,6 +266,12 @@ struct fuse_conn { /** Is removexattr not implemented by fs? */ unsigned no_removexattr : 1; + /** Is access not implemented by fs? */ + unsigned no_access : 1; + + /** Is create not implemented by fs? */ + unsigned no_create : 1; + /** Backing dev info */ struct backing_dev_info bdi; }; @@ -337,6 +343,17 @@ size_t fuse_send_read_common(struct fuse_req *req, struct file *file, */ 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); + +/** + * Send a RELEASE request + */ +void fuse_send_release(struct fuse_conn *fc, struct fuse_file *ff, + u64 nodeid, struct inode *inode, int flags, int isdir); + /** * Send RELEASE or RELEASEDIR request */ @@ -349,22 +366,22 @@ int fuse_fsync_common(struct file *file, struct dentry *de, int datasync, int isdir); /** - * Initialise file operations on a regular file + * Initialize file operations on a regular file */ void fuse_init_file_inode(struct inode *inode); /** - * Initialise inode operations on regular files and special files + * Initialize inode operations on regular files and special files */ void fuse_init_common(struct inode *inode); /** - * Initialise inode and file operations on a directory + * Initialize inode and file operations on a directory */ void fuse_init_dir(struct inode *inode); /** - * Initialise inode operations on a symlink + * Initialize inode operations on a symlink */ void fuse_init_symlink(struct inode *inode); @@ -411,7 +428,7 @@ struct fuse_req *fuse_get_request(struct fuse_conn *fc); /** * Decrement reference count of a request. If count goes to zero put - * on unused list (preallocated) or free reqest (not preallocated). + * on unused list (preallocated) or free request (not preallocated). */ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req); @@ -431,7 +448,7 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req); void request_send_background(struct fuse_conn *fc, struct fuse_req *req); /** - * Release inodes and file assiciated with background request + * Release inodes and file associated with background request */ void fuse_release_background(struct fuse_req *req); |