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