aboutsummaryrefslogtreecommitdiffstats
path: root/fs/aufs/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/aufs/dir.c')
-rw-r--r--fs/aufs/dir.c763
1 files changed, 763 insertions, 0 deletions
diff --git a/fs/aufs/dir.c b/fs/aufs/dir.c
new file mode 100644
index 000000000000..0bcb39ee7255
--- /dev/null
+++ b/fs/aufs/dir.c
@@ -0,0 +1,763 @@
+// 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/>.
+ */
+
+/*
+ * directory operations
+ */
+
+#include <linux/fs_stack.h>
+#include <linux/iversion.h>
+#include "aufs.h"
+
+void au_add_nlink(struct inode *dir, struct inode *h_dir)
+{
+ unsigned int nlink;
+
+ AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode));
+
+ nlink = dir->i_nlink;
+ nlink += h_dir->i_nlink - 2;
+ if (h_dir->i_nlink < 2)
+ nlink += 2;
+ smp_mb(); /* for i_nlink */
+ /* 0 can happen in revaliding */
+ set_nlink(dir, nlink);
+}
+
+void au_sub_nlink(struct inode *dir, struct inode *h_dir)
+{
+ unsigned int nlink;
+
+ AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode));
+
+ nlink = dir->i_nlink;
+ nlink -= h_dir->i_nlink - 2;
+ if (h_dir->i_nlink < 2)
+ nlink -= 2;
+ smp_mb(); /* for i_nlink */
+ /* nlink == 0 means the branch-fs is broken */
+ set_nlink(dir, nlink);
+}
+
+loff_t au_dir_size(struct file *file, struct dentry *dentry)
+{
+ loff_t sz;
+ aufs_bindex_t bindex, bbot;
+ struct file *h_file;
+ struct dentry *h_dentry;
+
+ sz = 0;
+ if (file) {
+ AuDebugOn(!d_is_dir(file->f_path.dentry));
+
+ bbot = au_fbbot_dir(file);
+ for (bindex = au_fbtop(file);
+ bindex <= bbot && sz < KMALLOC_MAX_SIZE;
+ bindex++) {
+ h_file = au_hf_dir(file, bindex);
+ if (h_file && file_inode(h_file))
+ sz += vfsub_f_size_read(h_file);
+ }
+ } else {
+ AuDebugOn(!dentry);
+ AuDebugOn(!d_is_dir(dentry));
+
+ bbot = au_dbtaildir(dentry);
+ for (bindex = au_dbtop(dentry);
+ bindex <= bbot && sz < KMALLOC_MAX_SIZE;
+ bindex++) {
+ h_dentry = au_h_dptr(dentry, bindex);
+ if (h_dentry && d_is_positive(h_dentry))
+ sz += i_size_read(d_inode(h_dentry));
+ }
+ }
+ if (sz < KMALLOC_MAX_SIZE)
+ sz = roundup_pow_of_two(sz);
+ if (sz > KMALLOC_MAX_SIZE)
+ sz = KMALLOC_MAX_SIZE;
+ else if (sz < NAME_MAX) {
+ BUILD_BUG_ON(AUFS_RDBLK_DEF < NAME_MAX);
+ sz = AUFS_RDBLK_DEF;
+ }
+ return sz;
+}
+
+struct au_dir_ts_arg {
+ struct dentry *dentry;
+ aufs_bindex_t brid;
+};
+
+static void au_do_dir_ts(void *arg)
+{
+ struct au_dir_ts_arg *a = arg;
+ struct au_dtime dt;
+ struct path h_path;
+ struct inode *dir, *h_dir;
+ struct super_block *sb;
+ struct au_branch *br;
+ struct au_hinode *hdir;
+ int err;
+ aufs_bindex_t btop, bindex;
+
+ sb = a->dentry->d_sb;
+ if (d_really_is_negative(a->dentry))
+ goto out;
+ /* no dir->i_mutex lock */
+ aufs_read_lock(a->dentry, AuLock_DW); /* noflush */
+
+ dir = d_inode(a->dentry);
+ btop = au_ibtop(dir);
+ bindex = au_br_index(sb, a->brid);
+ if (bindex < btop)
+ goto out_unlock;
+
+ br = au_sbr(sb, bindex);
+ h_path.dentry = au_h_dptr(a->dentry, bindex);
+ if (!h_path.dentry)
+ goto out_unlock;
+ h_path.mnt = au_br_mnt(br);
+ au_dtime_store(&dt, a->dentry, &h_path);
+
+ br = au_sbr(sb, btop);
+ if (!au_br_writable(br->br_perm))
+ goto out_unlock;
+ h_path.dentry = au_h_dptr(a->dentry, btop);
+ h_path.mnt = au_br_mnt(br);
+ err = vfsub_mnt_want_write(h_path.mnt);
+ if (err)
+ goto out_unlock;
+ hdir = au_hi(dir, btop);
+ au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT);
+ h_dir = au_h_iptr(dir, btop);
+ if (h_dir->i_nlink
+ && timespec64_compare(&h_dir->i_mtime, &dt.dt_mtime) < 0) {
+ dt.dt_h_path = h_path;
+ au_dtime_revert(&dt);
+ }
+ au_hn_inode_unlock(hdir);
+ vfsub_mnt_drop_write(h_path.mnt);
+ au_cpup_attr_timesizes(dir);
+
+out_unlock:
+ aufs_read_unlock(a->dentry, AuLock_DW);
+out:
+ dput(a->dentry);
+ au_nwt_done(&au_sbi(sb)->si_nowait);
+ au_kfree_try_rcu(arg);
+}
+
+void au_dir_ts(struct inode *dir, aufs_bindex_t bindex)
+{
+ int perm, wkq_err;
+ aufs_bindex_t btop;
+ struct au_dir_ts_arg *arg;
+ struct dentry *dentry;
+ struct super_block *sb;
+
+ IMustLock(dir);
+
+ dentry = d_find_any_alias(dir);
+ AuDebugOn(!dentry);
+ sb = dentry->d_sb;
+ btop = au_ibtop(dir);
+ if (btop == bindex) {
+ au_cpup_attr_timesizes(dir);
+ goto out;
+ }
+
+ perm = au_sbr_perm(sb, btop);
+ if (!au_br_writable(perm))
+ goto out;
+
+ arg = kmalloc(sizeof(*arg), GFP_NOFS);
+ if (!arg)
+ goto out;
+
+ arg->dentry = dget(dentry); /* will be dput-ted by au_do_dir_ts() */
+ arg->brid = au_sbr_id(sb, bindex);
+ wkq_err = au_wkq_nowait(au_do_dir_ts, arg, sb, /*flags*/0);
+ if (unlikely(wkq_err)) {
+ pr_err("wkq %d\n", wkq_err);
+ dput(dentry);
+ au_kfree_try_rcu(arg);
+ }
+
+out:
+ dput(dentry);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int reopen_dir(struct file *file)
+{
+ int err;
+ unsigned int flags;
+ aufs_bindex_t bindex, btail, btop;
+ struct dentry *dentry, *h_dentry;
+ struct file *h_file;
+
+ /* open all lower dirs */
+ dentry = file->f_path.dentry;
+ btop = au_dbtop(dentry);
+ for (bindex = au_fbtop(file); bindex < btop; bindex++)
+ au_set_h_fptr(file, bindex, NULL);
+ au_set_fbtop(file, btop);
+
+ btail = au_dbtaildir(dentry);
+ for (bindex = au_fbbot_dir(file); btail < bindex; bindex--)
+ au_set_h_fptr(file, bindex, NULL);
+ au_set_fbbot_dir(file, btail);
+
+ flags = vfsub_file_flags(file);
+ for (bindex = btop; bindex <= btail; bindex++) {
+ h_dentry = au_h_dptr(dentry, bindex);
+ if (!h_dentry)
+ continue;
+ h_file = au_hf_dir(file, bindex);
+ if (h_file)
+ continue;
+
+ h_file = au_h_open(dentry, bindex, flags, file, /*force_wr*/0);
+ err = PTR_ERR(h_file);
+ if (IS_ERR(h_file))
+ goto out; /* close all? */
+ au_set_h_fptr(file, bindex, h_file);
+ }
+ au_update_figen(file);
+ /* todo: necessary? */
+ /* file->f_ra = h_file->f_ra; */
+ err = 0;
+
+out:
+ return err;
+}
+
+static int do_open_dir(struct file *file, int flags, struct file *h_file)
+{
+ int err;
+ aufs_bindex_t bindex, btail;
+ struct dentry *dentry, *h_dentry;
+ struct vfsmount *mnt;
+
+ FiMustWriteLock(file);
+ AuDebugOn(h_file);
+
+ err = 0;
+ mnt = file->f_path.mnt;
+ dentry = file->f_path.dentry;
+ file->f_version = inode_query_iversion(d_inode(dentry));
+ bindex = au_dbtop(dentry);
+ au_set_fbtop(file, bindex);
+ btail = au_dbtaildir(dentry);
+ au_set_fbbot_dir(file, btail);
+ for (; !err && bindex <= btail; bindex++) {
+ h_dentry = au_h_dptr(dentry, bindex);
+ if (!h_dentry)
+ continue;
+
+ err = vfsub_test_mntns(mnt, h_dentry->d_sb);
+ if (unlikely(err))
+ break;
+ h_file = au_h_open(dentry, bindex, flags, file, /*force_wr*/0);
+ if (IS_ERR(h_file)) {
+ err = PTR_ERR(h_file);
+ break;
+ }
+ au_set_h_fptr(file, bindex, h_file);
+ }
+ au_update_figen(file);
+ /* todo: necessary? */
+ /* file->f_ra = h_file->f_ra; */
+ if (!err)
+ return 0; /* success */
+
+ /* close all */
+ for (bindex = au_fbtop(file); bindex <= btail; bindex++)
+ au_set_h_fptr(file, bindex, NULL);
+ au_set_fbtop(file, -1);
+ au_set_fbbot_dir(file, -1);
+
+ return err;
+}
+
+static int aufs_open_dir(struct inode *inode __maybe_unused,
+ struct file *file)
+{
+ int err;
+ struct super_block *sb;
+ struct au_fidir *fidir;
+
+ err = -ENOMEM;
+ sb = file->f_path.dentry->d_sb;
+ si_read_lock(sb, AuLock_FLUSH);
+ fidir = au_fidir_alloc(sb);
+ if (fidir) {
+ struct au_do_open_args args = {
+ .open = do_open_dir,
+ .fidir = fidir
+ };
+ err = au_do_open(file, &args);
+ if (unlikely(err))
+ au_kfree_rcu(fidir);
+ }
+ si_read_unlock(sb);
+ return err;
+}
+
+static int aufs_release_dir(struct inode *inode __maybe_unused,
+ struct file *file)
+{
+ struct au_vdir *vdir_cache;
+ struct au_finfo *finfo;
+ struct au_fidir *fidir;
+ struct au_hfile *hf;
+ aufs_bindex_t bindex, bbot;
+
+ finfo = au_fi(file);
+ fidir = finfo->fi_hdir;
+ if (fidir) {
+ au_hbl_del(&finfo->fi_hlist,
+ &au_sbi(file->f_path.dentry->d_sb)->si_files);
+ vdir_cache = fidir->fd_vdir_cache; /* lock-free */
+ if (vdir_cache)
+ au_vdir_free(vdir_cache);
+
+ bindex = finfo->fi_btop;
+ if (bindex >= 0) {
+ hf = fidir->fd_hfile + bindex;
+ /*
+ * calls fput() instead of filp_close(),
+ * since no dnotify or lock for the lower file.
+ */
+ bbot = fidir->fd_bbot;
+ for (; bindex <= bbot; bindex++, hf++)
+ if (hf->hf_file)
+ au_hfput(hf, /*execed*/0);
+ }
+ au_kfree_rcu(fidir);
+ finfo->fi_hdir = NULL;
+ }
+ au_finfo_fin(file);
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int au_do_flush_dir(struct file *file, fl_owner_t id)
+{
+ int err;
+ aufs_bindex_t bindex, bbot;
+ struct file *h_file;
+
+ err = 0;
+ bbot = au_fbbot_dir(file);
+ for (bindex = au_fbtop(file); !err && bindex <= bbot; bindex++) {
+ h_file = au_hf_dir(file, bindex);
+ if (h_file)
+ err = vfsub_flush(h_file, id);
+ }
+ return err;
+}
+
+static int aufs_flush_dir(struct file *file, fl_owner_t id)
+{
+ return au_do_flush(file, id, au_do_flush_dir);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int au_do_fsync_dir_no_file(struct dentry *dentry, int datasync)
+{
+ int err;
+ aufs_bindex_t bbot, bindex;
+ struct inode *inode;
+ struct super_block *sb;
+
+ err = 0;
+ sb = dentry->d_sb;
+ inode = d_inode(dentry);
+ IMustLock(inode);
+ bbot = au_dbbot(dentry);
+ for (bindex = au_dbtop(dentry); !err && bindex <= bbot; bindex++) {
+ struct path h_path;
+
+ if (au_test_ro(sb, bindex, inode))
+ continue;
+ h_path.dentry = au_h_dptr(dentry, bindex);
+ if (!h_path.dentry)
+ continue;
+
+ h_path.mnt = au_sbr_mnt(sb, bindex);
+ err = vfsub_fsync(NULL, &h_path, datasync);
+ }
+
+ return err;
+}
+
+static int au_do_fsync_dir(struct file *file, int datasync)
+{
+ int err;
+ aufs_bindex_t bbot, bindex;
+ struct file *h_file;
+ struct super_block *sb;
+ struct inode *inode;
+
+ err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1, /*fi_lsc*/0);
+ if (unlikely(err))
+ goto out;
+
+ inode = file_inode(file);
+ sb = inode->i_sb;
+ bbot = au_fbbot_dir(file);
+ for (bindex = au_fbtop(file); !err && bindex <= bbot; bindex++) {
+ h_file = au_hf_dir(file, bindex);
+ if (!h_file || au_test_ro(sb, bindex, inode))
+ continue;
+
+ err = vfsub_fsync(h_file, &h_file->f_path, datasync);
+ }
+
+out:
+ return err;
+}
+
+/*
+ * @file may be NULL
+ */
+static int aufs_fsync_dir(struct file *file, loff_t start, loff_t end,
+ int datasync)
+{
+ int err;
+ struct dentry *dentry;
+ struct inode *inode;
+ struct super_block *sb;
+
+ err = 0;
+ dentry = file->f_path.dentry;
+ inode = d_inode(dentry);
+ inode_lock(inode);
+ sb = dentry->d_sb;
+ si_noflush_read_lock(sb);
+ if (file)
+ err = au_do_fsync_dir(file, datasync);
+ else {
+ di_write_lock_child(dentry);
+ err = au_do_fsync_dir_no_file(dentry, datasync);
+ }
+ au_cpup_attr_timesizes(inode);
+ di_write_unlock(dentry);
+ if (file)
+ fi_write_unlock(file);
+
+ si_read_unlock(sb);
+ inode_unlock(inode);
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int aufs_iterate_shared(struct file *file, struct dir_context *ctx)
+{
+ int err;
+ struct dentry *dentry;
+ struct inode *inode, *h_inode;
+ struct super_block *sb;
+
+ AuDbg("%pD, ctx{%ps, %llu}\n", file, ctx->actor, ctx->pos);
+
+ dentry = file->f_path.dentry;
+ inode = d_inode(dentry);
+ IMustLock(inode);
+
+ sb = dentry->d_sb;
+ si_read_lock(sb, AuLock_FLUSH);
+ err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1, /*fi_lsc*/0);
+ if (unlikely(err))
+ goto out;
+ err = au_alive_dir(dentry);
+ if (!err)
+ err = au_vdir_init(file);
+ di_downgrade_lock(dentry, AuLock_IR);
+ if (unlikely(err))
+ goto out_unlock;
+
+ h_inode = au_h_iptr(inode, au_ibtop(inode));
+ if (!au_test_nfsd()) {
+ err = au_vdir_fill_de(file, ctx);
+ fsstack_copy_attr_atime(inode, h_inode);
+ } else {
+ /*
+ * nfsd filldir may call lookup_one_len(), vfs_getattr(),
+ * encode_fh() and others.
+ */
+ atomic_inc(&h_inode->i_count);
+ di_read_unlock(dentry, AuLock_IR);
+ si_read_unlock(sb);
+ err = au_vdir_fill_de(file, ctx);
+ fsstack_copy_attr_atime(inode, h_inode);
+ fi_write_unlock(file);
+ iput(h_inode);
+
+ AuTraceErr(err);
+ return err;
+ }
+
+out_unlock:
+ di_read_unlock(dentry, AuLock_IR);
+ fi_write_unlock(file);
+out:
+ si_read_unlock(sb);
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#define AuTestEmpty_WHONLY 1
+#define AuTestEmpty_CALLED (1 << 1)
+#define AuTestEmpty_SHWH (1 << 2)
+#define au_ftest_testempty(flags, name) ((flags) & AuTestEmpty_##name)
+#define au_fset_testempty(flags, name) \
+ do { (flags) |= AuTestEmpty_##name; } while (0)
+#define au_fclr_testempty(flags, name) \
+ do { (flags) &= ~AuTestEmpty_##name; } while (0)
+
+#ifndef CONFIG_AUFS_SHWH
+#undef AuTestEmpty_SHWH
+#define AuTestEmpty_SHWH 0
+#endif
+
+struct test_empty_arg {
+ struct dir_context ctx;
+ struct au_nhash *whlist;
+ unsigned int flags;
+ int err;
+ aufs_bindex_t bindex;
+};
+
+static int test_empty_cb(struct dir_context *ctx, const char *__name,
+ int namelen, loff_t offset __maybe_unused, u64 ino,
+ unsigned int d_type)
+{
+ struct test_empty_arg *arg = container_of(ctx, struct test_empty_arg,
+ ctx);
+ char *name = (void *)__name;
+
+ arg->err = 0;
+ au_fset_testempty(arg->flags, CALLED);
+ /* smp_mb(); */
+ if (name[0] == '.'
+ && (namelen == 1 || (name[1] == '.' && namelen == 2)))
+ goto out; /* success */
+
+ if (namelen <= AUFS_WH_PFX_LEN
+ || memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) {
+ if (au_ftest_testempty(arg->flags, WHONLY)
+ && !au_nhash_test_known_wh(arg->whlist, name, namelen))
+ arg->err = -ENOTEMPTY;
+ goto out;
+ }
+
+ name += AUFS_WH_PFX_LEN;
+ namelen -= AUFS_WH_PFX_LEN;
+ if (!au_nhash_test_known_wh(arg->whlist, name, namelen))
+ arg->err = au_nhash_append_wh
+ (arg->whlist, name, namelen, ino, d_type, arg->bindex,
+ au_ftest_testempty(arg->flags, SHWH));
+
+out:
+ /* smp_mb(); */
+ AuTraceErr(arg->err);
+ return arg->err;
+}
+
+static int do_test_empty(struct dentry *dentry, struct test_empty_arg *arg)
+{
+ int err;
+ struct file *h_file;
+ struct au_branch *br;
+
+ h_file = au_h_open(dentry, arg->bindex,
+ O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_LARGEFILE,
+ /*file*/NULL, /*force_wr*/0);
+ err = PTR_ERR(h_file);
+ if (IS_ERR(h_file))
+ goto out;
+
+ err = 0;
+ if (!au_opt_test(au_mntflags(dentry->d_sb), UDBA_NONE)
+ && !file_inode(h_file)->i_nlink)
+ goto out_put;
+
+ do {
+ arg->err = 0;
+ au_fclr_testempty(arg->flags, CALLED);
+ /* smp_mb(); */
+ err = vfsub_iterate_dir(h_file, &arg->ctx);
+ if (err >= 0)
+ err = arg->err;
+ } while (!err && au_ftest_testempty(arg->flags, CALLED));
+
+out_put:
+ fput(h_file);
+ br = au_sbr(dentry->d_sb, arg->bindex);
+ au_lcnt_dec(&br->br_nfiles);
+out:
+ return err;
+}
+
+struct do_test_empty_args {
+ int *errp;
+ struct dentry *dentry;
+ struct test_empty_arg *arg;
+};
+
+static void call_do_test_empty(void *args)
+{
+ struct do_test_empty_args *a = args;
+ *a->errp = do_test_empty(a->dentry, a->arg);
+}
+
+static int sio_test_empty(struct dentry *dentry, struct test_empty_arg *arg)
+{
+ int err, wkq_err;
+ struct dentry *h_dentry;
+ struct inode *h_inode;
+
+ h_dentry = au_h_dptr(dentry, arg->bindex);
+ h_inode = d_inode(h_dentry);
+ /* todo: i_mode changes anytime? */
+ inode_lock_shared_nested(h_inode, AuLsc_I_CHILD);
+ err = au_test_h_perm_sio(h_inode, MAY_EXEC | MAY_READ);
+ inode_unlock_shared(h_inode);
+ if (!err)
+ err = do_test_empty(dentry, arg);
+ else {
+ struct do_test_empty_args args = {
+ .errp = &err,
+ .dentry = dentry,
+ .arg = arg
+ };
+ unsigned int flags = arg->flags;
+
+ wkq_err = au_wkq_wait(call_do_test_empty, &args);
+ if (unlikely(wkq_err))
+ err = wkq_err;
+ arg->flags = flags;
+ }
+
+ return err;
+}
+
+int au_test_empty_lower(struct dentry *dentry)
+{
+ int err;
+ unsigned int rdhash;
+ aufs_bindex_t bindex, btop, btail;
+ struct au_nhash whlist;
+ struct test_empty_arg arg = {
+ .ctx = {
+ .actor = test_empty_cb
+ }
+ };
+ int (*test_empty)(struct dentry *dentry, struct test_empty_arg *arg);
+
+ SiMustAnyLock(dentry->d_sb);
+
+ rdhash = au_sbi(dentry->d_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;
+
+ arg.flags = 0;
+ arg.whlist = &whlist;
+ btop = au_dbtop(dentry);
+ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH))
+ au_fset_testempty(arg.flags, SHWH);
+ test_empty = do_test_empty;
+ if (au_opt_test(au_mntflags(dentry->d_sb), DIRPERM1))
+ test_empty = sio_test_empty;
+ arg.bindex = btop;
+ err = test_empty(dentry, &arg);
+ if (unlikely(err))
+ goto out_whlist;
+
+ au_fset_testempty(arg.flags, WHONLY);
+ btail = au_dbtaildir(dentry);
+ for (bindex = btop + 1; !err && bindex <= btail; bindex++) {
+ struct dentry *h_dentry;
+
+ h_dentry = au_h_dptr(dentry, bindex);
+ if (h_dentry && d_is_positive(h_dentry)) {
+ arg.bindex = bindex;
+ err = test_empty(dentry, &arg);
+ }
+ }
+
+out_whlist:
+ au_nhash_wh_free(&whlist);
+out:
+ return err;
+}
+
+int au_test_empty(struct dentry *dentry, struct au_nhash *whlist)
+{
+ int err;
+ struct test_empty_arg arg = {
+ .ctx = {
+ .actor = test_empty_cb
+ }
+ };
+ aufs_bindex_t bindex, btail;
+
+ err = 0;
+ arg.whlist = whlist;
+ arg.flags = AuTestEmpty_WHONLY;
+ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH))
+ au_fset_testempty(arg.flags, SHWH);
+ btail = au_dbtaildir(dentry);
+ for (bindex = au_dbtop(dentry); !err && bindex <= btail; bindex++) {
+ struct dentry *h_dentry;
+
+ h_dentry = au_h_dptr(dentry, bindex);
+ if (h_dentry && d_is_positive(h_dentry)) {
+ arg.bindex = bindex;
+ err = sio_test_empty(dentry, &arg);
+ }
+ }
+
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+const struct file_operations aufs_dir_fop = {
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+ .read = generic_read_dir,
+ .iterate_shared = aufs_iterate_shared,
+ .unlocked_ioctl = aufs_ioctl_dir,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = aufs_compat_ioctl_dir,
+#endif
+ .open = aufs_open_dir,
+ .release = aufs_release_dir,
+ .flush = aufs_flush_dir,
+ .fsync = aufs_fsync_dir
+};