xref: /openbsd-src/usr.sbin/rpki-client/repo.c (revision 6ca44032e7be0d795b9f13c99fbce059e942c15d)
1 /*	$OpenBSD: repo.c,v 1.44 2023/04/26 16:32:41 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 <poll.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #include <imsg.h>
37 
38 #include "extern.h"
39 
40 extern struct stats	stats;
41 extern int		noop;
42 extern int		rrdpon;
43 extern int		repo_timeout;
44 extern time_t		deadline;
45 int			nofetch;
46 
47 enum repo_state {
48 	REPO_LOADING = 0,
49 	REPO_DONE = 1,
50 	REPO_FAILED = -1,
51 };
52 
53 /*
54  * A ta, rsync or rrdp repository.
55  * Depending on what is needed the generic repository is backed by
56  * a ta, rsync or rrdp repository. Multiple repositories can use the
57  * same backend.
58  */
59 struct rrdprepo {
60 	SLIST_ENTRY(rrdprepo)	 entry;
61 	char			*notifyuri;
62 	char			*basedir;
63 	struct filepath_tree	 deleted;
64 	unsigned int		 id;
65 	enum repo_state		 state;
66 };
67 static SLIST_HEAD(, rrdprepo)	rrdprepos = SLIST_HEAD_INITIALIZER(rrdprepos);
68 
69 struct rsyncrepo {
70 	SLIST_ENTRY(rsyncrepo)	 entry;
71 	char			*repouri;
72 	char			*basedir;
73 	unsigned int		 id;
74 	enum repo_state		 state;
75 };
76 static SLIST_HEAD(, rsyncrepo)	rsyncrepos = SLIST_HEAD_INITIALIZER(rsyncrepos);
77 
78 struct tarepo {
79 	SLIST_ENTRY(tarepo)	 entry;
80 	char			*descr;
81 	char			*basedir;
82 	char			*temp;
83 	char			**uri;
84 	size_t			 urisz;
85 	size_t			 uriidx;
86 	unsigned int		 id;
87 	enum repo_state		 state;
88 };
89 static SLIST_HEAD(, tarepo)	tarepos = SLIST_HEAD_INITIALIZER(tarepos);
90 
91 struct repo {
92 	SLIST_ENTRY(repo)	 entry;
93 	char			*repouri;
94 	char			*notifyuri;
95 	char			*basedir;
96 	const struct rrdprepo	*rrdp;
97 	const struct rsyncrepo	*rsync;
98 	const struct tarepo	*ta;
99 	struct entityq		 queue;		/* files waiting for repo */
100 	struct repotalstats	 stats[TALSZ_MAX];
101 	struct repostats	 repostats;
102 	struct timespec		 start_time;
103 	time_t			 alarm;		/* sync timeout */
104 	int			 talid;
105 	int			 stats_used[TALSZ_MAX];
106 	unsigned int		 id;		/* identifier */
107 };
108 static SLIST_HEAD(, repo)	repos = SLIST_HEAD_INITIALIZER(repos);
109 
110 /* counter for unique repo id */
111 unsigned int		repoid;
112 
113 static struct rsyncrepo	*rsync_get(const char *, const char *);
114 static void		 remove_contents(char *);
115 
116 /*
117  * Database of all file path accessed during a run.
118  */
119 struct filepath {
120 	RB_ENTRY(filepath)	entry;
121 	char			*file;
122 };
123 
124 static inline int
125 filepathcmp(struct filepath *a, struct filepath *b)
126 {
127 	return strcmp(a->file, b->file);
128 }
129 
130 RB_PROTOTYPE(filepath_tree, filepath, entry, filepathcmp);
131 
132 /*
133  * Functions to lookup which files have been accessed during computation.
134  */
135 int
136 filepath_add(struct filepath_tree *tree, char *file)
137 {
138 	struct filepath *fp;
139 
140 	if ((fp = malloc(sizeof(*fp))) == NULL)
141 		err(1, NULL);
142 	if ((fp->file = strdup(file)) == NULL)
143 		err(1, NULL);
144 
145 	if (RB_INSERT(filepath_tree, tree, fp) != NULL) {
146 		/* already in the tree */
147 		free(fp->file);
148 		free(fp);
149 		return 0;
150 	}
151 
152 	return 1;
153 }
154 
155 /*
156  * Lookup a file path in the tree and return the object if found or NULL.
157  */
158 static struct filepath *
159 filepath_find(struct filepath_tree *tree, char *file)
160 {
161 	struct filepath needle = { .file = file };
162 
163 	return RB_FIND(filepath_tree, tree, &needle);
164 }
165 
166 /*
167  * Returns true if file exists in the tree.
168  */
169 static int
170 filepath_exists(struct filepath_tree *tree, char *file)
171 {
172 	return filepath_find(tree, file) != NULL;
173 }
174 
175 /*
176  * Remove entry from tree and free it.
177  */
178 static void
179 filepath_put(struct filepath_tree *tree, struct filepath *fp)
180 {
181 	RB_REMOVE(filepath_tree, tree, fp);
182 	free((void *)fp->file);
183 	free(fp);
184 }
185 
186 /*
187  * Free all elements of a filepath tree.
188  */
189 static void
190 filepath_free(struct filepath_tree *tree)
191 {
192 	struct filepath *fp, *nfp;
193 
194 	RB_FOREACH_SAFE(fp, filepath_tree, tree, nfp)
195 		filepath_put(tree, fp);
196 }
197 
198 RB_GENERATE(filepath_tree, filepath, entry, filepathcmp);
199 
200 /*
201  * Function to hash a string into a unique directory name.
202  * Returned hash needs to be freed.
203  */
204 static char *
205 hash_dir(const char *uri)
206 {
207 	unsigned char m[SHA256_DIGEST_LENGTH];
208 
209 	SHA256(uri, strlen(uri), m);
210 	return hex_encode(m, sizeof(m));
211 }
212 
213 /*
214  * Function to build the directory name based on URI and a directory
215  * as prefix. Skip the proto:// in URI but keep everything else.
216  */
217 static char *
218 repo_dir(const char *uri, const char *dir, int hash)
219 {
220 	const char *local;
221 	char *out, *hdir = NULL;
222 
223 	if (hash) {
224 		local = hdir = hash_dir(uri);
225 	} else {
226 		local = strchr(uri, ':');
227 		if (local != NULL)
228 			local += strlen("://");
229 		else
230 			local = uri;
231 	}
232 
233 	if (dir == NULL) {
234 		if ((out = strdup(local)) == NULL)
235 			err(1, NULL);
236 	} else {
237 		if (asprintf(&out, "%s/%s", dir, local) == -1)
238 			err(1, NULL);
239 	}
240 
241 	free(hdir);
242 	return out;
243 }
244 
245 /*
246  * Function to create all missing directories to a path.
247  * This functions alters the path temporarily.
248  */
249 static int
250 repo_mkpath(int fd, char *file)
251 {
252 	char *slash;
253 
254 	/* build directory hierarchy */
255 	slash = strrchr(file, '/');
256 	assert(slash != NULL);
257 	*slash = '\0';
258 	if (mkpathat(fd, file) == -1) {
259 		warn("mkpath %s", file);
260 		return -1;
261 	}
262 	*slash = '/';
263 	return 0;
264 }
265 
266 /*
267  * Return the state of a repository.
268  */
269 static enum repo_state
270 repo_state(const struct repo *rp)
271 {
272 	if (rp->ta)
273 		return rp->ta->state;
274 	if (rp->rsync)
275 		return rp->rsync->state;
276 	if (rp->rrdp)
277 		return rp->rrdp->state;
278 	/* No backend so sync is by definition done. */
279 	return REPO_DONE;
280 }
281 
282 /*
283  * Function called once a repository is done with the sync. Either
284  * successfully or after failure.
285  */
286 static void
287 repo_done(const void *vp, int ok)
288 {
289 	struct repo *rp;
290 	struct timespec flush_time;
291 
292 	SLIST_FOREACH(rp, &repos, entry) {
293 		if (vp != rp->ta && vp != rp->rsync && vp != rp->rrdp)
294 			continue;
295 
296 		/* for rrdp try to fall back to rsync */
297 		if (vp == rp->rrdp && !ok && !nofetch) {
298 			rp->rrdp = NULL;
299 			rp->rsync = rsync_get(rp->repouri, rp->basedir);
300 			/* need to check if it was already loaded */
301 			if (repo_state(rp) == REPO_LOADING)
302 				continue;
303 		}
304 
305 		entityq_flush(&rp->queue, rp);
306 		clock_gettime(CLOCK_MONOTONIC, &flush_time);
307 		timespecsub(&flush_time, &rp->start_time,
308 		    &rp->repostats.sync_time);
309 	}
310 }
311 
312 /*
313  * Build TA file name based on the repo info.
314  * If temp is set add Xs for mkostemp.
315  */
316 static char *
317 ta_filename(const struct tarepo *tr, int temp)
318 {
319 	const char *file;
320 	char *nfile;
321 
322 	/* does not matter which URI, all end with same filename */
323 	file = strrchr(tr->uri[0], '/');
324 	assert(file);
325 
326 	if (asprintf(&nfile, "%s%s%s", tr->basedir, file,
327 	    temp ? ".XXXXXXXX" : "") == -1)
328 		err(1, NULL);
329 
330 	return nfile;
331 }
332 
333 static void
334 ta_fetch(struct tarepo *tr)
335 {
336 	if (!rrdpon) {
337 		for (; tr->uriidx < tr->urisz; tr->uriidx++) {
338 			if (strncasecmp(tr->uri[tr->uriidx],
339 			    "rsync://", 8) == 0)
340 				break;
341 		}
342 	}
343 
344 	if (tr->uriidx >= tr->urisz) {
345 		tr->state = REPO_FAILED;
346 		logx("ta/%s: fallback to cache", tr->descr);
347 
348 		repo_done(tr, 0);
349 		return;
350 	}
351 
352 	logx("ta/%s: pulling from %s", tr->descr, tr->uri[tr->uriidx]);
353 
354 	if (strncasecmp(tr->uri[tr->uriidx], "rsync://", 8) == 0) {
355 		/*
356 		 * Create destination location.
357 		 * Build up the tree to this point.
358 		 */
359 		rsync_fetch(tr->id, tr->uri[tr->uriidx], tr->basedir, NULL);
360 	} else {
361 		int fd;
362 
363 		tr->temp = ta_filename(tr, 1);
364 		fd = mkostemp(tr->temp, O_CLOEXEC);
365 		if (fd == -1) {
366 			warn("mkostemp: %s", tr->temp);
367 			http_finish(tr->id, HTTP_FAILED, NULL);
368 			return;
369 		}
370 		if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1)
371 			warn("fchmod: %s", tr->temp);
372 
373 		http_fetch(tr->id, tr->uri[tr->uriidx], NULL, fd);
374 	}
375 }
376 
377 static struct tarepo *
378 ta_get(struct tal *tal)
379 {
380 	struct tarepo *tr;
381 
382 	/* no need to look for possible other repo */
383 
384 	if ((tr = calloc(1, sizeof(*tr))) == NULL)
385 		err(1, NULL);
386 	tr->id = ++repoid;
387 	SLIST_INSERT_HEAD(&tarepos, tr, entry);
388 
389 	if ((tr->descr = strdup(tal->descr)) == NULL)
390 		err(1, NULL);
391 	tr->basedir = repo_dir(tal->descr, "ta", 0);
392 
393 	/* steal URI information from TAL */
394 	tr->urisz = tal->urisz;
395 	tr->uri = tal->uri;
396 	tal->urisz = 0;
397 	tal->uri = NULL;
398 
399 	ta_fetch(tr);
400 
401 	return tr;
402 }
403 
404 static struct tarepo *
405 ta_find(unsigned int id)
406 {
407 	struct tarepo *tr;
408 
409 	SLIST_FOREACH(tr, &tarepos, entry)
410 		if (id == tr->id)
411 			break;
412 	return tr;
413 }
414 
415 static void
416 ta_free(void)
417 {
418 	struct tarepo *tr;
419 
420 	while ((tr = SLIST_FIRST(&tarepos)) != NULL) {
421 		SLIST_REMOVE_HEAD(&tarepos, entry);
422 		free(tr->descr);
423 		free(tr->basedir);
424 		free(tr->temp);
425 		free(tr->uri);
426 		free(tr);
427 	}
428 }
429 
430 static struct rsyncrepo *
431 rsync_get(const char *uri, const char *validdir)
432 {
433 	struct rsyncrepo *rr;
434 	char *repo;
435 
436 	if ((repo = rsync_base_uri(uri)) == NULL)
437 		errx(1, "bad caRepository URI: %s", uri);
438 
439 	SLIST_FOREACH(rr, &rsyncrepos, entry)
440 		if (strcmp(rr->repouri, repo) == 0) {
441 			free(repo);
442 			return rr;
443 		}
444 
445 	if ((rr = calloc(1, sizeof(*rr))) == NULL)
446 		err(1, NULL);
447 
448 	rr->id = ++repoid;
449 	SLIST_INSERT_HEAD(&rsyncrepos, rr, entry);
450 
451 	rr->repouri = repo;
452 	rr->basedir = repo_dir(repo, ".rsync", 0);
453 
454 	/* create base directory */
455 	if (mkpath(rr->basedir) == -1) {
456 		warn("mkpath %s", rr->basedir);
457 		rsync_finish(rr->id, 0);
458 		return rr;
459 	}
460 
461 	logx("%s: pulling from %s", rr->basedir, rr->repouri);
462 	rsync_fetch(rr->id, rr->repouri, rr->basedir, validdir);
463 
464 	return rr;
465 }
466 
467 static struct rsyncrepo *
468 rsync_find(unsigned int id)
469 {
470 	struct rsyncrepo *rr;
471 
472 	SLIST_FOREACH(rr, &rsyncrepos, entry)
473 		if (id == rr->id)
474 			break;
475 	return rr;
476 }
477 
478 static void
479 rsync_free(void)
480 {
481 	struct rsyncrepo *rr;
482 
483 	while ((rr = SLIST_FIRST(&rsyncrepos)) != NULL) {
484 		SLIST_REMOVE_HEAD(&rsyncrepos, entry);
485 		free(rr->repouri);
486 		free(rr->basedir);
487 		free(rr);
488 	}
489 }
490 
491 /*
492  * Build local file name base on the URI and the rrdprepo info.
493  */
494 static char *
495 rrdp_filename(const struct rrdprepo *rr, const char *uri, int valid)
496 {
497 	char *nfile;
498 	const char *dir = rr->basedir;
499 
500 	if (!valid_uri(uri, strlen(uri), "rsync://"))
501 		errx(1, "%s: bad URI %s", rr->basedir, uri);
502 	uri += strlen("rsync://");	/* skip proto */
503 	if (valid) {
504 		if ((nfile = strdup(uri)) == NULL)
505 			err(1, NULL);
506 	} else {
507 		if (asprintf(&nfile, "%s/%s", dir, uri) == -1)
508 			err(1, NULL);
509 	}
510 	return nfile;
511 }
512 
513 /*
514  * Build RRDP state file name based on the repo info.
515  * If temp is set add Xs for mkostemp.
516  */
517 static char *
518 rrdp_state_filename(const struct rrdprepo *rr, int temp)
519 {
520 	char *nfile;
521 
522 	if (asprintf(&nfile, "%s/.state%s", rr->basedir,
523 	    temp ? ".XXXXXXXX" : "") == -1)
524 		err(1, NULL);
525 
526 	return nfile;
527 }
528 
529 static struct rrdprepo *
530 rrdp_find(unsigned int id)
531 {
532 	struct rrdprepo *rr;
533 
534 	SLIST_FOREACH(rr, &rrdprepos, entry)
535 		if (id == rr->id)
536 			break;
537 	return rr;
538 }
539 
540 static void
541 rrdp_free(void)
542 {
543 	struct rrdprepo *rr;
544 
545 	while ((rr = SLIST_FIRST(&rrdprepos)) != NULL) {
546 		SLIST_REMOVE_HEAD(&rrdprepos, entry);
547 
548 		free(rr->notifyuri);
549 		free(rr->basedir);
550 
551 		filepath_free(&rr->deleted);
552 
553 		free(rr);
554 	}
555 }
556 
557 /*
558  * Check if a directory is an active rrdp repository.
559  * Returns 1 if found else 0.
560  */
561 static struct repo *
562 repo_rrdp_bypath(const char *dir)
563 {
564 	struct repo *rp;
565 
566 	SLIST_FOREACH(rp, &repos, entry) {
567 		if (rp->rrdp == NULL)
568 			continue;
569 		if (strcmp(dir, rp->rrdp->basedir) == 0)
570 			return rp;
571 	}
572 	return NULL;
573 }
574 
575 /*
576  * Check if the URI is actually covered by one of the repositories
577  * that depend on this RRDP repository.
578  * Returns 1 if the URI is valid, 0 if no repouri matches the URI.
579  */
580 static int
581 rrdp_uri_valid(struct rrdprepo *rr, const char *uri)
582 {
583 	struct repo *rp;
584 
585 	SLIST_FOREACH(rp, &repos, entry) {
586 		if (rp->rrdp != rr)
587 			continue;
588 		if (strncmp(uri, rp->repouri, strlen(rp->repouri)) == 0)
589 			return 1;
590 	}
591 	return 0;
592 }
593 
594 /*
595  * Allocate and insert a new repository.
596  */
597 static struct repo *
598 repo_alloc(int talid)
599 {
600 	struct repo *rp;
601 
602 	if ((rp = calloc(1, sizeof(*rp))) == NULL)
603 		err(1, NULL);
604 
605 	rp->id = ++repoid;
606 	rp->talid = talid;
607 	rp->alarm = getmonotime() + repo_timeout;
608 	TAILQ_INIT(&rp->queue);
609 	SLIST_INSERT_HEAD(&repos, rp, entry);
610 	clock_gettime(CLOCK_MONOTONIC, &rp->start_time);
611 
612 	stats.repos++;
613 	return rp;
614 }
615 
616 /*
617  * Parse the RRDP state file if it exists and set the session struct
618  * based on that information.
619  */
620 static void
621 rrdp_parse_state(const struct rrdprepo *rr, struct rrdp_session *state)
622 {
623 	FILE *f;
624 	int fd, ln = 0;
625 	const char *errstr;
626 	char *line = NULL, *file;
627 	size_t len = 0;
628 	ssize_t n;
629 
630 	file = rrdp_state_filename(rr, 0);
631 	if ((fd = open(file, O_RDONLY)) == -1) {
632 		if (errno != ENOENT)
633 			warn("%s: open state file", rr->basedir);
634 		free(file);
635 		return;
636 	}
637 	free(file);
638 	f = fdopen(fd, "r");
639 	if (f == NULL)
640 		err(1, "fdopen");
641 
642 	while ((n = getline(&line, &len, f)) != -1) {
643 		if (line[n - 1] == '\n')
644 			line[n - 1] = '\0';
645 		switch (ln) {
646 		case 0:
647 			if ((state->session_id = strdup(line)) == NULL)
648 				err(1, NULL);
649 			break;
650 		case 1:
651 			state->serial = strtonum(line, 1, LLONG_MAX, &errstr);
652 			if (errstr)
653 				goto fail;
654 			break;
655 		case 2:
656 			if ((state->last_mod = strdup(line)) == NULL)
657 				err(1, NULL);
658 			break;
659 		default:
660 			goto fail;
661 		}
662 		ln++;
663 	}
664 
665 	free(line);
666 	if (ferror(f))
667 		goto fail;
668 	fclose(f);
669 	return;
670 
671 fail:
672 	warnx("%s: troubles reading state file", rr->basedir);
673 	fclose(f);
674 	free(state->session_id);
675 	free(state->last_mod);
676 	memset(state, 0, sizeof(*state));
677 }
678 
679 /*
680  * Carefully write the RRDP session state file back.
681  */
682 void
683 rrdp_save_state(unsigned int id, struct rrdp_session *state)
684 {
685 	struct rrdprepo *rr;
686 	char *temp, *file;
687 	FILE *f;
688 	int fd;
689 
690 	rr = rrdp_find(id);
691 	if (rr == NULL)
692 		errx(1, "non-existent rrdp repo %u", id);
693 
694 	file = rrdp_state_filename(rr, 0);
695 	temp = rrdp_state_filename(rr, 1);
696 
697 	if ((fd = mkostemp(temp, O_CLOEXEC)) == -1) {
698 		warn("mkostemp %s", temp);
699 		goto fail;
700 	}
701 	(void)fchmod(fd, 0644);
702 	f = fdopen(fd, "w");
703 	if (f == NULL)
704 		err(1, "fdopen");
705 
706 	/* write session state file out */
707 	if (fprintf(f, "%s\n%lld\n", state->session_id,
708 	    state->serial) < 0) {
709 		fclose(f);
710 		goto fail;
711 	}
712 	if (state->last_mod != NULL) {
713 		if (fprintf(f, "%s\n", state->last_mod) < 0) {
714 			fclose(f);
715 			goto fail;
716 		}
717 	}
718 	if (fclose(f) != 0)
719 		goto fail;
720 
721 	if (rename(temp, file) == -1)
722 		warn("%s: rename state file", rr->basedir);
723 
724 	free(temp);
725 	free(file);
726 	return;
727 
728 fail:
729 	warnx("%s: failed to save state", rr->basedir);
730 	unlink(temp);
731 	free(temp);
732 	free(file);
733 }
734 
735 static struct rrdprepo *
736 rrdp_get(const char *uri)
737 {
738 	struct rrdp_session state = { 0 };
739 	struct rrdprepo *rr;
740 
741 	SLIST_FOREACH(rr, &rrdprepos, entry)
742 		if (strcmp(rr->notifyuri, uri) == 0) {
743 			if (rr->state == REPO_FAILED)
744 				return NULL;
745 			return rr;
746 		}
747 
748 	if ((rr = calloc(1, sizeof(*rr))) == NULL)
749 		err(1, NULL);
750 
751 	rr->id = ++repoid;
752 	SLIST_INSERT_HEAD(&rrdprepos, rr, entry);
753 
754 	if ((rr->notifyuri = strdup(uri)) == NULL)
755 		err(1, NULL);
756 	rr->basedir = repo_dir(uri, ".rrdp", 1);
757 
758 	RB_INIT(&rr->deleted);
759 
760 	/* create base directory */
761 	if (mkpath(rr->basedir) == -1) {
762 		warn("mkpath %s", rr->basedir);
763 		rrdp_finish(rr->id, 0);
764 		return rr;
765 	}
766 
767 	/* parse state and start the sync */
768 	rrdp_parse_state(rr, &state);
769 	rrdp_fetch(rr->id, rr->notifyuri, rr->notifyuri, &state);
770 	free(state.session_id);
771 	free(state.last_mod);
772 
773 	logx("%s: pulling from %s", rr->notifyuri, "network");
774 
775 	return rr;
776 }
777 
778 /*
779  * Remove RRDP repo and start over.
780  */
781 void
782 rrdp_clear(unsigned int id)
783 {
784 	struct rrdprepo *rr;
785 
786 	rr = rrdp_find(id);
787 	if (rr == NULL)
788 		errx(1, "non-existent rrdp repo %u", id);
789 
790 	/* remove rrdp repository contents */
791 	remove_contents(rr->basedir);
792 }
793 
794 /*
795  * Write a file into the temporary RRDP dir but only after checking
796  * its hash (if required). The function also makes sure that the file
797  * tracking is properly adjusted.
798  * Returns 1 on success, 0 if the repo is corrupt, -1 on IO error
799  */
800 int
801 rrdp_handle_file(unsigned int id, enum publish_type pt, char *uri,
802     char *hash, size_t hlen, char *data, size_t dlen)
803 {
804 	struct rrdprepo *rr;
805 	struct filepath *fp;
806 	ssize_t s;
807 	char *fn = NULL;
808 	int fd = -1, try = 0;
809 
810 	rr = rrdp_find(id);
811 	if (rr == NULL)
812 		errx(1, "non-existent rrdp repo %u", id);
813 	if (rr->state == REPO_FAILED)
814 		return -1;
815 
816 	/* check hash of original file for updates and deletes */
817 	if (pt == PUB_UPD || pt == PUB_DEL) {
818 		if (filepath_exists(&rr->deleted, uri)) {
819 			warnx("%s: already deleted", uri);
820 			return 0;
821 		}
822 		/* try to open file first in rrdp then in valid repo */
823 		do {
824 			free(fn);
825 			if ((fn = rrdp_filename(rr, uri, try++)) == NULL)
826 				return 0;
827 			fd = open(fn, O_RDONLY);
828 		} while (fd == -1 && try < 2);
829 
830 		if (!valid_filehash(fd, hash, hlen)) {
831 			warnx("%s: bad file digest for %s", rr->notifyuri, fn);
832 			free(fn);
833 			return 0;
834 		}
835 		free(fn);
836 	}
837 
838 	/* write new content or mark uri as deleted. */
839 	if (pt == PUB_DEL) {
840 		filepath_add(&rr->deleted, uri);
841 	} else {
842 		fp = filepath_find(&rr->deleted, uri);
843 		if (fp != NULL)
844 			filepath_put(&rr->deleted, fp);
845 
846 		/* add new file to rrdp dir */
847 		if ((fn = rrdp_filename(rr, uri, 0)) == NULL)
848 			return 0;
849 
850 		if (repo_mkpath(AT_FDCWD, fn) == -1)
851 			goto fail;
852 
853 		fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, 0644);
854 		if (fd == -1) {
855 			warn("open %s", fn);
856 			goto fail;
857 		}
858 
859 		if ((s = write(fd, data, dlen)) == -1) {
860 			warn("write %s", fn);
861 			goto fail;
862 		}
863 		close(fd);
864 		if ((size_t)s != dlen)	/* impossible */
865 			errx(1, "short write %s", fn);
866 		free(fn);
867 	}
868 
869 	return 1;
870 
871 fail:
872 	rr->state = REPO_FAILED;
873 	if (fd != -1)
874 		close(fd);
875 	free(fn);
876 	return -1;
877 }
878 
879 /*
880  * RSYNC sync finished, either with or without success.
881  */
882 void
883 rsync_finish(unsigned int id, int ok)
884 {
885 	struct rsyncrepo *rr;
886 	struct tarepo *tr;
887 
888 	tr = ta_find(id);
889 	if (tr != NULL) {
890 		/* repository changed state already, ignore request */
891 		if (tr->state != REPO_LOADING)
892 			return;
893 		if (ok) {
894 			logx("ta/%s: loaded from network", tr->descr);
895 			stats.rsync_repos++;
896 			tr->state = REPO_DONE;
897 			repo_done(tr, 1);
898 		} else {
899 			warnx("ta/%s: load from network failed", tr->descr);
900 			stats.rsync_fails++;
901 			tr->uriidx++;
902 			ta_fetch(tr);
903 		}
904 		return;
905 	}
906 
907 	rr = rsync_find(id);
908 	if (rr == NULL)
909 		errx(1, "unknown rsync repo %u", id);
910 	/* repository changed state already, ignore request */
911 	if (rr->state != REPO_LOADING)
912 		return;
913 
914 	if (ok) {
915 		logx("%s: loaded from network", rr->basedir);
916 		stats.rsync_repos++;
917 		rr->state = REPO_DONE;
918 	} else {
919 		warnx("%s: load from network failed, fallback to cache",
920 		    rr->basedir);
921 		stats.rsync_fails++;
922 		rr->state = REPO_FAILED;
923 		/* clear rsync repo since it failed */
924 		remove_contents(rr->basedir);
925 	}
926 
927 	repo_done(rr, ok);
928 }
929 
930 /*
931  * RRDP sync finished, either with or without success.
932  */
933 void
934 rrdp_finish(unsigned int id, int ok)
935 {
936 	struct rrdprepo *rr;
937 
938 	rr = rrdp_find(id);
939 	if (rr == NULL)
940 		errx(1, "unknown RRDP repo %u", id);
941 	/* repository changed state already, ignore request */
942 	if (rr->state != REPO_LOADING)
943 		return;
944 
945 	if (ok) {
946 		logx("%s: loaded from network", rr->notifyuri);
947 		stats.rrdp_repos++;
948 		rr->state = REPO_DONE;
949 	} else {
950 		warnx("%s: load from network failed, fallback to %s",
951 		    rr->notifyuri, nofetch ? "cache" : "rsync");
952 		stats.rrdp_fails++;
953 		rr->state = REPO_FAILED;
954 		/* clear the RRDP repo since it failed */
955 		remove_contents(rr->basedir);
956 		/* also clear the list of deleted files */
957 		filepath_free(&rr->deleted);
958 	}
959 
960 	repo_done(rr, ok);
961 }
962 
963 /*
964  * Handle responses from the http process. For TA file, either rename
965  * or delete the temporary file. For RRDP requests relay the request
966  * over to the rrdp process.
967  */
968 void
969 http_finish(unsigned int id, enum http_result res, const char *last_mod)
970 {
971 	struct tarepo *tr;
972 
973 	tr = ta_find(id);
974 	if (tr == NULL) {
975 		/* not a TA fetch therefore RRDP */
976 		rrdp_http_done(id, res, last_mod);
977 		return;
978 	}
979 
980 	/* repository changed state already, ignore request */
981 	if (tr->state != REPO_LOADING)
982 		return;
983 
984 	/* Move downloaded TA file into place, or unlink on failure. */
985 	if (res == HTTP_OK) {
986 		char *file;
987 
988 		file = ta_filename(tr, 0);
989 		if (rename(tr->temp, file) == -1)
990 			warn("rename to %s", file);
991 		free(file);
992 
993 		logx("ta/%s: loaded from network", tr->descr);
994 		tr->state = REPO_DONE;
995 		stats.http_repos++;
996 		repo_done(tr, 1);
997 	} else {
998 		if (unlink(tr->temp) == -1 && errno != ENOENT)
999 			warn("unlink %s", tr->temp);
1000 
1001 		tr->uriidx++;
1002 		warnx("ta/%s: load from network failed", tr->descr);
1003 		ta_fetch(tr);
1004 	}
1005 }
1006 
1007 /*
1008  * Look up a trust anchor, queueing it for download if not found.
1009  */
1010 struct repo *
1011 ta_lookup(int id, struct tal *tal)
1012 {
1013 	struct repo	*rp;
1014 
1015 	if (tal->urisz == 0)
1016 		errx(1, "TAL %s has no URI", tal->descr);
1017 
1018 	/* Look up in repository table. (Lookup should actually fail here) */
1019 	SLIST_FOREACH(rp, &repos, entry) {
1020 		if (strcmp(rp->repouri, tal->uri[0]) == 0)
1021 			return rp;
1022 	}
1023 
1024 	rp = repo_alloc(id);
1025 	rp->basedir = repo_dir(tal->descr, "ta", 0);
1026 	if ((rp->repouri = strdup(tal->uri[0])) == NULL)
1027 		err(1, NULL);
1028 
1029 	/* check if sync disabled ... */
1030 	if (noop) {
1031 		logx("%s: using cache", rp->basedir);
1032 		entityq_flush(&rp->queue, rp);
1033 		return rp;
1034 	}
1035 
1036 	/* try to create base directory */
1037 	if (mkpath(rp->basedir) == -1)
1038 		warn("mkpath %s", rp->basedir);
1039 
1040 	rp->ta = ta_get(tal);
1041 
1042 	/* need to check if it was already loaded */
1043 	if (repo_state(rp) != REPO_LOADING)
1044 		entityq_flush(&rp->queue, rp);
1045 
1046 	return rp;
1047 }
1048 
1049 /*
1050  * Look up a repository, queueing it for discovery if not found.
1051  */
1052 struct repo *
1053 repo_lookup(int talid, const char *uri, const char *notify)
1054 {
1055 	struct repo	*rp;
1056 	char		*repouri;
1057 
1058 	if ((repouri = rsync_base_uri(uri)) == NULL)
1059 		errx(1, "bad caRepository URI: %s", uri);
1060 
1061 	/* Look up in repository table. */
1062 	SLIST_FOREACH(rp, &repos, entry) {
1063 		if (strcmp(rp->repouri, repouri) != 0)
1064 			continue;
1065 		if (rp->notifyuri != NULL) {
1066 			if (notify == NULL)
1067 				continue;
1068 			if (strcmp(rp->notifyuri, notify) != 0)
1069 				continue;
1070 		} else if (notify != NULL)
1071 			continue;
1072 		/* found matching repo */
1073 		free(repouri);
1074 		return rp;
1075 	}
1076 
1077 	rp = repo_alloc(talid);
1078 	rp->basedir = repo_dir(repouri, NULL, 0);
1079 	rp->repouri = repouri;
1080 	if (notify != NULL)
1081 		if ((rp->notifyuri = strdup(notify)) == NULL)
1082 			err(1, NULL);
1083 
1084 	if (++talrepocnt[talid] >= MAX_REPO_PER_TAL) {
1085 		if (talrepocnt[talid] == MAX_REPO_PER_TAL)
1086 			warnx("too many repositories under %s", tals[talid]);
1087 		nofetch = 1;
1088 	}
1089 
1090 	/* check if sync disabled ... */
1091 	if (noop || nofetch) {
1092 		logx("%s: using cache", rp->basedir);
1093 		entityq_flush(&rp->queue, rp);
1094 		return rp;
1095 	}
1096 
1097 	/* try to create base directory */
1098 	if (mkpath(rp->basedir) == -1)
1099 		warn("mkpath %s", rp->basedir);
1100 
1101 	/* ... else try RRDP first if available then rsync */
1102 	if (notify != NULL)
1103 		rp->rrdp = rrdp_get(notify);
1104 	if (rp->rrdp == NULL)
1105 		rp->rsync = rsync_get(uri, rp->basedir);
1106 
1107 	/* need to check if it was already loaded */
1108 	if (repo_state(rp) != REPO_LOADING)
1109 		entityq_flush(&rp->queue, rp);
1110 
1111 	return rp;
1112 }
1113 
1114 /*
1115  * Find repository by identifier.
1116  */
1117 struct repo *
1118 repo_byid(unsigned int id)
1119 {
1120 	struct repo	*rp;
1121 
1122 	SLIST_FOREACH(rp, &repos, entry) {
1123 		if (rp->id == id)
1124 			return rp;
1125 	}
1126 	return NULL;
1127 }
1128 
1129 /*
1130  * Find repository by base path.
1131  */
1132 static struct repo *
1133 repo_bypath(const char *path)
1134 {
1135 	struct repo	*rp;
1136 
1137 	SLIST_FOREACH(rp, &repos, entry) {
1138 		if (strcmp(rp->basedir, path) == 0)
1139 			return rp;
1140 	}
1141 	return NULL;
1142 }
1143 
1144 /*
1145  * Return the repository base or alternate directory.
1146  * Returned string must be freed by caller.
1147  */
1148 char *
1149 repo_basedir(const struct repo *rp, int wantvalid)
1150 {
1151 	char *path = NULL;
1152 
1153 	if (!wantvalid) {
1154 		if (rp->ta) {
1155 			if ((path = strdup(rp->ta->basedir)) == NULL)
1156 				err(1, NULL);
1157 		} else if (rp->rsync) {
1158 			if ((path = strdup(rp->rsync->basedir)) == NULL)
1159 				err(1, NULL);
1160 		} else if (rp->rrdp) {
1161 			path = rrdp_filename(rp->rrdp, rp->repouri, 0);
1162 		} else
1163 			path = NULL;	/* only valid repo available */
1164 	} else if (rp->basedir != NULL) {
1165 		if ((path = strdup(rp->basedir)) == NULL)
1166 			err(1, NULL);
1167 	}
1168 
1169 	return path;
1170 }
1171 
1172 /*
1173  * Return the repository identifier.
1174  */
1175 unsigned int
1176 repo_id(const struct repo *rp)
1177 {
1178 	return rp->id;
1179 }
1180 
1181 /*
1182  * Return the repository URI.
1183  */
1184 const char *
1185 repo_uri(const struct repo *rp)
1186 {
1187 	return rp->repouri;
1188 }
1189 
1190 /*
1191  * Return the repository URI.
1192  */
1193 void
1194 repo_fetch_uris(const struct repo *rp, const char **carepo,
1195     const char **notifyuri)
1196 {
1197 	*carepo = rp->repouri;
1198 	*notifyuri = rp->notifyuri;
1199 }
1200 
1201 /*
1202  * Return 1 if repository is synced else 0.
1203  */
1204 int
1205 repo_synced(const struct repo *rp)
1206 {
1207 	if (repo_state(rp) == REPO_DONE &&
1208 	    !(rp->rrdp == NULL && rp->rsync == NULL && rp->ta == NULL))
1209 		return 1;
1210 	return 0;
1211 }
1212 
1213 /*
1214  * Return the protocol string "rrdp", "rsync", "https" which was used to sync.
1215  * Result is only correct if repository was properly synced.
1216  */
1217 const char *
1218 repo_proto(const struct repo *rp)
1219 {
1220 
1221 	if (rp->ta != NULL) {
1222 		const struct tarepo *tr = rp->ta;
1223 		if (tr->uriidx < tr->urisz &&
1224 		    strncasecmp(tr->uri[tr->uriidx], "rsync://", 8) == 0)
1225 			return "rsync";
1226 		else
1227 			return "https";
1228 	}
1229 	if (rp->rrdp != NULL)
1230 		return "rrdp";
1231 	return "rsync";
1232 }
1233 
1234 /*
1235  * Return the repository tal ID.
1236  */
1237 int
1238 repo_talid(const struct repo *rp)
1239 {
1240 	return rp->talid;
1241 }
1242 
1243 int
1244 repo_queued(struct repo *rp, struct entity *p)
1245 {
1246 	if (repo_state(rp) == REPO_LOADING) {
1247 		TAILQ_INSERT_TAIL(&rp->queue, p, entries);
1248 		return 1;
1249 	}
1250 	return 0;
1251 }
1252 
1253 static void
1254 repo_fail(struct repo *rp)
1255 {
1256 	/* reset the alarm since code may fallback to rsync */
1257 	rp->alarm = getmonotime() + repo_timeout;
1258 
1259 	if (rp->ta)
1260 		http_finish(rp->ta->id, HTTP_FAILED, NULL);
1261 	else if (rp->rsync)
1262 		rsync_finish(rp->rsync->id, 0);
1263 	else if (rp->rrdp)
1264 		rrdp_finish(rp->rrdp->id, 0);
1265 	else
1266 		errx(1, "%s: bad repo", rp->repouri);
1267 }
1268 
1269 static void
1270 repo_abort(struct repo *rp)
1271 {
1272 	/* reset the alarm */
1273 	rp->alarm = getmonotime() + repo_timeout;
1274 
1275 	if (rp->rsync)
1276 		rsync_abort(rp->rsync->id);
1277 	else if (rp->rrdp)
1278 		rrdp_abort(rp->rrdp->id);
1279 	else
1280 		repo_fail(rp);
1281 }
1282 
1283 int
1284 repo_check_timeout(int timeout)
1285 {
1286 	struct repo	*rp;
1287 	time_t		 now;
1288 	int		 diff;
1289 
1290 	now = getmonotime();
1291 
1292 	/* check against our runtime deadline first */
1293 	if (deadline != 0) {
1294 		if (deadline <= now) {
1295 			warnx("deadline reached, giving up on repository sync");
1296 			nofetch = 1;
1297 			/* clear deadline since nofetch is set */
1298 			deadline = 0;
1299 			/* increase now enough so that all pending repos fail */
1300 			now += repo_timeout;
1301 		} else {
1302 			diff = deadline - now;
1303 			diff *= 1000;
1304 			if (timeout == INFTIM || diff < timeout)
1305 				timeout = diff;
1306 		}
1307 	}
1308 	/* Look up in repository table. (Lookup should actually fail here) */
1309 	SLIST_FOREACH(rp, &repos, entry) {
1310 		if (repo_state(rp) == REPO_LOADING) {
1311 			if (rp->alarm <= now) {
1312 				warnx("%s: synchronisation timeout",
1313 				    rp->repouri);
1314 				repo_abort(rp);
1315 			} else {
1316 				diff = rp->alarm - now;
1317 				diff *= 1000;
1318 				if (timeout == INFTIM || diff < timeout)
1319 					timeout = diff;
1320 			}
1321 		}
1322 	}
1323 	return timeout;
1324 }
1325 
1326 /*
1327  * Update stats object of repository depending on rtype and subtype.
1328  */
1329 void
1330 repo_stat_inc(struct repo *rp, int talid, enum rtype type, enum stype subtype)
1331 {
1332 	if (rp == NULL)
1333 		return;
1334 	rp->stats_used[talid] = 1;
1335 	switch (type) {
1336 	case RTYPE_CER:
1337 		if (subtype == STYPE_OK)
1338 			rp->stats[talid].certs++;
1339 		if (subtype == STYPE_FAIL)
1340 			rp->stats[talid].certs_fail++;
1341 		if (subtype == STYPE_BGPSEC) {
1342 			rp->stats[talid].certs--;
1343 			rp->stats[talid].brks++;
1344 		}
1345 		break;
1346 	case RTYPE_MFT:
1347 		if (subtype == STYPE_OK)
1348 			rp->stats[talid].mfts++;
1349 		if (subtype == STYPE_FAIL)
1350 			rp->stats[talid].mfts_fail++;
1351 		if (subtype == STYPE_STALE)
1352 			rp->stats[talid].mfts_stale++;
1353 		break;
1354 	case RTYPE_ROA:
1355 		switch (subtype) {
1356 		case STYPE_OK:
1357 			rp->stats[talid].roas++;
1358 			break;
1359 		case STYPE_FAIL:
1360 			rp->stats[talid].roas_fail++;
1361 			break;
1362 		case STYPE_INVALID:
1363 			rp->stats[talid].roas_invalid++;
1364 			break;
1365 		case STYPE_TOTAL:
1366 			rp->stats[talid].vrps++;
1367 			break;
1368 		case STYPE_UNIQUE:
1369 			rp->stats[talid].vrps_uniqs++;
1370 			break;
1371 		case STYPE_DEC_UNIQUE:
1372 			rp->stats[talid].vrps_uniqs--;
1373 			break;
1374 		default:
1375 			break;
1376 		}
1377 		break;
1378 	case RTYPE_ASPA:
1379 		switch (subtype) {
1380 		case STYPE_OK:
1381 			rp->stats[talid].aspas++;
1382 			break;
1383 		case STYPE_FAIL:
1384 			rp->stats[talid].aspas_fail++;
1385 			break;
1386 		case STYPE_INVALID:
1387 			rp->stats[talid].aspas_invalid++;
1388 			break;
1389 		case STYPE_TOTAL:
1390 			rp->stats[talid].vaps++;
1391 			break;
1392 		case STYPE_UNIQUE:
1393 			rp->stats[talid].vaps_uniqs++;
1394 			break;
1395 		case STYPE_DEC_UNIQUE:
1396 			rp->stats[talid].vaps_uniqs--;
1397 			break;
1398 		case STYPE_BOTH:
1399 			rp->stats[talid].vaps_pas++;
1400 			break;
1401 		case STYPE_ONLY_IPV4:
1402 			rp->stats[talid].vaps_pas4++;
1403 			break;
1404 		case STYPE_ONLY_IPV6:
1405 			rp->stats[talid].vaps_pas6++;
1406 			break;
1407 		default:
1408 			break;
1409 		}
1410 		break;
1411 	case RTYPE_CRL:
1412 		rp->stats[talid].crls++;
1413 		break;
1414 	case RTYPE_GBR:
1415 		rp->stats[talid].gbrs++;
1416 		break;
1417 	case RTYPE_TAK:
1418 		rp->stats[talid].taks++;
1419 		break;
1420 	default:
1421 		break;
1422 	}
1423 }
1424 
1425 void
1426 repo_tal_stats_collect(void (*cb)(const struct repo *,
1427     const struct repotalstats *, void *), int talid, void *arg)
1428 {
1429 	struct repo	*rp;
1430 
1431 	SLIST_FOREACH(rp, &repos, entry) {
1432 		if (rp->stats_used[talid])
1433 			cb(rp, &rp->stats[talid], arg);
1434 	}
1435 }
1436 
1437 void
1438 repo_stats_collect(void (*cb)(const struct repo *, const struct repostats *,
1439     void *), void *arg)
1440 {
1441 	struct repo	*rp;
1442 
1443 	SLIST_FOREACH(rp, &repos, entry)
1444 		cb(rp, &rp->repostats, arg);
1445 }
1446 
1447 /*
1448  * Delayed delete of files from RRDP. Since RRDP has no security built-in
1449  * this code needs to check if this RRDP repository is actually allowed to
1450  * remove the file referenced by the URI.
1451  */
1452 static void
1453 repo_cleanup_rrdp(struct filepath_tree *tree)
1454 {
1455 	struct repo *rp;
1456 	struct rrdprepo *rr;
1457 	struct filepath *fp, *nfp;
1458 	char *fn;
1459 
1460 	SLIST_FOREACH(rp, &repos, entry) {
1461 		if (rp->rrdp == NULL)
1462 			continue;
1463 		rr = (struct rrdprepo *)rp->rrdp;
1464 		RB_FOREACH_SAFE(fp, filepath_tree, &rr->deleted, nfp) {
1465 			if (!rrdp_uri_valid(rr, fp->file)) {
1466 				warnx("%s: external URI %s", rr->notifyuri,
1467 				    fp->file);
1468 				filepath_put(&rr->deleted, fp);
1469 				continue;
1470 			}
1471 			/* try to remove file from rrdp repo ... */
1472 			fn = rrdp_filename(rr, fp->file, 0);
1473 
1474 			if (unlink(fn) == -1) {
1475 				if (errno != ENOENT)
1476 					warn("unlink %s", fn);
1477 			} else {
1478 				if (verbose > 1)
1479 					logx("deleted %s", fn);
1480 				rp->repostats.del_files++;
1481 			}
1482 			free(fn);
1483 
1484 			/* ... and from the valid repository if unused. */
1485 			fn = rrdp_filename(rr, fp->file, 1);
1486 			if (!filepath_exists(tree, fn)) {
1487 				if (unlink(fn) == -1) {
1488 					if (errno != ENOENT)
1489 						warn("unlink %s", fn);
1490 				} else {
1491 					if (verbose > 1)
1492 						logx("deleted %s", fn);
1493 					rp->repostats.del_files++;
1494 				}
1495 			} else
1496 				warnx("%s: referenced file supposed to be "
1497 				    "deleted", fn);
1498 
1499 			free(fn);
1500 			filepath_put(&rr->deleted, fp);
1501 		}
1502 	}
1503 }
1504 
1505 /*
1506  * All files in tree are valid and should be moved to the valid repository
1507  * if not already there. Rename the files to the new path and readd the
1508  * filepath entry with the new path if successful.
1509  */
1510 static void
1511 repo_move_valid(struct filepath_tree *tree)
1512 {
1513 	struct filepath *fp, *nfp;
1514 	size_t rsyncsz = strlen(".rsync/");
1515 	size_t rrdpsz = strlen(".rrdp/");
1516 	char *fn, *base;
1517 
1518 	RB_FOREACH_SAFE(fp, filepath_tree, tree, nfp) {
1519 		if (strncmp(fp->file, ".rsync/", rsyncsz) != 0 &&
1520 		    strncmp(fp->file, ".rrdp/", rrdpsz) != 0)
1521 			continue; /* not a temporary file path */
1522 
1523 		if (strncmp(fp->file, ".rsync/", rsyncsz) == 0) {
1524 			fn = fp->file + rsyncsz;
1525 		} else {
1526 			base = strchr(fp->file + rrdpsz, '/');
1527 			assert(base != NULL);
1528 			fn = base + 1;
1529 		}
1530 
1531 		if (repo_mkpath(AT_FDCWD, fn) == -1)
1532 			continue;
1533 
1534 		if (rename(fp->file, fn) == -1) {
1535 			warn("rename %s", fp->file);
1536 			continue;
1537 		}
1538 
1539 		/* switch filepath node to new path */
1540 		RB_REMOVE(filepath_tree, tree, fp);
1541 		base = fp->file;
1542 		if ((fp->file = strdup(fn)) == NULL)
1543 			err(1, NULL);
1544 		free(base);
1545 		if (RB_INSERT(filepath_tree, tree, fp) != NULL)
1546 			errx(1, "%s: both possibilities of file present",
1547 			    fp->file);
1548 	}
1549 }
1550 
1551 struct fts_state {
1552 	enum { BASE_DIR, RSYNC_DIR, RRDP_DIR }	type;
1553 	struct repo				*rp;
1554 } fts_state;
1555 
1556 static const struct rrdprepo *
1557 repo_is_rrdp(struct repo *rp)
1558 {
1559 	/* check for special pointers first these are not a repository */
1560 	if (rp != NULL && rp->rrdp != NULL)
1561 		return rp->rrdp->state == REPO_DONE ? rp->rrdp : NULL;
1562 	return NULL;
1563 }
1564 
1565 static inline char *
1566 skip_dotslash(char *in)
1567 {
1568 	if (memcmp(in, "./", 2) == 0)
1569 		return in + 2;
1570 	return in;
1571 }
1572 
1573 static void
1574 repo_cleanup_entry(FTSENT *e, struct filepath_tree *tree, int cachefd)
1575 {
1576 	const struct rrdprepo *rr;
1577 	char *path;
1578 
1579 	path = skip_dotslash(e->fts_path);
1580 	switch (e->fts_info) {
1581 	case FTS_NSOK:
1582 		if (filepath_exists(tree, path)) {
1583 			e->fts_parent->fts_number++;
1584 			break;
1585 		}
1586 		if (fts_state.type == RRDP_DIR && fts_state.rp != NULL) {
1587 			e->fts_parent->fts_number++;
1588 			/* handle rrdp .state files explicitly */
1589 			if (e->fts_level == 3 &&
1590 			    strcmp(e->fts_name, ".state") == 0)
1591 				break;
1592 			/* can't delete these extra files */
1593 			fts_state.rp->repostats.extra_files++;
1594 			if (verbose > 1)
1595 				logx("superfluous %s", path);
1596 			break;
1597 		}
1598 		rr = repo_is_rrdp(fts_state.rp);
1599 		if (rr != NULL) {
1600 			struct stat st;
1601 			char *fn;
1602 
1603 			if (asprintf(&fn, "%s/%s", rr->basedir, path) == -1)
1604 				err(1, NULL);
1605 
1606 			/*
1607 			 * If the file exists in the rrdp dir
1608 			 * that file is newer and needs to be kept
1609 			 * so unlink this file instead of moving
1610 			 * it over the file in the rrdp dir.
1611 			 */
1612 			if (fstatat(cachefd, fn, &st, 0) == 0 &&
1613 			    S_ISREG(st.st_mode)) {
1614 				free(fn);
1615 				goto unlink;
1616 			}
1617 			if (repo_mkpath(cachefd, fn) == 0) {
1618 				if (renameat(AT_FDCWD, e->fts_accpath,
1619 				    cachefd, fn) == -1)
1620 					warn("rename %s to %s", path, fn);
1621 				else if (verbose > 1)
1622 					logx("moved %s", path);
1623 				fts_state.rp->repostats.extra_files++;
1624 			}
1625 			free(fn);
1626 		} else {
1627  unlink:
1628 			if (unlink(e->fts_accpath) == -1) {
1629 				warn("unlink %s", path);
1630 			} else if (fts_state.type == RSYNC_DIR) {
1631 				/* no need to keep rsync files */
1632 				if (verbose > 1)
1633 					logx("superfluous %s", path);
1634 				if (fts_state.rp != NULL)
1635 					fts_state.rp->repostats.del_extra_files++;
1636 				else
1637 					stats.repo_stats.del_extra_files++;
1638 			} else {
1639 				if (verbose > 1)
1640 					logx("deleted %s", path);
1641 				if (fts_state.rp != NULL)
1642 					fts_state.rp->repostats.del_files++;
1643 				else
1644 					stats.repo_stats.del_files++;
1645 			}
1646 		}
1647 		break;
1648 	case FTS_D:
1649 		if (e->fts_level == FTS_ROOTLEVEL)
1650 			fts_state.type = BASE_DIR;
1651 		if (e->fts_level == 1) {
1652 			if (strcmp(".rsync", e->fts_name) == 0) {
1653 				fts_state.type = RSYNC_DIR;
1654 				fts_state.rp = NULL;
1655 			} else if (strcmp(".rrdp", e->fts_name) == 0) {
1656 				fts_state.type = RRDP_DIR;
1657 				fts_state.rp = NULL;
1658 			} else {
1659 				fts_state.type = BASE_DIR;
1660 				fts_state.rp = repo_bypath(path);
1661 			}
1662 		}
1663 		if (e->fts_level == 2) {
1664 			if (fts_state.type == RSYNC_DIR)
1665 				fts_state.rp = repo_bypath(path);
1666 			/*
1667 			 * special handling for rrdp directories,
1668 			 * clear them if they are not used anymore but
1669 			 * only if rrdp is active.
1670 			 */
1671 			if (fts_state.type == RRDP_DIR)
1672 				fts_state.rp = repo_rrdp_bypath(path);
1673 		}
1674 		break;
1675 	case FTS_DP:
1676 		if (e->fts_level == FTS_ROOTLEVEL)
1677 			break;
1678 		if (e->fts_level == 1) {
1679 			/* do not remove .rsync and .rrdp */
1680 			fts_state.rp = NULL;
1681 			if (fts_state.type == RRDP_DIR ||
1682 			    fts_state.type == RSYNC_DIR)
1683 				break;
1684 		}
1685 
1686 		e->fts_parent->fts_number += e->fts_number;
1687 
1688 		if (e->fts_number == 0) {
1689 			if (rmdir(e->fts_accpath) == -1)
1690 				warn("rmdir %s", path);
1691 			if (fts_state.rp != NULL)
1692 				fts_state.rp->repostats.del_dirs++;
1693 			else
1694 				stats.repo_stats.del_dirs++;
1695 		}
1696 		break;
1697 	case FTS_SL:
1698 	case FTS_SLNONE:
1699 		warnx("symlink %s", path);
1700 		if (unlink(e->fts_accpath) == -1)
1701 			warn("unlink %s", path);
1702 		stats.repo_stats.del_extra_files++;
1703 		break;
1704 	case FTS_NS:
1705 	case FTS_ERR:
1706 		if (e->fts_errno == ENOENT && e->fts_level == FTS_ROOTLEVEL)
1707 			break;
1708 		warnx("fts_read %s: %s", path, strerror(e->fts_errno));
1709 		break;
1710 	default:
1711 		warnx("fts_read %s: unhandled[%x]", path, e->fts_info);
1712 		break;
1713 	}
1714 }
1715 
1716 void
1717 repo_cleanup(struct filepath_tree *tree, int cachefd)
1718 {
1719 	char *argv[2] = { ".", NULL };
1720 	FTS *fts;
1721 	FTSENT *e;
1722 
1723 	/* first move temp files which have been used to valid dir */
1724 	repo_move_valid(tree);
1725 	/* then delete files requested by rrdp */
1726 	repo_cleanup_rrdp(tree);
1727 
1728 	if ((fts = fts_open(argv, FTS_PHYSICAL | FTS_NOSTAT, NULL)) == NULL)
1729 		err(1, "fts_open");
1730 	errno = 0;
1731 	while ((e = fts_read(fts)) != NULL) {
1732 		repo_cleanup_entry(e, tree, cachefd);
1733 		errno = 0;
1734 	}
1735 	if (errno)
1736 		err(1, "fts_read");
1737 	if (fts_close(fts) == -1)
1738 		err(1, "fts_close");
1739 }
1740 
1741 void
1742 repo_free(void)
1743 {
1744 	struct repo *rp;
1745 
1746 	while ((rp = SLIST_FIRST(&repos)) != NULL) {
1747 		SLIST_REMOVE_HEAD(&repos, entry);
1748 		free(rp->repouri);
1749 		free(rp->notifyuri);
1750 		free(rp->basedir);
1751 		free(rp);
1752 	}
1753 
1754 	ta_free();
1755 	rrdp_free();
1756 	rsync_free();
1757 }
1758 
1759 /*
1760  * Remove all files and directories under base but do not remove base itself.
1761  */
1762 static void
1763 remove_contents(char *base)
1764 {
1765 	char *argv[2] = { base, NULL };
1766 	FTS *fts;
1767 	FTSENT *e;
1768 
1769 	if ((fts = fts_open(argv, FTS_PHYSICAL | FTS_NOSTAT, NULL)) == NULL)
1770 		err(1, "fts_open");
1771 	errno = 0;
1772 	while ((e = fts_read(fts)) != NULL) {
1773 		switch (e->fts_info) {
1774 		case FTS_NSOK:
1775 		case FTS_SL:
1776 		case FTS_SLNONE:
1777 			if (unlink(e->fts_accpath) == -1)
1778 				warn("unlink %s", e->fts_path);
1779 			break;
1780 		case FTS_D:
1781 			break;
1782 		case FTS_DP:
1783 			/* keep root directory */
1784 			if (e->fts_level == FTS_ROOTLEVEL)
1785 				break;
1786 			if (rmdir(e->fts_accpath) == -1)
1787 				warn("rmdir %s", e->fts_path);
1788 			break;
1789 		case FTS_NS:
1790 		case FTS_ERR:
1791 			warnx("fts_read %s: %s", e->fts_path,
1792 			    strerror(e->fts_errno));
1793 			break;
1794 		default:
1795 			warnx("unhandled[%x] %s", e->fts_info,
1796 			    e->fts_path);
1797 			break;
1798 		}
1799 		errno = 0;
1800 	}
1801 	if (errno)
1802 		err(1, "fts_read");
1803 	if (fts_close(fts) == -1)
1804 		err(1, "fts_close");
1805 }
1806