From c47338b9449fc8171ba2e6513f4562d9d3d6eb34 Mon Sep 17 00:00:00 2001 From: Alberto Leiva Popper Date: Mon, 20 Jan 2025 11:37:58 -0600 Subject: [PATCH] Index each cache node separately Background: - Fort shouldn't lose the cache index when a signal interrupts it. - Writing the index during the signal handler is not possible, because of the async-signal-safe requirement. - Writing the index outside of the signal handler is seemingly not viable, because of the infelicities between the signal and multithreading APIs in C. I haven't completely discarded the "dropping multithreading" option, but since it seems disproportionate, I've been rethinking the index. This commit scatters the index across several files, to minimize lost information during a stopping signal. This will exacerbate the inode problem, but that's temporary. Reverts e0880f8eac3d35e576a2c1a2cb588424ab4b3e1b. --- src/cache.c | 542 ++++++++++++++++++++++++--------------- src/file.c | 7 +- src/file.h | 4 +- src/json_util.c | 68 +---- src/json_util.h | 3 +- src/log.h | 2 +- src/object/certificate.c | 8 +- src/object/manifest.c | 101 +++++--- src/rrdp.c | 175 ++++++++++--- src/rrdp.h | 4 +- src/types/str.c | 34 +++ src/types/str.h | 4 + test/Makefile.am | 5 +- test/cache_test.c | 237 +++++++++++++++-- test/rrdp_test.c | 202 +-------------- test/rrdp_update_test.c | 3 + 16 files changed, 821 insertions(+), 578 deletions(-) diff --git a/src/cache.c b/src/cache.c index f6b81583..e0b6b4a3 100644 --- a/src/cache.c +++ b/src/cache.c @@ -23,6 +23,7 @@ #include "task.h" #include "types/array.h" #include "types/path.h" +#include "types/str.h" #include "types/url.h" #include "types/uthash.h" @@ -38,6 +39,8 @@ enum node_state { DLS_FRESH, }; +struct cache_table; + /* * This is a delicate structure; pay attention. * @@ -54,12 +57,18 @@ enum node_state { * this must be done through careful coding and review. */ struct cache_node { + /* + * Hack: The "url" is a cache identifier, not an actual URL. + * If this is an rsync node, it equals `caRepository`. + * If this is an RRDP node, it's `rpkiNotify\tcaRepository`. + * This allows easy hash table indexing. + */ struct cache_mapping map; enum node_state state; /* Result code of recent dl attempt (DLS_FRESH only) */ int dlerr; - time_t attempt_ts; /* Refresh: Dl attempt. Fallback: Commit */ + time_t attempt_ts; /* Refresh: Dl attempt. Fallback: Unused */ time_t success_ts; /* Refresh: Dl success. Fallback: Commit */ struct mft_meta mft; /* RPP fallbacks only */ @@ -125,7 +134,7 @@ static STAILQ_HEAD(cache_commits, cache_commit) commits = STAILQ_HEAD_INITIALIZE static pthread_mutex_t commits_lock = PTHREAD_MUTEX_INITIALIZER; #define LOCKFILE ".lock" -#define INDEX_FILE "index.json" +#define METAFILE "meta.json" #define TAGNAME_VERSION "fort-version" #ifdef UNIT_TESTING @@ -139,7 +148,8 @@ delete_node(struct cache_table *tbl, struct cache_node *node, void *arg) __delete_node_cb(node); #endif - HASH_DEL(tbl->nodes, node); + if (tbl) + HASH_DEL(tbl->nodes, node); map_cleanup(&node->map); rrdp_state_free(node->rrdp); @@ -206,6 +216,48 @@ strip_rsync_module(char const *url) return NULL; } +static json_t * +node2json(struct cache_node *node) +{ + char *tab; + json_t *json; + + json = json_obj_new(); + if (json == NULL) + return NULL; + + tab = strchr(node->map.url, '\t'); + if (tab == NULL) { + if (json_add_str(json, "url", node->map.url)) + goto fail; + } else { + if (json_add_strn(json, "notification", node->map.url, tab - node->map.url)) + goto fail; + if (json_add_str(json, "url", tab + 1)) + goto fail; + } + if (json_add_str(json, "path", node->map.path)) + goto fail; + if (node->dlerr && json_add_int(json, "error", node->dlerr)) + goto fail; + if (node->attempt_ts && json_add_ts(json, "attempt", node->attempt_ts)) + goto fail; + if (node->success_ts && json_add_ts(json, "success", node->success_ts)) + goto fail; + if (node->mft.num.size && json_add_bigint(json, "mftNum", &node->mft.num)) + goto fail; + if (node->mft.update && json_add_ts(json, "mftUpdate", node->mft.update)) + goto fail; + if (node->rrdp) + if (json_object_add(json, "rrdp", rrdp_state2json(node->rrdp))) + goto fail; + + return json; + +fail: json_decref(json); + return NULL; +} + static int dl_rsync(struct cache_node *); static int dl_http(struct cache_node *); static int dl_rrdp(struct cache_node *); @@ -216,7 +268,7 @@ init_table(struct cache_table *tbl, char *name, bool enabled, dl_cb dl) memset(tbl, 0, sizeof(*tbl)); tbl->name = name; tbl->enabled = enabled; - cseq_init(&tbl->seq, name, false); + cseq_init(&tbl->seq, name, 0, false); tbl->download = dl; panic_on_fail(pthread_mutex_init(&tbl->lock, NULL), "pthread_mutex_init"); @@ -373,145 +425,264 @@ cache_setup(void) return 0; } +static char * +ctx2id(char const *rpkiNotify, char const *caRepository) +{ + char *result; + size_t nlen; + + if (rpkiNotify == NULL && caRepository == NULL) + return NULL; + if (rpkiNotify == NULL) + return pstrdup(caRepository); + if (caRepository == NULL) + return pstrdup(rpkiNotify); + + nlen = strlen(rpkiNotify); + result = pmalloc(nlen + strlen(caRepository) + 2); + strcpy(result, rpkiNotify); + result[nlen] = '\t'; + strcpy(result + nlen + 1, caRepository); + + return result; +} + static struct cache_node * json2node(json_t *json) { struct cache_node *node; - char const *str; + char const *notification; + char const *url; + char const *path; json_t *rrdp; int error; node = pzalloc(sizeof(struct cache_node)); - if (json_get_str(json, "url", &str)) - goto fail; - node->map.url = pstrdup(str); - if (json_get_str(json, "path", &str)) - goto fail; - node->map.path = pstrdup(str); - error = json_get_ts(json, "attempt", &node->attempt_ts); - if (error != 0 && error != ENOENT) - goto fail; - error = json_get_ts(json, "success", &node->success_ts); - if (error != 0 && error != ENOENT) - goto fail; - error = json_get_bigint(json, "mftNum", &node->mft.num); - if (error < 0) - goto fail; - error = json_get_ts(json, "mftUpdate", &node->mft.update); - if (error < 0) - goto fail; - error = json_get_object(json, "rrdp", &rrdp); - if (error < 0) - goto fail; - if (error == 0 && rrdp_json2state(rrdp, &node->rrdp)) - goto fail; + error = json_get_str(json, "notification", ¬ification); + switch (error) { + case 0: + break; + case ENOENT: + notification = NULL; + break; + default: + pr_op_debug("notification: %s", strerror(error)); + goto fail1; + } - return node; + error = json_get_str(json, "url", &url); + switch (error) { + case 0: + break; + case ENOENT: + url = NULL; + break; + default: + pr_op_debug("url: %s", strerror(error)); + goto fail1; + } -fail: map_cleanup(&node->map); - return NULL; -} + node->map.url = ctx2id(notification, url); + if (node->map.url == NULL) { + pr_op_debug("Tag is missing both notification and url."); + goto fail1; + } -static void -json2tbl(json_t *root, struct cache_table *tbl) -{ - json_t *array, *child; - int index; - struct cache_node *node; - size_t urlen; + error = json_get_str(json, "path", &path); + if (error) { + pr_op_debug("path: %s", strerror(error)); + goto fail2; + } + node->map.path = pstrdup(path); - if (json_get_object(root, tbl->name, &root)) - return; + error = json_get_ts(json, "attempt", &node->attempt_ts); + if (error != 0 && error != ENOENT) { + pr_op_debug("attempt: %s", strerror(error)); + goto fail2; + } - if (json_get_seq(root, "seq", &tbl->seq)) { - // XXX this is grouds to reset the cache... - pr_op_warn("Unable to load the 'seq' child for the %s table.", - tbl->name); - return; + error = json_get_ts(json, "success", &node->success_ts); + if (error != 0 && error != ENOENT) { + pr_op_debug("success: %s", strerror(error)); + goto fail2; } - if (json_get_array(root, "nodes", &array)) { - pr_op_warn("Unable to load the 'nodes' child for the %s table.", - tbl->name); - return; + + error = json_get_bigint(json, "mftNum", &node->mft.num); + if (error < 0) { + pr_op_debug("mftNum: %s", strerror(error)); + goto fail2; } - json_array_foreach(array, index, child) { - node = json2node(child); - if (node == NULL) - continue; - urlen = strlen(node->map.url); - // XXX worry about dupes - HASH_ADD_KEYPTR(hh, tbl->nodes, node->map.url, urlen, node); + error = json_get_ts(json, "mftUpdate", &node->mft.update); + if (error < 0) { + pr_op_debug("mftUpdate: %s", strerror(error)); + goto fail3; } + + error = json_get_object(json, "rrdp", &rrdp); + if (error < 0) { + pr_op_debug("rrdp: %s", strerror(error)); + goto fail3; + } + if (error == 0 && rrdp_json2state(rrdp, node->map.path, &node->rrdp)) + goto fail3; + + return node; + +fail3: INTEGER_cleanup(&node->mft.num); +fail2: map_cleanup(&node->map); +fail1: free(node); + return NULL; } static int -load_index_file(void) +check_root_metafile(void) { - json_t *root; json_error_t jerr; + json_t *root; char const *file_version; int error; - pr_op_debug("Loading " INDEX_FILE "..."); + pr_op_debug("Loading " METAFILE "..."); - root = json_load_file(INDEX_FILE, 0, &jerr); + root = json_load_file(METAFILE, 0, &jerr); if (root == NULL) { - if (json_error_code(&jerr) == json_error_cannot_open_file) - pr_op_debug(INDEX_FILE " does not exist."); - else + if (json_error_code(&jerr) == json_error_cannot_open_file) { + pr_op_debug(METAFILE " does not exist."); + return ENOENT; + } else { pr_op_err("Json parsing failure at %s (%d:%d): %s", - INDEX_FILE, jerr.line, jerr.column, jerr.text); - goto fail; + METAFILE, jerr.line, jerr.column, jerr.text); + return EINVAL; + } } + if (json_typeof(root) != JSON_OBJECT) { - pr_op_err("The root tag of " INDEX_FILE " is not an object."); + pr_op_err("The root tag of " METAFILE " is not an object."); goto fail; } error = json_get_str(root, TAGNAME_VERSION, &file_version); if (error) { if (error > 0) - pr_op_err(INDEX_FILE " is missing the '" + pr_op_err(METAFILE " is missing the '" TAGNAME_VERSION "' tag."); goto fail; } - if (strcmp(file_version, PACKAGE_VERSION) != 0) + if (strcmp(file_version, PACKAGE_VERSION) != 0) { + pr_op_err("The cache was written by Fort %s; " + "I need to clear it.", file_version); goto fail; - - json2tbl(root, &cache.rsync); - json2tbl(root, &cache.https); - json2tbl(root, &cache.rrdp); - json2tbl(root, &cache.fallback); + } json_decref(root); - pr_op_debug(INDEX_FILE " loaded."); - - /* - * There are many ways in which a mismatching cache index can cause - * erratic behavior that's hard to detect. Since the index is written at - * the end of the validation cycle, crashing at any point between a - * cache refresh and the index write results in a misindexed cache. - * - * Deleting the index right after loading it seems to be a simple and - * reliable way to force Fort to reset the cache after a crash. - * (Recovering with an empty cache is safer than with a misindexed one.) - */ - error = file_rm_f(INDEX_FILE); - if (error) - pr_op_warn("Unable to delete " INDEX_FILE ": %s. " - "This means Fort might not recover properly after a crash. " - "If Fort crashes for some reason, " - "please clear the cache manually before restarting.", - strerror(error)); - + pr_op_debug(METAFILE " loaded."); return 0; fail: json_decref(root); return EINVAL; } +static void +collect_meta(struct cache_table *tbl, struct dirent *dir) +{ + char filename[64]; + int wrt; + json_error_t jerr; + json_t *root; + struct cache_node *node; + size_t n; + + if (S_ISDOTS(dir)) + return; + + wrt = snprintf(filename, 64, "%s/%s.json", tbl->name, dir->d_name); + if (wrt >= 64) + pr_crit("collect_meta: %d %s %s", wrt, tbl->name, dir->d_name); + + pr_clutter("%s: Loading...", filename); + + root = json_load_file(filename, 0, &jerr); + if (root == NULL) { + if (json_error_code(&jerr) == json_error_cannot_open_file) + pr_op_warn("%s: File does not exist.", filename); + else + pr_op_warn("%s: Json parsing failure at (%d:%d): %s", + filename, jerr.line, jerr.column, jerr.text); + return; + } + + if (json_typeof(root) != JSON_OBJECT) { + pr_op_warn("%s: Root tag is not an object.", filename); + goto end; + } + + node = json2node(root); + if (node != NULL) { + n = strlen(node->map.url); + // XXX worry about dupes + HASH_ADD_KEYPTR(hh, tbl->nodes, node->map.url, n, node); + } + + pr_clutter("%s: Loaded.", filename); +end: json_decref(root); +} + +static void +collect_metas(struct cache_table *tbl) +{ + DIR *dir; + struct dirent *file; + unsigned long id, max_id; + int error; + + dir = opendir(tbl->name); + if (dir == NULL) { + error = errno; + if (error != ENOENT) + pr_op_warn("Cannot open %s: %s", + tbl->name, strerror(error)); + return; + } + + max_id = 0; + FOREACH_DIR_FILE(dir, file) { + if (hex2ulong(file->d_name, &id) != 0) + continue; + if (id > max_id) + max_id = id; + collect_meta(tbl, file); + } + error = errno; + if (error) + pr_op_warn("Could not finish traversing %s: %s", + tbl->name, strerror(error)); + + closedir(dir); + + tbl->seq.prefix = tbl->name; + tbl->seq.next_id = max_id + 1; + tbl->seq.pathlen = strlen(tbl->name); + tbl->seq.free_prefix = false; +} + +static int +load_index(void) +{ + int error; + + error = check_root_metafile(); + if (error) + return error; + + collect_metas(&cache.rsync); + collect_metas(&cache.https); + collect_metas(&cache.rrdp); + collect_metas(&cache.fallback); + return 0; +} + int cache_prepare(void) { @@ -521,7 +692,7 @@ cache_prepare(void) if (error) return error; - if (load_index_file() != 0) { + if (load_index() != 0) { error = reset_cache_dir(); if (error) goto fail; @@ -551,107 +722,6 @@ fail: flush_nodes(); return error; } -static json_t * -node2json(struct cache_node *node) -{ - json_t *json; - - json = json_obj_new(); - if (json == NULL) - return NULL; - - if (json_add_str(json, "url", node->map.url)) - goto fail; - if (json_add_str(json, "path", node->map.path)) - goto fail; - if (node->attempt_ts && json_add_ts(json, "attempt", node->attempt_ts)) - goto fail; - if (node->success_ts && json_add_ts(json, "success", node->success_ts)) - goto fail; - if (node->mft.num.size && json_add_bigint(json, "mftNum", &node->mft.num)) - goto fail; - if (node->mft.update && json_add_ts(json, "mftUpdate", node->mft.update)) - goto fail; - if (node->rrdp) - if (json_object_add(json, "rrdp", rrdp_state2json(node->rrdp))) - goto fail; - - return json; - -fail: json_decref(json); - return NULL; -} - -static json_t * -tbl2json(struct cache_table *tbl) -{ - struct json_t *json, *nodes; - struct cache_node *node, *tmp; - - json = json_obj_new(); - if (!json) - return NULL; - - if (json_add_seq(json, "seq", &tbl->seq)) - goto fail; - - nodes = json_array_new(); - if (!nodes) - goto fail; - if (json_object_add(json, "nodes", nodes)) - goto fail; - - HASH_ITER(hh, tbl->nodes, node, tmp) - if (json_array_add(nodes, node2json(node))) - goto fail; - - return json; - -fail: json_decref(json); - return NULL; -} - -static json_t * -build_index_file(void) -{ - json_t *json; - - json = json_obj_new(); - if (json == NULL) - return NULL; - - if (json_object_add(json, TAGNAME_VERSION, json_str_new(PACKAGE_VERSION))) - goto fail; - if (json_object_add(json, "rsync", tbl2json(&cache.rsync))) - goto fail; - if (json_object_add(json, "https", tbl2json(&cache.https))) - goto fail; - if (json_object_add(json, "rrdp", tbl2json(&cache.rrdp))) - goto fail; - if (json_object_add(json, "fallback", tbl2json(&cache.fallback))) - goto fail; - - return json; - -fail: json_decref(json); - return NULL; -} - -static void -write_index_file(void) -{ - struct json_t *json; - - json = build_index_file(); - if (json == NULL) - return; - - if (json_dump_file(json, INDEX_FILE, JSON_INDENT(2))) - pr_op_err("Unable to write " INDEX_FILE "; unknown cause."); - - json_decref(json); -} - static int dl_rsync(struct cache_node *module) { @@ -671,8 +741,8 @@ dl_rrdp(struct cache_node *notif) bool changed; int error; - error = rrdp_update(¬if->map, notif->success_ts, - &changed, ¬if->rrdp); + error = rrdp_update(¬if->map, notif->success_ts, &changed, + ¬if->rrdp); if (error) return error; @@ -730,6 +800,44 @@ provide_node(struct cache_table *tbl, char const *url) return node; } +static void +rm_metadata(struct cache_node *node) +{ + char *filename; + int error; + + filename = str_concat(node->map.path, ".json"); + pr_op_debug("rm %s", filename); + if (unlink(filename) < 0) { + error = errno; + if (error == ENOENT) + pr_op_debug("%s already doesn't exist.", filename); + else + pr_op_warn("Cannot rm %s: %s", filename, strerror(errno)); + } + + free(filename); +} + +static void +write_metadata(struct cache_node *node) +{ + char *filename; + json_t *json; + + json = node2json(node); + if (!json) + return; + filename = str_concat(node->map.path, ".json"); + + pr_op_debug("echo \"$json\" > %s", filename); + if (json_dump_file(json, filename, JSON_INDENT(2))) + pr_op_err("Unable to write %s; unknown cause.", filename); + + free(filename); + json_decref(json); +} + /* * @uri is either a caRepository or a rpkiNotify * By contract, only sets @result on return 0. @@ -770,7 +878,9 @@ do_refresh(struct cache_table *tbl, char const *uri, struct cache_node **result) mutex_unlock(&tbl->lock); node->attempt_ts = time_fatal(); + rm_metadata(node); node->dlerr = tbl->download(node); + write_metadata(node); downloaded = true; mutex_lock(&tbl->lock); @@ -1071,15 +1181,19 @@ cachent_print(struct cache_node *node) printf("\t%s (%s): ", node->map.url, node->map.path); switch (node->state) { case DLS_OUTDATED: - printf("stale"); + printf("stale "); break; case DLS_ONGOING: - printf("downloading"); + printf("downloading "); break; case DLS_FRESH: - printf("fresh (errcode %d)", node->dlerr); + printf("fresh (errcode %d) ", node->dlerr); break; } + + printf("attempt:%lx success:%lx ", node->attempt_ts, node->success_ts); + printf("mftUpdate:%lx ", node->mft.update); + rrdp_print(node->rrdp); printf("\n"); } @@ -1088,7 +1202,7 @@ table_print(struct cache_table *tbl) { struct cache_node *node, *tmp; - printf(" %s enabled:%d seq:%s/%lu\n", + printf("%s enabled:%d seq:%s/%lx\n", tbl->name, tbl->enabled, tbl->seq.prefix, tbl->seq.next_id); HASH_ITER(hh, tbl->nodes, node, tmp) @@ -1203,6 +1317,7 @@ commit_fallbacks(time_t now) struct cache_commit *commit; struct cache_node *fb; array_index i; + int error; while (!STAILQ_EMPTY(&commits)) { commit = STAILQ_FIRST(&commits); @@ -1222,9 +1337,19 @@ commit_fallbacks(time_t now) fb = provide_node(&cache.fallback, commit->caRepository); } - - if (file_mkdir(fb->map.path, true) != 0) - goto skip; + fb->success_ts = now; + + pr_op_debug("mkdir -f %s", fb->map.path); + if (mkdir(fb->map.path, CACHE_FILEMODE) < 0) { + error = errno; + if (error != EEXIST) { + pr_op_err("Cannot create '%s': %s", + fb->map.path, strerror(error)); + goto skip; + } + + rm_metadata(fb); /* error == EEXIST */ + } commit_rpp(commit, fb); discard_trash(commit, fb); @@ -1235,14 +1360,16 @@ commit_fallbacks(time_t now) pr_op_debug("Creating fallback for %s", map->url); fb = provide_node(&cache.fallback, map->url); + fb->success_ts = now; if (is_fallback(map->path)) goto freshen; file_ln(map->path, fb->map.path); } + write_metadata(fb); + freshen: fb->state = DLS_FRESH; - fb->attempt_ts = fb->success_ts = now; skip: free(commit->rpkiNotify); free(commit->caRepository); for (i = 0; i < commit->nfiles; i++) { @@ -1265,6 +1392,7 @@ remove_abandoned(struct cache_table *table, struct cache_node *node, void *arg) now = *((time_t *)arg); if (difftime(node->attempt_ts + cfg_cache_threshold(), now) < 0) { + rm_metadata(node); file_rm_rf(node->map.path); delete_node(table, node, NULL); } @@ -1323,7 +1451,7 @@ void cache_commit(void) { cleanup_cache(); - write_index_file(); + file_write_txt(METAFILE, "{ \"fort-version\": \"" PACKAGE_VERSION "\" }"); unlock_cache(); flush_nodes(); } diff --git a/src/file.c b/src/file.c index 81387a64..50df56f0 100644 --- a/src/file.c +++ b/src/file.c @@ -70,6 +70,7 @@ write_file(char const *path, void const *bytes, size_t n) if (error) return error; + errno = 0; if (fwrite(bytes, 1, n, out) != n) { error = errno; if (!error) /* Linux's man page does not mention errno */ @@ -210,6 +211,7 @@ file_rm_rf(char const *path) /* TODO (performance) optimize that 32 */ if (nftw(path, rm, 32, FTW_DEPTH | FTW_PHYS) < 0) { error = errno; + // XXX This msg is sometimes annoying; maybe defer it pr_op_warn("Cannot remove %s: %s", path, strerror(error)); return error ? error : -1; } @@ -246,10 +248,11 @@ file_ln(char const *oldpath, char const *newpath) } void -cseq_init(struct cache_sequence *seq, char *prefix, bool free_prefix) +cseq_init(struct cache_sequence *seq, char *prefix, unsigned long id, + bool free_prefix) { seq->prefix = prefix; - seq->next_id = 0; + seq->next_id = id; seq->pathlen = strlen(prefix) + 4; seq->free_prefix = free_prefix; } diff --git a/src/file.h b/src/file.h index a615d489..b7be2e1e 100644 --- a/src/file.h +++ b/src/file.h @@ -43,10 +43,10 @@ struct cache_sequence { char *prefix; unsigned long next_id; size_t pathlen; - bool free_prefix; + bool free_prefix; // XXX seems to be always false }; -void cseq_init(struct cache_sequence *, char *, bool); +void cseq_init(struct cache_sequence *, char *, unsigned long, bool); void cseq_cleanup(struct cache_sequence *); char *cseq_next(struct cache_sequence *); diff --git a/src/json_util.c b/src/json_util.c index 696a8540..14660632 100644 --- a/src/json_util.c +++ b/src/json_util.c @@ -173,35 +173,6 @@ json_get_object(json_t *parent, char const *name, json_t **obj) return 0; } -int -json_get_seq(json_t *parent, char const *name, struct cache_sequence *seq) -{ - json_t *child; - char const *pfx; - int error; - - error = json_get_object(parent, name, &child); - if (error) - return error; - - error = json_get_str(child, "pfx", &pfx); - if (error < 0) - return error; - if (error > 0) - return pr_op_err( - "The '%s' JSON object is missing mandatory child 'pfx'.", - name - ); - error = json_get_ulong(child, "next", &seq->next_id); - if (error < 0) - return error; - - seq->prefix = pstrdup(pfx); - seq->pathlen = strlen(pfx) + 4; - seq->free_prefix = true; - return 0; -} - /* * Any unknown members should be treated as errors, RFC8416 3.1: * "JSON members that are not defined here MUST NOT be used in SLURM @@ -266,6 +237,18 @@ json_add_str(json_t *parent, char const *name, char const *value) return 0; } +int +json_add_strn(json_t *parent, char const *name, char const *value, size_t len) +{ + if (json_object_set_new(parent, name, json_stringn(value, len))) + return pr_op_err( + "Cannot convert %s '%.*s' to json; unknown cause.", + name, (int)len, value + ); + + return 0; +} + int json_add_ts(json_t *parent, char const *name, time_t value) { @@ -288,33 +271,6 @@ json_add_ts(json_t *parent, char const *name, time_t value) return 0; } -int -json_add_seq(json_t *parent, char const *name, - struct cache_sequence const *value) -{ - json_t *seq; - - seq = json_obj_new(); - if (seq == NULL) - return -EINVAL; - - if (json_add_ulong(seq, "next", value->next_id)) - goto fail; - if (json_add_str(seq, "pfx", value->prefix)) - goto fail; - - if (json_object_set_new(parent, name, seq)) - return pr_op_err( - "Cannot convert sequence [%s, %lu] to json; unknown cause.", - value->prefix, value->next_id - ); - - return 0; - -fail: json_decref(seq); - return -EINVAL; -} - #define OOM_PFX " Likely out of memory (but there is no contract)." json_t * diff --git a/src/json_util.h b/src/json_util.h index 7d0261b6..e8f20f9a 100644 --- a/src/json_util.h +++ b/src/json_util.h @@ -35,7 +35,6 @@ int json_get_ts(json_t *, char const *, time_t *); int json_get_str(json_t *, char const *, char const **); int json_get_array(json_t *, char const *, json_t **); int json_get_object(json_t *, char const *, json_t **); -int json_get_seq(json_t *, char const *, struct cache_sequence *); bool json_valid_members_count(json_t *, size_t); @@ -43,8 +42,8 @@ int json_add_int(json_t *, char const *, int); int json_add_ulong(json_t *, char const *, unsigned long); int json_add_bigint(json_t *, char const *, INTEGER_t *); int json_add_str(json_t *, char const *, char const *); +int json_add_strn(json_t *, char const *, char const *, size_t); int json_add_ts(json_t *, char const *, time_t); -int json_add_seq(json_t *, char const *, struct cache_sequence const *); json_t *json_obj_new(void); json_t *json_array_new(void); diff --git a/src/log.h b/src/log.h index a4203481..1544a187 100644 --- a/src/log.h +++ b/src/log.h @@ -67,7 +67,7 @@ bool pr_val_enabled(unsigned int level); bool pr_op_enabled(unsigned int level); #define pr_clutter_enabled() false -#define pr_clutter(...) +#define pr_clutter(...) /* pr_op_debug(__VA_ARGS__) */ /* == Operation logs == */ diff --git a/src/object/certificate.c b/src/object/certificate.c index a783eb5c..12a8a968 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -1895,7 +1895,7 @@ int certificate_traverse(struct rpki_certificate *ca) { struct cache_cage *cage; - char const *mft; + char const *mft_path; array_index i; struct cache_mapping *map; char const *ext; @@ -1922,8 +1922,8 @@ certificate_traverse(struct rpki_certificate *ca) "I'm going to have to skip it.", ca->sias.caRepository); } -retry: mft = cage_map_file(cage, ca->sias.rpkiManifest); - if (!mft) { +retry: mft_path = cage_map_file(cage, ca->sias.rpkiManifest); + if (!mft_path) { if (cage_disable_refresh(cage)) goto retry; error = pr_val_err("caRepository '%s' is missing a manifest.", @@ -1931,7 +1931,7 @@ retry: mft = cage_map_file(cage, ca->sias.rpkiManifest); goto end; } - error = manifest_traverse(ca->sias.rpkiManifest, mft, cage, ca); + error = manifest_traverse(ca->sias.rpkiManifest, mft_path, cage, ca); if (error) { if (cage_disable_refresh(cage)) goto retry; diff --git a/src/object/manifest.c b/src/object/manifest.c index 09ad6834..7cf95c3e 100644 --- a/src/object/manifest.c +++ b/src/object/manifest.c @@ -208,21 +208,24 @@ validate_manifest(struct Manifest *mft, struct cache_cage *cage, } static void -shuffle_mft_files(struct Manifest *mft) +shuffle_mft_files(struct rpp *rpp) { - int i, j; + size_t i, j; unsigned int seed, rnd; - struct FileAndHash *tmpfah; + struct cache_mapping tmp; + + if (rpp->nfiles < 2) + return; seed = time(NULL) ^ getpid(); /* Fisher-Yates shuffle with modulo bias */ - for (i = 0; i < mft->fileList.list.count - 1; i++) { + for (i = 0; i < rpp->nfiles - 1; i++) { rnd = rand_r(&seed); - j = i + rnd % (mft->fileList.list.count - i); - tmpfah = mft->fileList.list.array[j]; - mft->fileList.list.array[j] = mft->fileList.list.array[i]; - mft->fileList.list.array[i] = tmpfah; + j = i + rnd % (rpp->nfiles - i); + tmp = rpp->files[j]; + rpp->files[j] = rpp->files[i]; + rpp->files[i] = tmp; } } @@ -291,7 +294,8 @@ check_file_and_hash(struct FileAndHash *fah, char const *path) */ static int -build_rpp(char const *mft_url, struct Manifest *mft, struct cache_cage *cage, +collect_files(char const *mft_url, char const *mft_path, + struct Manifest *mft, struct cache_cage *cage, struct rpki_certificate *parent) { struct rpp *rpp; @@ -302,11 +306,12 @@ build_rpp(char const *mft_url, struct Manifest *mft, struct cache_cage *cage, char const *path; int error; - shuffle_mft_files(mft); + if (mft->fileList.list.count == 0) + return pr_val_err("Manifest's file list is empty."); rpp = &parent->rpp; rpp_url = url_parent(mft_url); // XXX - rpp->nfiles = mft->fileList.list.count; + rpp->nfiles = mft->fileList.list.count + 1; /* plus manifest */ rpp->files = pzalloc(rpp->nfiles * sizeof(*rpp->files)); for (i = 0; i < mft->fileList.list.count; i++) { @@ -338,38 +343,64 @@ build_rpp(char const *mft_url, struct Manifest *mft, struct cache_cage *cage, error = check_file_and_hash(src, dst->path); if (error) goto revert; + } - if (strcmp(((char const *)src->file.buf) + src->file.size - 4, ".crl") == 0) { - if (rpp->crl.map != NULL) { - error = pr_val_err( - "Manifest has more than one CRL."); - goto revert; - } - rpp->crl.map = dst; + /* Manifest */ + dst = &rpp->files[mft->fileList.list.count]; + dst->url = pstrdup(mft_url); + dst->path = pstrdup(mft_path); + + return 0; + +revert: rpp_cleanup(rpp); + free(rpp_url); + return error; +} + +static int +load_crl(struct rpki_certificate *parent) +{ + struct rpp *rpp; + array_index f; + + rpp = &parent->rpp; + + for (f = 0; f < rpp->nfiles; f++) + if (str_ends_with(rpp->files[f].url, ".crl")) { + if (rpp->crl.map != NULL) + return pr_val_err("Manifest has more than one CRL."); + rpp->crl.map = &rpp->files[f]; } - } /* rfc6486#section-7 */ - if (rpp->crl.map == NULL) { - error = pr_val_err("Manifest lacks a CRL."); - goto revert; - } + if (rpp->crl.map == NULL) + return pr_val_err("Manifest lacks a CRL."); - error = crl_load(rpp->crl.map, parent->x509, &rpp->crl.obj); + return crl_load(rpp->crl.map, parent->x509, &rpp->crl.obj); +} + +static int +build_rpp(char const *mft_url, char const *mft_path, struct Manifest *mft, + struct cache_cage *cage, struct rpki_certificate *parent) +{ + int error; + + error = collect_files(mft_url, mft_path, mft, cage, parent); if (error) - goto revert; + return error; - free(rpp_url); - return 0; + shuffle_mft_files(&parent->rpp); + + error = load_crl(parent); + if (error) + rpp_cleanup(&parent->rpp); -revert: rpp_cleanup(rpp); - free(rpp_url); return error; } int -manifest_traverse(char const *url, char const *path, struct cache_cage *cage, - struct rpki_certificate *parent) +manifest_traverse(char const *mft_url, char const *mft_path, + struct cache_cage *cage, struct rpki_certificate *parent) { static OID oid = OID_MANIFEST; struct oid_arcs arcs = OID2ARCS("manifest", oid); @@ -379,10 +410,10 @@ manifest_traverse(char const *url, char const *path, struct cache_cage *cage, int error; /* Prepare */ - fnstack_push(url); // XXX + fnstack_push(mft_url); /* Decode */ - error = signed_object_decode(&sobj, path); + error = signed_object_decode(&sobj, mft_path); if (error) goto end1; error = decode_manifest(&sobj, &mft); @@ -390,7 +421,7 @@ manifest_traverse(char const *url, char const *path, struct cache_cage *cage, goto end2; /* Initialize @summary */ - error = build_rpp(url, mft, cage, parent); + error = build_rpp(mft_url, mft_path, mft, cage, parent); if (error) goto end3; @@ -404,7 +435,7 @@ manifest_traverse(char const *url, char const *path, struct cache_cage *cage, error = validate_manifest(mft, cage, &parent->rpp.mft); if (error) goto end5; - error = refs_validate_ee(&ee.sias, parent->rpp.crl.map->url, url); + error = refs_validate_ee(&ee.sias, parent->rpp.crl.map->url, mft_url); end5: rpki_certificate_cleanup(&ee); if (error) diff --git a/src/rrdp.c b/src/rrdp.c index d5588ca2..613eec00 100644 --- a/src/rrdp.c +++ b/src/rrdp.c @@ -18,6 +18,7 @@ #include "thread_var.h" #include "types/arraylist.h" #include "types/path.h" +#include "types/str.h" #include "types/url.h" #include "types/uthash.h" @@ -150,6 +151,21 @@ state_find_file(struct rrdp_state const *state, char const *url, size_t len) return file; } +static struct cache_file * +cache_file_add(struct rrdp_state *state, char *url, char *path) +{ + struct cache_file *file; + size_t urlen; + + file = pzalloc(sizeof(struct cache_file)); + file->map.url = url; + file->map.path = path; + urlen = strlen(url); + HASH_ADD_KEYPTR(hh, state->files, file->map.url, urlen, file); + + return file; +} + static void metadata_cleanup(struct file_metadata *meta) { @@ -525,7 +541,7 @@ handle_publish(xmlTextReaderPtr reader, struct parser_args *args) { struct publish tag = { 0 }; struct cache_file *file; - size_t len; + char *path; int error; error = parse_publish(reader, &tag); @@ -534,8 +550,7 @@ handle_publish(xmlTextReaderPtr reader, struct parser_args *args) pr_clutter("Publish %s", logv_filename(tag.meta.uri)); - len = strlen(tag.meta.uri); - file = state_find_file(args->state, tag.meta.uri, len); + file = state_find_file(args->state, tag.meta.uri, strlen(tag.meta.uri)); /* rfc8181#section-2.2 */ if (file) { @@ -575,17 +590,12 @@ handle_publish(xmlTextReaderPtr reader, struct parser_args *args) goto end; } - file = pzalloc(sizeof(struct cache_file)); - file->map.url = pstrdup(tag.meta.uri); - file->map.path = cseq_next(&args->state->seq); - if (!file->map.path) { - free(file->map.url); - free(file); + path = cseq_next(&args->state->seq); + if (!path) { error = -EINVAL; goto end; } - - HASH_ADD_KEYPTR(hh, args->state->files, file->map.url, len, file); + file = cache_file_add(args->state, pstrdup(tag.meta.uri), path); } error = file_write_bin(file->map.path, tag.content, tag.content_len); @@ -1173,8 +1183,8 @@ dl_notif(struct cache_mapping const *map, time_t mtim, bool *changed, * snapshot, and explodes them into @notif->path. */ int -rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed, - struct rrdp_state **state) +rrdp_update(struct cache_mapping const *notif, time_t mtim, + bool *changed, struct rrdp_state **state) { struct rrdp_state *old; struct update_notification new; @@ -1190,7 +1200,8 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed, if (!(*changed)) goto end; - pr_val_debug("New session/serial: %s/%s", new.session.session_id, + pr_val_debug("New session/serial: %s/%s", + new.session.session_id, new.session.serial.str); if ((*state) == NULL) { @@ -1198,7 +1209,7 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed, old = pzalloc(sizeof(struct rrdp_state)); /* session postponed! */ - cseq_init(&old->seq, pstrdup(notif->path), true); + cseq_init(&old->seq, notif->path, 0, false); STAILQ_INIT(&old->delta_hashes); error = file_mkdir(notif->path, false); @@ -1222,6 +1233,7 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed, serial_cmp = BN_cmp(old->session.serial.num, new.session.serial.num); if (serial_cmp < 0) { pr_val_debug("The Notification's serial changed."); + error = validate_session_desync(old, &new); if (error) goto snapshot_fallback; @@ -1232,8 +1244,8 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed, if (!error) goto end; /* - * The files are exploded and usable, but @cached is not - * updatable. So drop and create it anew. + * The files are exploded and usable, but @old is not updatable. + * So drop and create it anew. * We might lose some delta hashes, but it's better than * re-snapshotting the next time the notification changes. * Not sure if it matters. This looks so unlikely, it's @@ -1243,12 +1255,12 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed, } else if (serial_cmp > 0) { pr_val_debug("Cached serial is higher than notification serial."); - goto snapshot_fallback; + goto end; } else { pr_val_debug("The Notification changed, but the session ID and serial didn't, and no session desync was detected."); *changed = false; - goto clean_notif; + goto end; } snapshot_fallback: @@ -1287,7 +1299,7 @@ rrdp_create_fallback(char *cage, struct rrdp_state **_state, char const *url) state = *_state; if (state == NULL) { *_state = state = pzalloc(sizeof(struct rrdp_state)); - cseq_init(&state->seq, cage, false); + cseq_init(&state->seq, cage, 0, false); } file = pzalloc(sizeof(struct cache_file)); @@ -1383,8 +1395,6 @@ rrdp_state2json(struct rrdp_state *state) if (state->files) if (json_object_add(json, "files", files2json(state))) goto fail; - if (json_add_seq(json, "seq", &state->seq)) - goto fail; if (!STAILQ_EMPTY(&state->delta_hashes)) if (json_object_add(json, TAGNAME_DELTAS, dh2json(state))) goto fail; @@ -1433,6 +1443,63 @@ json2serial(json_t *parent, struct rrdp_serial *serial) return 0; } +static int +json2files(json_t *jparent, char *parent, struct rrdp_state *state) +{ + json_t *jfiles; + char const *jkey; + json_t *jvalue; + size_t parent_len; + char const *path; + unsigned long id, max_id; + int error; + + error = json_get_object(jparent, "files", &jfiles); + if (error < 0) { + pr_op_debug("files: %s", strerror(error)); + return error; + } + if (error > 0) + return 0; + + parent_len = strlen(parent); + max_id = 0; + + json_object_foreach(jfiles, jkey, jvalue) { + if (!json_is_string(jvalue)) { + pr_op_warn("RRDP file URL '%s' is not a string.", jkey); + continue; + } + + // XXX sanitize more + + path = json_string_value(jvalue); + if (strncmp(path, parent, parent_len || path[parent_len] != '/') != 0) { + pr_op_warn("RRDP path '%s' is not child of '%s'.", + path, parent); + continue; + } + + error = hex2ulong(path + parent_len + 1, &id); + if (error) { + pr_op_warn("RRDP file '%s' is not a hexadecimal number.", path); + continue; + } + if (id > max_id) + max_id = id; + + cache_file_add(state, pstrdup(jkey), pstrdup(path)); + } + + if (HASH_COUNT(state->files) == 0) { + pr_op_warn("RRDP cage does not index any files."); + return EINVAL; + } + + cseq_init(&state->seq, parent, max_id + 1, false); + return 0; +} + static int json2dh(json_t *json, struct rrdp_hash **dh) { @@ -1501,8 +1568,9 @@ json2dhs(json_t *json, struct rrdp_state *state) return 0; } +/* @path is expected to outlive the state. */ int -rrdp_json2state(json_t *json, struct rrdp_state **result) +rrdp_json2state(json_t *json, char *path, struct rrdp_state **result) { struct rrdp_state *state; int error; @@ -1510,30 +1578,30 @@ rrdp_json2state(json_t *json, struct rrdp_state **result) state = pzalloc(sizeof(struct rrdp_state)); error = json2session(json, &state->session.session_id); - if (error) - goto revert_notif; + if (error < 0) { + pr_op_debug("session: %s", strerror(error)); + goto fail; + } error = json2serial(json, &state->session.serial); - if (error) - goto revert_session; - error = json_get_seq(json, "seq", &state->seq); - if (error) - goto revert_serial; + if (error < 0) { + pr_op_debug("serial: %s", strerror(error)); + goto fail; + } + error = json2files(json, path, state); + if (error) { + pr_op_debug("files: %s", strerror(error)); + goto fail; + } error = json2dhs(json, state); - if (error) - goto revert_seq; + if (error) { + pr_op_debug("delta hashes: %s", strerror(error)); + goto fail; + } *result = state; return 0; -revert_seq: - cseq_cleanup(&state->seq); -revert_serial: - BN_free(state->session.serial.num); - free(state->session.serial.str); -revert_session: - free(state->session.session_id); -revert_notif: - free(state); +fail: rrdp_state_free(state); return error; } @@ -1555,3 +1623,28 @@ rrdp_state_free(struct rrdp_state *state) clear_delta_hashes(state); free(state); } + +void +rrdp_print(struct rrdp_state *rs) +{ + struct cache_file *file, *tmp; + struct rrdp_hash *hash; + unsigned int i; + + if (rs == NULL) + return; + + printf("session:%s/%s\n", rs->session.session_id, rs->session.serial.str); + HASH_ITER(hh, rs->files, file, tmp) + printf("\t\tfile: %s\n", /* file->map.url, */ file->map.path); + printf("\t\tseq:%s/%lx\n", rs->seq.prefix, rs->seq.next_id); + + STAILQ_FOREACH(hash, &rs->delta_hashes, hook) { + printf("\t\thash: "); + for (i = 0; i < RRDP_HASH_LEN; i++) + printf("%c%c", + hash_b2c(hash->bytes[i] >> 4), + hash_b2c(hash->bytes[i])); + printf("\n"); + } +} diff --git a/src/rrdp.h b/src/rrdp.h index 311b336b..f290ad22 100644 --- a/src/rrdp.h +++ b/src/rrdp.h @@ -17,8 +17,10 @@ char const *rrdp_file(struct rrdp_state const *, char const *); char const *rrdp_create_fallback(char *, struct rrdp_state **, char const *); json_t *rrdp_state2json(struct rrdp_state *); -int rrdp_json2state(json_t *, struct rrdp_state **); +int rrdp_json2state(json_t *, char *, struct rrdp_state **); void rrdp_state_free(struct rrdp_state *); +void rrdp_print(struct rrdp_state *); + #endif /* SRC_RRDP_H_ */ diff --git a/src/types/str.c b/src/types/str.c index 3c74edda..cbc9a174 100644 --- a/src/types/str.c +++ b/src/types/str.c @@ -1,10 +1,44 @@ #include "types/str.h" +#include #include #include "log.h" #include "types/path.h" +/* Allocates the result; will need free(). Never returns NULL. */ +char * +str_concat(char const *s1, char const *s2) +{ + size_t n; + char *result; + int written; + + n = strlen(s1) + strlen(s2) + 1; + result = pmalloc(n); + + written = snprintf(result, n, "%s%s", s1, s2); + if (written != n - 1) + pr_crit("str_concat: %zu %d %s %s", n, written, s1, s2); + + return result; +} + +int +hex2ulong(char const *hex, unsigned long *ulong) +{ + char *endptr; + + errno = 0; + *ulong = strtoul(hex, &endptr, 16); + if (errno) + return errno; + if (endptr[0] != 0) + return -1; + + return 0; +} + /** * Does not assume that @string is NULL-terminated. */ diff --git a/src/types/str.h b/src/types/str.h index 0489a103..7f6ebc46 100644 --- a/src/types/str.h +++ b/src/types/str.h @@ -7,6 +7,10 @@ #include "types/arraylist.h" +char *str_concat(char const *, char const *); + +int hex2ulong(char const *, unsigned long *); + int ia5s2string(ASN1_IA5STRING *, char **); int BN2string(BIGNUM *, char **); diff --git a/test/Makefile.am b/test/Makefile.am index be654b4d..05957041 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -30,8 +30,8 @@ check_PROGRAMS = address.test address_test_SOURCES = types/address_test.c address_test_LDADD = ${CHECK_LIBS} -check_PROGRAMS += asn1_int.test -asn1_int_test_SOURCES = asn1/asn1c/INTEGER_t_test.c +check_PROGRAMS += asn1_int.test +asn1_int_test_SOURCES = asn1/asn1c/INTEGER_t_test.c asn1_int_test_LDADD = ${CHECK_LIBS} check_PROGRAMS += base64.test @@ -82,6 +82,7 @@ check_PROGRAMS += rrdp_update.test rrdp_update_test_SOURCES = rrdp_update_test.c rrdp_update_test_LDADD = ${CHECK_LIBS} rrdp_update_test_LDADD += ${XML2_LIBS} +rrdp_update_test_LDADD += ${JANSSON_LIBS} check_PROGRAMS += rsync.test rsync_test_SOURCES = rsync_test.c diff --git a/test/cache_test.c b/test/cache_test.c index db5f4f03..d63b5980 100644 --- a/test/cache_test.c +++ b/test/cache_test.c @@ -16,8 +16,11 @@ #include "rrdp_util.h" #include "relax_ng.c" #include "rrdp.c" +#include "asn1/asn1c/asn_codecs_prim.c" +#include "asn1/asn1c/INTEGER.c" #include "types/map.c" #include "types/path.c" +#include "types/str.c" #include "types/url.c" /* Mocks */ @@ -51,9 +54,20 @@ rsync_download(char const *url, char const *path) MOCK_VOID(__delete_node_cb, struct cache_node const *node) MOCK_VOID(task_wakeup_busy, void) -__MOCK_ABORT(asn_INTEGER2str, char *, NULL, INTEGER_t const *bi) -MOCK_VOID(INTEGER_move, INTEGER_t *to, INTEGER_t *from) -MOCK_VOID(INTEGER_cleanup, INTEGER_t *i) +static asn_dec_rval_t dummy = { 0 }; +__MOCK_ABORT(ber_check_tags, asn_dec_rval_t, dummy, const asn_codec_ctx_t *ctx, + const asn_TYPE_descriptor_t *td, asn_struct_ctx_t *opt_ctx, + const void *ptr, size_t size, int tag_mode, int last_tag_form, + ber_tlv_len_t *last_length, int *opt_tlv_form) +__MOCK_ABORT(der_write_tags, ssize_t, 0, const asn_TYPE_descriptor_t *sd, + size_t struct_length, int tag_mode, int last_tag_form, ber_tlv_tag_t tag, + asn_app_consume_bytes_f *cb, void *app_key) +__MOCK_ABORT(asn__format_to_callback, ssize_t, 0, + int (*cb)(const void *, size_t, void *key), + void *key, const char *fmt, ...) +MOCK_ABORT_INT(asn_generic_no_constraint, + const asn_TYPE_descriptor_t *type_descriptor, const void *struct_ptr, + asn_app_constraint_failed_f *cb, void *key) /* Helpers */ @@ -62,8 +76,14 @@ setup_test(void) { dl_error = 0; init_tables(); - ck_assert_int_eq(0, system("rm -rf rsync/ https/ rrdp/ fallback/ tmp/")); - ck_assert_int_eq(0, system("mkdir rsync/ https/ rrdp/ fallback/ tmp/")); + + // XXX consider changing this function to `rm -rf /*` + ck_assert_int_eq(0, file_rm_rf(".")); + ck_assert_int_eq(0, mkdir("rsync", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("https", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("rrdp", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("fallback", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("tmp", CACHE_FILEMODE)); } static struct cache_cage * @@ -446,7 +466,10 @@ START_TEST(test_rsync_cleanup) setup_test(); - ck_assert_int_eq(0, system("mkdir rsync/0 rsync/1 rsync/2 rsync/3")); + ck_assert_int_eq(0, mkdir("rsync/0", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("rsync/1", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("rsync/2", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("rsync/3", CACHE_FILEMODE)); /* RPP0: Will remain constant */ ck_assert_int_eq(0, file_write_txt("rsync/0/0", "A")); @@ -477,9 +500,9 @@ START_TEST(test_rsync_cleanup) queue_commit(NULL, "rsync://domain/mod/rpp3", "rsync/3/0", "rsync/3/2"); cleanup_cache(); ck_filesystem("fallback", - /* RPP0 */ "fallback/0/0", "A", "fallback/0/1", "B", - /* RPP2 */ "fallback/1/0", "E", "fallback/1/1", "F", - /* RPP3 */ "fallback/2/0", "G", "fallback/2/1", "I", + /* RPP0 */ "fallback/0/0", "A", "fallback/0/1", "B", "fallback/0.json", "{", + /* RPP2 */ "fallback/1/0", "E", "fallback/1/1", "F", "fallback/1.json", "{", + /* RPP3 */ "fallback/2/0", "G", "fallback/2/1", "I", "fallback/2.json", "{", NULL); new_iteration(false); @@ -492,9 +515,9 @@ START_TEST(test_rsync_cleanup) cleanup_cache(); ck_filesystem("fallback", - /* RPP0 */ "fallback/0/0", "A", "fallback/0/1", "B", - /* RPP3 */ "fallback/2/0", "G", "fallback/2/2", "H", - /* RPP1 */ "fallback/3/0", "C", "fallback/3/1", "D", + /* RPP0 */ "fallback/0/0", "A", "fallback/0/1", "B", "fallback/0.json", "{", + /* RPP3 */ "fallback/2/0", "G", "fallback/2/2", "H", "fallback/2.json", "{", + /* RPP1 */ "fallback/3/0", "C", "fallback/3/1", "D", "fallback/3.json", "{", NULL); new_iteration(false); @@ -593,7 +616,10 @@ START_TEST(test_https_cleanup) map.path = "https/52"; cache_commit_file(&map); cleanup_cache(); - ck_filesystem("fallback", "fallback/0", "A", "fallback/1", "C", NULL); + ck_filesystem("fallback", + "fallback/0", "A", "fallback/0.json", "{", + "fallback/1", "C", "fallback/1.json", "{", + NULL); new_iteration(false); @@ -605,7 +631,10 @@ START_TEST(test_https_cleanup) map.path = "https/51"; cache_commit_file(&map); cleanup_cache(); - ck_filesystem("fallback", "fallback/0", "A", "fallback/2", "B", NULL); + ck_filesystem("fallback", + "fallback/0", "A", "fallback/0.json", "{", + "fallback/2", "B", "fallback/2.json", "{", + NULL); new_iteration(false); @@ -625,7 +654,10 @@ START_TEST(test_rrdp_cleanup) setup_test(); - ck_assert_int_eq(0, system("mkdir rrdp/0 rrdp/1 rrdp/2 rrdp/3")); + ck_assert_int_eq(0, mkdir("rrdp/0", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("rrdp/1", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("rrdp/2", CACHE_FILEMODE)); + ck_assert_int_eq(0, mkdir("rrdp/3", CACHE_FILEMODE)); ck_assert_int_eq(0, file_write_txt("rrdp/0/0", "A")); ck_assert_int_eq(0, file_write_txt("rrdp/0/1", "B")); @@ -651,9 +683,9 @@ START_TEST(test_rrdp_cleanup) queue_commit(notif, "rsync://domain/mod/rpp3", "rrdp/3/0", "rrdp/3/2"); cleanup_cache(); ck_filesystem("fallback", - "fallback/0/0", "A", "fallback/0/1", "B", - "fallback/1/0", "E", "fallback/1/1", "F", - "fallback/2/0", "G", "fallback/2/1", "I", + "fallback/0/0", "A", "fallback/0/1", "B", "fallback/0.json", "{", + "fallback/1/0", "E", "fallback/1/1", "F", "fallback/1.json", "{", + "fallback/2/0", "G", "fallback/2/1", "I", "fallback/2.json", "{", NULL); new_iteration(false); @@ -664,9 +696,9 @@ START_TEST(test_rrdp_cleanup) queue_commit(notif, "rsync://domain/mod/rpp3", "fallback/2/0", "rrdp/3/1"); cleanup_cache(); ck_filesystem("fallback", - "fallback/0/0", "A", "fallback/0/1", "B", - "fallback/2/0", "G", "fallback/2/2", "H", - "fallback/3/0", "C", "fallback/3/1", "D", + "fallback/0/0", "A", "fallback/0/1", "B", "fallback/0.json", "{", + "fallback/2/0", "G", "fallback/2/2", "H", "fallback/2.json", "{", + "fallback/3/0", "C", "fallback/3/1", "D", "fallback/3.json", "{", NULL); new_iteration(false); @@ -757,12 +789,157 @@ START_TEST(test_context) } END_TEST +static void +ck_rrdp(struct rrdp_state *expected, struct rrdp_state *actual) +{ + struct cache_file *expf, *actf, *tmp; + struct rrdp_hash *exph, *acth; + + if (expected == NULL) { + ck_assert_ptr_eq(NULL, actual); + return; + } + + ck_assert_ptr_ne(NULL, actual); + ck_assert_str_eq(expected->session.session_id, actual->session.session_id); + ck_assert_int_eq(0, BN_cmp(expected->session.serial.num, actual->session.serial.num)); + ck_assert_str_eq(expected->session.serial.str, actual->session.serial.str); + + ck_assert_int_eq(HASH_COUNT(expected->files), HASH_COUNT(actual->files)); + HASH_ITER(hh, expected->files, expf, tmp) { + HASH_FIND(hh, actual->files, expf->map.url, strlen(expf->map.url), actf); + ck_assert_ptr_ne(NULL, actf); + ck_assert_str_eq(expf->map.url, actf->map.url); + ck_assert_str_eq(expf->map.path, actf->map.path); + } + + acth = STAILQ_FIRST(&expected->delta_hashes); + STAILQ_FOREACH(exph, &expected->delta_hashes, hook) { + ck_assert_ptr_ne(NULL, acth); + ck_assert(memcmp(exph->bytes, acth->bytes, sizeof(exph->bytes)) == 0); + acth = STAILQ_NEXT(acth, hook); + } + ck_assert_ptr_eq(NULL, acth); +} + +static void +ck_json(struct cache_node *src) +{ + struct cache_node *dst; + json_t *json; + + json = node2json(src); + json_dumpf(json, stdout, JSON_INDENT(2)); + printf("\n"); + ck_assert_ptr_ne(NULL, json); + dst = json2node(json); + json_decref(json); + + ck_assert_ptr_ne(NULL, dst); + ck_assert_str_eq(src->map.url, dst->map.url); + ck_assert_str_eq(src->map.path, dst->map.path); + ck_assert_int_eq(DLS_OUTDATED, dst->state); /* Must be reset */ + ck_assert_int_eq(0, dst->dlerr); /* Must be reset */ + ck_assert_int_eq(src->attempt_ts, dst->attempt_ts); + ck_assert_int_eq(src->success_ts, dst->success_ts); + ck_assert(INTEGER_cmp(&src->mft.num, &dst->mft.num) == 0); + ck_assert_int_eq(src->mft.update, dst->mft.update); + ck_rrdp(src->rrdp, dst->rrdp); + + delete_node(NULL, src, NULL); + delete_node(NULL, dst, NULL); +} + +START_TEST(test_json_min) +{ + struct cache_node *node = pzalloc(sizeof(struct cache_node)); + + node->map.url = pstrdup("https://a.b.c/sample.cer"); + node->map.path = pstrdup("tmp/sample.cer"); + node->state = DLS_FRESH; + node->dlerr = ENOENT; + + ck_json(node); +} + +START_TEST(test_json_rrdp_min) +{ + struct cache_node *node = pzalloc(sizeof(struct cache_node)); + + node->map.url = pstrdup("https://a.b.c/sample.cer"); + node->map.path = pstrdup("rrdp/123"); + node->state = DLS_FRESH; + node->dlerr = ENOENT; + + node->rrdp = pmalloc(sizeof(struct rrdp_state)); + node->rrdp->session.session_id = pstrdup("session"); + node->rrdp->session.serial.num = BN_create(); + ck_assert_ptr_ne(NULL, node->rrdp->session.serial.num); + BN_add_word(node->rrdp->session.serial.num, 1357); + node->rrdp->session.serial.str = pstrdup("1357"); + cache_file_add(node->rrdp, pstrdup("rsync://a.b.c/d/e.mft"), pstrdup("rrdp/123/0")); + cseq_init(&node->rrdp->seq, node->map.path, 1, false); + STAILQ_INIT(&node->rrdp->delta_hashes); + + ck_json(node); +} + +START_TEST(test_json_max) +{ + struct cache_node *node = pzalloc(sizeof(struct cache_node)); + struct rrdp_hash *hash; + + node->map.url = pstrdup("https://a.b.c/sample.cer"); + node->map.path = pstrdup("rrdp/123"); + node->state = DLS_FRESH; + node->dlerr = ENOENT; + node->attempt_ts = 1234; + node->success_ts = 4321; + ck_assert_int_eq(0, asn_long2INTEGER(&node->mft.num, 5678)); + node->mft.update = 8765; + + node->rrdp = pmalloc(sizeof(struct rrdp_state)); + node->rrdp->session.session_id = pstrdup("session"); + node->rrdp->session.serial.num = BN_create(); + ck_assert_ptr_ne(NULL, node->rrdp->session.serial.num); + BN_add_word(node->rrdp->session.serial.num, 1357); + node->rrdp->session.serial.str = pstrdup("1357"); + cache_file_add(node->rrdp, pstrdup("rsync://a.b.c/d/e.mft"), pstrdup("rrdp/123/0")); + cache_file_add(node->rrdp, pstrdup("rsync://a.b.c/d/f.crl"), pstrdup("rrdp/123/1")); + cseq_init(&node->rrdp->seq, node->map.path, 2, false); + STAILQ_INIT(&node->rrdp->delta_hashes); + hash = pmalloc(sizeof(struct rrdp_hash)); + memset(&hash->bytes, 1, sizeof(hash->bytes)); + STAILQ_INSERT_HEAD(&node->rrdp->delta_hashes, hash, hook); + hash = pmalloc(sizeof(struct rrdp_hash)); + memset(&hash->bytes, 2, sizeof(hash->bytes)); + STAILQ_INSERT_HEAD(&node->rrdp->delta_hashes, hash, hook); + + ck_json(node); +} + +START_TEST(test_json_weirdurl) +{ + struct cache_node *node = pzalloc(sizeof(struct cache_node)); + + node->map.url = pstrdup("https://a.b.c/notif.xml\trsync://a.b.c/rpp"); + node->map.path = pstrdup("tmp/sample.cer"); + node->state = DLS_FRESH; + node->dlerr = ENOENT; + node->attempt_ts = 1234; + node->success_ts = 4321; + ck_assert_int_eq(0, asn_long2INTEGER(&node->mft.num, 5678)); + node->mft.update = 8765; + + ck_json(node); +} + /* Boilerplate */ static Suite *create_suite(void) { Suite *suite; - TCase *rsync, *https, *rrdp, *multi; + TCase *rsync, *https, *rrdp, *multi, *json; rsync = tcase_create("rsync"); tcase_add_test(rsync, test_cache_download_rsync); @@ -780,11 +957,18 @@ static Suite *create_suite(void) multi = tcase_create("multi-protocol"); tcase_add_test(multi, test_context); + json = tcase_create("json"); + tcase_add_test(json, test_json_min); + tcase_add_test(json, test_json_rrdp_min); + tcase_add_test(json, test_json_max); + tcase_add_test(json, test_json_weirdurl); + suite = suite_create("local-cache"); suite_add_tcase(suite, rsync); suite_add_tcase(suite, https); suite_add_tcase(suite, rrdp); suite_add_tcase(suite, multi); + suite_add_tcase(suite, json); return suite; } @@ -796,12 +980,17 @@ int main(void) int tests_failed; dls[0] = "Fort\n"; + if (mkdir("tmp", CACHE_FILEMODE) < 0 && errno != EEXIST) { fprintf(stderr, "mkdir('tmp/'): %s\n", strerror(errno)); return 1; } - if (chdir("tmp") < 0) { - fprintf(stderr, "chdir('tmp/'): %s\n", strerror(errno)); + if (mkdir("tmp/cache", CACHE_FILEMODE) < 0 && errno != EEXIST) { + fprintf(stderr, "mkdir('tmp/cache'): %s\n", strerror(errno)); + return 1; + } + if (chdir("tmp/cache") < 0) { + fprintf(stderr, "chdir('tmp/cache'): %s\n", strerror(errno)); return 1; } diff --git a/test/rrdp_test.c b/test/rrdp_test.c index dd8c6889..445354b0 100644 --- a/test/rrdp_test.c +++ b/test/rrdp_test.c @@ -14,49 +14,6 @@ #include "types/map.c" #include "types/url.c" -static void -ck_rrdp_session(char const *session, char const *serial, - struct rrdp_session *actual) -{ - BIGNUM *bn; - - ck_assert_str_eq(session, actual->session_id); - ck_assert_str_eq(serial, actual->serial.str); - - bn = BN_new(); - ck_assert_ptr_ne(NULL, bn); - ck_assert_int_eq(strlen(serial), BN_dec2bn(&bn, serial)); - ck_assert_int_eq(0, BN_cmp(bn, actual->serial.num)); - BN_free(bn); -} - -static struct rrdp_state * -create_rrdp_state(char const *session, char const *serial, ...) -{ - struct rrdp_state *state; - struct rrdp_hash *hash; - int dh_byte; - va_list args; - - state = pmalloc(sizeof(struct rrdp_state)); - - state->session.session_id = pstrdup(session); - state->session.serial.str = pstrdup(serial); - state->session.serial.num = NULL; /* Not needed for now. */ - state->files = NULL; - STAILQ_INIT(&state->delta_hashes); - - va_start(args, serial); - while ((dh_byte = va_arg(args, int)) != 0) { - hash = pmalloc(sizeof(struct rrdp_hash)); - memset(hash->bytes, dh_byte, sizeof(hash->bytes)); - STAILQ_INSERT_TAIL(&state->delta_hashes, hash, hook); - } - va_end(args); - - return state; -} - START_TEST(test_xmlChar_NULL_assumption) { xmlChar *xmlstr; @@ -610,161 +567,10 @@ START_TEST(test_parse_snapshot_bad_publish) } END_TEST -START_TEST(test_2s_simple) -{ - struct rrdp_state *state; - json_t *json, *jdeltas; - char const *str; - - state = create_rrdp_state("session", "1234", 0); - - json = rrdp_state2json(state); - ck_assert_ptr_ne(NULL, json); - - rrdp_state_free(state); - state = NULL; - - ck_assert_int_eq(0, json_get_str(json, TAGNAME_SESSION, &str)); - ck_assert_str_eq("session", str); - ck_assert_int_eq(0, json_get_str(json, TAGNAME_SERIAL, &str)); - ck_assert_str_eq("1234", str); - ck_assert_int_eq(ENOENT, json_get_array(json, TAGNAME_DELTAS, &jdeltas)); - - ck_assert_int_eq(0, rrdp_json2state(json, &state)); - ck_rrdp_session("session", "1234", &state->session); - ck_assert_uint_eq(true, STAILQ_EMPTY(&state->delta_hashes)); - - json_decref(json); - rrdp_state_free(state); -} -END_TEST - -static void -ck_hash(struct rrdp_hash *hash, unsigned char chara) -{ - size_t i; - for (i = 0; i < sizeof(hash->bytes); i++) - ck_assert_uint_eq(chara, hash->bytes[i]); -} - -START_TEST(test_2s_more) -{ - struct rrdp_state *state; - struct rrdp_hash *hash; - json_t *json, *jdeltas; - char const *str; - - state = create_rrdp_state("session", - "123456789012345678901234567890123456789012", - 0xAA, 0xBB, 0xCD, 0); - - json = rrdp_state2json(state); - ck_assert_ptr_ne(NULL, json); - - rrdp_state_free(state); - state = NULL; - - ck_assert_int_eq(0, json_get_str(json, TAGNAME_SESSION, &str)); - ck_assert_str_eq("session", str); - ck_assert_int_eq(0, json_get_str(json, TAGNAME_SERIAL, &str)); - ck_assert_str_eq("123456789012345678901234567890123456789012", str); - ck_assert_int_eq(0, json_get_array(json, TAGNAME_DELTAS, &jdeltas)); - ck_assert_uint_eq(3, json_array_size(jdeltas)); - ck_assert_str_eq("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - json_string_value(json_array_get(jdeltas, 0))); - ck_assert_str_eq("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - json_string_value(json_array_get(jdeltas, 1))); - ck_assert_str_eq("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", - json_string_value(json_array_get(jdeltas, 2))); - - ck_assert_int_eq(0, rrdp_json2state(json, &state)); - ck_rrdp_session("session", "123456789012345678901234567890123456789012", &state->session); - hash = STAILQ_FIRST(&state->delta_hashes); - ck_assert_ptr_ne(NULL, hash); - ck_hash(hash, 0xAA); - hash = STAILQ_NEXT(hash, hook); - ck_assert_ptr_ne(NULL, hash); - ck_hash(hash, 0xBB); - hash = STAILQ_NEXT(hash, hook); - ck_assert_ptr_ne(NULL, hash); - ck_hash(hash, 0xCD); - hash = STAILQ_NEXT(hash, hook); - ck_assert_ptr_eq(NULL, hash); - - json_decref(json); - rrdp_state_free(state); -} -END_TEST - -void -ck_json2state(int expected, char const *json_str) -{ - json_t *json; - json_error_t error; - struct rrdp_state *state; - - json = json_loads(json_str, 0, &error); - ck_assert_ptr_ne(NULL, json); - - state = NULL; - ck_assert_int_eq(expected, rrdp_json2state(json, &state)); - - json_decref(json); - if (state != NULL) - rrdp_state_free(state); -} - -START_TEST(test_2s_errors) -{ - struct rrdp_state state = { 0 }; - - ck_assert_ptr_eq(NULL, rrdp_state2json(&state)); - state.session.session_id = "sid"; - ck_assert_ptr_eq(NULL, rrdp_state2json(&state)); - - ck_json2state(ENOENT, "{}"); - ck_json2state(0, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":\"123\" }"); - ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":null, \"" TAGNAME_SERIAL "\":\"123\" }"); - ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":null }"); - ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":123, \"" TAGNAME_SERIAL "\":\"123\" }"); - ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":123 }"); - ck_json2state(ENOENT, "{ \"" TAGNAME_SESSION "\":\"sss\" }"); - ck_json2state(ENOENT, "{ \"" TAGNAME_SERIAL "\":\"123\" }"); - ck_json2state(-EINVAL, - "{ \"" TAGNAME_SESSION "\":\"sss\"," - "\"" TAGNAME_SERIAL "\":\"123\"," - "\"" TAGNAME_DELTAS "\":null }"); - ck_json2state(-EINVAL, - "{ \"" TAGNAME_SESSION "\":\"sss\"," - "\"" TAGNAME_SERIAL "\":\"123\"," - "\"" TAGNAME_DELTAS "\":\"123\" }"); - ck_json2state(-EINVAL, - "{ \"" TAGNAME_SESSION "\":\"sss\"," - "\"" TAGNAME_SERIAL "\":\"123\"," - "\"" TAGNAME_DELTAS "\":{} }"); - ck_json2state(0, - "{ \"" TAGNAME_SESSION "\":\"sss\"," - "\"" TAGNAME_SERIAL "\":\"123\"," - "\"" TAGNAME_DELTAS "\":[] }"); - ck_json2state(-EINVAL, - "{ \"" TAGNAME_SESSION "\":\"sss\"," - "\"" TAGNAME_SERIAL "\":\"123\"," - "\"" TAGNAME_DELTAS "\":[ 1 ] }"); - ck_json2state(-EINVAL, - "{ \"" TAGNAME_SESSION "\":\"sss\"," - "\"" TAGNAME_SERIAL "\":\"123\"," - "\"" TAGNAME_DELTAS "\":[ \"111\" ] }"); - ck_json2state(0, - "{ \"" TAGNAME_SESSION "\":\"sss\"," - "\"" TAGNAME_SERIAL "\":\"123\"," - "\"" TAGNAME_DELTAS "\":[ \"1111111111111111111111111111111111111111111111111111111111111111\" ] }"); -} -END_TEST - static Suite *create_suite(void) { Suite *suite; - TCase *misc, *parse, *cf; + TCase *misc, *parse; misc = tcase_create("misc"); tcase_add_test(misc, test_xmlChar_NULL_assumption); @@ -783,15 +589,9 @@ static Suite *create_suite(void) tcase_add_test(parse, test_parse_notification_bad_uri); tcase_add_test(parse, test_parse_snapshot_bad_publish); - cf = tcase_create("cachefile"); - tcase_add_test(parse, test_2s_simple); - tcase_add_test(parse, test_2s_more); - tcase_add_test(parse, test_2s_errors); - suite = suite_create("RRDP"); suite_add_tcase(suite, misc); suite_add_tcase(suite, parse); - suite_add_tcase(suite, cf); return suite; } diff --git a/test/rrdp_update_test.c b/test/rrdp_update_test.c index 447779db..1082e460 100644 --- a/test/rrdp_update_test.c +++ b/test/rrdp_update_test.c @@ -6,12 +6,15 @@ #include "common.c" #include "file.c" #include "hash.c" +#include "json_util.c" #include "mock.c" #include "mock_https.c" #include "relax_ng.c" #include "rrdp.c" #include "rrdp_util.h" #include "types/map.c" +#include "types/path.c" +#include "types/str.c" #include "types/url.c" /* Utils */ -- 2.47.2