summaryrefslogtreecommitdiff
path: root/fs/overlayfs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2023-06-29 13:01:27 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2023-06-29 13:01:27 -0700
commitbe3c213150dc4370ef211a78d78457ff166eba4e (patch)
tree60b6594e2fff8d29b421afc2d2bfa7b96694736f /fs/overlayfs
parenteee9c708cc89b4600c6e6cdda5bc2b8b4dad96cb (diff)
parent62149a745eee03194f025021640c80b84353089b (diff)
Merge tag 'ovl-update-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs
Pull overlayfs update from Amir Goldstein: - fix two NULL pointer deref bugs (Zhihao Cheng) - add support for "data-only" lower layers destined to be used by composefs - port overlayfs to the new mount api (Christian Brauner) * tag 'ovl-update-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs: (26 commits) ovl: add Amir as co-maintainer ovl: reserve ability to reconfigure mount options with new mount api ovl: modify layer parameter parsing ovl: port to new mount api ovl: factor out ovl_parse_options() helper ovl: store enum redirect_mode in config instead of a string ovl: pass ovl_fs to xino helpers ovl: clarify ovl_get_root() semantics ovl: negate the ofs->share_whiteout boolean ovl: check type and offset of struct vfsmount in ovl_entry ovl: implement lazy lookup of lowerdata in data-only layers ovl: prepare for lazy lookup of lowerdata inode ovl: prepare to store lowerdata redirect for lazy lowerdata lookup ovl: implement lookup in data-only layers ovl: introduce data-only lower layers ovl: remove unneeded goto instructions ovl: deduplicate lowerdata and lowerstack[] ovl: deduplicate lowerpath and lowerstack[] ovl: move ovl_entry into ovl_inode ovl: factor out ovl_free_entry() and ovl_stack_*() helpers ...
Diffstat (limited to 'fs/overlayfs')
-rw-r--r--fs/overlayfs/Makefile2
-rw-r--r--fs/overlayfs/copy_up.c11
-rw-r--r--fs/overlayfs/dir.c9
-rw-r--r--fs/overlayfs/export.c41
-rw-r--r--fs/overlayfs/file.c21
-rw-r--r--fs/overlayfs/inode.c73
-rw-r--r--fs/overlayfs/namei.c201
-rw-r--r--fs/overlayfs/overlayfs.h106
-rw-r--r--fs/overlayfs/ovl_entry.h91
-rw-r--r--fs/overlayfs/params.c389
-rw-r--r--fs/overlayfs/readdir.c19
-rw-r--r--fs/overlayfs/super.c907
-rw-r--r--fs/overlayfs/util.c179
13 files changed, 1350 insertions, 699 deletions
diff --git a/fs/overlayfs/Makefile b/fs/overlayfs/Makefile
index 9164c585eb2f..4e173d56b11f 100644
--- a/fs/overlayfs/Makefile
+++ b/fs/overlayfs/Makefile
@@ -6,4 +6,4 @@
obj-$(CONFIG_OVERLAY_FS) += overlay.o
overlay-objs := super.o namei.o util.o inode.o file.o dir.o readdir.o \
- copy_up.o export.o
+ copy_up.o export.o params.o
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index f658cc8ea492..568f743a5584 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -575,6 +575,7 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)
/* Restore timestamps on parent (best effort) */
ovl_set_timestamps(ofs, upperdir, &c->pstat);
ovl_dentry_set_upper_alias(c->dentry);
+ ovl_dentry_update_reval(c->dentry, upper);
}
}
inode_unlock(udir);
@@ -894,6 +895,7 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
inode_unlock(udir);
ovl_dentry_set_upper_alias(c->dentry);
+ ovl_dentry_update_reval(c->dentry, ovl_dentry_upper(c->dentry));
}
out:
@@ -1071,6 +1073,15 @@ static int ovl_copy_up_flags(struct dentry *dentry, int flags)
if (WARN_ON(disconnected && d_is_dir(dentry)))
return -EIO;
+ /*
+ * We may not need lowerdata if we are only doing metacopy up, but it is
+ * not very important to optimize this case, so do lazy lowerdata lookup
+ * before any copy up, so we can do it before taking ovl_inode_lock().
+ */
+ err = ovl_maybe_lookup_lowerdata(dentry);
+ if (err)
+ return err;
+
old_cred = ovl_override_creds(dentry->d_sb);
while (!err) {
struct dentry *next;
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index fc25fb95d5fc..033fc0458a3d 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -83,7 +83,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
ofs->whiteout = whiteout;
}
- if (ofs->share_whiteout) {
+ if (!ofs->no_shared_whiteout) {
whiteout = ovl_lookup_temp(ofs, workdir);
if (IS_ERR(whiteout))
goto out;
@@ -95,7 +95,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
if (err != -EMLINK) {
pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%i)\n",
ofs->whiteout->d_inode->i_nlink, err);
- ofs->share_whiteout = false;
+ ofs->no_shared_whiteout = true;
}
dput(whiteout);
}
@@ -269,8 +269,7 @@ static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
ovl_dir_modified(dentry->d_parent, false);
ovl_dentry_set_upper_alias(dentry);
- ovl_dentry_update_reval(dentry, newdentry,
- DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE);
+ ovl_dentry_init_reval(dentry, newdentry, NULL);
if (!hardlink) {
/*
@@ -953,7 +952,7 @@ static bool ovl_type_merge_or_lower(struct dentry *dentry)
static bool ovl_can_move(struct dentry *dentry)
{
- return ovl_redirect_dir(dentry->d_sb) ||
+ return ovl_redirect_dir(OVL_FS(dentry->d_sb)) ||
!d_is_dir(dentry) || !ovl_type_merge_or_lower(dentry);
}
diff --git a/fs/overlayfs/export.c b/fs/overlayfs/export.c
index defd4e231ad2..35680b6e175b 100644
--- a/fs/overlayfs/export.c
+++ b/fs/overlayfs/export.c
@@ -80,7 +80,7 @@ static int ovl_connectable_layer(struct dentry *dentry)
/* We can get overlay root from root of any layer */
if (dentry == dentry->d_sb->s_root)
- return oe->numlower;
+ return ovl_numlower(oe);
/*
* If it's an unindexed merge dir, then it's not connectable with any
@@ -91,7 +91,7 @@ static int ovl_connectable_layer(struct dentry *dentry)
return 0;
/* We can get upper/overlay path from indexed/lower dentry */
- return oe->lowerstack[0].layer->idx;
+ return ovl_lowerstack(oe)->layer->idx;
}
/*
@@ -105,6 +105,7 @@ static int ovl_connectable_layer(struct dentry *dentry)
static int ovl_connect_layer(struct dentry *dentry)
{
struct dentry *next, *parent = NULL;
+ struct ovl_entry *oe = OVL_E(dentry);
int origin_layer;
int err = 0;
@@ -112,7 +113,7 @@ static int ovl_connect_layer(struct dentry *dentry)
WARN_ON(!ovl_dentry_lower(dentry)))
return -EIO;
- origin_layer = OVL_E(dentry)->lowerstack[0].layer->idx;
+ origin_layer = ovl_lowerstack(oe)->layer->idx;
if (ovl_dentry_test_flag(OVL_E_CONNECTED, dentry))
return origin_layer;
@@ -285,21 +286,29 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb,
struct dentry *lower = lowerpath ? lowerpath->dentry : NULL;
struct dentry *upper = upper_alias ?: index;
struct dentry *dentry;
- struct inode *inode;
+ struct inode *inode = NULL;
struct ovl_entry *oe;
struct ovl_inode_params oip = {
- .lowerpath = lowerpath,
.index = index,
- .numlower = !!lower
};
/* We get overlay directory dentries with ovl_lookup_real() */
if (d_is_dir(upper ?: lower))
return ERR_PTR(-EIO);
+ oe = ovl_alloc_entry(!!lower);
+ if (!oe)
+ return ERR_PTR(-ENOMEM);
+
oip.upperdentry = dget(upper);
+ if (lower) {
+ ovl_lowerstack(oe)->dentry = dget(lower);
+ ovl_lowerstack(oe)->layer = lowerpath->layer;
+ }
+ oip.oe = oe;
inode = ovl_get_inode(sb, &oip);
if (IS_ERR(inode)) {
+ ovl_free_entry(oe);
dput(upper);
return ERR_CAST(inode);
}
@@ -314,20 +323,11 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb,
dentry = d_alloc_anon(inode->i_sb);
if (unlikely(!dentry))
goto nomem;
- oe = ovl_alloc_entry(lower ? 1 : 0);
- if (!oe)
- goto nomem;
- if (lower) {
- oe->lowerstack->dentry = dget(lower);
- oe->lowerstack->layer = lowerpath->layer;
- }
- dentry->d_fsdata = oe;
if (upper_alias)
ovl_dentry_set_upper_alias(dentry);
- ovl_dentry_update_reval(dentry, upper,
- DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE);
+ ovl_dentry_init_reval(dentry, upper, OVL_I_E(inode));
return d_instantiate_anon(dentry, inode);
@@ -342,15 +342,16 @@ out_iput:
/* Get the upper or lower dentry in stack whose on layer @idx */
static struct dentry *ovl_dentry_real_at(struct dentry *dentry, int idx)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ struct ovl_entry *oe = OVL_E(dentry);
+ struct ovl_path *lowerstack = ovl_lowerstack(oe);
int i;
if (!idx)
return ovl_dentry_upper(dentry);
- for (i = 0; i < oe->numlower; i++) {
- if (oe->lowerstack[i].layer->idx == idx)
- return oe->lowerstack[i].dentry;
+ for (i = 0; i < ovl_numlower(oe); i++) {
+ if (lowerstack[i].layer->idx == idx)
+ return lowerstack[i].dentry;
}
return NULL;
diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c
index 1f93a3ae113e..21245b00722a 100644
--- a/fs/overlayfs/file.c
+++ b/fs/overlayfs/file.c
@@ -107,14 +107,23 @@ static int ovl_real_fdget_meta(const struct file *file, struct fd *real,
{
struct dentry *dentry = file_dentry(file);
struct path realpath;
+ int err;
real->flags = 0;
real->file = file->private_data;
- if (allow_meta)
+ if (allow_meta) {
ovl_path_real(dentry, &realpath);
- else
+ } else {
+ /* lazy lookup of lowerdata */
+ err = ovl_maybe_lookup_lowerdata(dentry);
+ if (err)
+ return err;
+
ovl_path_realdata(dentry, &realpath);
+ }
+ if (!realpath.dentry)
+ return -EIO;
/* Has it been copied up since we'd opened it? */
if (unlikely(file_inode(real->file) != d_inode(realpath.dentry))) {
@@ -150,6 +159,11 @@ static int ovl_open(struct inode *inode, struct file *file)
struct path realpath;
int err;
+ /* lazy lookup of lowerdata */
+ err = ovl_maybe_lookup_lowerdata(dentry);
+ if (err)
+ return err;
+
err = ovl_maybe_copy_up(dentry, file->f_flags);
if (err)
return err;
@@ -158,6 +172,9 @@ static int ovl_open(struct inode *inode, struct file *file)
file->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
ovl_path_realdata(dentry, &realpath);
+ if (!realpath.dentry)
+ return -EIO;
+
realfile = ovl_open_realfile(file, &realpath);
if (IS_ERR(realfile))
return PTR_ERR(realfile);
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 541cf3717fc2..a63e57447be9 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -97,8 +97,9 @@ out:
static void ovl_map_dev_ino(struct dentry *dentry, struct kstat *stat, int fsid)
{
- bool samefs = ovl_same_fs(dentry->d_sb);
- unsigned int xinobits = ovl_xino_bits(dentry->d_sb);
+ struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
+ bool samefs = ovl_same_fs(ofs);
+ unsigned int xinobits = ovl_xino_bits(ofs);
unsigned int xinoshift = 64 - xinobits;
if (samefs) {
@@ -123,7 +124,7 @@ static void ovl_map_dev_ino(struct dentry *dentry, struct kstat *stat, int fsid)
stat->ino |= ((u64)fsid) << (xinoshift + 1);
stat->dev = dentry->d_sb->s_dev;
return;
- } else if (ovl_xino_warn(dentry->d_sb)) {
+ } else if (ovl_xino_warn(ofs)) {
pr_warn_ratelimited("inode number too big (%pd2, ino=%llu, xinobits=%d)\n",
dentry, stat->ino, xinobits);
}
@@ -149,7 +150,7 @@ static void ovl_map_dev_ino(struct dentry *dentry, struct kstat *stat, int fsid)
* is unique per underlying fs, so we use the unique anonymous
* bdev assigned to the underlying fs.
*/
- stat->dev = OVL_FS(dentry->d_sb)->fs[fsid].pseudo_dev;
+ stat->dev = ofs->fs[fsid].pseudo_dev;
}
}
@@ -186,7 +187,7 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path,
* If lower filesystem supports NFS file handles, this also guaranties
* persistent st_ino across mount cycle.
*/
- if (!is_dir || ovl_same_dev(dentry->d_sb)) {
+ if (!is_dir || ovl_same_dev(OVL_FS(dentry->d_sb))) {
if (!OVL_TYPE_UPPER(type)) {
fsid = ovl_layer_lower(dentry)->fsid;
} else if (OVL_TYPE_ORIGIN(type)) {
@@ -240,15 +241,22 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path,
/*
* If lower is not same as lowerdata or if there was
* no origin on upper, we can end up here.
+ * With lazy lowerdata lookup, guess lowerdata blocks
+ * from size to avoid lowerdata lookup on stat(2).
*/
struct kstat lowerdatastat;
u32 lowermask = STATX_BLOCKS;
ovl_path_lowerdata(dentry, &realpath);
- err = vfs_getattr(&realpath, &lowerdatastat,
- lowermask, flags);
- if (err)
- goto out;
+ if (realpath.dentry) {
+ err = vfs_getattr(&realpath, &lowerdatastat,
+ lowermask, flags);
+ if (err)
+ goto out;
+ } else {
+ lowerdatastat.blocks =
+ round_up(stat->size, stat->blksize) >> 9;
+ }
stat->blocks = lowerdatastat.blocks;
}
}
@@ -288,8 +296,8 @@ int ovl_permission(struct mnt_idmap *idmap,
int err;
/* Careful in RCU walk mode */
- ovl_i_path_real(inode, &realpath);
- if (!realpath.dentry) {
+ realinode = ovl_i_path_real(inode, &realpath);
+ if (!realinode) {
WARN_ON(!(mask & MAY_NOT_BLOCK));
return -ECHILD;
}
@@ -302,7 +310,6 @@ int ovl_permission(struct mnt_idmap *idmap,
if (err)
return err;
- realinode = d_inode(realpath.dentry);
old_cred = ovl_override_creds(inode->i_sb);
if (!upperinode &&
!special_file(realinode->i_mode) && mask & MAY_WRITE) {
@@ -559,20 +566,20 @@ struct posix_acl *do_ovl_get_acl(struct mnt_idmap *idmap,
struct inode *inode, int type,
bool rcu, bool noperm)
{
- struct inode *realinode = ovl_inode_real(inode);
+ struct inode *realinode;
struct posix_acl *acl;
struct path realpath;
- if (!IS_POSIXACL(realinode))
- return NULL;
-
/* Careful in RCU walk mode */
- ovl_i_path_real(inode, &realpath);
- if (!realpath.dentry) {
+ realinode = ovl_i_path_real(inode, &realpath);
+ if (!realinode) {
WARN_ON(!rcu);
return ERR_PTR(-ECHILD);
}
+ if (!IS_POSIXACL(realinode))
+ return NULL;
+
if (rcu) {
/*
* If the layer is idmapped drop out of RCU path walk
@@ -710,6 +717,9 @@ static int ovl_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
struct inode *realinode = ovl_inode_realdata(inode);
const struct cred *old_cred;
+ if (!realinode)
+ return -EIO;
+
if (!realinode->i_op->fiemap)
return -EOPNOTSUPP;
@@ -952,7 +962,7 @@ static inline void ovl_lockdep_annotate_inode_mutex_key(struct inode *inode)
static void ovl_next_ino(struct inode *inode)
{
- struct ovl_fs *ofs = inode->i_sb->s_fs_info;
+ struct ovl_fs *ofs = OVL_FS(inode->i_sb);
inode->i_ino = atomic_long_inc_return(&ofs->last_ino);
if (unlikely(!inode->i_ino))
@@ -961,7 +971,8 @@ static void ovl_next_ino(struct inode *inode)
static void ovl_map_ino(struct inode *inode, unsigned long ino, int fsid)
{
- int xinobits = ovl_xino_bits(inode->i_sb);
+ struct ovl_fs *ofs = OVL_FS(inode->i_sb);
+ int xinobits = ovl_xino_bits(ofs);
unsigned int xinoshift = 64 - xinobits;
/*
@@ -972,7 +983,7 @@ static void ovl_map_ino(struct inode *inode, unsigned long ino, int fsid)
* with d_ino also causes nfsd readdirplus to fail.
*/
inode->i_ino = ino;
- if (ovl_same_fs(inode->i_sb)) {
+ if (ovl_same_fs(ofs)) {
return;
} else if (xinobits && likely(!(ino >> xinoshift))) {
inode->i_ino |= (unsigned long)fsid << (xinoshift + 1);
@@ -1003,14 +1014,10 @@ void ovl_inode_init(struct inode *inode, struct ovl_inode_params *oip,
struct inode *realinode;
struct ovl_inode *oi = OVL_I(inode);
- if (oip->upperdentry)
- oi->__upperdentry = oip->upperdentry;
- if (oip->lowerpath && oip->lowerpath->dentry) {
- oi->lowerpath.dentry = dget(oip->lowerpath->dentry);
- oi->lowerpath.layer = oip->lowerpath->layer;
- }
- if (oip->lowerdata)
- oi->lowerdata = igrab(d_inode(oip->lowerdata));
+ oi->__upperdentry = oip->upperdentry;
+ oi->oe = oip->oe;
+ oi->redirect = oip->redirect;
+ oi->lowerdata_redirect = oip->lowerdata_redirect;
realinode = ovl_inode_real(inode);
ovl_copyattr(inode);
@@ -1325,7 +1332,7 @@ struct inode *ovl_get_inode(struct super_block *sb,
{
struct ovl_fs *ofs = OVL_FS(sb);
struct dentry *upperdentry = oip->upperdentry;
- struct ovl_path *lowerpath = oip->lowerpath;
+ struct ovl_path *lowerpath = ovl_lowerpath(oip->oe);
struct inode *realinode = upperdentry ? d_inode(upperdentry) : NULL;
struct inode *inode;
struct dentry *lowerdentry = lowerpath ? lowerpath->dentry : NULL;
@@ -1369,7 +1376,9 @@ struct inode *ovl_get_inode(struct super_block *sb,
}
dput(upperdentry);
+ ovl_free_entry(oip->oe);
kfree(oip->redirect);
+ kfree(oip->lowerdata_redirect);
goto out;
}
@@ -1398,14 +1407,12 @@ struct inode *ovl_get_inode(struct super_block *sb,
if (oip->index)
ovl_set_flag(OVL_INDEX, inode);
- OVL_I(inode)->redirect = oip->redirect;
-
if (bylower)
ovl_set_flag(OVL_CONST_INO, inode);
/* Check for non-merge dir that may have whiteouts */
if (is_dir) {
- if (((upperdentry && lowerdentry) || oip->numlower > 1) ||
+ if (((upperdentry && lowerdentry) || ovl_numlower(oip->oe) > 1) ||
ovl_path_check_origin_xattr(ofs, &realpath)) {
ovl_set_flag(OVL_WHITEOUTS, inode);
}
diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
index cfb3420b7df0..57adf911735f 100644
--- a/fs/overlayfs/namei.c
+++ b/fs/overlayfs/namei.c
@@ -14,6 +14,8 @@
#include <linux/exportfs.h>
#include "overlayfs.h"
+#include "../internal.h" /* for vfs_path_lookup */
+
struct ovl_lookup_data {
struct super_block *sb;
struct vfsmount *mnt;
@@ -24,6 +26,8 @@ struct ovl_lookup_data {
bool last;
char *redirect;
bool metacopy;
+ /* Referring to last redirect xattr */
+ bool absolute_redirect;
};
static int ovl_check_redirect(const struct path *path, struct ovl_lookup_data *d,
@@ -33,11 +37,13 @@ static int ovl_check_redirect(const struct path *path, struct ovl_lookup_data *d
char *buf;
struct ovl_fs *ofs = OVL_FS(d->sb);
+ d->absolute_redirect = false;
buf = ovl_get_redirect_xattr(ofs, path, prelen + strlen(post));
if (IS_ERR_OR_NULL(buf))
return PTR_ERR(buf);
if (buf[0] == '/') {
+ d->absolute_redirect = true;
/*
* One of the ancestor path elements in an absolute path
* lookup in ovl_lookup_layer() could have been opaque and
@@ -349,6 +355,61 @@ static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d,
return 0;
}
+static int ovl_lookup_data_layer(struct dentry *dentry, const char *redirect,
+ const struct ovl_layer *layer,
+ struct path *datapath)
+{
+ int err;
+
+ err = vfs_path_lookup(layer->mnt->mnt_root, layer->mnt, redirect,
+ LOOKUP_BENEATH | LOOKUP_NO_SYMLINKS | LOOKUP_NO_XDEV,
+ datapath);
+ pr_debug("lookup lowerdata (%pd2, redirect=\"%s\", layer=%d, err=%i)\n",
+ dentry, redirect, layer->idx, err);
+
+ if (err)
+ return err;
+
+ err = -EREMOTE;
+ if (ovl_dentry_weird(datapath->dentry))
+ goto out_path_put;
+
+ err = -ENOENT;
+ /* Only regular file is acceptable as lower data */
+ if (!d_is_reg(datapath->dentry))
+ goto out_path_put;
+
+ return 0;
+
+out_path_put:
+ path_put(datapath);
+
+ return err;
+}
+
+/* Lookup in data-only layers by absolute redirect to layer root */
+static int ovl_lookup_data_layers(struct dentry *dentry, const char *redirect,
+ struct ovl_path *lowerdata)
+{
+ struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
+ const struct ovl_layer *layer;
+ struct path datapath;
+ int err = -ENOENT;
+ int i;
+
+ layer = &ofs->layers[ofs->numlayer - ofs->numdatalayer];
+ for (i = 0; i < ofs->numdatalayer; i++, layer++) {
+ err = ovl_lookup_data_layer(dentry, redirect, layer, &datapath);
+ if (!err) {
+ mntput(datapath.mnt);
+ lowerdata->dentry = datapath.dentry;
+ lowerdata->layer = layer;
+ return 0;
+ }
+ }
+
+ return err;
+}
int ovl_check_origin_fh(struct ovl_fs *ofs, struct ovl_fh *fh, bool connected,
struct dentry *upperdentry, struct ovl_path **stackp)
@@ -356,7 +417,7 @@ int ovl_check_origin_fh(struct ovl_fs *ofs, struct ovl_fh *fh, bool connected,
struct dentry *origin = NULL;
int i;
- for (i = 1; i < ofs->numlayer; i++) {
+ for (i = 1; i <= ovl_numlowerlayer(ofs); i++) {
/*
* If lower fs uuid is not unique among lower fs we cannot match
* fh->uuid to layer.
@@ -790,20 +851,21 @@ fail:
*/
int ovl_path_next(int idx, struct dentry *dentry, struct path *path)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ struct ovl_entry *oe = OVL_E(dentry);
+ struct ovl_path *lowerstack = ovl_lowerstack(oe);
BUG_ON(idx < 0);
if (idx == 0) {
ovl_path_upper(dentry, path);
if (path->dentry)
- return oe->numlower ? 1 : -1;
+ return ovl_numlower(oe) ? 1 : -1;
idx++;
}
- BUG_ON(idx > oe->numlower);
- path->dentry = oe->lowerstack[idx - 1].dentry;
- path->mnt = oe->lowerstack[idx - 1].layer->mnt;
+ BUG_ON(idx > ovl_numlower(oe));
+ path->dentry = lowerstack[idx - 1].dentry;
+ path->mnt = lowerstack[idx - 1].layer->mnt;
- return (idx < oe->numlower) ? idx + 1 : -1;
+ return (idx < ovl_numlower(oe)) ? idx + 1 : -1;
}
/* Fix missing 'origin' xattr */
@@ -827,14 +889,60 @@ static int ovl_fix_origin(struct ovl_fs *ofs, struct dentry *dentry,
return err;
}
+/* Lazy lookup of lowerdata */
+int ovl_maybe_lookup_lowerdata(struct dentry *dentry)
+{
+ struct inode *inode = d_inode(dentry);
+ const char *redirect = ovl_lowerdata_redirect(inode);
+ struct ovl_path datapath = {};
+ const struct cred *old_cred;
+ int err;
+
+ if (!redirect || ovl_dentry_lowerdata(dentry))
+ return 0;
+
+ if (redirect[0] != '/')
+ return -EIO;
+
+ err = ovl_inode_lock_interruptible(inode);
+ if (err)
+ return err;
+
+ err = 0;
+ /* Someone got here before us? */
+ if (ovl_dentry_lowerdata(dentry))
+ goto out;
+
+ old_cred = ovl_override_creds(dentry->d_sb);
+ err = ovl_lookup_data_layers(dentry, redirect, &datapath);
+ revert_creds(old_cred);
+ if (err)
+ goto out_err;
+
+ err = ovl_dentry_set_lowerdata(dentry, &datapath);
+ if (err)
+ goto out_err;
+
+out:
+ ovl_inode_unlock(inode);
+ dput(datapath.dentry);
+
+ return err;
+
+out_err:
+ pr_warn_ratelimited("lazy lowerdata lookup failed (%pd2, err=%i)\n",
+ dentry, err);
+ goto out;
+}
+
struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
- struct ovl_entry *oe;
+ struct ovl_entry *oe = NULL;
const struct cred *old_cred;
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
- struct ovl_entry *poe = dentry->d_parent->d_fsdata;
- struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata;
+ struct ovl_entry *poe = OVL_E(dentry->d_parent);
+ struct ovl_entry *roe = OVL_E(dentry->d_sb->s_root);
struct ovl_path *stack = NULL, *origin_path = NULL;
struct dentry *upperdir, *upperdentry = NULL;
struct dentry *origin = NULL;
@@ -853,7 +961,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
.is_dir = false,
.opaque = false,
.stop = false,
- .last = ofs->config.redirect_follow ? false : !poe->numlower,
+ .last = ovl_redirect_follow(ofs) ? false : !ovl_numlower(poe),
.redirect = NULL,
.metacopy = false,
};
@@ -904,21 +1012,20 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
upperopaque = d.opaque;
}
- if (!d.stop && poe->numlower) {
+ if (!d.stop && ovl_numlower(poe)) {
err = -ENOMEM;
- stack = kcalloc(ofs->numlayer - 1, sizeof(struct ovl_path),
- GFP_KERNEL);
+ stack = ovl_stack_alloc(ofs->numlayer - 1);
if (!stack)
goto out_put_upper;
}
- for (i = 0; !d.stop && i < poe->numlower; i++) {
- struct ovl_path lower = poe->lowerstack[i];
+ for (i = 0; !d.stop && i < ovl_numlower(poe); i++) {
+ struct ovl_path lower = ovl_lowerstack(poe)[i];
- if (!ofs->config.redirect_follow)
- d.last = i == poe->numlower - 1;
- else
- d.last = lower.layer->idx == roe->numlower;
+ if (!ovl_redirect_follow(ofs))
+ d.last = i == ovl_numlower(poe) - 1;
+ else if (d.is_dir || !ofs->numdatalayer)
+ d.last = lower.layer->idx == ovl_numlower(roe);
d.mnt = lower.layer->mnt;
err = ovl_lookup_layer(lower.dentry, &d, &this, false);
@@ -995,7 +1102,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
* this attack vector when not necessary.
*/
err = -EPERM;
- if (d.redirect && !ofs->config.redirect_follow) {
+ if (d.redirect && !ovl_redirect_follow(ofs)) {
pr_warn_ratelimited("refusing to follow redirect for (%pd2)\n",
dentry);
goto out_put;
@@ -1011,6 +1118,12 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
}
}
+ /* Defer lookup of lowerdata in data-only layers to first access */
+ if (d.metacopy && ctr && ofs->numdatalayer && d.absolute_redirect) {
+ d.metacopy = false;
+ ctr++;
+ }
+
/*
* For regular non-metacopy upper dentries, there is no lower
* path based lookup, hence ctr will be zero. If a dentry is found
@@ -1067,13 +1180,14 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
}
}
- oe = ovl_alloc_entry(ctr);
- err = -ENOMEM;
- if (!oe)
- goto out_put;
+ if (ctr) {
+ oe = ovl_alloc_entry(ctr);
+ err = -ENOMEM;
+ if (!oe)
+ goto out_put;
- memcpy(oe->lowerstack, stack, sizeof(struct ovl_path) * ctr);
- dentry->d_fsdata = oe;
+ ovl_stack_cpy(ovl_lowerstack(oe), stack, ctr);
+ }
if (upperopaque)
ovl_dentry_set_opaque(dentry);
@@ -1106,14 +1220,16 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
if (upperdentry || ctr) {
struct ovl_inode_params oip = {
.upperdentry = upperdentry,
- .lowerpath = stack,
+ .oe = oe,
.index = index,
- .numlower = ctr,
.redirect = upperredirect,
- .lowerdata = (ctr > 1 && !d.is_dir) ?
- stack[ctr - 1].dentry : NULL,
};
+ /* Store lowerdata redirect for lazy lookup */
+ if (ctr > 1 && !d.is_dir && !stack[ctr - 1].dentry) {
+ oip.lowerdata_redirect = d.redirect;
+ d.redirect = NULL;
+ }
inode = ovl_get_inode(dentry->d_sb, &oip);
err = PTR_ERR(inode);
if (IS_ERR(inode))
@@ -1122,8 +1238,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
ovl_set_flag(OVL_UPPERDATA, inode);
}
- ovl_dentry_update_reval(dentry, upperdentry,
- DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE);
+ ovl_dentry_init_reval(dentry, upperdentry, OVL_I_E(inode));
revert_creds(old_cred);
if (origin_path) {
@@ -1131,18 +1246,15 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
kfree(origin_path);
}
dput(index);
- kfree(stack);
+ ovl_stack_free(stack, ctr);
kfree(d.redirect);
return d_splice_alias(inode, dentry);
out_free_oe:
- dentry->d_fsdata = NULL;
- kfree(oe);
+ ovl_free_entry(oe);
out_put:
dput(index);
- for (i = 0; i < ctr; i++)
- dput(stack[i].dentry);
- kfree(stack);
+ ovl_stack_free(stack, ctr);
out_put_upper:
if (origin_path) {
dput(origin_path->dentry);
@@ -1158,7 +1270,7 @@ out:
bool ovl_lower_positive(struct dentry *dentry)
{
- struct ovl_entry *poe = dentry->d_parent->d_fsdata;
+ struct ovl_entry *poe = OVL_E(dentry->d_parent);
const struct qstr *name = &dentry->d_name;
const struct cred *old_cred;
unsigned int i;
@@ -1178,12 +1290,13 @@ bool ovl_lower_positive(struct dentry *dentry)
old_cred = ovl_override_creds(dentry->d_sb);
/* Positive upper -> have to look up lower to see whether it exists */
- for (i = 0; !done && !positive && i < poe->numlower; i++) {
+ for (i = 0; !done && !positive && i < ovl_numlower(poe); i++) {
struct dentry *this;
- struct dentry *lowerdir = poe->lowerstack[i].dentry;
+ struct ovl_path *parentpath = &ovl_lowerstack(poe)[i];
- this = lookup_one_positive_unlocked(mnt_idmap(poe->lowerstack[i].layer->mnt),
- name->name, lowerdir, name->len);
+ this = lookup_one_positive_unlocked(
+ mnt_idmap(parentpath->layer->mnt),
+ name->name, parentpath->dentry, name->len);
if (IS_ERR(this)) {
switch (PTR_ERR(this)) {
case -ENOENT:
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index 23686e8a06c4..4142d1a457ff 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -58,11 +58,26 @@ enum ovl_entry_flag {
};
enum {
+ OVL_REDIRECT_OFF, /* "off" mode is never used. In effect */
+ OVL_REDIRECT_FOLLOW, /* ...it translates to either "follow" */
+ OVL_REDIRECT_NOFOLLOW, /* ...or "nofollow". */
+ OVL_REDIRECT_ON,
+};
+
+enum {
OVL_XINO_OFF,
OVL_XINO_AUTO,
OVL_XINO_ON,
};
+/* The set of options that user requested explicitly via mount options */
+struct ovl_opt_set {
+ bool metacopy;
+ bool redirect;
+ bool nfs_export;
+ bool index;
+};
+
/*
* The tuple (fh,uuid) is a universal unique identifier for a copy up origin,
* where:
@@ -353,17 +368,29 @@ static inline bool ovl_open_flags_need_copy_up(int flags)
return ((OPEN_FMODE(flags) & FMODE_WRITE) || (flags & O_TRUNC));
}
-static inline bool ovl_allow_offline_changes(struct ovl_fs *ofs)
-{
- /*
- * To avoid regressions in existing setups with overlay lower offline
- * changes, we allow lower changes only if none of the new features
- * are used.
- */
- return (!ofs->config.index && !ofs->config.metacopy &&
- !ofs->config.redirect_dir && ofs->config.xino != OVL_XINO_ON);
-}
+/* params.c */
+#define OVL_MAX_STACK 500
+
+struct ovl_fs_context_layer {
+ char *name;
+ struct path path;
+};
+
+struct ovl_fs_context {
+ struct path upper;
+ struct path work;
+ size_t capacity;
+ size_t nr; /* includes nr_data */
+ size_t nr_data;
+ struct ovl_opt_set set;
+ struct ovl_fs_context_layer *lower;
+};
+
+int ovl_parse_param_upperdir(const char *name, struct fs_context *fc,
+ bool workdir);
+int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc);
+void ovl_parse_param_drop_lowerdir(struct ovl_fs_context *ctx);
/* util.c */
int ovl_want_write(struct dentry *dentry);
@@ -374,21 +401,30 @@ int ovl_can_decode_fh(struct super_block *sb);
struct dentry *ovl_indexdir(struct super_block *sb);
bool ovl_index_all(struct super_block *sb);
bool ovl_verify_lower(struct super_block *sb);
+struct ovl_path *ovl_stack_alloc(unsigned int n);
+void ovl_stack_cpy(struct ovl_path *dst, struct ovl_path *src, unsigned int n);
+void ovl_stack_put(struct ovl_path *stack, unsigned int n);
+void ovl_stack_free(struct ovl_path *stack, unsigned int n);
struct ovl_entry *ovl_alloc_entry(unsigned int numlower);
+void ovl_free_entry(struct ovl_entry *oe);
bool ovl_dentry_remote(struct dentry *dentry);
-void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *upperdentry,
- unsigned int mask);
+void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *realdentry);
+void ovl_dentry_init_reval(struct dentry *dentry, struct dentry *upperdentry,
+ struct ovl_entry *oe);
+void ovl_dentry_init_flags(struct dentry *dentry, struct dentry *upperdentry,
+ struct ovl_entry *oe, unsigned int mask);
bool ovl_dentry_weird(struct dentry *dentry);
enum ovl_path_type ovl_path_type(struct dentry *dentry);
void ovl_path_upper(struct dentry *dentry, struct path *path);
void ovl_path_lower(struct dentry *dentry, struct path *path);
void ovl_path_lowerdata(struct dentry *dentry, struct path *path);
-void ovl_i_path_real(struct inode *inode, struct path *path);
+struct inode *ovl_i_path_real(struct inode *inode, struct path *path);
enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path);
enum ovl_path_type ovl_path_realdata(struct dentry *dentry, struct path *path);
struct dentry *ovl_dentry_upper(struct dentry *dentry);
struct dentry *ovl_dentry_lower(struct dentry *dentry);
struct dentry *ovl_dentry_lowerdata(struct dentry *dentry);
+int ovl_dentry_set_lowerdata(struct dentry *dentry, struct ovl_path *datapath);
const struct ovl_layer *ovl_i_layer_lower(struct inode *inode);
const struct ovl_layer *ovl_layer_lower(struct dentry *dentry);
struct dentry *ovl_dentry_real(struct dentry *dentry);
@@ -398,6 +434,7 @@ struct inode *ovl_inode_lower(struct inode *inode);
struct inode *ovl_inode_lowerdata(struct inode *inode);
struct inode *ovl_inode_real(struct inode *inode);
struct inode *ovl_inode_realdata(struct inode *inode);
+const char *ovl_lowerdata_redirect(struct inode *inode);
struct ovl_dir_cache *ovl_dir_cache(struct inode *inode);
void ovl_set_dir_cache(struct inode *inode, struct ovl_dir_cache *cache);
void ovl_dentry_set_flag(unsigned long flag, struct dentry *dentry);
@@ -412,7 +449,6 @@ bool ovl_dentry_needs_data_copy_up(struct dentry *dentry, int flags);
bool ovl_dentry_needs_data_copy_up_locked(struct dentry *dentry, int flags);
bool ovl_has_upperdata(struct inode *inode);
void ovl_set_upperdata(struct inode *inode);
-bool ovl_redirect_dir(struct super_block *sb);
const char *ovl_dentry_get_redirect(struct dentry *dentry);
void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect);
void ovl_inode_update(struct inode *inode, struct dentry *upperdentry);
@@ -480,31 +516,51 @@ static inline bool ovl_is_impuredir(struct super_block *sb,
return ovl_path_check_dir_xattr(ofs, &upperpath, OVL_XATTR_IMPURE);
}
+static inline bool ovl_redirect_follow(struct ovl_fs *ofs)
+{
+ return ofs->config.redirect_mode != OVL_REDIRECT_NOFOLLOW;
+}
+
+static inline bool ovl_redirect_dir(struct ovl_fs *ofs)
+{
+ return ofs->config.redirect_mode == OVL_REDIRECT_ON;
+}
+
/*
* With xino=auto, we do best effort to keep all inodes on same st_dev and
* d_ino consistent with st_ino.
* With xino=on, we do the same effort but we warn if we failed.
*/
-static inline bool ovl_xino_warn(struct super_block *sb)
+static inline bool ovl_xino_warn(struct ovl_fs *ofs)
{
- return OVL_FS(sb)->config.xino == OVL_XINO_ON;
+ return ofs->config.xino == OVL_XINO_ON;
+}
+
+/*
+ * To avoid regressions in existing setups with overlay lower offline changes,
+ * we allow lower changes only if none of the new features are used.
+ */
+static inline bool ovl_allow_offline_changes(struct ovl_fs *ofs)
+{
+ return (!ofs->config.index && !ofs->config.metacopy &&
+ !ovl_redirect_dir(ofs) && !ovl_xino_warn(ofs));
}
/* All layers on same fs? */
-static inline bool ovl_same_fs(struct super_block *sb)
+static inline bool ovl_same_fs(struct ovl_fs *ofs)
{
- return OVL_FS(sb)->xino_mode == 0;
+ return ofs->xino_mode == 0;
}
/* All overlay inodes have same st_dev? */
-static inline bool ovl_same_dev(struct super_block *sb)
+static inline bool ovl_same_dev(struct ovl_fs *ofs)
{
- return OVL_FS(sb)->xino_mode >= 0;
+ return ofs->xino_mode >= 0;
}
-static inline unsigned int ovl_xino_bits(struct super_block *sb)
+static inline unsigned int ovl_xino_bits(struct ovl_fs *ofs)
{
- return ovl_same_dev(sb) ? OVL_FS(sb)->xino_mode : 0;
+ return ovl_same_dev(ofs) ? ofs->xino_mode : 0;
}
static inline void ovl_inode_lock(struct inode *inode)
@@ -550,6 +606,7 @@ struct dentry *ovl_get_index_fh(struct ovl_fs *ofs, struct ovl_fh *fh);
struct dentry *ovl_lookup_index(struct ovl_fs *ofs, struct dentry *upper,
struct dentry *origin, bool verify);
int ovl_path_next(int idx, struct dentry *dentry, struct path *path);
+int ovl_maybe_lookup_lowerdata(struct dentry *dentry);
struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags);
bool ovl_lower_positive(struct dentry *dentry);
@@ -646,11 +703,10 @@ bool ovl_is_private_xattr(struct super_block *sb, const char *name);
struct ovl_inode_params {
struct inode *newinode;
struct dentry *upperdentry;
- struct ovl_path *lowerpath;
+ struct ovl_entry *oe;
bool index;
- unsigned int numlower;
char *redirect;
- struct dentry *lowerdata;
+ char *lowerdata_redirect;
};
void ovl_inode_init(struct inode *inode, struct ovl_inode_params *oip,
unsigned long ino, int fsid);
diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
index fd11fe6d6d45..306e1ecdc96d 100644
--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -6,13 +6,10 @@
*/
struct ovl_config {
- char *lowerdir;
char *upperdir;
char *workdir;
bool default_permissions;
- bool redirect_dir;
- bool redirect_follow;
- const char *redirect_mode;
+ int redirect_mode;
bool index;
bool uuid;
bool nfs_export;
@@ -32,6 +29,7 @@ struct ovl_sb {
};
struct ovl_layer {
+ /* ovl_free_fs() relies on @mnt being the first member! */
struct vfsmount *mnt;
/* Trap in ovl inode cache */
struct inode *trap;
@@ -40,18 +38,34 @@ struct ovl_layer {
int idx;
/* One fsid per unique underlying sb (upper fsid == 0) */
int fsid;
+ char *name;
};
+/*
+ * ovl_free_fs() relies on @mnt being the first member when unmounting
+ * the private mounts created for each layer. Let's check both the
+ * offset and type.
+ */
+static_assert(offsetof(struct ovl_layer, mnt) == 0);
+static_assert(__same_type(typeof_member(struct ovl_layer, mnt), struct vfsmount *));
+
struct ovl_path {
const struct ovl_layer *layer;
struct dentry *dentry;
};
+struct ovl_entry {
+ unsigned int __numlower;
+ struct ovl_path __lowerstack[];
+};
+
/* private information held for overlayfs's superblock */
struct ovl_fs {
unsigned int numlayer;
/* Number of unique fs among layers including upper fs */
unsigned int numfs;
+ /* Number of data-only lower layers */
+ unsigned int numdatalayer;
const struct ovl_layer *layers;
struct ovl_sb *fs;
/* workbasedir is the path at workdir= mount option */
@@ -70,7 +84,6 @@ struct ovl_fs {
/* Did we take the inuse lock? */
bool upperdir_locked;
bool workdir_locked;
- bool share_whiteout;
/* Traps in ovl inode cache */
struct inode *workbasedir_trap;
struct inode *workdir_trap;
@@ -79,12 +92,19 @@ struct ovl_fs {
int xino_mode;
/* For allocation of non-persistent inode numbers */
atomic_long_t last_ino;
- /* Whiteout dentry cache */
+ /* Shared whiteout cache */
struct dentry *whiteout;
+ bool no_shared_whiteout;
/* r/o snapshot of upperdir sb's only taken on volatile mounts */
errseq_t errseq;
};
+/* Number of lower layers, not including data-only layers */
+static inline unsigned int ovl_numlowerlayer(struct ovl_fs *ofs)
+{
+ return ofs->numlayer - ofs->numdatalayer - 1;
+}
+
static inline struct vfsmount *ovl_upper_mnt(struct ovl_fs *ofs)
{
return ofs->layers[0].mnt;
@@ -105,36 +125,53 @@ static inline bool ovl_should_sync(struct ovl_fs *ofs)
return !ofs->config.ovl_volatile;
}
-/* private information held for every overlayfs dentry */
-struct ovl_entry {
- union {
- struct {
- unsigned long flags;
- };
- struct rcu_head rcu;
- };
- unsigned numlower;
- struct ovl_path lowerstack[];
-};
+static inline unsigned int ovl_numlower(struct ovl_entry *oe)
+{
+ return oe ? oe->__numlower : 0;
+}
-struct ovl_entry *ovl_alloc_entry(unsigned int numlower);
+static inline struct ovl_path *ovl_lowerstack(struct ovl_entry *oe)
+{
+ return ovl_numlower(oe) ? oe->__lowerstack : NULL;
+}
-static inline struct ovl_entry *OVL_E(struct dentry *dentry)
+static inline struct ovl_path *ovl_lowerpath(struct ovl_entry *oe)
{
- return (struct ovl_entry *) dentry->d_fsdata;
+ return ovl_lowerstack(oe);
+}
+
+static inline struct ovl_path *ovl_lowerdata(struct ovl_entry *oe)
+{
+ struct ovl_path *lowerstack = ovl_lowerstack(oe);
+
+ return lowerstack ? &lowerstack[oe->__numlower - 1] : NULL;
+}
+
+/* May return NULL if lazy lookup of lowerdata is needed */
+static inline struct dentry *ovl_lowerdata_dentry(struct ovl_entry *oe)
+{
+ struct ovl_path *lowerdata = ovl_lowerdata(oe);
+
+ return lowerdata ? READ_ONCE(lowerdata->dentry) : NULL;
+}
+
+/* private information held for every overlayfs dentry */
+static inline unsigned long *OVL_E_FLAGS(struct dentry *dentry)
+{
+ return (unsigned long *) &dentry->d_fsdata;
}
struct ovl_inode {
union {
struct ovl_dir_cache *cache; /* directory */
- struct inode *lowerdata; /* regular file */
+ const char *lowerdata_redirect; /* regular file */
};
const char *redirect;
u64 version;
unsigned long flags;
struct inode vfs_inode;
struct dentry *__upperdentry;
- struct ovl_path lowerpath;
+ struct ovl_entry *oe;
/* synchronize copy up and more */
struct mutex lock;
@@ -145,6 +182,16 @@ static inline struct ovl_inode *OVL_I(struct inode *inode)
return container_of(inode, struct ovl_inode, vfs_inode);
}
+static inline struct ovl_entry *OVL_I_E(struct inode *inode)
+{
+ return inode ? OVL_I(inode)->oe : NULL;
+}
+
+static inline struct ovl_entry *OVL_E(struct dentry *dentry)
+{
+ return OVL_I_E(d_inode(dentry));
+}
+
static inline struct dentry *ovl_upperdentry_dereference(struct ovl_inode *oi)
{
return READ_ONCE(oi->__upperdentry);
diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
new file mode 100644
index 000000000000..d17d6b483dd0
--- /dev/null
+++ b/fs/overlayfs/params.c
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
+#include <linux/posix_acl_xattr.h>
+#include <linux/xattr.h>
+#include "overlayfs.h"
+
+static ssize_t ovl_parse_param_split_lowerdirs(char *str)
+{
+ ssize_t nr_layers = 1, nr_colons = 0;
+ char *s, *d;
+
+ for (s = d = str;; s++, d++) {
+ if (*s == '\\') {
+ s++;
+ } else if (*s == ':') {
+ bool next_colon = (*(s + 1) == ':');
+
+ nr_colons++;
+ if (nr_colons == 2 && next_colon) {
+ pr_err("only single ':' or double '::' sequences of unescaped colons in lowerdir mount option allowed.\n");
+ return -EINVAL;
+ }
+ /* count layers, not colons */
+ if (!next_colon)
+ nr_layers++;
+
+ *d = '\0';
+ continue;
+ }
+
+ *d = *s;
+ if (!*s) {
+ /* trailing colons */
+ if (nr_colons) {
+ pr_err("unescaped trailing colons in lowerdir mount option.\n");
+ return -EINVAL;
+ }
+ break;
+ }
+ nr_colons = 0;
+ }
+
+ return nr_layers;
+}
+
+static int ovl_mount_dir_noesc(const char *name, struct path *path)
+{
+ int err = -EINVAL;
+
+ if (!*name) {
+ pr_err("empty lowerdir\n");
+ goto out;
+ }
+ err = kern_path(name, LOOKUP_FOLLOW, path);
+ if (err) {
+ pr_err("failed to resolve '%s': %i\n", name, err);
+ goto out;
+ }
+ err = -EINVAL;
+ if (ovl_dentry_weird(path->dentry)) {
+ pr_err("filesystem on '%s' not supported\n", name);
+ goto out_put;
+ }
+ if (!d_is_dir(path->dentry)) {
+ pr_err("'%s' not a directory\n", name);
+ goto out_put;
+ }
+ return 0;
+
+out_put:
+ path_put_init(path);
+out:
+ return err;
+}
+
+static void ovl_unescape(char *s)
+{
+ char *d = s;
+
+ for (;; s++, d++) {
+ if (*s == '\\')
+ s++;
+ *d = *s;
+ if (!*s)
+ break;
+ }
+}
+
+static int ovl_mount_dir(const char *name, struct path *path)
+{
+ int err = -ENOMEM;
+ char *tmp = kstrdup(name, GFP_KERNEL);
+
+ if (tmp) {
+ ovl_unescape(tmp);
+ err = ovl_mount_dir_noesc(tmp, path);
+
+ if (!err && path->dentry->d_flags & DCACHE_OP_REAL) {
+ pr_err("filesystem on '%s' not supported as upperdir\n",
+ tmp);
+ path_put_init(path);
+ err = -EINVAL;
+ }
+ kfree(tmp);
+ }
+ return err;
+}
+
+int ovl_parse_param_upperdir(const char *name, struct fs_context *fc,
+ bool workdir)
+{
+ int err;
+ struct ovl_fs *ofs = fc->s_fs_info;
+ struct ovl_config *config = &ofs->config;
+ struct ovl_fs_context *ctx = fc->fs_private;
+ struct path path;
+ char *dup;
+
+ err = ovl_mount_dir(name, &path);
+ if (err)
+ return err;
+
+ /*
+ * Check whether upper path is read-only here to report failures
+ * early. Don't forget to recheck when the superblock is created
+ * as the mount attributes could change.
+ */
+ if (__mnt_is_readonly(path.mnt)) {
+ path_put(&path);
+ return -EINVAL;
+ }
+
+ dup = kstrdup(name, GFP_KERNEL);
+ if (!dup) {
+ path_put(&path);
+ return -ENOMEM;
+ }
+
+ if (workdir) {
+ kfree(config->workdir);
+ config->workdir = dup;
+ path_put(&ctx->work);
+ ctx->work = path;
+ } else {
+ kfree(config->upperdir);
+ config->upperdir = dup;
+ path_put(&ctx->upper);
+ ctx->upper = path;
+ }
+ return 0;
+}
+
+void ovl_parse_param_drop_lowerdir(struct ovl_fs_context *ctx)
+{
+ for (size_t nr = 0; nr < ctx->nr; nr++) {
+ path_put(&ctx->lower[nr].path);
+ kfree(ctx->lower[nr].name);
+ ctx->lower[nr].name = NULL;
+ }
+ ctx->nr = 0;
+ ctx->nr_data = 0;
+}
+
+/*
+ * Parse lowerdir= mount option:
+ *
+ * (1) lowerdir=/lower1:/lower2:/lower3::/data1::/data2
+ * Set "/lower1", "/lower2", and "/lower3" as lower layers and
+ * "/data1" and "/data2" as data lower layers. Any existing lower
+ * layers are replaced.
+ * (2) lowerdir=:/lower4
+ * Append "/lower4" to current stack of lower layers. This requires
+ * that there already is at least one lower layer configured.
+ * (3) lowerdir=::/lower5
+ * Append data "/lower5" as data lower layer. This requires that
+ * there's at least one regular lower layer present.
+ */
+int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
+{
+ int err;
+ struct ovl_fs_context *ctx = fc->fs_private;
+ struct ovl_fs_context_layer *l;
+ char *dup = NULL, *dup_iter;
+ ssize_t nr_lower = 0, nr = 0, nr_data = 0;
+ bool append = false, data_layer = false;
+
+ /*
+ * Ensure we're backwards compatible with mount(2)
+ * by allowing relative paths.
+ */
+
+ /* drop all existing lower layers */
+ if (!*name) {
+ ovl_parse_param_drop_lowerdir(ctx);
+ return 0;
+ }
+
+ if (strncmp(name, "::", 2) == 0) {
+ /*
+ * This is a data layer.
+ * There must be at least one regular lower layer
+ * specified.
+ */
+ if (ctx->nr == 0) {
+ pr_err("data lower layers without regular lower layers not allowed");
+ return -EINVAL;
+ }
+
+ /* Skip the leading "::". */
+ name += 2;
+ data_layer = true;
+ /*
+ * A data layer is automatically an append as there
+ * must've been at least one regular lower layer.
+ */
+ append = true;
+ } else if (*name == ':') {
+ /*
+ * This is a regular lower layer.
+ * If users want to append a layer enforce that they
+ * have already specified a first layer before. It's
+ * better to be strict.
+ */
+ if (ctx->nr == 0) {
+ pr_err("cannot append layer if no previous layer has been specified");
+ return -EINVAL;
+ }
+
+ /*
+ * Once a sequence of data layers has started regular
+ * lower layers are forbidden.
+ */
+ if (ctx->nr_data > 0) {
+ pr_err("regular lower layers cannot follow data lower layers");
+ return -EINVAL;
+ }
+
+ /* Skip the leading ":". */
+ name++;
+ append = true;
+ }
+
+ dup = kstrdup(name, GFP_KERNEL);
+ if (!dup)
+ return -ENOMEM;
+
+ err = -EINVAL;
+ nr_lower = ovl_parse_param_split_lowerdirs(dup);
+ if (nr_lower < 0)
+ goto out_err;
+
+ if ((nr_lower > OVL_MAX_STACK) ||
+ (append && (size_add(ctx->nr, nr_lower) > OVL_MAX_STACK))) {
+ pr_err("too many lower directories, limit is %d\n", OVL_MAX_STACK);
+ goto out_err;
+ }
+
+ if (!append)
+ ovl_parse_param_drop_lowerdir(ctx);
+
+ /*
+ * (1) append
+ *
+ * We want nr <= nr_lower <= capacity We know nr > 0 and nr <=
+ * capacity. If nr == 0 this wouldn't be append. If nr +
+ * nr_lower is <= capacity then nr <= nr_lower <= capacity
+ * already holds. If nr + nr_lower exceeds capacity, we realloc.
+ *
+ * (2) replace
+ *
+ * Ensure we're backwards compatible with mount(2) which allows
+ * "lowerdir=/a:/b:/c,lowerdir=/d:/e:/f" causing the last
+ * specified lowerdir mount option to win.
+ *
+ * We want nr <= nr_lower <= capacity We know either (i) nr == 0
+ * or (ii) nr > 0. We also know nr_lower > 0. The capacity
+ * could've been changed multiple times already so we only know
+ * nr <= capacity. If nr + nr_lower > capacity we realloc,
+ * otherwise nr <= nr_lower <= capacity holds already.
+ */
+ nr_lower += ctx->nr;
+ if (nr_lower > ctx->capacity) {
+ err = -ENOMEM;
+ l = krealloc_array(ctx->lower, nr_lower, sizeof(*ctx->lower),
+ GFP_KERNEL_ACCOUNT);
+ if (!l)
+ goto out_err;
+
+ ctx->lower = l;
+ ctx->capacity = nr_lower;
+ }
+
+ /*
+ * (3) By (1) and (2) we know nr <= nr_lower <= capacity.
+ * (4) If ctx->nr == 0 => replace
+ * We have verified above that the lowerdir mount option
+ * isn't an append, i.e., the lowerdir mount option
+ * doesn't start with ":" or "::".
+ * (4.1) The lowerdir mount options only contains regular lower
+ * layers ":".
+ * => Nothing to verify.
+ * (4.2) The lowerdir mount options contains regular ":" and
+ * data "::" layers.
+ * => We need to verify that data lower layers "::" aren't
+ * followed by regular ":" lower layers
+ * (5) If ctx->nr > 0 => append
+ * We know that there's at least one regular layer
+ * otherwise we would've failed when parsing the previous
+ * lowerdir mount option.
+ * (5.1) The lowerdir mount option is a regular layer ":" append
+ * => We need to verify that no data layers have been
+ * specified before.
+ * (5.2) The lowerdir mount option is a data layer "::" append
+ * We know that there's at least one regular layer or
+ * other data layers. => There's nothing to verify.
+ */
+ dup_iter = dup;
+ for (nr = ctx->nr; nr < nr_lower; nr++) {
+ l = &ctx->lower[nr];
+ memset(l, 0, sizeof(*l));
+
+ err = ovl_mount_dir_noesc(dup_iter, &l->path);
+ if (err)
+ goto out_put;
+
+ err = -ENOMEM;
+ l->name = kstrdup(dup_iter, GFP_KERNEL_ACCOUNT);
+ if (!l->name)
+ goto out_put;
+
+ if (data_layer)
+ nr_data++;
+
+ /* Calling strchr() again would overrun. */
+ if ((nr + 1) == nr_lower)
+ break;
+
+ err = -EINVAL;
+ dup_iter = strchr(dup_iter, '\0') + 1;
+ if (*dup_iter) {
+ /*
+ * This is a regular layer so we require that
+ * there are no data layers.
+ */
+ if ((ctx->nr_data + nr_data) > 0) {
+ pr_err("regular lower layers cannot follow data lower layers");
+ goto out_put;
+ }
+
+ data_layer = false;
+ continue;
+ }
+
+ /* This is a data lower layer. */
+ data_layer = true;
+ dup_iter++;
+ }
+ ctx->nr = nr_lower;
+ ctx->nr_data += nr_data;
+ kfree(dup);
+ return 0;
+
+out_put:
+ /*
+ * We know nr >= ctx->nr < nr_lower. If we failed somewhere
+ * we want to undo until nr == ctx->nr. This is correct for
+ * both ctx->nr == 0 and ctx->nr > 0.
+ */
+ for (; nr >= ctx->nr; nr--) {
+ l = &ctx->lower[nr];
+ kfree(l->name);
+ l->name = NULL;
+ path_put(&l->path);
+
+ /* don't overflow */
+ if (nr == 0)
+ break;
+ }
+
+out_err:
+ kfree(dup);
+
+ /* Intentionally don't realloc to a smaller size. */
+ return err;
+}
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index b6952b21a7ee..ee5c4736480f 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -118,7 +118,7 @@ static bool ovl_calc_d_ino(struct ovl_readdir_data *rdd,
return false;
/* Always recalc d_ino when remapping lower inode numbers */
- if (ovl_xino_bits(rdd->dentry->d_sb))
+ if (ovl_xino_bits(OVL_FS(rdd->dentry->d_sb)))
return true;
/* Always recalc d_ino for parent */
@@ -460,13 +460,14 @@ static int ovl_cache_update_ino(const struct path *path, struct ovl_cache_entry
{
struct dentry *dir = path->dentry;
+ struct ovl_fs *ofs = OVL_FS(dir->d_sb);
struct dentry *this = NULL;
enum ovl_path_type type;
u64 ino = p->real_ino;
- int xinobits = ovl_xino_bits(dir->d_sb);
+ int xinobits = ovl_xino_bits(ofs);
int err = 0;
- if (!ovl_same_dev(dir->d_sb))
+ if (!ovl_same_dev(ofs))
goto out;
if (p->name[0] == '.') {
@@ -515,7 +516,7 @@ get:
ino = ovl_remap_lower_ino(ino, xinobits,
ovl_layer_lower(this)->fsid,
p->name, p->len,
- ovl_xino_warn(dir->d_sb));
+ ovl_xino_warn(ofs));
}
out:
@@ -694,12 +695,13 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
int err;
struct ovl_dir_file *od = file->private_data;
struct dentry *dir = file->f_path.dentry;
+ struct ovl_fs *ofs = OVL_FS(dir->d_sb);
const struct ovl_layer *lower_layer = ovl_layer_lower(dir);
struct ovl_readdir_translate rdt = {
.ctx.actor = ovl_fill_real,
.orig_ctx = ctx,
- .xinobits = ovl_xino_bits(dir->d_sb),
- .xinowarn = ovl_xino_warn(dir->d_sb),
+ .xinobits = ovl_xino_bits(ofs),
+ .xinowarn = ovl_xino_warn(ofs),
};
if (rdt.xinobits && lower_layer)
@@ -735,6 +737,7 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx)
{
struct ovl_dir_file *od = file->private_data;
struct dentry *dentry = file->f_path.dentry;
+ struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct ovl_cache_entry *p;
const struct cred *old_cred;
int err;
@@ -749,8 +752,8 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx)
* dir is impure then need to adjust d_ino for copied up
* entries.
*/
- if (ovl_xino_bits(dentry->d_sb) ||
- (ovl_same_fs(dentry->d_sb) &&
+ if (ovl_xino_bits(ofs) ||
+ (ovl_same_fs(ofs) &&
(ovl_is_impure_dir(file) ||
OVL_TYPE_MERGE(ovl_path_type(dentry->d_parent))))) {
err = ovl_iterate_real(file, ctx);
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index f97ad8b40dbb..c14c52560fd6 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -16,6 +16,8 @@
#include <linux/posix_acl_xattr.h>
#include <linux/exportfs.h>
#include <linux/file.h>
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
#include "overlayfs.h"
MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
@@ -25,8 +27,6 @@ MODULE_LICENSE("GPL");
struct ovl_dir_cache;
-#define OVL_MAX_STACK 500
-
static bool ovl_redirect_dir_def = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_DIR);
module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644);
MODULE_PARM_DESC(redirect_dir,
@@ -54,29 +54,11 @@ module_param_named(xino_auto, ovl_xino_auto_def, bool, 0644);
MODULE_PARM_DESC(xino_auto,
"Auto enable xino feature");
-static void ovl_entry_stack_free(struct ovl_entry *oe)
-{
- unsigned int i;
-
- for (i = 0; i < oe->numlower; i++)
- dput(oe->lowerstack[i].dentry);
-}
-
static bool ovl_metacopy_def = IS_ENABLED(CONFIG_OVERLAY_FS_METACOPY);
module_param_named(metacopy, ovl_metacopy_def, bool, 0644);
MODULE_PARM_DESC(metacopy,
"Default to on or off for the metadata only copy up feature");
-static void ovl_dentry_release(struct dentry *dentry)
-{
- struct ovl_entry *oe = dentry->d_fsdata;
-
- if (oe) {
- ovl_entry_stack_free(oe);
- kfree_rcu(oe, rcu);
- }
-}
-
static struct dentry *ovl_d_real(struct dentry *dentry,
const struct inode *inode)
{
@@ -99,6 +81,15 @@ static struct dentry *ovl_d_real(struct dentry *dentry,
if (real && !inode && ovl_has_upperdata(d_inode(dentry)))
return real;
+ /*
+ * Best effort lazy lookup of lowerdata for !inode case to return
+ * the real lowerdata dentry. The only current caller of d_real() with
+ * NULL inode is d_real_inode() from trace_uprobe and this caller is
+ * likely going to be followed reading from the file, before placing
+ * uprobes on offset within the file, so lowerdata should be available
+ * when setting the uprobe.
+ */
+ ovl_maybe_lookup_lowerdata(dentry);
lower = ovl_dentry_lowerdata(dentry);
if (!lower)
goto bug;
@@ -121,6 +112,9 @@ static int ovl_revalidate_real(struct dentry *d, unsigned int flags, bool weak)
{
int ret = 1;
+ if (!d)
+ return 1;
+
if (weak) {
if (d->d_flags & DCACHE_OP_WEAK_REVALIDATE)
ret = d->d_op->d_weak_revalidate(d, flags);
@@ -138,7 +132,8 @@ static int ovl_revalidate_real(struct dentry *d, unsigned int flags, bool weak)
static int ovl_dentry_revalidate_common(struct dentry *dentry,
unsigned int flags, bool weak)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ struct ovl_entry *oe = OVL_E(dentry);
+ struct ovl_path *lowerstack = ovl_lowerstack(oe);
struct inode *inode = d_inode_rcu(dentry);
struct dentry *upper;
unsigned int i;
@@ -152,10 +147,9 @@ static int ovl_dentry_revalidate_common(struct dentry *dentry,
if (upper)
ret = ovl_revalidate_real(upper, flags, weak);
- for (i = 0; ret > 0 && i < oe->numlower; i++) {
- ret = ovl_revalidate_real(oe->lowerstack[i].dentry, flags,
- weak);
- }
+ for (i = 0; ret > 0 && i < ovl_numlower(oe); i++)
+ ret = ovl_revalidate_real(lowerstack[i].dentry, flags, weak);
+
return ret;
}
@@ -170,7 +164,6 @@ static int ovl_dentry_weak_revalidate(struct dentry *dentry, unsigned int flags)
}
static const struct dentry_operations ovl_dentry_operations = {
- .d_release = ovl_dentry_release,
.d_real = ovl_d_real,
.d_revalidate = ovl_dentry_revalidate,
.d_weak_revalidate = ovl_dentry_weak_revalidate,
@@ -190,9 +183,8 @@ static struct inode *ovl_alloc_inode(struct super_block *sb)
oi->version = 0;
oi->flags = 0;
oi->__upperdentry = NULL;
- oi->lowerpath.dentry = NULL;
- oi->lowerpath.layer = NULL;
- oi->lowerdata = NULL;
+ oi->lowerdata_redirect = NULL;
+ oi->oe = NULL;
mutex_init(&oi->lock);
return &oi->vfs_inode;
@@ -212,11 +204,11 @@ static void ovl_destroy_inode(struct inode *inode)
struct ovl_inode *oi = OVL_I(inode);
dput(oi->__upperdentry);
- dput(oi->lowerpath.dentry);
+ ovl_free_entry(oi->oe);
if (S_ISDIR(inode->i_mode))
ovl_dir_cache_free(inode);
else
- iput(oi->lowerdata);
+ kfree(oi->lowerdata_redirect);
}
static void ovl_free_fs(struct ovl_fs *ofs)
@@ -241,6 +233,7 @@ static void ovl_free_fs(struct ovl_fs *ofs)
for (i = 0; i < ofs->numlayer; i++) {
iput(ofs->layers[i].trap);
mounts[i] = ofs->layers[i].mnt;
+ kfree(ofs->layers[i].name);
}
kern_unmount_array(mounts, ofs->numlayer);
kfree(ofs->layers);
@@ -248,10 +241,8 @@ static void ovl_free_fs(struct ovl_fs *ofs)
free_anon_bdev(ofs->fs[i].pseudo_dev);
kfree(ofs->fs);
- kfree(ofs->config.lowerdir);
kfree(ofs->config.upperdir);
kfree(ofs->config.workdir);
- kfree(ofs->config.redirect_mode);
if (ofs->creator_cred)
put_cred(ofs->creator_cred);
kfree(ofs);
@@ -261,7 +252,8 @@ static void ovl_put_super(struct super_block *sb)
{
struct ovl_fs *ofs = sb->s_fs_info;
- ovl_free_fs(ofs);
+ if (ofs)
+ ovl_free_fs(ofs);
}
/* Sync real dirty inodes in upper filesystem (if it exists) */
@@ -337,17 +329,38 @@ static bool ovl_force_readonly(struct ovl_fs *ofs)
return (!ovl_upper_mnt(ofs) || !ofs->workdir);
}
-static const char *ovl_redirect_mode_def(void)
+static const struct constant_table ovl_parameter_redirect_dir[] = {
+ { "off", OVL_REDIRECT_OFF },
+ { "follow", OVL_REDIRECT_FOLLOW },
+ { "nofollow", OVL_REDIRECT_NOFOLLOW },
+ { "on", OVL_REDIRECT_ON },
+ {}
+};
+
+static const char *ovl_redirect_mode(struct ovl_config *config)
{
- return ovl_redirect_dir_def ? "on" : "off";
+ return ovl_parameter_redirect_dir[config->redirect_mode].name;
}
-static const char * const ovl_xino_str[] = {
- "off",
- "auto",
- "on",
+static int ovl_redirect_mode_def(void)
+{
+ return ovl_redirect_dir_def ? OVL_REDIRECT_ON :
+ ovl_redirect_always_follow ? OVL_REDIRECT_FOLLOW :
+ OVL_REDIRECT_NOFOLLOW;
+}
+
+static const struct constant_table ovl_parameter_xino[] = {
+ { "off", OVL_XINO_OFF },
+ { "auto", OVL_XINO_AUTO },
+ { "on", OVL_XINO_ON },
+ {}
};
+static const char *ovl_xino_mode(struct ovl_config *config)
+{
+ return ovl_parameter_xino[config->xino].name;
+}
+
static inline int ovl_xino_def(void)
{
return ovl_xino_auto_def ? OVL_XINO_AUTO : OVL_XINO_OFF;
@@ -365,16 +378,26 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
{
struct super_block *sb = dentry->d_sb;
struct ovl_fs *ofs = sb->s_fs_info;
-
- seq_show_option(m, "lowerdir", ofs->config.lowerdir);
+ size_t nr, nr_merged_lower = ofs->numlayer - ofs->numdatalayer;
+ const struct ovl_layer *data_layers = &ofs->layers[nr_merged_lower];
+
+ /* ofs->layers[0] is the upper layer */
+ seq_printf(m, ",lowerdir=%s", ofs->layers[1].name);
+ /* dump regular lower layers */
+ for (nr = 2; nr < nr_merged_lower; nr++)
+ seq_printf(m, ":%s", ofs->layers[nr].name);
+ /* dump data lower layers */
+ for (nr = 0; nr < ofs->numdatalayer; nr++)
+ seq_printf(m, "::%s", data_layers[nr].name);
if (ofs->config.upperdir) {
seq_show_option(m, "upperdir", ofs->config.upperdir);
seq_show_option(m, "workdir", ofs->config.workdir);
}
if (ofs->config.default_permissions)
seq_puts(m, ",default_permissions");
- if (strcmp(ofs->config.redirect_mode, ovl_redirect_mode_def()) != 0)
- seq_printf(m, ",redirect_dir=%s", ofs->config.redirect_mode);
+ if (ofs->config.redirect_mode != ovl_redirect_mode_def())
+ seq_printf(m, ",redirect_dir=%s",
+ ovl_redirect_mode(&ofs->config));
if (ofs->config.index != ovl_index_def)
seq_printf(m, ",index=%s", ofs->config.index ? "on" : "off");
if (!ofs->config.uuid)
@@ -382,8 +405,8 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
if (ofs->config.nfs_export != ovl_nfs_export_def)
seq_printf(m, ",nfs_export=%s", ofs->config.nfs_export ?
"on" : "off");
- if (ofs->config.xino != ovl_xino_def() && !ovl_same_fs(sb))
- seq_printf(m, ",xino=%s", ovl_xino_str[ofs->config.xino]);
+ if (ofs->config.xino != ovl_xino_def() && !ovl_same_fs(ofs))
+ seq_printf(m, ",xino=%s", ovl_xino_mode(&ofs->config));
if (ofs->config.metacopy != ovl_metacopy_def)
seq_printf(m, ",metacopy=%s",
ofs->config.metacopy ? "on" : "off");
@@ -394,16 +417,17 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
return 0;
}
-static int ovl_remount(struct super_block *sb, int *flags, char *data)
+static int ovl_reconfigure(struct fs_context *fc)
{
+ struct super_block *sb = fc->root->d_sb;
struct ovl_fs *ofs = sb->s_fs_info;
struct super_block *upper_sb;
int ret = 0;
- if (!(*flags & SB_RDONLY) && ovl_force_readonly(ofs))
+ if (!(fc->sb_flags & SB_RDONLY) && ovl_force_readonly(ofs))
return -EROFS;
- if (*flags & SB_RDONLY && !sb_rdonly(sb)) {
+ if (fc->sb_flags & SB_RDONLY && !sb_rdonly(sb)) {
upper_sb = ovl_upper_mnt(ofs)->mnt_sb;
if (ovl_should_sync(ofs)) {
down_read(&upper_sb->s_umount);
@@ -424,215 +448,143 @@ static const struct super_operations ovl_super_operations = {
.sync_fs = ovl_sync_fs,
.statfs = ovl_statfs,
.show_options = ovl_show_options,
- .remount_fs = ovl_remount,
};
enum {
- OPT_LOWERDIR,
- OPT_UPPERDIR,
- OPT_WORKDIR,
- OPT_DEFAULT_PERMISSIONS,
- OPT_REDIRECT_DIR,
- OPT_INDEX_ON,
- OPT_INDEX_OFF,
- OPT_UUID_ON,
- OPT_UUID_OFF,
- OPT_NFS_EXPORT_ON,
- OPT_USERXATTR,
- OPT_NFS_EXPORT_OFF,
- OPT_XINO_ON,
- OPT_XINO_OFF,
- OPT_XINO_AUTO,
- OPT_METACOPY_ON,
- OPT_METACOPY_OFF,
- OPT_VOLATILE,
- OPT_ERR,
+ Opt_lowerdir,
+ Opt_upperdir,
+ Opt_workdir,
+ Opt_default_permissions,
+ Opt_redirect_dir,
+ Opt_index,
+ Opt_uuid,
+ Opt_nfs_export,
+ Opt_userxattr,
+ Opt_xino,
+ Opt_metacopy,
+ Opt_volatile,
};
-static const match_table_t ovl_tokens = {
- {OPT_LOWERDIR, "lowerdir=%s"},
- {OPT_UPPERDIR, "upperdir=%s"},
- {OPT_WORKDIR, "workdir=%s"},
- {OPT_DEFAULT_PERMISSIONS, "default_permissions"},
- {OPT_REDIRECT_DIR, "redirect_dir=%s"},
- {OPT_INDEX_ON, "index=on"},
- {OPT_INDEX_OFF, "index=off"},
- {OPT_USERXATTR, "userxattr"},
- {OPT_UUID_ON, "uuid=on"},
- {OPT_UUID_OFF, "uuid=off"},
- {OPT_NFS_EXPORT_ON, "nfs_export=on"},
- {OPT_NFS_EXPORT_OFF, "nfs_export=off"},
- {OPT_XINO_ON, "xino=on"},
- {OPT_XINO_OFF, "xino=off"},
- {OPT_XINO_AUTO, "xino=auto"},
- {OPT_METACOPY_ON, "metacopy=on"},
- {OPT_METACOPY_OFF, "metacopy=off"},
- {OPT_VOLATILE, "volatile"},
- {OPT_ERR, NULL}
+static const struct constant_table ovl_parameter_bool[] = {
+ { "on", true },
+ { "off", false },
+ {}
};
-static char *ovl_next_opt(char **s)
-{
- char *sbegin = *s;
- char *p;
+#define fsparam_string_empty(NAME, OPT) \
+ __fsparam(fs_param_is_string, NAME, OPT, fs_param_can_be_empty, NULL)
+
+static const struct fs_parameter_spec ovl_parameter_spec[] = {
+ fsparam_string_empty("lowerdir", Opt_lowerdir),
+ fsparam_string("upperdir", Opt_upperdir),
+ fsparam_string("workdir", Opt_workdir),
+ fsparam_flag("default_permissions", Opt_default_permissions),
+ fsparam_enum("redirect_dir", Opt_redirect_dir, ovl_parameter_redirect_dir),
+ fsparam_enum("index", Opt_index, ovl_parameter_bool),
+ fsparam_enum("uuid", Opt_uuid, ovl_parameter_bool),
+ fsparam_enum("nfs_export", Opt_nfs_export, ovl_parameter_bool),
+ fsparam_flag("userxattr", Opt_userxattr),
+ fsparam_enum("xino", Opt_xino, ovl_parameter_xino),
+ fsparam_enum("metacopy", Opt_metacopy, ovl_parameter_bool),
+ fsparam_flag("volatile", Opt_volatile),
+ {}
+};
- if (sbegin == NULL)
- return NULL;
+static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+ int err = 0;
+ struct fs_parse_result result;
+ struct ovl_fs *ofs = fc->s_fs_info;
+ struct ovl_config *config = &ofs->config;
+ struct ovl_fs_context *ctx = fc->fs_private;
+ int opt;
- for (p = sbegin; *p; p++) {
- if (*p == '\\') {
- p++;
- if (!*p)
- break;
- } else if (*p == ',') {
- *p = '\0';
- *s = p + 1;
- return sbegin;
- }
- }
- *s = NULL;
- return sbegin;
-}
+ if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) {
+ /*
+ * On remount overlayfs has always ignored all mount
+ * options no matter if malformed or not so for
+ * backwards compatibility we do the same here.
+ */
+ if (fc->oldapi)
+ return 0;
-static int ovl_parse_redirect_mode(struct ovl_config *config, const char *mode)
-{
- if (strcmp(mode, "on") == 0) {
- config->redirect_dir = true;
/*
- * Does not make sense to have redirect creation without
- * redirect following.
+ * Give us the freedom to allow changing mount options
+ * with the new mount api in the future. So instead of
+ * silently ignoring everything we report a proper
+ * error. This is only visible for users of the new
+ * mount api.
*/
- config->redirect_follow = true;
- } else if (strcmp(mode, "follow") == 0) {
- config->redirect_follow = true;
- } else if (strcmp(mode, "off") == 0) {
- if (ovl_redirect_always_follow)
- config->redirect_follow = true;
- } else if (strcmp(mode, "nofollow") != 0) {
- pr_err("bad mount option \"redirect_dir=%s\"\n",
- mode);
+ return invalfc(fc, "No changes allowed in reconfigure");
+ }
+
+ opt = fs_parse(fc, ovl_parameter_spec, param, &result);
+ if (opt < 0)
+ return opt;
+
+ switch (opt) {
+ case Opt_lowerdir:
+ err = ovl_parse_param_lowerdir(param->string, fc);
+ break;
+ case Opt_upperdir:
+ fallthrough;
+ case Opt_workdir:
+ err = ovl_parse_param_upperdir(param->string, fc,
+ (Opt_workdir == opt));
+ break;
+ case Opt_default_permissions:
+ config->default_permissions = true;
+ break;
+ case Opt_redirect_dir:
+ config->redirect_mode = result.uint_32;
+ if (config->redirect_mode == OVL_REDIRECT_OFF) {
+ config->redirect_mode = ovl_redirect_always_follow ?
+ OVL_REDIRECT_FOLLOW :
+ OVL_REDIRECT_NOFOLLOW;
+ }
+ ctx->set.redirect = true;
+ break;
+ case Opt_index:
+ config->index = result.uint_32;
+ ctx->set.index = true;
+ break;
+ case Opt_uuid:
+ config->uuid = result.uint_32;
+ break;
+ case Opt_nfs_export:
+ config->nfs_export = result.uint_32;
+ ctx->set.nfs_export = true;
+ break;
+ case Opt_xino:
+ config->xino = result.uint_32;
+ break;
+ case Opt_metacopy:
+ config->metacopy = result.uint_32;
+ ctx->set.metacopy = true;
+ break;
+ case Opt_volatile:
+ config->ovl_volatile = true;
+ break;
+ case Opt_userxattr:
+ config->userxattr = true;
+ break;
+ default:
+ pr_err("unrecognized mount option \"%s\" or missing value\n",
+ param->key);
return -EINVAL;
}
- return 0;
+ return err;
}
-static int ovl_parse_opt(char *opt, struct ovl_config *config)
+static int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
+ struct ovl_config *config)
{
- char *p;
- int err;
- bool metacopy_opt = false, redirect_opt = false;
- bool nfs_export_opt = false, index_opt = false;
-
- config->redirect_mode = kstrdup(ovl_redirect_mode_def(), GFP_KERNEL);
- if (!config->redirect_mode)
- return -ENOMEM;
-
- while ((p = ovl_next_opt(&opt)) != NULL) {
- int token;
- substring_t args[MAX_OPT_ARGS];
-
- if (!*p)
- continue;
-
- token = match_token(p, ovl_tokens, args);
- switch (token) {
- case OPT_UPPERDIR:
- kfree(config->upperdir);
- config->upperdir = match_strdup(&args[0]);
- if (!config->upperdir)
- return -ENOMEM;
- break;
-
- case OPT_LOWERDIR:
- kfree(config->lowerdir);
- config->lowerdir = match_strdup(&args[0]);
- if (!config->lowerdir)
- return -ENOMEM;
- break;
-
- case OPT_WORKDIR:
- kfree(config->workdir);
- config->workdir = match_strdup(&args[0]);
- if (!config->workdir)
- return -ENOMEM;
- break;
-
- case OPT_DEFAULT_PERMISSIONS:
- config->default_permissions = true;
- break;
-
- case OPT_REDIRECT_DIR:
- kfree(config->redirect_mode);
- config->redirect_mode = match_strdup(&args[0]);
- if (!config->redirect_mode)
- return -ENOMEM;
- redirect_opt = true;
- break;
-
- case OPT_INDEX_ON:
- config->index = true;
- index_opt = true;
- break;
-
- case OPT_INDEX_OFF:
- config->index = false;
- index_opt = true;
- break;
-
- case OPT_UUID_ON:
- config->uuid = true;
- break;
-
- case OPT_UUID_OFF:
- config->uuid = false;
- break;
-
- case OPT_NFS_EXPORT_ON:
- config->nfs_export = true;
- nfs_export_opt = true;
- break;
-
- case OPT_NFS_EXPORT_OFF:
- config->nfs_export = false;
- nfs_export_opt = true;
- break;
-
- case OPT_XINO_ON:
- config->xino = OVL_XINO_ON;
- break;
+ struct ovl_opt_set set = ctx->set;
- case OPT_XINO_OFF:
- config->xino = OVL_XINO_OFF;
- break;
-
- case OPT_XINO_AUTO:
- config->xino = OVL_XINO_AUTO;
- break;
-
- case OPT_METACOPY_ON:
- config->metacopy = true;
- metacopy_opt = true;
- break;
-
- case OPT_METACOPY_OFF:
- config->metacopy = false;
- metacopy_opt = true;
- break;
-
- case OPT_VOLATILE:
- config->ovl_volatile = true;
- break;
-
- case OPT_USERXATTR:
- config->userxattr = true;
- break;
-
- default:
- pr_err("unrecognized mount option \"%s\" or missing value\n",
- p);
- return -EINVAL;
- }
+ if (ctx->nr_data > 0 && !config->metacopy) {
+ pr_err("lower data-only dirs require metacopy support.\n");
+ return -EINVAL;
}
/* Workdir/index are useless in non-upper mount */
@@ -643,9 +595,9 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
kfree(config->workdir);
config->workdir = NULL;
}
- if (config->index && index_opt) {
+ if (config->index && set.index) {
pr_info("option \"index=on\" is useless in a non-upper mount, ignore\n");
- index_opt = false;
+ set.index = false;
}
config->index = false;
}
@@ -655,47 +607,44 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
config->ovl_volatile = false;
}
- err = ovl_parse_redirect_mode(config, config->redirect_mode);
- if (err)
- return err;
-
/*
* This is to make the logic below simpler. It doesn't make any other
- * difference, since config->redirect_dir is only used for upper.
+ * difference, since redirect_dir=on is only used for upper.
*/
- if (!config->upperdir && config->redirect_follow)
- config->redirect_dir = true;
+ if (!config->upperdir && config->redirect_mode == OVL_REDIRECT_FOLLOW)
+ config->redirect_mode = OVL_REDIRECT_ON;
/* Resolve metacopy -> redirect_dir dependency */
- if (config->metacopy && !config->redirect_dir) {
- if (metacopy_opt && redirect_opt) {
+ if (config->metacopy && config->redirect_mode != OVL_REDIRECT_ON) {
+ if (set.metacopy && set.redirect) {
pr_err("conflicting options: metacopy=on,redirect_dir=%s\n",
- config->redirect_mode);
+ ovl_redirect_mode(config));
return -EINVAL;
}
- if (redirect_opt) {
+ if (set.redirect) {
/*
* There was an explicit redirect_dir=... that resulted
* in this conflict.
*/
pr_info("disabling metacopy due to redirect_dir=%s\n",
- config->redirect_mode);
+ ovl_redirect_mode(config));
config->metacopy = false;
} else {
/* Automatically enable redirect otherwise. */
- config->redirect_follow = config->redirect_dir = true;
+ config->redirect_mode = OVL_REDIRECT_ON;
}
}
/* Resolve nfs_export -> index dependency */
if (config->nfs_export && !config->index) {
- if (!config->upperdir && config->redirect_follow) {
+ if (!config->upperdir &&
+ config->redirect_mode != OVL_REDIRECT_NOFOLLOW) {
pr_info("NFS export requires \"redirect_dir=nofollow\" on non-upper mount, falling back to nfs_export=off.\n");
config->nfs_export = false;
- } else if (nfs_export_opt && index_opt) {
+ } else if (set.nfs_export && set.index) {
pr_err("conflicting options: nfs_export=on,index=off\n");
return -EINVAL;
- } else if (index_opt) {
+ } else if (set.index) {
/*
* There was an explicit index=off that resulted
* in this conflict.
@@ -710,11 +659,11 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
/* Resolve nfs_export -> !metacopy dependency */
if (config->nfs_export && config->metacopy) {
- if (nfs_export_opt && metacopy_opt) {
+ if (set.nfs_export && set.metacopy) {
pr_err("conflicting options: nfs_export=on,metacopy=on\n");
return -EINVAL;
}
- if (metacopy_opt) {
+ if (set.metacopy) {
/*
* There was an explicit metacopy=on that resulted
* in this conflict.
@@ -734,12 +683,13 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
/* Resolve userxattr -> !redirect && !metacopy dependency */
if (config->userxattr) {
- if (config->redirect_follow && redirect_opt) {
+ if (set.redirect &&
+ config->redirect_mode != OVL_REDIRECT_NOFOLLOW) {
pr_err("conflicting options: userxattr,redirect_dir=%s\n",
- config->redirect_mode);
+ ovl_redirect_mode(config));
return -EINVAL;
}
- if (config->metacopy && metacopy_opt) {
+ if (config->metacopy && set.metacopy) {
pr_err("conflicting options: userxattr,metacopy=on\n");
return -EINVAL;
}
@@ -749,7 +699,7 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
* options must be explicitly enabled if used together with
* userxattr.
*/
- config->redirect_dir = config->redirect_follow = false;
+ config->redirect_mode = OVL_REDIRECT_NOFOLLOW;
config->metacopy = false;
}
@@ -849,69 +799,6 @@ out_err:
goto out_unlock;
}
-static void ovl_unescape(char *s)
-{
- char *d = s;
-
- for (;; s++, d++) {
- if (*s == '\\')
- s++;
- *d = *s;
- if (!*s)
- break;
- }
-}
-
-static int ovl_mount_dir_noesc(const char *name, struct path *path)
-{
- int err = -EINVAL;
-
- if (!*name) {
- pr_err("empty lowerdir\n");
- goto out;
- }
- err = kern_path(name, LOOKUP_FOLLOW, path);
- if (err) {
- pr_err("failed to resolve '%s': %i\n", name, err);
- goto out;
- }
- err = -EINVAL;
- if (ovl_dentry_weird(path->dentry)) {
- pr_err("filesystem on '%s' not supported\n", name);
- goto out_put;
- }
- if (!d_is_dir(path->dentry)) {
- pr_err("'%s' not a directory\n", name);
- goto out_put;
- }
- return 0;
-
-out_put:
- path_put_init(path);
-out:
- return err;
-}
-
-static int ovl_mount_dir(const char *name, struct path *path)
-{
- int err = -ENOMEM;
- char *tmp = kstrdup(name, GFP_KERNEL);
-
- if (tmp) {
- ovl_unescape(tmp);
- err = ovl_mount_dir_noesc(tmp, path);
-
- if (!err && path->dentry->d_flags & DCACHE_OP_REAL) {
- pr_err("filesystem on '%s' not supported as upperdir\n",
- tmp);
- path_put_init(path);
- err = -EINVAL;
- }
- kfree(tmp);
- }
- return err;
-}
-
static int ovl_check_namelen(const struct path *path, struct ovl_fs *ofs,
const char *name)
{
@@ -932,10 +819,6 @@ static int ovl_lower_dir(const char *name, struct path *path,
int fh_type;
int err;
- err = ovl_mount_dir_noesc(name, path);
- if (err)
- return err;
-
err = ovl_check_namelen(path, ofs, name);
if (err)
return err;
@@ -984,26 +867,6 @@ static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir)
return ok;
}
-static unsigned int ovl_split_lowerdirs(char *str)
-{
- unsigned int ctr = 1;
- char *s, *d;
-
- for (s = d = str;; s++, d++) {
- if (*s == '\\') {
- s++;
- } else if (*s == ':') {
- *d = '\0';
- ctr++;
- continue;
- }
- *d = *s;
- if (!*s)
- break;
- }
- return ctr;
-}
-
static int ovl_own_xattr_get(const struct xattr_handler *handler,
struct dentry *dentry, struct inode *inode,
const char *name, void *buffer, size_t size)
@@ -1104,15 +967,12 @@ static int ovl_report_in_use(struct ovl_fs *ofs, const char *name)
}
static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
- struct ovl_layer *upper_layer, struct path *upperpath)
+ struct ovl_layer *upper_layer,
+ const struct path *upperpath)
{
struct vfsmount *upper_mnt;
int err;
- err = ovl_mount_dir(ofs->config.upperdir, upperpath);
- if (err)
- goto out;
-
/* Upperdir path should not be r/o */
if (__mnt_is_readonly(upperpath->mnt)) {
pr_err("upper fs is r/o, try multi-lower layers mount\n");
@@ -1142,6 +1002,11 @@ static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
upper_layer->idx = 0;
upper_layer->fsid = 0;
+ err = -ENOMEM;
+ upper_layer->name = kstrdup(ofs->config.upperdir, GFP_KERNEL);
+ if (!upper_layer->name)
+ goto out;
+
/*
* Inherit SB_NOSEC flag from upperdir.
*
@@ -1333,10 +1198,17 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
if (err) {
pr_warn("failed to set xattr on upper\n");
ofs->noxattr = true;
- if (ofs->config.index || ofs->config.metacopy) {
- ofs->config.index = false;
+ if (ovl_redirect_follow(ofs)) {
+ ofs->config.redirect_mode = OVL_REDIRECT_NOFOLLOW;
+ pr_warn("...falling back to redirect_dir=nofollow.\n");
+ }
+ if (ofs->config.metacopy) {
ofs->config.metacopy = false;
- pr_warn("...falling back to index=off,metacopy=off.\n");
+ pr_warn("...falling back to metacopy=off.\n");
+ }
+ if (ofs->config.index) {
+ ofs->config.index = false;
+ pr_warn("...falling back to index=off.\n");
}
/*
* xattr support is required for persistent st_ino.
@@ -1399,46 +1271,37 @@ out:
}
static int ovl_get_workdir(struct super_block *sb, struct ovl_fs *ofs,
- const struct path *upperpath)
+ const struct path *upperpath,
+ const struct path *workpath)
{
int err;
- struct path workpath = { };
-
- err = ovl_mount_dir(ofs->config.workdir, &workpath);
- if (err)
- goto out;
err = -EINVAL;
- if (upperpath->mnt != workpath.mnt) {
+ if (upperpath->mnt != workpath->mnt) {
pr_err("workdir and upperdir must reside under the same mount\n");
- goto out;
+ return err;
}
- if (!ovl_workdir_ok(workpath.dentry, upperpath->dentry)) {
+ if (!ovl_workdir_ok(workpath->dentry, upperpath->dentry)) {
pr_err("workdir and upperdir must be separate subtrees\n");
- goto out;
+ return err;
}
- ofs->workbasedir = dget(workpath.dentry);
+ ofs->workbasedir = dget(workpath->dentry);
if (ovl_inuse_trylock(ofs->workbasedir)) {
ofs->workdir_locked = true;
} else {
err = ovl_report_in_use(ofs, "workdir");
if (err)
- goto out;
+ return err;
}
err = ovl_setup_trap(sb, ofs->workbasedir, &ofs->workbasedir_trap,
"workdir");
if (err)
- goto out;
-
- err = ovl_make_workdir(sb, ofs, &workpath);
-
-out:
- path_put(&workpath);
+ return err;
- return err;
+ return ovl_make_workdir(sb, ofs, workpath);
}
static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs,
@@ -1454,7 +1317,7 @@ static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs,
/* Verify lower root is upper root origin */
err = ovl_verify_origin(ofs, upperpath->dentry,
- oe->lowerstack[0].dentry, true);
+ ovl_lowerstack(oe)->dentry, true);
if (err) {
pr_err("failed to verify upper root origin\n");
goto out;
@@ -1574,7 +1437,7 @@ static int ovl_get_fsid(struct ovl_fs *ofs, const struct path *path)
pr_warn("%s uuid detected in lower fs '%pd2', falling back to xino=%s,index=off,nfs_export=off.\n",
uuid_is_null(&sb->s_uuid) ? "null" :
"conflicting",
- path->dentry, ovl_xino_str[ofs->config.xino]);
+ path->dentry, ovl_xino_mode(&ofs->config));
}
}
@@ -1591,19 +1454,31 @@ static int ovl_get_fsid(struct ovl_fs *ofs, const struct path *path)
return ofs->numfs++;
}
+/*
+ * The fsid after the last lower fsid is used for the data layers.
+ * It is a "null fs" with a null sb, null uuid, and no pseudo dev.
+ */
+static int ovl_get_data_fsid(struct ovl_fs *ofs)
+{
+ return ofs->numfs;
+}
+
+
static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
- struct path *stack, unsigned int numlower,
- struct ovl_layer *layers)
+ struct ovl_fs_context *ctx, struct ovl_layer *layers)
{
int err;
unsigned int i;
+ size_t nr_merged_lower;
- err = -ENOMEM;
- ofs->fs = kcalloc(numlower + 1, sizeof(struct ovl_sb), GFP_KERNEL);
+ ofs->fs = kcalloc(ctx->nr + 2, sizeof(struct ovl_sb), GFP_KERNEL);
if (ofs->fs == NULL)
- goto out;
+ return -ENOMEM;
- /* idx/fsid 0 are reserved for upper fs even with lower only overlay */
+ /*
+ * idx/fsid 0 are reserved for upper fs even with lower only overlay
+ * and the last fsid is reserved for "null fs" of the data layers.
+ */
ofs->numfs++;
/*
@@ -1615,7 +1490,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
err = get_anon_bdev(&ofs->fs[0].pseudo_dev);
if (err) {
pr_err("failed to get anonymous bdev for upper fs\n");
- goto out;
+ return err;
}
if (ovl_upper_mnt(ofs)) {
@@ -1623,14 +1498,19 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
ofs->fs[0].is_lower = false;
}
- for (i = 0; i < numlower; i++) {
+ nr_merged_lower = ctx->nr - ctx->nr_data;
+ for (i = 0; i < ctx->nr; i++) {
+ struct ovl_fs_context_layer *l = &ctx->lower[i];
struct vfsmount *mnt;
struct inode *trap;
int fsid;
- err = fsid = ovl_get_fsid(ofs, &stack[i]);
- if (err < 0)
- goto out;
+ if (i < nr_merged_lower)
+ fsid = ovl_get_fsid(ofs, &l->path);
+ else
+ fsid = ovl_get_data_fsid(ofs);
+ if (fsid < 0)
+ return fsid;
/*
* Check if lower root conflicts with this overlay layers before
@@ -1639,24 +1519,24 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
* the upperdir/workdir is in fact in-use by our
* upperdir/workdir.
*/
- err = ovl_setup_trap(sb, stack[i].dentry, &trap, "lowerdir");
+ err = ovl_setup_trap(sb, l->path.dentry, &trap, "lowerdir");
if (err)
- goto out;
+ return err;
- if (ovl_is_inuse(stack[i].dentry)) {
+ if (ovl_is_inuse(l->path.dentry)) {
err = ovl_report_in_use(ofs, "lowerdir");
if (err) {
iput(trap);
- goto out;
+ return err;
}
}
- mnt = clone_private_mount(&stack[i]);
+ mnt = clone_private_mount(&l->path);
err = PTR_ERR(mnt);
if (IS_ERR(mnt)) {
pr_err("failed to clone lowerpath\n");
iput(trap);
- goto out;
+ return err;
}
/*
@@ -1670,6 +1550,8 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
layers[ofs->numlayer].idx = ofs->numlayer;
layers[ofs->numlayer].fsid = fsid;
layers[ofs->numlayer].fs = &ofs->fs[fsid];
+ layers[ofs->numlayer].name = l->name;
+ l->name = NULL;
ofs->numlayer++;
ofs->fs[fsid].is_lower = true;
}
@@ -1706,69 +1588,63 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
ofs->xino_mode);
}
- err = 0;
-out:
- return err;
+ return 0;
}
static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
- const char *lower, unsigned int numlower,
- struct ovl_fs *ofs, struct ovl_layer *layers)
+ struct ovl_fs_context *ctx,
+ struct ovl_fs *ofs,
+ struct ovl_layer *layers)
{
int err;
- struct path *stack = NULL;
unsigned int i;
+ size_t nr_merged_lower;
struct ovl_entry *oe;
+ struct ovl_path *lowerstack;
- if (!ofs->config.upperdir && numlower == 1) {
+ struct ovl_fs_context_layer *l;
+
+ if (!ofs->config.upperdir && ctx->nr == 1) {
pr_err("at least 2 lowerdir are needed while upperdir nonexistent\n");
return ERR_PTR(-EINVAL);
}
- stack = kcalloc(numlower, sizeof(struct path), GFP_KERNEL);
- if (!stack)
- return ERR_PTR(-ENOMEM);
-
err = -EINVAL;
- for (i = 0; i < numlower; i++) {
- err = ovl_lower_dir(lower, &stack[i], ofs, &sb->s_stack_depth);
- if (err)
- goto out_err;
+ for (i = 0; i < ctx->nr; i++) {
+ l = &ctx->lower[i];
- lower = strchr(lower, '\0') + 1;
+ err = ovl_lower_dir(l->name, &l->path, ofs, &sb->s_stack_depth);
+ if (err)
+ return ERR_PTR(err);
}
err = -EINVAL;
sb->s_stack_depth++;
if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) {
pr_err("maximum fs stacking depth exceeded\n");
- goto out_err;
+ return ERR_PTR(err);
}
- err = ovl_get_layers(sb, ofs, stack, numlower, layers);
+ err = ovl_get_layers(sb, ofs, ctx, layers);
if (err)
- goto out_err;
+ return ERR_PTR(err);
err = -ENOMEM;
- oe = ovl_alloc_entry(numlower);
+ /* Data-only layers are not merged in root directory */
+ nr_merged_lower = ctx->nr - ctx->nr_data;
+ oe = ovl_alloc_entry(nr_merged_lower);
if (!oe)
- goto out_err;
+ return ERR_PTR(err);
- for (i = 0; i < numlower; i++) {
- oe->lowerstack[i].dentry = dget(stack[i].dentry);
- oe->lowerstack[i].layer = &ofs->layers[i+1];
+ lowerstack = ovl_lowerstack(oe);
+ for (i = 0; i < nr_merged_lower; i++) {
+ l = &ctx->lower[i];
+ lowerstack[i].dentry = dget(l->path.dentry);
+ lowerstack[i].layer = &ofs->layers[i + 1];
}
-
-out:
- for (i = 0; i < numlower; i++)
- path_put(&stack[i]);
- kfree(stack);
+ ofs->numdatalayer = ctx->nr_data;
return oe;
-
-out_err:
- oe = ERR_PTR(err);
- goto out;
}
/*
@@ -1849,20 +1725,18 @@ static struct dentry *ovl_get_root(struct super_block *sb,
struct ovl_entry *oe)
{
struct dentry *root;
- struct ovl_path *lowerpath = &oe->lowerstack[0];
+ struct ovl_path *lowerpath = ovl_lowerstack(oe);
unsigned long ino = d_inode(lowerpath->dentry)->i_ino;
int fsid = lowerpath->layer->fsid;
struct ovl_inode_params oip = {
.upperdentry = upperdentry,
- .lowerpath = lowerpath,
+ .oe = oe,
};
root = d_make_root(ovl_new_inode(sb, S_IFDIR, 0));
if (!root)
return NULL;
- root->d_fsdata = oe;
-
if (upperdentry) {
/* Root inode uses upper st_ino/i_ino */
ino = d_inode(upperdentry)->i_ino;
@@ -1877,73 +1751,47 @@ static struct dentry *ovl_get_root(struct super_block *sb,
ovl_dentry_set_flag(OVL_E_CONNECTED, root);
ovl_set_upperdata(d_inode(root));
ovl_inode_init(d_inode(root), &oip, ino, fsid);
- ovl_dentry_update_reval(root, upperdentry, DCACHE_OP_WEAK_REVALIDATE);
+ ovl_dentry_init_flags(root, upperdentry, oe, DCACHE_OP_WEAK_REVALIDATE);
+ /* root keeps a reference of upperdentry */
+ dget(upperdentry);
return root;
}
-static int ovl_fill_super(struct super_block *sb, void *data, int silent)
+static int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
{
- struct path upperpath = { };
+ struct ovl_fs *ofs = sb->s_fs_info;
+ struct ovl_fs_context *ctx = fc->fs_private;
struct dentry *root_dentry;
struct ovl_entry *oe;
- struct ovl_fs *ofs;
struct ovl_layer *layers;
struct cred *cred;
- char *splitlower = NULL;
- unsigned int numlower;
int err;
err = -EIO;
- if (WARN_ON(sb->s_user_ns != current_user_ns()))
- goto out;
+ if (WARN_ON(fc->user_ns != current_user_ns()))
+ goto out_err;
sb->s_d_op = &ovl_dentry_operations;
err = -ENOMEM;
- ofs = kzalloc(sizeof(struct ovl_fs), GFP_KERNEL);
- if (!ofs)
- goto out;
-
- err = -ENOMEM;
ofs->creator_cred = cred = prepare_creds();
if (!cred)
goto out_err;
- /* Is there a reason anyone would want not to share whiteouts? */
- ofs->share_whiteout = true;
-
- ofs->config.index = ovl_index_def;
- ofs->config.uuid = true;
- ofs->config.nfs_export = ovl_nfs_export_def;
- ofs->config.xino = ovl_xino_def();
- ofs->config.metacopy = ovl_metacopy_def;
- err = ovl_parse_opt((char *) data, &ofs->config);
+ err = ovl_fs_params_verify(ctx, &ofs->config);
if (err)
goto out_err;
err = -EINVAL;
- if (!ofs->config.lowerdir) {
- if (!silent)
+ if (ctx->nr == 0) {
+ if (!(fc->sb_flags & SB_SILENT))
pr_err("missing 'lowerdir'\n");
goto out_err;
}
err = -ENOMEM;
- splitlower = kstrdup(ofs->config.lowerdir, GFP_KERNEL);
- if (!splitlower)
- goto out_err;
-
- err = -EINVAL;
- numlower = ovl_split_lowerdirs(splitlower);
- if (numlower > OVL_MAX_STACK) {
- pr_err("too many lower directories, limit is %d\n",
- OVL_MAX_STACK);
- goto out_err;
- }
-
- err = -ENOMEM;
- layers = kcalloc(numlower + 1, sizeof(struct ovl_layer), GFP_KERNEL);
+ layers = kcalloc(ctx->nr + 1, sizeof(struct ovl_layer), GFP_KERNEL);
if (!layers)
goto out_err;
@@ -1975,7 +1823,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
goto out_err;
}
- err = ovl_get_upper(sb, ofs, &layers[0], &upperpath);
+ err = ovl_get_upper(sb, ofs, &layers[0], &ctx->upper);
if (err)
goto out_err;
@@ -1989,7 +1837,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
}
}
- err = ovl_get_workdir(sb, ofs, &upperpath);
+ err = ovl_get_workdir(sb, ofs, &ctx->upper, &ctx->work);
if (err)
goto out_err;
@@ -1999,7 +1847,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
sb->s_stack_depth = upper_sb->s_stack_depth;
sb->s_time_gran = upper_sb->s_time_gran;
}
- oe = ovl_get_lowerstack(sb, splitlower, numlower, ofs, layers);
+ oe = ovl_get_lowerstack(sb, ctx, ofs, layers);
err = PTR_ERR(oe);
if (IS_ERR(oe))
goto out_err;
@@ -2014,7 +1862,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
}
if (!ovl_force_readonly(ofs) && ofs->config.index) {
- err = ovl_get_indexdir(sb, ofs, oe, &upperpath);
+ err = ovl_get_indexdir(sb, ofs, oe, &ctx->upper);
if (err)
goto out_free_oe;
@@ -2055,40 +1903,115 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
sb->s_iflags |= SB_I_SKIP_SYNC;
err = -ENOMEM;
- root_dentry = ovl_get_root(sb, upperpath.dentry, oe);
+ root_dentry = ovl_get_root(sb, ctx->upper.dentry, oe);
if (!root_dentry)
goto out_free_oe;
- mntput(upperpath.mnt);
- kfree(splitlower);
-
sb->s_root = root_dentry;
return 0;
out_free_oe:
- ovl_entry_stack_free(oe);
- kfree(oe);
+ ovl_free_entry(oe);
out_err:
- kfree(splitlower);
- path_put(&upperpath);
ovl_free_fs(ofs);
-out:
+ sb->s_fs_info = NULL;
return err;
}
-static struct dentry *ovl_mount(struct file_system_type *fs_type, int flags,
- const char *dev_name, void *raw_data)
+static int ovl_get_tree(struct fs_context *fc)
{
- return mount_nodev(fs_type, flags, raw_data, ovl_fill_super);
+ return get_tree_nodev(fc, ovl_fill_super);
+}
+
+static inline void ovl_fs_context_free(struct ovl_fs_context *ctx)
+{
+ ovl_parse_param_drop_lowerdir(ctx);
+ path_put(&ctx->upper);
+ path_put(&ctx->work);
+ kfree(ctx->lower);
+ kfree(ctx);
+}
+
+static void ovl_free(struct fs_context *fc)
+{
+ struct ovl_fs *ofs = fc->s_fs_info;
+ struct ovl_fs_context *ctx = fc->fs_private;
+
+ /*
+ * ofs is stored in the fs_context when it is initialized.
+ * ofs is transferred to the superblock on a successful mount,
+ * but if an error occurs before the transfer we have to free
+ * it here.
+ */
+ if (ofs)
+ ovl_free_fs(ofs);
+
+ if (ctx)
+ ovl_fs_context_free(ctx);
+}
+
+static const struct fs_context_operations ovl_context_ops = {
+ .parse_param = ovl_parse_param,
+ .get_tree = ovl_get_tree,
+ .reconfigure = ovl_reconfigure,
+ .free = ovl_free,
+};
+
+/*
+ * This is called during fsopen() and will record the user namespace of
+ * the caller in fc->user_ns since we've raised FS_USERNS_MOUNT. We'll
+ * need it when we actually create the superblock to verify that the
+ * process creating the superblock is in the same user namespace as
+ * process that called fsopen().
+ */
+static int ovl_init_fs_context(struct fs_context *fc)
+{
+ struct ovl_fs_context *ctx;
+ struct ovl_fs *ofs;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL_ACCOUNT);
+ if (!ctx)
+ return -ENOMEM;
+
+ /*
+ * By default we allocate for three lower layers. It's likely
+ * that it'll cover most users.
+ */
+ ctx->lower = kmalloc_array(3, sizeof(*ctx->lower), GFP_KERNEL_ACCOUNT);
+ if (!ctx->lower)
+ goto out_err;
+ ctx->capacity = 3;
+
+ ofs = kzalloc(sizeof(struct ovl_fs), GFP_KERNEL);
+ if (!ofs)
+ goto out_err;
+
+ ofs->config.redirect_mode = ovl_redirect_mode_def();
+ ofs->config.index = ovl_index_def;
+ ofs->config.uuid = true;
+ ofs->config.nfs_export = ovl_nfs_export_def;
+ ofs->config.xino = ovl_xino_def();
+ ofs->config.metacopy = ovl_metacopy_def;
+
+ fc->s_fs_info = ofs;
+ fc->fs_private = ctx;
+ fc->ops = &ovl_context_ops;
+ return 0;
+
+out_err:
+ ovl_fs_context_free(ctx);
+ return -ENOMEM;
+
}
static struct file_system_type ovl_fs_type = {
- .owner = THIS_MODULE,
- .name = "overlay",
- .fs_flags = FS_USERNS_MOUNT,
- .mount = ovl_mount,
- .kill_sb = kill_anon_super,
+ .owner = THIS_MODULE,
+ .name = "overlay",
+ .init_fs_context = ovl_init_fs_context,
+ .parameters = ovl_parameter_spec,
+ .fs_flags = FS_USERNS_MOUNT,
+ .kill_sb = kill_anon_super,
};
MODULE_ALIAS_FS("overlay");
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 923d66d131c1..7ef9e13c404a 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -83,33 +83,84 @@ bool ovl_verify_lower(struct super_block *sb)
return ofs->config.nfs_export && ofs->config.index;
}
+struct ovl_path *ovl_stack_alloc(unsigned int n)
+{
+ return kcalloc(n, sizeof(struct ovl_path), GFP_KERNEL);
+}
+
+void ovl_stack_cpy(struct ovl_path *dst, struct ovl_path *src, unsigned int n)
+{
+ unsigned int i;
+
+ memcpy(dst, src, sizeof(struct ovl_path) * n);
+ for (i = 0; i < n; i++)
+ dget(src[i].dentry);
+}
+
+void ovl_stack_put(struct ovl_path *stack, unsigned int n)
+{
+ unsigned int i;
+
+ for (i = 0; stack && i < n; i++)
+ dput(stack[i].dentry);
+}
+
+void ovl_stack_free(struct ovl_path *stack, unsigned int n)
+{
+ ovl_stack_put(stack, n);
+ kfree(stack);
+}
+
struct ovl_entry *ovl_alloc_entry(unsigned int numlower)
{
- size_t size = offsetof(struct ovl_entry, lowerstack[numlower]);
+ size_t size = offsetof(struct ovl_entry, __lowerstack[numlower]);
struct ovl_entry *oe = kzalloc(size, GFP_KERNEL);
if (oe)
- oe->numlower = numlower;
+ oe->__numlower = numlower;
return oe;
}
+void ovl_free_entry(struct ovl_entry *oe)
+{
+ ovl_stack_put(ovl_lowerstack(oe), ovl_numlower(oe));
+ kfree(oe);
+}
+
+#define OVL_D_REVALIDATE (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE)
+
bool ovl_dentry_remote(struct dentry *dentry)
{
- return dentry->d_flags &
- (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE);
+ return dentry->d_flags & OVL_D_REVALIDATE;
}
-void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *upperdentry,
- unsigned int mask)
+void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *realdentry)
{
- struct ovl_entry *oe = OVL_E(dentry);
+ if (!ovl_dentry_remote(realdentry))
+ return;
+
+ spin_lock(&dentry->d_lock);
+ dentry->d_flags |= realdentry->d_flags & OVL_D_REVALIDATE;
+ spin_unlock(&dentry->d_lock);
+}
+
+void ovl_dentry_init_reval(struct dentry *dentry, struct dentry *upperdentry,
+ struct ovl_entry *oe)
+{
+ return ovl_dentry_init_flags(dentry, upperdentry, oe, OVL_D_REVALIDATE);
+}
+
+void ovl_dentry_init_flags(struct dentry *dentry, struct dentry *upperdentry,
+ struct ovl_entry *oe, unsigned int mask)
+{
+ struct ovl_path *lowerstack = ovl_lowerstack(oe);
unsigned int i, flags = 0;
if (upperdentry)
flags |= upperdentry->d_flags;
- for (i = 0; i < oe->numlower; i++)
- flags |= oe->lowerstack[i].dentry->d_flags;
+ for (i = 0; i < ovl_numlower(oe) && lowerstack[i].dentry; i++)
+ flags |= lowerstack[i].dentry->d_flags;
spin_lock(&dentry->d_lock);
dentry->d_flags &= ~mask;
@@ -127,7 +178,7 @@ bool ovl_dentry_weird(struct dentry *dentry)
enum ovl_path_type ovl_path_type(struct dentry *dentry)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ struct ovl_entry *oe = OVL_E(dentry);
enum ovl_path_type type = 0;
if (ovl_dentry_upper(dentry)) {
@@ -136,7 +187,7 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry)
/*
* Non-dir dentry can hold lower dentry of its copy up origin.
*/
- if (oe->numlower) {
+ if (ovl_numlower(oe)) {
if (ovl_test_flag(OVL_CONST_INO, d_inode(dentry)))
type |= __OVL_PATH_ORIGIN;
if (d_is_dir(dentry) ||
@@ -144,7 +195,7 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry)
type |= __OVL_PATH_MERGE;
}
} else {
- if (oe->numlower > 1)
+ if (ovl_numlower(oe) > 1)
type |= __OVL_PATH_MERGE;
}
return type;
@@ -160,11 +211,12 @@ void ovl_path_upper(struct dentry *dentry, struct path *path)
void ovl_path_lower(struct dentry *dentry, struct path *path)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ struct ovl_entry *oe = OVL_E(dentry);
+ struct ovl_path *lowerpath = ovl_lowerstack(oe);
- if (oe->numlower) {
- path->mnt = oe->lowerstack[0].layer->mnt;
- path->dentry = oe->lowerstack[0].dentry;
+ if (ovl_numlower(oe)) {
+ path->mnt = lowerpath->layer->mnt;
+ path->dentry = lowerpath->dentry;
} else {
*path = (struct path) { };
}
@@ -172,11 +224,19 @@ void ovl_path_lower(struct dentry *dentry, struct path *path)
void ovl_path_lowerdata(struct dentry *dentry, struct path *path)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ struct ovl_entry *oe = OVL_E(dentry);
+ struct ovl_path *lowerdata = ovl_lowerdata(oe);
+ struct dentry *lowerdata_dentry = ovl_lowerdata_dentry(oe);
- if (oe->numlower) {
- path->mnt = oe->lowerstack[oe->numlower - 1].layer->mnt;
- path->dentry = oe->lowerstack[oe->numlower - 1].dentry;
+ if (lowerdata_dentry) {
+ path->dentry = lowerdata_dentry;
+ /*
+ * Pairs with smp_wmb() in ovl_dentry_set_lowerdata().
+ * Make sure that if lowerdata->dentry is visible, then
+ * datapath->layer is visible as well.
+ */
+ smp_rmb();
+ path->mnt = READ_ONCE(lowerdata->layer)->mnt;
} else {
*path = (struct path) { };
}
@@ -215,16 +275,16 @@ struct dentry *ovl_dentry_upper(struct dentry *dentry)
struct dentry *ovl_dentry_lower(struct dentry *dentry)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ struct ovl_entry *oe = OVL_E(dentry);
- return oe->numlower ? oe->lowerstack[0].dentry : NULL;
+ return ovl_numlower(oe) ? ovl_lowerstack(oe)->dentry : NULL;
}
const struct ovl_layer *ovl_layer_lower(struct dentry *dentry)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ struct ovl_entry *oe = OVL_E(dentry);
- return oe->numlower ? oe->lowerstack[0].layer : NULL;
+ return ovl_numlower(oe) ? ovl_lowerstack(oe)->layer : NULL;
}
/*
@@ -235,9 +295,30 @@ const struct ovl_layer *ovl_layer_lower(struct dentry *dentry)
*/
struct dentry *ovl_dentry_lowerdata(struct dentry *dentry)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ return ovl_lowerdata_dentry(OVL_E(dentry));
+}
+
+int ovl_dentry_set_lowerdata(struct dentry *dentry, struct ovl_path *datapath)
+{
+ struct ovl_entry *oe = OVL_E(dentry);
+ struct ovl_path *lowerdata = ovl_lowerdata(oe);
+ struct dentry *datadentry = datapath->dentry;
+
+ if (WARN_ON_ONCE(ovl_numlower(oe) <= 1))
+ return -EIO;
+
+ WRITE_ONCE(lowerdata->layer, datapath->layer);
+ /*
+ * Pairs with smp_rmb() in ovl_path_lowerdata().
+ * Make sure that if lowerdata->dentry is visible, then
+ * lowerdata->layer is visible as well.
+ */
+ smp_wmb();
+ WRITE_ONCE(lowerdata->dentry, dget(datadentry));
- return oe->numlower ? oe->lowerstack[oe->numlower - 1].dentry : NULL;
+ ovl_dentry_update_reval(dentry, datadentry);
+
+ return 0;
}
struct dentry *ovl_dentry_real(struct dentry *dentry)
@@ -250,15 +331,19 @@ struct dentry *ovl_i_dentry_upper(struct inode *inode)
return ovl_upperdentry_dereference(OVL_I(inode));
}
-void ovl_i_path_real(struct inode *inode, struct path *path)
+struct inode *ovl_i_path_real(struct inode *inode, struct path *path)
{
+ struct ovl_path *lowerpath = ovl_lowerpath(OVL_I_E(inode));
+
path->dentry = ovl_i_dentry_upper(inode);
if (!path->dentry) {
- path->dentry = OVL_I(inode)->lowerpath.dentry;
- path->mnt = OVL_I(inode)->lowerpath.layer->mnt;
+ path->dentry = lowerpath->dentry;
+ path->mnt = lowerpath->layer->mnt;
} else {
path->mnt = ovl_upper_mnt(OVL_FS(inode->i_sb));
}
+
+ return path->dentry ? d_inode_rcu(path->dentry) : NULL;
}
struct inode *ovl_inode_upper(struct inode *inode)
@@ -270,9 +355,9 @@ struct inode *ovl_inode_upper(struct inode *inode)
struct inode *ovl_inode_lower(struct inode *inode)
{
- struct dentry *lowerdentry = OVL_I(inode)->lowerpath.dentry;
+ struct ovl_path *lowerpath = ovl_lowerpath(OVL_I_E(inode));
- return lowerdentry ? d_inode(lowerdentry) : NULL;
+ return lowerpath ? d_inode(lowerpath->dentry) : NULL;
}
struct inode *ovl_inode_real(struct inode *inode)
@@ -283,10 +368,12 @@ struct inode *ovl_inode_real(struct inode *inode)
/* Return inode which contains lower data. Do not return metacopy */
struct inode *ovl_inode_lowerdata(struct inode *inode)
{
+ struct dentry *lowerdata = ovl_lowerdata_dentry(OVL_I_E(inode));
+
if (WARN_ON(!S_ISREG(inode->i_mode)))
return NULL;
- return OVL_I(inode)->lowerdata ?: ovl_inode_lower(inode);
+ return lowerdata ? d_inode(lowerdata) : NULL;
}
/* Return real inode which contains data. Does not return metacopy inode */
@@ -301,9 +388,15 @@ struct inode *ovl_inode_realdata(struct inode *inode)
return ovl_inode_lowerdata(inode);
}
+const char *ovl_lowerdata_redirect(struct inode *inode)
+{
+ return inode && S_ISREG(inode->i_mode) ?
+ OVL_I(inode)->lowerdata_redirect : NULL;
+}
+
struct ovl_dir_cache *ovl_dir_cache(struct inode *inode)
{
- return OVL_I(inode)->cache;
+ return inode && S_ISDIR(inode->i_mode) ? OVL_I(inode)->cache : NULL;
}
void ovl_set_dir_cache(struct inode *inode, struct ovl_dir_cache *cache)
@@ -313,17 +406,17 @@ void ovl_set_dir_cache(struct inode *inode, struct ovl_dir_cache *cache)
void ovl_dentry_set_flag(unsigned long flag, struct dentry *dentry)
{
- set_bit(flag, &OVL_E(dentry)->flags);
+ set_bit(flag, OVL_E_FLAGS(dentry));
}
void ovl_dentry_clear_flag(unsigned long flag, struct dentry *dentry)
{
- clear_bit(flag, &OVL_E(dentry)->flags);
+ clear_bit(flag, OVL_E_FLAGS(dentry));
}
bool ovl_dentry_test_flag(unsigned long flag, struct dentry *dentry)
{
- return test_bit(flag, &OVL_E(dentry)->flags);
+ return test_bit(flag, OVL_E_FLAGS(dentry));
}
bool ovl_dentry_is_opaque(struct dentry *dentry)
@@ -413,13 +506,6 @@ bool ovl_dentry_needs_data_copy_up(struct dentry *dentry, int flags)
return !ovl_has_upperdata(d_inode(dentry));
}
-bool ovl_redirect_dir(struct super_block *sb)
-{
- struct ovl_fs *ofs = sb->s_fs_info;
-
- return ofs->config.redirect_dir && !ofs->noxattr;
-}
-
const char *ovl_dentry_get_redirect(struct dentry *dentry)
{
return OVL_I(d_inode(dentry))->redirect;
@@ -999,7 +1085,7 @@ out:
bool ovl_is_metacopy_dentry(struct dentry *dentry)
{
- struct ovl_entry *oe = dentry->d_fsdata;
+ struct ovl_entry *oe = OVL_E(dentry);
if (!d_is_reg(dentry))
return false;
@@ -1010,7 +1096,7 @@ bool ovl_is_metacopy_dentry(struct dentry *dentry)
return false;
}
- return (oe->numlower > 1);
+ return (ovl_numlower(oe) > 1);
}
char *ovl_get_redirect_xattr(struct ovl_fs *ofs, const struct path *path, int padding)
@@ -1105,8 +1191,7 @@ void ovl_copyattr(struct inode *inode)
vfsuid_t vfsuid;
vfsgid_t vfsgid;
- ovl_i_path_real(inode, &realpath);
- realinode = d_inode(realpath.dentry);
+ realinode = ovl_i_path_real(inode, &realpath);
real_idmap = mnt_idmap(realpath.mnt);
vfsuid = i_uid_into_vfsuid(real_idmap, realinode);