// SPDX-License-Identifier: GPL-2.0-only
/*
*
* Copyright (C) 2011 Novell Inc.
*/
#include <uapi/linux/magic.h>
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/xattr.h>
#include <linux/mount.h>
#include <linux/parser.h>
#include <linux/module.h>
#include <linux/statfs.h>
#include <linux/seq_file.h>
#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"
#include "params.h"
MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
MODULE_DESCRIPTION("Overlay filesystem");
MODULE_LICENSE("GPL");
struct ovl_dir_cache;
static struct dentry *ovl_d_real(struct dentry *dentry, enum d_real_type type)
{
struct dentry *upper, *lower;
int err;
switch (type) {
case D_REAL_DATA:
case D_REAL_METADATA:
break;
default:
goto bug;
}
if (!d_is_reg(dentry)) {
/* d_real_inode() is only relevant for regular files */
return dentry;
}
upper = ovl_dentry_upper(dentry);
if (upper && (type == D_REAL_METADATA ||
ovl_has_upperdata(d_inode(dentry))))
return upper;
if (type == D_REAL_METADATA) {
lower = ovl_dentry_lower(dentry);
goto real_lower;
}
/*
* Best effort lazy lookup of lowerdata for D_REAL_DATA case to return
* the real lowerdata dentry. The only current caller of d_real() with
* D_REAL_DATA 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.
*/
err = ovl_verify_lowerdata(dentry);
if (err)
goto bug;
lower = ovl_dentry_lowerdata(dentry);
if (!lower)
goto bug;
real_lower:
/* Handle recursion into stacked lower fs */
return d_real(lower, type);
bug:
WARN(1, "%s(%pd4, %d): real dentry not found\n", __func__, dentry, type);
return dentry;
}
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);
} else if (d->d_flags & DCACHE_OP_REVALIDATE) {
struct dentry *parent;
struct inode *dir;
struct name_snapshot n;
if (flags & LOOKUP_RCU) {
parent = READ_ONCE(d->d_parent);
dir = d_inode_rcu(parent);
if (!dir)
return -ECHILD;
} else {
parent = dget_parent(d);
dir = d_inode(parent);
}
take_dentry_name_snapshot(&n, d);
ret = d->d_op->d_revalidate(dir, &n.name, d, flags);
release_dentry_name_snapshot(&n);
if (!(flags & LOOKUP_RCU))
dput(parent);
if (!ret) {
if (!(flags & LOOKUP_RCU))
d_invalidate(d);
ret = -ESTALE;
}
}
return ret;
}
static int ovl_dentry_revalidate_common(struct dentry *dentry,
unsigned int flags, bool weak)
{
struct ovl_entry *oe;
struct ovl_path *lowerstack;
struct inode *inode = d_inode_rcu(dentry);
struct dentry *upper;
unsigned int i;
int ret = 1;
/* Careful in RCU mode */
if (!inode)
return -ECHILD;
oe = OVL_I_E(inode);
lowerstack = ovl_lowerstack(oe);
upper = ovl_i_dentry_upper(inode);
if (upper)
ret = ovl_revalidate_real(upper, flags, weak);
for (i = 0; ret > 0 && i < ovl_numlower(oe); i++)
ret = ovl_revalidate_real(lowerstack[i].dentry, flags, weak);
return ret;
}
static int ovl_dentry_revalidate(struct inode *dir, const struct qstr *name,
struct dentry *dentry, unsigned int flags)
{
return ovl_dentry_revalidate_common(dentry, flags, false);
}
static int ovl_dentry_weak_revalidate(struct dentry *dentry, unsigned int flags)
{
return ovl_dentry_revalidate_common(dentry, flags, true);
}
static const struct dentry_operations ovl_dentry_operations = {
.d_real = ovl_d_real,
.d_revalidate = ovl_dentry_revalidate,
.d_weak_revalidate = ovl_dentry_weak_revalidate,
};
#if IS_ENABLED(CONFIG_UNICODE)
static const struct dentry_operations ovl_dentry_ci_operations = {
.d_real = ovl_d_real,
.d_revalidate = ovl_dentry_revalidate,
.d_weak_revalidate = ovl_dentry_weak_revalidate,
.d_hash = generic_ci_d_hash,
.d_compare = generic_ci_d_compare,
};
#endif
static struct kmem_cache *ovl_inode_cachep;
static struct inode *ovl_alloc_inode(struct super_block *sb)
{
struct ovl_inode *oi = alloc_inode_sb(sb, ovl_inode_cachep, GFP_KERNEL);
if (!oi)
return
|