mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1062 lines
24 KiB
1062 lines
24 KiB
// 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 <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); |
|
} |
|
}
|
|
|