// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 RedHat Inc.  All Rights Reserved.
 * Author: Andreas Gruenbacher <agruenba@redhat.com>
 *
 * Make sure that reading and writing to a pipe via splice.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <malloc.h>

#define SECTOR_SIZE 512
#define BUFFER_SIZE (150 * SECTOR_SIZE)

void read_from_pipe(int fd, const char *filename, size_t size)
{
	char buffer[SECTOR_SIZE];
	size_t sz;
	ssize_t ret;

	while (size) {
		sz = size;
		if (sz > sizeof buffer)
			sz = sizeof buffer;
		ret = read(fd, buffer, sz);
		if (ret < 0)
			err(1, "read: %s", filename);
		if (ret == 0) {
			fprintf(stderr, "read: %s: unexpected EOF\n", filename);
			exit(1);
		}
		size -= sz;
	}
}

void do_splice1(int fd, const char *filename, size_t size)
{
	bool retried = false;
	int pipefd[2];

	if (pipe(pipefd) == -1)
		err(1, "pipe");
	while (size) {
		ssize_t spliced;

		spliced = splice(fd, NULL, pipefd[1], NULL, size, SPLICE_F_MOVE);
		if (spliced == -1) {
			if (errno == EAGAIN && !retried) {
				retried = true;
				fprintf(stderr, "retrying splice\n");
				sleep(1);
				continue;
			}
			err(1, "splice");
		}
		read_from_pipe(pipefd[0], filename, spliced);
		size -= spliced;
	}
	close(pipefd[0]);
	close(pipefd[1]);
}

void do_splice2(int fd, const char *filename, size_t size)
{
	bool retried = false;
	int pipefd[2];
	int pid;

	if (pipe(pipefd) == -1)
		err(1, "pipe");

	pid = fork();
	if (pid == 0) {
		close(pipefd[1]);
		read_from_pipe(pipefd[0], filename, size);
		exit(0);
	} else {
		close(pipefd[0]);
		while (size) {
			ssize_t spliced;

			spliced = splice(fd, NULL, pipefd[1], NULL, size, SPLICE_F_MOVE);
			if (spliced == -1) {
				if (errno == EAGAIN && !retried) {
					retried = true;
					fprintf(stderr, "retrying splice\n");
					sleep(1);
					continue;
				}
				err(1, "splice");
			}
			size -= spliced;
		}
		close(pipefd[1]);
		waitpid(pid, NULL, 0);
	}
}

void usage(const char *argv0)
{
	fprintf(stderr, "USAGE: %s [-rd] {filename}\n", basename(argv0));
	exit(2);
}

int main(int argc, char *argv[])
{
	void (*do_splice)(int fd, const char *filename, size_t size);
	const char *filename;
	char *buffer;
	int opt, open_flags, fd;
	ssize_t ret;

	do_splice = do_splice1;
	open_flags = O_CREAT | O_TRUNC | O_RDWR | O_DIRECT;

	while ((opt = getopt(argc, argv, "rd")) != -1) {
		switch(opt) {
		case 'r':
			do_splice = do_splice2;
			break;
		case 'd':
			open_flags &= ~O_DIRECT;
			break;
		default:  /* '?' */
			usage(argv[0]);
		}
	}

	if (optind >= argc)
		usage(argv[0]);
	filename = argv[optind];

	printf("%s reader %s O_DIRECT\n",
		   do_splice == do_splice1 ? "sequential" : "concurrent",
		   (open_flags & O_DIRECT) ? "with" : "without");

	buffer = memalign(SECTOR_SIZE, BUFFER_SIZE);
	if (buffer == NULL)
		err(1, "memalign");

	fd = open(filename, open_flags, 0666);
	if (fd == -1)
		err(1, "open: %s", filename);

	memset(buffer, 'x', BUFFER_SIZE);
	ret = write(fd, buffer, BUFFER_SIZE);
	if (ret < 0)
		err(1, "write: %s", filename);
	if (ret != BUFFER_SIZE) {
		fprintf(stderr, "%s: short write\n", filename);
		exit(1);
	}

	ret = lseek(fd, 0, SEEK_SET);
	if (ret != 0)
		err(1, "lseek: %s", filename);

	do_splice(fd, filename, BUFFER_SIZE);

	if (unlink(filename) == -1)
		err(1, "unlink: %s", filename);

	return 0;
}
