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

/* test 0031:
 * - create a pair of directories, and generate a source tree of files and
 *   directories
 * - fork and exec lcshare serving the source tree with --loopback
 * - 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 <string.h>
#include <sys/wait.h>

#define MAXFILESZ 1048576
#define MAXFILES 10
#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", "-b", "--remote", NULL, NULL, NULL };
	char **argv;
	int argc = sizeof argv0 / sizeof argv0[0] - 1;

	argv0[4] = src;
	argv0[5] = dst;
	argv = argv0;
	log_commandline(argc, argv);
	/* ensure we own destination before dropping privs */
	if (chown(dst, getuid(), -1) == -1) goto err_exit;
	test_log("dropping privileges before executing lcsync\n");
	if (!test_assert(sec_drop_privs() == 0, "sec_drop_privs()")) goto err_exit;
	if (execv("../src/lcsync", argv) == -1) perror("execv");
err_exit:
	exit(EXIT_FAILURE);
}

int main(int _argc, char *_argv[])
{
	(void) _argc; /* unused */
	char *src, *dst, *dstsub;
	int rc;

	test_cap_require(CAP_NET_ADMIN);
	test_name("commandline: (remote) recursive directory syncing no trailing slash => SYNC_SUBDIR");
	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;

	rc = snprintf(NULL, 0, "%s/%s", dst, src) + 1;
	dstsub = malloc(rc);
	if (!dstsub) goto err_free_src_dst;
	snprintf(dstsub, rc, "%s/%s", dst, src);

#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

	/* first, start lcshare () */
	pid_t pid[2];
	pid[LCSHARE] = fork();
	if (!test_assert(pid[LCSHARE] != -1, "fork [LCSHARE]")) {
		goto err_free_src_dst;
	}
	else if (pid[LCSHARE] == 0) {
		exec_lcshare(src);
		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_src_dst;
	}
	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(src, dst);
	}
	if (test_status != TEST_OK) goto err_free_src_dst;

#endif

	/* verify */
	test_verify_dirs(src, dstsub);
	free(dstsub);

err_free_src_dst:
	free(src); free(dst);
	return test_status;
}
