Home | History | Annotate | Line # | Download | only in lib
      1 /*	$NetBSD: iterate.c,v 1.4 2021/04/10 19:49:59 nia Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2007 Joerg Sonnenberger <joerg (at) 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