ovl: lookup index entry for copy up origin
authorAmir Goldstein <amir73il@gmail.com>
Wed, 21 Jun 2017 12:28:41 +0000 (15:28 +0300)
committerMiklos Szeredi <mszeredi@redhat.com>
Tue, 4 Jul 2017 20:03:17 +0000 (22:03 +0200)
When inodes index feature is enabled, lookup in indexdir for the index
entry of lower real inode or copy up origin inode. The index entry name
is the hex representation of the lower inode file handle.

If the index dentry in negative, then either no lower aliases have been
copied up yet, or aliases have been copied up in older kernels and are
not indexed.

If the index dentry for a copy up origin inode is positive, but points
to an inode different than the upper inode, then either the upper inode
has been copied up and not indexed or it was indexed, but since then
index dir was cleared. Either way, that index cannot be used to indentify
the overlay inode.

If a positive dentry that matches the upper inode was found, then it is
safe to use the copy up origin st_ino for upper hardlinks, because all
indexed upper hardlinks are represented by the same overlay inode as the
copy up origin.

Set the INDEX type flag on an indexed upper dentry. A non-upper dentry
may also have a positive index from copy up of another lower hardlink.
This situation will be handled by following patches.

Index lookup is going to be used to prevent breaking hardlinks on copy up.

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

index 23d64d51f331b68464fd0cdac207a1b1b5510abf..35bb956af8e8a2e1c0aa13d66ada81458774cf3b 100644 (file)
@@ -96,11 +96,15 @@ int ovl_getattr(const struct path *path, struct kstat *stat,
 
                        WARN_ON_ONCE(stat->dev != lowerstat.dev);
                        /*
-                        * Lower hardlinks are broken on copy up to different
+                        * Lower hardlinks may be broken on copy up to different
                         * upper files, so we cannot use the lower origin st_ino
                         * for those different files, even for the same fs case.
+                        * With inodes index enabled, it is safe to use st_ino
+                        * of an indexed hardlinked origin. The index validates
+                        * that the upper hardlink is not broken.
                         */
-                       if (is_dir || lowerstat.nlink == 1)
+                       if (is_dir || lowerstat.nlink == 1 ||
+                           ovl_test_flag(OVL_INDEX, d_inode(dentry)))
                                stat->ino = lowerstat.ino;
                }
                stat->dev = dentry->d_sb->s_dev;
index 0c816e9aa50c0337cf434da61bd1b192dbe4a06e..3bec4cb39967804450504bb3da85339a9efebe09 100644 (file)
@@ -378,6 +378,94 @@ fail:
        goto out;
 }
 
+/*
+ * Lookup in indexdir for the index entry of a lower real inode or a copy up
+ * origin inode. The index entry name is the hex representation of the lower
+ * inode file handle.
+ *
+ * If the index dentry in negative, then either no lower aliases have been
+ * copied up yet, or aliases have been copied up in older kernels and are
+ * not indexed.
+ *
+ * If the index dentry for a copy up origin inode is positive, but points
+ * to an inode different than the upper inode, then either the upper inode
+ * has been copied up and not indexed or it was indexed, but since then
+ * index dir was cleared. Either way, that index cannot be used to indentify
+ * the overlay inode.
+ */
+int ovl_get_index_name(struct dentry *origin, struct qstr *name)
+{
+       int err;
+       struct ovl_fh *fh;
+       char *n, *s;
+
+       fh = ovl_encode_fh(origin, false);
+       if (IS_ERR(fh))
+               return PTR_ERR(fh);
+
+       err = -ENOMEM;
+       n = kzalloc(fh->len * 2, GFP_TEMPORARY);
+       if (n) {
+               s  = bin2hex(n, fh, fh->len);
+               *name = (struct qstr) QSTR_INIT(n, s - n);
+               err = 0;
+       }
+       kfree(fh);
+
+       return err;
+
+}
+
+static struct dentry *ovl_lookup_index(struct dentry *dentry,
+                                      struct dentry *upper,
+                                      struct dentry *origin)
+{
+       struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
+       struct dentry *index;
+       struct inode *inode;
+       struct qstr name;
+       int err;
+
+       err = ovl_get_index_name(origin, &name);
+       if (err)
+               return ERR_PTR(err);
+
+       index = lookup_one_len_unlocked(name.name, ofs->indexdir, name.len);
+       if (IS_ERR(index)) {
+               pr_warn_ratelimited("overlayfs: failed inode index lookup (ino=%lu, key=%*s, err=%i);\n"
+                                   "overlayfs: mount with '-o index=off' to disable inodes index.\n",
+                                   d_inode(origin)->i_ino, name.len, name.name,
+                                   err);
+               goto out;
+       }
+
+       if (d_is_negative(index)) {
+               if (upper && d_inode(origin)->i_nlink > 1) {
+                       pr_warn_ratelimited("overlayfs: hard link with origin but no index (ino=%lu).\n",
+                                           d_inode(origin)->i_ino);
+                       goto fail;
+               }
+
+               dput(index);
+               index = NULL;
+       } else if (upper && d_inode(index) != d_inode(upper)) {
+               inode = d_inode(index);
+               pr_warn_ratelimited("overlayfs: wrong index found (index ino: %lu, upper ino: %lu).\n",
+                                   d_inode(index)->i_ino,
+                                   d_inode(upper)->i_ino);
+               goto fail;
+       }
+
+out:
+       kfree(name.name);
+       return index;
+
+fail:
+       dput(index);
+       index = ERR_PTR(-EIO);
+       goto out;
+}
+
 /*
  * Returns next layer in stack starting from top.
  * Returns -1 if this is the last layer.
@@ -409,6 +497,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
        struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata;
        struct path *stack = NULL;
        struct dentry *upperdir, *upperdentry = NULL;
+       struct dentry *index = NULL;
        unsigned int ctr = 0;
        struct inode *inode = NULL;
        bool upperopaque = false;
@@ -506,6 +595,18 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
                }
        }
 
+       /* Lookup index by lower inode and verify it matches upper inode */
+       if (ctr && !d.is_dir && ovl_indexdir(dentry->d_sb)) {
+               struct dentry *origin = stack[0].dentry;
+
+               index = ovl_lookup_index(dentry, upperdentry, origin);
+               if (IS_ERR(index)) {
+                       err = PTR_ERR(index);
+                       index = NULL;
+                       goto out_put;
+               }
+       }
+
        oe = ovl_alloc_entry(ctr);
        err = -ENOMEM;
        if (!oe)
@@ -515,6 +616,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
        memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr);
        dentry->d_fsdata = oe;
 
+       if (index && !upperdentry)
+               upperdentry = dget(index);
+
        if (upperdentry || ctr) {
                err = -ENOMEM;
                inode = ovl_get_inode(dentry, upperdentry);
@@ -522,9 +626,12 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
                        goto out_free_oe;
 
                OVL_I(inode)->redirect = upperredirect;
+               if (index)
+                       ovl_set_flag(OVL_INDEX, inode);
        }
 
        revert_creds(old_cred);
+       dput(index);
        kfree(stack);
        kfree(d.redirect);
        d_add(dentry, inode);
@@ -535,6 +642,7 @@ out_free_oe:
        dentry->d_fsdata = NULL;
        kfree(oe);
 out_put:
+       dput(index);
        for (i = 0; i < ctr; i++)
                dput(stack[i].dentry);
        kfree(stack);
index 58bbd135a7b3a64b3eb998f33973431da159a93a..437a0301e1b6d9c829e9828023bc17adfcfcf7fe 100644 (file)
@@ -28,6 +28,7 @@ enum ovl_path_type {
 
 enum ovl_flag {
        OVL_IMPURE,
+       OVL_INDEX,
 };
 
 /*
@@ -236,6 +237,7 @@ static inline bool ovl_is_impuredir(struct dentry *dentry)
 /* namei.c */
 int ovl_verify_origin(struct dentry *dentry, struct vfsmount *mnt,
                      struct dentry *origin, bool is_upper, bool set);
+int ovl_get_index_name(struct dentry *origin, struct qstr *name);
 int ovl_path_next(int idx, struct dentry *dentry, struct path *path);
 struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags);
 bool ovl_lower_positive(struct dentry *dentry);