repo.c revision 1.9
1/*	$OpenBSD: repo.c,v 1.9 2021/08/12 15:27:15 claudio Exp $ */
2/*
3 * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
4 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/queue.h>
20#include <sys/tree.h>
21#include <sys/types.h>
22#include <sys/stat.h>
23
24#include <assert.h>
25#include <err.h>
26#include <errno.h>
27#include <fcntl.h>
28#include <fts.h>
29#include <limits.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34
35#include <imsg.h>
36
37#include "extern.h"
38
39extern struct stats	stats;
40extern int		noop;
41extern int		rrdpon;
42
43enum repo_state {
44	REPO_LOADING = 0,
45	REPO_DONE = 1,
46	REPO_FAILED = -1,
47};
48
49/*
50 * A ta, rsync or rrdp repository.
51 * Depending on what is needed the generic repository is backed by
52 * a ta, rsync or rrdp repository. Multiple repositories can use the
53 * same backend.
54 */
55struct rrdprepo {
56	SLIST_ENTRY(rrdprepo)	 entry;
57	char			*notifyuri;
58	char			*basedir;
59	char			*temp;
60	struct filepath_tree	 added;
61	struct filepath_tree	 deleted;
62	size_t			 id;
63	enum repo_state		 state;
64};
65SLIST_HEAD(, rrdprepo)	rrdprepos = SLIST_HEAD_INITIALIZER(rrdprepos);
66
67struct rsyncrepo {
68	SLIST_ENTRY(rsyncrepo)	 entry;
69	char			*repouri;
70	char			*basedir;
71	size_t			 id;
72	enum repo_state		 state;
73};
74SLIST_HEAD(, rsyncrepo)	rsyncrepos = SLIST_HEAD_INITIALIZER(rsyncrepos);
75
76struct tarepo {
77	SLIST_ENTRY(tarepo)	 entry;
78	char			*descr;
79	char			*basedir;
80	char			*temp;
81	char			**uri;
82	size_t			 urisz;
83	size_t			 uriidx;
84	size_t			 id;
85	enum repo_state		 state;
86};
87SLIST_HEAD(, tarepo)	tarepos = SLIST_HEAD_INITIALIZER(tarepos);
88
89struct	repo {
90	SLIST_ENTRY(repo)	 entry;
91	char			*repouri;	/* CA repository base URI */
92	const struct rrdprepo	*rrdp;
93	const struct rsyncrepo	*rsync;
94	const struct tarepo	*ta;
95	struct entityq		 queue;		/* files waiting for repo */
96	size_t			 id;		/* identifier */
97};
98SLIST_HEAD(, repo)	repos = SLIST_HEAD_INITIALIZER(repos);
99
100/* counter for unique repo id */
101size_t			repoid;
102
103/*
104 * Database of all file path accessed during a run.
105 */
106struct filepath {
107	RB_ENTRY(filepath)	entry;
108	char			*file;
109};
110
111static inline int
112filepathcmp(struct filepath *a, struct filepath *b)
113{
114	return strcmp(a->file, b->file);
115}
116
117RB_PROTOTYPE(filepath_tree, filepath, entry, filepathcmp);
118
119/*
120 * Functions to lookup which files have been accessed during computation.
121 */
122int
123filepath_add(struct filepath_tree *tree, char *file)
124{
125	struct filepath *fp;
126
127	if ((fp = malloc(sizeof(*fp))) == NULL)
128		err(1, NULL);
129	if ((fp->file = strdup(file)) == NULL)
130		err(1, NULL);
131
132	if (RB_INSERT(filepath_tree, tree, fp) != NULL) {
133		/* already in the tree */
134		free(fp->file);
135		free(fp);
136		return 0;
137	}
138
139	return 1;
140}
141
142/*
143 * Lookup a file path in the tree and return the object if found or NULL.
144 */
145static struct filepath *
146filepath_find(struct filepath_tree *tree, char *file)
147{
148	struct filepath needle;
149
150	needle.file = file;
151	return RB_FIND(filepath_tree, tree, &needle);
152}
153
154/*
155 * Returns true if file exists in the tree.
156 */
157static int
158filepath_exists(struct filepath_tree *tree, char *file)
159{
160	return filepath_find(tree, file) != NULL;
161}
162
163/*
164 * Return true if a filepath entry exists that starts with path.
165 */
166static int
167filepath_dir_exists(struct filepath_tree *tree, char *path)
168{
169	struct filepath needle;
170	struct filepath *res;
171
172	needle.file = path;
173	res = RB_NFIND(filepath_tree, tree, &needle);
174	while (res != NULL && strstr(res->file, path) == res->file) {
175		/* make sure that filepath actually is in that path */
176		if (res->file[strlen(path)] == '/')
177			return 1;
178		res = RB_NEXT(filepath_tree, tree, res);
179	}
180	return 0;
181}
182
183/*
184 * Remove entry from tree and free it.
185 */
186static void
187filepath_put(struct filepath_tree *tree, struct filepath *fp)
188{
189	RB_REMOVE(filepath_tree, tree, fp);
190	free((void *)fp->file);
191	free(fp);
192}
193
194/*
195 * Free all elements of a filepath tree.
196 */
197static void
198filepath_free(struct filepath_tree *tree)
199{
200	struct filepath *fp, *nfp;
201
202	RB_FOREACH_SAFE(fp, filepath_tree, tree, nfp)
203		filepath_put(tree, fp);
204}
205
206RB_GENERATE(filepath_tree, filepath, entry, filepathcmp);
207
208/*
209 * Function to hash a string into a unique directory name.
210 * prefixed with dir.
211 */
212static char *
213hash_dir(const char *uri, const char *dir)
214{
215	const char hex[] = "0123456789abcdef";
216	unsigned char m[SHA256_DIGEST_LENGTH];
217	char hash[SHA256_DIGEST_LENGTH * 2 + 1];
218	char *out;
219	size_t i;
220
221	SHA256(uri, strlen(uri), m);
222	for (i = 0; i < SHA256_DIGEST_LENGTH; i++) {
223		hash[i * 2] = hex[m[i] >> 4];
224		hash[i * 2 + 1] = hex[m[i] & 0xf];
225	}
226	hash[SHA256_DIGEST_LENGTH * 2] = '\0';
227
228	asprintf(&out, "%s/%s", dir, hash);
229	return out;
230}
231
232/*
233 * Function to build the directory name based on URI and a directory
234 * as prefix. Skip the proto:// in URI but keep everything else.
235 */
236static char *
237rsync_dir(const char *uri, const char *dir)
238{
239	char *local, *out;
240
241	local = strchr(uri, ':') + strlen("://");
242
243	asprintf(&out, "%s/%s", dir, local);
244	return out;
245}
246
247/*
248 * Function to create all missing directories to a path.
249 * This functions alters the path temporarily.
250 */
251static int
252repo_mkpath(char *file)
253{
254	char *slash;
255
256	/* build directory hierarchy */
257	slash = strrchr(file, '/');
258	assert(slash != NULL);
259	*slash = '\0';
260	if (mkpath(file) == -1) {
261		warn("mkpath %s", file);
262		return -1;
263	}
264	*slash = '/';
265	return 0;
266}
267
268/*
269 * Build TA file name based on the repo info.
270 * If temp is set add Xs for mkostemp.
271 */
272static char *
273ta_filename(const struct tarepo *tr, int temp)
274{
275	const char *file;
276	char *nfile;
277
278	/* does not matter which URI, all end with same filename */
279	file = strrchr(tr->uri[0], '/');
280	assert(file);
281
282	if (asprintf(&nfile, "%s%s%s", tr->basedir, file,
283	    temp ? ".XXXXXXXX": "") == -1)
284		err(1, NULL);
285
286	return nfile;
287}
288
289/*
290 * Build local file name base on the URI and the rrdprepo info.
291 */
292static char *
293rrdp_filename(const struct rrdprepo *rr, const char *uri, int temp)
294{
295	char *nfile;
296	char *dir = rr->basedir;
297
298	if (temp)
299		dir = rr->temp;
300
301	if (!valid_uri(uri, strlen(uri), "rsync://")) {
302		warnx("%s: bad URI %s", rr->basedir, uri);
303		return NULL;
304	}
305
306	uri += strlen("rsync://");	/* skip proto */
307	if (asprintf(&nfile, "%s/%s", dir, uri) == -1)
308		err(1, NULL);
309	return nfile;
310}
311
312/*
313 * Build RRDP state file name based on the repo info.
314 * If temp is set add Xs for mkostemp.
315 */
316static char *
317rrdp_state_filename(const struct rrdprepo *rr, int temp)
318{
319	char *nfile;
320
321	if (asprintf(&nfile, "%s/.state%s", rr->basedir,
322	    temp ? ".XXXXXXXX": "") == -1)
323		err(1, NULL);
324
325	return nfile;
326}
327
328
329
330static void
331ta_fetch(struct tarepo *tr)
332{
333	if (!rrdpon) {
334		for (; tr->uriidx < tr->urisz; tr->uriidx++) {
335			if (strncasecmp(tr->uri[tr->uriidx],
336			    "rsync://", 8) == 0)
337				break;
338		}
339	}
340
341	if (tr->uriidx >= tr->urisz) {
342		struct repo *rp;
343
344		tr->state = REPO_FAILED;
345		logx("ta/%s: fallback to cache", tr->descr);
346
347		SLIST_FOREACH(rp, &repos, entry)
348			if (rp->ta == tr)
349				entityq_flush(&rp->queue, rp);
350		return;
351	}
352
353	logx("ta/%s: pulling from %s", tr->descr, tr->uri[tr->uriidx]);
354
355	if (strncasecmp(tr->uri[tr->uriidx], "rsync://", 8) == 0) {
356		/*
357		 * Create destination location.
358		 * Build up the tree to this point.
359		 */
360		rsync_fetch(tr->id, tr->uri[tr->uriidx], tr->basedir);
361	} else {
362		int fd;
363
364		tr->temp = ta_filename(tr, 1);
365		fd = mkostemp(tr->temp, O_CLOEXEC);
366		if (fd == -1) {
367			warn("mkostemp: %s", tr->temp);
368			http_finish(tr->id, HTTP_FAILED, NULL);
369			return;
370		}
371		if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1)
372			warn("fchmod: %s", tr->temp);
373
374		http_fetch(tr->id, tr->uri[tr->uriidx], NULL, fd);
375	}
376}
377
378static struct tarepo *
379ta_get(struct tal *tal)
380{
381	struct tarepo *tr;
382
383	/* no need to look for possible other repo */
384
385	if (tal->urisz == 0)
386		errx(1, "TAL %s has no URI", tal->descr);
387
388	if ((tr = calloc(1, sizeof(*tr))) == NULL)
389		err(1, NULL);
390	tr->id = ++repoid;
391	SLIST_INSERT_HEAD(&tarepos, tr, entry);
392
393	if ((tr->descr = strdup(tal->descr)) == NULL)
394		err(1, NULL);
395	if (asprintf(&tr->basedir, "ta/%s", tal->descr) == -1)
396		err(1, NULL);
397
398	/* steal URI infromation from TAL */
399	tr->urisz = tal->urisz;
400	tr->uri = tal->uri;
401	tal->urisz = 0;
402	tal->uri = NULL;
403
404	if (noop) {
405		tr->state = REPO_DONE;
406		logx("ta/%s: using cache", tr->descr);
407		/* there is nothing in the queue so no need to flush */
408	} else {
409		/* try to create base directory */
410		if (mkpath(tr->basedir) == -1)
411			warn("mkpath %s", tr->basedir);
412
413		ta_fetch(tr);
414	}
415
416	return tr;
417}
418
419static struct tarepo *
420ta_find(size_t id)
421{
422	struct tarepo *tr;
423
424	SLIST_FOREACH(tr, &tarepos, entry)
425		if (id == tr->id)
426			break;
427	return tr;
428}
429
430static void
431ta_free(void)
432{
433	struct tarepo *tr;
434
435	while ((tr = SLIST_FIRST(&tarepos)) != NULL) {
436		SLIST_REMOVE_HEAD(&tarepos, entry);
437		free(tr->descr);
438		free(tr->basedir);
439		free(tr->temp);
440		free(tr->uri);
441		free(tr);
442	}
443}
444
445static struct rsyncrepo *
446rsync_get(const char *uri)
447{
448	struct rsyncrepo *rr;
449	char *repo;
450
451	if ((repo = rsync_base_uri(uri)) == NULL)
452		errx(1, "bad caRepository URI: %s", uri);
453
454	SLIST_FOREACH(rr, &rsyncrepos, entry)
455		if (strcmp(rr->repouri, repo) == 0) {
456			free(repo);
457			return rr;
458		}
459
460	if ((rr = calloc(1, sizeof(*rr))) == NULL)
461		err(1, NULL);
462
463	rr->id = ++repoid;
464	SLIST_INSERT_HEAD(&rsyncrepos, rr, entry);
465
466	rr->repouri = repo;
467	rr->basedir = rsync_dir(repo, "rsync");
468
469	if (noop) {
470		rr->state = REPO_DONE;
471		logx("%s: using cache", rr->basedir);
472		/* there is nothing in the queue so no need to flush */
473	} else {
474		/* create base directory */
475		if (mkpath(rr->basedir) == -1) {
476			warn("mkpath %s", rr->basedir);
477			rsync_finish(rr->id, 0);
478			return rr;
479		}
480
481		logx("%s: pulling from %s", rr->basedir, rr->repouri);
482		rsync_fetch(rr->id, rr->repouri, rr->basedir);
483	}
484
485	return rr;
486}
487
488static struct rsyncrepo *
489rsync_find(size_t id)
490{
491	struct rsyncrepo *rr;
492
493	SLIST_FOREACH(rr, &rsyncrepos, entry)
494		if (id == rr->id)
495			break;
496	return rr;
497}
498
499static void
500rsync_free(void)
501{
502	struct rsyncrepo *rr;
503
504	while ((rr = SLIST_FIRST(&rsyncrepos)) != NULL) {
505		SLIST_REMOVE_HEAD(&rsyncrepos, entry);
506		free(rr->repouri);
507		free(rr->basedir);
508		free(rr);
509	}
510}
511
512static int rrdprepo_fetch(struct rrdprepo *);
513
514static struct rrdprepo *
515rrdp_get(const char *uri)
516{
517	struct rrdprepo *rr;
518
519	SLIST_FOREACH(rr, &rrdprepos, entry)
520		if (strcmp(rr->notifyuri, uri) == 0) {
521			if (rr->state == REPO_FAILED)
522				return NULL;
523			return rr;
524		}
525
526	if ((rr = calloc(1, sizeof(*rr))) == NULL)
527		err(1, NULL);
528
529	rr->id = ++repoid;
530	SLIST_INSERT_HEAD(&rrdprepos, rr, entry);
531
532	if ((rr->notifyuri = strdup(uri)) == NULL)
533		err(1, NULL);
534	rr->basedir = hash_dir(uri, "rrdp");
535
536	RB_INIT(&rr->added);
537	RB_INIT(&rr->deleted);
538
539	if (noop) {
540		rr->state = REPO_DONE;
541		logx("%s: using cache", rr->notifyuri);
542		/* there is nothing in the queue so no need to flush */
543	} else {
544		/* create base directory */
545		if (mkpath(rr->basedir) == -1) {
546			warn("mkpath %s", rr->basedir);
547			rrdp_finish(rr->id, 0);
548			return rr;
549		}
550		if (rrdprepo_fetch(rr) == -1) {
551			rrdp_finish(rr->id, 0);
552			return rr;
553		}
554
555		logx("%s: pulling from %s", rr->notifyuri, "network");
556	}
557
558	return rr;
559}
560
561static struct rrdprepo *
562rrdp_find(size_t id)
563{
564	struct rrdprepo *rr;
565
566	SLIST_FOREACH(rr, &rrdprepos, entry)
567		if (id == rr->id)
568			break;
569	return rr;
570}
571
572static void
573rrdp_free(void)
574{
575	struct rrdprepo *rr;
576
577	while ((rr = SLIST_FIRST(&rrdprepos)) != NULL) {
578		SLIST_REMOVE_HEAD(&rrdprepos, entry);
579
580		free(rr->notifyuri);
581		free(rr->basedir);
582		free(rr->temp);
583
584		filepath_free(&rr->added);
585		filepath_free(&rr->deleted);
586
587		free(rr);
588	}
589}
590
591static struct rrdprepo *
592rrdp_basedir(const char *dir)
593{
594	struct rrdprepo *rr;
595
596	SLIST_FOREACH(rr, &rrdprepos, entry)
597		if (strcmp(dir, rr->basedir) == 0) {
598			if (rr->state == REPO_FAILED)
599				return NULL;
600			return rr;
601		}
602
603	return NULL;
604}
605
606/*
607 * Allocate and insert a new repository.
608 */
609static struct repo *
610repo_alloc(void)
611{
612	struct repo *rp;
613
614	if ((rp = calloc(1, sizeof(*rp))) == NULL)
615		err(1, NULL);
616
617	rp->id = ++repoid;
618	TAILQ_INIT(&rp->queue);
619	SLIST_INSERT_HEAD(&repos, rp, entry);
620
621	stats.repos++;
622	return rp;
623}
624
625/*
626 * Return the state of a repository.
627 */
628static enum repo_state
629repo_state(struct repo *rp)
630{
631	if (rp->ta)
632		return rp->ta->state;
633	if (rp->rrdp)
634		return rp->rrdp->state;
635	if (rp->rsync)
636		return rp->rsync->state;
637	errx(1, "%s: bad repo", rp->repouri);
638}
639
640/*
641 * Parse the RRDP state file if it exists and set the session struct
642 * based on that information.
643 */
644static void
645rrdp_parse_state(const struct rrdprepo *rr, struct rrdp_session *state)
646{
647	FILE *f;
648	int fd, ln = 0;
649	const char *errstr;
650	char *line = NULL, *file;
651	size_t len = 0;
652	ssize_t n;
653
654	file = rrdp_state_filename(rr, 0);
655	if ((fd = open(file, O_RDONLY)) == -1) {
656		if (errno != ENOENT)
657			warn("%s: open state file", rr->basedir);
658		free(file);
659		return;
660	}
661	free(file);
662	f = fdopen(fd, "r");
663	if (f == NULL)
664		err(1, "fdopen");
665
666	while ((n = getline(&line, &len, f)) != -1) {
667		if (line[n - 1] == '\n')
668			line[n - 1] = '\0';
669		switch (ln) {
670		case 0:
671			if ((state->session_id = strdup(line)) == NULL)
672				err(1, NULL);
673			break;
674		case 1:
675			state->serial = strtonum(line, 1, LLONG_MAX, &errstr);
676			if (errstr)
677				goto fail;
678			break;
679		case 2:
680			if ((state->last_mod = strdup(line)) == NULL)
681				err(1, NULL);
682			break;
683		default:
684			goto fail;
685		}
686		ln++;
687	}
688
689	free(line);
690	if (ferror(f))
691		goto fail;
692	fclose(f);
693	return;
694
695fail:
696	warnx("%s: troubles reading state file", rr->basedir);
697	fclose(f);
698	free(state->session_id);
699	free(state->last_mod);
700	memset(state, 0, sizeof(*state));
701}
702
703/*
704 * Carefully write the RRDP session state file back.
705 */
706void
707rrdp_save_state(size_t id, struct rrdp_session *state)
708{
709	struct rrdprepo *rr;
710	char *temp, *file;
711	FILE *f;
712	int fd;
713
714	rr = rrdp_find(id);
715	if (rr == NULL)
716		errx(1, "non-existant rrdp repo %zu", id);
717
718	file = rrdp_state_filename(rr, 0);
719	temp = rrdp_state_filename(rr, 1);
720
721	if ((fd = mkostemp(temp, O_CLOEXEC)) == -1) {
722		warn("mkostemp %s", temp);
723		goto fail;
724	}
725	(void) fchmod(fd, 0644);
726	f = fdopen(fd, "w");
727	if (f == NULL)
728		err(1, "fdopen");
729
730	/* write session state file out */
731	if (fprintf(f, "%s\n%lld\n", state->session_id,
732	    state->serial) < 0) {
733		fclose(f);
734		goto fail;
735	}
736	if (state->last_mod != NULL) {
737		if (fprintf(f, "%s\n", state->last_mod) < 0) {
738			fclose(f);
739			goto fail;
740		}
741	}
742	if (fclose(f) != 0)
743		goto fail;
744
745	if (rename(temp, file) == -1)
746		warn("%s: rename state file", rr->basedir);
747
748	free(temp);
749	free(file);
750	return;
751
752fail:
753	warnx("%s: failed to save state", rr->basedir);
754	unlink(temp);
755	free(temp);
756	free(file);
757}
758
759/*
760 * Write a file into the temporary RRDP dir but only after checking
761 * its hash (if required). The function also makes sure that the file
762 * tracking is properly adjusted.
763 * Returns 1 on success, 0 if the repo is corrupt, -1 on IO error
764 */
765int
766rrdp_handle_file(size_t id, enum publish_type pt, char *uri,
767    char *hash, size_t hlen, char *data, size_t dlen)
768{
769	struct rrdprepo *rr;
770	struct filepath *fp;
771	ssize_t s;
772	char *fn;
773	int fd = -1;
774
775	rr = rrdp_find(id);
776	if (rr == NULL)
777		errx(1, "non-existant rrdp repo %zu", id);
778	if (rr->state == REPO_FAILED)
779		return -1;
780
781	if (pt == PUB_UPD || pt == PUB_DEL) {
782		if (filepath_exists(&rr->deleted, uri)) {
783			warnx("%s: already deleted", uri);
784			return 0;
785		}
786		fp = filepath_find(&rr->added, uri);
787		if (fp == NULL) {
788			if ((fn = rrdp_filename(rr, uri, 0)) == NULL)
789				return 0;
790		} else {
791			filepath_put(&rr->added, fp);
792			if ((fn = rrdp_filename(rr, uri, 1)) == NULL)
793				return 0;
794		}
795		if (!valid_filehash(fn, hash, hlen)) {
796			warnx("%s: bad message digest", fn);
797			free(fn);
798			return 0;
799		}
800		free(fn);
801	}
802
803	if (pt == PUB_DEL) {
804		filepath_add(&rr->deleted, uri);
805	} else {
806		fp = filepath_find(&rr->deleted, uri);
807		if (fp != NULL)
808			filepath_put(&rr->deleted, fp);
809
810		/* add new file to temp dir */
811		if ((fn = rrdp_filename(rr, uri, 1)) == NULL)
812			return 0;
813
814		if (repo_mkpath(fn) == -1)
815			goto fail;
816
817		fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, 0644);
818		if (fd == -1) {
819			warn("open %s", fn);
820			goto fail;
821		}
822
823		if ((s = write(fd, data, dlen)) == -1) {
824			warn("write %s", fn);
825			goto fail;
826		}
827		close(fd);
828		if ((size_t)s != dlen)	/* impossible */
829			errx(1, "short write %s", fn);
830		free(fn);
831		filepath_add(&rr->added, uri);
832	}
833
834	return 1;
835
836fail:
837	rr->state = REPO_FAILED;
838	if (fd != -1)
839		close(fd);
840	free(fn);
841	return -1;
842}
843
844/*
845 * Initiate a RRDP sync, create the required temporary directory and
846 * parse a possible state file before sending the request to the RRDP process.
847 */
848static int
849rrdprepo_fetch(struct rrdprepo *rr)
850{
851	struct rrdp_session state = { 0 };
852
853	if (asprintf(&rr->temp, "%s.XXXXXXXX", rr->basedir) == -1)
854		err(1, NULL);
855	if (mkdtemp(rr->temp) == NULL) {
856		warn("mkdtemp %s", rr->temp);
857		return -1;
858	}
859
860	rrdp_parse_state(rr, &state);
861	rrdp_fetch(rr->id, rr->notifyuri, rr->notifyuri, &state);
862
863	free(state.session_id);
864	free(state.last_mod);
865
866	return 0;
867}
868
869static int
870rrdp_merge_repo(struct rrdprepo *rr)
871{
872	struct filepath *fp, *nfp;
873	char *fn, *rfn;
874
875	RB_FOREACH_SAFE(fp, filepath_tree, &rr->added, nfp) {
876		fn = rrdp_filename(rr, fp->file, 1);
877		rfn = rrdp_filename(rr, fp->file, 0);
878
879		if (fn == NULL || rfn == NULL)
880			errx(1, "bad filepath");	/* should not happen */
881
882		if (repo_mkpath(rfn) == -1) {
883			goto fail;
884		}
885
886		if (rename(fn, rfn) == -1) {
887			warn("rename %s", rfn);
888			goto fail;
889		}
890
891		free(rfn);
892		free(fn);
893		filepath_put(&rr->added, fp);
894	}
895
896	return 1;
897
898fail:
899	free(rfn);
900	free(fn);
901	return 0;
902}
903
904static void
905rrdp_clean_temp(struct rrdprepo *rr)
906{
907	struct filepath *fp, *nfp;
908	char *fn;
909
910	filepath_free(&rr->deleted);
911
912	RB_FOREACH_SAFE(fp, filepath_tree, &rr->added, nfp) {
913		if ((fn = rrdp_filename(rr, fp->file, 1)) != NULL) {
914			if (unlink(fn) == -1)
915				warn("unlink %s", fn);
916			free(fn);
917		}
918		filepath_put(&rr->added, fp);
919	}
920}
921
922/*
923 * RSYNC sync finished, either with or without success.
924 */
925void
926rsync_finish(size_t id, int ok)
927{
928	struct rsyncrepo *rr;
929	struct tarepo *tr;
930	struct repo *rp;
931
932	tr = ta_find(id);
933	if (tr != NULL) {
934		if (ok) {
935			logx("ta/%s: loaded from network", tr->descr);
936			stats.rsync_repos++;
937			tr->state = REPO_DONE;
938		} else {
939			logx("ta/%s: load from network failed", tr->descr);
940			stats.rsync_fails++;
941			tr->uriidx++;
942			ta_fetch(tr);
943			return;
944		}
945		SLIST_FOREACH(rp, &repos, entry)
946			if (rp->ta == tr)
947				entityq_flush(&rp->queue, rp);
948
949		return;
950	}
951
952	rr = rsync_find(id);
953	if (rr == NULL)
954		errx(1, "unknown rsync repo %zu", id);
955
956	if (ok) {
957		logx("%s: loaded from network", rr->basedir);
958		stats.rsync_repos++;
959		rr->state = REPO_DONE;
960	} else {
961		logx("%s: load from network failed, fallback to cache",
962		    rr->basedir);
963		stats.rsync_fails++;
964		rr->state = REPO_FAILED;
965	}
966
967	SLIST_FOREACH(rp, &repos, entry)
968		if (rp->rsync == rr)
969			entityq_flush(&rp->queue, rp);
970}
971
972/*
973 * RRDP sync finshed, either with or without success.
974 */
975void
976rrdp_finish(size_t id, int ok)
977{
978	struct rrdprepo *rr;
979	struct repo *rp;
980
981	rr = rrdp_find(id);
982	if (rr == NULL)
983		errx(1, "unknown RRDP repo %zu", id);
984
985	if (ok && rrdp_merge_repo(rr)) {
986		logx("%s: loaded from network", rr->notifyuri);
987		rr->state = REPO_DONE;
988		stats.rrdp_repos++;
989		SLIST_FOREACH(rp, &repos, entry)
990			if (rp->rrdp == rr)
991				entityq_flush(&rp->queue, rp);
992	} else if (!ok) {
993		rrdp_clean_temp(rr);
994		stats.rrdp_fails++;
995		rr->state = REPO_FAILED;
996		logx("%s: load from network failed, fallback to rsync",
997		    rr->notifyuri);
998		SLIST_FOREACH(rp, &repos, entry)
999			if (rp->rrdp == rr) {
1000				rp->rrdp = NULL;
1001				rp->rsync = rsync_get(rp->repouri);
1002				/* need to check if it was already loaded */
1003				if (repo_state(rp) != REPO_LOADING)
1004					entityq_flush(&rp->queue, rp);
1005			}
1006	} else {
1007		rrdp_clean_temp(rr);
1008		stats.rrdp_fails++;
1009		rr->state = REPO_FAILED;
1010		logx("%s: load from network failed", rr->notifyuri);
1011		SLIST_FOREACH(rp, &repos, entry)
1012			if (rp->rrdp == rr)
1013				entityq_flush(&rp->queue, rp);
1014	}
1015}
1016
1017/*
1018 * Handle responses from the http process. For TA file, either rename
1019 * or delete the temporary file. For RRDP requests relay the request
1020 * over to the rrdp process.
1021 */
1022void
1023http_finish(size_t id, enum http_result res, const char *last_mod)
1024{
1025	struct tarepo *tr;
1026	struct repo *rp;
1027
1028	tr = ta_find(id);
1029	if (tr == NULL) {
1030		/* not a TA fetch therefor RRDP */
1031		rrdp_http_done(id, res, last_mod);
1032		return;
1033	}
1034
1035	/* Move downloaded TA file into place, or unlink on failure. */
1036	if (res == HTTP_OK) {
1037		char *file;
1038
1039		file = ta_filename(tr, 0);
1040		if (rename(tr->temp, file) == -1)
1041			warn("rename to %s", file);
1042		free(file);
1043
1044		logx("ta/%s: loaded from network", tr->descr);
1045		tr->state = REPO_DONE;
1046		stats.http_repos++;
1047	} else {
1048		if (unlink(tr->temp) == -1 && errno != ENOENT)
1049			warn("unlink %s", tr->temp);
1050
1051		tr->uriidx++;
1052		logx("ta/%s: load from network failed", tr->descr);
1053		ta_fetch(tr);
1054		return;
1055	}
1056
1057	SLIST_FOREACH(rp, &repos, entry)
1058		if (rp->ta == tr)
1059			entityq_flush(&rp->queue, rp);
1060}
1061
1062
1063
1064/*
1065 * Look up a trust anchor, queueing it for download if not found.
1066 */
1067struct repo *
1068ta_lookup(struct tal *tal)
1069{
1070	struct repo	*rp;
1071
1072	/* Look up in repository table. (Lookup should actually fail here) */
1073	SLIST_FOREACH(rp, &repos, entry) {
1074		if (strcmp(rp->repouri, tal->descr) == 0)
1075			return rp;
1076	}
1077
1078	rp = repo_alloc();
1079	if ((rp->repouri = strdup(tal->descr)) == NULL)
1080		err(1, NULL);
1081	rp->ta = ta_get(tal);
1082
1083	return rp;
1084}
1085
1086/*
1087 * Look up a repository, queueing it for discovery if not found.
1088 */
1089struct repo *
1090repo_lookup(const char *uri, const char *notify)
1091{
1092	struct repo *rp;
1093
1094	/* Look up in repository table. */
1095	SLIST_FOREACH(rp, &repos, entry) {
1096		if (strcmp(rp->repouri, uri) != 0)
1097			continue;
1098		return rp;
1099	}
1100
1101	rp = repo_alloc();
1102	if ((rp->repouri = strdup(uri)) == NULL)
1103		err(1, NULL);
1104
1105	/* try RRDP first if available */
1106	if (notify != NULL)
1107		rp->rrdp = rrdp_get(notify);
1108	if (rp->rrdp == NULL)
1109		rp->rsync = rsync_get(uri);
1110
1111	return rp;
1112}
1113
1114/*
1115 * Build local file name base on the URI and the repo info.
1116 */
1117char *
1118repo_filename(const struct repo *rp, const char *uri)
1119{
1120	char *nfile;
1121	char *dir, *repouri;
1122
1123	if (uri == NULL && rp->ta)
1124		return ta_filename(rp->ta, 0);
1125
1126	assert(uri != NULL);
1127	if (rp->rrdp)
1128		return rrdp_filename(rp->rrdp, uri, 0);
1129
1130	/* must be rsync */
1131	dir = rp->rsync->basedir;
1132	repouri = rp->rsync->repouri;
1133
1134	if (strstr(uri, repouri) != uri) {
1135		warnx("%s: URI %s outside of repository", repouri, uri);
1136		return NULL;
1137	}
1138
1139	uri += strlen(repouri) + 1;	/* skip base and '/' */
1140
1141	if (asprintf(&nfile, "%s/%s", dir, uri) == -1)
1142		err(1, NULL);
1143	return nfile;
1144}
1145
1146int
1147repo_queued(struct repo *rp, struct entity *p)
1148{
1149	if (repo_state(rp) == REPO_LOADING) {
1150		TAILQ_INSERT_TAIL(&rp->queue, p, entries);
1151		return 1;
1152	}
1153	return 0;
1154}
1155
1156static char **
1157add_to_del(char **del, size_t *dsz, char *file)
1158{
1159	size_t i = *dsz;
1160
1161	del = reallocarray(del, i + 1, sizeof(*del));
1162	if (del == NULL)
1163		err(1, NULL);
1164	if ((del[i] = strdup(file)) == NULL)
1165		err(1, NULL);
1166	*dsz = i + 1;
1167	return del;
1168}
1169
1170static char **
1171repo_rrdp_cleanup(struct filepath_tree *tree, struct rrdprepo *rr,
1172    char **del, size_t *delsz)
1173{
1174	struct filepath *fp, *nfp;
1175	char *fn;
1176
1177	RB_FOREACH_SAFE(fp, filepath_tree, &rr->deleted, nfp) {
1178		fn = rrdp_filename(rr, fp->file, 0);
1179		/* temp dir will be cleaned up by repo_cleanup() */
1180
1181		if (fn == NULL)
1182			errx(1, "bad filepath");	/* should not happen */
1183
1184		if (!filepath_exists(tree, fn))
1185			del = add_to_del(del, delsz, fn);
1186		else
1187			warnx("%s: referenced file supposed to be deleted", fn);
1188
1189		free(fn);
1190		filepath_put(&rr->deleted, fp);
1191	}
1192
1193	return del;
1194}
1195
1196void
1197repo_cleanup(struct filepath_tree *tree)
1198{
1199	size_t i, cnt, delsz = 0, dirsz = 0;
1200	char **del = NULL, **dir = NULL;
1201	char *argv[4] = { "ta", "rsync", "rrdp", NULL };
1202	struct rrdprepo *rr;
1203	FTS *fts;
1204	FTSENT *e;
1205
1206	if ((fts = fts_open(argv, FTS_PHYSICAL | FTS_NOSTAT, NULL)) == NULL)
1207		err(1, "fts_open");
1208	errno = 0;
1209	while ((e = fts_read(fts)) != NULL) {
1210		switch (e->fts_info) {
1211		case FTS_NSOK:
1212			if (!filepath_exists(tree, e->fts_path))
1213				del = add_to_del(del, &delsz,
1214				    e->fts_path);
1215			break;
1216		case FTS_D:
1217			/* special cleanup for rrdp directories */
1218			if ((rr = rrdp_basedir(e->fts_path)) != NULL) {
1219				del = repo_rrdp_cleanup(tree, rr, del, &delsz);
1220				if (fts_set(fts, e, FTS_SKIP) == -1)
1221					err(1, "fts_set");
1222			}
1223			break;
1224		case FTS_DP:
1225			if (!filepath_dir_exists(tree, e->fts_path))
1226				dir = add_to_del(dir, &dirsz,
1227				    e->fts_path);
1228			break;
1229		case FTS_SL:
1230		case FTS_SLNONE:
1231			warnx("symlink %s", e->fts_path);
1232			del = add_to_del(del, &delsz, e->fts_path);
1233			break;
1234		case FTS_NS:
1235		case FTS_ERR:
1236			if (e->fts_errno == ENOENT &&
1237			    (strcmp(e->fts_path, "rsync") == 0 ||
1238			    strcmp(e->fts_path, "rrdp") == 0))
1239				continue;
1240			warnx("fts_read %s: %s", e->fts_path,
1241			    strerror(e->fts_errno));
1242			break;
1243		default:
1244			warnx("unhandled[%x] %s", e->fts_info,
1245			    e->fts_path);
1246			break;
1247		}
1248
1249		errno = 0;
1250	}
1251	if (errno)
1252		err(1, "fts_read");
1253	if (fts_close(fts) == -1)
1254		err(1, "fts_close");
1255
1256	cnt = 0;
1257	for (i = 0; i < delsz; i++) {
1258		if (unlink(del[i]) == -1) {
1259			if (errno != ENOENT)
1260				warn("unlink %s", del[i]);
1261		} else {
1262			if (verbose > 1)
1263				logx("deleted %s", del[i]);
1264			cnt++;
1265		}
1266		free(del[i]);
1267	}
1268	free(del);
1269	stats.del_files = cnt;
1270
1271	cnt = 0;
1272	for (i = 0; i < dirsz; i++) {
1273		if (rmdir(dir[i]) == -1)
1274			warn("rmdir %s", dir[i]);
1275		else
1276			cnt++;
1277		free(dir[i]);
1278	}
1279	free(dir);
1280	stats.del_dirs = cnt;
1281}
1282
1283void
1284repo_free(void)
1285{
1286	struct repo *rp;
1287
1288	while ((rp = SLIST_FIRST(&repos)) != NULL) {
1289		SLIST_REMOVE_HEAD(&repos, entry);
1290		free(rp->repouri);
1291		free(rp);
1292	}
1293
1294	ta_free();
1295	rrdp_free();
1296	rsync_free();
1297}
1298