/*
 * Copyright (C) 2013 Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 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.
 *
 * Further, this software is distributed without any warranty that it
 * is free of the rightful claim of any third person regarding
 * infringement or the like.  Any license provided herein, whether
 * implied or otherwise, applies only to this software file.  Patent
 * licenses, if any, provided herein do not apply to combinations of
 * this program with other software, or any other product whatsoever.
 *
 * 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 Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/*
 * Basic tests for open(2) and make sure open(2) works and handles error
 * conditions correctly.
 *
 * There are 28 test cases:
 * 1. Open regular file O_RDONLY
 * 2. Open regular file O_WRONLY
 * 3. Open regular file O_RDWR
 * 4. Open regular file O_RDWR | O_SYNC
 * 5. Open regular file O_RDWR | O_TRUNC
 * 6. Open dir O_RDONLY
 * 7. Open dir O_RDWR, expect EISDIR
 * 8. Open regular file O_DIRECTORY, expect ENOTDIR
 * 9. Open hard link file O_RDONLY
 * 10. Open hard link file O_WRONLY
 * 11. Open hard link file O_RDWR
 * 12. Open sym link file O_RDONLY
 * 13. Open sym link file O_WRONLY
 * 14. Open sym link file O_RDWR
 * 15. Open sym link dir O_RDONLY
 * 16. Open sym link dir O_WRONLY, expect EISDIR
 * 17. Open sym link dir O_RDWR, expect EISDIR
 * 18. Open device special file O_RDONLY
 * 19. Open device special file O_WRONLY
 * 20. Open device special file O_RDWR
 * 21. Open non-existing regular file in existing dir
 * 22. Open link file O_RDONLY | O_CREAT
 * 23. Open symlink file O_RDONLY | O_CREAT
 * 24. Open regular file O_RDONLY | O_CREAT
 * 25. Open symlink dir O_RDONLY | O_CREAT, expect EISDIR
 * 26. Open dir O_RDONLY | O_CREAT, expect EISDIR
 * 27. Open regular file O_RDONLY | O_TRUNC, behaviour is undefined but should
 *     not oops or hang
 * 28. Open regular file(non-empty) O_RDONLY | O_TRUNC, behaviour is undefined
 *     but should not oops or hang
 */

#define _GNU_SOURCE
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "test.h"
#include "usctest.h"

char *TCID = "open11";

/* Define test files */
#define T_REG "t_reg"			/* regular file with content */
#define T_REG_EMPTY "t_reg_empty"	/* empty regular file */
#define T_LINK_REG "t_link_reg"		/* hard link to T_REG */
#define T_NEW_REG "t_new_reg"		/* new regular file to be created */
#define T_SYMLINK_REG "t_symlink_reg"	/* symlink to T_REG */
#define T_DIR "t_dir"			/* test dir */
#define T_SYMLINK_DIR "t_symlink_dir"	/* symlink to T_DIR */
#define T_DEV "t_dev"			/* test device special file */

#define T_MSG "this is a test string"

static void setup(void);
static void cleanup(void);

struct test_case {
	char *desc;
	char *path;
	int flags;
	mode_t mode;
	int err;
};
struct test_case tc[] = {
	/*
	 * Test open(2) regular file
	 */
	{	/* open regular file O_RDONLY */
		.desc = "Open regular file O_RDONLY",
		.path = T_REG_EMPTY,
		.flags = O_RDONLY,
		.mode = 0644,
		.err = 0,
	},
	{	/* open regular file O_WRONLY */
		.desc = "Open regular file O_WRONLY",
		.path = T_REG_EMPTY,
		.flags = O_WRONLY,
		.mode = 0644,
		.err = 0,
	},
	{	/* open regular file O_RDWR */
		.desc = "Open regular file O_RDWR",
		.path = T_REG_EMPTY,
		.flags = O_RDWR,
		.mode = 0644,
		.err = 0,
	},
	{	/* open regular file O_RDWR | O_SYNC*/
		.desc = "Open regular file O_RDWR | O_SYNC",
		.path = T_REG_EMPTY,
		.flags = O_RDWR | O_SYNC,
		.mode = 0644,
		.err = 0,
	},
	{	/* open regular file O_RDWR | O_TRUNC */
		.desc = "Open regular file O_RDWR | O_TRUNC",
		.path = T_REG_EMPTY,
		.flags = O_RDWR | O_TRUNC,
		.mode = 0644,
		.err = 0,
	},
	/*
	 * Test open(2) directory
	 */
	{	/* open dir O_RDONLY */
		.desc = "Open dir O_RDONLY",
		.path = T_DIR,
		.flags = O_RDONLY,
		.mode = 0755,
		.err = 0,
	},
	{	/* open dir O_RDWR */
		.desc = "Open dir O_RDWR, expect EISDIR",
		.path = T_DIR,
		.flags = O_RDWR,
		.mode = 0755,
		.err = EISDIR,
	},
	{	/* open regular file O_DIRECTORY */
		.desc = "Open regular file O_DIRECTORY, expect ENOTDIR",
		.path = T_REG_EMPTY,
		.flags = O_RDONLY | O_DIRECTORY,
		.mode = 0644,
		.err = ENOTDIR,
	},
	/*
	 * Test open(2) hard link
	 */
	{	/* open hard link file O_RDONLY */
		.desc = "Open hard link file O_RDONLY",
		.path = T_LINK_REG,
		.flags = O_RDONLY,
		.mode = 0644,
		.err = 0,
	},
	{	/* open hard link file O_WRONLY */
		.desc = "Open hard link file O_WRONLY",
		.path = T_LINK_REG,
		.flags = O_WRONLY,
		.mode = 0644,
		.err = 0,
	},
	{	/* open hard link file O_RDWR */
		.desc = "Open hard link file O_RDWR",
		.path = T_LINK_REG,
		.flags = O_RDWR,
		.mode = 0644,
		.err = 0,
	},
	/*
	 * Test open(2) sym link
	 */
	{	/* open sym link file O_RDONLY */
		.desc = "Open sym link file O_RDONLY",
		.path = T_SYMLINK_REG,
		.flags = O_RDONLY,
		.mode = 0644,
		.err = 0,
	},
	{	/* open sym link file O_WRONLY */
		.desc = "Open sym link file O_WRONLY",
		.path = T_SYMLINK_REG,
		.flags = O_WRONLY,
		.mode = 0644,
		.err = 0,
	},
	{	/* open sym link file O_RDWR */
		.desc = "Open sym link file O_RDWR",
		.path = T_SYMLINK_REG,
		.flags = O_RDWR,
		.mode = 0644,
		.err = 0,
	},
	{	/* open sym link dir O_RDONLY */
		.desc = "Open sym link dir O_RDONLY",
		.path = T_SYMLINK_DIR,
		.flags = O_RDONLY,
		.mode = 0644,
		.err = 0,
	},
	{	/* open sym link dir O_WRONLY */
		.desc = "Open sym link dir O_WRONLY, expect EISDIR",
		.path = T_SYMLINK_DIR,
		.flags = O_WRONLY,
		.mode = 0644,
		.err = EISDIR,
	},
	{	/* open sym link dir O_RDWR */
		.desc = "Open sym link dir O_RDWR, expect EISDIR",
		.path = T_SYMLINK_DIR,
		.flags = O_RDWR,
		.mode = 0644,
		.err = EISDIR,
	},
	/*
	 * Test open(2) device special
	 */
	{	/* open device special file O_RDONLY */
		.desc = "Open device special file O_RDONLY",
		.path = T_DEV,
		.flags = O_RDONLY,
		.mode = 0644,
		.err = 0,
	},
	{	/* open device special file O_WRONLY */
		.desc = "Open device special file O_WRONLY",
		.path = T_DEV,
		.flags = O_WRONLY,
		.mode = 0644,
		.err = 0,
	},
	{	/* open device special file O_RDWR */
		.desc = "Open device special file O_RDWR",
		.path = T_DEV,
		.flags = O_RDWR,
		.mode = 0644,
		.err = 0,
	},
	/*
	 * Test open(2) non-existing file
	 */
	{	/* open non-existing regular file in existing dir */
		.desc = "Open non-existing regular file in existing dir",
		.path = T_DIR"/"T_NEW_REG,
		.flags = O_RDWR | O_CREAT,
		.mode = 0644,
		.err = 0,
	},
	/*
	 * test open(2) with O_CREAT
	 */
	{	/* open hard link file O_RDONLY | O_CREAT */
		.desc = "Open link file O_RDONLY | O_CREAT",
		.path = T_LINK_REG,
		.flags = O_RDONLY | O_CREAT,
		.mode = 0644,
		.err = 0,
	},
	{	/* open sym link file O_RDONLY | O_CREAT */
		.desc = "Open symlink file O_RDONLY | O_CREAT",
		.path = T_SYMLINK_REG,
		.flags = O_RDONLY | O_CREAT,
		.mode = 0644,
		.err = 0,
	},
	{	/* open regular file O_RDONLY | O_CREAT */
		.desc = "Open regular file O_RDONLY | O_CREAT",
		.path = T_REG_EMPTY,
		.flags = O_RDONLY | O_CREAT,
		.mode = 0644,
		.err = 0,
	},
	{	/* open symlink dir O_RDONLY | O_CREAT */
		.desc = "Open symlink dir O_RDONLY | O_CREAT, expect EISDIR",
		.path = T_SYMLINK_DIR,
		.flags = O_RDONLY | O_CREAT,
		.mode = 0644,
		.err = EISDIR,
	},
	{	/* open dir O_RDONLY | O_CREAT */
		.desc = "Open dir O_RDONLY | O_CREAT, expect EISDIR",
		.path = T_DIR,
		.flags = O_RDONLY | O_CREAT,
		.mode = 0644,
		.err = EISDIR,
	},
	/*
	 * Other random open(2) tests
	 */
	{	/* open regular file O_RDONLY | O_TRUNC */
		.desc = "Open regular file O_RDONLY | O_TRUNC, "
			"behaviour is undefined but should not oops or hang",
		.path = T_REG_EMPTY,
		.flags = O_RDONLY | O_TRUNC,
		.mode = 0644,
		.err = -1,
	},
	{	/* open regular(non-empty) file O_RDONLY | O_TRUNC */
		.desc = "Open regular file(non-empty) O_RDONLY | O_TRUNC, "
			"behaviour is undefined but should not oops or hang",
		.path = T_REG,
		.flags = O_RDONLY | O_TRUNC,
		.mode = 0644,
		.err = -1,
	},
};

int TST_TOTAL = sizeof(tc) / sizeof(tc[0]);

int main(int argc, char *argv[])
{
	int lc;
	int i;
	int fd;
	int ret;
	const char *msg;

	msg = parse_opts(argc, argv, NULL, NULL);
	if (msg != NULL)
		tst_brkm(TBROK, tst_exit, "OPTION PARSING ERROR - %s", msg);

	setup();

	for (lc = 0; TEST_LOOPING(lc); lc++) {
		for (i = 0; i < TST_TOTAL; i++) {
			TEST(open(tc[i].path, tc[i].flags, tc[i].mode));
			fd = TEST_RETURN;

			if (tc[i].err == -1 || TEST_ERRNO == tc[i].err) {
				tst_resm(TPASS, "%s", tc[i].desc);
			} else {
				tst_resm(TFAIL | TTERRNO,
					 "%s - expected errno %d - Got",
					 tc[i].desc, tc[i].err);
			}
			if (fd > 0) {
				ret = close(fd);
				if (ret < 0)
					tst_resm(TWARN, "%s - close failed: %s",
						 tc[i].desc, strerror(errno));
			}
		}
	}

	cleanup();
	tst_exit();
}

static void setup(void)
{
	int fd;
	int ret;

	tst_require_root(NULL);

	tst_tmpdir();

	/* Create test files */
	fd = open(T_REG, O_WRONLY | O_CREAT, 0644);
	if (fd == -1)
		tst_brkm(TBROK | TERRNO, cleanup, "Create %s failed", T_REG);
	ret = write(fd, T_MSG, sizeof(T_MSG));
	if (ret == -1) {
		close(fd);
		tst_brkm(TBROK | TERRNO, cleanup, "Write %s failed", T_REG);
	}
	close(fd);

	fd = creat(T_REG_EMPTY, 0644);
	if (fd == -1)
		tst_brkm(TBROK | TERRNO, cleanup, "Create %s failed",
			 T_REG_EMPTY);
	close(fd);

	ret = link(T_REG, T_LINK_REG);
	if (ret == -1)
		tst_brkm(TBROK | TERRNO, cleanup, "Hard link %s -> %s failed",
			 T_REG, T_LINK_REG);

	ret = symlink(T_REG, T_SYMLINK_REG);
	if (ret == -1)
		tst_brkm(TBROK | TERRNO, cleanup, "Symlink %s -> %s failed",
			 T_REG, T_SYMLINK_REG);

	ret = mkdir(T_DIR, 0755);
	if (ret == -1)
		tst_brkm(TBROK | TERRNO, cleanup, "mkdir %s failed", T_DIR);

	ret = symlink(T_DIR, T_SYMLINK_DIR);
	if (ret == -1)
		tst_brkm(TBROK | TERRNO, cleanup, "Symlink %s -> %s failed",
			 T_DIR, T_SYMLINK_DIR);

	ret = mknod(T_DEV, S_IFCHR, makedev(1, 5));
	if (ret == -1)
		tst_brkm(TBROK | TERRNO, cleanup, "Create char dev %s failed",
			 T_DEV);

	TEST_PAUSE;
}

static void cleanup(void)
{
	TEST_CLEANUP;
	tst_rmdir();
}
