/*
 * Copyright (c) Crackerjack Project., 2007-2008 ,Hitachi, Ltd
 *          Author(s): Takahiro Yasui <takahiro.yasui.mp@hitachi.com>,
 *		       Yumiko Sugita <yumiko.sugita.yf@hitachi.com>,
 *		       Satoshi Fujiwara <sa-fuji@sdl.hitachi.co.jp>
 * Copyright (c) 2016 Linux Test Project
 *
 * This program 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 would 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, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <errno.h>
#include <mqueue.h>
#include <pwd.h>

#include "tst_test.h"
#include "tst_safe_file_ops.h"
#include "tst_safe_posix_ipc.h"

#define QUEUE_NAME	"/test_mqueue"
#define QUEUE_INIT	"/init_mqueue"

static uid_t euid;
static struct passwd *pw;
static char *qname;
static struct rlimit rlim;

static mqd_t fd, fd2;
static mqd_t fd3 = -1;
static int max_queues;

struct test_case {
	const char *desc;
	char *qname;
	int oflag;
	struct mq_attr *rq;
	int ret;
	int err;
	void (*setup)(void);
	void (*cleanup)(void);
};

#define PROC_MAX_QUEUES "/proc/sys/fs/mqueue/queues_max"

static void create_queue(void);
static void unlink_queue(void);
static void set_rlimit(void);
static void restore_rlimit(void);
static void set_max_queues(void);
static void restore_max_queues(void);

static struct test_case tcase[] = {
	{
		.desc = "NORMAL",
		.qname = QUEUE_NAME,
		.oflag = O_CREAT,
		.rq = &(struct mq_attr){.mq_maxmsg = 20, .mq_msgsize = 16384},
		.ret = 0,
		.err = 0,
	},
	{
		.desc = "NORMAL",
		.qname = QUEUE_NAME,
		.oflag = O_CREAT,
		.ret = 0,
		.err = 0,
	},
	{
		.desc = "NORMAL",
		.qname = "/caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaa",
		.oflag = O_CREAT,
		.ret = 0,
		.err = 0,
	},
	{
		.desc = "NORMAL",
		.qname = "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
			"aaaaaaaaaaaaaaaa",
		.oflag = O_CREAT,
		.ret = -1,
		.err = ENAMETOOLONG,
	},

	{
		.desc = "NORMAL",
		.qname = "",
		.oflag = O_CREAT,
		.ret = -1,
		.err = EINVAL,
	},
	{
		.desc = "NORMAL",
		.qname = QUEUE_NAME,
		.ret = -1,
		.err = EACCES,
		.setup = create_queue,
		.cleanup = unlink_queue,
	},
	{
		.desc = "NORMAL",
		.qname = QUEUE_NAME,
		.oflag = O_CREAT | O_EXCL,
		.ret = -1,
		.err = EEXIST,
		.setup = create_queue,
		.cleanup = unlink_queue,
	},
	{
		.desc = "NO_FILE",
		.qname = QUEUE_NAME,
		.oflag = O_CREAT,
		.ret = -1,
		.err = EMFILE,
		.setup = set_rlimit,
		.cleanup = restore_rlimit,
	},
	{
		.desc = "NORMAL",
		.qname = "/notexist",
		.oflag = 0,
		.ret = -1,
		.err = ENOENT,
	},
	{
		.desc = "NO_SPACE",
		.qname = QUEUE_NAME,
		.oflag = O_CREAT,
		.ret = -1,
		.err = ENOSPC,
		.setup = set_max_queues,
		.cleanup = restore_max_queues,
	}
};

static void create_queue(void)
{
	fd2 = SAFE_MQ_OPEN(QUEUE_NAME, O_CREAT | O_EXCL | O_RDWR, S_IRWXU, NULL);

	SAFE_SETEUID(pw->pw_uid);
}

static void unlink_queue(void)
{
	SAFE_SETEUID(euid);
	if (fd2 > 0)
		SAFE_CLOSE(fd2);

	if (mq_unlink(QUEUE_NAME))
		tst_brk(TBROK | TERRNO, "mq_close(" QUEUE_NAME ") failed");
}


static void set_max_queues(void)
{
	SAFE_FILE_SCANF(PROC_MAX_QUEUES, "%d", &max_queues);
	SAFE_FILE_PRINTF(PROC_MAX_QUEUES, "%d", 1);

	SAFE_SETEUID(pw->pw_uid);
}

static void restore_max_queues(void)
{
	SAFE_SETEUID(euid);

	SAFE_FILE_PRINTF(PROC_MAX_QUEUES, "%d", max_queues);
}

static void set_rlimit(void)
{
	if (rlim.rlim_cur > 0) {
		struct rlimit r;
		r.rlim_cur = 0;
		r.rlim_max = rlim.rlim_max;
		SAFE_SETRLIMIT(RLIMIT_NOFILE, &r);
	}
}

static void restore_rlimit(void)
{
	SAFE_SETRLIMIT(RLIMIT_NOFILE, &rlim);
}

static void setup(void)
{
	euid = geteuid();
	pw = SAFE_GETPWNAM("nobody");
	SAFE_GETRLIMIT(RLIMIT_NOFILE, &rlim);

	fd3 = SAFE_MQ_OPEN(QUEUE_INIT, O_CREAT | O_EXCL | O_RDWR, S_IRWXU, NULL);
}

static void cleanup(void)
{
	if (fd > 0)
		mq_close(fd);

	if (fd2 > 0)
		mq_close(fd2);

	if (fd3 > 0 && mq_close(fd3))
		tst_res(TWARN | TERRNO, "mq_close(%s) failed", QUEUE_INIT);

	if (mq_unlink(QUEUE_INIT))
		tst_res(TWARN | TERRNO, "mq_unlink(%s) failed", QUEUE_INIT);

	mq_unlink(qname);
}

static void do_test(unsigned int i)
{
	struct test_case *tc = &tcase[i];
	struct mq_attr oldattr;

	qname = tc->qname;
	fd = fd2 = -1;

	tst_res(TINFO, "queue name \"%s\"", qname);

	if (tc->setup)
		tc->setup();

	TEST(fd = mq_open(qname, tc->oflag, S_IRWXU, tc->rq));

	if (fd > 0 && tc->rq) {
		if (mq_getattr(fd, &oldattr) < 0) {
			tst_res(TFAIL | TERRNO, "mq_getattr failed");
			goto CLEANUP;
		}

		if (oldattr.mq_maxmsg != tc->rq->mq_maxmsg
			|| oldattr.mq_msgsize != tc->rq->mq_msgsize) {
			tst_res(TFAIL, "wrong mq_attr: "
				"mq_maxmsg expected %ld return %ld, "
				"mq_msgsize expected %ld return %ld",
				tc->rq->mq_maxmsg, oldattr.mq_maxmsg, tc->rq->mq_msgsize,
				oldattr.mq_msgsize);
			goto CLEANUP;
		}
	}

	if (tc->ret == 0) {
		if (TEST_RETURN < 0) {
			tst_res(TFAIL | TTERRNO, "%s wrong return code: %ld",
				tc->desc, TEST_RETURN);
		} else {
			tst_res(TPASS | TTERRNO, "%s returned: %ld",
				tc->desc, TEST_RETURN);
		}

		goto CLEANUP;
	}

	if (TEST_ERRNO != tc->err) {
		tst_res(TFAIL | TTERRNO, "%s expected errno: %d",
			tc->desc, TEST_ERRNO);
		goto CLEANUP;
	}

	if (TEST_RETURN != tc->ret) {
		tst_res(TFAIL | TTERRNO, "%s wrong return code: %ld",
			tc->desc, TEST_RETURN);
	} else {
		tst_res(TPASS | TTERRNO, "%s returned: %ld",
			tc->desc, TEST_RETURN);
	}

CLEANUP:
	if (tc->cleanup)
		tc->cleanup();

	if (TEST_RETURN != -1) {
		if (fd > 0)
			SAFE_CLOSE(fd);
		mq_unlink(qname);
	}
}

static struct tst_test test = {
	.tcnt = ARRAY_SIZE(tcase),
	.test = do_test,
	.needs_root = 1,
	.setup = setup,
	.cleanup = cleanup,
};
