// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2015-2022 FUJITSU LIMITED. All rights reserved
 * Author: Guangwen Feng <fenggw-fnst@cn.fujitsu.com>
 */

/*\
 * [Description]
 *
 * Test for feature MNT_EXPIRE of umount2():
 *
 * - EINVAL when flag is specified with either MNT_FORCE or MNT_DETACH
 * - EAGAIN when initial call to umount2(2) with MNT_EXPIRE
 * - EAGAIN when umount2(2) with MNT_EXPIRE after access(2)
 * - succeed when second call to umount2(2) with MNT_EXPIRE
 *
 * Test for feature UMOUNT_NOFOLLOW of umount2():
 *
 * - EINVAL when target is a symbolic link
 * - succeed when target is a mount point
 */

#include "lapi/mount.h"
#include "tst_test.h"

#define MNTPOINT        "mntpoint"
#define SYMLINK	"symlink"

#define FLAG_DESC(x, y) .flag = x, .exp_errno = 0, \
	.desc = "umount2("y") with "#x" expected success"

#define FLAG_EXP_ERRNO_DESC(x, y, z) .flag = x, .exp_errno = y, \
	.desc = "umount2("z") with "#x" expected "#y

static int mount_flag;

static struct tcase {
	int flag;
	int exp_errno;
	const char *desc;
	const char *mntpoint;
	int do_access;
} tcases[] = {
	{FLAG_EXP_ERRNO_DESC(MNT_EXPIRE | MNT_FORCE, EINVAL, ""), MNTPOINT, 0},
	{FLAG_EXP_ERRNO_DESC(MNT_EXPIRE | MNT_DETACH, EINVAL, ""), MNTPOINT, 0},
	{FLAG_EXP_ERRNO_DESC(MNT_EXPIRE, EAGAIN, "initial call"), MNTPOINT, 0},
	{FLAG_EXP_ERRNO_DESC(MNT_EXPIRE, EAGAIN, "after access"), MNTPOINT, 1},
	{FLAG_DESC(MNT_EXPIRE, "second call"), MNTPOINT, 0},
	{FLAG_EXP_ERRNO_DESC(UMOUNT_NOFOLLOW, EINVAL, "symlink"), SYMLINK, 0},
	{FLAG_DESC(UMOUNT_NOFOLLOW, "mntpoint"), MNTPOINT, 0},
};

static int umount2_retry(const char *target, int flags)
{
	int i, ret;

	for (i = 0; i < 50; i++) {
		ret = umount2(target, flags);
		if (ret == 0 || errno != EBUSY)
			return ret;

		tst_res(TINFO, "umount('%s', %i) failed with EBUSY, try %2i...",
			target, flags, i);

		usleep(100000);
	}

	tst_res(TWARN, "Failed to umount('%s', %i) after 50 retries",
		target, flags);

	errno = EBUSY;
	return -1;
}

static void test_umount2(unsigned int n)
{
	struct tcase *tc = &tcases[n];

	if (!mount_flag) {
		SAFE_MOUNT(tst_device->dev, MNTPOINT, tst_device->fs_type, 0, NULL);
		mount_flag = 1;
	}

	tst_res(TINFO, "Testing %s", tc->desc);

	if (tc->do_access)
		SAFE_ACCESS(MNTPOINT, F_OK);

	if (tc->exp_errno)
		TST_EXP_FAIL(umount2_retry(tc->mntpoint, tc->flag), tc->exp_errno,
			"umount2_retry(%s, %d)", tc->mntpoint, tc->flag);
	else
		TST_EXP_PASS(umount2_retry(tc->mntpoint, tc->flag),
			"umount2_retry(%s, %d)", tc->mntpoint, tc->flag);

	if (!!tc->exp_errno ^ !!TST_PASS)
		mount_flag = 0;
}

static void setup(void)
{
	SAFE_SYMLINK(MNTPOINT, SYMLINK);
}

static void cleanup(void)
{
	if (mount_flag)
		SAFE_UMOUNT(MNTPOINT);
}

static struct tst_test test = {
	.tcnt = ARRAY_SIZE(tcases),
	.cleanup = cleanup,
	.setup = setup,
	.needs_root = 1,
	.format_device = 1,
	.mntpoint = MNTPOINT,
	.test = test_umount2,
};
