// 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
};
