diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index b18e06daeffb33422aa2ca41a30479aba8ea8254..a058b664859c38e269d846083f48fc522b924141 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); } /* @@ -140,6 +148,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 +165,10 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) return 0; } + spin_lock(&fc->lock); + attr_version = fc->attr_version; + spin_unlock(&fc->lock); + parent = dget_parent(entry); fuse_lookup_init(req, parent->d_inode, entry, &outarg); request_send(fc, req); @@ -180,8 +193,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 +243,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 +258,10 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, return ERR_PTR(PTR_ERR(forget_req)); } + spin_lock(&fc->lock); + attr_version = fc->attr_version; + spin_unlock(&fc->lock); + fuse_lookup_init(req, dir, entry, &outarg); request_send(fc, req); err = req->out.h.error; @@ -253,7 +273,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 +297,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; @@ -363,7 +384,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; @@ -373,7 +394,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; @@ -428,7 +449,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; @@ -451,7 +472,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; @@ -663,15 +684,43 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, return err; } -static 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) { int err; struct fuse_attr_out arg; 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); + spin_lock(&fc->lock); + attr_version = fc->attr_version; + spin_unlock(&fc->lock); + req->in.h.opcode = FUSE_GETATTR; req->in.h.nodeid = get_node_id(inode); req->out.numargs = 1; @@ -685,29 +734,16 @@ static int fuse_do_getattr(struct inode *inode) 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, &arg.attr, + attr_timeout(&arg), + attr_version); + if (stat) + fuse_fillattr(inode, &arg.attr, stat); } } return err; } -/* - * Check if attributes are still valid, and if not send a GETATTR - * request to refresh them. - */ -static int fuse_refresh_attributes(struct inode *inode) -{ - struct fuse_inode *fi = get_fuse_inode(inode); - - if (fi->i_time < get_jiffies_64()) - return fuse_do_getattr(inode); - else - return 0; -} - /* * Calling into a user-controlled filesystem gives the filesystem * daemon ptrace-like capabilities over the requester process. This @@ -795,11 +831,14 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) */ if ((fc->flags & FUSE_DEFAULT_PERMISSIONS) || ((mask & MAY_EXEC) && S_ISREG(inode->i_mode))) { - err = fuse_refresh_attributes(inode); - if (err) - return err; + struct fuse_inode *fi = get_fuse_inode(inode); + if (fi->i_time < get_jiffies_64()) { + err = fuse_do_getattr(inode, NULL); + if (err) + return err; - refreshed = true; + refreshed = true; + } } if (fc->flags & FUSE_DEFAULT_PERMISSIONS) { @@ -809,7 +848,7 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) attributes. This is also needed, because the root node will at first have no permissions */ if (err == -EACCES && !refreshed) { - err = fuse_do_getattr(inode); + err = fuse_do_getattr(inode, NULL); if (!err) err = generic_permission(inode, mask, NULL); } @@ -825,7 +864,7 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) if (refreshed) return -EACCES; - err = fuse_do_getattr(inode); + err = fuse_do_getattr(inode, NULL); if (!err && !(inode->i_mode & S_IXUGO)) return -EACCES; } @@ -999,7 +1038,6 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) { 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; @@ -1053,8 +1091,7 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) return -EIO; } - fuse_change_attributes(inode, &outarg.attr); - fi->i_time = time_to_jiffies(outarg.attr_valid, outarg.attr_valid_nsec); + fuse_change_attributes(inode, &outarg.attr, attr_timeout(&outarg), 0); return 0; } @@ -1069,8 +1106,10 @@ static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry, if (!fuse_allow_task(fc, current)) return -EACCES; - err = fuse_refresh_attributes(inode); - if (!err) { + if (fi->i_time < get_jiffies_64()) + err = fuse_do_getattr(inode, stat); + else { + err = 0; generic_fillattr(inode, stat); stat->mode = fi->orig_i_mode; } diff --git a/fs/fuse/file.c b/fs/fuse/file.c index c4b98c03a46ef7c24761e70a723486067e3a9806..4a28c3d377322668bcbb78b7e9620f5b1c69b6ab 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -478,6 +478,7 @@ static int fuse_buffered_write(struct file *file, struct inode *inode, int err; size_t nres; struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_inode *fi = get_fuse_inode(inode); unsigned offset = pos & (PAGE_CACHE_SIZE - 1); struct fuse_req *req; @@ -499,6 +500,7 @@ static int fuse_buffered_write(struct file *file, struct inode *inode, if (!err) { pos += nres; spin_lock(&fc->lock); + fi->attr_version = ++fc->attr_version; if (pos > inode->i_size) i_size_write(inode, pos); spin_unlock(&fc->lock); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index e7464b8ebbfbce655ad4a6d20b289b6068d4764f..ffbcadaa7d671594c0b8f78549275b74cdeeb93c 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -67,6 +67,9 @@ struct fuse_inode { /** The sticky bit in inode->i_mode may have been removed, so preserve the original mode */ mode_t orig_i_mode; + + /** Version of last attribute change */ + u64 attr_version; }; /** FUSE specific file data */ @@ -387,6 +390,9 @@ struct fuse_conn { /** Reserved request for the DESTROY message */ struct fuse_req *destroy_req; + + /** Version counter for attribute changes */ + u64 attr_version; }; static inline struct fuse_conn *get_fuse_conn_super(struct super_block *sb) @@ -416,7 +422,8 @@ 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); + int generation, struct fuse_attr *attr, + u64 attr_valid, u64 attr_version); /** * Send FORGET command @@ -477,7 +484,8 @@ void fuse_init_symlink(struct inode *inode); /** * Change attributes of an inode */ -void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr); +void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr, + u64 attr_valid, u64 attr_version); /** * Initialize the client device diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 00bb5a255ded8b11b8770caa541451b12b185818..2167fc4fcab809af4d9ca72dd00a98974708b9e4 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -117,12 +117,22 @@ static void fuse_truncate(struct address_space *mapping, loff_t offset) unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1); } -void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr) + +void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr, + u64 attr_valid, u64 attr_version) { struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_inode *fi = get_fuse_inode(inode); loff_t oldsize; + spin_lock(&fc->lock); + if (attr_version != 0 && fi->attr_version > attr_version) { + spin_unlock(&fc->lock); + return; + } + fi->attr_version = ++fc->attr_version; + fi->i_time = attr_valid; + inode->i_ino = attr->ino; inode->i_mode = (inode->i_mode & S_IFMT) | (attr->mode & 07777); inode->i_nlink = attr->nlink; @@ -145,7 +155,6 @@ void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr) if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS)) inode->i_mode &= ~S_ISVTX; - spin_lock(&fc->lock); oldsize = inode->i_size; i_size_write(inode, attr->size); spin_unlock(&fc->lock); @@ -194,7 +203,8 @@ static int fuse_inode_set(struct inode *inode, void *_nodeidp) } struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, - int generation, struct fuse_attr *attr) + int generation, struct fuse_attr *attr, + u64 attr_valid, u64 attr_version) { struct inode *inode; struct fuse_inode *fi; @@ -222,7 +232,8 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, spin_lock(&fc->lock); fi->nlookup ++; spin_unlock(&fc->lock); - fuse_change_attributes(inode, attr); + fuse_change_attributes(inode, attr, attr_valid, attr_version); + return inode; } @@ -457,6 +468,7 @@ static struct fuse_conn *new_conn(void) } fc->reqctr = 0; fc->blocked = 1; + fc->attr_version = 1; get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key)); } out: @@ -488,7 +500,7 @@ static struct inode *get_root_inode(struct super_block *sb, unsigned mode) attr.mode = mode; attr.ino = FUSE_ROOT_ID; attr.nlink = 1; - return fuse_iget(sb, 1, 0, &attr); + return fuse_iget(sb, 1, 0, &attr, 0, 0); } static const struct super_operations fuse_super_operations = {