summaryrefslogtreecommitdiff
path: root/fs/overlayfs/namei.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/overlayfs/namei.c')
-rw-r--r--fs/overlayfs/namei.c61
1 files changed, 49 insertions, 12 deletions
diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
index 64d63b260709..ddf60254888a 100644
--- a/fs/overlayfs/namei.c
+++ b/fs/overlayfs/namei.c
@@ -27,6 +27,7 @@ struct ovl_lookup_data {
bool last;
char *redirect;
char *upperredirect;
+ char *lowerdata_redirect;
int metacopy;
/* Referring to last redirect xattr */
bool absolute_redirect;
@@ -39,13 +40,12 @@ 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;
+ d->absolute_redirect = buf[0] == '/';
+ if (d->absolute_redirect) {
/*
* One of the ancestor path elements in an absolute path
* lookup in ovl_lookup_layer() could have been opaque and
@@ -1047,6 +1047,14 @@ out:
out_err:
pr_warn_ratelimited("lazy lowerdata lookup failed (%pd2, err=%i)\n",
dentry, err);
+ /*
+ * The data of a metacopy file is missing or is not a regular file in
+ * the data-only layers: the layers are corrupted. Do not return the
+ * error of the lookup (e.g. ENOENT) to the caller, it would make the
+ * file look like it does not exist.
+ */
+ if (err != -ENOMEM)
+ err = -EIO;
goto out;
}
@@ -1079,7 +1087,8 @@ static bool ovl_check_follow_redirect(struct ovl_lookup_data *d)
pr_warn_ratelimited("refusing to follow metacopy origin for (%pd2)\n", d->dentry);
return false;
}
- if ((d->redirect || d->upperredirect) && !ovl_redirect_follow(ofs)) {
+ /* Check follow to lower layer, redirects to lowerdata are not checked here */
+ if ((d->redirect || d->upperredirect) && !ovl_redirect_follow_lower(ofs)) {
pr_warn_ratelimited("refusing to follow redirect for (%pd2)\n", d->dentry);
return false;
}
@@ -1097,13 +1106,26 @@ struct ovl_lookup_ctx {
unsigned int ctr;
};
+/*
+ * Without metacopy=on, the absolute redirect of a lower metacopy file is
+ * followed only into the data-only layers.
+ */
+static bool ovl_is_lowerdata_redirect(struct ovl_lookup_ctx *ctx,
+ struct ovl_lookup_data *d)
+{
+ struct ovl_fs *ofs = OVL_FS(d->sb);
+
+ return ofs->numdatalayer && !ofs->config.metacopy && ctx->ctr &&
+ d->metacopy && d->absolute_redirect;
+}
+
static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data *d)
{
struct dentry *dentry = ctx->dentry;
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct ovl_entry *poe = OVL_E(dentry->d_parent);
struct ovl_entry *roe = OVL_E(dentry->d_sb->s_root);
- bool check_redirect = (ovl_redirect_follow(ofs) || ofs->numdatalayer);
+ bool check_redirect = ovl_redirect_follow(ofs);
struct dentry *upperdir;
struct dentry *this;
struct dentry *origin = NULL;
@@ -1234,6 +1256,10 @@ static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data
ctx->ctr++;
}
+ /* Lookup of lowerdata in the data-only layers only */
+ if (ovl_is_lowerdata_redirect(ctx, d))
+ d->stop = true;
+
if (d->stop)
break;
@@ -1251,6 +1277,8 @@ static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data
if (d->metacopy && ctx->ctr && ofs->numdatalayer && d->absolute_redirect) {
d->metacopy = 0;
ctx->ctr++;
+ d->lowerdata_redirect = d->redirect;
+ d->redirect = NULL;
} else if (!ovl_check_follow_redirect(d)) {
err = -EPERM;
return err;
@@ -1367,24 +1395,32 @@ static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data
};
/* Store lowerdata redirect for lazy lookup */
- if (ctx->ctr > 1 && !d->is_dir && !ctx->stack[ctx->ctr - 1].dentry) {
- oip.lowerdata_redirect = d->redirect;
- d->redirect = NULL;
- }
+ if (d->lowerdata_redirect)
+ oip.lowerdata_redirect = d->lowerdata_redirect;
inode = ovl_get_inode(dentry->d_sb, &oip);
if (IS_ERR(inode))
return PTR_ERR(inode);
+ d->lowerdata_redirect = NULL;
+
ctx->inode = inode;
if (ctx->upperdentry && !uppermetacopy)
ovl_set_flag(OVL_UPPERDATA, ctx->inode);
if (metacopy_size > OVL_METACOPY_MIN_SIZE)
ovl_set_flag(OVL_HAS_DIGEST, ctx->inode);
- }
- ovl_dentry_init_reval(dentry, ctx->upperdentry, OVL_I_E(ctx->inode));
+ /*
+ * ovl_get_inode() puts ctx->upperdentry if it finds a cached
+ * inode, e.g. for another hard link, so use the upper dentry
+ * of the inode.
+ */
+ ovl_dentry_init_reval(dentry, ovl_i_dentry_upper(inode),
+ OVL_I_E(inode));
+ } else {
+ ovl_dentry_init_reval(dentry, NULL, NULL);
+ }
return 0;
}
@@ -1394,7 +1430,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
{
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct ovl_entry *poe = OVL_E(dentry->d_parent);
- bool check_redirect = (ovl_redirect_follow(ofs) || ofs->numdatalayer);
+ bool check_redirect = ovl_redirect_follow(ofs);
int err;
struct ovl_lookup_ctx ctx = {
.dentry = dentry,
@@ -1419,6 +1455,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
dput(ctx.index);
ovl_stack_free(ctx.stack, ctx.ctr);
kfree(d.redirect);
+ kfree(d.lowerdata_redirect);
if (err) {
ovl_free_entry(ctx.oe);