/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2023-2024 Brett Sheffield <bacs@librecast.net> */

/* test 0038: single file syncing
 *
 * - create test file
 * - fork and exec lcshare serving the source file with --loopback
 * - fork and exec lcsync with to sync the source file to the destination
 * - compare
 * - scratch destination file and re-sync
 */

#include "test.h"
#include "testdata.h"
#include "testsync.h"
#include "testnet.h"
#include "globals.h"
#include "sync.h"
#include "arg.h"
#include "sec.h"
#include <assert.h>
#include <errno.h>
#include <libgen.h>
#include <librecast/mdex.h>
#include <semaphore.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>

#define MAXFILESZ 1048576
#define TIMEOUT_SECONDS 20

sem_t sem;

enum {
	LCSHARE = 0,
	LCSYNC  = 1
};

static void handle_sigchld(int signo, siginfo_t *info, void *context)
{
	(void)signo, (void)info, (void)context;
	sem_post(&sem);
}

void log_commandline(int argc, char *argv[])
{
	for (int i = 0; i < argc; i++) {
		test_log("%s ", argv[i]);
	}
	test_log("\n");
}

void exec_lcshare(char *src)
{
	char prog[] = "lcshare";
	char *argv0[] = { prog, "-b", "--loopback", NULL, NULL };
	char **argv;
	int argc = sizeof argv0 / sizeof argv0[0] - 1;
	argv = argv0;
	argv0[3] = src;
	log_commandline(argc, argv);
	if (execv("../src/lcshare", argv) == -1) {
		perror("execv");
	}
	exit(EXIT_FAILURE);
}

void exec_lcsync(char *src, char *dst)
{
	char prog[] = "lcsync";
	char *argv0[] = { prog, "-b", "--remote", src, dst, NULL };
	char **argv;
	int argc = sizeof argv0 / sizeof argv0[0] - 1;

	argv = argv0;

	log_commandline(argc, argv);
	if (execv("../src/lcsync", argv) == -1) perror("execv");
	exit(EXIT_FAILURE);
}

int main(void)
{
	char src[] = "0000-0038.src.tmp.XXXXXX";
	char dst[] = "0000-0038.dst.tmp.XXXXXX";
	size_t sz_s = MAXFILESZ;
	int fd;
	int repeat = 1;

	test_cap_require(CAP_NET_ADMIN);
	test_name("commandline: (remote) sync single file");
	test_require_net(TEST_NET_BASIC);

	/* create source directory tree and files */
	fd = test_data_file(src, sz_s, TEST_TMP | TEST_RND);
	close(fd);
	/* use last 6 chars of src filename for destination */
	size_t off = strlen(src) - 6;
	memcpy(dst + off, src + off, 6);

#if 0
	/* XXX; match src and dst match */
	char cmd[128];
	snprintf(cmd, sizeof cmd, "cp -p %s %s", src, dst);
	test_log("`%s`\n", cmd);
	if (system(cmd)) { test_assert(0, "cmd failed"); return test_status; }
#else

	if (sem_init(&sem, 0, 0) == -1) return test_status;

	/* first, start lcshare () */
	pid_t pid[2];
	pid[LCSHARE] = fork();
	if (!test_assert(pid[LCSHARE] != -1, "fork [LCSHARE]")) {
		goto err_free_sem;
	}
	else if (pid[LCSHARE] == 0) {
		exec_lcshare(src);
		sleep(1); /* give lcshare time to index files */
	}

resync:
	/* now run lcsync */
	pid[LCSYNC] = fork();
	if (!test_assert(pid[LCSYNC] != -1, "fork [LCSYNC]")) {
		goto err_free_sem;
	}
	if (pid[LCSYNC]) {
		/* parent - set timed wait for child process */
		struct sigaction act = {0};
		act.sa_sigaction = &handle_sigchld;
		if (sigaction(SIGCHLD, &act, NULL) == -1) {
			perror("sigaction");
		}
		else {
			struct timespec ts = { .tv_sec = TIMEOUT_SECONDS };
			clock_gettime(CLOCK_REALTIME, &ts);
			ts.tv_sec += TIMEOUT_SECONDS;
			if (!test_assert(sem_timedwait(&sem, &ts) == 0 || errno == EINTR, "timeout")) {
				perror("sem_timedwait");
			}
		}
	}
	else {
		exec_lcsync(src, dst);
	}
	if (test_status != TEST_OK) goto err_free_sem;
#endif

	/* verify */
	sleep(1);
	test_assert(test_file_match(src, dst) == 0, "test_file_match(): dst matches src");

	while (repeat--) {
		test_log("scratching destination file, resyncing\n");
		test_file_scratch(dst, 0, 42);
		goto resync;
	}

	/* now signal lcshare to stop */
	test_log("sending SIGINT to lcshare\n");
	kill(pid[LCSHARE], SIGINT);

err_free_sem:
	sem_destroy(&sem);
	return test_status;
}
