/*
 * Copyright (C) 2013-2020 Canonical, Ltd.
 *
 * 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 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * This code is a complete clean re-write of the stress tool by
 * Colin Ian King <colin.king@canonical.com> and attempts to be
 * backwardly compatible with the stress tool by Amos Waterland
 * <apw@rossby.metr.ou.edu> but has more stress tests and more
 * functionality.
 *
 */
#include "stress-ng.h"

typedef int (*stress_open_func_t)(void);

static const stress_help_t help[] = {
	{ "o N", "open N",	"start N workers exercising open/close" },
	{ NULL,	"open-ops N",	"stop after N open/close bogo operations" },
	{ NULL,	NULL,		NULL }
};

static inline int open_arg2(const char *pathname, int flags)
{
#if defined(__NR_open)
	return syscall(__NR_open, pathname, flags);
#else
	return open(pathname, flags);
#endif
}

static inline int open_arg3(const char *pathname, int flags, mode_t mode)
{
#if defined(__NR_open)
	return syscall(__NR_open, pathname, flags, mode);
#else
	return open(pathname, flags, mode);
#endif
}

static int open_dev_zero_rd(void)
{
	int flags = 0;
#if defined(O_ASYNC)
	flags |= (stress_mwc32() & O_ASYNC);
#endif
#if defined(O_CLOEXEC)
	flags |= (stress_mwc32() & O_CLOEXEC);
#endif
#if defined(O_LARGEFILE)
	flags |= (stress_mwc32() & O_LARGEFILE);
#endif
#if defined(O_NOFOLLOW)
	flags |= (stress_mwc32() & O_NOFOLLOW);
#endif
#if defined(O_NONBLOCK)
	flags |= (stress_mwc32() & O_NONBLOCK);
#endif

	return open_arg2("/dev/zero", O_RDONLY | flags);
}

static int open_dev_null_wr(void)
{
	int flags = 0;
#if defined(O_ASYNC)
	flags |= (stress_mwc32() & O_ASYNC);
#endif
#if defined(O_CLOEXEC)
	flags |= (stress_mwc32() & O_CLOEXEC);
#endif
#if defined(O_LARGEFILE)
	flags |= (stress_mwc32() & O_LARGEFILE);
#endif
#if defined(O_NOFOLLOW)
	flags |= (stress_mwc32() & O_NOFOLLOW);
#endif
#if defined(O_NONBLOCK)
	flags |= (stress_mwc32() & O_NONBLOCK);
#endif
#if defined(O_DSYNC)
	flags |= (stress_mwc32() & O_DSYNC);
#endif
#if defined(O_SYNC)
	flags |= (stress_mwc32() & O_SYNC);
#endif

	return open_arg2("/dev/null", O_WRONLY | flags);
}

#if defined(O_TMPFILE)
static int open_tmp_rdwr(void)
{
	int flags = 0;
#if defined(O_TRUNC)
	flags |= (stress_mwc32() & O_TRUNC);
#endif
#if defined(O_APPEND)
	flags |= (stress_mwc32() & O_APPEND);
#endif
#if defined(O_NOATIME)
	flags |= (stress_mwc32() & O_NOATIME);
#endif
#if defined(O_DIRECT)
	flags |= (stress_mwc32() & O_DIRECT);
#endif
	return open_arg3("/tmp", O_TMPFILE | flags | O_RDWR, S_IRUSR | S_IWUSR);
}
#endif

#if defined(O_TMPFILE)
static int open_tmpfile_no_rdwr(void)
{
	/* Force -EINVAL, need O_WRONLY or O_RDWR to succeed */
	return open_arg3("/tmp", O_TMPFILE, S_IRUSR | S_IWUSR);
}
#endif

#if defined(HAVE_POSIX_OPENPT) && defined(O_RDWR) && defined(N_NOCTTY)
static int open_pt(void)
{
	return posix_openpt(O_RDWR | O_NOCTTY);
}
#endif

#if defined(O_TMPFILE) && defined(O_EXCL)
static int open_tmp_rdwr_excl(void)
{
	return open_arg3("/tmp", O_TMPFILE | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);
}
#endif

#if defined(O_DIRECTORY)
static int open_dir(void)
{
	return open_arg2(".", O_DIRECTORY | O_RDONLY);
}
#endif

#if defined(O_PATH)
static int open_path(void)
{
	return open_arg2(".", O_DIRECTORY | O_PATH);
}
#endif

#if defined(O_CREAT)
static int open_create_eisdir(void)
{
	return open_arg3(".", O_CREAT, S_IRUSR | S_IWUSR);
}
#endif

#if defined(HAVE_OPENAT) && defined(AT_FDCWD)
static int open_with_openat_cwd(void)
{
	char filename[PATH_MAX];
	int fd;

	(void)snprintf(filename, sizeof(filename), "stress-open-%d-%" PRIu32,
		(int)getpid(), stress_mwc32());

	fd = openat(AT_FDCWD, filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
	if (fd >= 0)
		(void)unlink(filename);
	return fd;
}
#endif

#if defined(HAVE_OPENAT) &&	\
    defined(AT_FDCWD) &&	\
    defined(O_PATH) &&		\
    defined(O_DIRECTORY)
static int open_with_openat_dirfd(void)
{
	char filename[PATH_MAX];
	int fd, dirfd;

	(void)snprintf(filename, sizeof(filename), "stress-open-%d-%" PRIu32,
		(int)getpid(), stress_mwc32());

	dirfd = open_arg2(".", O_DIRECTORY | O_PATH);
	if (dirfd < 0)
		return -1;

	fd = openat(dirfd, filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
	if (fd >= 0)
		(void)unlink(filename);
	(void)close(dirfd);
	return fd;
}
#endif

static stress_open_func_t open_funcs[] = {
	open_dev_zero_rd,
	open_dev_null_wr,
#if defined(O_TMPFILE)
	open_tmp_rdwr,
#endif
#if defined(O_TMPFILE) &&	\
    defined(O_EXCL)
	open_tmp_rdwr_excl,
#endif
#if defined(O_TMPFILE)
	open_tmpfile_no_rdwr,
#endif
#if defined(O_DIRECTORY)
	open_dir,
#endif
#if defined(O_PATH)
	open_path,
#endif
#if defined(HAVE_POSIX_OPENPT) &&	\
    defined(O_RDWR) &&			\
    defined(N_NOCTTY)
	open_pt,
#endif
#if defined(O_CREAT)
	open_create_eisdir,
#endif
#if defined(HAVE_OPENAT) &&		\
    defined(AT_FDCWD)
	open_with_openat_cwd,
#endif
#if defined(HAVE_OPENAT) &&	\
    defined(AT_FDCWD) &&	\
    defined(O_PATH) &&		\
    defined(O_DIRECTORY)
	open_with_openat_dirfd,
#endif
};

/*
 *  stress_open()
 *	stress system by rapid open/close calls
 */
static int stress_open(const stress_args_t *args)
{
	static int fds[STRESS_FD_MAX];
	size_t max_fd = stress_get_max_file_limit();

	if (max_fd > SIZEOF_ARRAY(fds))
		max_fd = SIZEOF_ARRAY(fds);

	do {
		size_t i, n;

		for (i = 0; i < max_fd; i++) {
			for (;;) {
				int idx;

				if (!keep_stressing())
					goto close_all;

				idx = stress_mwc32() % SIZEOF_ARRAY(open_funcs);
				fds[i] = open_funcs[idx]();

				if (fds[i] >= 0)
					break;

				/* Check if we hit the open file limit */
				if ((errno == EMFILE) || (errno == ENFILE))
					goto close_all;

				/* Other error occurred, retry */
			}
			inc_counter(args);
		}
close_all:
		n = i;

		for (i = 0; i < n; i++) {
			if (fds[i] < 0)
				break;
			(void)close(fds[i]);
		}
	} while (keep_stressing());

	return EXIT_SUCCESS;
}

stressor_info_t stress_open_info = {
	.stressor = stress_open,
	.class = CLASS_FILESYSTEM | CLASS_OS,
	.help = help
};
