1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2023-2024 Brett Sheffield <bacs@librecast.net> */
#define _DEFAULT_SOURCE /* be64toh */
#define _XOPEN_SOURCE 700 /* for nftw() */
#define _NETBSD_SOURCE /* required for scandir() on NetBSD */
#include "test.h"
#include "testdata.h"
#include "testnet.h"
#include <sys/types.h>
#include <errno.h>
#include <dirent.h>
#include <ftw.h>
#include <librecast_pvt.h>
#include <librecast/mdex.h>
#include <librecast/mtree.h>
#include <librecast/net.h>
#include <librecast/sync.h>
#include <unistd.h>
#if HAVE_RQ_OTI
#define MAXFILESZ 1048576
#define MAXFILES 2
#define MAXDIRS 2
#define DEPTH 2
#define TIMEOUT_SECONDS 3
/*
* test 0116
*
* create a tree of files and directories and use lc_syncfile() to sync
* the top-level directory non-recursively into the destination.
*
* Compare the resulting directories and ensure they match.
*/
static sem_t sem_recv;
struct pkg_s {
char *src;
char *dst;
};
void *thread_recv(void *arg)
{
struct pkg_s *pkg = (struct pkg_s *)arg;
unsigned char hash[HASHSIZE];
const int flags =
SYNC_SUBDIR | SYNC_ATIME | SYNC_MTIME | SYNC_OWNER | SYNC_GROUP | SYNC_MODE;
ssize_t rc;
mdex_aliashash(pkg->src, hash, sizeof hash);
fprintf(stderr, "%s() ", __func__);
hash_hex_debug(stderr, hash, sizeof hash);
lc_ctx_t *lctx = lc_ctx_new();
pthread_cleanup_push((void (*)(void *))lc_ctx_free, lctx);
rc = lc_syncfile(lctx, hash, pkg->dst, NULL, NULL, NULL, flags);
test_assert(rc > 0, "lc_syncfile returned %zi", rc);
pthread_cleanup_pop(1); /* lc_ctx_free */
sem_post(&sem_recv);
return NULL;
}
static int scandirfilter(const struct dirent *dir)
{
/* filter current and parent directories */
return !(!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, ".."));
}
/* check mode, owner, group, times */
static int statcmp(const char *src, const char *dst)
{
struct stat ssb, dsb;
int rc;
rc = stat(src, &ssb);
if (!test_assert(rc == 0, "stat '%s'", src)) return -1;
rc = stat(dst, &dsb);
if (!test_assert(rc == 0, "stat '%s'", dst)) return -1;
test_assert(ssb.st_mode == dsb.st_mode, "st_mode: '%s' %o %o", dst, ssb.st_mode, dsb.st_mode);
test_assert(ssb.st_uid == dsb.st_uid, "st_uid: '%s'", dst);
test_assert(ssb.st_gid == dsb.st_gid, "st_gid: '%s'", dst);
test_assert(ssb.st_size == dsb.st_size, "st_size: '%s'", dst);
#ifndef HAVE_UTIMENSAT
/* without utimensat(), we only have microsecond precision */
ssb.st_mtim.tv_nsec /= 1000; ssb.st_mtim.tv_nsec *= 1000;
ssb.st_atim.tv_nsec /= 1000; ssb.st_atim.tv_nsec *= 1000;
#endif
/* No point testing atime unless the filesystem is mounted noatime */
/* test_assert(ssb.st_atim.tv_nsec == dsb.st_atim.tv_nsec, "st_atim: '%s'", dst); */
test_assert(ssb.st_mtim.tv_nsec == dsb.st_mtim.tv_nsec, "st_mtim: '%s'", dst);
return 0;
}
#endif /* HAVE_RQ_OTI */
int main(int argc, char *argv[])
{
(void)argc, (void)argv;
char name[] = "lc_syncfile() - sync directory (not recursive)";
#if HAVE_RQ_OTI
struct dirent **namelist;
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 *src = NULL, *dst = NULL;
int rc;
test_cap_require(CAP_NET_ADMIN);
test_name(name);
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;
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;
/* index source tree */
mdex = mdex_init(512);
if (!test_assert(mdex != NULL, "mdex_init()")) goto err_free_src_dst;
rc = mdex_addfile(mdex, src, NULL, MDEX_RECURSE);
if (!test_assert(rc == 0, "mdex_addfile() returned %i", rc)) goto err_mdex_free;
/* 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;
test_assert(share != NULL, "lc_share()");
/* start receive thread, sync files */
test_log("syncing '%s' => '%s'\n", src, dst);
rc = sem_init(&sem_recv, 0, 0);
if (!test_assert(rc == 0, "sem_init(sem_recv)")) goto err_unshare;
pkg_recv.src = src;
pkg_recv.dst = 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 */
/* src has no trailing slash, so the directory must be created inside dst */
struct stat ssb, dsb;
char *destdir;
int len;
len = snprintf(NULL, 0, "%s/%s", dst, src);
destdir = malloc(len + 1);
if (!test_assert(destdir != NULL, "malloc destdir")) goto err_sem_destroy;
if (!test_assert(len == snprintf(destdir, len + 1, "%s/%s", dst, src), "build destdir string"))
goto err_free_destdir;
rc = stat(destdir, &dsb);
if (!test_assert(rc == 0, "%s exists", destdir)) goto err_free_destdir;
rc = stat(src, &ssb);
if (!test_assert(rc == 0, "stat src")) goto err_free_destdir;
/* check mode, owner, group, times */
test_assert(statcmp(src, destdir) == 0, "statcmp");
/* we didn't ask for recursion, so make sure directory is empty */
rc = scandir(destdir, &namelist, scandirfilter, alphasort);
test_assert(rc == 0, "destination directory exists and is empty");
err_free_destdir:
free(destdir);
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(src); free(dst);
return test_status;
#else
return test_skip(name);
#endif /* HAVE_RQ_OTI */
}
|