summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/overlayfs/dir.c84
-rw-r--r--fs/overlayfs/inode.c51
-rw-r--r--fs/overlayfs/overlayfs.h1
-rw-r--r--fs/overlayfs/super.c12
4 files changed, 100 insertions, 48 deletions
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 96b1bdcf3674..d456e9fb012a 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 @@ out_cleanup:
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 2bdd3cae0f71..6be0d276fd05 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 6410209ea616..abeef1e6db56 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 313f773652ff..44c4510f5adf 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);
}