#include <linux/fs.h>
#include <linux/cred.h>
#include <linux/ctype.h>
#include <linux/hex.h>
#include <linux/namei.h>
#include <linux/xattr.h>
#include <linux/ratelimit.h>
#include <linux/mount.h>
#include <linux/exportfs.h>
#include "overlayfs.h"
struct ovl_lookup_data {
struct super_block *sb;
struct dentry *dentry;
const struct ovl_layer *layer;
struct qstr name;
bool is_dir;
bool opaque;
bool xwhiteouts;
bool stop;
bool last;
char *redirect;
char *upperredirect;
int metacopy;
bool absolute_redirect;
};
static int ovl_check_redirect(const struct path *path, struct ovl_lookup_data *d,
size_t prelen, const char *post)
{
int res;
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->stop = false;
} else {
res = strlen(buf) + 1;
memmove(buf + prelen, buf, res);
memcpy(buf, d->name.name, prelen);
}
strcat(buf, post);
kfree(d->redirect);
d->redirect = buf;
d->name.name = d->redirect;
d->name.len = strlen(d->redirect);
return 0;
}
static int ovl_acceptable(void *ctx, struct dentry *dentry)
{
if (!d_is_dir(dentry))
return 1;
if (d_unhashed(dentry))
return 0;
return is_subdir(dentry, ((struct vfsmount *)ctx)->mnt_root);
}
int ovl_check_fb_len(struct ovl_fb *fb, int fb_len)
{
if (fb_len < sizeof(struct ovl_fb) || fb_len < fb->len)
return -EINVAL;
if (fb->magic != OVL_FH_MAGIC)
return -EINVAL;
if (fb->version > OVL_FH_VERSION || fb->flags & ~OVL_FH_FLAG_ALL)
return -ENODATA;
if (!(fb->flags & OVL_FH_FLAG_ANY_ENDIAN) &&
(fb->flags & OVL_FH_FLAG_BIG_ENDIAN) != OVL_FH_FLAG_CPU_ENDIAN)
return -ENODATA;
return 0;
}
static struct ovl_fh *ovl_get_fh(struct ovl_fs *ofs, struct dentry *upperdentry,
enum ovl_xattr ox)
{
int res, err;
struct ovl_fh *fh = NULL;
res = ovl_getxattr_upper(ofs, upperdentry, ox, NULL, 0);
if (res < 0) {
if (res == -ENODATA || res == -EOPNOTSUPP)
return NULL;
goto fail;
}
if (res == 0)
return NULL;
fh = kzalloc(res + OVL_FH_WIRE_OFFSET, GFP_KERNEL);
if (!fh)
return ERR_PTR(-ENOMEM);
res = ovl_getxattr_upper(ofs, upperdentry, ox, fh->buf, res);
if (res < 0)
goto fail;
err = ovl_check_fb_len(&fh->fb, res);
if (err < 0) {
if (err == -ENODATA)
goto out;
goto invalid;
}
return fh;
out:
kfree(fh);
return NULL;
fail:
pr_warn_ratelimited("failed to get origin (%i)\n", res);
goto out;
invalid:
pr_warn_ratelimited("invalid origin (%*phN)\n", res, fh);
goto out;
}
bool ovl_uuid_match(struct ovl_fs *ofs, const struct super_block *sb,
const uuid_t *uuid)
{
return ovl_origin_uuid(ofs) ? uuid_equal(uuid, &sb->s_uuid) :
uuid_is_null(uuid);
}
struct dentry *ovl_decode_real_fh(struct ovl_fs *ofs, struct ovl_fh *fh,
struct vfsmount *mnt, bool connected)
{
struct dentry *real;
int bytes;
if (!capable(CAP_DAC_READ_SEARCH))
return NULL;
if (!ovl_uuid_match(ofs, mnt->mnt_sb, &fh->fb.uuid))
return NULL;
bytes = (fh->fb.len - offsetof(struct ovl_fb, fid));
real = exportfs_decode_fh(mnt, (struct fid *)fh->fb.fid,
bytes >> 2, (int)fh->fb.type,
connected ? ovl_acceptable : NULL, mnt);
if (IS_ERR(real)) {
if (real == ERR_PTR(-ESTALE) &&
!(fh->fb.flags & OVL_FH_FLAG_PATH_UPPER))
real = NULL;
return real;
}
if (ovl_dentry_weird(real)) {
dput(real);
return NULL;
}
return real;
}
static struct dentry *ovl_lookup_positive_unlocked(struct ovl_lookup_data *d,
const char *name,
struct dentry *base, int len,
bool drop_negative)
{
struct dentry *ret = lookup_one_unlocked(mnt_idmap(d->layer->mnt),
&QSTR_LEN(name, len), base);
if (!IS_ERR(ret) && d_flags_negative(smp_load_acquire(&ret->d_flags))) {
if (drop_negative && ret->d_lockref.count == 1) {
spin_lock(&ret->d_lock);
if (d_is_negative(ret) && ret->d_lockref.count == 1)
__d_drop(ret);
spin_unlock(&ret->d_lock);
}
dput(ret);
ret = ERR_PTR(-ENOENT);
}
return ret;
}
static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
const char *name, unsigned int namelen,
size_t prelen, const char *post,
struct dentry **ret, bool drop_negative)
{
struct ovl_fs *ofs = OVL_FS(d->sb);
struct dentry *this = NULL;
const char *warn;
struct path path;
int err;
bool last_element = !post[0];
bool is_upper = d->layer->idx == 0;
char val;
if (ofs->casefold != ovl_dentry_casefolded(base)) {
warn = "parent wrong casefold";
err = -ESTALE;
goto out_warn;
}
this = ovl_lookup_positive_unlocked(d, name, base, namelen, drop_negative);
if (IS_ERR(this)) {
err = PTR_ERR(this);
this = NULL;
if (err == -ENOENT || err == -ENAMETOOLONG)
goto out;
goto out_err;
}
if (ofs->casefold != ovl_dentry_casefolded(this)) {
warn = "child wrong casefold";
err = -EREMOTE;
goto out_warn;
}
if (ovl_dentry_weird(this)) {
warn = "unsupported object type";
err = -EREMOTE;
goto out_warn;
}
path.dentry = this;
path.mnt = d->layer->mnt;
if (ovl_path_is_whiteout(ofs, &path)) {
d->stop = d->opaque = true;
goto put_and_out;
}
if (last_element && d->metacopy && !d_is_reg(this)) {
d->stop = true;
goto put_and_out;
}
if (!d_can_lookup(this)) {
if (d->is_dir || !last_element) {
d->stop = true;
goto put_and_out;
}
err = ovl_check_metacopy_xattr(ofs, &path, NULL);
if (err < 0)
goto out_err;
d->metacopy = err;
d->stop = !d->metacopy;
if (!d->metacopy || d->last)
goto out;
} else {
if (ovl_lookup_trap_inode(d->sb, this)) {
warn = "overlapping layers";
err = -ELOOP;
goto out_warn;
}
if (last_element)
d->is_dir = true;
if (d->last)
goto out;
val = ovl_get_opaquedir_val(ofs, &path);
if (last_element && !is_upper && val == 'x') {
d->xwhiteouts = true;
ovl_layer_set_xwhiteouts(ofs, d->layer);
} else if (val == 'y') {
d->stop = true;
if (last_element)
d->opaque = true;
goto out;
}
}
err = ovl_check_redirect(&path, d, prelen, post);
if (err)
goto out_err;
out:
*ret = this;
return 0;
put_and_out:
dput(this);
this = NULL;
goto out;
out_warn:
pr_warn_ratelimited("failed lookup in %s (%pd2, name='%.*s', err=%i): %s\n",
is_upper ? "upper" : "lower", base,
namelen, name, err, warn);
out_err:
dput(this);
return err;
}
static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d,
struct dentry **ret, bool drop_negative)
{
size_t rem = d->name.len - 1;
struct dentry *dentry = NULL;
int err;
if (d->name.name[0] != '/')
return ovl_lookup_single(base, d, d->name.name, d->name.len,
0, "", ret, drop_negative);
while (!IS_ERR_OR_NULL(base) && d_can_lookup(base)) {
const char *s = d->name.name + d->name.len - rem;
const char *next = strchrnul(s, '/');
size_t thislen = next - s;
bool end = !next[0];
if (WARN_ON(s[-1] != '/'))
return -EIO;
err = ovl_lookup_single(base, d, s, thislen,
d->name.len - rem, next, &base,
drop_negative);
dput(dentry);
if (err)
return err;
dentry = base;
if (end)
break;
rem -= thislen + 1;
if (WARN_ON(rem >= d->name.len))
return -EIO;
}
*ret = dentry;
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;
if (!d_is_reg(datapath->dentry))
goto out_path_put;
return 0;
out_path_put:
path_put(datapath);
return err;
}
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)
{
struct dentry *origin = NULL;
int i;
for (i = 1; i <= ovl_numlowerlayer(ofs); i++) {
if (ofs->layers[i].fsid &&
ofs->layers[i].fs->bad_uuid)
continue;
origin = ovl_decode_real_fh(ofs, fh, ofs->layers[i].mnt,
connected);
if (origin)
break;
}
if (!origin)
return -ESTALE;
else if (IS_ERR(origin))
return PTR_ERR(origin);
if (upperdentry && !ovl_upper_is_whiteout(ofs, upperdentry) &&
inode_wrong_type(d_inode(upperdentry), d_inode(origin)->i_mode))
goto invalid;
if (!*stackp)
*stackp = kmalloc_obj(struct ovl_path);
if (!*stackp) {
dput(origin);
return -ENOMEM;
}
**stackp = (struct ovl_path){
.dentry = origin,
.layer = &ofs->layers[i]
};
return 0;
invalid:
pr_warn_ratelimited("invalid origin (%pd2, ftype=%x, origin ftype=%x).\n",
upperdentry, d_inode(upperdentry)->i_mode & S_IFMT,
d_inode(origin)->i_mode & S_IFMT);
dput(origin);
return -ESTALE;
}
static int ovl_check_origin(struct ovl_fs *ofs, struct dentry *upperdentry,
struct ovl_path **stackp)
{
struct ovl_fh *fh = ovl_get_fh(ofs, upperdentry, OVL_XATTR_ORIGIN);
int err;
if (IS_ERR_OR_NULL(fh))
return PTR_ERR(fh);
err = ovl_check_origin_fh(ofs, fh, false, upperdentry, stackp);
kfree(fh);
if (err) {
if (err == -ESTALE)
return 0;
return err;
}
return 0;
}
static int ovl_verify_fh(struct ovl_fs *ofs, struct dentry *dentry,
enum ovl_xattr ox, const struct ovl_fh *fh)
{
struct ovl_fh *ofh = ovl_get_fh(ofs, dentry, ox);
int err = 0;
if (!ofh)
return -ENODATA;
if (IS_ERR(ofh))
return PTR_ERR(ofh);
if (fh->fb.len != ofh->fb.len || memcmp(&fh->fb, &ofh->fb, fh->fb.len))
err = -ESTALE;
kfree(ofh);
return err;
}
int ovl_verify_set_fh(struct ovl_fs *ofs, struct dentry *dentry,
enum ovl_xattr ox, const struct ovl_fh *fh,
bool is_upper, bool set)
{
int err;
err = ovl_verify_fh(ofs, dentry, ox, fh);
if (set && err == -ENODATA)
err = ovl_setxattr(ofs, dentry, ox, fh->buf, fh->fb.len);
return err;
}
int ovl_verify_origin_xattr(struct ovl_fs *ofs, struct dentry *dentry,
enum ovl_xattr ox, struct dentry *real,
bool is_upper, bool set)
{
struct inode *inode;
struct ovl_fh *fh;
int err;
fh = ovl_encode_real_fh(ofs, d_inode(real), is_upper);
err = PTR_ERR(fh);
if (IS_ERR(fh)) {
fh = NULL;
goto fail;
}
err = ovl_verify_set_fh(ofs, dentry, ox, fh, is_upper, set);
if (err)
goto fail;
out:
kfree(fh);
return err;
fail:
inode = d_inode(real);
pr_warn_ratelimited("failed to verify %s (%pd2, ino=%lu, err=%i)\n",
is_upper ? "upper" : "origin", real,
inode ? inode->i_ino : 0, err);
goto out;
}
struct dentry *ovl_index_upper(struct ovl_fs *ofs, struct dentry *index,
bool connected)
{
struct ovl_fh *fh;
struct dentry *upper;
if (!d_is_dir(index))
return dget(index);
fh = ovl_get_fh(ofs, index, OVL_XATTR_UPPER);
if (IS_ERR_OR_NULL(fh))
return ERR_CAST(fh);
upper = ovl_decode_real_fh(ofs, fh, ovl_upper_mnt(ofs), connected);
kfree(fh);
if (IS_ERR_OR_NULL(upper))
return upper ?: ERR_PTR(-ESTALE);
if (!d_is_dir(upper)) {
pr_warn_ratelimited("invalid index upper (%pd2, upper=%pd2).\n",
index, upper);
dput(upper);
return ERR_PTR(-EIO);
}
return upper;
}
int ovl_verify_index(struct ovl_fs *ofs, struct dentry *index)
{
struct ovl_fh *fh = NULL;
size_t len;
struct ovl_path origin = { };
struct ovl_path *stack = &origin;
struct dentry *upper = NULL;
int err;
if (!d_inode(index))
return 0;
err = -EINVAL;
if (index->d_name.len < sizeof(struct ovl_fb)*2)
goto fail;
err = -ENOMEM;
len = index->d_name.len / 2;
fh = kzalloc(len + OVL_FH_WIRE_OFFSET, GFP_KERNEL);
if (!fh)
goto fail;
err = -EINVAL;
if (hex2bin(fh->buf, index->d_name.name, len))
goto fail;
err = ovl_check_fb_len(&fh->fb, len);
if (err)
goto fail;
if (ovl_is_whiteout(index))
goto out;
if (d_is_dir(index) && !ofs->config.nfs_export)
goto out;
upper = ovl_index_upper(ofs, index, false);
if (IS_ERR_OR_NULL(upper)) {
err = PTR_ERR(upper);
if (err == -ESTALE)
goto orphan;
else if (!err)
err = -ESTALE;
goto fail;
}
err = ovl_verify_fh(ofs, upper, OVL_XATTR_ORIGIN, fh);
dput(upper);
if (err)
goto fail;
if (!d_is_dir(index) && d_inode(index)->i_nlink == 1) {
err = ovl_check_origin_fh(ofs, fh, false, index, &stack);
if (err)
goto fail;
if (ovl_get_nlink(ofs, origin.dentry, index, 0) == 0)
goto orphan;
}
out:
dput(origin.dentry);
kfree(fh);
return err;
fail:
pr_warn_ratelimited("failed to verify index (%pd2, ftype=%x, err=%i)\n",
index, d_inode(index)->i_mode & S_IFMT, err);
goto out;
orphan:
pr_warn_ratelimited("orphan index entry (%pd2, ftype=%x, nlink=%u)\n",
index, d_inode(index)->i_mode & S_IFMT,
d_inode(index)->i_nlink);
err = -ENOENT;
goto out;
}
int ovl_get_index_name_fh(const struct ovl_fh *fh, struct qstr *name)
{
char *n, *s;
n = kcalloc(fh->fb.len, 2, GFP_KERNEL);
if (!n)
return -ENOMEM;
s = bin2hex(n, fh->buf, fh->fb.len);
*name = (struct qstr) QSTR_INIT(n, s - n);
return 0;
}
int ovl_get_index_name(struct ovl_fs *ofs, struct dentry *origin,
struct qstr *name)
{
struct ovl_fh *fh;
int err;
fh = ovl_encode_real_fh(ofs, d_inode(origin), false);
if (IS_ERR(fh))
return PTR_ERR(fh);
err = ovl_get_index_name_fh(fh, name);
kfree(fh);
return err;
}
struct dentry *ovl_get_index_fh(struct ovl_fs *ofs, struct ovl_fh *fh)
{
struct dentry *index;
struct qstr name;
int err;
err = ovl_get_index_name_fh(fh, &name);
if (err)
return ERR_PTR(err);
index = lookup_noperm_positive_unlocked(&name, ofs->workdir);
kfree(name.name);
if (IS_ERR(index)) {
if (PTR_ERR(index) == -ENOENT)
index = NULL;
return index;
}
if (ovl_is_whiteout(index))
err = -ESTALE;
else if (ovl_dentry_weird(index))
err = -EIO;
else
return index;
dput(index);
return ERR_PTR(err);
}
struct dentry *ovl_lookup_index(struct ovl_fs *ofs, struct dentry *upper,
struct dentry *origin, bool verify)
{
struct dentry *index;
struct inode *inode;
struct qstr name;
bool is_dir = d_is_dir(origin);
int err;
err = ovl_get_index_name(ofs, origin, &name);
if (err)
return ERR_PTR(err);
index = lookup_one_positive_unlocked(ovl_upper_mnt_idmap(ofs), &name,
ofs->workdir);
if (IS_ERR(index)) {
err = PTR_ERR(index);
if (err == -ENOENT) {
index = NULL;
goto out;
}
pr_warn_ratelimited("failed inode index lookup (ino=%lu, key=%.*s, err=%i);\n"
"overlayfs: mount with '-o index=off' to disable inodes index.\n",
d_inode(origin)->i_ino, name.len, name.name,
err);
goto out;
}
inode = d_inode(index);
if (ovl_is_whiteout(index) && !verify) {
dput(index);
index = ERR_PTR(-ESTALE);
goto out;
} else if (ovl_dentry_weird(index) || ovl_is_whiteout(index) ||
inode_wrong_type(inode, d_inode(origin)->i_mode)) {
pr_warn_ratelimited("bad index found (index=%pd2, ftype=%x, origin ftype=%x).\n",
index, d_inode(index)->i_mode & S_IFMT,
d_inode(origin)->i_mode & S_IFMT);
goto fail;
} else if (is_dir && verify) {
if (!upper) {
pr_warn_ratelimited("suspected uncovered redirected dir found (origin=%pd2, index=%pd2).\n",
origin, index);
goto fail;
}
err = ovl_verify_upper(ofs, index, upper, false);
if (err) {
if (err == -ESTALE) {
pr_warn_ratelimited("suspected multiply redirected dir found (upper=%pd2, origin=%pd2, index=%pd2).\n",
upper, origin, index);
}
goto fail;
}
} else if (upper && d_inode(upper) != inode) {
goto out_dput;
}
out:
kfree(name.name);
return index;
out_dput:
dput(index);
index = NULL;
goto out;
fail:
dput(index);
index = ERR_PTR(-EIO);
goto out;
}
int ovl_path_next(int idx, struct dentry *dentry, struct path *path,
const struct ovl_layer **layer)
{
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) {
*layer = &OVL_FS(dentry->d_sb)->layers[0];
return ovl_numlower(oe) ? 1 : -1;
}
idx++;
}
BUG_ON(idx > ovl_numlower(oe));
path->dentry = lowerstack[idx - 1].dentry;
*layer = lowerstack[idx - 1].layer;
path->mnt = (*layer)->mnt;
return (idx < ovl_numlower(oe)) ? idx + 1 : -1;
}
static int ovl_fix_origin(struct ovl_fs *ofs, struct dentry *dentry,
struct dentry *lower, struct dentry *upper)
{
const struct ovl_fh *fh;
int err;
if (ovl_check_origin_xattr(ofs, upper))
return 0;
fh = ovl_get_origin_fh(ofs, lower);
if (IS_ERR(fh))
return PTR_ERR(fh);
err = ovl_want_write(dentry);
if (err)
goto out;
err = ovl_set_origin_fh(ofs, fh, upper);
if (!err)
err = ovl_set_impure(dentry->d_parent, upper->d_parent);
ovl_drop_write(dentry);
out:
kfree(fh);
return err;
}
static int ovl_maybe_validate_verity(struct dentry *dentry)
{
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct inode *inode = d_inode(dentry);
struct path datapath, metapath;
int err;
if (!ofs->config.verity_mode ||
!ovl_is_metacopy_dentry(dentry) ||
ovl_test_flag(OVL_VERIFIED_DIGEST, inode))
return 0;
if (!ovl_test_flag(OVL_HAS_DIGEST, inode)) {
if (ofs->config.verity_mode == OVL_VERITY_REQUIRE) {
pr_warn_ratelimited("metacopy file '%pd' has no digest specified\n",
dentry);
return -EIO;
}
return 0;
}
ovl_path_lowerdata(dentry, &datapath);
if (!datapath.dentry)
return -EIO;
ovl_path_real(dentry, &metapath);
if (!metapath.dentry)
return -EIO;
err = ovl_inode_lock_interruptible(inode);
if (err)
return err;
if (!ovl_test_flag(OVL_VERIFIED_DIGEST, inode)) {
with_ovl_creds(dentry->d_sb)
err = ovl_validate_verity(ofs, &metapath, &datapath);
if (err == 0)
ovl_set_flag(OVL_VERIFIED_DIGEST, inode);
}
ovl_inode_unlock(inode);
return err;
}
static 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 = {};
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;
if (ovl_dentry_lowerdata(dentry))
goto out;
with_ovl_creds(dentry->d_sb)
err = ovl_lookup_data_layers(dentry, redirect, &datapath);
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;
}
int ovl_verify_lowerdata(struct dentry *dentry)
{
int err;
err = ovl_maybe_lookup_lowerdata(dentry);
if (err)
return err;
return ovl_maybe_validate_verity(dentry);
}
static bool ovl_check_follow_redirect(struct ovl_lookup_data *d)
{
struct ovl_fs *ofs = OVL_FS(d->sb);
if (d->metacopy && !ofs->config.metacopy) {
pr_warn_ratelimited("refusing to follow metacopy origin for (%pd2)\n", d->dentry);
return false;
}
if ((d->redirect || d->upperredirect) && !ovl_redirect_follow(ofs)) {
pr_warn_ratelimited("refusing to follow redirect for (%pd2)\n", d->dentry);
return false;
}
return true;
}
struct ovl_lookup_ctx {
struct dentry *dentry;
struct ovl_entry *oe;
struct ovl_path *stack;
struct ovl_path *origin_path;
struct dentry *upperdentry;
struct dentry *index;
struct inode *inode;
unsigned int ctr;
};
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);
struct dentry *upperdir;
struct dentry *this;
struct dentry *origin = NULL;
bool upperopaque = false;
bool uppermetacopy = false;
int metacopy_size = 0;
unsigned int i;
int err;
upperdir = ovl_dentry_upper(dentry->d_parent);
if (upperdir) {
d->layer = &ofs->layers[0];
err = ovl_lookup_layer(upperdir, d, &ctx->upperdentry, true);
if (err)
return err;
if (ctx->upperdentry && ctx->upperdentry->d_flags & DCACHE_OP_REAL)
return -EREMOTE;
if (ctx->upperdentry && !d->is_dir) {
err = ovl_check_origin(ofs, ctx->upperdentry, &ctx->origin_path);
if (err)
return err;
if (d->metacopy)
uppermetacopy = true;
metacopy_size = d->metacopy;
}
if (d->redirect) {
err = -ENOMEM;
d->upperredirect = kstrdup(d->redirect, GFP_KERNEL);
if (!d->upperredirect)
return err;
if (d->redirect[0] == '/')
poe = roe;
}
upperopaque = d->opaque;
}
if (!d->stop && ovl_numlower(poe)) {
err = -ENOMEM;
ctx->stack = ovl_stack_alloc(ofs->numlayer - 1);
if (!ctx->stack)
return err;
}
for (i = 0; !d->stop && i < ovl_numlower(poe); i++) {
struct ovl_path lower = ovl_lowerstack(poe)[i];
if (!ovl_check_follow_redirect(d)) {
err = -EPERM;
return err;
}
if (!check_redirect)
d->last = i == ovl_numlower(poe) - 1;
else if (d->is_dir || !ofs->numdatalayer)
d->last = lower.layer->idx == ovl_numlower(roe);
d->layer = lower.layer;
err = ovl_lookup_layer(lower.dentry, d, &this, false);
if (err)
return err;
if (!this)
continue;
if (ctx->upperdentry && !ctx->ctr && !ofs->noxattr && d->is_dir) {
err = ovl_fix_origin(ofs, dentry, this, ctx->upperdentry);
if (err) {
dput(this);
return err;
}
}
if (ctx->upperdentry && !ctx->ctr &&
((d->is_dir && ovl_verify_lower(dentry->d_sb)) ||
(!d->is_dir && ofs->config.index && ctx->origin_path))) {
err = ovl_verify_origin(ofs, ctx->upperdentry, this, false);
if (err) {
dput(this);
if (d->is_dir)
break;
return err;
}
origin = this;
}
if (!ctx->upperdentry && !d->is_dir && !ctx->ctr && d->metacopy)
metacopy_size = d->metacopy;
if (d->metacopy && ctx->ctr) {
dput(this);
this = NULL;
} else {
ctx->stack[ctx->ctr].dentry = this;
ctx->stack[ctx->ctr].layer = lower.layer;
ctx->ctr++;
}
if (d->stop)
break;
if (d->redirect && d->redirect[0] == '/' && poe != roe) {
poe = roe;
i = lower.layer->idx - 1;
}
}
if (d->metacopy && ctx->ctr && ofs->numdatalayer && d->absolute_redirect) {
d->metacopy = 0;
ctx->ctr++;
} else if (!ovl_check_follow_redirect(d)) {
err = -EPERM;
return err;
}
if (d->metacopy || (uppermetacopy && !ctx->ctr)) {
pr_warn_ratelimited("metacopy with no lower data found - abort lookup (%pd2)\n",
dentry);
err = -EIO;
return err;
} else if (!d->is_dir && ctx->upperdentry && !ctx->ctr && ctx->origin_path) {
if (WARN_ON(ctx->stack != NULL)) {
err = -EIO;
return err;
}
ctx->stack = ctx->origin_path;
ctx->ctr = 1;
origin = ctx->origin_path->dentry;
ctx->origin_path = NULL;
}
if (!ctx->upperdentry && ctx->ctr)
origin = ctx->stack[0].dentry;
if (origin && ovl_indexdir(dentry->d_sb) &&
(!d->is_dir || ovl_index_all(dentry->d_sb))) {
ctx->index = ovl_lookup_index(ofs, ctx->upperdentry, origin, true);
if (IS_ERR(ctx->index)) {
err = PTR_ERR(ctx->index);
ctx->index = NULL;
return err;
}
}
if (ctx->ctr) {
ctx->oe = ovl_alloc_entry(ctx->ctr);
err = -ENOMEM;
if (!ctx->oe)
return err;
ovl_stack_cpy(ovl_lowerstack(ctx->oe), ctx->stack, ctx->ctr);
}
if (upperopaque)
ovl_dentry_set_opaque(dentry);
if (d->xwhiteouts)
ovl_dentry_set_xwhiteouts(dentry);
if (ctx->upperdentry)
ovl_dentry_set_upper_alias(dentry);
else if (ctx->index) {
char *upperredirect;
struct path upperpath = {
.dentry = ctx->upperdentry = dget(ctx->index),
.mnt = ovl_upper_mnt(ofs),
};
upperredirect = ovl_get_redirect_xattr(ofs, &upperpath, 0);
if (IS_ERR(upperredirect))
return PTR_ERR(upperredirect);
d->upperredirect = upperredirect;
err = ovl_check_metacopy_xattr(ofs, &upperpath, NULL);
if (err < 0)
return err;
d->metacopy = uppermetacopy = err;
metacopy_size = err;
if (!ovl_check_follow_redirect(d)) {
err = -EPERM;
return err;
}
}
if (ctx->upperdentry || ctx->ctr) {
struct inode *inode;
struct ovl_inode_params oip = {
.upperdentry = ctx->upperdentry,
.oe = ctx->oe,
.index = ctx->index,
.redirect = d->upperredirect,
};
if (ctx->ctr > 1 && !d->is_dir && !ctx->stack[ctx->ctr - 1].dentry) {
oip.lowerdata_redirect = d->redirect;
d->redirect = NULL;
}
inode = ovl_get_inode(dentry->d_sb, &oip);
if (IS_ERR(inode))
return PTR_ERR(inode);
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));
return 0;
}
struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
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);
int err;
struct ovl_lookup_ctx ctx = {
.dentry = dentry,
};
struct ovl_lookup_data d = {
.sb = dentry->d_sb,
.dentry = dentry,
.name = dentry->d_name,
.last = check_redirect ? false : !ovl_numlower(poe),
};
if (dentry->d_name.len > ofs->namelen)
return ERR_PTR(-ENAMETOOLONG);
with_ovl_creds(dentry->d_sb)
err = ovl_lookup_layers(&ctx, &d);
if (ctx.origin_path) {
dput(ctx.origin_path->dentry);
kfree(ctx.origin_path);
}
dput(ctx.index);
ovl_stack_free(ctx.stack, ctx.ctr);
kfree(d.redirect);
if (err) {
ovl_free_entry(ctx.oe);
dput(ctx.upperdentry);
kfree(d.upperredirect);
return ERR_PTR(err);
}
return d_splice_alias(ctx.inode, dentry);
}
bool ovl_lower_positive(struct dentry *dentry)
{
struct ovl_entry *poe = OVL_E(dentry->d_parent);
const struct qstr *name = &dentry->d_name;
unsigned int i;
bool positive = false;
bool done = false;
if (!dentry->d_inode)
return ovl_dentry_is_opaque(dentry);
if (!ovl_dentry_upper(dentry))
return true;
with_ovl_creds(dentry->d_sb) {
for (i = 0; !done && !positive && i < ovl_numlower(poe); i++) {
struct dentry *this;
struct ovl_path *parentpath = &ovl_lowerstack(poe)[i];
this = lookup_one_positive_unlocked(mnt_idmap(parentpath->layer->mnt),
&QSTR_LEN(name->name, name->len),
parentpath->dentry);
if (IS_ERR(this)) {
switch (PTR_ERR(this)) {
case -ENOENT:
case -ENAMETOOLONG:
break;
default:
positive = true;
break;
}
} else {
struct path path = {
.dentry = this,
.mnt = parentpath->layer->mnt,
};
positive = !ovl_path_is_whiteout(OVL_FS(dentry->d_sb), &path);
done = true;
dput(this);
}
}
}
return positive;
}