diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 96b1bdcf36745555316b1d3d7121a255e2bbd75c..d456e9fb012a06c68bc428b9f0e539938aaf2fc6 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -163,12 +163,17 @@ static int ovl_dir_getattr(struct vfsmount *mnt, struct dentry *dentry, /* Common operations required to be done after creation of file on upper */ static void ovl_instantiate(struct dentry *dentry, struct inode *inode, - struct dentry *newdentry) + struct dentry *newdentry, bool hardlink) { ovl_dentry_version_inc(dentry->d_parent); ovl_dentry_update(dentry, newdentry); - ovl_inode_update(inode, d_inode(newdentry)); - ovl_copyattr(newdentry->d_inode, inode); + if (!hardlink) { + ovl_inode_update(inode, d_inode(newdentry)); + ovl_copyattr(newdentry->d_inode, inode); + } else { + WARN_ON(ovl_inode_real(inode, NULL) != d_inode(newdentry)); + inc_nlink(inode); + } d_instantiate(dentry, inode); } @@ -191,7 +196,7 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode, if (err) goto out_dput; - ovl_instantiate(dentry, inode, newdentry); + ovl_instantiate(dentry, inode, newdentry, !!hardlink); newdentry = NULL; out_dput: dput(newdentry); @@ -361,7 +366,8 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, /* * mode could have been mutilated due to umask (e.g. sgid directory) */ - if (!S_ISLNK(stat->mode) && newdentry->d_inode->i_mode != stat->mode) { + if (!hardlink && + !S_ISLNK(stat->mode) && newdentry->d_inode->i_mode != stat->mode) { struct iattr attr = { .ia_valid = ATTR_MODE, .ia_mode = stat->mode, @@ -373,7 +379,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, goto out_cleanup; } - if (S_ISDIR(stat->mode)) { + if (!hardlink && S_ISDIR(stat->mode)) { err = ovl_set_opaque(newdentry); if (err) goto out_cleanup; @@ -389,7 +395,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, if (err) goto out_cleanup; } - ovl_instantiate(dentry, inode, newdentry); + ovl_instantiate(dentry, inode, newdentry, !!hardlink); newdentry = NULL; out_dput2: dput(upper); @@ -405,28 +411,17 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, goto out_dput2; } -static int ovl_create_or_link(struct dentry *dentry, int mode, dev_t rdev, - const char *link, struct dentry *hardlink) +static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, + struct kstat *stat, const char *link, + struct dentry *hardlink) { int err; - struct inode *inode; const struct cred *old_cred; struct cred *override_cred; - struct kstat stat = { - .rdev = rdev, - }; - - err = -ENOMEM; - inode = ovl_new_inode(dentry->d_sb, mode); - if (!inode) - goto out; err = ovl_copy_up(dentry->d_parent); if (err) - goto out_iput; - - inode_init_owner(inode, dentry->d_parent->d_inode, mode); - stat.mode = inode->i_mode; + return err; old_cred = ovl_override_creds(dentry->d_sb); err = -ENOMEM; @@ -438,10 +433,10 @@ static int ovl_create_or_link(struct dentry *dentry, int mode, dev_t rdev, put_cred(override_cred); if (!ovl_dentry_is_opaque(dentry)) - err = ovl_create_upper(dentry, inode, &stat, link, + err = ovl_create_upper(dentry, inode, stat, link, hardlink); else - err = ovl_create_over_whiteout(dentry, inode, &stat, + err = ovl_create_over_whiteout(dentry, inode, stat, link, hardlink); } revert_creds(old_cred); @@ -451,11 +446,7 @@ static int ovl_create_or_link(struct dentry *dentry, int mode, dev_t rdev, WARN_ON(inode->i_mode != realinode->i_mode); WARN_ON(!uid_eq(inode->i_uid, realinode->i_uid)); WARN_ON(!gid_eq(inode->i_gid, realinode->i_gid)); - inode = NULL; } -out_iput: - iput(inode); -out: return err; } @@ -463,13 +454,30 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, const char *link) { int err; + struct inode *inode; + struct kstat stat = { + .rdev = rdev, + }; err = ovl_want_write(dentry); - if (!err) { - err = ovl_create_or_link(dentry, mode, rdev, link, NULL); - ovl_drop_write(dentry); - } + if (err) + goto out; + + err = -ENOMEM; + inode = ovl_new_inode(dentry->d_sb, mode); + if (!inode) + goto out_drop_write; + + inode_init_owner(inode, dentry->d_parent->d_inode, mode); + stat.mode = inode->i_mode; + + err = ovl_create_or_link(dentry, inode, &stat, link, NULL); + if (err) + iput(inode); +out_drop_write: + ovl_drop_write(dentry); +out: return err; } @@ -504,7 +512,7 @@ static int ovl_link(struct dentry *old, struct inode *newdir, struct dentry *new) { int err; - struct dentry *upper; + struct inode *inode; err = ovl_want_write(old); if (err) @@ -514,8 +522,12 @@ static int ovl_link(struct dentry *old, struct inode *newdir, if (err) goto out_drop_write; - upper = ovl_dentry_upper(old); - err = ovl_create_or_link(new, upper->d_inode->i_mode, 0, NULL, upper); + inode = d_inode(old); + ihold(inode); + + err = ovl_create_or_link(new, inode, NULL, NULL, ovl_dentry_upper(old)); + if (err) + iput(inode); out_drop_write: ovl_drop_write(old); @@ -684,6 +696,8 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir) else err = ovl_remove_and_whiteout(dentry, is_dir); revert_creds(old_cred); + if (!err && !is_dir) + drop_nlink(dentry->d_inode); out_drop_write: ovl_drop_write(dentry); out: diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 2bdd3cae0f7171d76464f4e2bc8c1212b23e9a81..6be0d276fd0522ca0a4fcf8f0c4fa05384d8118c 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -409,14 +409,8 @@ static const struct inode_operations ovl_symlink_inode_operations = { .update_time = ovl_update_time, }; -struct inode *ovl_new_inode(struct super_block *sb, umode_t mode) +static void ovl_fill_inode(struct inode *inode, umode_t mode) { - struct inode *inode; - - inode = new_inode(sb); - if (!inode) - return NULL; - inode->i_ino = get_next_ino(); inode->i_mode = mode; inode->i_flags |= S_NOCMTIME; @@ -432,6 +426,10 @@ struct inode *ovl_new_inode(struct super_block *sb, umode_t mode) inode->i_op = &ovl_symlink_inode_operations; break; + default: + WARN(1, "illegal file type: %i\n", mode); + /* Fall through */ + case S_IFREG: case S_IFSOCK: case S_IFBLK: @@ -439,11 +437,42 @@ struct inode *ovl_new_inode(struct super_block *sb, umode_t mode) case S_IFIFO: inode->i_op = &ovl_file_inode_operations; break; + } +} - default: - WARN(1, "illegal file type: %i\n", mode); - iput(inode); - inode = NULL; +struct inode *ovl_new_inode(struct super_block *sb, umode_t mode) +{ + struct inode *inode; + + inode = new_inode(sb); + if (inode) + ovl_fill_inode(inode, mode); + + return inode; +} + +static int ovl_inode_test(struct inode *inode, void *data) +{ + return ovl_inode_real(inode, NULL) == data; +} + +static int ovl_inode_set(struct inode *inode, void *data) +{ + inode->i_private = (void *) (((unsigned long) data) | OVL_ISUPPER_MASK); + return 0; +} + +struct inode *ovl_get_inode(struct super_block *sb, struct inode *realinode) + +{ + struct inode *inode; + + inode = iget5_locked(sb, (unsigned long) realinode, + ovl_inode_test, ovl_inode_set, realinode); + if (inode && inode->i_state & I_NEW) { + ovl_fill_inode(inode, realinode->i_mode); + set_nlink(inode, realinode->i_nlink); + unlock_new_inode(inode); } return inode; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 6410209ea6167bbae7083a6fe8f5febc0a2df1cc..abeef1e6db56877a22d3f2853a64031e5703a68f 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -195,6 +195,7 @@ int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags); int ovl_update_time(struct inode *inode, struct timespec *ts, int flags); struct inode *ovl_new_inode(struct super_block *sb, umode_t mode); +struct inode *ovl_get_inode(struct super_block *sb, struct inode *realinode); static inline void ovl_copyattr(struct inode *from, struct inode *to) { to->i_uid = from->i_uid; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 313f773652fff87f2355bc02e09175a953b1acbb..44c4510f5adf6cde4e021d15ae08163fed348e37 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -232,8 +232,11 @@ void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) void ovl_inode_update(struct inode *inode, struct inode *upperinode) { WARN_ON(!upperinode); + WARN_ON(!inode_unhashed(inode)); WRITE_ONCE(inode->i_private, (unsigned long) upperinode | OVL_ISUPPER_MASK); + if (!S_ISDIR(upperinode->i_mode)) + __insert_inode_hash(inode, (unsigned long) upperinode); } void ovl_dentry_version_inc(struct dentry *dentry) @@ -572,10 +575,15 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, realinode = d_inode(realdentry); err = -ENOMEM; - inode = ovl_new_inode(dentry->d_sb, realinode->i_mode); + if (upperdentry && !d_is_dir(upperdentry)) { + inode = ovl_get_inode(dentry->d_sb, realinode); + } else { + inode = ovl_new_inode(dentry->d_sb, realinode->i_mode); + if (inode) + ovl_inode_init(inode, realinode, !!upperdentry); + } if (!inode) goto out_free_oe; - ovl_inode_init(inode, realinode, !!upperdentry); ovl_copyattr(realdentry->d_inode, inode); }