ovl: share inode for hard link
Inode attributes are copied up to overlay inode (uid, gid, mode, atime, mtime, ctime) so generic code using these fields works correcty. If a hard link is created in overlayfs separate inodes are allocated for each link. If chmod/chown/etc. is performed on one of the links then the inode belonging to the other ones won't be updated. This patch attempts to fix this by sharing inodes for hard links. Use inode hash (with real inode pointer as a key) to make sure overlay inodes are shared for hard links on upper. Hard links on lower are still split (which is not user observable until the copy-up happens, see Documentation/filesystems/overlayfs.txt under "Non-standard behavior"). The inode is only inserted in the hash if it is non-directoy and upper. Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
This commit is contained in:
parent
39b681f802
commit
51f7e52dc9
4 changed files with 100 additions and 48 deletions
|
@ -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 */
|
/* Common operations required to be done after creation of file on upper */
|
||||||
static void ovl_instantiate(struct dentry *dentry, struct inode *inode,
|
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_version_inc(dentry->d_parent);
|
||||||
ovl_dentry_update(dentry, newdentry);
|
ovl_dentry_update(dentry, newdentry);
|
||||||
|
if (!hardlink) {
|
||||||
ovl_inode_update(inode, d_inode(newdentry));
|
ovl_inode_update(inode, d_inode(newdentry));
|
||||||
ovl_copyattr(newdentry->d_inode, inode);
|
ovl_copyattr(newdentry->d_inode, inode);
|
||||||
|
} else {
|
||||||
|
WARN_ON(ovl_inode_real(inode, NULL) != d_inode(newdentry));
|
||||||
|
inc_nlink(inode);
|
||||||
|
}
|
||||||
d_instantiate(dentry, inode);
|
d_instantiate(dentry, inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +196,7 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
|
||||||
if (err)
|
if (err)
|
||||||
goto out_dput;
|
goto out_dput;
|
||||||
|
|
||||||
ovl_instantiate(dentry, inode, newdentry);
|
ovl_instantiate(dentry, inode, newdentry, !!hardlink);
|
||||||
newdentry = NULL;
|
newdentry = NULL;
|
||||||
out_dput:
|
out_dput:
|
||||||
dput(newdentry);
|
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)
|
* 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 = {
|
struct iattr attr = {
|
||||||
.ia_valid = ATTR_MODE,
|
.ia_valid = ATTR_MODE,
|
||||||
.ia_mode = stat->mode,
|
.ia_mode = stat->mode,
|
||||||
|
@ -373,7 +379,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
|
||||||
goto out_cleanup;
|
goto out_cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISDIR(stat->mode)) {
|
if (!hardlink && S_ISDIR(stat->mode)) {
|
||||||
err = ovl_set_opaque(newdentry);
|
err = ovl_set_opaque(newdentry);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_cleanup;
|
goto out_cleanup;
|
||||||
|
@ -389,7 +395,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
|
||||||
if (err)
|
if (err)
|
||||||
goto out_cleanup;
|
goto out_cleanup;
|
||||||
}
|
}
|
||||||
ovl_instantiate(dentry, inode, newdentry);
|
ovl_instantiate(dentry, inode, newdentry, !!hardlink);
|
||||||
newdentry = NULL;
|
newdentry = NULL;
|
||||||
out_dput2:
|
out_dput2:
|
||||||
dput(upper);
|
dput(upper);
|
||||||
|
@ -405,28 +411,17 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
|
||||||
goto out_dput2;
|
goto out_dput2;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ovl_create_or_link(struct dentry *dentry, int mode, dev_t rdev,
|
static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
|
||||||
const char *link, struct dentry *hardlink)
|
struct kstat *stat, const char *link,
|
||||||
|
struct dentry *hardlink)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
struct inode *inode;
|
|
||||||
const struct cred *old_cred;
|
const struct cred *old_cred;
|
||||||
struct cred *override_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);
|
err = ovl_copy_up(dentry->d_parent);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_iput;
|
return err;
|
||||||
|
|
||||||
inode_init_owner(inode, dentry->d_parent->d_inode, mode);
|
|
||||||
stat.mode = inode->i_mode;
|
|
||||||
|
|
||||||
old_cred = ovl_override_creds(dentry->d_sb);
|
old_cred = ovl_override_creds(dentry->d_sb);
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
|
@ -438,10 +433,10 @@ static int ovl_create_or_link(struct dentry *dentry, int mode, dev_t rdev,
|
||||||
put_cred(override_cred);
|
put_cred(override_cred);
|
||||||
|
|
||||||
if (!ovl_dentry_is_opaque(dentry))
|
if (!ovl_dentry_is_opaque(dentry))
|
||||||
err = ovl_create_upper(dentry, inode, &stat, link,
|
err = ovl_create_upper(dentry, inode, stat, link,
|
||||||
hardlink);
|
hardlink);
|
||||||
else
|
else
|
||||||
err = ovl_create_over_whiteout(dentry, inode, &stat,
|
err = ovl_create_over_whiteout(dentry, inode, stat,
|
||||||
link, hardlink);
|
link, hardlink);
|
||||||
}
|
}
|
||||||
revert_creds(old_cred);
|
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(inode->i_mode != realinode->i_mode);
|
||||||
WARN_ON(!uid_eq(inode->i_uid, realinode->i_uid));
|
WARN_ON(!uid_eq(inode->i_uid, realinode->i_uid));
|
||||||
WARN_ON(!gid_eq(inode->i_gid, realinode->i_gid));
|
WARN_ON(!gid_eq(inode->i_gid, realinode->i_gid));
|
||||||
inode = NULL;
|
|
||||||
}
|
}
|
||||||
out_iput:
|
|
||||||
iput(inode);
|
|
||||||
out:
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,13 +454,30 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
|
||||||
const char *link)
|
const char *link)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
struct inode *inode;
|
||||||
|
struct kstat stat = {
|
||||||
|
.rdev = rdev,
|
||||||
|
};
|
||||||
|
|
||||||
err = ovl_want_write(dentry);
|
err = ovl_want_write(dentry);
|
||||||
if (!err) {
|
if (err)
|
||||||
err = ovl_create_or_link(dentry, mode, rdev, link, NULL);
|
goto out;
|
||||||
ovl_drop_write(dentry);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,7 +512,7 @@ static int ovl_link(struct dentry *old, struct inode *newdir,
|
||||||
struct dentry *new)
|
struct dentry *new)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
struct dentry *upper;
|
struct inode *inode;
|
||||||
|
|
||||||
err = ovl_want_write(old);
|
err = ovl_want_write(old);
|
||||||
if (err)
|
if (err)
|
||||||
|
@ -514,8 +522,12 @@ static int ovl_link(struct dentry *old, struct inode *newdir,
|
||||||
if (err)
|
if (err)
|
||||||
goto out_drop_write;
|
goto out_drop_write;
|
||||||
|
|
||||||
upper = ovl_dentry_upper(old);
|
inode = d_inode(old);
|
||||||
err = ovl_create_or_link(new, upper->d_inode->i_mode, 0, NULL, upper);
|
ihold(inode);
|
||||||
|
|
||||||
|
err = ovl_create_or_link(new, inode, NULL, NULL, ovl_dentry_upper(old));
|
||||||
|
if (err)
|
||||||
|
iput(inode);
|
||||||
|
|
||||||
out_drop_write:
|
out_drop_write:
|
||||||
ovl_drop_write(old);
|
ovl_drop_write(old);
|
||||||
|
@ -684,6 +696,8 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir)
|
||||||
else
|
else
|
||||||
err = ovl_remove_and_whiteout(dentry, is_dir);
|
err = ovl_remove_and_whiteout(dentry, is_dir);
|
||||||
revert_creds(old_cred);
|
revert_creds(old_cred);
|
||||||
|
if (!err && !is_dir)
|
||||||
|
drop_nlink(dentry->d_inode);
|
||||||
out_drop_write:
|
out_drop_write:
|
||||||
ovl_drop_write(dentry);
|
ovl_drop_write(dentry);
|
||||||
out:
|
out:
|
||||||
|
|
|
@ -409,14 +409,8 @@ static const struct inode_operations ovl_symlink_inode_operations = {
|
||||||
.update_time = ovl_update_time,
|
.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_ino = get_next_ino();
|
||||||
inode->i_mode = mode;
|
inode->i_mode = mode;
|
||||||
inode->i_flags |= S_NOCMTIME;
|
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;
|
inode->i_op = &ovl_symlink_inode_operations;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
WARN(1, "illegal file type: %i\n", mode);
|
||||||
|
/* Fall through */
|
||||||
|
|
||||||
case S_IFREG:
|
case S_IFREG:
|
||||||
case S_IFSOCK:
|
case S_IFSOCK:
|
||||||
case S_IFBLK:
|
case S_IFBLK:
|
||||||
|
@ -439,11 +437,42 @@ struct inode *ovl_new_inode(struct super_block *sb, umode_t mode)
|
||||||
case S_IFIFO:
|
case S_IFIFO:
|
||||||
inode->i_op = &ovl_file_inode_operations;
|
inode->i_op = &ovl_file_inode_operations;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
struct inode *ovl_new_inode(struct super_block *sb, umode_t mode)
|
||||||
WARN(1, "illegal file type: %i\n", mode);
|
{
|
||||||
iput(inode);
|
struct inode *inode;
|
||||||
inode = NULL;
|
|
||||||
|
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;
|
return inode;
|
||||||
|
|
|
@ -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);
|
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_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)
|
static inline void ovl_copyattr(struct inode *from, struct inode *to)
|
||||||
{
|
{
|
||||||
to->i_uid = from->i_uid;
|
to->i_uid = from->i_uid;
|
||||||
|
|
|
@ -232,8 +232,11 @@ void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry)
|
||||||
void ovl_inode_update(struct inode *inode, struct inode *upperinode)
|
void ovl_inode_update(struct inode *inode, struct inode *upperinode)
|
||||||
{
|
{
|
||||||
WARN_ON(!upperinode);
|
WARN_ON(!upperinode);
|
||||||
|
WARN_ON(!inode_unhashed(inode));
|
||||||
WRITE_ONCE(inode->i_private,
|
WRITE_ONCE(inode->i_private,
|
||||||
(unsigned long) upperinode | OVL_ISUPPER_MASK);
|
(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)
|
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);
|
realinode = d_inode(realdentry);
|
||||||
|
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
|
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);
|
inode = ovl_new_inode(dentry->d_sb, realinode->i_mode);
|
||||||
|
if (inode)
|
||||||
|
ovl_inode_init(inode, realinode, !!upperdentry);
|
||||||
|
}
|
||||||
if (!inode)
|
if (!inode)
|
||||||
goto out_free_oe;
|
goto out_free_oe;
|
||||||
ovl_inode_init(inode, realinode, !!upperdentry);
|
|
||||||
ovl_copyattr(realdentry->d_inode, inode);
|
ovl_copyattr(realdentry->d_inode, inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue