linux_dsm_epyc7002/fs/aufs/whout.c
AuxXxilium 5fa3ea047a init: add dsm gpl source
Signed-off-by: AuxXxilium <info@auxxxilium.tech>
2024-07-05 18:00:04 +02:00

1063 lines
24 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005-2019 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 <http://www.gnu.org/licenses/>.
*/
/*
* whiteout for logical deletion and opaque directory
*/
#include "aufs.h"
#define WH_MASK 0444
/*
* If a directory contains this file, then it is opaque. We start with the
* .wh. flag so that it is blocked by lookup.
*/
static struct qstr diropq_name = QSTR_INIT(AUFS_WH_DIROPQ,
sizeof(AUFS_WH_DIROPQ) - 1);
/*
* generate whiteout name, which is NOT terminated by NULL.
* @name: original d_name.name
* @len: original d_name.len
* @wh: whiteout qstr
* returns zero when succeeds, otherwise error.
* succeeded value as wh->name should be freed by kfree().
*/
int au_wh_name_alloc(struct qstr *wh, const struct qstr *name)
{
char *p;
if (unlikely(name->len > PATH_MAX - AUFS_WH_PFX_LEN))
return -ENAMETOOLONG;
wh->len = name->len + AUFS_WH_PFX_LEN;
p = kmalloc(wh->len, GFP_NOFS);
wh->name = p;
if (p) {
memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
memcpy(p + AUFS_WH_PFX_LEN, name->name, name->len);
/* smp_mb(); */
return 0;
}
return -ENOMEM;
}
/* ---------------------------------------------------------------------- */
/*
* test if the @wh_name exists under @h_parent.
* @try_sio specifies the necessary of super-io.
*/
int au_wh_test(struct dentry *h_parent, struct qstr *wh_name, int try_sio)
{
int err;
struct dentry *wh_dentry;
if (!try_sio)
wh_dentry = vfsub_lkup_one(wh_name, h_parent);
else
wh_dentry = au_sio_lkup_one(wh_name, h_parent);
err = PTR_ERR(wh_dentry);
if (IS_ERR(wh_dentry)) {
if (err == -ENAMETOOLONG)
err = 0;
goto out;
}
err = 0;
if (d_is_negative(wh_dentry))
goto out_wh; /* success */
err = 1;
if (d_is_reg(wh_dentry))
goto out_wh; /* success */
err = -EIO;
AuIOErr("%pd Invalid whiteout entry type 0%o.\n",
wh_dentry, d_inode(wh_dentry)->i_mode);
out_wh:
dput(wh_dentry);
out:
return err;
}
/*
* test if the @h_dentry sets opaque or not.
*/
int au_diropq_test(struct dentry *h_dentry)
{
int err;
struct inode *h_dir;
h_dir = d_inode(h_dentry);
err = au_wh_test(h_dentry, &diropq_name,
au_test_h_perm_sio(h_dir, MAY_EXEC));
return err;
}
/*
* returns a negative dentry whose name is unique and temporary.
*/
struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct au_branch *br,
struct qstr *prefix)
{
struct dentry *dentry;
int i;
char defname[NAME_MAX - AUFS_MAX_NAMELEN + DNAME_INLINE_LEN + 1],
*name, *p;
/* strict atomic_t is unnecessary here */
static unsigned short cnt;
struct qstr qs;
BUILD_BUG_ON(sizeof(cnt) * 2 > AUFS_WH_TMP_LEN);
name = defname;
qs.len = sizeof(defname) - DNAME_INLINE_LEN + prefix->len - 1;
if (unlikely(prefix->len > DNAME_INLINE_LEN)) {
dentry = ERR_PTR(-ENAMETOOLONG);
if (unlikely(qs.len > NAME_MAX))
goto out;
dentry = ERR_PTR(-ENOMEM);
name = kmalloc(qs.len + 1, GFP_NOFS);
if (unlikely(!name))
goto out;
}
/* doubly whiteout-ed */
memcpy(name, AUFS_WH_PFX AUFS_WH_PFX, AUFS_WH_PFX_LEN * 2);
p = name + AUFS_WH_PFX_LEN * 2;
memcpy(p, prefix->name, prefix->len);
p += prefix->len;
*p++ = '.';
AuDebugOn(name + qs.len + 1 - p <= AUFS_WH_TMP_LEN);
qs.name = name;
for (i = 0; i < 3; i++) {
sprintf(p, "%.*x", AUFS_WH_TMP_LEN, cnt++);
dentry = au_sio_lkup_one(&qs, h_parent);
if (IS_ERR(dentry) || d_is_negative(dentry))
goto out_name;
dput(dentry);
}
/* pr_warn("could not get random name\n"); */
dentry = ERR_PTR(-EEXIST);
AuDbg("%.*s\n", AuLNPair(&qs));
BUG();
out_name:
if (name != defname)
au_kfree_try_rcu(name);
out:
AuTraceErrPtr(dentry);
return dentry;
}
/*
* rename the @h_dentry on @br to the whiteouted temporary name.
*/
int au_whtmp_ren(struct dentry *h_dentry, struct au_branch *br)
{
int err;
struct path h_path = {
.mnt = au_br_mnt(br)
};
struct inode *h_dir, *delegated;
struct dentry *h_parent;
h_parent = h_dentry->d_parent; /* dir inode is locked */
h_dir = d_inode(h_parent);
IMustLock(h_dir);
h_path.dentry = au_whtmp_lkup(h_parent, br, &h_dentry->d_name);
err = PTR_ERR(h_path.dentry);
if (IS_ERR(h_path.dentry))
goto out;
/* under the same dir, no need to lock_rename() */
delegated = NULL;
err = vfsub_rename(h_dir, h_dentry, h_dir, &h_path, &delegated,
/*flags*/0);
AuTraceErr(err);
if (unlikely(err == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal rename\n");
iput(delegated);
}
dput(h_path.dentry);
out:
AuTraceErr(err);
return err;
}
/* ---------------------------------------------------------------------- */
/*
* functions for removing a whiteout
*/
static int do_unlink_wh(struct inode *h_dir, struct path *h_path)
{
int err, force;
struct inode *delegated;
/*
* forces superio when the dir has a sticky bit.
* this may be a violation of unix fs semantics.
*/
force = (h_dir->i_mode & S_ISVTX)
&& !uid_eq(current_fsuid(), d_inode(h_path->dentry)->i_uid);
delegated = NULL;
err = vfsub_unlink(h_dir, h_path, &delegated, force);
if (unlikely(err == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal unlink\n");
iput(delegated);
}
return err;
}
int au_wh_unlink_dentry(struct inode *h_dir, struct path *h_path,
struct dentry *dentry)
{
int err;
err = do_unlink_wh(h_dir, h_path);
if (!err && dentry)
au_set_dbwh(dentry, -1);
return err;
}
static int unlink_wh_name(struct dentry *h_parent, struct qstr *wh,
struct au_branch *br)
{
int err;
struct path h_path = {
.mnt = au_br_mnt(br)
};
err = 0;
h_path.dentry = vfsub_lkup_one(wh, h_parent);
if (IS_ERR(h_path.dentry))
err = PTR_ERR(h_path.dentry);
else {
if (d_is_reg(h_path.dentry))
err = do_unlink_wh(d_inode(h_parent), &h_path);
dput(h_path.dentry);
}
return err;
}
/* ---------------------------------------------------------------------- */
/*
* initialize/clean whiteout for a branch
*/
static void au_wh_clean(struct inode *h_dir, struct path *whpath,
const int isdir)
{
int err;
struct inode *delegated;
if (d_is_negative(whpath->dentry))
return;
if (isdir)
err = vfsub_rmdir(h_dir, whpath);
else {
delegated = NULL;
err = vfsub_unlink(h_dir, whpath, &delegated, /*force*/0);
if (unlikely(err == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal unlink\n");
iput(delegated);
}
}
if (unlikely(err))
pr_warn("failed removing %pd (%d), ignored.\n",
whpath->dentry, err);
}
static int test_linkable(struct dentry *h_root)
{
struct inode *h_dir = d_inode(h_root);
if (h_dir->i_op->link)
return 0;
pr_err("%pd (%s) doesn't support link(2), use noplink and rw+nolwh\n",
h_root, au_sbtype(h_root->d_sb));
return -ENOSYS; /* the branch doesn't have its ->link() */
}
/* todo: should this mkdir be done in /sbin/mount.aufs helper? */
static int au_whdir(struct inode *h_dir, struct path *path)
{
int err;
err = -EEXIST;
if (d_is_negative(path->dentry)) {
int mode = 0700;
if (au_test_nfs(path->dentry->d_sb))
mode |= 0111;
err = vfsub_mkdir(h_dir, path, mode);
} else if (d_is_dir(path->dentry))
err = 0;
else
pr_err("unknown %pd exists\n", path->dentry);
return err;
}
struct au_wh_base {
const struct qstr *name;
struct dentry *dentry;
};
static void au_wh_init_ro(struct inode *h_dir, struct au_wh_base base[],
struct path *h_path)
{
h_path->dentry = base[AuBrWh_BASE].dentry;
au_wh_clean(h_dir, h_path, /*isdir*/0);
h_path->dentry = base[AuBrWh_PLINK].dentry;
au_wh_clean(h_dir, h_path, /*isdir*/1);
h_path->dentry = base[AuBrWh_ORPH].dentry;
au_wh_clean(h_dir, h_path, /*isdir*/1);
}
/*
* returns tri-state,
* minus: error, caller should print the message
* zero: success
* plus: error, caller should NOT print the message
*/
static int au_wh_init_rw_nolink(struct dentry *h_root, struct au_wbr *wbr,
int do_plink, struct au_wh_base base[],
struct path *h_path)
{
int err;
struct inode *h_dir;
h_dir = d_inode(h_root);
h_path->dentry = base[AuBrWh_BASE].dentry;
au_wh_clean(h_dir, h_path, /*isdir*/0);
h_path->dentry = base[AuBrWh_PLINK].dentry;
if (do_plink) {
err = test_linkable(h_root);
if (unlikely(err)) {
err = 1;
goto out;
}
err = au_whdir(h_dir, h_path);
if (unlikely(err))
goto out;
wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry);
} else
au_wh_clean(h_dir, h_path, /*isdir*/1);
h_path->dentry = base[AuBrWh_ORPH].dentry;
err = au_whdir(h_dir, h_path);
if (unlikely(err))
goto out;
wbr->wbr_orph = dget(base[AuBrWh_ORPH].dentry);
out:
return err;
}
/*
* for the moment, aufs supports the branch filesystem which does not support
* link(2). testing on FAT which does not support i_op->setattr() fully either,
* copyup failed. finally, such filesystem will not be used as the writable
* branch.
*
* returns tri-state, see above.
*/
static int au_wh_init_rw(struct dentry *h_root, struct au_wbr *wbr,
int do_plink, struct au_wh_base base[],
struct path *h_path)
{
int err;
struct inode *h_dir;
WbrWhMustWriteLock(wbr);
err = test_linkable(h_root);
if (unlikely(err)) {
err = 1;
goto out;
}
/*
* todo: should this create be done in /sbin/mount.aufs helper?
*/
err = -EEXIST;
h_dir = d_inode(h_root);
if (d_is_negative(base[AuBrWh_BASE].dentry)) {
h_path->dentry = base[AuBrWh_BASE].dentry;
err = vfsub_create(h_dir, h_path, WH_MASK, /*want_excl*/true);
} else if (d_is_reg(base[AuBrWh_BASE].dentry))
err = 0;
else
pr_err("unknown %pd2 exists\n", base[AuBrWh_BASE].dentry);
if (unlikely(err))
goto out;
h_path->dentry = base[AuBrWh_PLINK].dentry;
if (do_plink) {
err = au_whdir(h_dir, h_path);
if (unlikely(err))
goto out;
wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry);
} else
au_wh_clean(h_dir, h_path, /*isdir*/1);
wbr->wbr_whbase = dget(base[AuBrWh_BASE].dentry);
h_path->dentry = base[AuBrWh_ORPH].dentry;
err = au_whdir(h_dir, h_path);
if (unlikely(err))
goto out;
wbr->wbr_orph = dget(base[AuBrWh_ORPH].dentry);
out:
return err;
}
/*
* initialize the whiteout base file/dir for @br.
*/
int au_wh_init(struct au_branch *br, struct super_block *sb)
{
int err, i;
const unsigned char do_plink
= !!au_opt_test(au_mntflags(sb), PLINK);
struct inode *h_dir;
struct path path = br->br_path;
struct dentry *h_root = path.dentry;
struct au_wbr *wbr = br->br_wbr;
static const struct qstr base_name[] = {
[AuBrWh_BASE] = QSTR_INIT(AUFS_BASE_NAME,
sizeof(AUFS_BASE_NAME) - 1),
[AuBrWh_PLINK] = QSTR_INIT(AUFS_PLINKDIR_NAME,
sizeof(AUFS_PLINKDIR_NAME) - 1),
[AuBrWh_ORPH] = QSTR_INIT(AUFS_ORPHDIR_NAME,
sizeof(AUFS_ORPHDIR_NAME) - 1)
};
struct au_wh_base base[] = {
[AuBrWh_BASE] = {
.name = base_name + AuBrWh_BASE,
.dentry = NULL
},
[AuBrWh_PLINK] = {
.name = base_name + AuBrWh_PLINK,
.dentry = NULL
},
[AuBrWh_ORPH] = {
.name = base_name + AuBrWh_ORPH,
.dentry = NULL
}
};
if (wbr)
WbrWhMustWriteLock(wbr);
for (i = 0; i < AuBrWh_Last; i++) {
/* doubly whiteouted */
struct dentry *d;
d = au_wh_lkup(h_root, (void *)base[i].name, br);
err = PTR_ERR(d);
if (IS_ERR(d))
goto out;
base[i].dentry = d;
AuDebugOn(wbr
&& wbr->wbr_wh[i]
&& wbr->wbr_wh[i] != base[i].dentry);
}
if (wbr)
for (i = 0; i < AuBrWh_Last; i++) {
dput(wbr->wbr_wh[i]);
wbr->wbr_wh[i] = NULL;
}
err = 0;
if (!au_br_writable(br->br_perm)) {
h_dir = d_inode(h_root);
au_wh_init_ro(h_dir, base, &path);
} else if (!au_br_wh_linkable(br->br_perm)) {
err = au_wh_init_rw_nolink(h_root, wbr, do_plink, base, &path);
if (err > 0)
goto out;
else if (err)
goto out_err;
} else {
err = au_wh_init_rw(h_root, wbr, do_plink, base, &path);
if (err > 0)
goto out;
else if (err)
goto out_err;
}
goto out; /* success */
out_err:
pr_err("an error(%d) on the writable branch %pd(%s)\n",
err, h_root, au_sbtype(h_root->d_sb));
out:
for (i = 0; i < AuBrWh_Last; i++)
dput(base[i].dentry);
return err;
}
/* ---------------------------------------------------------------------- */
/*
* whiteouts are all hard-linked usually.
* when its link count reaches a ceiling, we create a new whiteout base
* asynchronously.
*/
struct reinit_br_wh {
struct super_block *sb;
struct au_branch *br;
};
static void reinit_br_wh(void *arg)
{
int err;
aufs_bindex_t bindex;
struct path h_path;
struct reinit_br_wh *a = arg;
struct au_wbr *wbr;
struct inode *dir, *delegated;
struct dentry *h_root;
struct au_hinode *hdir;
err = 0;
wbr = a->br->br_wbr;
/* big aufs lock */
si_noflush_write_lock(a->sb);
if (!au_br_writable(a->br->br_perm))
goto out;
bindex = au_br_index(a->sb, a->br->br_id);
if (unlikely(bindex < 0))
goto out;
di_read_lock_parent(a->sb->s_root, AuLock_IR);
dir = d_inode(a->sb->s_root);
hdir = au_hi(dir, bindex);
h_root = au_h_dptr(a->sb->s_root, bindex);
AuDebugOn(h_root != au_br_dentry(a->br));
au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT);
wbr_wh_write_lock(wbr);
err = au_h_verify(wbr->wbr_whbase, au_opt_udba(a->sb), hdir->hi_inode,
h_root, a->br);
if (!err) {
h_path.dentry = wbr->wbr_whbase;
h_path.mnt = au_br_mnt(a->br);
delegated = NULL;
err = vfsub_unlink(hdir->hi_inode, &h_path, &delegated,
/*force*/0);
if (unlikely(err == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal unlink\n");
iput(delegated);
}
} else {
pr_warn("%pd is moved, ignored\n", wbr->wbr_whbase);
err = 0;
}
dput(wbr->wbr_whbase);
wbr->wbr_whbase = NULL;
if (!err)
err = au_wh_init(a->br, a->sb);
wbr_wh_write_unlock(wbr);
au_hn_inode_unlock(hdir);
di_read_unlock(a->sb->s_root, AuLock_IR);
if (!err)
au_fhsm_wrote(a->sb, bindex, /*force*/0);
out:
if (wbr)
atomic_dec(&wbr->wbr_wh_running);
au_lcnt_dec(&a->br->br_count);
si_write_unlock(a->sb);
au_nwt_done(&au_sbi(a->sb)->si_nowait);
au_kfree_rcu(a);
if (unlikely(err))
AuIOErr("err %d\n", err);
}
static void kick_reinit_br_wh(struct super_block *sb, struct au_branch *br)
{
int do_dec, wkq_err;
struct reinit_br_wh *arg;
do_dec = 1;
if (atomic_inc_return(&br->br_wbr->wbr_wh_running) != 1)
goto out;
/* ignore ENOMEM */
arg = kmalloc(sizeof(*arg), GFP_NOFS);
if (arg) {
/*
* dec(wh_running), kfree(arg) and dec(br_count)
* in reinit function
*/
arg->sb = sb;
arg->br = br;
au_lcnt_inc(&br->br_count);
wkq_err = au_wkq_nowait(reinit_br_wh, arg, sb, /*flags*/0);
if (unlikely(wkq_err)) {
atomic_dec(&br->br_wbr->wbr_wh_running);
au_lcnt_dec(&br->br_count);
au_kfree_rcu(arg);
}
do_dec = 0;
}
out:
if (do_dec)
atomic_dec(&br->br_wbr->wbr_wh_running);
}
/* ---------------------------------------------------------------------- */
/*
* create the whiteout @wh.
*/
static int link_or_create_wh(struct super_block *sb, aufs_bindex_t bindex,
struct dentry *wh)
{
int err;
struct path h_path = {
.dentry = wh
};
struct au_branch *br;
struct au_wbr *wbr;
struct dentry *h_parent;
struct inode *h_dir, *delegated;
h_parent = wh->d_parent; /* dir inode is locked */
h_dir = d_inode(h_parent);
IMustLock(h_dir);
br = au_sbr(sb, bindex);
h_path.mnt = au_br_mnt(br);
wbr = br->br_wbr;
wbr_wh_read_lock(wbr);
if (wbr->wbr_whbase) {
delegated = NULL;
err = vfsub_link(wbr->wbr_whbase, h_dir, &h_path, &delegated);
if (unlikely(err == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal link\n");
iput(delegated);
}
if (!err || err != -EMLINK)
goto out;
/* link count full. re-initialize br_whbase. */
kick_reinit_br_wh(sb, br);
}
/* return this error in this context */
err = vfsub_create(h_dir, &h_path, WH_MASK, /*want_excl*/true);
if (!err)
au_fhsm_wrote(sb, bindex, /*force*/0);
out:
wbr_wh_read_unlock(wbr);
return err;
}
/* ---------------------------------------------------------------------- */
/*
* create or remove the diropq.
*/
static struct dentry *do_diropq(struct dentry *dentry, aufs_bindex_t bindex,
unsigned int flags)
{
struct dentry *opq_dentry, *h_dentry;
struct super_block *sb;
struct au_branch *br;
int err;
sb = dentry->d_sb;
br = au_sbr(sb, bindex);
h_dentry = au_h_dptr(dentry, bindex);
opq_dentry = vfsub_lkup_one(&diropq_name, h_dentry);
if (IS_ERR(opq_dentry))
goto out;
if (au_ftest_diropq(flags, CREATE)) {
err = link_or_create_wh(sb, bindex, opq_dentry);
if (!err) {
au_set_dbdiropq(dentry, bindex);
goto out; /* success */
}
} else {
struct path tmp = {
.dentry = opq_dentry,
.mnt = au_br_mnt(br)
};
err = do_unlink_wh(au_h_iptr(d_inode(dentry), bindex), &tmp);
if (!err)
au_set_dbdiropq(dentry, -1);
}
dput(opq_dentry);
opq_dentry = ERR_PTR(err);
out:
return opq_dentry;
}
struct do_diropq_args {
struct dentry **errp;
struct dentry *dentry;
aufs_bindex_t bindex;
unsigned int flags;
};
static void call_do_diropq(void *args)
{
struct do_diropq_args *a = args;
*a->errp = do_diropq(a->dentry, a->bindex, a->flags);
}
struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex,
unsigned int flags)
{
struct dentry *diropq, *h_dentry;
h_dentry = au_h_dptr(dentry, bindex);
if (!au_test_h_perm_sio(d_inode(h_dentry), MAY_EXEC | MAY_WRITE))
diropq = do_diropq(dentry, bindex, flags);
else {
int wkq_err;
struct do_diropq_args args = {
.errp = &diropq,
.dentry = dentry,
.bindex = bindex,
.flags = flags
};
wkq_err = au_wkq_wait(call_do_diropq, &args);
if (unlikely(wkq_err))
diropq = ERR_PTR(wkq_err);
}
return diropq;
}
/* ---------------------------------------------------------------------- */
/*
* lookup whiteout dentry.
* @h_parent: lower parent dentry which must exist and be locked
* @base_name: name of dentry which will be whiteouted
* returns dentry for whiteout.
*/
struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name,
struct au_branch *br)
{
int err;
struct qstr wh_name;
struct dentry *wh_dentry;
err = au_wh_name_alloc(&wh_name, base_name);
wh_dentry = ERR_PTR(err);
if (!err) {
wh_dentry = vfsub_lkup_one(&wh_name, h_parent);
au_kfree_try_rcu(wh_name.name);
}
return wh_dentry;
}
/*
* link/create a whiteout for @dentry on @bindex.
*/
struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex,
struct dentry *h_parent)
{
struct dentry *wh_dentry;
struct super_block *sb;
int err;
sb = dentry->d_sb;
wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, au_sbr(sb, bindex));
if (!IS_ERR(wh_dentry) && d_is_negative(wh_dentry)) {
err = link_or_create_wh(sb, bindex, wh_dentry);
if (!err) {
au_set_dbwh(dentry, bindex);
au_fhsm_wrote(sb, bindex, /*force*/0);
} else {
dput(wh_dentry);
wh_dentry = ERR_PTR(err);
}
}
return wh_dentry;
}
/* ---------------------------------------------------------------------- */
/* Delete all whiteouts in this directory on branch bindex. */
static int del_wh_children(struct dentry *h_dentry, struct au_nhash *whlist,
aufs_bindex_t bindex, struct au_branch *br)
{
int err;
unsigned long ul, n;
struct qstr wh_name;
char *p;
struct hlist_head *head;
struct au_vdir_wh *pos;
struct au_vdir_destr *str;
err = -ENOMEM;
p = (void *)__get_free_page(GFP_NOFS);
wh_name.name = p;
if (unlikely(!wh_name.name))
goto out;
err = 0;
memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
p += AUFS_WH_PFX_LEN;
n = whlist->nh_num;
head = whlist->nh_head;
for (ul = 0; !err && ul < n; ul++, head++) {
hlist_for_each_entry(pos, head, wh_hash) {
if (pos->wh_bindex != bindex)
continue;
str = &pos->wh_str;
if (str->len + AUFS_WH_PFX_LEN <= PATH_MAX) {
memcpy(p, str->name, str->len);
wh_name.len = AUFS_WH_PFX_LEN + str->len;
err = unlink_wh_name(h_dentry, &wh_name, br);
if (!err)
continue;
break;
}
AuIOErr("whiteout name too long %.*s\n",
str->len, str->name);
err = -EIO;
break;
}
}
free_page((unsigned long)wh_name.name);
out:
return err;
}
struct del_wh_children_args {
int *errp;
struct dentry *h_dentry;
struct au_nhash *whlist;
aufs_bindex_t bindex;
struct au_branch *br;
};
static void call_del_wh_children(void *args)
{
struct del_wh_children_args *a = args;
*a->errp = del_wh_children(a->h_dentry, a->whlist, a->bindex, a->br);
}
/* ---------------------------------------------------------------------- */
struct au_whtmp_rmdir *au_whtmp_rmdir_alloc(struct super_block *sb, gfp_t gfp)
{
struct au_whtmp_rmdir *whtmp;
int err;
unsigned int rdhash;
SiMustAnyLock(sb);
whtmp = kzalloc(sizeof(*whtmp), gfp);
if (unlikely(!whtmp)) {
whtmp = ERR_PTR(-ENOMEM);
goto out;
}
/* no estimation for dir size */
rdhash = au_sbi(sb)->si_rdhash;
if (!rdhash)
rdhash = AUFS_RDHASH_DEF;
err = au_nhash_alloc(&whtmp->whlist, rdhash, gfp);
if (unlikely(err)) {
au_kfree_rcu(whtmp);
whtmp = ERR_PTR(err);
}
out:
return whtmp;
}
void au_whtmp_rmdir_free(struct au_whtmp_rmdir *whtmp)
{
if (whtmp->br)
au_lcnt_dec(&whtmp->br->br_count);
dput(whtmp->wh_dentry);
iput(whtmp->dir);
au_nhash_wh_free(&whtmp->whlist);
au_kfree_rcu(whtmp);
}
/*
* rmdir the whiteouted temporary named dir @h_dentry.
* @whlist: whiteouted children.
*/
int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex,
struct dentry *wh_dentry, struct au_nhash *whlist)
{
int err;
unsigned int h_nlink;
struct path h_tmp;
struct inode *wh_inode, *h_dir;
struct au_branch *br;
h_dir = d_inode(wh_dentry->d_parent); /* dir inode is locked */
IMustLock(h_dir);
br = au_sbr(dir->i_sb, bindex);
wh_inode = d_inode(wh_dentry);
inode_lock_nested(wh_inode, AuLsc_I_CHILD);
/*
* someone else might change some whiteouts while we were sleeping.
* it means this whlist may have an obsoleted entry.
*/
if (!au_test_h_perm_sio(wh_inode, MAY_EXEC | MAY_WRITE))
err = del_wh_children(wh_dentry, whlist, bindex, br);
else {
int wkq_err;
struct del_wh_children_args args = {
.errp = &err,
.h_dentry = wh_dentry,
.whlist = whlist,
.bindex = bindex,
.br = br
};
wkq_err = au_wkq_wait(call_del_wh_children, &args);
if (unlikely(wkq_err))
err = wkq_err;
}
inode_unlock(wh_inode);
if (!err) {
h_tmp.dentry = wh_dentry;
h_tmp.mnt = au_br_mnt(br);
h_nlink = h_dir->i_nlink;
err = vfsub_rmdir(h_dir, &h_tmp);
/* some fs doesn't change the parent nlink in some cases */
h_nlink -= h_dir->i_nlink;
}
if (!err) {
if (au_ibtop(dir) == bindex) {
/* todo: dir->i_mutex is necessary */
au_cpup_attr_timesizes(dir);
if (h_nlink)
vfsub_drop_nlink(dir);
}
return 0; /* success */
}
pr_warn("failed removing %pd(%d), ignored\n", wh_dentry, err);
return err;
}
static void call_rmdir_whtmp(void *args)
{
int err;
aufs_bindex_t bindex;
struct au_whtmp_rmdir *a = args;
struct super_block *sb;
struct dentry *h_parent;
struct inode *h_dir;
struct au_hinode *hdir;
/* rmdir by nfsd may cause deadlock with this i_mutex */
/* inode_lock(a->dir); */
err = -EROFS;
sb = a->dir->i_sb;
si_read_lock(sb, !AuLock_FLUSH);
if (!au_br_writable(a->br->br_perm))
goto out;
bindex = au_br_index(sb, a->br->br_id);
if (unlikely(bindex < 0))
goto out;
err = -EIO;
ii_write_lock_parent(a->dir);
h_parent = dget_parent(a->wh_dentry);
h_dir = d_inode(h_parent);
hdir = au_hi(a->dir, bindex);
err = vfsub_mnt_want_write(au_br_mnt(a->br));
if (unlikely(err))
goto out_mnt;
au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT);
err = au_h_verify(a->wh_dentry, au_opt_udba(sb), h_dir, h_parent,
a->br);
if (!err)
err = au_whtmp_rmdir(a->dir, bindex, a->wh_dentry, &a->whlist);
au_hn_inode_unlock(hdir);
vfsub_mnt_drop_write(au_br_mnt(a->br));
out_mnt:
dput(h_parent);
ii_write_unlock(a->dir);
out:
/* inode_unlock(a->dir); */
au_whtmp_rmdir_free(a);
si_read_unlock(sb);
au_nwt_done(&au_sbi(sb)->si_nowait);
if (unlikely(err))
AuIOErr("err %d\n", err);
}
void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex,
struct dentry *wh_dentry, struct au_whtmp_rmdir *args)
{
int wkq_err;
struct super_block *sb;
IMustLock(dir);
/* all post-process will be done in do_rmdir_whtmp(). */
sb = dir->i_sb;
args->dir = au_igrab(dir);
args->br = au_sbr(sb, bindex);
au_lcnt_inc(&args->br->br_count);
args->wh_dentry = dget(wh_dentry);
wkq_err = au_wkq_nowait(call_rmdir_whtmp, args, sb, /*flags*/0);
if (unlikely(wkq_err)) {
pr_warn("rmdir error %pd (%d), ignored\n", wh_dentry, wkq_err);
au_whtmp_rmdir_free(args);
}
}