Unionfs: core cache-coherency code
authorErez_Zadok <ezk@cs.sunysb.edu>
Fri, 16 Nov 2007 18:56:13 +0000 (13:56 -0500)
committerRachita Kothiyal <rachita@dewey.fsl.cs.sunysb.edu>
Thu, 1 May 2008 23:02:54 +0000 (19:02 -0400)
This represents the core of the cache-coherency code, the code which
maintains time-based invariants; the lower objects should never be newer
than the upper Unionfs objects.  If they are newer, then it means that a
user has modified the lower Unionfs branches directly.  If so, then we have
to revalidate our objects by rebuilding them, and possibly discard any stale
data or meta-data.  See Documentation/filesystems/unionfs/concepts.txt under
the "Cache Coherency" section for more details of this design and
implementation.

Signed-off-by: Erez Zadok <ezk@cs.sunysb.edu>
fs/unionfs/dentry.c
fs/unionfs/union.h

index e6c5ccf169f75d8e77d6860446d61260303263d9..da94cbc19b6b9817cf50242a38615364ac6f6f05 100644 (file)
@@ -203,15 +203,12 @@ out:
  * Determine if the lower inode objects have changed from below the unionfs
  * inode.  Return 1 if changed, 0 otherwise.
  */
-static int is_newer_lower(struct dentry *dentry)
+int is_newer_lower(const struct dentry *dentry)
 {
        int bindex;
        struct inode *inode = dentry->d_inode;
        struct inode *lower_inode;
 
-       if (IS_ROOT(dentry))    /* XXX: root dentry can never be invalid?! */
-               return 0;
-
        if (!inode)
                return 0;
 
@@ -226,16 +223,18 @@ static int is_newer_lower(struct dentry *dentry)
                 */
                if (timespec_compare(&inode->i_mtime,
                                     &lower_inode->i_mtime) < 0) {
-                       printk("unionfs: resyncing with lower inode "
-                              "(new mtime, name=%s)\n",
+                       printk("unionfs: new lower inode mtime "
+                              "(bindex=%d, name=%s)\n", bindex,
                               dentry->d_name.name);
+                       show_dinode_times(dentry);
                        return 1; /* mtime changed! */
                }
                if (timespec_compare(&inode->i_ctime,
                                     &lower_inode->i_ctime) < 0) {
-                       printk("unionfs: resyncing with lower inode "
-                              "(new ctime, name=%s)\n",
+                       printk("unionfs: new lower inode ctime "
+                              "(bindex=%d, name=%s)\n", bindex,
                               dentry->d_name.name);
+                       show_dinode_times(dentry);
                        return 1; /* ctime changed! */
                }
        }
@@ -247,25 +246,23 @@ static int is_newer_lower(struct dentry *dentry)
  * when the lower inode has changed, and we have to force processes to get
  * the new data.
  *
- * XXX: this function "works" in that as long as a user process will have
- * caused unionfs to be called, directly or indirectly, even to just do
- * ->d_revalidate, then we will have purged the current unionfs data and the
+ * XXX: Our implementation works in that as long as a user process will have
+ * caused Unionfs to be called, directly or indirectly, even to just do
+ * ->d_revalidate; then we will have purged the current Unionfs data and the
  * process will see the new data.  For example, a process that continually
  * re-reads the same file's data will see the NEW data as soon as the lower
- * file had changed, upon the next read(2) syscall.  However, this doesn't
- * work when the process re-reads the file's data via mmap: once we respond
- * to ->readpage(s), then the kernel maps the page into the process's
- * address space and there doesn't appear to be a way to force the kernel to
- * invalidate those pages/mappings, and force the process to re-issue
- * ->readpage.  If there's a way to invalidate active mappings and force a
- * ->readpage, let us know please (invalidate_inode_pages2 doesn't do the
- * trick).
+ * file had changed, upon the next read(2) syscall (even if the file is
+ * still open!)  However, this doesn't work when the process re-reads the
+ * open file's data via mmap(2) (unless the user unmaps/closes the file and
+ * remaps/reopens it).  Once we respond to ->readpage(s), then the kernel
+ * maps the page into the process's address space and there doesn't appear
+ * to be a way to force the kernel to invalidate those pages/mappings, and
+ * force the process to re-issue ->readpage.  If there's a way to invalidate
+ * active mappings and force a ->readpage, let us know please
+ * (invalidate_inode_pages2 doesn't do the trick).
  */
 static inline void purge_inode_data(struct dentry *dentry)
 {
-       /* reset generation number to zero, guaranteed to be "old" */
-       atomic_set(&UNIONFS_D(dentry)->generation, 0);
-
        /* remove all non-private mappings */
        unmap_mapping_range(dentry->d_inode->i_mapping, 0, 0, 0);
 
@@ -276,8 +273,16 @@ static inline void purge_inode_data(struct dentry *dentry)
 /*
  * Revalidate a parent chain of dentries, then the actual node.
  * Assumes that dentry is locked, but will lock all parents if/when needed.
+ *
+ * If 'willwrite' is 1, and the lower inode times are not in sync, then
+ * *don't* purge_inode_data, as it could deadlock if ->write calls us and we
+ * try to truncate a locked page.  Besides, if unionfs is about to write
+ * data to a file, then there's the data unionfs is about to write is more
+ * authoritative than what's below, therefore we can safely overwrite the
+ * lower inode times and data.
  */
-int __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd)
+int __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd,
+                                int willwrite)
 {
        int valid = 0;          /* default is invalid (0); valid is 1. */
        struct dentry **chain = NULL; /* chain of dentries to reval */
@@ -291,11 +296,26 @@ int __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd)
        chain_len = 0;
        sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
        dtmp = dentry->d_parent;
-       if (dtmp->d_inode && is_newer_lower(dtmp)) {
-               dgen = 0;
+       dgen = atomic_read(&UNIONFS_D(dtmp)->generation);
+       /* XXX: should we check if is_newer_lower all the way up? */
+       if (is_newer_lower(dtmp)) {
+               /*
+                * Special case: the root dentry's generation number must
+                * always be valid, but its lower inode times don't have to
+                * be, so sync up the times only.
+                */
+               if (IS_ROOT(dtmp))
+                       unionfs_copy_attr_times(dtmp->d_inode);
+               else {
+                       /*
+                        * reset generation number to zero, guaranteed to be
+                        * "old"
+                        */
+                       dgen = 0;
+                       atomic_set(&UNIONFS_D(dtmp)->generation, dgen);
+               }
                purge_inode_data(dtmp);
-       } else
-               dgen = atomic_read(&UNIONFS_D(dtmp)->generation);
+       }
        while (sbgen != dgen) {
                /* The root entry should always be valid */
                BUG_ON(IS_ROOT(dtmp));
@@ -357,11 +377,22 @@ int __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd)
 out_this:
        /* finally, lock this dentry and revalidate it */
        verify_locked(dentry);
-       if (dentry->d_inode && is_newer_lower(dentry)) {
-               dgen = 0;
-               purge_inode_data(dentry);
-       } else
-               dgen = atomic_read(&UNIONFS_D(dentry)->generation);
+       dgen = atomic_read(&UNIONFS_D(dentry)->generation);
+       if (is_newer_lower(dentry)) {
+               /* root dentry special case as aforementioned */
+               if (IS_ROOT(dentry))
+                       unionfs_copy_attr_times(dentry->d_inode);
+               else {
+                       /*
+                        * reset generation number to zero, guaranteed to be
+                        * "old"
+                        */
+                       dgen = 0;
+                       atomic_set(&UNIONFS_D(dentry)->generation, dgen);
+               }
+               if (!willwrite)
+                       purge_inode_data(dentry);
+       }
        valid = __unionfs_d_revalidate_one(dentry, nd);
 
        /*
index 48ba4d13757bdc5e8534975bc751d5e78791cd37..54d45ce82687e17c512ca4608837582f103a99ff 100644 (file)
@@ -312,7 +312,9 @@ extern int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 extern int unionfs_unlink(struct inode *dir, struct dentry *dentry);
 extern int unionfs_rmdir(struct inode *dir, struct dentry *dentry);
 
-int __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd);
+extern int __unionfs_d_revalidate_chain(struct dentry *dentry,
+                                       struct nameidata *nd, int willwrite);
+extern int is_newer_lower(const struct dentry *dentry);
 int unionfs_force_rm(struct dentry *dentry, struct dentry **lower_dentry,
                     int bindex);
 int unionfs_silly_rename(struct dentry *dentry, struct dentry *lower_dentry);