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

/* test 0033:
 * The aim of this test is to check that argument handling works when the shared
 * files are NOT in subdirectories of the CWD.
 *
 * - create a pair of directories, and generate a source tree of files and
 *   directories (trailing slash on src)
 * - create another directory and cd into it
 * - fork and exec lcshare serving the source tree with --loopback ../src
 * - fork and exec lcsync with --archive --remote to recursively sync the source
 *   tree to the destination directory
 * - compare
 */

#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 MAXFILES 5
#define MAXDIRS 5
#define DEPTH 2
#define TIMEOUT_SECONDS 20

static 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, "-a", "-b", "--remote", NULL, NULL, NULL };
	char *sharepath;
	char **argv;
	size_t len;
	int argc = sizeof argv0 / sizeof argv0[0] - 1;

	sharepath = strdup(src);
	argv0[5] = dst;
	argv = argv0;

	/* append trailing slash to src */
	len = strlen(sharepath);
	argv0[4] = realloc(sharepath, len + 2);
	if (!argv0[4]) exit(EXIT_FAILURE);
	argv0[4][len] = '/';
	argv0[4][len + 1] = '\0';

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

int main(int _argc, char *_argv[])
{
	(void) _argc; /* unused */
	char *src = NULL, *dst = NULL, *wd = NULL;
	char rsrc[] = "../0000-0033.src.tmp.XXXXXX";
	char rdst[] = "../0000-0033.src.tmp.XXXXXX";
	int rc;

	test_cap_require(CAP_NET_ADMIN);
	test_name("commandline: (remote) recursive directory syncing (lcshare ../src)");
	test_require_net(TEST_NET_BASIC);

	/* create source directory tree and files */
	rc = test_createtestdirs(basename(_argv[0]), &src, &dst);
	if (!test_assert(rc == 0, "test_createtestdirs()")) return test_status;
	rc = test_createtesttree(src, MAXFILESZ, MAXFILES, MAXDIRS, DEPTH, 0);
	if (!test_assert(rc == 0, "test_createtesttree()")) goto err_free_src_dst;

	/* create working directory and change into it */
	rc = test_createtestdir(basename(_argv[0]), &wd, "src");
	if (!test_assert(rc == 0, "test_createtestdir() (working directory)")) goto err_free_src_dst;
	rc = chdir(wd);
	if (!test_assert(rc == 0, "chdir() (working directory)")) goto err_free_wd;

	/* build relative paths to ../src and ../dst */
	strcpy(rsrc + 3, src);
	strcpy(rdst + 3, dst);

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

	if (sem_init(&sem, 0, 0) == -1) goto err_free_wd;

	/* 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(rsrc);
		sleep(1); /* give lcshare time to index files */
	}

	/* 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");
			}
		}
		/* now signal lcshare to stop */
		test_log("sending SIGINT to lcshare\n");
		kill(pid[LCSHARE], SIGINT);
	}
	else {
		exec_lcsync(src, rdst);
	}
	if (test_status != TEST_OK) goto err_free_sem;

#endif

	/* verify */
	test_verify_dirs(rsrc, rdst);
err_free_sem:
	sem_destroy(&sem);
err_free_wd:
	free(wd);
err_free_src_dst:
	free(src); free(dst);
	return test_status;
}
