diff options
-rw-r--r-- | fs/overlayfs/copy_up.c | 5 | ||||
-rw-r--r-- | fs/overlayfs/dir.c | 19 | ||||
-rw-r--r-- | fs/overlayfs/inode.c | 112 | ||||
-rw-r--r-- | fs/overlayfs/overlayfs.h | 5 | ||||
-rw-r--r-- | fs/overlayfs/util.c | 66 |
5 files changed, 204 insertions, 3 deletions
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 9f5a47338e59..f193976560de 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -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; } diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 8b2b23181b19..641d9ee97f91 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -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: diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 44d262a0a77e..196a4e5450c0 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -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) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 751b36a5c22f..c1321ab38224 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -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); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index a290be449b8b..04d5018e728e 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -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); +} |