GIT: unionfs2-2.6.27.y: Cache coherency: resync unionfs data/meta-data when lower files change

Erez Zadok ezk at fsl.cs.sunysb.edu
Thu Aug 12 23:16:38 EDT 2010


commit 9f33a375e644e255078118a5d201d43e2f21042d
Author: Erez_Zadok <ezk at cs.sunysb.edu>
Date:   Mon Jun 11 12:48:29 2007 -0400

    Cache coherency: resync unionfs data/meta-data when lower files change
    
    Whenever we revalidate a file or dentry, we check to see if any of the lower
    inodes have changed (new mtime/ctime).  If so, we revalidate the upper
    unionfs objects.  This method "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.  This also works for
    meta-data changes which change the ctime (chmod, chown, chgrp, etc).
    
    However, this doesn't work when the process re-reads the file's data via
    mmap and the data was already read before 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.  Note:
    only pages that have already been readpage'ed are not updated; any other
    pages which unionfs's ->readpage would be called on, WILL get the updated
    data.  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).
    
    Signed-off-by: Erez Zadok <ezk at cs.sunysb.edu>

diff --git a/fs/unionfs/dentry.c b/fs/unionfs/dentry.c
index 85f68f1..81db0ae 100644
--- a/fs/unionfs/dentry.c
+++ b/fs/unionfs/dentry.c
@@ -165,6 +165,13 @@ static int __unionfs_d_revalidate_one(struct dentry *dentry,
 		valid = 0;
 
 	if (valid) {
+		/*
+		 * If we get here, and we copy the meta-data from the lower
+		 * inode to our inode, then it is vital that we have already
+		 * purged all unionfs-level file data.  We do that in the
+		 * caller (__unionfs_d_revalidate_chain) by calling
+		 * purge_inode_data.
+		 */
 		fsstack_copy_attr_all(dentry->d_inode,
 				      unionfs_lower_inode(dentry->d_inode),
 				      unionfs_get_nlinks);
@@ -177,6 +184,80 @@ 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 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;
+
+	for (bindex = ibstart(inode); bindex <= ibend(inode); bindex++) {
+		lower_inode = unionfs_lower_inode_idx(inode, bindex);
+		if (!lower_inode)
+			continue;
+		/*
+		 * We may want to apply other tests to determine if the
+		 * lower inode's data has changed, but checking for changed
+		 * ctime and mtime on the lower inode should be enough.
+		 */
+		if (timespec_compare(&inode->i_mtime,
+				     &lower_inode->i_mtime) < 0) {
+			printk("unionfs: resyncing with lower inode "
+			       "(new mtime, name=%s)\n",
+			       dentry->d_name.name);
+			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",
+			       dentry->d_name.name);
+			return 1; /* ctime changed! */
+		}
+	}
+	return 0;		/* default: lower is not newer */
+}
+
+/*
+ * Purge/remove/unmap all date pages of a unionfs inode.  This is called
+ * 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
+ * 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).
+ */
+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);
+
+	if (dentry->d_inode->i_data.nrpages)
+		truncate_inode_pages(&dentry->d_inode->i_data, 0);
+}
+
+/*
  * Revalidate a parent chain of dentries, then the actual node.
  * Assumes that dentry is locked, but will lock all parents if/when needed.
  */
@@ -194,7 +275,11 @@ 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;
-	dgen = atomic_read(&UNIONFS_D(dtmp)->generation);
+	if (dtmp->d_inode && is_newer_lower(dtmp)) {
+		dgen = 0;
+		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));
@@ -256,7 +341,11 @@ int __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd)
 out_this:
 	/* finally, lock this dentry and revalidate it */
 	verify_locked(dentry);
-	dgen = atomic_read(&UNIONFS_D(dentry)->generation);
+	if (dentry->d_inode && is_newer_lower(dentry)) {
+		dgen = 0;
+		purge_inode_data(dentry);
+	} else
+		dgen = atomic_read(&UNIONFS_D(dentry)->generation);
 	valid = __unionfs_d_revalidate_one(dentry, nd);
 
 	/*


More information about the unionfs-cvs mailing list