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

/* test 0035:
 *
 * recursive network sync of subdirectory
 *
 * eg. if we are sharing:
 *
 *   lcshare -b --loopback /usr/src/linux
 *
 * then we are syncing a subdirectory like so:
 *   lcsync -a -b --remote linux/scripts /tmp # should create /tmp/linux/scripts
 *
 */

#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 <string.h>
#include <sys/wait.h>

#define MAXFILESZ 1048576
#define MAXFILES 5
#define MAXDIRS 5
#define DEPTH 2
#define TIMEOUT_SECONDS 20

enum {
	LCSHARE = 0,
	LCSYNC  = 1
};

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

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

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

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

int main(int _argc, char *_argv[])
{
	(void) _argc; /* unused */
	char *src = NULL, *dst = NULL, *wd = NULL;
	char *rsrc = NULL;
	char *rdst = NULL;
	char *sub = NULL;
	char *owd = NULL;
	int rc;

	test_cap_require(CAP_NET_ADMIN);
	test_name("commandline: (remote) recursive directory syncing (subdir of 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 new src directory */
	rc = test_createtestdir(basename(_argv[0]), &wd, "src");
	rc = snprintf(NULL, 0, "%s/subdir", wd) + 1;
	sub = malloc(rc);
	if (!test_assert(sub != NULL, "malloc(sub)")) goto err_free_src_dst;
	snprintf(sub, rc, "%s/subdir", wd);
	rc = rename(src, sub);
	if (!test_assert(rc == 0, "rename '%s' => '%s'", src, sub)) {
		perror("rename");
		goto err_free_src_dst;
	}

	/* build absolute path for dst */
	rsrc = realpath(wd, NULL);
	rdst = realpath(dst, NULL);
	src = wd;

	fprintf(stderr, "src: '%s'\n", src);
	fprintf(stderr, "dst: '%s'\n", dst);
	fprintf(stderr, "rsrc: '%s'\n", rsrc);
	fprintf(stderr, "rdst: '%s'\n", rdst);
	fprintf(stderr, "sub: '%s'\n", sub);

	/* create working directory and change into it */
	owd = getcwd(NULL, 0);
	if (!test_assert(owd != NULL, "getcwd()")) goto err_free_src_dst;
	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;

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

	/* first, start lcshare () */
	pid_t pid[2];
	pid[LCSHARE] = fork();
	if (!test_assert(pid[LCSHARE] != -1, "fork [LCSHARE]")) {
		goto err_free_wd;
	}
	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_wd;
	}
	if (pid[LCSYNC]) {
		/* parent - set timed wait for child process */
		struct timespec ts = { .tv_sec = TIMEOUT_SECONDS };
		sigset_t set;
		sigemptyset(&set);
		sigaddset(&set, SIGCHLD);
		rc = sigprocmask(SIG_BLOCK, &set, NULL);
		if (rc != -1) {
			rc = sigtimedwait(&set, NULL, &ts);
			if (rc == -1) {
				test_assert(rc != -1, "timeout");
			}
			else {
				if (errno == EAGAIN) {
					test_log("timeout\n");
					test_log("sending SIGINT to lcsync\n");
					kill(pid[LCSYNC], SIGINT);
				}
			}
		}
		else {
			perror("sigprocmask");
			test_status = TEST_FAIL;
		}

		/* now signal lcshare to stop */
		test_log("sending SIGINT to lcshare\n");
		kill(pid[LCSHARE], SIGINT);
	}
	else {
		exec_lcsync(sub, rdst);
	}
	if (test_status != TEST_OK) goto err_free_wd;

#endif

	/* verify */
	rc = chdir(owd);
	if (!test_assert(rc == 0, "chdir(%s)", rsrc)) goto err_free_wd;
	free(owd);
	test_verify_dirs(sub, rdst);

err_free_wd:
	free(wd);
err_free_src_dst:
	free(rsrc); free(rdst);
	free(src); free(dst);
	return test_status;
}
