iterate.c revision 1.1.1.4
1/* $NetBSD: iterate.c,v 1.1.1.4 2010/01/30 21:33:47 joerg 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 * Generic iteration function: 49 * - get new entries from srciter, stop on NULL 50 * - call matchiter for those entries, stop on non-null return value. 51 */ 52int 53iterate_pkg_generic_src(int (*matchiter)(const char *, void *), 54 void *match_cookie, const char *(*srciter)(void *), void *src_cookie) 55{ 56 int retval; 57 const char *entry; 58 59 retval = 0; 60 61 while ((entry = (*srciter)(src_cookie)) != NULL) { 62 if ((retval = (*matchiter)(entry, match_cookie)) != 0) 63 break; 64 } 65 66 return retval; 67} 68 69struct pkg_dir_iter_arg { 70 DIR *dirp; 71 int filter_suffix; 72 int allow_nonfiles; 73}; 74 75static const char * 76pkg_dir_iter(void *cookie) 77{ 78 struct pkg_dir_iter_arg *arg = cookie; 79 struct dirent *dp; 80 size_t len; 81 82 while ((dp = readdir(arg->dirp)) != NULL) { 83#if defined(DT_UNKNOWN) && defined(DT_DIR) 84 if (arg->allow_nonfiles == 0 && 85 dp->d_type != DT_UNKNOWN && dp->d_type != DT_REG) 86 continue; 87#endif 88 len = strlen(dp->d_name); 89 /* .tbz or .tgz suffix length + some prefix*/ 90 if (len < 5) 91 continue; 92 if (arg->filter_suffix == 0 || 93 memcmp(dp->d_name + len - 4, ".tgz", 4) == 0 || 94 memcmp(dp->d_name + len - 4, ".tbz", 4) == 0) 95 return dp->d_name; 96 } 97 return NULL; 98} 99 100/* 101 * Call matchiter for every package in the directory. 102 */ 103int 104iterate_local_pkg_dir(const char *dir, int filter_suffix, int allow_nonfiles, 105 int (*matchiter)(const char *, void *), void *cookie) 106{ 107 struct pkg_dir_iter_arg arg; 108 int retval; 109 110 if ((arg.dirp = opendir(dir)) == NULL) 111 return -1; 112 113 arg.filter_suffix = filter_suffix; 114 arg.allow_nonfiles = allow_nonfiles; 115 retval = iterate_pkg_generic_src(matchiter, cookie, pkg_dir_iter, &arg); 116 117 if (closedir(arg.dirp) == -1) 118 return -1; 119 return retval; 120} 121 122static const char * 123pkg_db_iter(void *cookie) 124{ 125 DIR *dirp = cookie; 126 struct dirent *dp; 127 128 while ((dp = readdir(dirp)) != NULL) { 129 if (strcmp(dp->d_name, ".") == 0) 130 continue; 131 if (strcmp(dp->d_name, "..") == 0) 132 continue; 133 if (strcmp(dp->d_name, "pkgdb.byfile.db") == 0) 134 continue; 135 if (strcmp(dp->d_name, ".cookie") == 0) 136 continue; 137 if (strcmp(dp->d_name, "pkg-vulnerabilities") == 0) 138 continue; 139#if defined(DT_UNKNOWN) && defined(DT_DIR) 140 if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_DIR) 141 continue; 142#endif 143 return dp->d_name; 144 } 145 return NULL; 146} 147 148/* 149 * Call matchiter for every installed package. 150 */ 151int 152iterate_pkg_db(int (*matchiter)(const char *, void *), void *cookie) 153{ 154 DIR *dirp; 155 int retval; 156 157 if ((dirp = opendir(pkgdb_get_dir())) == NULL) { 158 if (errno == ENOENT) 159 return 0; /* No pkgdb directory == empty pkgdb */ 160 return -1; 161 } 162 163 retval = iterate_pkg_generic_src(matchiter, cookie, pkg_db_iter, dirp); 164 165 if (closedir(dirp) == -1) 166 return -1; 167 return retval; 168} 169 170static int 171match_by_basename(const char *pkg, void *cookie) 172{ 173 const char *target = cookie; 174 const char *pkg_version; 175 176 if ((pkg_version = strrchr(pkg, '-')) == NULL) { 177 warnx("Entry %s in pkgdb is not a valid package name", pkg); 178 return 0; 179 } 180 if (strncmp(pkg, target, pkg_version - pkg) == 0 && 181 pkg + strlen(target) == pkg_version) 182 return 1; 183 else 184 return 0; 185} 186 187static int 188match_by_pattern(const char *pkg, void *cookie) 189{ 190 const char *pattern = cookie; 191 192 return pkg_match(pattern, pkg); 193} 194 195struct add_matching_arg { 196 lpkg_head_t *pkghead; 197 int got_match; 198 int (*match_fn)(const char *pkg, void *cookie); 199 void *cookie; 200}; 201 202static int 203match_and_add(const char *pkg, void *cookie) 204{ 205 struct add_matching_arg *arg = cookie; 206 lpkg_t *lpp; 207 208 if ((*arg->match_fn)(pkg, arg->cookie) == 1) { 209 arg->got_match = 1; 210 211 lpp = alloc_lpkg(pkg); 212 TAILQ_INSERT_TAIL(arg->pkghead, lpp, lp_link); 213 } 214 return 0; 215} 216 217/* 218 * Find all installed packages with the given basename and add them 219 * to pkghead. 220 * Returns -1 on error, 0 if no match was found and 1 otherwise. 221 */ 222int 223add_installed_pkgs_by_basename(const char *pkgbase, lpkg_head_t *pkghead) 224{ 225 struct add_matching_arg arg; 226 227 arg.pkghead = pkghead; 228 arg.got_match = 0; 229 arg.match_fn = match_by_basename; 230 arg.cookie = __UNCONST(pkgbase); 231 232 if (iterate_pkg_db(match_and_add, &arg) == -1) { 233 warnx("could not process pkgdb"); 234 return -1; 235 } 236 return arg.got_match; 237} 238 239/* 240 * Match all installed packages against pattern, add the matches to pkghead. 241 * Returns -1 on error, 0 if no match was found and 1 otherwise. 242 */ 243int 244add_installed_pkgs_by_pattern(const char *pattern, lpkg_head_t *pkghead) 245{ 246 struct add_matching_arg arg; 247 248 arg.pkghead = pkghead; 249 arg.got_match = 0; 250 arg.match_fn = match_by_pattern; 251 arg.cookie = __UNCONST(pattern); 252 253 if (iterate_pkg_db(match_and_add, &arg) == -1) { 254 warnx("could not process pkgdb"); 255 return -1; 256 } 257 return arg.got_match; 258} 259 260struct best_installed_match_arg { 261 const char *pattern; 262 char *best_current_match; 263}; 264 265static int 266match_best_installed(const char *pkg, void *cookie) 267{ 268 struct best_installed_match_arg *arg = cookie; 269 270 switch (pkg_order(arg->pattern, pkg, arg->best_current_match)) { 271 case 0: 272 case 2: 273 /* 274 * Either current package doesn't match or 275 * the older match is better. Nothing to do. 276 */ 277 break; 278 case 1: 279 /* Current package is better, remember it. */ 280 free(arg->best_current_match); 281 arg->best_current_match = xstrdup(pkg); 282 break; 283 } 284 return 0; 285} 286 287/* 288 * Returns a copy of the name of best matching package. 289 * If no package matched the pattern or an error occured, return NULL. 290 */ 291char * 292find_best_matching_installed_pkg(const char *pattern) 293{ 294 struct best_installed_match_arg arg; 295 296 arg.pattern = pattern; 297 arg.best_current_match = NULL; 298 299 if (iterate_pkg_db(match_best_installed, &arg) == -1) { 300 warnx("could not process pkgdb"); 301 return NULL; 302 } 303 304 return arg.best_current_match; 305} 306 307struct call_matching_arg { 308 const char *pattern; 309 int (*call_fn)(const char *pkg, void *cookie); 310 void *cookie; 311}; 312 313static int 314match_and_call(const char *pkg, void *cookie) 315{ 316 struct call_matching_arg *arg = cookie; 317 318 if (pkg_match(arg->pattern, pkg) == 1) { 319 return (*arg->call_fn)(pkg, arg->cookie); 320 } else 321 return 0; 322} 323 324/* 325 * Find all packages that match the given pattern and call the function 326 * for each of them. Iteration stops if the callback return non-0. 327 * Returns -1 on error, 0 if the iteration finished or whatever the 328 * callback returned otherwise. 329 */ 330int 331match_installed_pkgs(const char *pattern, int (*cb)(const char *, void *), 332 void *cookie) 333{ 334 struct call_matching_arg arg; 335 336 arg.pattern = pattern; 337 arg.call_fn = cb; 338 arg.cookie = cookie; 339 340 return iterate_pkg_db(match_and_call, &arg); 341} 342 343struct best_file_match_arg { 344 const char *pattern; 345 char *best_current_match_filtered; 346 char *best_current_match; 347 int filter_suffix; 348}; 349 350static int 351match_best_file(const char *filename, void *cookie) 352{ 353 struct best_file_match_arg *arg = cookie; 354 const char *active_filename; 355 char *filtered_filename; 356 357 if (arg->filter_suffix) { 358 size_t len; 359 360 len = strlen(filename); 361 if (len < 5 || 362 (memcmp(filename + len - 4, ".tgz", 4) != 0 && 363 memcmp(filename + len - 4, ".tbz", 4) != 0)) { 364 warnx("filename %s does not contain a recognized suffix", filename); 365 return -1; 366 } 367 filtered_filename = xmalloc(len - 4 + 1); 368 memcpy(filtered_filename, filename, len - 4); 369 filtered_filename[len - 4] = '\0'; 370 active_filename = filtered_filename; 371 } else { 372 filtered_filename = NULL; 373 active_filename = filename; 374 } 375 376 switch (pkg_order(arg->pattern, active_filename, arg->best_current_match_filtered)) { 377 case 0: 378 case 2: 379 /* 380 * Either current package doesn't match or 381 * the older match is better. Nothing to do. 382 */ 383 free(filtered_filename); 384 return 0; 385 case 1: 386 /* Current package is better, remember it. */ 387 free(arg->best_current_match); 388 free(arg->best_current_match_filtered); 389 arg->best_current_match = xstrdup(filename); 390 if (filtered_filename != NULL) 391 arg->best_current_match_filtered = filtered_filename; 392 else 393 arg->best_current_match_filtered = xstrdup(active_filename); 394 return 0; 395 default: 396 errx(EXIT_FAILURE, "Invalid error from pkg_order"); 397 /* NOTREACHED */ 398 } 399} 400 401/* 402 * Returns a copy of the name of best matching file. 403 * If no package matched the pattern or an error occured, return NULL. 404 */ 405char * 406find_best_matching_file(const char *dir, const char *pattern, int filter_suffix, int allow_nonfiles) 407{ 408 struct best_file_match_arg arg; 409 410 arg.filter_suffix = filter_suffix; 411 arg.pattern = pattern; 412 arg.best_current_match = NULL; 413 arg.best_current_match_filtered = NULL; 414 415 if (iterate_local_pkg_dir(dir, filter_suffix, allow_nonfiles, match_best_file, &arg) == -1) { 416 warnx("could not process directory"); 417 return NULL; 418 } 419 free(arg.best_current_match_filtered); 420 421 return arg.best_current_match; 422} 423 424struct call_matching_file_arg { 425 const char *pattern; 426 int (*call_fn)(const char *pkg, void *cookie); 427 void *cookie; 428 int filter_suffix; 429}; 430 431static int 432match_file_and_call(const char *filename, void *cookie) 433{ 434 struct call_matching_file_arg *arg = cookie; 435 const char *active_filename; 436 char *filtered_filename; 437 int ret; 438 439 if (arg->filter_suffix) { 440 size_t len; 441 442 len = strlen(filename); 443 if (len < 5 || 444 (memcmp(filename + len - 4, ".tgz", 4) != 0 && 445 memcmp(filename + len - 4, ".tbz", 4) != 0)) { 446 warnx("filename %s does not contain a recognized suffix", filename); 447 return -1; 448 } 449 filtered_filename = xmalloc(len - 4 + 1); 450 memcpy(filtered_filename, filename, len - 4); 451 filtered_filename[len - 4] = '\0'; 452 active_filename = filtered_filename; 453 } else { 454 filtered_filename = NULL; 455 active_filename = filename; 456 } 457 458 ret = pkg_match(arg->pattern, active_filename); 459 free(filtered_filename); 460 461 if (ret == 1) 462 return (*arg->call_fn)(filename, arg->cookie); 463 else 464 return 0; 465} 466 467/* 468 * Find all packages that match the given pattern and call the function 469 * for each of them. Iteration stops if the callback return non-0. 470 * Returns -1 on error, 0 if the iteration finished or whatever the 471 * callback returned otherwise. 472 */ 473int 474match_local_files(const char *dir, int filter_suffix, int allow_nonfiles, const char *pattern, 475 int (*cb)(const char *, void *), void *cookie) 476{ 477 struct call_matching_file_arg arg; 478 479 arg.pattern = pattern; 480 arg.call_fn = cb; 481 arg.cookie = cookie; 482 arg.filter_suffix = filter_suffix; 483 484 return iterate_local_pkg_dir(dir, filter_suffix, allow_nonfiles, match_file_and_call, &arg); 485} 486