ovl: hash overlay non-dir inodes by copy up origin

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 35bb956..d9fe07d 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -467,6 +467,25 @@
 	return 0;
 }
 
+static bool ovl_verify_inode(struct inode *inode, struct dentry *lowerdentry,
+			     struct dentry *upperdentry)
+{
+	struct inode *lowerinode = lowerdentry ? d_inode(lowerdentry) : NULL;
+
+	/* Lower (origin) inode must match, even if NULL */
+	if (ovl_inode_lower(inode) != lowerinode)
+		return false;
+
+	/*
+	 * Allow non-NULL __upperdentry in inode even if upperdentry is NULL.
+	 * This happens when finding a lower alias for a copied up hard link.
+	 */
+	if (upperdentry && ovl_inode_upper(inode) != d_inode(upperdentry))
+		return false;
+
+	return true;
+}
+
 struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry)
 {
 	struct dentry *lowerdentry = ovl_dentry_lower(dentry);
@@ -476,12 +495,25 @@
 	if (!realinode)
 		realinode = d_inode(lowerdentry);
 
-	if (upperdentry && !d_is_dir(upperdentry)) {
-		inode = iget5_locked(dentry->d_sb, (unsigned long) realinode,
-				     ovl_inode_test, ovl_inode_set, realinode);
+	if (!S_ISDIR(realinode->i_mode) &&
+	    (upperdentry || (lowerdentry && ovl_indexdir(dentry->d_sb)))) {
+		struct inode *key = d_inode(lowerdentry ?: upperdentry);
+
+		inode = iget5_locked(dentry->d_sb, (unsigned long) key,
+				     ovl_inode_test, ovl_inode_set, key);
 		if (!inode)
-			goto out;
+			goto out_nomem;
 		if (!(inode->i_state & I_NEW)) {
+			/*
+			 * Verify that the underlying files stored in the inode
+			 * match those in the dentry.
+			 */
+			if (!ovl_verify_inode(inode, lowerdentry, upperdentry)) {
+				iput(inode);
+				inode = ERR_PTR(-ESTALE);
+				goto out;
+			}
+
 			dput(upperdentry);
 			goto out;
 		}
@@ -490,7 +522,7 @@
 	} else {
 		inode = new_inode(dentry->d_sb);
 		if (!inode)
-			goto out;
+			goto out_nomem;
 	}
 	ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev);
 	ovl_inode_init(inode, upperdentry, lowerdentry);
@@ -502,4 +534,8 @@
 		unlock_new_inode(inode);
 out:
 	return inode;
+
+out_nomem:
+	inode = ERR_PTR(-ENOMEM);
+	goto out;
 }
diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
index 4df37e8..f7fb0c9 100644
--- a/fs/overlayfs/namei.c
+++ b/fs/overlayfs/namei.c
@@ -678,9 +678,9 @@
 		upperdentry = dget(index);
 
 	if (upperdentry || ctr) {
-		err = -ENOMEM;
 		inode = ovl_get_inode(dentry, upperdentry);
-		if (!inode)
+		err = PTR_ERR(inode);
+		if (IS_ERR(inode))
 			goto out_free_oe;
 
 		OVL_I(inode)->redirect = upperredirect;
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 90b50b8e..22ed51f 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -236,7 +236,6 @@
 {
 	struct inode *upperinode = d_inode(upperdentry);
 
-	WARN_ON(!inode_unhashed(inode));
 	WARN_ON(!inode_is_locked(upperdentry->d_parent->d_inode));
 	WARN_ON(OVL_I(inode)->__upperdentry);
 
@@ -245,7 +244,7 @@
 	 */
 	smp_wmb();
 	OVL_I(inode)->__upperdentry = upperdentry;
-	if (!S_ISDIR(upperinode->i_mode)) {
+	if (!S_ISDIR(upperinode->i_mode) && inode_unhashed(inode)) {
 		inode->i_private = upperinode;
 		__insert_inode_hash(inode, (unsigned long) upperinode);
 	}