Gitweb:     
http://git.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=1fb69e7817296da8a6824804bb206ca1e7f31425
Commit:     1fb69e7817296da8a6824804bb206ca1e7f31425
Parent:     e57ac68378a287d6336d187b26971f35f7ee7251
Author:     Miklos Szeredi <[EMAIL PROTECTED]>
AuthorDate: Thu Oct 18 03:06:58 2007 -0700
Committer:  Linus Torvalds <[EMAIL PROTECTED]>
CommitDate: Thu Oct 18 14:37:30 2007 -0700

    fuse: fix race between getattr and write
    
    Getattr and lookup operations can be running in parallel to attribute 
changing
    operations, such as write and setattr.
    
    This means, that if for example getattr was slower than a write, the cached
    size attribute could be set to a stale value.
    
    To prevent this race, introduce a per-filesystem attribute version counter.
    This counter is incremented whenever cached attributes are modified, and the
    incremented value stored in the inode.
    
    Before storing new attributes in the cache, getattr and lookup check, using
    the version number, whether the attributes have been modified during the
    request's lifetime.  If so, the returned attributes are not cached, because
    they might be stale.
    
    Thanks to Jakub Bogusz for the bug report and test program.
    
    [EMAIL PROTECTED]: coding-style fixes]
    Signed-off-by: Miklos Szeredi <[EMAIL PROTECTED]>
    Cc: Jakub Bogusz <[EMAIL PROTECTED]>
    Signed-off-by: Andrew Morton <[EMAIL PROTECTED]>
    Signed-off-by: Linus Torvalds <[EMAIL PROTECTED]>
---
 fs/fuse/dir.c    |  125 +++++++++++++++++++++++++++++++++++------------------
 fs/fuse/file.c   |    2 +
 fs/fuse/fuse_i.h |   12 ++++-
 fs/fuse/inode.c  |   22 +++++++--
 4 files changed, 111 insertions(+), 50 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index b18e06d..a058b66 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,30 +734,17 @@ 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
  * means, that the filesystem daemon is able to record the exact
@@ -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 c4b98c0..4a28c3d 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 e7464b8..ffbcada 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 00bb5a2..2167fc4 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 = {
-
To unsubscribe from this list: send the line "unsubscribe git-commits-head" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to