// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) Linux Test Project, 2014-2020
 *
 * errno tests for setns(2) - reassociate thread with a namespace
 */
#define _GNU_SOURCE
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <sched.h>
#include <pwd.h>
#include <string.h>
#include "config.h"
#include "tst_test.h"
#include "lapi/syscalls.h"
#include "setns.h"

static const char nobody_uid[] = "nobody";
static struct passwd *ltpuser;
static int regular_fd;

struct testcase_t {
	const char *msg;
	int fd;
	int ns_type;
	int exp_ret;
	int exp_errno;
	int skip;
	void (*setup) (struct testcase_t *, int i);
	void (*cleanup) (struct testcase_t *);
};

static void setup0(struct testcase_t *t, int i)
{
	t->ns_type = ns_types[i];
}

static void setup1(struct testcase_t *t, int i)
{
	t->ns_type = ns_types[i];
	t->fd = regular_fd;
}

static void setup2(struct testcase_t *t, int i)
{
	t->fd = ns_fds[i];
}

static void setup3(struct testcase_t *t, int i)
{
	if (ns_total < 2) {
		t->skip = 1;
		return;
	}

	t->fd = ns_fds[i];
	t->ns_type = ns_types[(i+1) % ns_total];
}

static void setup4(struct testcase_t *t, int i)
{
	SAFE_SETEUID(ltpuser->pw_uid);

	t->fd = ns_fds[i];
	t->ns_type = ns_types[i];
}

static void cleanup4(LTP_ATTRIBUTE_UNUSED struct testcase_t *t)
{
	SAFE_SETEUID(0);
}

static struct testcase_t tcases[] = {
	{
		.msg = "invalid fd",
		.fd = -1,
		.exp_ret = -1,
		.exp_errno = EBADF,
		.setup = setup0,
	},
	{
		.msg = "regular file fd",
		.exp_ret = -1,
		.exp_errno = EINVAL,
		.setup = setup1,
	},
	{
		.msg = "invalid ns_type",
		.ns_type = -1,
		.exp_ret = -1,
		.exp_errno = EINVAL,
		.setup = setup2,
	},
	{
		.msg = "mismatch ns_type/fd",
		.exp_ret = -1,
		.exp_errno = EINVAL,
		.setup = setup3,
	},
	{
		.msg = "without CAP_SYS_ADMIN",
		.exp_ret = -1,
		.exp_errno = EPERM,
		.setup = setup4,
		.cleanup = cleanup4,
	}
};

static void test_setns(unsigned int testno)
{
	int ret, i;
	struct testcase_t *t = &tcases[testno];

	for (i = 0; i < ns_total; i++) {
		if (t->setup)
			t->setup(t, i);

		if (t->skip) {
			tst_res(TCONF, "skip %s", t->msg);
			continue;
		}

		tst_res(TINFO, "setns(%d, 0x%x)", t->fd, t->ns_type);
		ret = tst_syscall(__NR_setns, t->fd, t->ns_type);
		if (ret == t->exp_ret) {
			if (ret == -1 && errno == t->exp_errno) {
				tst_res(TPASS, "%s exp_errno=%d (%s)",
					t->msg,	t->exp_errno,
					tst_strerrno(t->exp_errno));
			} else {
				tst_res(TFAIL|TERRNO, "%s exp_errno=%d (%s)",
					t->msg, t->exp_errno,
					tst_strerrno(t->exp_errno));
			}
		} else {
			tst_res(TFAIL, "%s ret=%d expected=%d",
				t->msg,	ret, t->exp_ret);
		}

		if (t->cleanup)
			t->cleanup(t);
	}
}

static void setup(void)
{
	/* runtime check if syscall is supported */
	tst_syscall(__NR_setns, -1, 0);

	init_available_ns();
	if (ns_total == 0)
		tst_brk(TCONF, "no ns types/proc entries");

	ltpuser = SAFE_GETPWNAM(nobody_uid);
	regular_fd = SAFE_OPEN("dummy", O_RDWR|O_CREAT, 0600);
	SAFE_UNLINK("dummy");
}

static void cleanup(void)
{
	close_ns_fds();
	if (regular_fd)
		SAFE_CLOSE(regular_fd);
}

static struct tst_test test = {
	.tcnt = ARRAY_SIZE(tcases),
	.test = test_setns,
	.setup = setup,
	.cleanup = cleanup,
	.needs_root = 1,
	.needs_tmpdir = 1,
};

