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