X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=fs%2Ffuse%2Fdir.c;h=80d2f5292cf91f490532e903c0e37795e705de15;hb=13f14b4d8be225cbb11ff2be7c048590a9ccf87b;hp=bd5a772d8ccf09f09eee4310c4ce2174095f1314;hpb=eedab661a51966c454e38c17266a531aa58b4a98;p=linux-2.6 diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index bd5a772d8c..80d2f5292c 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -63,13 +63,21 @@ static u64 time_to_jiffies(unsigned long sec, unsigned long nsec) * Set dentry and possibly attribute timeouts from the lookup/mk* * replies */ -static void fuse_change_timeout(struct dentry *entry, struct fuse_entry_out *o) +static void fuse_change_entry_timeout(struct dentry *entry, + struct fuse_entry_out *o) { fuse_dentry_settime(entry, time_to_jiffies(o->entry_valid, o->entry_valid_nsec)); - if (entry->d_inode) - get_fuse_inode(entry->d_inode)->i_time = - time_to_jiffies(o->attr_valid, o->attr_valid_nsec); +} + +static u64 attr_timeout(struct fuse_attr_out *o) +{ + return time_to_jiffies(o->attr_valid, o->attr_valid_nsec); +} + +static u64 entry_attr_timeout(struct fuse_entry_out *o) +{ + return time_to_jiffies(o->attr_valid, o->attr_valid_nsec); } /* @@ -108,16 +116,37 @@ static void fuse_lookup_init(struct fuse_req *req, struct inode *dir, struct dentry *entry, struct fuse_entry_out *outarg) { + struct fuse_conn *fc = get_fuse_conn(dir); + + memset(outarg, 0, sizeof(struct fuse_entry_out)); req->in.h.opcode = FUSE_LOOKUP; req->in.h.nodeid = get_node_id(dir); req->in.numargs = 1; req->in.args[0].size = entry->d_name.len + 1; req->in.args[0].value = entry->d_name.name; req->out.numargs = 1; - req->out.args[0].size = sizeof(struct fuse_entry_out); + if (fc->minor < 9) + req->out.args[0].size = FUSE_COMPAT_ENTRY_OUT_SIZE; + else + req->out.args[0].size = sizeof(struct fuse_entry_out); req->out.args[0].value = outarg; } +static u64 fuse_get_attr_version(struct fuse_conn *fc) +{ + u64 curr_version; + + /* + * The spin lock isn't actually needed on 64bit archs, but we + * don't yet care too much about such optimizations. + */ + spin_lock(&fc->lock); + curr_version = fc->attr_version; + spin_unlock(&fc->lock); + + return curr_version; +} + /* * Check whether the dentry is still valid * @@ -140,6 +169,7 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) struct fuse_req *req; struct fuse_req *forget_req; struct dentry *parent; + u64 attr_version; /* For negative dentries, always do a fresh lookup */ if (!inode) @@ -156,6 +186,8 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) return 0; } + attr_version = fuse_get_attr_version(fc); + parent = dget_parent(entry); fuse_lookup_init(req, parent->d_inode, entry, &outarg); request_send(fc, req); @@ -180,8 +212,10 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT) return 0; - fuse_change_attributes(inode, &outarg.attr); - fuse_change_timeout(entry, &outarg); + fuse_change_attributes(inode, &outarg.attr, + entry_attr_timeout(&outarg), + attr_version); + fuse_change_entry_timeout(entry, &outarg); } return 1; } @@ -228,6 +262,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req; struct fuse_req *forget_req; + u64 attr_version; if (entry->d_name.len > FUSE_NAME_MAX) return ERR_PTR(-ENAMETOOLONG); @@ -242,6 +277,8 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, return ERR_PTR(PTR_ERR(forget_req)); } + attr_version = fuse_get_attr_version(fc); + fuse_lookup_init(req, dir, entry, &outarg); request_send(fc, req); err = req->out.h.error; @@ -253,7 +290,8 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, err = -EIO; if (!err && outarg.nodeid) { inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, - &outarg.attr); + &outarg.attr, entry_attr_timeout(&outarg), + attr_version); if (!inode) { fuse_send_forget(fc, forget_req, outarg.nodeid, 1); return ERR_PTR(-ENOMEM); @@ -276,7 +314,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, entry->d_op = &fuse_dentry_operations; if (!err) - fuse_change_timeout(entry, &outarg); + fuse_change_entry_timeout(entry, &outarg); else fuse_invalidate_entry_cache(entry); return NULL; @@ -288,12 +326,11 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, static void fuse_sync_release(struct fuse_conn *fc, struct fuse_file *ff, u64 nodeid, int flags) { - struct fuse_req *req; - - req = fuse_release_fill(ff, nodeid, flags, FUSE_RELEASE); - req->force = 1; - request_send(fc, req); - fuse_put_request(fc, req); + fuse_release_fill(ff, nodeid, flags, FUSE_RELEASE); + ff->reserved_req->force = 1; + request_send(fc, ff->reserved_req); + fuse_put_request(fc, ff->reserved_req); + kfree(ff); } /* @@ -336,6 +373,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode, flags &= ~O_NOCTTY; memset(&inarg, 0, sizeof(inarg)); + memset(&outentry, 0, sizeof(outentry)); inarg.flags = flags; inarg.mode = mode; req->in.h.opcode = FUSE_CREATE; @@ -346,7 +384,10 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode, 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); + if (fc->minor < 9) + req->out.args[0].size = FUSE_COMPAT_ENTRY_OUT_SIZE; + else + 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; @@ -364,7 +405,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode, fuse_put_request(fc, req); inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation, - &outentry.attr); + &outentry.attr, entry_attr_timeout(&outentry), 0); if (!inode) { flags &= ~(O_CREAT | O_EXCL | O_TRUNC); ff->fh = outopen.fh; @@ -374,7 +415,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode, } fuse_put_request(fc, forget_req); d_instantiate(entry, inode); - fuse_change_timeout(entry, &outentry); + fuse_change_entry_timeout(entry, &outentry); file = lookup_instantiate_filp(nd, entry, generic_file_open); if (IS_ERR(file)) { ff->fh = outopen.fh; @@ -411,9 +452,13 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, return PTR_ERR(forget_req); } + memset(&outarg, 0, sizeof(outarg)); req->in.h.nodeid = get_node_id(dir); req->out.numargs = 1; - req->out.args[0].size = sizeof(outarg); + if (fc->minor < 9) + req->out.args[0].size = FUSE_COMPAT_ENTRY_OUT_SIZE; + else + req->out.args[0].size = sizeof(outarg); req->out.args[0].value = &outarg; request_send(fc, req); err = req->out.h.error; @@ -429,7 +474,7 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, goto out_put_forget_req; inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, - &outarg.attr); + &outarg.attr, entry_attr_timeout(&outarg), 0); if (!inode) { fuse_send_forget(fc, forget_req, outarg.nodeid, 1); return -ENOMEM; @@ -452,7 +497,7 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, } else d_instantiate(entry, inode); - fuse_change_timeout(entry, &outarg); + fuse_change_entry_timeout(entry, &outarg); fuse_invalidate_attr(dir); return 0; @@ -612,6 +657,9 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, err = req->out.h.error; fuse_put_request(fc, req); if (!err) { + /* ctime changes */ + fuse_invalidate_attr(oldent->d_inode); + fuse_invalidate_attr(olddir); if (olddir != newdir) fuse_invalidate_attr(newdir); @@ -664,34 +712,103 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, return err; } -int fuse_do_getattr(struct inode *inode) +static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr, + struct kstat *stat) +{ + stat->dev = inode->i_sb->s_dev; + stat->ino = attr->ino; + stat->mode = (inode->i_mode & S_IFMT) | (attr->mode & 07777); + stat->nlink = attr->nlink; + stat->uid = attr->uid; + stat->gid = attr->gid; + stat->rdev = inode->i_rdev; + stat->atime.tv_sec = attr->atime; + stat->atime.tv_nsec = attr->atimensec; + stat->mtime.tv_sec = attr->mtime; + stat->mtime.tv_nsec = attr->mtimensec; + stat->ctime.tv_sec = attr->ctime; + stat->ctime.tv_nsec = attr->ctimensec; + stat->size = attr->size; + stat->blocks = attr->blocks; + stat->blksize = (1 << inode->i_blkbits); +} + +static int fuse_do_getattr(struct inode *inode, struct kstat *stat, + struct file *file) { int err; - struct fuse_attr_out arg; + struct fuse_getattr_in inarg; + struct fuse_attr_out outarg; struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_req *req = fuse_get_req(fc); + struct fuse_req *req; + u64 attr_version; + + req = fuse_get_req(fc); if (IS_ERR(req)) return PTR_ERR(req); + attr_version = fuse_get_attr_version(fc); + + memset(&inarg, 0, sizeof(inarg)); + memset(&outarg, 0, sizeof(outarg)); + /* Directories have separate file-handle space */ + if (file && S_ISREG(inode->i_mode)) { + struct fuse_file *ff = file->private_data; + + inarg.getattr_flags |= FUSE_GETATTR_FH; + inarg.fh = ff->fh; + } req->in.h.opcode = FUSE_GETATTR; 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(arg); - req->out.args[0].value = &arg; + if (fc->minor < 9) + req->out.args[0].size = FUSE_COMPAT_ATTR_OUT_SIZE; + else + 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 ((inode->i_mode ^ arg.attr.mode) & S_IFMT) { + if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) { make_bad_inode(inode); err = -EIO; } else { - struct fuse_inode *fi = get_fuse_inode(inode); - fuse_change_attributes(inode, &arg.attr); - fi->i_time = time_to_jiffies(arg.attr_valid, - arg.attr_valid_nsec); + fuse_change_attributes(inode, &outarg.attr, + attr_timeout(&outarg), + attr_version); + if (stat) + fuse_fillattr(inode, &outarg.attr, stat); + } + } + return err; +} + +int fuse_update_attributes(struct inode *inode, struct kstat *stat, + struct file *file, bool *refreshed) +{ + struct fuse_inode *fi = get_fuse_inode(inode); + int err; + bool r; + + if (fi->i_time < get_jiffies_64()) { + r = true; + err = fuse_do_getattr(inode, stat, file); + } else { + r = false; + err = 0; + if (stat) { + generic_fillattr(inode, stat); + stat->mode = fi->orig_i_mode; } } + + if (refreshed != NULL) + *refreshed = r; + return err; } @@ -708,7 +825,7 @@ int fuse_do_getattr(struct inode *inode) * for which the owner of the mount has ptrace privilege. This * excludes processes started by other users, suid or sgid processes. */ -static int fuse_allow_task(struct fuse_conn *fc, struct task_struct *task) +int fuse_allow_task(struct fuse_conn *fc, struct task_struct *task) { if (fc->flags & FUSE_ALLOW_OTHER) return 1; @@ -724,30 +841,6 @@ static int fuse_allow_task(struct fuse_conn *fc, struct task_struct *task) return 0; } -/* - * Check whether the inode attributes are still valid - * - * If the attribute validity timeout has expired, then fetch the fresh - * attributes with a 'getattr' request - * - * I'm not sure why cached attributes are never returned for the root - * inode, this is probably being too cautious. - */ -static int fuse_revalidate(struct dentry *entry) -{ - struct inode *inode = entry->d_inode; - struct fuse_inode *fi = get_fuse_inode(inode); - struct fuse_conn *fc = get_fuse_conn(inode); - - if (!fuse_allow_task(fc, current)) - return -EACCES; - if (get_node_id(inode) != FUSE_ROOT_ID && - fi->i_time >= get_jiffies_64()) - return 0; - - return fuse_do_getattr(inode); -} - static int fuse_access(struct inode *inode, int mask) { struct fuse_conn *fc = get_fuse_conn(inode); @@ -795,17 +888,30 @@ static int fuse_access(struct inode *inode, int mask) static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) { struct fuse_conn *fc = get_fuse_conn(inode); + bool refreshed = false; + int err = 0; if (!fuse_allow_task(fc, current)) return -EACCES; - else if (fc->flags & FUSE_DEFAULT_PERMISSIONS) { + + /* + * If attributes are needed, refresh them before proceeding + */ + if ((fc->flags & FUSE_DEFAULT_PERMISSIONS) || + ((mask & MAY_EXEC) && S_ISREG(inode->i_mode))) { + err = fuse_update_attributes(inode, NULL, NULL, &refreshed); + if (err) + return err; + } + + if (fc->flags & FUSE_DEFAULT_PERMISSIONS) { int err = generic_permission(inode, mask, NULL); /* If permission is denied, try to refresh file attributes. This is also needed, because the root node will at first have no permissions */ - if (err == -EACCES) { - err = fuse_do_getattr(inode); + if (err == -EACCES && !refreshed) { + err = fuse_do_getattr(inode, NULL, NULL); if (!err) err = generic_permission(inode, mask, NULL); } @@ -814,17 +920,19 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) exist. So if permissions are revoked this won't be noticed immediately, only after the attribute timeout has expired */ - - return err; - } else { - int mode = inode->i_mode; - if ((mask & MAY_EXEC) && !S_ISDIR(mode) && !(mode & S_IXUGO)) - return -EACCES; - - if (nd && (nd->flags & (LOOKUP_ACCESS | LOOKUP_CHDIR))) - return fuse_access(inode, mask); - return 0; + } else if (nd && (nd->flags & (LOOKUP_ACCESS | LOOKUP_CHDIR))) { + err = fuse_access(inode, mask); + } else if ((mask & MAY_EXEC) && S_ISREG(inode->i_mode)) { + if (!(inode->i_mode & S_IXUGO)) { + if (refreshed) + return -EACCES; + + err = fuse_do_getattr(inode, NULL, NULL); + if (!err && !(inode->i_mode & S_IXUGO)) + return -EACCES; + } } + return err; } static int parse_dirfile(char *buf, size_t nbytes, struct file *file, @@ -955,6 +1063,20 @@ static int fuse_dir_fsync(struct file *file, struct dentry *de, int datasync) return file ? fuse_fsync_common(file, de, datasync, 1) : 0; } +static bool update_mtime(unsigned ivalid) +{ + /* Always update if mtime is explicitly set */ + if (ivalid & ATTR_MTIME_SET) + return true; + + /* If it's an open(O_TRUNC) or an ftruncate(), don't update */ + if ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE))) + return false; + + /* In all other cases update */ + return true; +} + static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg) { unsigned ivalid = iattr->ia_valid; @@ -967,33 +1089,19 @@ static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg) arg->valid |= FATTR_GID, arg->gid = iattr->ia_gid; if (ivalid & ATTR_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)) { - arg->valid |= FATTR_ATIME | FATTR_MTIME; + if (ivalid & ATTR_ATIME) { + arg->valid |= FATTR_ATIME; 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; + arg->atimensec = iattr->ia_atime.tv_nsec; + if (!(ivalid & ATTR_ATIME_SET)) + arg->valid |= FATTR_ATIME_NOW; } -} - -static void fuse_vmtruncate(struct inode *inode, loff_t offset) -{ - struct fuse_conn *fc = get_fuse_conn(inode); - int need_trunc; - - spin_lock(&fc->lock); - need_trunc = inode->i_size > offset; - i_size_write(inode, offset); - spin_unlock(&fc->lock); - - if (need_trunc) { - struct address_space *mapping = inode->i_mapping; - unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1); - truncate_inode_pages(mapping, offset); + if ((ivalid & ATTR_MTIME) && update_mtime(ivalid)) { + arg->valid |= FATTR_MTIME; + arg->mtime = iattr->ia_mtime.tv_sec; + arg->mtimensec = iattr->ia_mtime.tv_nsec; + if (!(ivalid & ATTR_MTIME_SET)) + arg->valid |= FATTR_MTIME_NOW; } } @@ -1005,16 +1113,18 @@ static void fuse_vmtruncate(struct inode *inode, loff_t offset) * vmtruncate() doesn't allow for this case, so do the rlimit checking * and the actual truncation by hand. */ -static int fuse_setattr(struct dentry *entry, struct iattr *attr) +static int fuse_do_setattr(struct dentry *entry, struct iattr *attr, + struct file *file) { struct inode *inode = entry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_inode *fi = get_fuse_inode(inode); struct fuse_req *req; struct fuse_setattr_in inarg; struct fuse_attr_out outarg; int err; - int is_truncate = 0; + + if (!fuse_allow_task(fc, current)) + return -EACCES; if (fc->flags & FUSE_DEFAULT_PERMISSIONS) { err = inode_change_ok(inode, attr); @@ -1022,9 +1132,11 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) return err; } + if ((attr->ia_valid & ATTR_OPEN) && fc->atomic_o_trunc) + return 0; + if (attr->ia_valid & ATTR_SIZE) { unsigned long limit; - is_truncate = 1; if (IS_SWAPFILE(inode)) return -ETXTBSY; limit = current->signal->rlim[RLIMIT_FSIZE].rlim_cur; @@ -1039,44 +1151,65 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) return PTR_ERR(req); memset(&inarg, 0, sizeof(inarg)); + memset(&outarg, 0, sizeof(outarg)); iattr_to_fattr(attr, &inarg); + if (file) { + struct fuse_file *ff = file->private_data; + inarg.valid |= FATTR_FH; + inarg.fh = ff->fh; + } + if (attr->ia_valid & ATTR_SIZE) { + /* For mandatory locking in truncate */ + inarg.valid |= FATTR_LOCKOWNER; + inarg.lock_owner = fuse_lock_owner_id(fc, current->files); + } req->in.h.opcode = FUSE_SETATTR; 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); + if (fc->minor < 9) + req->out.args[0].size = FUSE_COMPAT_ATTR_OUT_SIZE; + else + 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 ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) { - make_bad_inode(inode); - err = -EIO; - } else { - if (is_truncate) - fuse_vmtruncate(inode, outarg.attr.size); - fuse_change_attributes(inode, &outarg.attr); - fi->i_time = time_to_jiffies(outarg.attr_valid, - outarg.attr_valid_nsec); - } - } else if (err == -EINTR) - fuse_invalidate_attr(inode); + if (err) { + if (err == -EINTR) + fuse_invalidate_attr(inode); + return err; + } - return err; + if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) { + make_bad_inode(inode); + return -EIO; + } + + fuse_change_attributes(inode, &outarg.attr, attr_timeout(&outarg), 0); + return 0; +} + +static int fuse_setattr(struct dentry *entry, struct iattr *attr) +{ + if (attr->ia_valid & ATTR_FILE) + return fuse_do_setattr(entry, attr, attr->ia_file); + else + return fuse_do_setattr(entry, attr, NULL); } static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry, struct kstat *stat) { struct inode *inode = entry->d_inode; - int err = fuse_revalidate(entry); - if (!err) - generic_fillattr(inode, stat); + struct fuse_conn *fc = get_fuse_conn(inode); - return err; + if (!fuse_allow_task(fc, current)) + return -EACCES; + + return fuse_update_attributes(inode, stat, NULL, NULL); } static int fuse_setxattr(struct dentry *entry, const char *name, @@ -1176,6 +1309,9 @@ static ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size) struct fuse_getxattr_out outarg; ssize_t ret; + if (!fuse_allow_task(fc, current)) + return -EACCES; + if (fc->no_listxattr) return -EOPNOTSUPP;