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

#include "test.h"
#include "testdata.h"
#include "testnet.h"
#include "testsync.h"
#include <errno.h>
#include <librecast_pvt.h>
#include <librecast/mdex.h>
#include <librecast/mtree.h>
#include <librecast/net.h>
#include <librecast/sync.h>
#include <sys/resource.h>
#include <unistd.h>

#define LINUX_SRC "/usr/src/linux-6.5.5"
#define MAXFILESZ 1048576
#define MAXFILES 10
#define MAXDIRS 5
#define DEPTH 2
#define TIMEOUT_SECONDS 3600

/*
 * test 0122
 *
 * Sync the Linux kernel using lc_syncfile()
 *
 * Compare the resulting directories and ensure they match.
 */

static sem_t sem_recv;

struct pkg_s {
	char *src;
	char *dst;
};

/* append trailing slash to string, return allocated new string */
char *appendslash(char *str)
{
	size_t len = strlen(str);
	if (str[len - 1] == '/') return strdup(str);
	char *newstr = malloc(len + 2);
	assert(newstr);
	strcpy(newstr, str);
	newstr[len] = '/';
	newstr[len + 1] = '\0';
	return newstr;
}

void *thread_recv(void *arg)
{
	struct pkg_s *pkg = (struct pkg_s *)arg;
	const int flags =
		SYNC_RECURSE | SYNC_ATIME | SYNC_MTIME | SYNC_OWNER | SYNC_GROUP | SYNC_MODE;
	ssize_t rc;
	lc_ctx_t *lctx = lc_ctx_new();
	pthread_cleanup_push((void (*)(void *))lc_ctx_free, lctx);
	lc_ctx_stream(lctx, stderr);
	lc_ctx_debug(lctx, LCTX_DEBUG_SYNCFILE);
	char *slashed = appendslash(pkg->src);
	assert(slashed);
	pthread_cleanup_push(free, slashed);
	test_log("src (with trailing slash appended): '%s'\n", slashed);
	rc = lc_syncfile_hash(lctx, slashed, pkg->dst, NULL, NULL, NULL, flags);
	test_assert(rc > 0, "lc_syncfile_hash returned %zi", rc);
	if (rc == -1) perror("lc_syncfile_hash");
	pthread_cleanup_pop(1); /* free(slashed) */
	pthread_cleanup_pop(1); /* lc_ctx_free */
	sem_post(&sem_recv);
	return NULL;
}

int main(int argc, char *argv[])
{
	(void)argc;
	lc_ctx_t *lctx;
	mdex_t *mdex;
	lc_share_t *share;
	pthread_t tid_recv;
	struct pkg_s pkg_recv = {0};
	struct timespec timeout = {0};
	char *dst = NULL;
	char *rpath = NULL;
	int rc;

	test_cap_require(CAP_NET_ADMIN);
	test_name("lc_syncfile() - recursive file syncing");
	test_require_net(TEST_NET_BASIC);

	/* create source directory tree and files */
#if 0
	char *src = NULL;
	rc = test_createtestdirs(basename(argv[0]), &src, &dst);
	if (!test_assert(rc == 0, "test_createtestdirs()")) return test_status;
	test_random_meta(src, TEST_OWN|TEST_MOD);
	rc = test_createtesttree(src, MAXFILESZ, MAXFILES, MAXDIRS, DEPTH, TEST_OWN|TEST_MOD);
	if (!test_assert(rc == 0, "test_createtesttree()")) goto err_free_src_dst;
#else
	char *src = LINUX_SRC;
	rc = test_createtestdir(basename(argv[0]), &dst, "dst");
	if (!test_assert(rc == 0, "test_createtestdir() - dst directory")) goto err_free_src_dst;
#endif

	/* how many open files are we permitted? */
	struct rlimit rlim;
	rc = getrlimit(RLIMIT_NOFILE, &rlim);
	test_log("RLIMIT_NOFILE cur = %i, max = %i\n", rlim.rlim_cur, rlim.rlim_max);

	/* use realpath for source */
	rpath = realpath(src, NULL);

	/* index source tree */
	mdex = mdex_init(512);
	if (!test_assert(mdex != NULL, "mdex_init()")) goto err_free_src_dst;
	mdex->stream = stderr;
	mdex->debug = MDEX_DEBUG_FILE;
	test_log("mdexing files...\n");
	rc = mdex_addfile(mdex, rpath, NULL, MDEX_RECURSE);
	test_log("mdexing done.\n");
	if (!test_assert(rc == 0, "mdex_addfile() returned %i", rc)) goto err_mdex_free;

#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; }
#endif

	/* share the mdex */
	lctx = lc_ctx_new();
	if (!test_assert(lctx != NULL, "lc_ctx_new()")) goto err_mdex_free;
	share = lc_share(lctx, mdex, 0, NULL, NULL, LC_SHARE_LOOPBACK);
	if (!test_assert(share != NULL, "lc_share()")) goto err_free_lctx;

	/* start receive thread, sync files */
	rc = sem_init(&sem_recv, 0, 0);
	if (!test_assert(rc == 0, "sem_init(sem_recv)")) goto err_unshare;
	pkg_recv.src = mdex_sharepath(mdex, rpath);
	pkg_recv.dst = dst;
	test_log("syncing '%s' => '%s'\n", pkg_recv.src, pkg_recv.dst);
	rc = pthread_create(&tid_recv, NULL, thread_recv, &pkg_recv);
	if (!test_assert(rc == 0, "create recv thread")) goto err_sem_destroy;

	/* handle timeout */
	clock_gettime(CLOCK_REALTIME, &timeout);
	timeout.tv_sec += TIMEOUT_SECONDS;
	if ((rc = sem_timedwait(&sem_recv, &timeout)) == -1 && errno == ETIMEDOUT) {
		pthread_cancel(tid_recv);
	}
	test_assert(rc == 0, "timeout waiting for recv thread");
	pthread_join(tid_recv, NULL);

	/* verify src and dst match */
	test_verify_dirs(rpath, dst);
err_sem_destroy:
	sem_destroy(&sem_recv);
err_unshare:
	lc_unshare(share);
err_free_lctx:
	lc_ctx_free(lctx);
err_mdex_free:
	mdex_free(mdex);
err_free_src_dst:
	free(dst);
	free(pkg_recv.src);
	free(rpath);
	return test_status;
}
