// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) International Business Machines  Corp., 2012
 * Copyright (c) Linux Test Project, 2012
 * Copyright (C) 2021 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
 */

/*\
 * [Description]
 *
 * Fork two children, one child mallocs randomly sized trunks of memory
 * and initializes them; the other child calls process_vm_readv with
 * the remote iovecs initialized to the original process memory
 * locations and the local iovecs initialized to randomly sized and
 * allocated local memory locations. The second child then verifies
 * that the data is copied correctly.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "tst_test.h"
#include "lapi/syscalls.h"

#define MAX_IOVECS 1024

static struct tcase {
	int bufsize;
	int remote_iovecs;
	int local_iovecs;
} testcases[] = {
	{ .bufsize = 1024, .remote_iovecs = 1024, .local_iovecs = 8 },
	{ .bufsize = 1024, .remote_iovecs = 512, .local_iovecs = 16 },
	{ .bufsize = 1024, .remote_iovecs = 256, .local_iovecs = 32 },
	{ .bufsize = 1024, .remote_iovecs = 128, .local_iovecs = 64 },
	{ .bufsize = 1024, .remote_iovecs = 64, .local_iovecs = 128 },
	{ .bufsize = 1024, .remote_iovecs = 32, .local_iovecs = 256 },
	{ .bufsize = 1024, .remote_iovecs = 16, .local_iovecs = 512 },
	{ .bufsize = 1024, .remote_iovecs = 8, .local_iovecs = 1024 },

	{ .bufsize = 131072, .remote_iovecs = 1024, .local_iovecs = 8 },
	{ .bufsize = 131072, .remote_iovecs = 512, .local_iovecs = 16 },
	{ .bufsize = 131072, .remote_iovecs = 256, .local_iovecs = 32 },
	{ .bufsize = 131072, .remote_iovecs = 128, .local_iovecs = 64 },
	{ .bufsize = 131072, .remote_iovecs = 64, .local_iovecs = 128 },
	{ .bufsize = 131072, .remote_iovecs = 32, .local_iovecs = 256 },
	{ .bufsize = 131072, .remote_iovecs = 16, .local_iovecs = 512 },
	{ .bufsize = 131072, .remote_iovecs = 8, .local_iovecs = 1024 },
};

static char **data_ptr;

static void create_data_size(int *arr, int arr_sz, int buffsize)
{
	long bufsz_left;
	int i;

	bufsz_left = buffsize;
	for (i = 0; i < arr_sz - 1; i++) {
		arr[i] = rand() % ((bufsz_left / 2) + 1);
		bufsz_left -= arr[i];
	}

	arr[arr_sz - 1] = bufsz_left;
}

static void child_alloc(const int *sizes, int nr_iovecs)
{
	int i, j;
	long count;

	count = 0;
	for (i = 0; i < nr_iovecs; i++) {
		data_ptr[i] = sizes[i] ? SAFE_MALLOC(sizes[i]) : NULL;

		for (j = 0; j < sizes[i]; j++) {
			data_ptr[i][j] = count % 256;
			count++;
		}
	}

	tst_res(TINFO, "child_alloc: memory allocated and initialized");

	TST_CHECKPOINT_WAKE_AND_WAIT(0);
}

static void child_read(const int *sizes, int local_iovecs, int remote_iovecs,
			 pid_t pid_alloc, int buffsize)
{
	struct iovec local[local_iovecs];
	struct iovec remote[remote_iovecs];
	int i, j;
	int count;
	int nr_error;
	int local_sizes[local_iovecs];
	unsigned char expect, actual;

	for (i = 0; i < remote_iovecs; i++) {
		remote[i].iov_base = (void *)data_ptr[i];
		remote[i].iov_len = sizes[i];
	}

	create_data_size(local_sizes, local_iovecs, buffsize);
	for (i = 0; i < local_iovecs; i++) {
		local[i].iov_base = SAFE_MALLOC(local_sizes[i]);
		local[i].iov_len = local_sizes[i];
	}

	tst_res(TINFO, "child_read: reading string from same memory location");

	TST_EXP_POSITIVE(tst_syscall(__NR_process_vm_readv, pid_alloc, local,
				     local_iovecs, remote, remote_iovecs, 0UL),
			 "process_vm_read()");

	if (TST_RET != buffsize) {
		tst_brk(TBROK, "process_vm_readv: expected %d bytes but got %ld",
			buffsize, TST_RET);
	}

	count = 0;
	nr_error = 0;
	for (i = 0; i < local_iovecs; i++) {
		for (j = 0; j < (int)local[i].iov_len; j++) {
			expect = count % 256;
			actual = ((unsigned char *)local[i].iov_base)[j];
			if (expect != actual)
				nr_error++;

			count++;
		}
	}

	if (nr_error)
		tst_brk(TFAIL, "child_read: %d incorrect bytes received", nr_error);
	else
		tst_res(TPASS, "child_read: all bytes are correctly received");
}

static void setup(void)
{
	tst_syscall(__NR_process_vm_readv, getpid(), NULL, 0UL, NULL, 0UL, 0UL);

	data_ptr = SAFE_MMAP(NULL, sizeof(void *) * MAX_IOVECS, PROT_READ | PROT_WRITE,
			     MAP_SHARED | MAP_ANONYMOUS, -1, 0);
}

static void cleanup(void)
{
	if (data_ptr)
		SAFE_MUNMAP(data_ptr, sizeof(void *) * MAX_IOVECS);
}

static void run(unsigned int i)
{
	int bufsize = testcases[i].bufsize;
	int remote_iovecs = testcases[i].remote_iovecs;
	int local_iovecs = testcases[i].local_iovecs;
	pid_t pid_alloc;
	pid_t pid_read;
	int status;
	int sizes[remote_iovecs];

	tst_res(TINFO, "bufsize=%d, remote_iovecs=%d, local_iovecs=%d", bufsize,
		remote_iovecs, local_iovecs);

	create_data_size(sizes, remote_iovecs, bufsize);

	pid_alloc = SAFE_FORK();
	if (!pid_alloc) {
		child_alloc(sizes, remote_iovecs);
		return;
	}

	TST_CHECKPOINT_WAIT(0);

	pid_read = SAFE_FORK();
	if (!pid_read) {
		child_read(sizes, local_iovecs, remote_iovecs, pid_alloc, bufsize);
		return;
	}

	SAFE_WAITPID(pid_read, &status, 0);
	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
		tst_res(TFAIL, "child_read: %s", tst_strstatus(status));

	TST_CHECKPOINT_WAKE(0);
}

static struct tst_test test = {
	.test = run,
	.setup = setup,
	.cleanup = cleanup,
	.forks_child = 1,
	.needs_checkpoints = 1,
	.tcnt = ARRAY_SIZE(testcases),
};
