/*
 * Copyright (C) 2022-2026 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"
#include "core-builtin.h"
#include "core-mmap.h"

#if defined(HAVE_POLL_H)
#include <poll.h>
#endif

#define STRESS_RING_PIPE_NUM_MIN	(4)
#define STRESS_RING_PIPE_NUM_MAX	(256*1024)

#define STRESS_RING_PIPE_SIZE_MIN	(1)
#define STRESS_RING_PIPE_SIZE_MAX	(4096)

typedef struct {
	int fds[2];
} pipe_fds_t;

static const stress_help_t help[] = {
	{ NULL, "ring-pipe N",		"start N workers exercising a ring of pipes" },
	{ NULL,	"ring-pipe-num N",	"number of pipes to use" },
	{ NULL,	"ring-pipe-ops N",	"stop after N ring pipe I/O bogo operations" },
	{ NULL,	"ring-pipe-size N",	"size of data to be written and read in bytes" },
	{ NULL, "ring-pipe-splice",	"use splice instead of read+write" },
	{ NULL,	NULL,			NULL }
};

static const stress_opt_t opts[] = {
	{ OPT_ring_pipe_num,	"ring-pipe-num",    TYPE_ID_SIZE_T, STRESS_RING_PIPE_NUM_MIN, STRESS_RING_PIPE_NUM_MAX, NULL },
	{ OPT_ring_pipe_size,	"ring-pipe-size",   TYPE_ID_SIZE_T_BYTES_VM, STRESS_RING_PIPE_SIZE_MIN, STRESS_RING_PIPE_SIZE_MAX, NULL },
	{ OPT_ring_pipe_splice,	"ring-pipe-splice", TYPE_ID_BOOL, 0, 1, NULL },
	END_OPT,
};

#if defined(HAVE_POLL_H) &&	\
    defined(HAVE_POLL)
static int stress_pipe_non_block(stress_args_t *args, const int fd)
{
	int flags;
	int ret;

	flags = fcntl(fd, F_GETFL);
	if (UNLIKELY(flags < 0)) {
		pr_inf("%s: fcntl F_GETFL failed pipe fd %d, errno=%d (%s)\n",
			args->name, fd, errno, strerror(errno));
		return -1;
	}
	flags |= O_NONBLOCK;
	ret = fcntl(fd, F_SETFL, flags);
	if (UNLIKELY(ret < 0)) {
		pr_inf("%s: set O_NONBLOCK on pipe fd %d failed, errno=%d (%s)\n",
			args->name, fd, errno, strerror(errno));
		return -1;
	}
	return 0;
}

/*
 *  stress_pipe_read()
 *	read data from a pipe, return bytes read, -1 for failure
 */
static ssize_t stress_pipe_read(
	stress_args_t *args,
	const int fd,
	char *buf,
	const size_t buf_len)
{
	ssize_t sret;

	sret = read(fd, buf, buf_len);
	if (UNLIKELY(sret < 0)) {
		pr_inf("%s: read from pipe fd %d failed, errno=%d (%s)\n",
			args->name, fd, errno, strerror(errno));
		return -1;
	}
	return sret;
}

/*
 *  stress_pipe_write
 *	write data to a pipe, return bytes written, -1 for failure
 */
static ssize_t stress_pipe_write(
	stress_args_t *args,
	const int fd,
	const char *buf,
	const size_t buf_len)
{
	ssize_t sret;

	sret = write(fd, buf, buf_len);
	if (UNLIKELY(sret < 0)) {
		pr_inf("%s: write to pipe fd %d failed, errno=%d (%s)\n",
			args->name, fd, errno, strerror(errno));
		return -1;
	}
	return sret;
}

/*
 *  stress_ring_pipe
 *	stress by heavy pipe I/O in a ring of pipes
 */
static int stress_ring_pipe(stress_args_t *args)
{
	double duration = 0.0;
	double bytes = 0.0;
	double rate;
	size_t i;
	size_t n_pipes;
	size_t ring_pipe_num = 256;
	size_t ring_pipe_size = 4096;
	bool ring_pipe_splice = false;
	char *buf;
	pipe_fds_t *pipe_fds;
	int ret;
	int max_fd;
	int rc = EXIT_NO_RESOURCE;
	ssize_t sret;
	struct pollfd *poll_fds;

	(void)stress_setting_get("ring-pipe-num", &ring_pipe_num);
	(void)stress_setting_get("ring-pipe-size", &ring_pipe_size);
	(void)stress_setting_get("ring-pipe-splice", &ring_pipe_splice);

	buf = (char *)stress_mmap_populate(NULL, (size_t)STRESS_RING_PIPE_SIZE_MAX,
		PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
	if (buf == MAP_FAILED) {
		pr_inf_skip("%s: mmap %d size buffer failed%s, "
			"errno=%d (%s), skipping stressor\n",
			args->name, STRESS_RING_PIPE_SIZE_MAX,
			stress_memory_free_get(), errno, strerror(errno));
		goto err_ret;
	}
	stress_memory_anon_name_set(buf, STRESS_RING_PIPE_SIZE_MAX, "ring-pipe-buffer");

	pipe_fds = (pipe_fds_t *)calloc(ring_pipe_num, sizeof(*pipe_fds));
	if (!pipe_fds) {
		pr_inf_skip("%s: allocate %zu pipe file descriptors failed%s, "
			"skipping stressor\n", args->name, ring_pipe_num,
			stress_memory_free_get());
		goto err_unmap_buf;
	}
	poll_fds = (struct pollfd *)calloc(ring_pipe_num, sizeof(*poll_fds));
	if (!poll_fds) {
		pr_inf_skip("%s: allocate %zu poll descriptors failed%s, "
			"skipping stressor\n", args->name, ring_pipe_num,
			stress_memory_free_get());
		goto err_free_pipe_fds;
	}

	for (max_fd = 0, n_pipes = 0; n_pipes < ring_pipe_num; n_pipes++) {
		ret = pipe(pipe_fds[n_pipes].fds);
		if (ret < 0)
			break;

		poll_fds[n_pipes].fd = pipe_fds[n_pipes].fds[0];
		poll_fds[n_pipes].events = POLLIN;
		poll_fds[n_pipes].revents = 0;

		if (max_fd < pipe_fds[n_pipes].fds[0])
			max_fd = pipe_fds[n_pipes].fds[0];
		if (max_fd < pipe_fds[n_pipes].fds[1])
			max_fd = pipe_fds[n_pipes].fds[1];
		(void)stress_pipe_non_block(args, pipe_fds[n_pipes].fds[0]);
		(void)stress_pipe_non_block(args, pipe_fds[n_pipes].fds[1]);

	}

	if (n_pipes == 0) {
		pr_inf_skip("%s: not enough pipes were created, "
			"skipping stressor\n", args->name);
		goto err_close_pipes;
	} else if (n_pipes < ring_pipe_num) {
		pr_inf("%s: limiting to %zu pipes due to file descriptor limit\n",
			args->name, n_pipes);
	}

#if !defined(HAVE_SPLICE)
	if (stress_instance_zero(args) && ring_pipe_splice) {
		pr_inf("%s: note: falling back to using read + writes as "
			"splice is not available\n", args->name);
		ring_pipe_splice = false;
	}
#endif

	if (stress_instance_zero(args)) {
		pr_inf("%s: using %zu pipes with %zu byte data, %s\n",
			args->name, n_pipes, ring_pipe_size,
			ring_pipe_splice ? "using splice" : "using read+write");
	}

	stress_proc_state_set(args->name, STRESS_STATE_SYNC_WAIT);
	stress_sync_start_wait(args);
	stress_proc_state_set(args->name, STRESS_STATE_RUN);

	(void)shim_memset(buf, 0xa5, STRESS_RING_PIPE_SIZE_MAX);

	if (stress_pipe_write(args, pipe_fds[0].fds[1], buf, ring_pipe_size) < 0)
		goto err_deinit;
	if (stress_pipe_write(args, pipe_fds[n_pipes >> 1].fds[1], buf, ring_pipe_size) < 0)
		goto err_deinit;

	do {
		ret = poll(poll_fds, n_pipes, 100);
		if (UNLIKELY(ret == 0)) {
			pr_inf("%s: unexpected poll timeout\n", args->name);
			break;
		} else {
			for (i = 0; LIKELY(stress_continue(args) && (i < n_pipes)); i++) {
				if (poll_fds[i].revents & POLLIN) {
					double t;
					register size_t j = (i + 1);

					j = (j >= n_pipes) ? 0 : j;
#if defined(HAVE_SPLICE)
					if (ring_pipe_splice) {
#if defined(SPLICE_F_MOVE)
						int flag = SPLICE_F_MOVE;
#else
						int flag = 0;
#endif
						t = stress_time_now();
						sret = splice(pipe_fds[i].fds[0], NULL, pipe_fds[j].fds[1], NULL,
							      ring_pipe_size, flag);
						if (UNLIKELY(sret < 0)) {
							pr_inf("%s: splice failed, errno=%d (%s)\n",
								args->name, errno, strerror(errno));
							goto finish;
						}
						duration += stress_time_now() - t;
						stress_bogo_inc(args);
						bytes += (double)sret;
						continue;
					}
#endif
					t = stress_time_now();
					sret = stress_pipe_read(args, pipe_fds[i].fds[0], buf, ring_pipe_size);
					if (UNLIKELY(sret < 0))
						goto finish;
					sret = stress_pipe_write(args, pipe_fds[j].fds[1], buf, sret);
					if (UNLIKELY(sret < 0))
						goto finish;
					duration += stress_time_now() - t;
					stress_bogo_inc(args);
					bytes += (double)sret;
				}
			}
		}
	} while (stress_continue(args));
	rc = EXIT_SUCCESS;

finish:
	rate = (duration > 0.0) ? (double)stress_bogo_get(args) / duration : 0.0;
	stress_metrics_set(args, "pipe read+write calls per sec",
		rate, STRESS_METRIC_HARMONIC_MEAN);
	rate = (duration > 0.0) ? (double)bytes / duration : 0.0;
	stress_metrics_set(args, "MB per sec data pipe write",
		rate / (double)MB, STRESS_METRIC_HARMONIC_MEAN);

err_deinit:
	stress_proc_state_set(args->name, STRESS_STATE_DEINIT);
err_close_pipes:
	for (i = 0; i < n_pipes; i++) {
		(void)close(pipe_fds[i].fds[0]);
		(void)close(pipe_fds[i].fds[1]);
	}
	free(poll_fds);
err_free_pipe_fds:
	free(pipe_fds);
err_unmap_buf:
	(void)munmap((void *)buf, STRESS_RING_PIPE_SIZE_MAX);
err_ret:
	return rc;
}

static const stress_exercises_t exercises[] = {
	STRESS_EX_FEATURE("hot-package"),
	STRESS_EX_FEATURE("ipc"),
	STRESS_EX_FEATURE("system-time"),

#if defined(HAVE_POLL_H) &&	\
    defined(HAVE_POLL)
	STRESS_EX_SYSCALL("fcntl"),
#endif
	STRESS_EX_SYSCALL("poll"),
	STRESS_EX_SYSCALL("read"),
	STRESS_EX_SYSCALL("write"),

	STRESS_EX_END,
};

const stressor_info_t stress_ring_pipe_info = {
	.stressor = stress_ring_pipe,
	.classifier = CLASS_PIPE_IO | CLASS_OS,
	.opts = opts,
	.verify = VERIFY_NONE,
	.help = help,
	.exercises = exercises,
};

#else

const stressor_info_t stress_ring_pipe_info = {
	.stressor = stress_unimplemented,
	.classifier = CLASS_PIPE_IO | CLASS_OS,
	.opts = opts,
	.verify = VERIFY_NONE,
	.help = help,
	.unimplemented_reason = "built without poll.h or poll() support"
};

#endif
