++static int au_drinfo_construct(struct au_drinfo_fdata **fdata,
++ struct dentry *h_dentry,
++ unsigned char *allocated)
++{
++ int err, v;
++ struct au_drinfo_fdata *f, *p;
++ struct au_drinfo *drinfo;
++ struct inode *h_inode;
++ struct qstr *qname;
++
++ err = 0;
++ f = *fdata;
++ h_inode = d_inode(h_dentry);
++ qname = &h_dentry->d_name;
++ drinfo = &f->drinfo;
++ drinfo->ino = (__force uint64_t)cpu_to_be64(h_inode->i_ino);
++ drinfo->oldnamelen = qname->len;
++ if (*allocated < sizeof(*f) + qname->len) {
++ v = roundup_pow_of_two(*allocated + qname->len);
++ p = au_krealloc(f, v, GFP_NOFS, /*may_shrink*/0);
++ if (unlikely(!p)) {
++ err = -ENOMEM;
++ AuTraceErr(err);
++ goto out;
++ }
++ f = p;
++ *fdata = f;
++ *allocated = v;
++ drinfo = &f->drinfo;
++ }
++ memcpy(drinfo->oldname, qname->name, qname->len);
++ AuDbg("i%llu, %.*s\n",
++ be64_to_cpu((__force __be64)drinfo->ino), drinfo->oldnamelen,
++ drinfo->oldname);
++
++out:
++ AuTraceErr(err);
++ return err;
++}
++
++/* callers have to free the return value */
++static struct au_drinfo *au_drinfo_read_k(struct file *file, ino_t h_ino)
++{
++ struct au_drinfo *ret, *drinfo;
++ struct au_drinfo_fdata fdata;
++ int len;
++ loff_t pos;
++ ssize_t ssz;
++
++ ret = ERR_PTR(-EIO);
++ pos = 0;
++ ssz = vfsub_read_k(file, &fdata, sizeof(fdata), &pos);
++ if (unlikely(ssz != sizeof(fdata))) {
++ AuIOErr("ssz %zd, %u, %pD2\n",
++ ssz, (unsigned int)sizeof(fdata), file);
++ goto out;
++ }
++
++ fdata.magic = ntohl((__force __be32)fdata.magic);
++ switch (fdata.magic) {
++ case AUFS_DRINFO_MAGIC_V1:
++ break;
++ default:
++ AuIOErr("magic-num 0x%x, 0x%x, %pD2\n",
++ fdata.magic, AUFS_DRINFO_MAGIC_V1, file);
++ goto out;
++ }
++
++ drinfo = &fdata.drinfo;
++ len = drinfo->oldnamelen;
++ if (!len) {
++ AuIOErr("broken drinfo %pD2\n", file);
++ goto out;
++ }
++
++ ret = NULL;
++ drinfo->ino = be64_to_cpu((__force __be64)drinfo->ino);
++ if (unlikely(h_ino && drinfo->ino != h_ino)) {
++ AuDbg("ignored i%llu, i%llu, %pD2\n",
++ (unsigned long long)drinfo->ino,
++ (unsigned long long)h_ino, file);
++ goto out; /* success */
++ }
++
++ ret = kmalloc(sizeof(*ret) + len, GFP_NOFS);
++ if (unlikely(!ret)) {
++ ret = ERR_PTR(-ENOMEM);
++ AuTraceErrPtr(ret);
++ goto out;
++ }
++
++ *ret = *drinfo;
++ ssz = vfsub_read_k(file, (void *)ret->oldname, len, &pos);
++ if (unlikely(ssz != len)) {
++ au_kfree_rcu(ret);
++ ret = ERR_PTR(-EIO);
++ AuIOErr("ssz %zd, %u, %pD2\n", ssz, len, file);
++ goto out;
++ }
++
++ AuDbg("oldname %.*s\n", ret->oldnamelen, ret->oldname);
++
++out:
++ return ret;
++}
++
++/* ---------------------------------------------------------------------- */
++
++/* in order to be revertible */
++struct au_drinfo_rev_elm {
++ int created;
++ struct dentry *info_dentry;
++ struct au_drinfo *info_last;
++};
++
++struct au_drinfo_rev {
++ unsigned char already;
++ aufs_bindex_t nelm;
++ struct au_drinfo_rev_elm elm[0];
++};
++
++/* todo: isn't it too large? */
++struct au_drinfo_store {
++ struct path h_ppath;
++ struct dentry *h_dentry;
++ struct au_drinfo_fdata *fdata;
++ char *infoname; /* inside of whname, just after PFX */
++ char whname[sizeof(AUFS_WH_DR_INFO_PFX) + AUFS_DIRREN_ENV_VAL_SZ];
++ aufs_bindex_t btgt, btail;
++ unsigned char no_sio,
++ allocated, /* current size of *fdata */
++ infonamelen, /* room size for p */
++ whnamelen, /* length of the generated name */
++ renameback; /* renamed back */
++};
++
++/* on rename(2) error, the caller should revert it using @elm */
++static int au_drinfo_do_store(struct au_drinfo_store *w,
++ struct au_drinfo_rev_elm *elm)
++{
++ int err, len;
++ ssize_t ssz;
++ loff_t pos;
++ struct path infopath = {
++ .mnt = w->h_ppath.mnt
++ };
++ struct inode *h_dir, *h_inode, *delegated;
++ struct file *infofile;
++ struct qstr *qname;
++
++ AuDebugOn(elm
++ && memcmp(elm, page_address(ZERO_PAGE(0)), sizeof(*elm)));
++
++ infopath.dentry = vfsub_lookup_one_len(w->whname, w->h_ppath.dentry,
++ w->whnamelen);
++ AuTraceErrPtr(infopath.dentry);
++ if (IS_ERR(infopath.dentry)) {
++ err = PTR_ERR(infopath.dentry);
++ goto out;
++ }
++
++ err = 0;
++ h_dir = d_inode(w->h_ppath.dentry);
++ if (elm && d_is_negative(infopath.dentry)) {
++ err = vfsub_create(h_dir, &infopath, 0600, /*want_excl*/true);
++ AuTraceErr(err);
++ if (unlikely(err))
++ goto out_dput;
++ elm->created = 1;
++ elm->info_dentry = dget(infopath.dentry);
++ }
++
++ infofile = vfsub_dentry_open(&infopath, O_RDWR);
++ AuTraceErrPtr(infofile);
++ if (IS_ERR(infofile)) {
++ err = PTR_ERR(infofile);
++ goto out_dput;
++ }
++
++ h_inode = d_inode(infopath.dentry);
++ if (elm && i_size_read(h_inode)) {
++ h_inode = d_inode(w->h_dentry);
++ elm->info_last = au_drinfo_read_k(infofile, h_inode->i_ino);
++ AuTraceErrPtr(elm->info_last);
++ if (IS_ERR(elm->info_last)) {
++ err = PTR_ERR(elm->info_last);
++ elm->info_last = NULL;
++ AuDebugOn(elm->info_dentry);
++ goto out_fput;
++ }
++ }
++
++ if (elm && w->renameback) {
++ delegated = NULL;
++ err = vfsub_unlink(h_dir, &infopath, &delegated, /*force*/0);
++ AuTraceErr(err);
++ if (unlikely(err == -EWOULDBLOCK))
++ iput(delegated);
++ goto out_fput;
++ }
++
++ pos = 0;
++ qname = &w->h_dentry->d_name;
++ len = sizeof(*w->fdata) + qname->len;
++ if (!elm)
++ len = sizeof(*w->fdata) + w->fdata->drinfo.oldnamelen;
++ ssz = vfsub_write_k(infofile, w->fdata, len, &pos);
++ if (ssz == len) {
++ AuDbg("hi%llu, %.*s\n", w->fdata->drinfo.ino,
++ w->fdata->drinfo.oldnamelen, w->fdata->drinfo.oldname);
++ goto out_fput; /* success */
++ } else {
++ err = -EIO;
++ if (ssz < 0)
++ err = ssz;
++ /* the caller should revert it using @elm */
++ }
++
++out_fput:
++ fput(infofile);
++out_dput:
++ dput(infopath.dentry);
++out:
++ AuTraceErr(err);
++ return err;
++}
++
++struct au_call_drinfo_do_store_args {
++ int *errp;
++ struct au_drinfo_store *w;
++ struct au_drinfo_rev_elm *elm;
++};
++
++static void au_call_drinfo_do_store(void *args)
++{
++ struct au_call_drinfo_do_store_args *a = args;
++
++ *a->errp = au_drinfo_do_store(a->w, a->elm);
++}
++
++static int au_drinfo_store_sio(struct au_drinfo_store *w,
++ struct au_drinfo_rev_elm *elm)
++{
++ int err, wkq_err;
++
++ if (w->no_sio)
++ err = au_drinfo_do_store(w, elm);
++ else {
++ struct au_call_drinfo_do_store_args a = {
++ .errp = &err,
++ .w = w,
++ .elm = elm
++ };
++ wkq_err = au_wkq_wait(au_call_drinfo_do_store, &a);
++ if (unlikely(wkq_err))
++ err = wkq_err;
++ }
++ AuTraceErr(err);
++
++ return err;
++}
++
++static int au_drinfo_store_work_init(struct au_drinfo_store *w,
++ aufs_bindex_t btgt)
++{
++ int err;
++
++ memset(w, 0, sizeof(*w));
++ w->allocated = roundup_pow_of_two(sizeof(*w->fdata) + 40);
++ strcpy(w->whname, AUFS_WH_DR_INFO_PFX);
++ w->infoname = w->whname + sizeof(AUFS_WH_DR_INFO_PFX) - 1;
++ w->infonamelen = sizeof(w->whname) - sizeof(AUFS_WH_DR_INFO_PFX);
++ w->btgt = btgt;
++ w->no_sio = !!uid_eq(current_fsuid(), GLOBAL_ROOT_UID);
++
++ err = -ENOMEM;
++ w->fdata = kcalloc(1, w->allocated, GFP_NOFS);
++ if (unlikely(!w->fdata)) {
++ AuTraceErr(err);
++ goto out;
++ }
++ w->fdata->magic = (__force uint32_t)htonl(AUFS_DRINFO_MAGIC_V1);
++ err = 0;
++
++out:
++ return err;
++}
++
++static void au_drinfo_store_work_fin(struct au_drinfo_store *w)
++{
++ au_kfree_rcu(w->fdata);
++}
++
++static void au_drinfo_store_rev(struct au_drinfo_rev *rev,
++ struct au_drinfo_store *w)
++{
++ struct au_drinfo_rev_elm *elm;
++ struct inode *h_dir, *delegated;
++ int err, nelm;
++ struct path infopath = {
++ .mnt = w->h_ppath.mnt
++ };
++
++ h_dir = d_inode(w->h_ppath.dentry);
++ IMustLock(h_dir);
++
++ err = 0;
++ elm = rev->elm;
++ for (nelm = rev->nelm; nelm > 0; nelm--, elm++) {
++ AuDebugOn(elm->created && elm->info_last);
++ if (elm->created) {
++ AuDbg("here\n");
++ delegated = NULL;
++ infopath.dentry = elm->info_dentry;
++ err = vfsub_unlink(h_dir, &infopath, &delegated,
++ !w->no_sio);
++ AuTraceErr(err);
++ if (unlikely(err == -EWOULDBLOCK))
++ iput(delegated);
++ dput(elm->info_dentry);
++ } else if (elm->info_last) {
++ AuDbg("here\n");
++ w->fdata->drinfo = *elm->info_last;
++ memcpy(w->fdata->drinfo.oldname,
++ elm->info_last->oldname,
++ elm->info_last->oldnamelen);
++ err = au_drinfo_store_sio(w, /*elm*/NULL);
++ au_kfree_rcu(elm->info_last);
++ }
++ if (unlikely(err))
++ AuIOErr("%d, %s\n", err, w->whname);
++ /* go on even if err */
++ }
++}
++
++/* caller has to call au_dr_rename_fin() later */
++static int au_drinfo_store(struct dentry *dentry, aufs_bindex_t btgt,
++ struct qstr *dst_name, void *_rev)
++{
++ int err, sz, nelm;
++ aufs_bindex_t bindex, btail;
++ struct au_drinfo_store work;
++ struct au_drinfo_rev *rev, **p;
++ struct au_drinfo_rev_elm *elm;
++ struct super_block *sb;
++ struct au_branch *br;
++ struct au_hinode *hdir;
++
++ err = au_drinfo_store_work_init(&work, btgt);
++ AuTraceErr(err);
++ if (unlikely(err))
++ goto out;
++
++ err = -ENOMEM;
++ btail = au_dbtaildir(dentry);
++ nelm = btail - btgt;
++ sz = sizeof(*rev) + sizeof(*elm) * nelm;
++ rev = kcalloc(1, sz, GFP_NOFS);
++ if (unlikely(!rev)) {
++ AuTraceErr(err);
++ goto out_args;
++ }
++ rev->nelm = nelm;
++ elm = rev->elm;
++ p = _rev;
++ *p = rev;
++
++ err = 0;
++ sb = dentry->d_sb;
++ work.h_ppath.dentry = au_h_dptr(dentry, btgt);
++ work.h_ppath.mnt = au_sbr_mnt(sb, btgt);
++ hdir = au_hi(d_inode(dentry), btgt);
++ au_hn_inode_lock_nested(hdir, AuLsc_I_CHILD);
++ for (bindex = btgt + 1; bindex <= btail; bindex++, elm++) {
++ work.h_dentry = au_h_dptr(dentry, bindex);
++ if (!work.h_dentry)
++ continue;
++
++ err = au_drinfo_construct(&work.fdata, work.h_dentry,
++ &work.allocated);
++ AuTraceErr(err);
++ if (unlikely(err))
++ break;
++
++ work.renameback = au_qstreq(&work.h_dentry->d_name, dst_name);
++ br = au_sbr(sb, bindex);
++ work.whnamelen = sizeof(AUFS_WH_DR_INFO_PFX) - 1;
++ work.whnamelen += au_drinfo_name(br, work.infoname,
++ work.infonamelen);
++ AuDbg("whname %.*s, i%llu, %.*s\n",
++ work.whnamelen, work.whname,
++ be64_to_cpu((__force __be64)work.fdata->drinfo.ino),
++ work.fdata->drinfo.oldnamelen,
++ work.fdata->drinfo.oldname);
++
++ err = au_drinfo_store_sio(&work, elm);
++ AuTraceErr(err);
++ if (unlikely(err))
++ break;
++ }
++ if (unlikely(err)) {
++ /* revert all drinfo */
++ au_drinfo_store_rev(rev, &work);
++ au_kfree_try_rcu(rev);
++ *p = NULL;
++ }
++ au_hn_inode_unlock(hdir);
++
++out_args:
++ au_drinfo_store_work_fin(&work);
++out:
++ return err;
++}
++
++/* ---------------------------------------------------------------------- */
++
++int au_dr_rename(struct dentry *src, aufs_bindex_t bindex,
++ struct qstr *dst_name, void *_rev)
++{
++ int err, already;
++ ino_t ino;
++ struct super_block *sb;
++ struct au_branch *br;
++ struct au_dr_br *dr;
++ struct dentry *h_dentry;
++ struct inode *h_inode;
++ struct au_dr_hino *ent;
++ struct au_drinfo_rev *rev, **p;
++
++ AuDbg("bindex %d\n", bindex);
++
++ err = -ENOMEM;
++ ent = kmalloc(sizeof(*ent), GFP_NOFS);
++ if (unlikely(!ent))
++ goto out;
++
++ sb = src->d_sb;
++ br = au_sbr(sb, bindex);
++ dr = &br->br_dirren;
++ h_dentry = au_h_dptr(src, bindex);
++ h_inode = d_inode(h_dentry);
++ ino = h_inode->i_ino;
++ ent->dr_h_ino = ino;
++ already = au_dr_hino_test_add(dr, ino, ent);
++ AuDbg("b%d, hi%llu, already %d\n",
++ bindex, (unsigned long long)ino, already);
++
++ err = au_drinfo_store(src, bindex, dst_name, _rev);
++ AuTraceErr(err);
++ if (!err) {
++ p = _rev;
++ rev = *p;
++ rev->already = already;
++ goto out; /* success */
++ }
++
++ /* revert */
++ if (!already)
++ au_dr_hino_del(dr, ent);
++ au_kfree_rcu(ent);
++
++out:
++ AuTraceErr(err);
++ return err;
++}
++
++void au_dr_rename_fin(struct dentry *src, aufs_bindex_t btgt, void *_rev)
++{
++ struct au_drinfo_rev *rev;
++ struct au_drinfo_rev_elm *elm;
++ int nelm;
++
++ rev = _rev;
++ elm = rev->elm;
++ for (nelm = rev->nelm; nelm > 0; nelm--, elm++) {
++ dput(elm->info_dentry);
++ au_kfree_rcu(elm->info_last);
++ }
++ au_kfree_try_rcu(rev);
++}
++
++void au_dr_rename_rev(struct dentry *src, aufs_bindex_t btgt, void *_rev)
++{
++ int err;
++ struct au_drinfo_store work;
++ struct au_drinfo_rev *rev = _rev;
++ struct super_block *sb;
++ struct au_branch *br;
++ struct inode *h_inode;
++ struct au_dr_br *dr;
++ struct au_dr_hino *ent;
++
++ err = au_drinfo_store_work_init(&work, btgt);
++ if (unlikely(err))
++ goto out;
++
++ sb = src->d_sb;
++ br = au_sbr(sb, btgt);
++ work.h_ppath.dentry = au_h_dptr(src, btgt);
++ work.h_ppath.mnt = au_br_mnt(br);
++ au_drinfo_store_rev(rev, &work);
++ au_drinfo_store_work_fin(&work);
++ if (rev->already)
++ goto out;
++
++ dr = &br->br_dirren;
++ h_inode = d_inode(work.h_ppath.dentry);
++ ent = au_dr_hino_find(dr, h_inode->i_ino);
++ BUG_ON(!ent);
++ au_dr_hino_del(dr, ent);
++ au_kfree_rcu(ent);
++
++out:
++ au_kfree_try_rcu(rev);
++ if (unlikely(err))
++ pr_err("failed to remove dirren info\n");
++}
++
++/* ---------------------------------------------------------------------- */
++
++static struct au_drinfo *au_drinfo_do_load(struct path *h_ppath,
++ char *whname, int whnamelen,
++ struct dentry **info_dentry)
++{
++ struct au_drinfo *drinfo;
++ struct file *f;
++ struct inode *h_dir;
++ struct path infopath;
++ int unlocked;
++
++ AuDbg("%pd/%.*s\n", h_ppath->dentry, whnamelen, whname);
++
++ *info_dentry = NULL;
++ drinfo = NULL;
++ unlocked = 0;
++ h_dir = d_inode(h_ppath->dentry);
++ inode_lock_shared_nested(h_dir, AuLsc_I_PARENT);
++ infopath.dentry = vfsub_lookup_one_len(whname, h_ppath->dentry,
++ whnamelen);
++ if (IS_ERR(infopath.dentry)) {
++ drinfo = (void *)infopath.dentry;
++ goto out;
++ }
++
++ if (d_is_negative(infopath.dentry))
++ goto out_dput; /* success */
++
++ infopath.mnt = h_ppath->mnt;
++ f = vfsub_dentry_open(&infopath, O_RDONLY);
++ inode_unlock_shared(h_dir);
++ unlocked = 1;
++ if (IS_ERR(f)) {
++ drinfo = (void *)f;
++ goto out_dput;
++ }
++
++ drinfo = au_drinfo_read_k(f, /*h_ino*/0);
++ if (IS_ERR_OR_NULL(drinfo))
++ goto out_fput;
++
++ AuDbg("oldname %.*s\n", drinfo->oldnamelen, drinfo->oldname);
++ *info_dentry = dget(infopath.dentry); /* keep it alive */
++
++out_fput:
++ fput(f);
++out_dput:
++ dput(infopath.dentry);
++out:
++ if (!unlocked)
++ inode_unlock_shared(h_dir);
++ AuTraceErrPtr(drinfo);
++ return drinfo;
++}
++
++struct au_drinfo_do_load_args {
++ struct au_drinfo **drinfop;
++ struct path *h_ppath;
++ char *whname;
++ int whnamelen;
++ struct dentry **info_dentry;
++};
++
++static void au_call_drinfo_do_load(void *args)
++{
++ struct au_drinfo_do_load_args *a = args;
++
++ *a->drinfop = au_drinfo_do_load(a->h_ppath, a->whname, a->whnamelen,
++ a->info_dentry);
++}
++
++struct au_drinfo_load {
++ struct path h_ppath;
++ struct qstr *qname;
++ unsigned char no_sio;
++
++ aufs_bindex_t ninfo;
++ struct au_drinfo **drinfo;
++};
++
++static int au_drinfo_load(struct au_drinfo_load *w, aufs_bindex_t bindex,
++ struct au_branch *br)
++{
++ int err, wkq_err, whnamelen, e;
++ char whname[sizeof(AUFS_WH_DR_INFO_PFX) + AUFS_DIRREN_ENV_VAL_SZ]
++ = AUFS_WH_DR_INFO_PFX;
++ struct au_drinfo *drinfo;
++ struct qstr oldname;
++ struct inode *h_dir, *delegated;
++ struct dentry *info_dentry;
++ struct path infopath;
++
++ whnamelen = sizeof(AUFS_WH_DR_INFO_PFX) - 1;
++ whnamelen += au_drinfo_name(br, whname + whnamelen,
++ sizeof(whname) - whnamelen);
++ if (w->no_sio)
++ drinfo = au_drinfo_do_load(&w->h_ppath, whname, whnamelen,
++ &info_dentry);
++ else {
++ struct au_drinfo_do_load_args args = {
++ .drinfop = &drinfo,
++ .h_ppath = &w->h_ppath,
++ .whname = whname,
++ .whnamelen = whnamelen,
++ .info_dentry = &info_dentry
++ };
++ wkq_err = au_wkq_wait(au_call_drinfo_do_load, &args);
++ if (unlikely(wkq_err))
++ drinfo = ERR_PTR(wkq_err);
++ }
++ err = PTR_ERR(drinfo);
++ if (IS_ERR_OR_NULL(drinfo))
++ goto out;
++
++ err = 0;
++ oldname.len = drinfo->oldnamelen;
++ oldname.name = drinfo->oldname;
++ if (au_qstreq(w->qname, &oldname)) {
++ /* the name is renamed back */
++ au_kfree_rcu(drinfo);
++ drinfo = NULL;
++
++ infopath.dentry = info_dentry;
++ infopath.mnt = w->h_ppath.mnt;
++ h_dir = d_inode(w->h_ppath.dentry);
++ delegated = NULL;
++ inode_lock_nested(h_dir, AuLsc_I_PARENT);
++ e = vfsub_unlink(h_dir, &infopath, &delegated, !w->no_sio);
++ inode_unlock(h_dir);
++ if (unlikely(e))
++ AuIOErr("ignored %d, %pd2\n", e, &infopath.dentry);
++ if (unlikely(e == -EWOULDBLOCK))
++ iput(delegated);
++ }
++ au_kfree_rcu(w->drinfo[bindex]);
++ w->drinfo[bindex] = drinfo;
++ dput(info_dentry);
++
++out:
++ AuTraceErr(err);
++ return err;
++}
++
++/* ---------------------------------------------------------------------- */
++
++static void au_dr_lkup_free(struct au_drinfo **drinfo, int n)
++{
++ struct au_drinfo **p = drinfo;
++
++ while (n-- > 0)
++ au_kfree_rcu(*drinfo++);
++ au_kfree_try_rcu(p);
++}
++
++int au_dr_lkup(struct au_do_lookup_args *lkup, struct dentry *dentry,
++ aufs_bindex_t btgt)
++{
++ int err, ninfo;
++ struct au_drinfo_load w;
++ aufs_bindex_t bindex, bbot;
++ struct au_branch *br;
++ struct inode *h_dir;
++ struct au_dr_hino *ent;
++ struct super_block *sb;
++
++ AuDbg("%.*s, name %.*s, whname %.*s, b%d\n",
++ AuLNPair(&dentry->d_name), AuLNPair(&lkup->dirren.dr_name),
++ AuLNPair(&lkup->whname), btgt);
++
++ sb = dentry->d_sb;
++ bbot = au_sbbot(sb);
++ w.ninfo = bbot + 1;
++ if (!lkup->dirren.drinfo) {
++ lkup->dirren.drinfo = kcalloc(w.ninfo,
++ sizeof(*lkup->dirren.drinfo),
++ GFP_NOFS);
++ if (unlikely(!lkup->dirren.drinfo)) {
++ err = -ENOMEM;
++ goto out;
++ }
++ lkup->dirren.ninfo = w.ninfo;
++ }
++ w.drinfo = lkup->dirren.drinfo;
++ w.no_sio = !!uid_eq(current_fsuid(), GLOBAL_ROOT_UID);
++ w.h_ppath.dentry = au_h_dptr(dentry, btgt);
++ AuDebugOn(!w.h_ppath.dentry);
++ w.h_ppath.mnt = au_sbr_mnt(sb, btgt);
++ w.qname = &dentry->d_name;
++
++ ninfo = 0;
++ for (bindex = btgt + 1; bindex <= bbot; bindex++) {
++ br = au_sbr(sb, bindex);
++ err = au_drinfo_load(&w, bindex, br);
++ if (unlikely(err))
++ goto out_free;
++ if (w.drinfo[bindex])
++ ninfo++;
++ }
++ if (!ninfo) {
++ br = au_sbr(sb, btgt);
++ h_dir = d_inode(w.h_ppath.dentry);
++ ent = au_dr_hino_find(&br->br_dirren, h_dir->i_ino);
++ AuDebugOn(!ent);
++ au_dr_hino_del(&br->br_dirren, ent);
++ au_kfree_rcu(ent);
++ }
++ goto out; /* success */
++
++out_free:
++ au_dr_lkup_free(lkup->dirren.drinfo, lkup->dirren.ninfo);
++ lkup->dirren.ninfo = 0;
++ lkup->dirren.drinfo = NULL;
++out:
++ AuTraceErr(err);
++ return err;
++}
++
++void au_dr_lkup_fin(struct au_do_lookup_args *lkup)
++{
++ au_dr_lkup_free(lkup->dirren.drinfo, lkup->dirren.ninfo);
++}
++
++int au_dr_lkup_name(struct au_do_lookup_args *lkup, aufs_bindex_t btgt)
++{
++ int err;
++ struct au_drinfo *drinfo;
++
++ err = 0;
++ if (!lkup->dirren.drinfo)
++ goto out;
++ AuDebugOn(lkup->dirren.ninfo < btgt + 1);
++ drinfo = lkup->dirren.drinfo[btgt + 1];
++ if (!drinfo)
++ goto out;
++
++ au_kfree_try_rcu(lkup->whname.name);
++ lkup->whname.name = NULL;
++ lkup->dirren.dr_name.len = drinfo->oldnamelen;
++ lkup->dirren.dr_name.name = drinfo->oldname;
++ lkup->name = &lkup->dirren.dr_name;
++ err = au_wh_name_alloc(&lkup->whname, lkup->name);
++ if (!err)
++ AuDbg("name %.*s, whname %.*s, b%d\n",
++ AuLNPair(lkup->name), AuLNPair(&lkup->whname),
++ btgt);
++
++out:
++ AuTraceErr(err);
++ return err;
++}
++
++int au_dr_lkup_h_ino(struct au_do_lookup_args *lkup, aufs_bindex_t bindex,
++ ino_t h_ino)
++{
++ int match;
++ struct au_drinfo *drinfo;
++
++ match = 1;
++ if (!lkup->dirren.drinfo)
++ goto out;
++ AuDebugOn(lkup->dirren.ninfo < bindex + 1);
++ drinfo = lkup->dirren.drinfo[bindex + 1];
++ if (!drinfo)
++ goto out;
++
++ match = (drinfo->ino == h_ino);
++ AuDbg("match %d\n", match);
++
++out:
++ return match;
++}
++
++/* ---------------------------------------------------------------------- */
++
++int au_dr_opt_set(struct super_block *sb)
++{
++ int err;
++ aufs_bindex_t bindex, bbot;
++ struct au_branch *br;
++
++ err = 0;
++ bbot = au_sbbot(sb);
++ for (bindex = 0; !err && bindex <= bbot; bindex++) {
++ br = au_sbr(sb, bindex);
++ err = au_dr_hino(sb, bindex, /*br*/NULL, &br->br_path);
++ }
++
++ return err;
++}
++
++int au_dr_opt_flush(struct super_block *sb)
++{
++ int err;
++ aufs_bindex_t bindex, bbot;
++ struct au_branch *br;
++
++ err = 0;
++ bbot = au_sbbot(sb);
++ for (bindex = 0; !err && bindex <= bbot; bindex++) {
++ br = au_sbr(sb, bindex);
++ if (au_br_writable(br->br_perm))
++ err = au_dr_hino(sb, bindex, /*br*/NULL, /*path*/NULL);
++ }
++
++ return err;
++}
++
++int au_dr_opt_clr(struct super_block *sb, int no_flush)
++{
++ int err;
++ aufs_bindex_t bindex, bbot;
++ struct au_branch *br;
++
++ err = 0;
++ if (!no_flush) {
++ err = au_dr_opt_flush(sb);
++ if (unlikely(err))
++ goto out;
++ }
++
++ bbot = au_sbbot(sb);
++ for (bindex = 0; bindex <= bbot; bindex++) {
++ br = au_sbr(sb, bindex);
++ au_dr_hino_free(&br->br_dirren);
++ }
++
++out:
++ return err;
++}
+diff -urN /usr/share/empty/fs/aufs/dirren.h linux/fs/aufs/dirren.h
+--- /usr/share/empty/fs/aufs/dirren.h 1970-01-01 01:00:00.000000000 +0100
++++ linux/fs/aufs/dirren.h 2019-03-05 12:13:00.139224339 +0100
+@@ -0,0 +1,140 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/*
++ * Copyright (C) 2017-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/>.
++ */
++
++/*
++ * renamed dir info
++ */
++
++#ifndef __AUFS_DIRREN_H__
++#define __AUFS_DIRREN_H__
++
++#ifdef __KERNEL__
++
++#include <linux/dcache.h>
++#include <linux/statfs.h>
++#include <linux/uuid.h>
++#include "hbl.h"
++
++#define AuDirren_NHASH 100
++
++#ifdef CONFIG_AUFS_DIRREN
++enum au_brid_type {
++ AuBrid_Unset,
++ AuBrid_UUID,
++ AuBrid_FSID,
++ AuBrid_DEV
++};
++
++struct au_dr_brid {
++ enum au_brid_type type;
++ union {
++ uuid_t uuid; /* unimplemented yet */
++ fsid_t fsid;
++ dev_t dev;
++ };
++};
++
++/* 20 is the max digits length of ulong 64 */
++/* brid-type "_" uuid "_" inum */
++#define AUFS_DIRREN_FNAME_SZ (1 + 1 + UUID_STRING_LEN + 20)
++#define AUFS_DIRREN_ENV_VAL_SZ (AUFS_DIRREN_FNAME_SZ + 1 + 20)
++
++struct au_dr_hino {
++ struct hlist_bl_node dr_hnode;
++ ino_t dr_h_ino;
++};
++
++struct au_dr_br {
++ struct hlist_bl_head dr_h_ino[AuDirren_NHASH];
++ struct au_dr_brid dr_brid;
++};
++
++struct au_dr_lookup {
++ /* dr_name is pointed by struct au_do_lookup_args.name */
++ struct qstr dr_name; /* subset of dr_info */
++ aufs_bindex_t ninfo;
++ struct au_drinfo **drinfo;
++};
++#else
++struct au_dr_hino;
++/* empty */
++struct au_dr_br { };
++struct au_dr_lookup { };
++#endif
++
++/* ---------------------------------------------------------------------- */
++
++struct au_branch;
++struct au_do_lookup_args;
++struct au_hinode;
++#ifdef CONFIG_AUFS_DIRREN
++int au_dr_hino_test_add(struct au_dr_br *dr, ino_t h_ino,
++ struct au_dr_hino *add_ent);
++void au_dr_hino_free(struct au_dr_br *dr);
++int au_dr_br_init(struct super_block *sb, struct au_branch *br,
++ const struct path *path);
++int au_dr_br_fin(struct super_block *sb, struct au_branch *br);
++int au_dr_rename(struct dentry *src, aufs_bindex_t bindex,
++ struct qstr *dst_name, void *_rev);
++void au_dr_rename_fin(struct dentry *src, aufs_bindex_t btgt, void *rev);
++void au_dr_rename_rev(struct dentry *src, aufs_bindex_t bindex, void *rev);
++int au_dr_lkup(struct au_do_lookup_args *lkup, struct dentry *dentry,
++ aufs_bindex_t bindex);
++int au_dr_lkup_name(struct au_do_lookup_args *lkup, aufs_bindex_t btgt);
++int au_dr_lkup_h_ino(struct au_do_lookup_args *lkup, aufs_bindex_t bindex,
++ ino_t h_ino);
++void au_dr_lkup_fin(struct au_do_lookup_args *lkup);
++int au_dr_opt_set(struct super_block *sb);
++int au_dr_opt_flush(struct super_block *sb);
++int au_dr_opt_clr(struct super_block *sb, int no_flush);
++#else
++AuStubInt0(au_dr_hino_test_add, struct au_dr_br *dr, ino_t h_ino,
++ struct au_dr_hino *add_ent);
++AuStubVoid(au_dr_hino_free, struct au_dr_br *dr);
++AuStubInt0(au_dr_br_init, struct super_block *sb, struct au_branch *br,
++ const struct path *path);
++AuStubInt0(au_dr_br_fin, struct super_block *sb, struct au_branch *br);
++AuStubInt0(au_dr_rename, struct dentry *src, aufs_bindex_t bindex,
++ struct qstr *dst_name, void *_rev);
++AuStubVoid(au_dr_rename_fin, struct dentry *src, aufs_bindex_t btgt, void *rev);
++AuStubVoid(au_dr_rename_rev, struct dentry *src, aufs_bindex_t bindex,
++ void *rev);
++AuStubInt0(au_dr_lkup, struct au_do_lookup_args *lkup, struct dentry *dentry,
++ aufs_bindex_t bindex);
++AuStubInt0(au_dr_lkup_name, struct au_do_lookup_args *lkup, aufs_bindex_t btgt);
++AuStubInt0(au_dr_lkup_h_ino, struct au_do_lookup_args *lkup,
++ aufs_bindex_t bindex, ino_t h_ino);
++AuStubVoid(au_dr_lkup_fin, struct au_do_lookup_args *lkup);
++AuStubInt0(au_dr_opt_set, struct super_block *sb);
++AuStubInt0(au_dr_opt_flush, struct super_block *sb);
++AuStubInt0(au_dr_opt_clr, struct super_block *sb, int no_flush);
++#endif
++
++/* ---------------------------------------------------------------------- */
++
++#ifdef CONFIG_AUFS_DIRREN
++static inline int au_dr_ihash(ino_t h_ino)
++{
++ return h_ino % AuDirren_NHASH;
++}
++#else
++AuStubInt0(au_dr_ihash, ino_t h_ino);
++#endif
++
++#endif /* __KERNEL__ */
++#endif /* __AUFS_DIRREN_H__ */
+diff -urN /usr/share/empty/fs/aufs/dynop.c linux/fs/aufs/dynop.c
+--- /usr/share/empty/fs/aufs/dynop.c 1970-01-01 01:00:00.000000000 +0100
++++ linux/fs/aufs/dynop.c 2019-07-08 09:31:38.324384703 +0200
+@@ -0,0 +1,370 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (C) 2010-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/>.
++ */
++
++/*
++ * dynamically customizable operations for regular files
++ */
++
++#include "aufs.h"
++
++#define DyPrSym(key) AuDbgSym(key->dk_op.dy_hop)
++
++/*
++ * How large will these lists be?
++ * Usually just a few elements, 20-30 at most for each, I guess.
++ */
++static struct hlist_bl_head dynop[AuDyLast];
++
++static struct au_dykey *dy_gfind_get(struct hlist_bl_head *hbl,
++ const void *h_op)
++{
++ struct au_dykey *key, *tmp;
++ struct hlist_bl_node *pos;
++
++ key = NULL;
++ hlist_bl_lock(hbl);
++ hlist_bl_for_each_entry(tmp, pos, hbl, dk_hnode)
++ if (tmp->dk_op.dy_hop == h_op) {
++ if (kref_get_unless_zero(&tmp->dk_kref))
++ key = tmp;
++ break;
++ }
++ hlist_bl_unlock(hbl);
++
++ return key;
++}
++
++static struct au_dykey *dy_bradd(struct au_branch *br, struct au_dykey *key)
++{
++ struct au_dykey **k, *found;
++ const void *h_op = key->dk_op.dy_hop;
++ int i;
++
++ found = NULL;
++ k = br->br_dykey;
++ for (i = 0; i < AuBrDynOp; i++)
++ if (k[i]) {
++ if (k[i]->dk_op.dy_hop == h_op) {
++ found = k[i];
++ break;
++ }
++ } else
++ break;
++ if (!found) {
++ spin_lock(&br->br_dykey_lock);
++ for (; i < AuBrDynOp; i++)
++ if (k[i]) {
++ if (k[i]->dk_op.dy_hop == h_op) {
++ found = k[i];
++ break;
++ }
++ } else {
++ k[i] = key;
++ break;
++ }
++ spin_unlock(&br->br_dykey_lock);
++ BUG_ON(i == AuBrDynOp); /* expand the array */
++ }
++
++ return found;
++}
++
++/* kref_get() if @key is already added */
++static struct au_dykey *dy_gadd(struct hlist_bl_head *hbl, struct au_dykey *key)
++{
++ struct au_dykey *tmp, *found;
++ struct hlist_bl_node *pos;
++ const void *h_op = key->dk_op.dy_hop;
++
++ found = NULL;
++ hlist_bl_lock(hbl);
++ hlist_bl_for_each_entry(tmp, pos, hbl, dk_hnode)
++ if (tmp->dk_op.dy_hop == h_op) {
++ if (kref_get_unless_zero(&tmp->dk_kref))
++ found = tmp;
++ break;
++ }
++ if (!found)
++ hlist_bl_add_head(&key->dk_hnode, hbl);
++ hlist_bl_unlock(hbl);
++
++ if (!found)
++ DyPrSym(key);
++ return found;
++}
++
++static void dy_free_rcu(struct rcu_head *rcu)
++{
++ struct au_dykey *key;
++
++ key = container_of(rcu, struct au_dykey, dk_rcu);
++ DyPrSym(key);
++ kfree(key);
++}
++
++static void dy_free(struct kref *kref)
++{
++ struct au_dykey *key;
++ struct hlist_bl_head *hbl;
++
++ key = container_of(kref, struct au_dykey, dk_kref);
++ hbl = dynop + key->dk_op.dy_type;
++ au_hbl_del(&key->dk_hnode, hbl);
++ call_rcu(&key->dk_rcu, dy_free_rcu);
++}
++
++void au_dy_put(struct au_dykey *key)
++{
++ kref_put(&key->dk_kref, dy_free);
++}
++
++/* ---------------------------------------------------------------------- */
++
++#define DyDbgSize(cnt, op) AuDebugOn(cnt != sizeof(op)/sizeof(void *))
++
++#ifdef CONFIG_AUFS_DEBUG
++#define DyDbgDeclare(cnt) unsigned int cnt = 0
++#define DyDbgInc(cnt) do { cnt++; } while (0)
++#else
++#define DyDbgDeclare(cnt) do {} while (0)
++#define DyDbgInc(cnt) do {} while (0)
++#endif
++
++#define DySet(func, dst, src, h_op, h_sb) do { \
++ DyDbgInc(cnt); \
++ if (h_op->func) { \
++ if (src.func) \
++ dst.func = src.func; \
++ else \
++ AuDbg("%s %s\n", au_sbtype(h_sb), #func); \
++ } \
++} while (0)
++
++#define DySetForce(func, dst, src) do { \
++ AuDebugOn(!src.func); \
++ DyDbgInc(cnt); \
++ dst.func = src.func; \
++} while (0)
++
++#define DySetAop(func) \
++ DySet(func, dyaop->da_op, aufs_aop, h_aop, h_sb)
++#define DySetAopForce(func) \
++ DySetForce(func, dyaop->da_op, aufs_aop)
++
++static void dy_aop(struct au_dykey *key, const void *h_op,
++ struct super_block *h_sb __maybe_unused)
++{
++ struct au_dyaop *dyaop = (void *)key;
++ const struct address_space_operations *h_aop = h_op;
++ DyDbgDeclare(cnt);
++
++ AuDbg("%s\n", au_sbtype(h_sb));
++
++ DySetAop(writepage);
++ DySetAopForce(readpage); /* force */
++ DySetAop(writepages);
++ DySetAop(set_page_dirty);
++ DySetAop(readpages);
++ DySetAop(write_begin);
++ DySetAop(write_end);
++ DySetAop(bmap);
++ DySetAop(invalidatepage);
++ DySetAop(releasepage);
++ DySetAop(freepage);
++ /* this one will be changed according to an aufs mount option */
++ DySetAop(direct_IO);
++ DySetAop(migratepage);
++ DySetAop(isolate_page);
++ DySetAop(putback_page);
++ DySetAop(launder_page);
++ DySetAop(is_partially_uptodate);
++ DySetAop(is_dirty_writeback);
++ DySetAop(error_remove_page);
++ DySetAop(swap_activate);
++ DySetAop(swap_deactivate);
++
++ DyDbgSize(cnt, *h_aop);
++}
++
++/* ---------------------------------------------------------------------- */
++
++static void dy_bug(struct kref *kref)
++{
++ BUG();
++}
++
++static struct au_dykey *dy_get(struct au_dynop *op, struct au_branch *br)
++{
++ struct au_dykey *key, *old;
++ struct hlist_bl_head *hbl;
++ struct op {
++ unsigned int sz;
++ void (*set)(struct au_dykey *key, const void *h_op,
++ struct super_block *h_sb __maybe_unused);
++ };
++ static const struct op a[] = {
++ [AuDy_AOP] = {
++ .sz = sizeof(struct au_dyaop),
++ .set = dy_aop
++ }
++ };
++ const struct op *p;
++
++ hbl = dynop + op->dy_type;
++ key = dy_gfind_get(hbl, op->dy_hop);
++ if (key)
++ goto out_add; /* success */
++
++ p = a + op->dy_type;
++ key = kzalloc(p->sz, GFP_NOFS);
++ if (unlikely(!key)) {
++ key = ERR_PTR(-ENOMEM);
++ goto out;
++ }
++
++ key->dk_op.dy_hop = op->dy_hop;
++ kref_init(&key->dk_kref);
++ p->set(key, op->dy_hop, au_br_sb(br));
++ old = dy_gadd(hbl, key);
++ if (old) {
++ au_kfree_rcu(key);
++ key = old;
++ }
++
++out_add:
++ old = dy_bradd(br, key);
++ if (old)
++ /* its ref-count should never be zero here */
++ kref_put(&key->dk_kref, dy_bug);
++out:
++ return key;
++}
++
++/* ---------------------------------------------------------------------- */
++/*
++ * Aufs prohibits O_DIRECT by default even if the branch supports it.
++ * This behaviour is necessary to return an error from open(O_DIRECT) instead
++ * of the succeeding I/O. The dio mount option enables O_DIRECT and makes
++ * open(O_DIRECT) always succeed, but the succeeding I/O may return an error.
++ * See the aufs manual in detail.
++ */
++static void dy_adx(struct au_dyaop *dyaop, int do_dx)
++{
++ if (!do_dx)
++ dyaop->da_op.direct_IO = NULL;
++ else
++ dyaop->da_op.direct_IO = aufs_aop.direct_IO;
++}
++
++static struct au_dyaop *dy_aget(struct au_branch *br,
++ const struct address_space_operations *h_aop,
++ int do_dx)
++{
++ struct au_dyaop *dyaop;
++ struct au_dynop op;
++
++ op.dy_type = AuDy_AOP;
++ op.dy_haop = h_aop;
++ dyaop = (void *)dy_get(&op, br);
++ if (IS_ERR(dyaop))
++ goto out;
++ dy_adx(dyaop, do_dx);
++
++out:
++ return dyaop;
++}
++
++int au_dy_iaop(struct inode *inode, aufs_bindex_t bindex,
++ struct inode *h_inode)
++{
++ int err, do_dx;
++ struct super_block *sb;
++ struct au_branch *br;
++ struct au_dyaop *dyaop;
++
++ AuDebugOn(!S_ISREG(h_inode->i_mode));
++ IiMustWriteLock(inode);
++
++ sb = inode->i_sb;
++ br = au_sbr(sb, bindex);
++ do_dx = !!au_opt_test(au_mntflags(sb), DIO);
++ dyaop = dy_aget(br, h_inode->i_mapping->a_ops, do_dx);
++ err = PTR_ERR(dyaop);
++ if (IS_ERR(dyaop))
++ /* unnecessary to call dy_fput() */
++ goto out;
++
++ err = 0;
++ inode->i_mapping->a_ops = &dyaop->da_op;
++
++out:
++ return err;
++}
++
++/*
++ * Is it safe to replace a_ops during the inode/file is in operation?
++ * Yes, I hope so.
++ */
++int au_dy_irefresh(struct inode *inode)
++{
++ int err;
++ aufs_bindex_t btop;
++ struct inode *h_inode;
++
++ err = 0;
++ if (S_ISREG(inode->i_mode)) {
++ btop = au_ibtop(inode);
++ h_inode = au_h_iptr(inode, btop);
++ err = au_dy_iaop(inode, btop, h_inode);
++ }
++ return err;
++}
++
++void au_dy_arefresh(int do_dx)
++{
++ struct hlist_bl_head *hbl;
++ struct hlist_bl_node *pos;
++ struct au_dykey *key;
++
++ hbl = dynop + AuDy_AOP;
++ hlist_bl_lock(hbl);
++ hlist_bl_for_each_entry(key, pos, hbl, dk_hnode)
++ dy_adx((void *)key, do_dx);
++ hlist_bl_unlock(hbl);
++}
++
++/* ---------------------------------------------------------------------- */
++
++void __init au_dy_init(void)
++{
++ int i;
++
++ /* make sure that 'struct au_dykey *' can be any type */
++ BUILD_BUG_ON(offsetof(struct au_dyaop, da_key));
++
++ for (i = 0; i < AuDyLast; i++)
++ INIT_HLIST_BL_HEAD(dynop + i);
++}
++
++void au_dy_fin(void)
++{
++ int i;
++
++ for (i = 0; i < AuDyLast; i++)
++ WARN_ON(!hlist_bl_empty(dynop + i));
++}
+diff -urN /usr/share/empty/fs/aufs/dynop.h linux/fs/aufs/dynop.h
+--- /usr/share/empty/fs/aufs/dynop.h 1970-01-01 01:00:00.000000000 +0100
++++ linux/fs/aufs/dynop.h 2019-03-05 12:13:00.139224339 +0100
+@@ -0,0 +1,75 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/*
++ * Copyright (C) 2010-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/>.
++ */
++
++/*
++ * dynamically customizable operations (for regular files only)
++ */
++
++#ifndef __AUFS_DYNOP_H__
++#define __AUFS_DYNOP_H__
++
++#ifdef __KERNEL__
++
++#include <linux/fs.h>
++#include <linux/kref.h>