Пример #1
0
static int au_file_refresh_by_inode(struct file *file, int *need_reopen)
{
	int err;
	struct au_pin pin;
	struct au_finfo *finfo;
	struct dentry *parent, *hi_wh;
	struct inode *inode;
	struct super_block *sb;
	struct au_cp_generic cpg = {
		.dentry	= file->f_path.dentry,
		.bdst	= -1,
		.bsrc	= -1,
		.len	= -1,
		.pin	= &pin,
		.flags	= AuCpup_DTIME
	};

	FiMustWriteLock(file);

	err = 0;
	finfo = au_fi(file);
	sb = cpg.dentry->d_sb;
	inode = cpg.dentry->d_inode;
	cpg.bdst = au_ibstart(inode);
	if (cpg.bdst == finfo->fi_btop || IS_ROOT(cpg.dentry))
		goto out;

	parent = dget_parent(cpg.dentry);
	if (au_test_ro(sb, cpg.bdst, inode)) {
		di_read_lock_parent(parent, !AuLock_IR);
		err = AuWbrCopyup(au_sbi(sb), cpg.dentry);
		cpg.bdst = err;
		di_read_unlock(parent, !AuLock_IR);
		if (unlikely(err < 0))
			goto out_parent;
		err = 0;
	}

	di_read_lock_parent(parent, AuLock_IR);
	hi_wh = au_hi_wh(inode, cpg.bdst);
	if (!S_ISDIR(inode->i_mode)
	    && au_opt_test(au_mntflags(sb), PLINK)
	    && au_plink_test(inode)
	    && !d_unhashed(cpg.dentry)
	    && cpg.bdst < au_dbstart(cpg.dentry)) {
		err = au_test_and_cpup_dirs(cpg.dentry, cpg.bdst);
		if (unlikely(err))
			goto out_unlock;

		/* always superio. */
		err = au_pin(&pin, cpg.dentry, cpg.bdst, AuOpt_UDBA_NONE,
			     AuPin_DI_LOCKED | AuPin_MNT_WRITE);
		if (!err) {
			err = au_sio_cpup_simple(&cpg);
			au_unpin(&pin);
		}
	} else if (hi_wh) {
		/* already copied-up after unlink */
		err = au_reopen_wh(file, cpg.bdst, hi_wh);
		*need_reopen = 0;
	}

out_unlock:
	di_read_unlock(parent, AuLock_IR);
out_parent:
	dput(parent);
out:
	return err;
}

static void au_do_refresh_dir(struct file *file)
{
	aufs_bindex_t bindex, bend, new_bindex, brid;
	struct au_hfile *p, tmp, *q;
	struct au_finfo *finfo;
	struct super_block *sb;
	struct au_fidir *fidir;

	FiMustWriteLock(file);

	sb = file->f_path.dentry->d_sb;
	finfo = au_fi(file);
	fidir = finfo->fi_hdir;
	AuDebugOn(!fidir);
	p = fidir->fd_hfile + finfo->fi_btop;
	brid = p->hf_br->br_id;
	bend = fidir->fd_bbot;
	for (bindex = finfo->fi_btop; bindex <= bend; bindex++, p++) {
		if (!p->hf_file)
			continue;

		new_bindex = au_br_index(sb, p->hf_br->br_id);
		if (new_bindex == bindex)
			continue;
		if (new_bindex < 0) {
			au_set_h_fptr(file, bindex, NULL);
			continue;
		}

		/* swap two lower inode, and loop again */
		q = fidir->fd_hfile + new_bindex;
		tmp = *q;
		*q = *p;
		*p = tmp;
		if (tmp.hf_file) {
			bindex--;
			p--;
		}
	}

	p = fidir->fd_hfile;
	if (!au_test_mmapped(file) && !d_unlinked(file->f_path.dentry)) {
		bend = au_sbend(sb);
		for (finfo->fi_btop = 0; finfo->fi_btop <= bend;
		     finfo->fi_btop++, p++)
			if (p->hf_file) {
				if (file_inode(p->hf_file))
					break;
				au_hfput(p, file);
			}
	} else {
		bend = au_br_index(sb, brid);
		for (finfo->fi_btop = 0; finfo->fi_btop < bend;
		     finfo->fi_btop++, p++)
			if (p->hf_file)
				au_hfput(p, file);
		bend = au_sbend(sb);
	}

	p = fidir->fd_hfile + bend;
	for (fidir->fd_bbot = bend; fidir->fd_bbot >= finfo->fi_btop;
	     fidir->fd_bbot--, p--)
		if (p->hf_file) {
			if (file_inode(p->hf_file))
				break;
			au_hfput(p, file);
		}
	AuDebugOn(fidir->fd_bbot < finfo->fi_btop);
}
Пример #2
0
static int au_ready_to_write_wh(struct file *file, loff_t len,
				aufs_bindex_t bcpup, struct au_pin *pin)
{
	int err;
	struct inode *inode, *h_inode;
	struct dentry *h_dentry, *hi_wh;
	struct au_cp_generic cpg = {
		.dentry	= file->f_path.dentry,
		.bdst	= bcpup,
		.bsrc	= -1,
		.len	= len,
		.pin	= pin
	};

	au_update_dbstart(cpg.dentry);
	inode = cpg.dentry->d_inode;
	h_inode = NULL;
	if (au_dbstart(cpg.dentry) <= bcpup
	    && au_dbend(cpg.dentry) >= bcpup) {
		h_dentry = au_h_dptr(cpg.dentry, bcpup);
		if (h_dentry)
			h_inode = h_dentry->d_inode;
	}
	hi_wh = au_hi_wh(inode, bcpup);
	if (!hi_wh && !h_inode)
		err = au_sio_cpup_wh(&cpg, file);
	else
		/* already copied-up after unlink */
		err = au_reopen_wh(file, bcpup, hi_wh);

	if (!err
	    && (inode->i_nlink > 1
		|| (inode->i_state & I_LINKABLE))
	    && au_opt_test(au_mntflags(cpg.dentry->d_sb), PLINK))
		au_plink_append(inode, bcpup, au_h_dptr(cpg.dentry, bcpup));

	return err;
}

/*
 * prepare the @file for writing.
 */
int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin)
{
	int err;
	aufs_bindex_t dbstart;
	struct dentry *parent;
	struct inode *inode;
	struct super_block *sb;
	struct file *h_file;
	struct au_cp_generic cpg = {
		.dentry	= file->f_path.dentry,
		.bdst	= -1,
		.bsrc	= -1,
		.len	= len,
		.pin	= pin,
		.flags	= AuCpup_DTIME
	};

	sb = cpg.dentry->d_sb;
	inode = cpg.dentry->d_inode;
	cpg.bsrc = au_fbstart(file);
	err = au_test_ro(sb, cpg.bsrc, inode);
	if (!err && (au_hf_top(file)->f_mode & FMODE_WRITE)) {
		err = au_pin(pin, cpg.dentry, cpg.bsrc, AuOpt_UDBA_NONE,
			     /*flags*/0);
		goto out;
	}

	/* need to cpup or reopen */
	parent = dget_parent(cpg.dentry);
	di_write_lock_parent(parent);
	err = AuWbrCopyup(au_sbi(sb), cpg.dentry);
	cpg.bdst = err;
	if (unlikely(err < 0))
		goto out_dgrade;
	err = 0;

	if (!d_unhashed(cpg.dentry) && !au_h_dptr(parent, cpg.bdst)) {
		err = au_cpup_dirs(cpg.dentry, cpg.bdst);
		if (unlikely(err))
			goto out_dgrade;
	}

	err = au_pin(pin, cpg.dentry, cpg.bdst, AuOpt_UDBA_NONE,
		     AuPin_DI_LOCKED | AuPin_MNT_WRITE);
	if (unlikely(err))
		goto out_dgrade;

	dbstart = au_dbstart(cpg.dentry);
	if (dbstart <= cpg.bdst)
		cpg.bsrc = cpg.bdst;

	if (dbstart <= cpg.bdst		/* just reopen */
	    || !d_unhashed(cpg.dentry)	/* copyup and reopen */
		) {
		h_file = au_h_open_pre(cpg.dentry, cpg.bsrc, /*force_wr*/0);
		if (IS_ERR(h_file))
			err = PTR_ERR(h_file);
		else {
			di_downgrade_lock(parent, AuLock_IR);
			if (dbstart > cpg.bdst)
				err = au_sio_cpup_simple(&cpg);
			if (!err)
				err = au_reopen_nondir(file);
			au_h_open_post(cpg.dentry, cpg.bsrc, h_file);
		}
	} else {			/* copyup as wh and reopen */
		/*
		 * since writable hfsplus branch is not supported,
		 * h_open_pre/post() are unnecessary.
		 */
		err = au_ready_to_write_wh(file, len, cpg.bdst, pin);
		di_downgrade_lock(parent, AuLock_IR);
	}

	if (!err) {
		au_pin_set_parent_lflag(pin, /*lflag*/0);
		goto out_dput; /* success */
	}
	au_unpin(pin);
	goto out_unlock;

out_dgrade:
	di_downgrade_lock(parent, AuLock_IR);
out_unlock:
	di_read_unlock(parent, AuLock_IR);
out_dput:
	dput(parent);
out:
	return err;
}

/* ---------------------------------------------------------------------- */

int au_do_flush(struct file *file, fl_owner_t id,
		int (*flush)(struct file *file, fl_owner_t id))
{
	int err;
	struct super_block *sb;
	struct inode *inode;

	inode = file_inode(file);
	sb = inode->i_sb;
	si_noflush_read_lock(sb);
	fi_read_lock(file);
	ii_read_lock_child(inode);

	err = flush(file, id);
	au_cpup_attr_timesizes(inode);

	ii_read_unlock(inode);
	fi_read_unlock(file);
	si_read_unlock(sb);
	return err;
}
Пример #3
0
/*
 * initial procedure of adding a new entry.
 * prepare writable branch and the parent dir, lock it,
 * and lookup whiteout for the new entry.
 */
static struct dentry*
lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt,
		  struct dentry *src_dentry, struct au_pin *pin,
		  struct au_wr_dir_args *wr_dir_args)
{
	struct dentry *wh_dentry, *h_parent;
	struct super_block *sb;
	struct au_branch *br;
	int err;
	unsigned int udba;
	aufs_bindex_t bcpup;

	AuDbg("%.*s\n", AuDLNPair(dentry));

	err = au_wr_dir(dentry, src_dentry, wr_dir_args);
	bcpup = err;
	wh_dentry = ERR_PTR(err);
	if (unlikely(err < 0))
		goto out;

	sb = dentry->d_sb;
	udba = au_opt_udba(sb);
	err = au_pin(pin, dentry, bcpup, udba,
		     AuPin_DI_LOCKED | AuPin_MNT_WRITE);
	wh_dentry = ERR_PTR(err);
	if (unlikely(err))
		goto out;

	h_parent = au_pinned_h_parent(pin);
	if (udba != AuOpt_UDBA_NONE
	    && au_dbstart(dentry) == bcpup)
		err = au_may_add(dentry, bcpup, h_parent,
				 au_ftest_wrdir(wr_dir_args->flags, ISDIR));
	else if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN))
		err = -ENAMETOOLONG;
	wh_dentry = ERR_PTR(err);
	if (unlikely(err))
		goto out_unpin;

	br = au_sbr(sb, bcpup);
	if (dt) {
		struct path tmp = {
			.dentry	= h_parent,
			.mnt	= br->br_mnt
		};
		au_dtime_store(dt, au_pinned_parent(pin), &tmp);
	}

	wh_dentry = NULL;
	if (bcpup != au_dbwh(dentry))
		goto out; /* success */

	wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, br);

out_unpin:
	if (IS_ERR(wh_dentry))
		au_unpin(pin);
out:
	return wh_dentry;
}

/* ---------------------------------------------------------------------- */

enum { Mknod, Symlink, Creat };
struct simple_arg {
	int type;
	union {
		struct {
			int mode;
			struct nameidata *nd;
		} c;
		struct {
			const char *symname;
		} s;
		struct {
			int mode;
			dev_t dev;
		} m;
	} u;
};

static int add_simple(struct inode *dir, struct dentry *dentry,
		      struct simple_arg *arg)
{
	int err;
	aufs_bindex_t bstart;
	unsigned char created;
	struct au_dtime dt;
	struct au_pin pin;
	struct path h_path;
	struct dentry *wh_dentry, *parent;
	struct inode *h_dir;
	struct au_wr_dir_args wr_dir_args = {
		.force_btgt	= -1,
		.flags		= AuWrDir_ADD_ENTRY
	};

	AuDbg("%.*s\n", AuDLNPair(dentry));
	IMustLock(dir);

	parent = dentry->d_parent; /* dir inode is locked */
	err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN);
	if (unlikely(err))
		goto out;
	err = au_d_may_add(dentry);
	if (unlikely(err))
		goto out_unlock;
	di_write_lock_parent(parent);
	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL, &pin,
				      &wr_dir_args);
	err = PTR_ERR(wh_dentry);
	if (IS_ERR(wh_dentry))
		goto out_parent;

	bstart = au_dbstart(dentry);
	h_path.dentry = au_h_dptr(dentry, bstart);
	h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart);
	h_dir = au_pinned_h_dir(&pin);
	switch (arg->type) {
	case Creat:
		err = vfsub_create(h_dir, &h_path, arg->u.c.mode);
		break;
	case Symlink:
		err = vfsub_symlink(h_dir, &h_path, arg->u.s.symname);
		break;
	case Mknod:
		err = vfsub_mknod(h_dir, &h_path, arg->u.m.mode, arg->u.m.dev);
		break;
	default:
		BUG();
	}
	created = !err;
	if (!err)
		err = epilog(dir, bstart, wh_dentry, dentry);

	/* revert */
	if (unlikely(created && err && h_path.dentry->d_inode)) {
		int rerr;
		rerr = vfsub_unlink(h_dir, &h_path, /*force*/0);
		if (rerr) {
			AuIOErr("%.*s revert failure(%d, %d)\n",
				AuDLNPair(dentry), err, rerr);
			err = -EIO;
		}
		au_dtime_revert(&dt);
	}

	au_unpin(&pin);
	dput(wh_dentry);

out_parent:
	di_write_unlock(parent);
out_unlock:
	if (unlikely(err)) {
		au_update_dbstart(dentry);
		d_drop(dentry);
	}
	aufs_read_unlock(dentry, AuLock_DW);
out:
	return err;
}

int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
{
	struct simple_arg arg = {
		.type = Mknod,
		.u.m = {
			.mode	= mode,
			.dev	= dev
		}
	};
	return add_simple(dir, dentry, &arg);
}

int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
{
	struct simple_arg arg = {
		.type = Symlink,
		.u.s.symname = symname
	};
	return add_simple(dir, dentry, &arg);
}

int aufs_create(struct inode *dir, struct dentry *dentry, int mode,
		struct nameidata *nd)
{
	struct simple_arg arg = {
		.type = Creat,
		.u.c = {
			.mode	= mode,
			.nd	= nd
		}
	};
	return add_simple(dir, dentry, &arg);
}

/* ---------------------------------------------------------------------- */

struct au_link_args {
	aufs_bindex_t bdst, bsrc;
	struct au_pin pin;
	struct path h_path;
	struct dentry *src_parent, *parent;
};

static int au_cpup_before_link(struct dentry *src_dentry,
			       struct au_link_args *a)
{
	int err;
	struct dentry *h_src_dentry;
	struct mutex *h_mtx;
	struct file *h_file;

	di_read_lock_parent(a->src_parent, AuLock_IR);
	err = au_test_and_cpup_dirs(src_dentry, a->bdst);
	if (unlikely(err))
		goto out;

	h_src_dentry = au_h_dptr(src_dentry, a->bsrc);
	h_mtx = &h_src_dentry->d_inode->i_mutex;
	err = au_pin(&a->pin, src_dentry, a->bdst,
		     au_opt_udba(src_dentry->d_sb),
		     AuPin_DI_LOCKED | AuPin_MNT_WRITE);
	if (unlikely(err))
		goto out;
	mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
	h_file = au_h_open_pre(src_dentry, a->bsrc);
	if (IS_ERR(h_file)) {
		err = PTR_ERR(h_file);
		h_file = NULL;
	} else
		err = au_sio_cpup_simple(src_dentry, a->bdst, -1,
					 AuCpup_DTIME /* | AuCpup_KEEPLINO */);
	mutex_unlock(h_mtx);
	au_h_open_post(src_dentry, a->bsrc, h_file);
	au_unpin(&a->pin);

out:
	di_read_unlock(a->src_parent, AuLock_IR);
	return err;
}

static int au_cpup_or_link(struct dentry *src_dentry, struct au_link_args *a)
{
	int err;
	unsigned char plink;
	struct inode *h_inode, *inode;
	struct dentry *h_src_dentry;
	struct super_block *sb;
	struct file *h_file;

	plink = 0;
	h_inode = NULL;
	sb = src_dentry->d_sb;
	inode = src_dentry->d_inode;
	if (au_ibstart(inode) <= a->bdst)
		h_inode = au_h_iptr(inode, a->bdst);
	if (!h_inode || !h_inode->i_nlink) {
		/* copyup src_dentry as the name of dentry. */
		au_set_dbstart(src_dentry, a->bdst);
		au_set_h_dptr(src_dentry, a->bdst, dget(a->h_path.dentry));
		h_inode = au_h_dptr(src_dentry, a->bsrc)->d_inode;
		mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
		h_file = au_h_open_pre(src_dentry, a->bsrc);
		if (IS_ERR(h_file)) {
			err = PTR_ERR(h_file);
			h_file = NULL;
		} else
			err = au_sio_cpup_single(src_dentry, a->bdst, a->bsrc,
						 -1, AuCpup_KEEPLINO,
						 a->parent);
		mutex_unlock(&h_inode->i_mutex);
		au_h_open_post(src_dentry, a->bsrc, h_file);
		au_set_h_dptr(src_dentry, a->bdst, NULL);
		au_set_dbstart(src_dentry, a->bsrc);
	} else {
		/* the inode of src_dentry already exists on a.bdst branch */
		h_src_dentry = d_find_alias(h_inode);
		if (!h_src_dentry && au_plink_test(inode)) {
			plink = 1;
			h_src_dentry = au_plink_lkup(inode, a->bdst);
			err = PTR_ERR(h_src_dentry);
			if (IS_ERR(h_src_dentry))
				goto out;

			if (unlikely(!h_src_dentry->d_inode)) {
				dput(h_src_dentry);
				h_src_dentry = NULL;
			}

		}
		if (h_src_dentry) {
			err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
					 &a->h_path);
			dput(h_src_dentry);
		} else {
			AuIOErr("no dentry found for hi%lu on b%d\n",
				h_inode->i_ino, a->bdst);
			err = -EIO;
		}
	}

	if (!err && !plink)
		au_plink_append(inode, a->bdst, a->h_path.dentry);

out:
	AuTraceErr(err);
	return err;
}
Пример #4
0
static int au_cmoo(struct dentry *dentry)
{
	int err, cmoo;
	unsigned int udba;
	struct path h_path;
	struct au_pin pin;
	struct au_cp_generic cpg = {
		.dentry	= dentry,
		.bdst	= -1,
		.bsrc	= -1,
		.len	= -1,
		.pin	= &pin,
		.flags	= AuCpup_DTIME | AuCpup_HOPEN
	};
	struct inode *inode, *delegated;
	struct super_block *sb;
	struct au_sbinfo *sbinfo;
	struct au_fhsm *fhsm;
	pid_t pid;
	struct au_branch *br;
	struct dentry *parent;
	struct au_hinode *hdir;

	DiMustWriteLock(dentry);
	inode = dentry->d_inode;
	IiMustWriteLock(inode);

	err = 0;
	if (IS_ROOT(dentry))
		goto out;
	cpg.bsrc = au_dbstart(dentry);
	if (!cpg.bsrc)
		goto out;

	sb = dentry->d_sb;
	sbinfo = au_sbi(sb);
	fhsm = &sbinfo->si_fhsm;
	pid = au_fhsm_pid(fhsm);
	if (pid
	    && (current->pid == pid
		|| current->real_parent->pid == pid))
		goto out;

	br = au_sbr(sb, cpg.bsrc);
	cmoo = au_br_cmoo(br->br_perm);
	if (!cmoo)
		goto out;
	if (!S_ISREG(inode->i_mode))
		cmoo &= AuBrAttr_COO_ALL;
	if (!cmoo)
		goto out;

	parent = dget_parent(dentry);
	di_write_lock_parent(parent);
	err = au_wbr_do_copyup_bu(dentry, cpg.bsrc - 1);
	cpg.bdst = err;
	if (unlikely(err < 0)) {
		err = 0;	/* there is no upper writable branch */
		goto out_dgrade;
	}
	AuDbg("bsrc %d, bdst %d\n", cpg.bsrc, cpg.bdst);

	/* do not respect the coo attrib for the target branch */
	err = au_cpup_dirs(dentry, cpg.bdst);
	if (unlikely(err))
		goto out_dgrade;

	di_downgrade_lock(parent, AuLock_IR);
	udba = au_opt_udba(sb);
	err = au_pin(&pin, dentry, cpg.bdst, udba,
		     AuPin_DI_LOCKED | AuPin_MNT_WRITE);
	if (unlikely(err))
		goto out_parent;

	err = au_sio_cpup_simple(&cpg);
	au_unpin(&pin);
	if (unlikely(err))
		goto out_parent;
	if (!(cmoo & AuBrWAttr_MOO))
		goto out_parent; /* success */

	err = au_pin(&pin, dentry, cpg.bsrc, udba,
		     AuPin_DI_LOCKED | AuPin_MNT_WRITE);
	if (unlikely(err))
		goto out_parent;

	h_path.mnt = au_br_mnt(br);
	h_path.dentry = au_h_dptr(dentry, cpg.bsrc);
	hdir = au_hi(parent->d_inode, cpg.bsrc);
	delegated = NULL;
	err = vfsub_unlink(hdir->hi_inode, &h_path, &delegated, /*force*/1);
	au_unpin(&pin);
	/* todo: keep h_dentry or not? */
	if (unlikely(err == -EWOULDBLOCK)) {
		pr_warn("cannot retry for NFSv4 delegation"
			" for an internal unlink\n");
		iput(delegated);
	}
	if (unlikely(err)) {
		pr_err("unlink %pd after coo failed (%d), ignored\n",
		       dentry, err);
		err = 0;
	}
	goto out_parent; /* success */

out_dgrade:
	di_downgrade_lock(parent, AuLock_IR);
out_parent:
	di_read_unlock(parent, AuLock_IR);
	dput(parent);
out:
	AuTraceErr(err);
	return err;
}

int au_do_open(struct file *file, int (*open)(struct file *file, int flags),
	       struct au_fidir *fidir)
{
	int err;
	struct dentry *dentry;
	struct au_finfo *finfo;

	err = au_finfo_init(file, fidir);
	if (unlikely(err))
		goto out;

	dentry = file->f_path.dentry;
	di_write_lock_child(dentry);
	err = au_cmoo(dentry);
	di_downgrade_lock(dentry, AuLock_IR);
	if (!err)
		err = open(file, vfsub_file_flags(file));
	di_read_unlock(dentry, AuLock_IR);

	finfo = au_fi(file);
	if (!err) {
		finfo->fi_file = file;
		au_sphl_add(&finfo->fi_hlist,
			    &au_sbi(file->f_path.dentry->d_sb)->si_files);
	}
	fi_write_unlock(file);
	if (unlikely(err)) {
		finfo->fi_hdir = NULL;
		au_finfo_fin(file);
	}

out:
	return err;
}
Пример #5
0
/*
 * test if the branch is deletable or not.
 */
static int test_dentry_busy(struct dentry *root, aufs_bindex_t bindex,
                            unsigned int sigen, const unsigned int verbose)
{
    int err, i, j, ndentry;
    aufs_bindex_t bstart, bend;
    struct au_dcsub_pages dpages;
    struct au_dpage *dpage;
    struct dentry *d;
    struct inode *inode;

    err = au_dpages_init(&dpages, GFP_NOFS);
    if (unlikely(err))
        goto out;
    err = au_dcsub_pages(&dpages, root, NULL, NULL);
    if (unlikely(err))
        goto out_dpages;

    for (i = 0; !err && i < dpages.ndpage; i++) {
        dpage = dpages.dpages + i;
        ndentry = dpage->ndentry;
        for (j = 0; !err && j < ndentry; j++) {
            d = dpage->dentries[j];
            AuDebugOn(!d->d_count);
            if (!au_digen_test(d, sigen)) {
                di_read_lock_child(d, AuLock_IR);
                if (unlikely(au_dbrange_test(d))) {
                    di_read_unlock(d, AuLock_IR);
                    continue;
                }
            } else {
                di_write_lock_child(d);
                if (unlikely(au_dbrange_test(d))) {
                    di_write_unlock(d);
                    continue;
                }
                err = au_reval_dpath(d, sigen);
                if (!err)
                    di_downgrade_lock(d, AuLock_IR);
                else {
                    di_write_unlock(d);
                    break;
                }
            }

            /* AuDbgDentry(d); */
            inode = d->d_inode;
            bstart = au_dbstart(d);
            bend = au_dbend(d);
            if (bstart <= bindex
                    && bindex <= bend
                    && au_h_dptr(d, bindex)
                    && ((inode && !S_ISDIR(inode->i_mode))
                        || bstart == bend)) {
                err = -EBUSY;
                AuVerbose(verbose, "busy %.*s\n", AuDLNPair(d));
                AuDbgDentry(d);
            }
            di_read_unlock(d, AuLock_IR);
        }
    }

out_dpages:
    au_dpages_free(&dpages);
out:
    return err;
}