Commit 2902df92 authored by J. R. Okajima's avatar J. R. Okajima
Browse files

aufs: inode op, rename 2/2, body



Implement i_op->rename().
This is a big monster and I don't like it.

In this version, the copy-up always happen.
Signed-off-by: default avatarJ. R. Okajima <hooanon05g@gmail.com>
parent 2a422a58
......@@ -17,7 +17,7 @@ aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \
dynop.o \
finfo.o file.o \
dir.o vdir.o \
iinfo.o inode.o i_op.o i_op_add.o i_op_del.o
iinfo.o inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o
# all are boolean
aufs-$(CONFIG_PROC_FS) += procfs.o plink.o
......
......@@ -652,6 +652,7 @@ struct inode_operations aufs_iop[] = {
.mkdir = aufs_mkdir,
.rmdir = aufs_rmdir,
.mknod = aufs_mknod,
.rename = aufs_rename,
.permission = aufs_permission,
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005-2019 Junjiro R. Okajima
*/
/*
* inode operation (rename entry)
* todo: this is crazy monster
*/
#include <linux/iversion.h>
#include "aufs.h"
enum { AuSRC, AuDST, AuSrcDst };
enum { AuPARENT, AuCHILD, AuParentChild };
#define AuRen_ISDIR_SRC 1
#define AuRen_ISDIR_DST (1 << 1)
#define AuRen_ISSAMEDIR (1 << 2)
#define AuRen_WHSRC (1 << 3)
#define AuRen_WHDST (1 << 4)
#define AuRen_MNT_WRITE (1 << 5)
#define AuRen_DT_DSTDIR (1 << 6)
#define AuRen_DIROPQ_SRC (1 << 7)
#define AuRen_DIROPQ_DST (1 << 8)
#define au_ftest_ren(flags, name) ((flags) & AuRen_##name)
#define au_fset_ren(flags, name) \
do { (flags) |= AuRen_##name; } while (0)
#define au_fclr_ren(flags, name) \
do { (flags) &= ~AuRen_##name; } while (0)
struct au_ren_args {
struct {
struct dentry *dentry, *h_dentry, *parent, *h_parent,
*wh_dentry;
struct inode *dir, *inode;
struct au_hinode *hdir, *hinode;
struct au_dtime dt[AuParentChild];
aufs_bindex_t btop, bdiropq;
} sd[AuSrcDst];
#define src_dentry sd[AuSRC].dentry
#define src_dir sd[AuSRC].dir
#define src_inode sd[AuSRC].inode
#define src_h_dentry sd[AuSRC].h_dentry
#define src_parent sd[AuSRC].parent
#define src_h_parent sd[AuSRC].h_parent
#define src_wh_dentry sd[AuSRC].wh_dentry
#define src_hdir sd[AuSRC].hdir
#define src_hinode sd[AuSRC].hinode
#define src_h_dir sd[AuSRC].hdir->hi_inode
#define src_dt sd[AuSRC].dt
#define src_btop sd[AuSRC].btop
#define src_bdiropq sd[AuSRC].bdiropq
#define dst_dentry sd[AuDST].dentry
#define dst_dir sd[AuDST].dir
#define dst_inode sd[AuDST].inode
#define dst_h_dentry sd[AuDST].h_dentry
#define dst_parent sd[AuDST].parent
#define dst_h_parent sd[AuDST].h_parent
#define dst_wh_dentry sd[AuDST].wh_dentry
#define dst_hdir sd[AuDST].hdir
#define dst_hinode sd[AuDST].hinode
#define dst_h_dir sd[AuDST].hdir->hi_inode
#define dst_dt sd[AuDST].dt
#define dst_btop sd[AuDST].btop
#define dst_bdiropq sd[AuDST].bdiropq
struct dentry *h_trap;
struct au_branch *br;
struct path h_path;
struct au_nhash whlist;
aufs_bindex_t btgt, src_bwh;
struct {
unsigned short auren_flags;
unsigned char flags; /* syscall parameter */
unsigned char exchange;
} __packed;
struct au_whtmp_rmdir *thargs;
struct dentry *h_dst;
};
/* ---------------------------------------------------------------------- */
/*
* functions for reverting.
* when an error happened in a single rename systemcall, we should revert
* everything as if nothing happened.
* we don't need to revert the copied-up/down the parent dir since they are
* harmless.
*/
#define RevertFailure(fmt, ...) do { \
AuIOErr("revert failure: " fmt " (%d, %d)\n", \
##__VA_ARGS__, err, rerr); \
err = -EIO; \
} while (0)
static void au_ren_do_rev_diropq(int err, struct au_ren_args *a, int idx)
{
int rerr;
struct dentry *d;
#define src_or_dst(member) a->sd[idx].member
d = src_or_dst(dentry); /* {src,dst}_dentry */
au_hn_inode_lock_nested(src_or_dst(hinode), AuLsc_I_CHILD);
rerr = au_diropq_remove(d, a->btgt);
au_hn_inode_unlock(src_or_dst(hinode));
au_set_dbdiropq(d, src_or_dst(bdiropq));
if (rerr)
RevertFailure("remove diropq %pd", d);
#undef src_or_dst_
}
static void au_ren_rev_diropq(int err, struct au_ren_args *a)
{
if (au_ftest_ren(a->auren_flags, DIROPQ_SRC))
au_ren_do_rev_diropq(err, a, AuSRC);
if (au_ftest_ren(a->auren_flags, DIROPQ_DST))
au_ren_do_rev_diropq(err, a, AuDST);
}
static void au_ren_rev_rename(int err, struct au_ren_args *a)
{
int rerr;
struct inode *delegated;
a->h_path.dentry = vfsub_lkup_one(&a->src_dentry->d_name,
a->src_h_parent);
rerr = PTR_ERR(a->h_path.dentry);
if (IS_ERR(a->h_path.dentry)) {
RevertFailure("lkup one %pd", a->src_dentry);
return;
}
delegated = NULL;
rerr = vfsub_rename(a->dst_h_dir,
au_h_dptr(a->src_dentry, a->btgt),
a->src_h_dir, &a->h_path, &delegated, a->flags);
if (unlikely(rerr == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal rename\n");
iput(delegated);
}
d_drop(a->h_path.dentry);
dput(a->h_path.dentry);
/* au_set_h_dptr(a->src_dentry, a->btgt, NULL); */
if (rerr)
RevertFailure("rename %pd", a->src_dentry);
}
static void au_ren_rev_whtmp(int err, struct au_ren_args *a)
{
int rerr;
struct inode *delegated;
a->h_path.dentry = vfsub_lkup_one(&a->dst_dentry->d_name,
a->dst_h_parent);
rerr = PTR_ERR(a->h_path.dentry);
if (IS_ERR(a->h_path.dentry)) {
RevertFailure("lkup one %pd", a->dst_dentry);
return;
}
if (d_is_positive(a->h_path.dentry)) {
d_drop(a->h_path.dentry);
dput(a->h_path.dentry);
return;
}
delegated = NULL;
rerr = vfsub_rename(a->dst_h_dir, a->h_dst, a->dst_h_dir, &a->h_path,
&delegated, a->flags);
if (unlikely(rerr == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal rename\n");
iput(delegated);
}
d_drop(a->h_path.dentry);
dput(a->h_path.dentry);
if (!rerr)
au_set_h_dptr(a->dst_dentry, a->btgt, dget(a->h_dst));
else
RevertFailure("rename %pd", a->h_dst);
}
static void au_ren_rev_whsrc(int err, struct au_ren_args *a)
{
int rerr;
a->h_path.dentry = a->src_wh_dentry;
rerr = au_wh_unlink_dentry(a->src_h_dir, &a->h_path, a->src_dentry);
au_set_dbwh(a->src_dentry, a->src_bwh);
if (rerr)
RevertFailure("unlink %pd", a->src_wh_dentry);
}
#undef RevertFailure
/* ---------------------------------------------------------------------- */
/*
* when we have to copyup the renaming entry, do it with the rename-target name
* in order to minimize the cost (the later actual rename is unnecessary).
* otherwise rename it on the target branch.
*/
static int au_ren_or_cpup(struct au_ren_args *a)
{
int err;
struct dentry *d;
struct inode *delegated;
d = a->src_dentry;
if (au_dbtop(d) == a->btgt) {
a->h_path.dentry = a->dst_h_dentry;
AuDebugOn(au_dbtop(d) != a->btgt);
delegated = NULL;
err = vfsub_rename(a->src_h_dir, au_h_dptr(d, a->btgt),
a->dst_h_dir, &a->h_path, &delegated,
a->flags);
if (unlikely(err == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal rename\n");
iput(delegated);
}
} else
BUG();
if (!err && a->h_dst)
/* it will be set to dinfo later */
dget(a->h_dst);
return err;
}
/* cf. aufs_rmdir() */
static int au_ren_del_whtmp(struct au_ren_args *a)
{
struct inode *dir;
dir = a->dst_dir;
SiMustAnyLock(dir->i_sb);
au_nhash_wh_free(&a->thargs->whlist);
a->thargs->whlist = a->whlist;
a->whlist.nh_num = 0;
au_whtmp_kick_rmdir(dir, a->btgt, a->h_dst, a->thargs);
dput(a->h_dst);
a->thargs = NULL;
return 0;
}
/* make it 'opaque' dir. */
static int au_ren_do_diropq(struct au_ren_args *a, int idx)
{
int err;
struct dentry *d, *diropq;
#define src_or_dst(member) a->sd[idx].member
err = 0;
d = src_or_dst(dentry); /* {src,dst}_dentry */
src_or_dst(bdiropq) = au_dbdiropq(d);
src_or_dst(hinode) = au_hi(src_or_dst(inode), a->btgt);
au_hn_inode_lock_nested(src_or_dst(hinode), AuLsc_I_CHILD);
diropq = au_diropq_create(d, a->btgt);
au_hn_inode_unlock(src_or_dst(hinode));
if (IS_ERR(diropq))
err = PTR_ERR(diropq);
else
dput(diropq);
#undef src_or_dst_
return err;
}
static int au_ren_diropq(struct au_ren_args *a)
{
int err;
struct dentry *d;
err = 0;
d = a->dst_dentry; /* already renamed on the branch */
if (au_ftest_ren(a->auren_flags, ISDIR_SRC)
&& a->btgt != au_dbdiropq(a->src_dentry)
&& (a->dst_wh_dentry
|| a->btgt <= au_dbdiropq(d)
/* hide the lower to keep xino */
/* the lowers may not be a dir, but we hide them anyway */
|| a->btgt < au_dbbot(d))) {
AuDbg("here\n");
err = au_ren_do_diropq(a, AuSRC);
if (unlikely(err))
goto out;
au_fset_ren(a->auren_flags, DIROPQ_SRC);
}
if (!a->exchange)
goto out; /* success */
d = a->src_dentry; /* already renamed on the branch */
if (au_ftest_ren(a->auren_flags, ISDIR_DST)
&& a->btgt != au_dbdiropq(a->dst_dentry)
&& (a->btgt < au_dbdiropq(d)
|| a->btgt < au_dbbot(d))) {
AuDbgDentry(a->src_dentry);
AuDbgDentry(a->dst_dentry);
err = au_ren_do_diropq(a, AuDST);
if (unlikely(err))
goto out_rev_src;
au_fset_ren(a->auren_flags, DIROPQ_DST);
}
goto out; /* success */
out_rev_src:
AuDbg("err %d, reverting src\n", err);
au_ren_rev_diropq(err, a);
out:
return err;
}
static int do_rename(struct au_ren_args *a)
{
int err;
struct dentry *d, *h_d;
if (!a->exchange) {
/* prepare workqueue args for asynchronous rmdir */
h_d = a->dst_h_dentry;
if (au_ftest_ren(a->auren_flags, ISDIR_DST)
&& d_is_positive(h_d)) {
err = -ENOMEM;
a->thargs = au_whtmp_rmdir_alloc(a->src_dentry->d_sb,
GFP_NOFS);
if (unlikely(!a->thargs))
goto out;
a->h_dst = dget(h_d);
}
/* create whiteout for src_dentry */
if (au_ftest_ren(a->auren_flags, WHSRC)) {
a->src_bwh = au_dbwh(a->src_dentry);
AuDebugOn(a->src_bwh >= 0);
a->src_wh_dentry = au_wh_create(a->src_dentry, a->btgt,
a->src_h_parent);
err = PTR_ERR(a->src_wh_dentry);
if (IS_ERR(a->src_wh_dentry))
goto out_thargs;
}
/* lookup whiteout for dentry */
if (au_ftest_ren(a->auren_flags, WHDST)) {
h_d = au_wh_lkup(a->dst_h_parent,
&a->dst_dentry->d_name, a->br);
err = PTR_ERR(h_d);
if (IS_ERR(h_d))
goto out_whsrc;
if (d_is_negative(h_d))
dput(h_d);
else
a->dst_wh_dentry = h_d;
}
/* rename dentry to tmpwh */
if (a->thargs) {
err = au_whtmp_ren(a->dst_h_dentry, a->br);
if (unlikely(err))
goto out_whdst;
d = a->dst_dentry;
au_set_h_dptr(d, a->btgt, NULL);
err = au_lkup_neg(d, a->btgt, /*wh*/0);
if (unlikely(err))
goto out_whtmp;
a->dst_h_dentry = au_h_dptr(d, a->btgt);
}
}
BUG_ON(d_is_positive(a->dst_h_dentry) && a->src_btop != a->btgt);
#if 0
BUG_ON(!au_ftest_ren(a->auren_flags, DIRREN)
&& d_is_positive(a->dst_h_dentry)
&& a->src_btop != a->btgt);
#endif
/* rename by vfs_rename or cpup */
err = au_ren_or_cpup(a);
if (unlikely(err))
/* leave the copied-up one */
goto out_whtmp;
/* make dir opaque */
err = au_ren_diropq(a);
if (unlikely(err))
goto out_rename;
/* update target timestamps */
AuDebugOn(au_dbtop(a->src_dentry) != a->btgt);
a->h_path.dentry = au_h_dptr(a->src_dentry, a->btgt);
a->src_inode->i_ctime = d_inode(a->h_path.dentry)->i_ctime;
if (!a->exchange) {
/* remove whiteout for dentry */
if (a->dst_wh_dentry) {
a->h_path.dentry = a->dst_wh_dentry;
err = au_wh_unlink_dentry(a->dst_h_dir, &a->h_path,
a->dst_dentry);
if (unlikely(err))
goto out_diropq;
}
/* remove whtmp */
if (a->thargs)
au_ren_del_whtmp(a); /* ignore this error */
}
err = 0;
goto out_success;
out_diropq:
au_ren_rev_diropq(err, a);
out_rename:
au_ren_rev_rename(err, a);
dput(a->h_dst);
out_whtmp:
if (a->thargs)
au_ren_rev_whtmp(err, a);
out_whdst:
dput(a->dst_wh_dentry);
a->dst_wh_dentry = NULL;
out_whsrc:
if (a->src_wh_dentry)
au_ren_rev_whsrc(err, a);
out_success:
dput(a->src_wh_dentry);
dput(a->dst_wh_dentry);
out_thargs:
if (a->thargs) {
dput(a->h_dst);
au_whtmp_rmdir_free(a->thargs);
a->thargs = NULL;
}
out:
return err;
}
/* ---------------------------------------------------------------------- */
/*
* test if @dentry dir can be rename destination or not.
* success means, it is a logically empty dir.
*/
static int may_rename_dstdir(struct dentry *dentry, struct au_nhash *whlist)
{
return au_test_empty(dentry, whlist);
}
/*
* test if @dentry dir can be rename source or not.
* if it can, return 0.
* success means,
* - it is a logically empty dir.
* - or, it exists on writable branch and has no children including whiteouts
* on the lower branch.
*/
static int may_rename_srcdir(struct dentry *dentry, aufs_bindex_t btgt)
{
int err;
unsigned int rdhash;
aufs_bindex_t btop;
struct super_block *sb;
sb = dentry->d_sb;
btop = au_dbtop(dentry);
if (btop != btgt) {
struct au_nhash whlist;
SiMustAnyLock(sb);
rdhash = au_sbi(sb)->si_rdhash;
if (!rdhash)
rdhash = au_rdhash_est(au_dir_size(/*file*/NULL,
dentry));
err = au_nhash_alloc(&whlist, rdhash, GFP_NOFS);
if (unlikely(err))
goto out;
err = au_test_empty(dentry, &whlist);
au_nhash_wh_free(&whlist);
goto out;
}
if (btop == au_dbtaildir(dentry))
return 0; /* success */
err = au_test_empty_lower(dentry);
out:
if (err == -ENOTEMPTY) {
AuWarn1("renaming dir who has child(ren) on multiple branches,"
" is not supported\n");
err = -EXDEV;
}
return err;
}
/* side effect: sets whlist and h_dentry */
static int au_ren_may_dir(struct au_ren_args *a)
{
int err;
unsigned int rdhash;
struct dentry *d;
d = a->dst_dentry;
SiMustAnyLock(d->d_sb);
err = 0;
if (au_ftest_ren(a->auren_flags, ISDIR_DST) && a->dst_inode) {
rdhash = au_sbi(d->d_sb)->si_rdhash;
if (!rdhash)
rdhash = au_rdhash_est(au_dir_size(/*file*/NULL, d));
err = au_nhash_alloc(&a->whlist, rdhash, GFP_NOFS);
if (unlikely(err))
goto out;
if (!a->exchange) {
au_set_dbtop(d, a->dst_btop);
err = may_rename_dstdir(d, &a->whlist);
au_set_dbtop(d, a->btgt);
} else
err = may_rename_srcdir(d, a->btgt);
}
a->dst_h_dentry = au_h_dptr(d, au_dbtop(d));
if (unlikely(err))
goto out;
d = a->src_dentry;
a->src_h_dentry = au_h_dptr(d, au_dbtop(d));
if (au_ftest_ren(a->auren_flags, ISDIR_SRC)) {
err = may_rename_srcdir(d, a->btgt);
if (unlikely(err)) {
au_nhash_wh_free(&a->whlist);
a->whlist.nh_num = 0;
}
}
out:
return err;
}
/* ---------------------------------------------------------------------- */
/*
* simple tests for rename.
* following the checks in vfs, plus the parent-child relationship.
*/
static int au_may_ren(struct au_ren_args *a)
{
int err, isdir;
struct inode *h_inode;
if (a->src_btop == a->btgt) {
err = au_may_del(a->src_dentry, a->btgt, a->src_h_parent,
au_ftest_ren(a->auren_flags, ISDIR_SRC));
if (unlikely(err))
goto out;
err = -EINVAL;
if (unlikely(a->src_h_dentry == a->h_trap))
goto out;
}
err = 0;
if (a->dst_btop != a->btgt)
goto out;
err = -ENOTEMPTY;
if (unlikely(a->dst_h_dentry == a->h_trap))
goto out;
err = -EIO;
isdir = !!au_ftest_ren(a->auren_flags, ISDIR_DST);
if (d_really_is_negative(a->dst_dentry)) {
if (d_is_negative(a->dst_h_dentry))
err = au_may_add(a->dst_dentry, a->btgt,
a->dst_h_parent, isdir);
} else {
if (unlikely(d_is_negative(a->dst_h_dentry)))
goto out;
h_inode = d_inode(a->dst_h_dentry);
if (h_inode->i_nlink)
err = au_may_del(a->dst_dentry, a->btgt,
a->dst_h_parent, isdir);
}
out:
if (unlikely(err == -ENOENT || err == -EEXIST))
err = -EIO;
AuTraceErr(err);
return err;
}
/* ---------------------------------------------------------------------- */
/*
* locking order
* (VFS)
* - src_dir and dir by lock_rename()
* - inode if exists
* (aufs)
* - lock all
* + src_dentry and dentry by aufs_read_and_write_lock2() which calls,
* + si_read_lock
* + di_write_lock2_child()
* + di_write_lock_child()
* + ii_write_lock_child()
* + di_write_lock_child2()
* + ii_write_lock_child2()
* + src_parent and parent
* + di_write_lock_parent()
* + ii_write_lock_parent()
* + di_write_lock_parent2()
* + ii_write_lock_parent2()
* + lower src_dir and dir by vfsub_lock_rename()
* + verify the every relationships between child and parent. if any
* of them failed, unlock all and return -EBUSY.
*/
static void au_ren_unlock(struct au_ren_args *a)
{
vfsub_unlock_rename(a->src_h_parent, a->src_hdir,
a->dst_h_parent, a->dst_hdir);
if (au_ftest_ren(a->auren_flags, MNT_WRITE))
vfsub_mnt_drop_write(au_br_mnt(a->br));
}
static int au_ren_lock(struct au_ren_args *a)
{
int err;
unsigned int udba;
err = 0;
a->src_h_parent = au_h_dptr(a->src_parent, a->btgt);
a->src_hdir = au_hi(a->src_dir, a->btgt);
a->dst_h_parent = au_h_dptr(a->dst_parent, a->btgt);
a->dst_hdir = au_hi(a->dst_dir, a->btgt);
err = vfsub_mnt_want_write(au_br_mnt(a->br));
if (unlikely(err))
goto out;
au_fset_ren(a->auren_flags, MNT_WRITE);
a->h_trap = vfsub_lock_rename(a->src_h_parent, a->src_hdir,
a->dst_h_parent, a->dst_hdir);
udba = au_opt_udba(a->src_dentry->d_sb);
if (unlikely(a->src_hdir->hi_inode != d_inode(a->src_h_parent)
|| a->dst_hdir->hi_inode != d_inode(a->dst_h_parent)))
err = au_busy_or_stale();
if (!err && au_dbtop(a->src_dentry) == a->btgt)
err = au_h_verify(a->src_h_dentry, udba,
d_inode(a->src_h_parent), a->src_h_parent,
a->br);
if (!err && au_dbtop(a->dst_dentry) == a->btgt)
err = au_h_verify(a->dst_h_dentry, udba,
d_inode(a->dst_h_parent), a->dst_h_parent,
a->br);
if (!err)
goto out; /* success */
err = au_busy_or_stale();
au_ren_unlock(a);
out:
return err;
}
/* ---------------------------------------------------------------------- */
static void au_ren_refresh_dir(struct au_ren_args *a)
{
struct inode *dir;
dir = a->dst_dir;
inode_inc_iversion(dir);
if (au_ftest_ren(a->auren_flags, ISDIR_SRC)) {
/* is this updating defined in POSIX? */
au_cpup_attr_timesizes(a->src_inode);
au_cpup_attr_nlink(dir, /*force*/1);
}
au_dir_ts(dir, a->btgt);
if (a->exchange) {
dir = a->src_dir;
inode_inc_iversion(dir);
if (au_ftest_ren(a->auren_flags, ISDIR_DST)) {
/* is this updating defined in POSIX? */
au_cpup_attr_timesizes(a->dst_inode);
au_cpup_attr_nlink(dir, /*force*/1);
}
au_dir_ts(dir, a->btgt);
}
if (au_ftest_ren(a->auren_flags, ISSAMEDIR))
return;
dir = a->src_dir;
inode_inc_iversion(dir);
if (au_ftest_ren(a->auren_flags, ISDIR_SRC))
au_cpup_attr_nlink(dir, /*force*/1);
au_dir_ts(dir, a->btgt);
}
static void au_ren_refresh(struct au_ren_args *a)
{
aufs_bindex_t bbot, bindex;
struct dentry *d, *h_d;
struct inode *i, *h_i;
struct super_block *sb;
d = a->dst_dentry;
d_drop(d);
if (a->h_dst)
/* already dget-ed by au_ren_or_cpup() */
au_set_h_dptr(d, a->btgt, a->h_dst);
i = a->dst_inode;
if (i) {
if (!a->exchange) {
if (!au_ftest_ren(a->auren_flags, ISDIR_DST))
vfsub_drop_nlink(i);
else {
vfsub_dead_dir(i);
au_cpup_attr_timesizes(i);
}
au_update_dbrange(d, /*do_put_zero*/1);
} else
au_cpup_attr_nlink(i, /*force*/1);
} else {
bbot = a->btgt;
for (bindex = au_dbtop(d); bindex < bbot; bindex++)
au_set_h_dptr(d, bindex, NULL);
bbot = au_dbbot(d);
for (bindex = a->btgt + 1; bindex <= bbot; bindex++)
au_set_h_dptr(d, bindex, NULL);
au_update_dbrange(d, /*do_put_zero*/0);
}
d = a->src_dentry;
if (!a->exchange) {
au_set_dbwh(d, -1);
bbot = au_dbbot(d);
for (bindex = a->btgt + 1; bindex <= bbot; bindex++) {
h_d = au_h_dptr(d, bindex);
if (h_d)
au_set_h_dptr(d, bindex, NULL);
}
au_set_dbbot(d, a->btgt);
sb = d->d_sb;
i = a->src_inode;
if (au_opt_test(au_mntflags(sb), PLINK) && au_plink_test(i))
return; /* success */
bbot = au_ibbot(i);
for (bindex = a->btgt + 1; bindex <= bbot; bindex++) {
h_i = au_h_iptr(i, bindex);
if (h_i) {
au_xino_write(sb, bindex, h_i->i_ino, /*ino*/0);
/* ignore this error */
au_set_h_iptr(i, bindex, NULL, 0);
}
}
au_set_ibbot(i, a->btgt);
}
d_drop(a->src_dentry);
}
/* ---------------------------------------------------------------------- */
/* mainly for link(2) and rename(2) */
int au_wbr(struct dentry *dentry, aufs_bindex_t btgt)
{
aufs_bindex_t bdiropq, bwh;
struct dentry *parent;
struct au_branch *br;
parent = dentry->d_parent;
IMustLock(d_inode(parent)); /* dir is locked */
bdiropq = au_dbdiropq(parent);
bwh = au_dbwh(dentry);
br = au_sbr(dentry->d_sb, btgt);
if (au_br_rdonly(br)
|| (0 <= bdiropq && bdiropq < btgt)
|| (0 <= bwh && bwh < btgt))
btgt = -1;
AuDbg("btgt %d\n", btgt);
return btgt;
}
/* sets src_btop, dst_btop and btgt */
static int au_ren_wbr(struct au_ren_args *a)
{
int err;
struct au_wr_dir_args wr_dir_args = {
/* .force_btgt = -1, */
.flags = AuWrDir_ADD_ENTRY
};
a->src_btop = au_dbtop(a->src_dentry);
a->dst_btop = au_dbtop(a->dst_dentry);
if (au_ftest_ren(a->auren_flags, ISDIR_SRC)
|| au_ftest_ren(a->auren_flags, ISDIR_DST))
au_fset_wrdir(wr_dir_args.flags, ISDIR);
wr_dir_args.force_btgt = a->src_btop;
if (a->dst_inode && a->dst_btop < a->src_btop)
wr_dir_args.force_btgt = a->dst_btop;
wr_dir_args.force_btgt = au_wbr(a->dst_dentry, wr_dir_args.force_btgt);
err = au_wr_dir(a->dst_dentry, a->src_dentry, &wr_dir_args);
a->btgt = err;
if (a->exchange)
au_update_dbtop(a->dst_dentry);
return err;
}
static void au_ren_dt(struct au_ren_args *a)
{
a->h_path.dentry = a->src_h_parent;
au_dtime_store(a->src_dt + AuPARENT, a->src_parent, &a->h_path);
if (!au_ftest_ren(a->auren_flags, ISSAMEDIR)) {
a->h_path.dentry = a->dst_h_parent;
au_dtime_store(a->dst_dt + AuPARENT, a->dst_parent, &a->h_path);
}
au_fclr_ren(a->auren_flags, DT_DSTDIR);
if (!au_ftest_ren(a->auren_flags, ISDIR_SRC)
&& !a->exchange)
return;
a->h_path.dentry = a->src_h_dentry;
au_dtime_store(a->src_dt + AuCHILD, a->src_dentry, &a->h_path);
if (d_is_positive(a->dst_h_dentry)) {
au_fset_ren(a->auren_flags, DT_DSTDIR);
a->h_path.dentry = a->dst_h_dentry;
au_dtime_store(a->dst_dt + AuCHILD, a->dst_dentry, &a->h_path);
}
}
static void au_ren_rev_dt(int err, struct au_ren_args *a)
{
struct dentry *h_d;
struct inode *h_inode;
au_dtime_revert(a->src_dt + AuPARENT);
if (!au_ftest_ren(a->auren_flags, ISSAMEDIR))
au_dtime_revert(a->dst_dt + AuPARENT);
if (au_ftest_ren(a->auren_flags, ISDIR_SRC) && err != -EIO) {
h_d = a->src_dt[AuCHILD].dt_h_path.dentry;
h_inode = d_inode(h_d);
inode_lock_nested(h_inode, AuLsc_I_CHILD);
au_dtime_revert(a->src_dt + AuCHILD);
inode_unlock(h_inode);
if (au_ftest_ren(a->auren_flags, DT_DSTDIR)) {
h_d = a->dst_dt[AuCHILD].dt_h_path.dentry;
h_inode = d_inode(h_d);
inode_lock_nested(h_inode, AuLsc_I_CHILD);
au_dtime_revert(a->dst_dt + AuCHILD);
inode_unlock(h_inode);
}
}
}
/* ---------------------------------------------------------------------- */
int aufs_rename(struct inode *_src_dir, struct dentry *_src_dentry,
struct inode *_dst_dir, struct dentry *_dst_dentry,
unsigned int _flags)
{
int err, lock_flags;
/* reduce stack space */
struct au_ren_args *a;
struct au_pin pin;
AuDbg("%pd, %pd, 0x%x\n", _src_dentry, _dst_dentry, _flags);
IMustLock(_src_dir);
IMustLock(_dst_dir);
err = -EINVAL;
if (unlikely(_flags & RENAME_WHITEOUT))
goto out;
err = -ENOMEM;
BUILD_BUG_ON(sizeof(*a) > PAGE_SIZE);
a = kzalloc(sizeof(*a), GFP_NOFS);
if (unlikely(!a))
goto out;
a->flags = _flags;
BUILD_BUG_ON(sizeof(a->exchange) == sizeof(u8)
&& RENAME_EXCHANGE > U8_MAX);
a->exchange = _flags & RENAME_EXCHANGE;
a->src_dir = _src_dir;
a->src_dentry = _src_dentry;
a->src_inode = NULL;
if (d_really_is_positive(a->src_dentry))
a->src_inode = d_inode(a->src_dentry);
a->src_parent = a->src_dentry->d_parent; /* dir inode is locked */
a->dst_dir = _dst_dir;
a->dst_dentry = _dst_dentry;
a->dst_inode = NULL;
if (d_really_is_positive(a->dst_dentry))
a->dst_inode = d_inode(a->dst_dentry);
a->dst_parent = a->dst_dentry->d_parent; /* dir inode is locked */
if (a->dst_inode) {
/*
* if EXCHANGE && src is non-dir && dst is dir,
* dst is not locked.
*/
/* IMustLock(a->dst_inode); */
au_igrab(a->dst_inode);
}
err = -ENOTDIR;
lock_flags = AuLock_FLUSH | AuLock_NOPLM | AuLock_GEN;
if (d_is_dir(a->src_dentry)) {
au_fset_ren(a->auren_flags, ISDIR_SRC);
if (unlikely(!a->exchange
&& d_really_is_positive(a->dst_dentry)
&& !d_is_dir(a->dst_dentry)))
goto out_free;
lock_flags |= AuLock_DIRS;
}
if (a->dst_inode && d_is_dir(a->dst_dentry)) {
au_fset_ren(a->auren_flags, ISDIR_DST);
if (unlikely(!a->exchange
&& d_really_is_positive(a->src_dentry)
&& !d_is_dir(a->src_dentry)))
goto out_free;
lock_flags |= AuLock_DIRS;
}
err = aufs_read_and_write_lock2(a->dst_dentry, a->src_dentry,
lock_flags);
if (unlikely(err))
goto out_free;
err = au_d_hashed_positive(a->src_dentry);
if (unlikely(err))
goto out_unlock;
err = -ENOENT;
if (a->dst_inode) {
/*
* If it is a dir, VFS unhash it before this
* function. It means we cannot rely upon d_unhashed().
*/
if (unlikely(!a->dst_inode->i_nlink))
goto out_unlock;
if (!au_ftest_ren(a->auren_flags, ISDIR_DST)) {
err = au_d_hashed_positive(a->dst_dentry);
if (unlikely(err && !a->exchange))
goto out_unlock;
} else if (unlikely(IS_DEADDIR(a->dst_inode)))
goto out_unlock;
} else if (unlikely(d_unhashed(a->dst_dentry)))
goto out_unlock;
/*
* is it possible?
* yes, it happened (in linux-3.3-rcN) but I don't know why.
* there may exist a problem somewhere else.
*/
err = -EINVAL;
if (unlikely(d_inode(a->dst_parent) == d_inode(a->src_dentry)))
goto out_unlock;
au_fset_ren(a->auren_flags, ISSAMEDIR); /* temporary */
di_write_lock_parent(a->dst_parent);
/* which branch we process */
err = au_ren_wbr(a);
if (unlikely(err < 0))
goto out_parent;
a->br = au_sbr(a->dst_dentry->d_sb, a->btgt);
a->h_path.mnt = au_br_mnt(a->br);
/* are they available to be renamed */
err = au_ren_may_dir(a);
if (unlikely(err))
goto out_children;
/* prepare the writable parent dir on the same branch */
if (a->dst_btop == a->btgt) {
au_fset_ren(a->auren_flags, WHDST);
} else {
err = au_cpup_dirs(a->dst_dentry, a->btgt);
if (unlikely(err))
goto out_children;
}
err = 0;
if (!a->exchange) {
if (a->src_dir != a->dst_dir) {
/*
* this temporary unlock is safe,
* because both dir->i_mutex are locked.
*/
di_write_unlock(a->dst_parent);
di_write_lock_parent(a->src_parent);
err = au_wr_dir_need_wh(a->src_dentry,
au_ftest_ren(a->auren_flags,
ISDIR_SRC),
&a->btgt);
di_write_unlock(a->src_parent);
di_write_lock2_parent(a->src_parent, a->dst_parent,
/*isdir*/1);
au_fclr_ren(a->auren_flags, ISSAMEDIR);
} else
err = au_wr_dir_need_wh(a->src_dentry,
au_ftest_ren(a->auren_flags,
ISDIR_SRC),
&a->btgt);
}
if (unlikely(err < 0))
goto out_children;
if (err)
au_fset_ren(a->auren_flags, WHSRC);
/* cpup src */
if (a->src_btop != a->btgt) {
err = au_pin(&pin, a->src_dentry, a->btgt,
au_opt_udba(a->src_dentry->d_sb),
AuPin_DI_LOCKED | AuPin_MNT_WRITE);
if (!err) {
struct au_cp_generic cpg = {
.dentry = a->src_dentry,
.bdst = a->btgt,
.bsrc = a->src_btop,
.len = -1,
.pin = &pin,
.flags = AuCpup_DTIME
};
AuDebugOn(au_dbtop(a->src_dentry) != a->src_btop);
err = au_sio_cpup_simple(&cpg);
au_unpin(&pin);
}
if (unlikely(err))
goto out_children;
a->src_btop = a->btgt;
a->src_h_dentry = au_h_dptr(a->src_dentry, a->btgt);
if (!a->exchange)
au_fset_ren(a->auren_flags, WHSRC);
}
/* cpup dst */
if (a->exchange && a->dst_inode
&& a->dst_btop != a->btgt) {
err = au_pin(&pin, a->dst_dentry, a->btgt,
au_opt_udba(a->dst_dentry->d_sb),
AuPin_DI_LOCKED | AuPin_MNT_WRITE);
if (!err) {
struct au_cp_generic cpg = {
.dentry = a->dst_dentry,
.bdst = a->btgt,
.bsrc = a->dst_btop,
.len = -1,
.pin = &pin,
.flags = AuCpup_DTIME
};
err = au_sio_cpup_simple(&cpg);
au_unpin(&pin);
}
if (unlikely(err))
goto out_children;
a->dst_btop = a->btgt;
a->dst_h_dentry = au_h_dptr(a->dst_dentry, a->btgt);
}
/* lock them all */
err = au_ren_lock(a);
if (unlikely(err))
/* leave the copied-up one */
goto out_children;
if (!a->exchange) {
if (!au_opt_test(au_mntflags(a->dst_dir->i_sb), UDBA_NONE))
err = au_may_ren(a);
else if (unlikely(a->dst_dentry->d_name.len > AUFS_MAX_NAMELEN))
err = -ENAMETOOLONG;
if (unlikely(err))
goto out_hdir;
}
/* store timestamps to be revertible */
au_ren_dt(a);
/* here we go */
err = do_rename(a);
if (unlikely(err))
goto out_dt;
/* update dir attributes */
au_ren_refresh_dir(a);
/* dput/iput all lower dentries */
au_ren_refresh(a);
goto out_hdir; /* success */
out_dt:
au_ren_rev_dt(err, a);
out_hdir:
au_ren_unlock(a);
out_children:
au_nhash_wh_free(&a->whlist);
if (err && a->dst_inode && a->dst_btop != a->btgt) {
AuDbg("btop %d, btgt %d\n", a->dst_btop, a->btgt);
au_set_h_dptr(a->dst_dentry, a->btgt, NULL);
au_set_dbtop(a->dst_dentry, a->dst_btop);
}
out_parent:
if (!err) {
if (!a->exchange)
d_move(a->src_dentry, a->dst_dentry);
else
d_exchange(a->src_dentry, a->dst_dentry);
} else {
au_update_dbtop(a->dst_dentry);
if (!a->dst_inode)
d_drop(a->dst_dentry);
}
if (au_ftest_ren(a->auren_flags, ISSAMEDIR))
di_write_unlock(a->dst_parent);
else
di_write_unlock2(a->src_parent, a->dst_parent);
out_unlock:
aufs_read_and_write_unlock2(a->dst_dentry, a->src_dentry);
out_free:
iput(a->dst_inode);
if (a->thargs)
au_whtmp_rmdir_free(a->thargs);
au_kfree_rcu(a);
out:
AuTraceErr(err);
return err;
}
......@@ -179,6 +179,12 @@ int au_may_del(struct dentry *dentry, aufs_bindex_t bindex,
int aufs_unlink(struct inode *dir, struct dentry *dentry);
int aufs_rmdir(struct inode *dir, struct dentry *dentry);
/* i_op_ren.c */
int au_wbr(struct dentry *dentry, aufs_bindex_t btgt);
int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
struct inode *dir, struct dentry *dentry,
unsigned int flags);
/* iinfo.c */
struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex);
void au_hiput(struct au_hinode *hinode);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment