1 /*	$NetBSD: iterate.c,v 1.4 2021/04/10 19:49:59 nia Exp $	*/
2 
3 /*-
4  * Copyright (c) 2007 Joerg Sonnenberger <joerg@NetBSD.org>.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #if HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35 
36 #include <nbcompat.h>
37 
38 #if HAVE_ERR_H
39 #include <err.h>
40 #endif
41 #if HAVE_ERRNO_H
42 #include <errno.h>
43 #endif
44 
45 #include "lib.h"
46 
47 /*
48  * We define a couple of different caches to hold frequently accessed data.
49  *
50  * Firstly, we cache the results of readdir() on the package database directory
51  * when using iterate_pkg_db_cached().  This helps a lot during recursive calls
52  * and avoids exponential system calls, but is not suitable for situations
53  * where the database directory may be updated, for example during installs.
54  * In those situations the regular iterate_pkg_db() must be used.
55  *
56  * Secondly, we have a cache for matches of pattern lookups, avoiding expensive
57  * pkg_match() calls each time.
58  */
59 struct pkg_db_list {
60 	char *pkgname;
61 	SLIST_ENTRY(pkg_db_list) entries;
62 };
63 SLIST_HEAD(pkg_db_list_head, pkg_db_list);
64 
65 struct pkg_match_list {
66 	char *pattern;
67 	char *pkgname;
68 	SLIST_ENTRY(pkg_match_list) entries;
69 };
70 SLIST_HEAD(pkg_match_list_head, pkg_match_list);
71 
72 static struct pkg_db_list_head pkg_list_cache;
73 static struct pkg_match_list_head pkg_match_cache[PKG_HASH_SIZE];
74 
75 /*
76  * Generic iteration function:
77  * - get new entries from srciter, stop on NULL
78  * - call matchiter for those entries, stop on non-null return value.
79  */
80 int
iterate_pkg_generic_src(int (* matchiter)(const char *,void *),void * match_cookie,const char * (* srciter)(void *),void * src_cookie)81 iterate_pkg_generic_src(int (*matchiter)(const char *, void *),
82     void *match_cookie, const char *(*srciter)(void *), void *src_cookie)
83 {
84 	int retval;
85 	const char *entry;
86 
87 	retval = 0;
88 
89 	while ((entry = (*srciter)(src_cookie)) != NULL) {
90 		if ((retval = (*matchiter)(entry, match_cookie)) != 0)
91 			break;
92 	}
93 
94 	return retval;
95 }
96 
97 struct pkg_dir_iter_arg {
98 	DIR *dirp;
99 	int filter_suffix;
100 	int allow_nonfiles;
101 };
102 
103 static const char *
pkg_dir_iter(void * cookie)104 pkg_dir_iter(void *cookie)
105 {
106 	struct pkg_dir_iter_arg *arg = cookie;
107 	struct dirent *dp;
108 	size_t len;
109 
110 	while ((dp = readdir(arg->dirp)) != NULL) {
111 #if defined(DT_UNKNOWN) && defined(DT_DIR)
112 		if (arg->allow_nonfiles == 0 &&
113 		    dp->d_type != DT_UNKNOWN && dp->d_type != DT_REG)
114 			continue;
115 #endif
116 		len = strlen(dp->d_name);
117 		/* .tbz or .tgz suffix length + some prefix*/
118 		if (len < 5)
119 			continue;
120 		if (arg->filter_suffix == 0 ||
121 		    memcmp(dp->d_name + len - 4, ".tgz", 4) == 0 ||
122 		    memcmp(dp->d_name + len - 4, ".tbz", 4) == 0)
123 			return dp->d_name;
124 	}
125 	return NULL;
126 }
127 
128 /*
129  * Call matchiter for every package in the directory.
130  */
131 int
iterate_local_pkg_dir(const char * dir,int filter_suffix,int allow_nonfiles,int (* matchiter)(const char *,void *),void * cookie)132 iterate_local_pkg_dir(const char *dir, int filter_suffix, int allow_nonfiles,
133     int (*matchiter)(const char *, void *), void *cookie)
134 {
135 	struct pkg_dir_iter_arg arg;
136 	int retval;
137 
138 	if ((arg.dirp = opendir(dir)) == NULL)
139 		return -1;
140 
141 	arg.filter_suffix = filter_suffix;
142 	arg.allow_nonfiles = allow_nonfiles;
143 	retval = iterate_pkg_generic_src(matchiter, cookie, pkg_dir_iter, &arg);
144 
145 	if (closedir(arg.dirp) == -1)
146 		return -1;
147 	return retval;
148 }
149 
150 static const char *
pkg_db_iter(void * cookie)151 pkg_db_iter(void *cookie)
152 {
153 	DIR *dirp = cookie;
154 	struct dirent *dp;
155 
156 	while ((dp = readdir(dirp)) != NULL) {
157 		if (strcmp(dp->d_name, ".") == 0)
158 			continue;
159 		if (strcmp(dp->d_name, "..") == 0)
160 			continue;
161 		if (strcmp(dp->d_name, "pkgdb.byfile.db") == 0)
162 			continue;
163 		if (strcmp(dp->d_name, ".cookie") == 0)
164 			continue;
165 		if (strcmp(dp->d_name, "pkg-vulnerabilities") == 0)
166 			continue;
167 #if defined(DT_UNKNOWN) && defined(DT_DIR)
168 		if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_DIR)
169 			continue;
170 #endif
171 		return dp->d_name;
172 	}
173 	return NULL;
174 }
175 
176 /*
177  * Call matchiter for every installed package.
178  */
179 int
iterate_pkg_db(int (* matchiter)(const char *,void *),void * cookie)180 iterate_pkg_db(int (*matchiter)(const char *, void *), void *cookie)
181 {
182 	DIR *dirp;
183 	int retval;
184 
185 	if ((dirp = opendir(pkgdb_get_dir())) == NULL) {
186 		if (errno == ENOENT)
187 			return 0; /* No pkgdb directory == empty pkgdb */
188 		return -1;
189 	}
190 
191 	retval = iterate_pkg_generic_src(matchiter, cookie, pkg_db_iter, dirp);
192 
193 	if (closedir(dirp) == -1)
194 		return -1;
195 	return retval;
196 }
197 
198 struct pkg_db_iter_arg {
199 	struct pkg_db_list_head head;
200 	struct pkg_db_list *list;
201 };
202 
203 static const char *
pkg_db_iter_cached(void * cookie)204 pkg_db_iter_cached(void *cookie)
205 {
206 	struct pkg_db_iter_arg *arg = cookie;
207 
208 	if (arg->list == NULL)
209 		arg->list = SLIST_FIRST(&arg->head);
210 	else
211 		arg->list = SLIST_NEXT(arg->list, entries);
212 
213 	if (arg->list != NULL)
214 		return arg->list->pkgname;
215 
216 	return NULL;
217 }
218 
219 /*
220  * Call matchiter for every installed package, using cached data to
221  * significantly increase performance during recursive calls.
222  *
223  * This is not suitable for every situation, for example when finding new
224  * matches after package installation/removal.  In those situations the
225  * regular iterate_pkg_db() must be used.
226  */
227 static int
iterate_pkg_db_cached(int (* matchiter)(const char *,void *),void * cookie)228 iterate_pkg_db_cached(int (*matchiter)(const char *, void *), void *cookie)
229 {
230 	DIR *dirp;
231 	struct pkg_db_iter_arg arg;
232 	struct pkg_db_list *pkg;
233 	const char *pkgdir;
234 	int retval;
235 
236 	if (SLIST_EMPTY(&pkg_list_cache)) {
237 		SLIST_INIT(&pkg_list_cache);
238 
239 		if ((dirp = opendir(pkgdb_get_dir())) == NULL) {
240 			if (errno == ENOENT)
241 				return 0; /* Empty pkgdb */
242 			return -1;
243 		}
244 
245 		while ((pkgdir = pkg_db_iter(dirp)) != NULL) {
246 			pkg = xmalloc(sizeof(struct pkg_db_list));
247 			pkg->pkgname = xstrdup(pkgdir);
248 			SLIST_INSERT_HEAD(&pkg_list_cache, pkg, entries);
249 		}
250 
251 		if (closedir(dirp) == -1)
252 			return -1;
253 	}
254 
255 	arg.head = pkg_list_cache;
256 	arg.list = NULL;
257 
258 	retval = iterate_pkg_generic_src(matchiter, cookie,
259 	    pkg_db_iter_cached, &arg);
260 
261 	return retval;
262 }
263 
264 static int
match_by_basename(const char * pkg,void * cookie)265 match_by_basename(const char *pkg, void *cookie)
266 {
267 	const char *target = cookie;
268 	const char *pkg_version;
269 
270 	if ((pkg_version = strrchr(pkg, '-')) == NULL) {
271 		warnx("Entry %s in pkgdb is not a valid package name", pkg);
272 		return 0;
273 	}
274 	if (strncmp(pkg, target, pkg_version - pkg) == 0 &&
275 	    pkg + strlen(target) == pkg_version)
276 		return 1;
277 	else
278 		return 0;
279 }
280 
281 static int
match_by_pattern(const char * pkg,void * cookie)282 match_by_pattern(const char *pkg, void *cookie)
283 {
284 	const char *pattern = cookie;
285 
286 	return pkg_match(pattern, pkg);
287 }
288 
289 struct add_matching_arg {
290 	lpkg_head_t *pkghead;
291 	int got_match;
292 	int (*match_fn)(const char *pkg, void *cookie);
293 	void *cookie;
294 };
295 
296 static int
match_and_add(const char * pkg,void * cookie)297 match_and_add(const char *pkg, void *cookie)
298 {
299 	struct add_matching_arg *arg = cookie;
300 	lpkg_t *lpp;
301 
302 	if ((*arg->match_fn)(pkg, arg->cookie) == 1) {
303 		arg->got_match = 1;
304 
305 		lpp = alloc_lpkg(pkg);
306 		TAILQ_INSERT_TAIL(arg->pkghead, lpp, lp_link);
307 	}
308 	return 0;
309 }
310 
311 /*
312  * Find all installed packages with the given basename and add them
313  * to pkghead.
314  * Returns -1 on error, 0 if no match was found and 1 otherwise.
315  */
316 int
add_installed_pkgs_by_basename(const char * pkgbase,lpkg_head_t * pkghead)317 add_installed_pkgs_by_basename(const char *pkgbase, lpkg_head_t *pkghead)
318 {
319 	struct add_matching_arg arg;
320 
321 	arg.pkghead = pkghead;
322 	arg.got_match = 0;
323 	arg.match_fn = match_by_basename;
324 	arg.cookie = __UNCONST(pkgbase);
325 
326 	if (iterate_pkg_db(match_and_add, &arg) == -1) {
327 		warnx("could not process pkgdb");
328 		return -1;
329 	}
330 	return arg.got_match;
331 }
332 
333 /*
334  * Match all installed packages against pattern, add the matches to pkghead.
335  * Returns -1 on error, 0 if no match was found and 1 otherwise.
336  */
337 int
add_installed_pkgs_by_pattern(const char * pattern,lpkg_head_t * pkghead)338 add_installed_pkgs_by_pattern(const char *pattern, lpkg_head_t *pkghead)
339 {
340 	struct add_matching_arg arg;
341 
342 	arg.pkghead = pkghead;
343 	arg.got_match = 0;
344 	arg.match_fn = match_by_pattern;
345 	arg.cookie = __UNCONST(pattern);
346 
347 	if (iterate_pkg_db(match_and_add, &arg) == -1) {
348 		warnx("could not process pkgdb");
349 		return -1;
350 	}
351 	return arg.got_match;
352 }
353 
354 struct best_installed_match_arg {
355 	const char *pattern;
356 	char *best_current_match;
357 };
358 
359 static int
match_best_installed(const char * pkg,void * cookie)360 match_best_installed(const char *pkg, void *cookie)
361 {
362 	struct best_installed_match_arg *arg = cookie;
363 
364 	switch (pkg_order(arg->pattern, pkg, arg->best_current_match)) {
365 	case 0:
366 	case 2:
367 		/*
368 		 * Either current package doesn't match or
369 		 * the older match is better. Nothing to do.
370 		 */
371 		break;
372 	case 1:
373 		/* Current package is better, remember it. */
374 		free(arg->best_current_match);
375 		arg->best_current_match = xstrdup(pkg);
376 		break;
377 	}
378 	return 0;
379 }
380 
381 /*
382  * Returns a copy of the name of best matching package.
383  * If no package matched the pattern or an error occured, return NULL.
384  *
385  * If use_cached is set, return a cached match entry if it exists, and also use
386  * the iterate_pkg_db cache, otherwise clear any matching cache entry and use
387  * regular iterate_pkg_db().
388  */
389 char *
find_best_matching_installed_pkg(const char * pattern,int use_cached)390 find_best_matching_installed_pkg(const char *pattern, int use_cached)
391 {
392 	struct best_installed_match_arg arg;
393 	struct pkg_match_list *pkg;
394 	int idx = PKG_HASH_ENTRY(pattern), rv;
395 
396 	if (pattern == NULL)
397 		return NULL;
398 
399 	SLIST_FOREACH(pkg, &pkg_match_cache[idx], entries) {
400 		if (strcmp(pattern, pkg->pattern) == 0) {
401 			if (use_cached)
402 				return xstrdup(pkg->pkgname);
403 			SLIST_REMOVE(&pkg_match_cache[idx], pkg,
404 			    pkg_match_list, entries);
405 			free(pkg->pattern);
406 			free(pkg->pkgname);
407 			free(pkg);
408 			break;
409 		}
410 	}
411 
412 	arg.pattern = pattern;
413 	arg.best_current_match = NULL;
414 
415 	if (use_cached)
416 		rv = iterate_pkg_db_cached(match_best_installed, &arg);
417 	else
418 		rv = iterate_pkg_db(match_best_installed, &arg);
419 
420 	if (rv == -1) {
421 		warnx("could not process pkgdb");
422 		return NULL;
423 	}
424 
425 	if (arg.best_current_match != NULL) {
426 		pkg = xmalloc(sizeof(struct pkg_match_list));
427 		pkg->pattern = xstrdup(pattern);
428 		pkg->pkgname = xstrdup(arg.best_current_match);
429 		SLIST_INSERT_HEAD(&pkg_match_cache[idx],
430 		    pkg, entries);
431 	}
432 
433 	return arg.best_current_match;
434 }
435 
436 struct call_matching_arg {
437 	const char *pattern;
438 	int (*call_fn)(const char *pkg, void *cookie);
439 	void *cookie;
440 };
441 
442 static int
match_and_call(const char * pkg,void * cookie)443 match_and_call(const char *pkg, void *cookie)
444 {
445 	struct call_matching_arg *arg = cookie;
446 
447 	if (pkg_match(arg->pattern, pkg) == 1) {
448 		return (*arg->call_fn)(pkg, arg->cookie);
449 	} else
450 		return 0;
451 }
452 
453 /*
454  * Find all packages that match the given pattern and call the function
455  * for each of them. Iteration stops if the callback return non-0.
456  * Returns -1 on error, 0 if the iteration finished or whatever the
457  * callback returned otherwise.
458  */
459 int
match_installed_pkgs(const char * pattern,int (* cb)(const char *,void *),void * cookie)460 match_installed_pkgs(const char *pattern, int (*cb)(const char *, void *),
461     void *cookie)
462 {
463 	struct call_matching_arg arg;
464 
465 	arg.pattern = pattern;
466 	arg.call_fn = cb;
467 	arg.cookie = cookie;
468 
469 	return iterate_pkg_db(match_and_call, &arg);
470 }
471 
472 struct best_file_match_arg {
473 	const char *pattern;
474 	char *best_current_match_filtered;
475 	char *best_current_match;
476 	int filter_suffix;
477 };
478 
479 static int
match_best_file(const char * filename,void * cookie)480 match_best_file(const char *filename, void *cookie)
481 {
482 	struct best_file_match_arg *arg = cookie;
483 	const char *active_filename;
484 	char *filtered_filename;
485 
486 	if (arg->filter_suffix) {
487 		size_t len;
488 
489 		len = strlen(filename);
490 		if (len < 5 ||
491 		    (memcmp(filename + len - 4, ".tgz", 4) != 0 &&
492 		     memcmp(filename + len - 4, ".tbz", 4) != 0)) {
493 			warnx("filename %s does not contain a recognized suffix", filename);
494 			return -1;
495 		}
496 		filtered_filename = xmalloc(len - 4 + 1);
497 		memcpy(filtered_filename, filename, len - 4);
498 		filtered_filename[len - 4] = '\0';
499 		active_filename = filtered_filename;
500 	} else {
501 		filtered_filename = NULL;
502 		active_filename = filename;
503 	}
504 
505 	switch (pkg_order(arg->pattern, active_filename, arg->best_current_match_filtered)) {
506 	case 0:
507 	case 2:
508 		/*
509 		 * Either current package doesn't match or
510 		 * the older match is better. Nothing to do.
511 		 */
512 		free(filtered_filename);
513 		return 0;
514 	case 1:
515 		/* Current package is better, remember it. */
516 		free(arg->best_current_match);
517 		free(arg->best_current_match_filtered);
518 		arg->best_current_match = xstrdup(filename);
519 		if (filtered_filename != NULL)
520 			arg->best_current_match_filtered = filtered_filename;
521 		else
522 			arg->best_current_match_filtered = xstrdup(active_filename);
523 		return 0;
524 	default:
525 		errx(EXIT_FAILURE, "Invalid error from pkg_order");
526 		/* NOTREACHED */
527 	}
528 }
529 
530 /*
531  * Returns a copy of the name of best matching file.
532  * If no package matched the pattern or an error occured, return NULL.
533  */
534 char *
find_best_matching_file(const char * dir,const char * pattern,int filter_suffix,int allow_nonfiles)535 find_best_matching_file(const char *dir, const char *pattern, int filter_suffix, int allow_nonfiles)
536 {
537 	struct best_file_match_arg arg;
538 
539 	arg.filter_suffix = filter_suffix;
540 	arg.pattern = pattern;
541 	arg.best_current_match = NULL;
542 	arg.best_current_match_filtered = NULL;
543 
544 	if (iterate_local_pkg_dir(dir, filter_suffix, allow_nonfiles, match_best_file, &arg) == -1) {
545 		warnx("could not process directory");
546 		return NULL;
547 	}
548 	free(arg.best_current_match_filtered);
549 
550 	return arg.best_current_match;
551 }
552 
553 struct call_matching_file_arg {
554 	const char *pattern;
555 	int (*call_fn)(const char *pkg, void *cookie);
556 	void *cookie;
557 	int filter_suffix;
558 };
559 
560 static int
match_file_and_call(const char * filename,void * cookie)561 match_file_and_call(const char *filename, void *cookie)
562 {
563 	struct call_matching_file_arg *arg = cookie;
564 	const char *active_filename;
565 	char *filtered_filename;
566 	int ret;
567 
568 	if (arg->filter_suffix) {
569 		size_t len;
570 
571 		len = strlen(filename);
572 		if (len < 5 ||
573 		    (memcmp(filename + len - 4, ".tgz", 4) != 0 &&
574 		     memcmp(filename + len - 4, ".tbz", 4) != 0)) {
575 			warnx("filename %s does not contain a recognized suffix", filename);
576 			return -1;
577 		}
578 		filtered_filename = xmalloc(len - 4 + 1);
579 		memcpy(filtered_filename, filename, len - 4);
580 		filtered_filename[len - 4] = '\0';
581 		active_filename = filtered_filename;
582 	} else {
583 		filtered_filename = NULL;
584 		active_filename = filename;
585 	}
586 
587 	ret = pkg_match(arg->pattern, active_filename);
588 	free(filtered_filename);
589 
590 	if (ret == 1)
591 		return (*arg->call_fn)(filename, arg->cookie);
592 	else
593 		return 0;
594 }
595 
596 /*
597  * Find all packages that match the given pattern and call the function
598  * for each of them. Iteration stops if the callback return non-0.
599  * Returns -1 on error, 0 if the iteration finished or whatever the
600  * callback returned otherwise.
601  */
602 int
match_local_files(const char * dir,int filter_suffix,int allow_nonfiles,const char * pattern,int (* cb)(const char *,void *),void * cookie)603 match_local_files(const char *dir, int filter_suffix, int allow_nonfiles, const char *pattern,
604     int (*cb)(const char *, void *), void *cookie)
605 {
606 	struct call_matching_file_arg arg;
607 
608 	arg.pattern = pattern;
609 	arg.call_fn = cb;
610 	arg.cookie = cookie;
611 	arg.filter_suffix = filter_suffix;
612 
613 	return iterate_local_pkg_dir(dir, filter_suffix, allow_nonfiles, match_file_and_call, &arg);
614 }
615