ovl: cleanup orphan index entries
authorAmir Goldstein <amir73il@gmail.com>
Wed, 21 Jun 2017 10:46:12 +0000 (13:46 +0300)
committerMiklos Szeredi <mszeredi@redhat.com>
Tue, 4 Jul 2017 20:03:19 +0000 (22:03 +0200)
index entry should live only as long as there are upper or lower
hardlinks.

Cleanup orphan index entries on mount and when dropping the last
overlay inode nlink.

When about to cleanup or link up to orphan index and the index inode
nlink > 1, admit that something went wrong and adjust overlay nlink
to index inode nlink - 1 to prevent it from dropping below zero.
This could happen when adding lower hardlinks underneath a mounted
overlay and then trying to unlink them.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/overlayfs/inode.c
fs/overlayfs/namei.c
fs/overlayfs/overlayfs.h
fs/overlayfs/super.c
fs/overlayfs/util.c

index 196a4e5450c07e4b79b0d3771a5dd7703e7af271..69f4fc26ee398002879b7383b48f14a27e26c015 100644 (file)
@@ -510,9 +510,9 @@ int ovl_set_nlink_lower(struct dentry *dentry)
        return ovl_set_nlink_common(dentry, ovl_dentry_lower(dentry), "L%+i");
 }
 
-static unsigned int ovl_get_nlink(struct dentry *lowerdentry,
-                                 struct dentry *upperdentry,
-                                 unsigned int fallback)
+unsigned int ovl_get_nlink(struct dentry *lowerdentry,
+                          struct dentry *upperdentry,
+                          unsigned int fallback)
 {
        int nlink_diff;
        int nlink;
index 2d8b6292fe21e90cfc4d50f9cec50739bc73b382..9bc0e580a5b3fa44dbacd26ec0196e1ec4bd282b 100644 (file)
@@ -425,6 +425,11 @@ int ovl_verify_index(struct dentry *index, struct path *lowerstack,
        if (err)
                goto fail;
 
+       /* Check if index is orphan and don't warn before cleaning it */
+       if (d_inode(index)->i_nlink == 1 &&
+           ovl_get_nlink(index, origin.dentry, 0) == 0)
+               err = -ENOENT;
+
        dput(origin.dentry);
 out:
        kfree(fh);
index c1321ab3822476a9e0d3e7a4f377663234b27394..60d26605e039ede4fb95c68a54090b97baf80801 100644 (file)
@@ -263,6 +263,9 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,
 /* inode.c */
 int ovl_set_nlink_upper(struct dentry *dentry);
 int ovl_set_nlink_lower(struct dentry *dentry);
+unsigned int ovl_get_nlink(struct dentry *lowerdentry,
+                          struct dentry *upperdentry,
+                          unsigned int fallback);
 int ovl_setattr(struct dentry *dentry, struct iattr *attr);
 int ovl_getattr(const struct path *path, struct kstat *stat,
                u32 request_mask, unsigned int flags);
index f29ee08cf99f832a2103e46b7e215a7101d205b6..44dc2d6ffe0f077c09767320ba149eeac42ab5a5 100644 (file)
@@ -1069,7 +1069,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
                        if (err)
                                pr_err("overlayfs: failed to verify index dir origin\n");
 
-                       /* Cleanup bad/stale index entries */
+                       /* Cleanup bad/stale/orphan index entries */
                        if (!err)
                                err = ovl_indexdir_cleanup(ufs->indexdir,
                                                           ufs->upper_mnt,
index 04d5018e728ea0cc186b37abdcba80ecf9843ca1..c492ba75c659513c45f9ac750fbe6833a506b230 100644 (file)
@@ -14,6 +14,8 @@
 #include <linux/xattr.h>
 #include <linux/exportfs.h>
 #include <linux/uuid.h>
+#include <linux/namei.h>
+#include <linux/ratelimit.h>
 #include "overlayfs.h"
 #include "ovl_entry.h"
 
@@ -411,6 +413,58 @@ void ovl_inuse_unlock(struct dentry *dentry)
        }
 }
 
+/* Called must hold OVL_I(inode)->oi_lock */
+static void ovl_cleanup_index(struct dentry *dentry)
+{
+       struct inode *dir = ovl_indexdir(dentry->d_sb)->d_inode;
+       struct dentry *lowerdentry = ovl_dentry_lower(dentry);
+       struct dentry *upperdentry = ovl_dentry_upper(dentry);
+       struct dentry *index = NULL;
+       struct inode *inode;
+       struct qstr name;
+       int err;
+
+       err = ovl_get_index_name(lowerdentry, &name);
+       if (err)
+               goto fail;
+
+       inode = d_inode(upperdentry);
+       if (inode->i_nlink != 1) {
+               pr_warn_ratelimited("overlayfs: cleanup linked index (%pd2, ino=%lu, nlink=%u)\n",
+                                   upperdentry, inode->i_ino, inode->i_nlink);
+               /*
+                * We either have a bug with persistent union nlink or a lower
+                * hardlink was added while overlay is mounted. Adding a lower
+                * hardlink and then unlinking all overlay hardlinks would drop
+                * overlay nlink to zero before all upper inodes are unlinked.
+                * As a safety measure, when that situation is detected, set
+                * the overlay nlink to the index inode nlink minus one for the
+                * index entry itself.
+                */
+               set_nlink(d_inode(dentry), inode->i_nlink - 1);
+               ovl_set_nlink_upper(dentry);
+               goto out;
+       }
+
+       inode_lock_nested(dir, I_MUTEX_PARENT);
+       /* TODO: whiteout instead of cleanup to block future open by handle */
+       index = lookup_one_len(name.name, ovl_indexdir(dentry->d_sb), name.len);
+       err = PTR_ERR(index);
+       if (!IS_ERR(index))
+               err = ovl_cleanup(dir, index);
+       inode_unlock(dir);
+       if (err)
+               goto fail;
+
+out:
+       dput(index);
+       return;
+
+fail:
+       pr_err("overlayfs: cleanup index of '%pd2' failed (%i)\n", dentry, err);
+       goto out;
+}
+
 /*
  * Operations that change overlay inode and upper inode nlink need to be
  * synchronized with copy up for persistent nlink accounting.
@@ -473,6 +527,16 @@ out:
 
 void ovl_nlink_end(struct dentry *dentry, bool locked)
 {
-       if (locked)
+       if (locked) {
+               if (ovl_test_flag(OVL_INDEX, d_inode(dentry)) &&
+                   d_inode(dentry)->i_nlink == 0) {
+                       const struct cred *old_cred;
+
+                       old_cred = ovl_override_creds(dentry->d_sb);
+                       ovl_cleanup_index(dentry);
+                       revert_creds(old_cred);
+               }
+
                mutex_unlock(&OVL_I(d_inode(dentry))->lock);
+       }
 }