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 */ 59struct pkg_db_list { 60 char *pkgname; 61 SLIST_ENTRY(pkg_db_list) entries; 62}; 63SLIST_HEAD(pkg_db_list_head, pkg_db_list); 64 65struct pkg_match_list { 66 char *pattern; 67 char *pkgname; 68 SLIST_ENTRY(pkg_match_list) entries; 69}; 70SLIST_HEAD(pkg_match_list_head, pkg_match_list); 71 72static struct pkg_db_list_head pkg_list_cache; 73static 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 */ 80int 81iterate_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 97struct pkg_dir_iter_arg { 98 DIR *dirp; 99 int filter_suffix; 100 int allow_nonfiles; 101}; 102 103static const char * 104pkg_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 */ 131int 132iterate_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 150static const char * 151pkg_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 */ 179int 180iterate_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 198struct pkg_db_iter_arg { 199 struct pkg_db_list_head head; 200 struct pkg_db_list *list; 201}; 202 203static const char * 204pkg_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 */ 227static int 228iterate_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 264static int 265match_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 281static int 282match_by_pattern(const char *pkg, void *cookie) 283{ 284 const char *pattern = cookie; 285 286 return pkg_match(pattern, pkg); 287} 288 289struct 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 296static int 297match_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 */ 316int 317add_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 */ 337int 338add_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 354struct best_installed_match_arg { 355 const char *pattern; 356 char *best_current_match; 357}; 358 359static int 360match_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 */ 389char * 390find_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 436struct call_matching_arg { 437 const char *pattern; 438 int (*call_fn)(const char *pkg, void *cookie); 439 void *cookie; 440}; 441 442static int 443match_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 */ 459int 460match_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 472struct 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 479static int 480match_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 */ 534char * 535find_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 553struct 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 560static int 561match_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 */ 602int 603match_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