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
iterate_pkg_generic_src(int (* matchiter)(const char *,void *),void * match_cookie,const char * (* srciter)(void *),void * src_cookie)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 *
pkg_dir_iter(void * cookie)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
iterate_local_pkg_dir(const char * dir,int filter_suffix,int allow_nonfiles,int (* matchiter)(const char *,void *),void * cookie)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 *
pkg_db_iter(void * cookie)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
iterate_pkg_db(int (* matchiter)(const char *,void *),void * cookie)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
match_by_basename(const char * pkg,void * cookie)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
match_by_pattern(const char * pkg,void * cookie)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
match_and_add(const char * pkg,void * cookie)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
add_installed_pkgs_by_basename(const char * pkgbase,lpkg_head_t * pkghead)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
add_installed_pkgs_by_pattern(const char * pattern,lpkg_head_t * pkghead)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
match_best_installed(const char * pkg,void * cookie)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 *
find_best_matching_installed_pkg(const char * pattern)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
match_and_call(const char * pkg,void * cookie)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
match_installed_pkgs(const char * pattern,int (* cb)(const char *,void *),void * cookie)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
match_best_file(const char * filename,void * cookie)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 *
find_best_matching_file(const char * dir,const char * pattern,int filter_suffix,int allow_nonfiles)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
match_file_and_call(const char * filename,void * cookie)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
match_local_files(const char * dir,int filter_suffix,int allow_nonfiles,const char * pattern,int (* cb)(const char *,void *),void * cookie)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