// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005-2020 Junjiro R. Okajima
*
* This program, aufs is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* sub-routines for VFS
*/
#include
#include
#include
#include
#include
#include "aufs.h"
#ifdef CONFIG_AUFS_BR_FUSE
int vfsub_test_mntns(struct vfsmount *mnt, struct super_block *h_sb)
{
if (!au_test_fuse(h_sb) || !au_userns)
return 0;
return is_current_mnt_ns(mnt) ? 0 : -EACCES;
}
#endif
int vfsub_sync_filesystem(struct super_block *h_sb, int wait)
{
int err;
lockdep_off();
down_read(&h_sb->s_umount);
err = __sync_filesystem(h_sb, wait);
up_read(&h_sb->s_umount);
lockdep_on();
return err;
}
/* ---------------------------------------------------------------------- */
int vfsub_update_h_iattr(struct path *h_path, int *did)
{
int err;
struct kstat st;
struct super_block *h_sb;
/* for remote fs, leave work for its getattr or d_revalidate */
/* for bad i_attr fs, handle them in aufs_getattr() */
/* still some fs may acquire i_mutex. we need to skip them */
err = 0;
if (!did)
did = &err;
h_sb = h_path->dentry->d_sb;
*did = (!au_test_fs_remote(h_sb) && au_test_fs_refresh_iattr(h_sb));
if (*did)
err = vfsub_getattr(h_path, &st);
return err;
}
/* ---------------------------------------------------------------------- */
struct file *vfsub_dentry_open(struct path *path, int flags)
{
return dentry_open(path, flags /* | __FMODE_NONOTIFY */,
current_cred());
}
struct file *vfsub_filp_open(const char *path, int oflags, int mode)
{
struct file *file;
lockdep_off();
file = filp_open(path,
oflags /* | __FMODE_NONOTIFY */,
mode);
lockdep_on();
if (IS_ERR(file))
goto out;
vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
out:
return file;
}
/*
* Ideally this function should call VFS:do_last() in order to keep all its
* checkings. But it is very hard for aufs to regenerate several VFS internal
* structure such as nameidata. This is a second (or third) best approach.
* cf. linux/fs/namei.c:do_last(), lookup_open() and atomic_open().
*/
int vfsub_atomic_open(struct inode *dir, struct dentry *dentry,
struct vfsub_aopen_args *args)
{
int err;
struct au_branch *br = args->br;
struct file *file = args->file;
/* copied from linux/fs/namei.c:atomic_open() */
struct dentry *const DENTRY_NOT_SET = (void *)-1UL;
IMustLock(dir);
AuDebugOn(!dir->i_op->atomic_open);
err = au_br_test_oflag(args->open_flag, br);
if (unlikely(err))
goto out;
au_lcnt_inc(&br->br_nfiles);
file->f_path.dentry = DENTRY_NOT_SET;
file->f_path.mnt = au_br_mnt(br);
AuDbg("%ps\n", dir->i_op->atomic_open);
err = dir->i_op->atomic_open(dir, dentry, file, args->open_flag,
args->create_mode);
if (unlikely(err < 0)) {
au_lcnt_dec(&br->br_nfiles);
goto out;
}
/* temporary workaround for nfsv4 branch */
if (au_test_nfs(dir->i_sb))
nfs_mark_for_revalidate(dir);
if (file->f_mode & FMODE_CREATED)
fsnotify_create(dir, dentry);
if (!(file->f_mode & FMODE_OPENED)) {
au_lcnt_dec(&br->br_nfiles);
goto out;
}
/* todo: call VFS:may_open() here */
/* todo: ima_file_check() too? */
if (!err && (args->open_flag & __FMODE_EXEC))
err = deny_write_access(file);
if (!err)
fsnotify_open(file);
else
au_lcnt_dec(&br->br_nfiles);
/* note that the file is created and still opened */
out:
return err;
}
int vfsub_kern_path(const char *name, unsigned int flags, struct path *path)
{
int err;
err = kern_path(name, flags, path);
if (!err && d_is_positive(path->dentry))
vfsub_update_h_iattr(path, /*did*/NULL); /*ignore*/
return err;
}
struct dentry *vfsub_lookup_one_len_unlocked(const char *name,
struct dentry *parent, int len)
{
struct path path = {
.mnt = NULL
};
path.dentry = lookup_one_len_unlocked(name, parent, len);
if (IS_ERR(path.dentry))
goto out;
if (d_is_positive(path.dentry))
vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/
out:
AuTraceErrPtr(path.dentry);
return path.dentry;
}
struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent,
int len)
{
struct path path = {
.mnt = NULL
};
/* VFS checks it too, but by WARN_ON_ONCE() */
IMustLock(d_inode(parent));
path.dentry = lookup_one_len(name, parent, len);
if (IS_ERR(path.dentry))
goto out;
if (d_is_positive(path.dentry))
vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/
out:
AuTraceErrPtr(path.dentry);
return path.dentry;
}
void vfsub_call_lkup_one(void *args)
{
struct vfsub_lkup_one_args *a = args;
*a->errp = vfsub_lkup_one(a->name, a->parent);
}
/* ---------------------------------------------------------------------- */
struct dentry *vfsub_lock_rename(struct dentry *d1, struct au_hinode *hdir1,
struct dentry *d2, struct au_hinode *hdir2)
{
struct dentry *d;
lockdep_off();
d = lock_rename(d1, d2);
lockdep_on();
au_hn_suspend(hdir1);
if (hdir1 != hdir2)
au_hn_suspend(hdir2);
return d;
}
void vfsub_unlock_rename(struct dentry *d1, struct au_hinode *hdir1,
struct dentry *d2, struct au_hinode *hdir2)
{
au_hn_resume(hdir1);
if (hdir1 != hdir2)
au_hn_resume(hdir2);
lockdep_off();
unlock_rename(d1, d2);
lockdep_on();
}
/* ---------------------------------------------------------------------- */
int vfsub_create(struct inode *dir, struct path *path, int mode, bool want_excl)
{
int err;
struct dentry *d;
IMustLock(dir);
d = path->dentry;
path->dentry = d->d_parent;
err = security_path_mknod(path, d, mode, 0);
path->dentry = d;
if (unlikely(err))
goto out;
lockdep_off();
err = vfs_create(dir, path->dentry, mode, want_excl);
lockdep_on();
if (!err) {
struct path tmp = *path;
int did;
vfsub_update_h_iattr(&tmp, &did);
if (did) {
tmp.dentry = path->dentry->d_parent;
vfsub_update_h_iattr(&tmp, /*did*/NULL);
}
/*ignore*/
}
out:
return err;
}
int vfsub_symlink(struct inode *dir, struct path *path, const char *symname)
{
int err;
struct dentry *d;
IMustLock(dir);
d = path->dentry;
path->dentry = d->d_parent;
err = security_path_symlink(path, d, symname);
path->dentry = d;
if (unlikely(err))
goto out;
lockdep_off();
err = vfs_symlink(dir, path->dentry, symname);
lockdep_on();
if (!err) {
struct path tmp = *path;
int did;
vfsub_update_h_iattr(&tmp, &did);
if (did) {
tmp.dentry = path->dentry->d_parent;
vfsub_update_h_iattr(&tmp, /*did*/NULL);
}
/*ignore*/
}
out:
return err;
}
int vfsub_mknod(struct inode *dir, struct path *path, int mode, dev_t dev)
{
int err;
struct dentry *d;
IMustLock(dir);
d = path->dentry;
path->dentry = d->d_parent;
err = security_path_mknod(path, d, mode, new_encode_dev(dev));
path->dentry = d;
if (unlikely(err))
goto out;
lockdep_off();
err = vfs_mknod(dir, path->dentry, mode, dev);
lockdep_on();
if (!err) {
struct path tmp = *path;
int did;
vfsub_update_h_iattr(&tmp, &did);
if (did) {
tmp.dentry = path->dentry->d_parent;
vfsub_update_h_iattr(&tmp, /*did*/NULL);
}
/*ignore*/
}
out:
return err;
}
static int au_test_nlink(struct inode *inode)
{
const unsigned int link_max = UINT_MAX >> 1; /* rough margin */
if (!au_test_fs_no_limit_nlink(inode->i_sb)
|| inode->i_nlink < link_max)
return 0;
return -EMLINK;
}
int vfsub_link(struct dentry *src_dentry, struct inode *dir, struct path *path,
struct inode **delegated_inode)
{
int err;
struct dentry *d;
IMustLock(dir);
err = au_test_nlink(d_inode(src_dentry));
if (unlikely(err))
return err;
/* we don't call may_linkat() */
d = path->dentry;
path->dentry = d->d_parent;
err = security_path_link(src_dentry, path, d);
path->dentry = d;
if (unlikely(err))
goto out;
lockdep_off();
err = vfs_link(src_dentry, dir, path->dentry, delegated_inode);
lockdep_on();
if (!err) {
struct path tmp = *path;
int did;
/* fuse has different memory inode for the same inumber */
vfsub_update_h_iattr(&tmp, &did);
if (did) {
tmp.dentry = path->dentry->d_parent;
vfsub_update_h_iattr(&tmp, /*did*/NULL);
tmp.dentry = src_dentry;
vfsub_update_h_iattr(&tmp, /*did*/NULL);
}
/*ignore*/
}
out:
return err;
}
int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
struct inode *dir, struct path *path,
struct inode **delegated_inode, unsigned int flags)
{
int err;
struct path tmp = {
.mnt = path->mnt
};
struct dentry *d;
IMustLock(dir);
IMustLock(src_dir);
d = path->dentry;
path->dentry = d->d_parent;
tmp.dentry = src_dentry->d_parent;
err = security_path_rename(&tmp, src_dentry, path, d, /*flags*/0);
path->dentry = d;
if (unlikely(err))
goto out;
lockdep_off();
err = vfs_rename(src_dir, src_dentry, dir, path->dentry,
delegated_inode, flags);
lockdep_on();
if (!err) {
int did;
tmp.dentry = d->d_parent;
vfsub_update_h_iattr(&tmp, &did);
if (did) {
tmp.dentry = src_dentry;
vfsub_update_h_iattr(&tmp, /*did*/NULL);
tmp.dentry = src_dentry->d_parent;
vfsub_update_h_iattr(&tmp, /*did*/NULL);
}
/*ignore*/
}
out:
return err;
}
int vfsub_mkdir(struct inode *dir, struct path *path, int mode)
{
int err;
struct dentry *d;
IMustLock(dir);
d = path->dentry;
path->dentry = d->d_parent;
err = security_path_mkdir(path, d, mode);
path->dentry = d;
if (unlikely(err))
goto out;
lockdep_off();
err = vfs_mkdir(dir, path->dentry, mode);
lockdep_on();
if (!err) {
struct path tmp = *path;
int did;
vfsub_update_h_iattr(&tmp, &did);
if (did) {
tmp.dentry = path->dentry->d_parent;
vfsub_update_h_iattr(&tmp, /*did*/NULL);
}
/*ignore*/
}
out:
return err;
}
int vfsub_rmdir(struct inode *dir, struct path *path)
{
int err;
struct dentry *d;
IMustLock(dir);
d = path->dentry;
path->dentry = d->d_parent;
err = security_path_rmdir(path, d);
path->dentry = d;
if (unlikely(err))
goto out;
lockdep_off();
err = vfs_rmdir(dir, path->dentry);
lockdep_on();
if (!err) {
struct path tmp = {
.dentry = path->dentry->d_parent,
.mnt = path->mnt
};
vfsub_update_h_iattr(&tmp, /*did*/NULL); /*ignore*/
}
out:
return err;
}
/* ---------------------------------------------------------------------- */
/* todo: support mmap_sem? */
ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count,
loff_t *ppos)
{
ssize_t err;
lockdep_off();
err = vfs_read(file, ubuf, count, ppos);
lockdep_on();
if (err >= 0)
vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
return err;
}
ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count,
loff_t *ppos)
{
ssize_t err;
lockdep_off();
err = kernel_read(file, kbuf, count, ppos);
lockdep_on();
AuTraceErr(err);
if (err >= 0)
vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
return err;
}
ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count,
loff_t *ppos)
{
ssize_t err;
lockdep_off();
err = vfs_write(file, ubuf, count, ppos);
lockdep_on();
if (err >= 0)
vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
return err;
}
ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, loff_t *ppos)
{
ssize_t err;
lockdep_off();
err = kernel_write(file, kbuf, count, ppos);
lockdep_on();
if (err >= 0)
vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
return err;
}
int vfsub_flush(struct file *file, fl_owner_t id)
{
int err;
err = 0;
if (file->f_op->flush) {
if (!au_test_nfs(file->f_path.dentry->d_sb))
err = file->f_op->flush(file, id);
else {
lockdep_off();
err = file->f_op->flush(file, id);
lockdep_on();
}
if (!err)
vfsub_update_h_iattr(&file->f_path, /*did*/NULL);
/*ignore*/
}
return err;
}
int vfsub_iterate_dir(struct file *file, struct dir_context *ctx)
{
int err;
AuDbg("%pD, ctx{%ps, %llu}\n", file, ctx->actor, ctx->pos);
lockdep_off();
err = iterate_dir(file, ctx);
lockdep_on();
if (err >= 0)
vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
return err;
}
long vfsub_splice_to(struct file *in, loff_t *ppos,
struct pipe_inode_info *pipe, size_t len,
unsigned int flags)
{
long err;
lockdep_off();
err = do_splice_to(in, ppos, pipe, len, flags);
lockdep_on();
file_accessed(in);
if (err >= 0)
vfsub_update_h_iattr(&in->f_path, /*did*/NULL); /*ignore*/
return err;
}
long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out,
loff_t *ppos, size_t len, unsigned int flags)
{
long err;
lockdep_off();
err = do_splice_from(pipe, out, ppos, len, flags);
lockdep_on();
if (err >= 0)
vfsub_update_h_iattr(&out->f_path, /*did*/NULL); /*ignore*/
return err;
}
int vfsub_fsync(struct file *file, struct path *path, int datasync)
{
int err;
/* file can be NULL */
lockdep_off();
err = vfs_fsync(file, datasync);
lockdep_on();
if (!err) {
if (!path) {
AuDebugOn(!file);
path = &file->f_path;
}
vfsub_update_h_iattr(path, /*did*/NULL); /*ignore*/
}
return err;
}
/* cf. open.c:do_sys_truncate() and do_sys_ftruncate() */
int vfsub_trunc(struct path *h_path, loff_t length, unsigned int attr,
struct file *h_file)
{
int err;
struct inode *h_inode;
struct super_block *h_sb;
if (!h_file) {
err = vfsub_truncate(h_path, length);
goto out;
}
h_inode = d_inode(h_path->dentry);
h_sb = h_inode->i_sb;
lockdep_off();
sb_start_write(h_sb);
lockdep_on();
err = locks_verify_truncate(h_inode, h_file, length);
if (!err)
err = security_path_truncate(h_path);
if (!err) {
lockdep_off();
err = do_truncate(h_path->dentry, length, attr, h_file);
lockdep_on();
}
lockdep_off();
sb_end_write(h_sb);
lockdep_on();
out:
return err;
}
/* ---------------------------------------------------------------------- */
struct au_vfsub_mkdir_args {
int *errp;
struct inode *dir;
struct path *path;
int mode;
};
static void au_call_vfsub_mkdir(void *args)
{
struct au_vfsub_mkdir_args *a = args;
*a->errp = vfsub_mkdir(a->dir, a->path, a->mode);
}
int vfsub_sio_mkdir(struct inode *dir, struct path *path, int mode)
{
int err, do_sio, wkq_err;
do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE);
if (!do_sio) {
lockdep_off();
err = vfsub_mkdir(dir, path, mode);
lockdep_on();
} else {
struct au_vfsub_mkdir_args args = {
.errp = &err,
.dir = dir,
.path = path,
.mode = mode
};
wkq_err = au_wkq_wait(au_call_vfsub_mkdir, &args);
if (unlikely(wkq_err))
err = wkq_err;
}
return err;
}
struct au_vfsub_rmdir_args {
int *errp;
struct inode *dir;
struct path *path;
};
static void au_call_vfsub_rmdir(void *args)
{
struct au_vfsub_rmdir_args *a = args;
*a->errp = vfsub_rmdir(a->dir, a->path);
}
int vfsub_sio_rmdir(struct inode *dir, struct path *path)
{
int err, do_sio, wkq_err;
do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE);
if (!do_sio) {
lockdep_off();
err = vfsub_rmdir(dir, path);
lockdep_on();
} else {
struct au_vfsub_rmdir_args args = {
.errp = &err,
.dir = dir,
.path = path
};
wkq_err = au_wkq_wait(au_call_vfsub_rmdir, &args);
if (unlikely(wkq_err))
err = wkq_err;
}
return err;
}
/* ---------------------------------------------------------------------- */
struct notify_change_args {
int *errp;
struct path *path;
struct iattr *ia;
struct inode **delegated_inode;
};
static void call_notify_change(void *args)
{
struct notify_change_args *a = args;
struct inode *h_inode;
h_inode = d_inode(a->path->dentry);
IMustLock(h_inode);
*a->errp = -EPERM;
if (!IS_IMMUTABLE(h_inode) && !IS_APPEND(h_inode)) {
lockdep_off();
*a->errp = notify_change(a->path->dentry, a->ia,
a->delegated_inode);
lockdep_on();
if (!*a->errp)
vfsub_update_h_iattr(a->path, /*did*/NULL); /*ignore*/
}
AuTraceErr(*a->errp);
}
int vfsub_notify_change(struct path *path, struct iattr *ia,
struct inode **delegated_inode)
{
int err;
struct notify_change_args args = {
.errp = &err,
.path = path,
.ia = ia,
.delegated_inode = delegated_inode
};
call_notify_change(&args);
return err;
}
int vfsub_sio_notify_change(struct path *path, struct iattr *ia,
struct inode **delegated_inode)
{
int err, wkq_err;
struct notify_change_args args = {
.errp = &err,
.path = path,
.ia = ia,
.delegated_inode = delegated_inode
};
wkq_err = au_wkq_wait(call_notify_change, &args);
if (unlikely(wkq_err))
err = wkq_err;
return err;
}
/* ---------------------------------------------------------------------- */
struct unlink_args {
int *errp;
struct inode *dir;
struct path *path;
struct inode **delegated_inode;
};
static void call_unlink(void *args)
{
struct unlink_args *a = args;
struct dentry *d = a->path->dentry;
struct inode *h_inode;
const int stop_sillyrename = (au_test_nfs(d->d_sb)
&& au_dcount(d) == 1);
IMustLock(a->dir);
a->path->dentry = d->d_parent;
*a->errp = security_path_unlink(a->path, d);
a->path->dentry = d;
if (unlikely(*a->errp))
return;
if (!stop_sillyrename)
dget(d);
h_inode = NULL;
if (d_is_positive(d)) {
h_inode = d_inode(d);
ihold(h_inode);
}
lockdep_off();
*a->errp = vfs_unlink(a->dir, d, a->delegated_inode);
lockdep_on();
if (!*a->errp) {
struct path tmp = {
.dentry = d->d_parent,
.mnt = a->path->mnt
};
vfsub_update_h_iattr(&tmp, /*did*/NULL); /*ignore*/
}
if (!stop_sillyrename)
dput(d);
if (h_inode)
iput(h_inode);
AuTraceErr(*a->errp);
}
/*
* @dir: must be locked.
* @dentry: target dentry.
*/
int vfsub_unlink(struct inode *dir, struct path *path,
struct inode **delegated_inode, int force)
{
int err;
struct unlink_args args = {
.errp = &err,
.dir = dir,
.path = path,
.delegated_inode = delegated_inode
};
if (!force)
call_unlink(&args);
else {
int wkq_err;
wkq_err = au_wkq_wait(call_unlink, &args);
if (unlikely(wkq_err))
err = wkq_err;
}
return err;
}