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 */ 52 int 53 iterate_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 69 struct pkg_dir_iter_arg { 70 DIR *dirp; 71 int filter_suffix; 72 int allow_nonfiles; 73 }; 74 75 static const char * 76 pkg_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 */ 103 int 104 iterate_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 122 static const char * 123 pkg_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 */ 151 int 152 iterate_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 170 static int 171 match_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 187 static int 188 match_by_pattern(const char *pkg, void *cookie) 189 { 190 const char *pattern = cookie; 191 192 return pkg_match(pattern, pkg); 193 } 194 195 struct 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 202 static int 203 match_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 */ 222 int 223 add_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 */ 243 int 244 add_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 260 struct best_installed_match_arg { 261 const char *pattern; 262 char *best_current_match; 263 }; 264 265 static int 266 match_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 */ 291 char * 292 find_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 307 struct call_matching_arg { 308 const char *pattern; 309 int (*call_fn)(const char *pkg, void *cookie); 310 void *cookie; 311 }; 312 313 static int 314 match_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 */ 330 int 331 match_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 343 struct 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 350 static int 351 match_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 */ 405 char * 406 find_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 424 struct 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 431 static int 432 match_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 */ 473 int 474 match_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