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 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 * 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 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 * 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 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 * 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 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 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 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 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 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 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 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 * 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 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 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 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 * 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 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 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