/*
 * Copyright (C) 2013-2021 Canonical, Ltd.
 * Copyright (C)      2022 Colin Ian King.
 *
 * 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.
 *
 */
#include "stress-ng.h"

#if defined(HAVE_LIBGEN_H)
#include <libgen.h>
#endif

#if defined(HAVE_LINUX_FS_H)
#include <linux/fs.h>
#endif

static const stress_help_t help[] = {
	{ NULL,	"inode-flags N",	"start N workers exercising various inode flags" },
	{ NULL,	"inode-flags-ops N",	"stop inode-flags workers after N bogo operations" },
	{ NULL,	NULL,			NULL }
};

#if defined(HAVE_LIB_PTHREAD) && \
    defined(HAVE_LIBGEN_H) && \
    defined(FS_IOC_GETFLAGS) && \
    defined(FS_IOC_SETFLAGS) && \
    defined(FS_IOC_SETFLAGS) && \
    defined(O_DIRECTORY)

#define MAX_INODE_FLAG_THREADS		(4)

typedef struct {
	int dir_fd;
	int file_fd;
} stress_data_t;

static volatile bool keep_running;
static sigset_t set;
static shim_pthread_spinlock_t spinlock;

/*
 *  stress_inode_flags_ioctl()
 *	try and toggle an inode flag on/off
 */
static void stress_inode_flags_ioctl(
	const stress_args_t *args,
	const int fd,
	const int flag)
{
	int ret, attr;

	if (!(keep_running || keep_stressing(args)))
		return;

	ret = ioctl(fd, FS_IOC_GETFLAGS, &attr);
	if (ret != 0)
		return;

	attr |= flag;
	ret = ioctl(fd, FS_IOC_SETFLAGS, &attr);
	(void)ret;

	attr &= ~flag;
	ret = ioctl(fd, FS_IOC_SETFLAGS, &attr);
	(void)ret;
}

/*
 *  stress_inode_flags_ioctl_sane()
 *	set flags to a sane state so that file can be removed
 */
static inline void stress_inode_flags_ioctl_sane(const int fd)
{
	int ret;
	const long int flag = 0;

	ret = ioctl(fd, FS_IOC_SETFLAGS, &flag);
	(void)ret;
}

/*
 *  stress_inode_flags_stressor()
 *	exercise inode flags, see man ioctl_flags for
 *	more details of these flags. Some are never going
 *	to be implemented and some are just relevant to
 *	specific file systems. We just want to try and
 *	toggle these on and off to see if they break rather
 *	than fail.
 */
static int stress_inode_flags_stressor(
	const stress_args_t *args,
	stress_data_t *data)
{
	while (keep_running && keep_stressing(args)) {
		int ret;

		stress_inode_flags_ioctl(args, data->dir_fd, 0);
#if defined(FS_DIRSYNC_FL)
		stress_inode_flags_ioctl(args, data->dir_fd, FS_DIRSYNC_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_PROJINHERIT_FL)
		stress_inode_flags_ioctl(args, data->dir_fd, FS_PROJINHERIT_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_SYNC_FL)
		stress_inode_flags_ioctl(args, data->dir_fd, FS_SYNC_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_TOPDIR_FL)
		stress_inode_flags_ioctl(args, data->dir_fd, FS_TOPDIR_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_APPEND_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_APPEND_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_COMPR_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_COMPR_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_IMMUTABLE_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_IMMUTABLE_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_JOURNAL_DATA_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_JOURNAL_DATA_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_NOCOW_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_NOCOW_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_NODUMP_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_NODUMP_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_NOTAIL_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_NOTAIL_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_PROJINHERIT_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_PROJINHERIT_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_SECRM_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_SECRM_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_SYNC_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_SYNC_FL);
#else
		UNEXPECTED
#endif
#if defined(FS_UNRM_FL)
		stress_inode_flags_ioctl(args, data->file_fd, FS_UNRM_FL);
#else
		UNEXPECTED
#endif
		ret = shim_pthread_spin_lock(&spinlock);
		if (!ret) {
			inc_counter(args);
			ret = shim_pthread_spin_unlock(&spinlock);
			(void)ret;
		}
		stress_inode_flags_ioctl_sane(data->file_fd);
	}
	stress_inode_flags_ioctl_sane(data->file_fd);

	return 0;
}

/*
 *  stress_inode_flags()
 *	exercise inode flags on a file
 */
static void *stress_inode_flags_thread(void *arg)
{
	static void *nowt = NULL;
	stress_pthread_args_t *pa = (stress_pthread_args_t *)arg;

	/*
	 *  Block all signals, let controlling thread
	 *  handle these
	 */
	(void)sigprocmask(SIG_BLOCK, &set, NULL);

	pa->pthread_ret = stress_inode_flags_stressor(pa->args, pa->data);

	return &nowt;
}

/*
 *  stress_inode_flags
 *	stress reading all of /dev
 */
static int stress_inode_flags(const stress_args_t *args)
{
	size_t i;
	pthread_t pthreads[MAX_INODE_FLAG_THREADS];
	int rc, ret[MAX_INODE_FLAG_THREADS];
	stress_pthread_args_t pa[MAX_INODE_FLAG_THREADS];
	stress_data_t data;
	char tmp[PATH_MAX], file_name[PATH_MAX];
	char *dir_name;

	rc = shim_pthread_spin_init(&spinlock, SHIM_PTHREAD_PROCESS_SHARED);
	if (rc) {
		pr_fail("%s: pthread_spin_init failed, errno = %d (%s)\n",
			args->name, rc, strerror(rc));
		return EXIT_FAILURE;
	}

	rc = stress_temp_dir_mk_args(args);
	if (rc < 0)
		return exit_status(-rc);
	(void)stress_temp_filename_args(args,
		file_name, sizeof(file_name), stress_mwc32());

	shim_strlcpy(tmp, file_name, sizeof(tmp));
	dir_name = dirname(tmp);

	data.dir_fd = open(dir_name, O_RDONLY | O_DIRECTORY);
	if (data.dir_fd < 0) {
		pr_err("%s: cannot open %s: errno=%d (%s)\n",
			args->name, dir_name, errno, strerror(errno));
		rc = EXIT_NO_RESOURCE;
		goto tidy;
	}
	data.file_fd = open(file_name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
	if (data.file_fd < 0) {
		pr_err("%s: cannot open %s: errno=%d (%s)\n",
			args->name, file_name, errno, strerror(errno));
		rc = EXIT_NO_RESOURCE;
		goto tidy_dir_fd;
	}

	(void)memset(ret, 0, sizeof(ret));
	keep_running = true;

	for (i = 0; i < MAX_INODE_FLAG_THREADS; i++) {
		pa[i].args = args;
		pa[i].data = (void *)&data;
		pa[i].pthread_ret = 0;

		ret[i] = pthread_create(&pthreads[i], NULL,
				stress_inode_flags_thread, &pa[i]);
	}

	stress_set_proc_state(args->name, STRESS_STATE_RUN);

	do {
		stress_inode_flags_stressor(args, &data);
	} while (keep_stressing(args));

	keep_running = false;
	rc = EXIT_SUCCESS;

	for (i = 0; i < MAX_INODE_FLAG_THREADS; i++) {
		if (ret[i] == 0) {
			(void)pthread_join(pthreads[i], NULL);
			if (pa[i].pthread_ret < 0)
				rc = EXIT_FAILURE;
		}
	}

	stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
	(void)close(data.file_fd);
tidy_dir_fd:
	stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
	(void)close(data.dir_fd);
tidy:
	stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
	(void)shim_unlink(file_name);
	stress_temp_dir_rm_args(args);
	(void)shim_pthread_spin_destroy(&spinlock);

	return rc;
}

stressor_info_t stress_inode_flags_info = {
	.stressor = stress_inode_flags,
	.class = CLASS_OS | CLASS_FILESYSTEM,
	.help = help
};
#else
stressor_info_t stress_inode_flags_info = {
	.stressor = stress_not_implemented ,
	.class = CLASS_OS | CLASS_FILESYSTEM,
	.help = help
};
#endif
