ovl: persistent overlay inode nlink for indexed inodes
authorAmir Goldstein <amir73il@gmail.com>
Tue, 20 Jun 2017 12:35:14 +0000 (15:35 +0300)
committerMiklos Szeredi <mszeredi@redhat.com>
Tue, 4 Jul 2017 20:03:19 +0000 (22:03 +0200)
With inodes index enabled, an overlay inode nlink counts the union of upper
and non-covered lower hardlinks. During the lifetime of a non-pure upper
inode, the following nlink modifying operations can happen:

1. Lower hardlink copy up
2. Upper hardlink created, unlinked or renamed over
3. Lower hardlink whiteout or renamed over

For the first, copy up case, the union nlink does not change, whether the
operation succeeds or fails, but the upper inode nlink may change.
Therefore, before copy up, we store the union nlink value relative to the
lower inode nlink in the index inode xattr trusted.overlay.nlink.

For the second, upper hardlink case, the union nlink should be incremented
or decremented IFF the operation succeeds, aligned with nlink change of the
upper inode. Therefore, before link/unlink/rename, we store the union nlink
value relative to the upper inode nlink in the index inode.

For the last, lower cover up case, we simplify things by preceding the
whiteout or cover up with copy up. This makes sure that there is an index
upper inode where the nlink xattr can be stored before the copied up upper
entry is unlink.

Return the overlay inode nlinks for indexed upper inodes on stat(2).

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

index 9f5a47338e59bcf676f1ada5c796d76639475494..f193976560dee7b4da90963067de7669c73adfd2 100644 (file)
@@ -323,6 +323,10 @@ static int ovl_link_up(struct dentry *parent, struct dentry *dentry)
        struct dentry *upperdir = ovl_dentry_upper(parent);
        struct inode *udir = d_inode(upperdir);
 
+       err = ovl_set_nlink_lower(dentry);
+       if (err)
+               return err;
+
        inode_lock_nested(udir, I_MUTEX_PARENT);
        upper = lookup_one_len(dentry->d_name.name, upperdir,
                               dentry->d_name.len);
@@ -335,6 +339,7 @@ static int ovl_link_up(struct dentry *parent, struct dentry *dentry)
                        ovl_dentry_set_upper_alias(dentry);
        }
        inode_unlock(udir);
+       ovl_set_nlink_upper(dentry);
 
        return err;
 }
index 8b2b23181b19cfe26b692159ffac6f6fa230335c..641d9ee97f91fcc3a92b456e7007578dbe269d2e 100644 (file)
@@ -591,6 +591,7 @@ static int ovl_link(struct dentry *old, struct inode *newdir,
                    struct dentry *new)
 {
        int err;
+       bool locked = false;
        struct inode *inode;
 
        err = ovl_want_write(old);
@@ -601,6 +602,10 @@ static int ovl_link(struct dentry *old, struct inode *newdir,
        if (err)
                goto out_drop_write;
 
+       err = ovl_nlink_start(old, &locked);
+       if (err)
+               goto out_drop_write;
+
        inode = d_inode(old);
        ihold(inode);
 
@@ -608,6 +613,7 @@ static int ovl_link(struct dentry *old, struct inode *newdir,
        if (err)
                iput(inode);
 
+       ovl_nlink_end(old, locked);
 out_drop_write:
        ovl_drop_write(old);
 out:
@@ -743,8 +749,8 @@ out:
 
 static int ovl_do_remove(struct dentry *dentry, bool is_dir)
 {
-       enum ovl_path_type type;
        int err;
+       bool locked = false;
        const struct cred *old_cred;
 
        err = ovl_want_write(dentry);
@@ -755,7 +761,9 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir)
        if (err)
                goto out_drop_write;
 
-       type = ovl_path_type(dentry);
+       err = ovl_nlink_start(dentry, &locked);
+       if (err)
+               goto out_drop_write;
 
        old_cred = ovl_override_creds(dentry->d_sb);
        if (!ovl_lower_positive(dentry))
@@ -769,6 +777,7 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir)
                else
                        drop_nlink(dentry->d_inode);
        }
+       ovl_nlink_end(dentry, locked);
 out_drop_write:
        ovl_drop_write(dentry);
 out:
@@ -891,6 +900,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
                      unsigned int flags)
 {
        int err;
+       bool locked = false;
        struct dentry *old_upperdir;
        struct dentry *new_upperdir;
        struct dentry *olddentry;
@@ -934,6 +944,10 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
                err = ovl_copy_up(new);
                if (err)
                        goto out_drop_write;
+       } else {
+               err = ovl_nlink_start(new, &locked);
+               if (err)
+                       goto out_drop_write;
        }
 
        old_cred = ovl_override_creds(old->d_sb);
@@ -1072,6 +1086,7 @@ out_unlock:
        unlock_rename(new_upperdir, old_upperdir);
 out_revert_creds:
        revert_creds(old_cred);
+       ovl_nlink_end(new, locked);
 out_drop_write:
        ovl_drop_write(old);
 out:
index 44d262a0a77edcfa742a15792966861e53cf027d..196a4e5450c07e4b79b0d3771a5dd7703e7af271 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/cred.h>
 #include <linux/xattr.h>
 #include <linux/posix_acl.h>
+#include <linux/ratelimit.h>
 #include "overlayfs.h"
 
 int ovl_setattr(struct dentry *dentry, struct iattr *attr)
@@ -130,6 +131,15 @@ int ovl_getattr(const struct path *path, struct kstat *stat,
        if (is_dir && OVL_TYPE_MERGE(type))
                stat->nlink = 1;
 
+       /*
+        * Return the overlay inode nlinks for indexed upper inodes.
+        * Overlay inode nlink counts the union of the upper hardlinks
+        * and non-covered lower hardlinks. It does not include the upper
+        * index hardlink.
+        */
+       if (!is_dir && ovl_test_flag(OVL_INDEX, d_inode(dentry)))
+               stat->nlink = dentry->d_inode->i_nlink;
+
 out:
        revert_creds(old_cred);
 
@@ -442,6 +452,103 @@ static void ovl_fill_inode(struct inode *inode, umode_t mode, dev_t rdev)
        }
 }
 
+/*
+ * With inodes index enabled, an overlay inode nlink counts the union of upper
+ * hardlinks and non-covered lower hardlinks. During the lifetime of a non-pure
+ * upper inode, the following nlink modifying operations can happen:
+ *
+ * 1. Lower hardlink copy up
+ * 2. Upper hardlink created, unlinked or renamed over
+ * 3. Lower hardlink whiteout or renamed over
+ *
+ * For the first, copy up case, the union nlink does not change, whether the
+ * operation succeeds or fails, but the upper inode nlink may change.
+ * Therefore, before copy up, we store the union nlink value relative to the
+ * lower inode nlink in the index inode xattr trusted.overlay.nlink.
+ *
+ * For the second, upper hardlink case, the union nlink should be incremented
+ * or decremented IFF the operation succeeds, aligned with nlink change of the
+ * upper inode. Therefore, before link/unlink/rename, we store the union nlink
+ * value relative to the upper inode nlink in the index inode.
+ *
+ * For the last, lower cover up case, we simplify things by preceding the
+ * whiteout or cover up with copy up. This makes sure that there is an index
+ * upper inode where the nlink xattr can be stored before the copied up upper
+ * entry is unlink.
+ */
+#define OVL_NLINK_ADD_UPPER    (1 << 0)
+
+/*
+ * On-disk format for indexed nlink:
+ *
+ * nlink relative to the upper inode - "U[+-]NUM"
+ * nlink relative to the lower inode - "L[+-]NUM"
+ */
+
+static int ovl_set_nlink_common(struct dentry *dentry,
+                               struct dentry *realdentry, const char *format)
+{
+       struct inode *inode = d_inode(dentry);
+       struct inode *realinode = d_inode(realdentry);
+       char buf[13];
+       int len;
+
+       len = snprintf(buf, sizeof(buf), format,
+                      (int) (inode->i_nlink - realinode->i_nlink));
+
+       return ovl_do_setxattr(ovl_dentry_upper(dentry),
+                              OVL_XATTR_NLINK, buf, len, 0);
+}
+
+int ovl_set_nlink_upper(struct dentry *dentry)
+{
+       return ovl_set_nlink_common(dentry, ovl_dentry_upper(dentry), "U%+i");
+}
+
+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)
+{
+       int nlink_diff;
+       int nlink;
+       char buf[13];
+       int err;
+
+       if (!lowerdentry || !upperdentry || d_inode(lowerdentry)->i_nlink == 1)
+               return fallback;
+
+       err = vfs_getxattr(upperdentry, OVL_XATTR_NLINK, &buf, sizeof(buf) - 1);
+       if (err < 0)
+               goto fail;
+
+       buf[err] = '\0';
+       if ((buf[0] != 'L' && buf[0] != 'U') ||
+           (buf[1] != '+' && buf[1] != '-'))
+               goto fail;
+
+       err = kstrtoint(buf + 1, 10, &nlink_diff);
+       if (err < 0)
+               goto fail;
+
+       nlink = d_inode(buf[0] == 'L' ? lowerdentry : upperdentry)->i_nlink;
+       nlink += nlink_diff;
+
+       if (nlink <= 0)
+               goto fail;
+
+       return nlink;
+
+fail:
+       pr_warn_ratelimited("overlayfs: failed to get index nlink (%pd2, err=%i)\n",
+                           upperdentry, err);
+       return fallback;
+}
+
 struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev)
 {
        struct inode *inode;
@@ -495,6 +602,7 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry)
        if (!S_ISDIR(realinode->i_mode) &&
            (upperdentry || (lowerdentry && ovl_indexdir(dentry->d_sb)))) {
                struct inode *key = d_inode(lowerdentry ?: upperdentry);
+               unsigned int nlink;
 
                inode = iget5_locked(dentry->d_sb, (unsigned long) key,
                                     ovl_inode_test, ovl_inode_set, key);
@@ -515,7 +623,9 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry)
                        goto out;
                }
 
-               set_nlink(inode, realinode->i_nlink);
+               nlink = ovl_get_nlink(lowerdentry, upperdentry,
+                                     realinode->i_nlink);
+               set_nlink(inode, nlink);
        } else {
                inode = new_inode(dentry->d_sb);
                if (!inode)
index 751b36a5c22f253579fe643a5e33f335e6fd265f..c1321ab3822476a9e0d3e7a4f377663234b27394 100644 (file)
@@ -25,6 +25,7 @@ enum ovl_path_type {
 #define OVL_XATTR_REDIRECT OVL_XATTR_PREFIX "redirect"
 #define OVL_XATTR_ORIGIN OVL_XATTR_PREFIX "origin"
 #define OVL_XATTR_IMPURE OVL_XATTR_PREFIX "impure"
+#define OVL_XATTR_NLINK OVL_XATTR_PREFIX "nlink"
 
 enum ovl_flag {
        OVL_IMPURE,
@@ -229,6 +230,8 @@ void ovl_set_flag(unsigned long flag, struct inode *inode);
 bool ovl_test_flag(unsigned long flag, struct inode *inode);
 bool ovl_inuse_trylock(struct dentry *dentry);
 void ovl_inuse_unlock(struct dentry *dentry);
+int ovl_nlink_start(struct dentry *dentry, bool *locked);
+void ovl_nlink_end(struct dentry *dentry, bool locked);
 
 static inline bool ovl_is_impuredir(struct dentry *dentry)
 {
@@ -258,6 +261,8 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,
                         struct path *lowerstack, unsigned int numlower);
 
 /* inode.c */
+int ovl_set_nlink_upper(struct dentry *dentry);
+int ovl_set_nlink_lower(struct dentry *dentry);
 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 a290be449b8b69cb42f7a2a718a66ee939b37b94..04d5018e728ea0cc186b37abdcba80ecf9843ca1 100644 (file)
@@ -410,3 +410,69 @@ void ovl_inuse_unlock(struct dentry *dentry)
                spin_unlock(&inode->i_lock);
        }
 }
+
+/*
+ * Operations that change overlay inode and upper inode nlink need to be
+ * synchronized with copy up for persistent nlink accounting.
+ */
+int ovl_nlink_start(struct dentry *dentry, bool *locked)
+{
+       struct ovl_inode *oi = OVL_I(d_inode(dentry));
+       const struct cred *old_cred;
+       int err;
+
+       if (!d_inode(dentry) || d_is_dir(dentry))
+               return 0;
+
+       /*
+        * With inodes index is enabled, we store the union overlay nlink
+        * in an xattr on the index inode. When whiting out lower hardlinks
+        * we need to decrement the overlay persistent nlink, but before the
+        * first copy up, we have no upper index inode to store the xattr.
+        *
+        * As a workaround, before whiteout/rename over of a lower hardlink,
+        * copy up to create the upper index. Creating the upper index will
+        * initialize the overlay nlink, so it could be dropped if unlink
+        * or rename succeeds.
+        *
+        * TODO: implement metadata only index copy up when called with
+        *       ovl_copy_up_flags(dentry, O_PATH).
+        */
+       if (ovl_indexdir(dentry->d_sb) && !ovl_dentry_has_upper_alias(dentry) &&
+           d_inode(ovl_dentry_lower(dentry))->i_nlink > 1) {
+               err = ovl_copy_up(dentry);
+               if (err)
+                       return err;
+       }
+
+       err = mutex_lock_interruptible(&oi->lock);
+       if (err)
+               return err;
+
+       if (!ovl_test_flag(OVL_INDEX, d_inode(dentry)))
+               goto out;
+
+       old_cred = ovl_override_creds(dentry->d_sb);
+       /*
+        * The overlay inode nlink should be incremented/decremented IFF the
+        * upper operation succeeds, along with nlink change of upper inode.
+        * Therefore, before link/unlink/rename, we store the union nlink
+        * value relative to the upper inode nlink in an upper inode xattr.
+        */
+       err = ovl_set_nlink_upper(dentry);
+       revert_creds(old_cred);
+
+out:
+       if (err)
+               mutex_unlock(&oi->lock);
+       else
+               *locked = true;
+
+       return err;
+}
+
+void ovl_nlink_end(struct dentry *dentry, bool locked)
+{
+       if (locked)
+               mutex_unlock(&OVL_I(d_inode(dentry))->lock);
+}