turns printfs back on
[freebsd-src/fkvm-freebsd.git] / usr.sbin / pkg_install / lib / match.c
blob1f8b02a59463aa52489c2ac8984507c39d326caf
1 /*
2 * FreeBSD install - a package for the installation and maintainance
3 * of non-core utilities.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * Maxim Sobolev
15 * 24 February 2001
17 * Routines used to query installed packages.
21 #include <sys/cdefs.h>
22 __FBSDID("$FreeBSD$");
24 #include "lib.h"
25 #include <err.h>
26 #include <fnmatch.h>
27 #include <fts.h>
28 #include <regex.h>
31 * Simple structure representing argv-like
32 * NULL-terminated list.
34 struct store {
35 int currlen;
36 int used;
37 char **store;
40 static int rex_match(const char *, const char *, int);
41 static int csh_match(const char *, const char *, int);
42 struct store *storecreate(struct store *);
43 static int storeappend(struct store *, const char *);
44 static int fname_cmp(const FTSENT * const *, const FTSENT * const *);
47 * Function to query names of installed packages.
48 * MatchType - one of MATCH_ALL, MATCH_EREGEX, MATCH_REGEX, MATCH_GLOB, MATCH_NGLOB;
49 * patterns - NULL-terminated list of glob or regex patterns
50 * (could be NULL for MATCH_ALL);
51 * retval - return value (could be NULL if you don't want/need
52 * return value).
53 * Returns NULL-terminated list with matching names.
54 * Names in list returned are dynamically allocated and should
55 * not be altered by the caller.
57 char **
58 matchinstalled(match_t MatchType, char **patterns, int *retval)
60 int i, errcode, len;
61 char *matched;
62 const char *paths[2] = {LOG_DIR, NULL};
63 static struct store *store = NULL;
64 FTS *ftsp;
65 FTSENT *f;
66 Boolean *lmatched = NULL;
68 store = storecreate(store);
69 if (store == NULL) {
70 if (retval != NULL)
71 *retval = 1;
72 return NULL;
75 if (retval != NULL)
76 *retval = 0;
78 if (!isdir(paths[0])) {
79 if (retval != NULL)
80 *retval = 1;
81 return NULL;
82 /* Not reached */
85 /* Count number of patterns */
86 if (patterns != NULL) {
87 for (len = 0; patterns[len]; len++) {}
88 lmatched = alloca(sizeof(*lmatched) * len);
89 if (lmatched == NULL) {
90 warnx("%s(): alloca() failed", __func__);
91 if (retval != NULL)
92 *retval = 1;
93 return NULL;
95 } else
96 len = 0;
98 for (i = 0; i < len; i++)
99 lmatched[i] = FALSE;
101 ftsp = fts_open((char * const *)(uintptr_t)paths, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT, fname_cmp);
102 if (ftsp != NULL) {
103 while ((f = fts_read(ftsp)) != NULL) {
104 if (f->fts_info == FTS_D && f->fts_level == 1) {
105 fts_set(ftsp, f, FTS_SKIP);
106 matched = NULL;
107 errcode = 0;
108 if (MatchType == MATCH_ALL)
109 matched = f->fts_name;
110 else
111 for (i = 0; patterns[i]; i++) {
112 errcode = pattern_match(MatchType, patterns[i], f->fts_name);
113 if (errcode == 1) {
114 matched = f->fts_name;
115 lmatched[i] = TRUE;
116 errcode = 0;
118 if (matched != NULL || errcode != 0)
119 break;
121 if (errcode == 0 && matched != NULL)
122 errcode = storeappend(store, matched);
123 if (errcode != 0) {
124 if (retval != NULL)
125 *retval = 1;
126 return NULL;
127 /* Not reached */
131 fts_close(ftsp);
134 if (MatchType == MATCH_GLOB) {
135 for (i = 0; i < len; i++)
136 if (lmatched[i] == FALSE)
137 storeappend(store, patterns[i]);
140 if (store->used == 0)
141 return NULL;
142 else
143 return store->store;
147 pattern_match(match_t MatchType, char *pattern, const char *pkgname)
149 int errcode = 0;
150 const char *fname = pkgname;
151 char basefname[PATH_MAX];
152 char condchar = '\0';
153 char *condition;
155 /* do we have an appended condition? */
156 condition = strpbrk(pattern, "<>=");
157 if (condition) {
158 const char *ch;
159 /* yes, isolate the pattern from the condition ... */
160 if (condition > pattern && condition[-1] == '!')
161 condition--;
162 condchar = *condition;
163 *condition = '\0';
164 /* ... and compare the name without version */
165 ch = strrchr(fname, '-');
166 if (ch && ch - fname < PATH_MAX) {
167 strlcpy(basefname, fname, ch - fname + 1);
168 fname = basefname;
172 switch (MatchType) {
173 case MATCH_EREGEX:
174 case MATCH_REGEX:
175 errcode = rex_match(pattern, fname, MatchType == MATCH_EREGEX ? 1 : 0);
176 break;
177 case MATCH_NGLOB:
178 case MATCH_GLOB:
179 errcode = (csh_match(pattern, fname, 0) == 0) ? 1 : 0;
180 break;
181 case MATCH_EXACT:
182 errcode = (strcmp(pattern, fname) == 0) ? 1 : 0;
183 break;
184 case MATCH_ALL:
185 errcode = 1;
186 break;
187 default:
188 break;
191 /* loop over all appended conditions */
192 while (condition) {
193 /* restore the pattern */
194 *condition = condchar;
195 /* parse the condition (fun with bits) */
196 if (errcode == 1) {
197 char *nextcondition;
198 /* compare version numbers */
199 int match = 0;
200 if (*++condition == '=') {
201 match = 2;
202 condition++;
204 switch(condchar) {
205 case '<':
206 match |= 1;
207 break;
208 case '>':
209 match |= 4;
210 break;
211 case '=':
212 match |= 2;
213 break;
214 case '!':
215 match = 5;
216 break;
218 /* isolate the version number from the next condition ... */
219 nextcondition = strpbrk(condition, "<>=!");
220 if (nextcondition) {
221 condchar = *nextcondition;
222 *nextcondition = '\0';
224 /* and compare the versions (version_cmp removes the filename for us) */
225 if ((match & (1 << (version_cmp(pkgname, condition) + 1))) == 0)
226 errcode = 0;
227 condition = nextcondition;
228 } else {
229 break;
233 return errcode;
237 * Synopsis is similar to matchinstalled(), but use origin
238 * as a key for matching packages.
240 char ***
241 matchallbyorigin(const char **origins, int *retval)
243 char **installed, **allorigins = NULL;
244 char ***matches = NULL;
245 int i, j;
247 if (retval != NULL)
248 *retval = 0;
250 installed = matchinstalled(MATCH_ALL, NULL, retval);
251 if (installed == NULL)
252 return NULL;
254 /* Gather origins for all installed packages */
255 for (i = 0; installed[i] != NULL; i++) {
256 FILE *fp;
257 char *buf, *cp, tmp[PATH_MAX];
258 int cmd;
260 allorigins = realloc(allorigins, (i + 1) * sizeof(*allorigins));
261 allorigins[i] = NULL;
263 snprintf(tmp, PATH_MAX, "%s/%s", LOG_DIR, installed[i]);
265 * SPECIAL CASE: ignore empty dirs, since we can can see them
266 * during port installation.
268 if (isemptydir(tmp))
269 continue;
270 snprintf(tmp, PATH_MAX, "%s/%s", tmp, CONTENTS_FNAME);
271 fp = fopen(tmp, "r");
272 if (fp == NULL) {
273 warnx("the package info for package '%s' is corrupt", installed[i]);
274 continue;
277 cmd = -1;
278 while (fgets(tmp, sizeof(tmp), fp)) {
279 int len = strlen(tmp);
281 while (len && isspace(tmp[len - 1]))
282 tmp[--len] = '\0';
283 if (!len)
284 continue;
285 cp = tmp;
286 if (tmp[0] != CMD_CHAR)
287 continue;
288 cmd = plist_cmd(tmp + 1, &cp);
289 if (cmd == PLIST_ORIGIN) {
290 asprintf(&buf, "%s", cp);
291 allorigins[i] = buf;
292 break;
295 if (cmd != PLIST_ORIGIN && ( Verbose || 0 != strncmp("bsdpan-", installed[i], 7 ) ) )
296 warnx("package %s has no origin recorded", installed[i]);
297 fclose(fp);
300 /* Resolve origins into package names, retaining the sequence */
301 for (i = 0; origins[i] != NULL; i++) {
302 matches = realloc(matches, (i + 1) * sizeof(*matches));
303 struct store *store = NULL;
304 store = storecreate(store);
306 for (j = 0; installed[j] != NULL; j++) {
307 if (allorigins[j]) {
308 if (csh_match(origins[i], allorigins[j], FNM_PATHNAME) == 0) {
309 storeappend(store, installed[j]);
313 if (store->used == 0)
314 matches[i] = NULL;
315 else
316 matches[i] = store->store;
319 if (allorigins) {
320 for (i = 0; installed[i] != NULL; i++)
321 if (allorigins[i])
322 free(allorigins[i]);
323 free(allorigins);
326 return matches;
330 * Synopsis is similar to matchinstalled(), but use origin
331 * as a key for matching packages.
333 char **
334 matchbyorigin(const char *origin, int *retval)
336 const char *origins[2];
337 char ***tmp;
339 origins[0] = origin;
340 origins[1] = NULL;
342 tmp = matchallbyorigin(origins, retval);
343 if (tmp && tmp[0]) {
344 return tmp[0];
345 } else {
346 return NULL;
351 * Small linked list to memoize results of isinstalledpkg(). A hash table
352 * would be faster but for n ~= 1000 may be overkill.
354 struct iip_memo {
355 LIST_ENTRY(iip_memo) iip_link;
356 char *iip_name;
357 int iip_result;
359 LIST_HEAD(, iip_memo) iip_memo = LIST_HEAD_INITIALIZER(iip_memo);
363 * Return 1 if the specified package is installed,
364 * 0 if not, and -1 if an error occured.
367 isinstalledpkg(const char *name)
369 int result;
370 char *buf, *buf2;
371 struct iip_memo *memo;
373 LIST_FOREACH(memo, &iip_memo, iip_link) {
374 if (strcmp(memo->iip_name, name) == 0)
375 return memo->iip_result;
378 buf2 = NULL;
379 asprintf(&buf, "%s/%s", LOG_DIR, name);
380 if (buf == NULL)
381 goto errout;
382 if (!isdir(buf) || access(buf, R_OK) == FAIL) {
383 result = 0;
384 } else {
385 asprintf(&buf2, "%s/%s", buf, CONTENTS_FNAME);
386 if (buf2 == NULL)
387 goto errout;
389 if (!isfile(buf2) || access(buf2, R_OK) == FAIL)
390 result = -1;
391 else
392 result = 1;
395 free(buf);
396 buf = strdup(name);
397 if (buf == NULL)
398 goto errout;
399 free(buf2);
400 buf2 = NULL;
402 memo = malloc(sizeof *memo);
403 if (memo == NULL)
404 goto errout;
405 memo->iip_name = buf;
406 memo->iip_result = result;
407 LIST_INSERT_HEAD(&iip_memo, memo, iip_link);
408 return result;
410 errout:
411 if (buf != NULL)
412 free(buf);
413 if (buf2 != NULL)
414 free(buf2);
415 return -1;
419 * Returns 1 if specified pkgname matches RE pattern.
420 * Otherwise returns 0 if doesn't match or -1 if RE
421 * engine reported an error (usually invalid syntax).
423 static int
424 rex_match(const char *pattern, const char *pkgname, int extended)
426 char errbuf[128];
427 int errcode;
428 int retval;
429 regex_t rex;
431 retval = 0;
433 errcode = regcomp(&rex, pattern, (extended ? REG_EXTENDED : REG_BASIC) | REG_NOSUB);
434 if (errcode == 0)
435 errcode = regexec(&rex, pkgname, 0, NULL, 0);
437 if (errcode == 0) {
438 retval = 1;
439 } else if (errcode != REG_NOMATCH) {
440 regerror(errcode, &rex, errbuf, sizeof(errbuf));
441 warnx("%s: %s", pattern, errbuf);
442 retval = -1;
445 regfree(&rex);
447 return retval;
451 * Match string by a csh-style glob pattern. Returns 0 on
452 * match and FNM_NOMATCH otherwise, to be compatible with
453 * fnmatch(3).
455 static int
456 csh_match(const char *pattern, const char *string, int flags)
458 int ret = FNM_NOMATCH;
461 const char *nextchoice = pattern;
462 const char *current = NULL;
464 int prefixlen = -1;
465 int currentlen = 0;
467 int level = 0;
469 do {
470 const char *pos = nextchoice;
471 const char *postfix = NULL;
473 Boolean quoted = FALSE;
475 nextchoice = NULL;
477 do {
478 const char *eb;
479 if (!*pos) {
480 postfix = pos;
481 } else if (quoted) {
482 quoted = FALSE;
483 } else {
484 switch (*pos) {
485 case '{':
486 ++level;
487 if (level == 1) {
488 current = pos+1;
489 prefixlen = pos-pattern;
491 break;
492 case ',':
493 if (level == 1 && !nextchoice) {
494 nextchoice = pos+1;
495 currentlen = pos-current;
497 break;
498 case '}':
499 if (level == 1) {
500 postfix = pos+1;
501 if (!nextchoice)
502 currentlen = pos-current;
504 level--;
505 break;
506 case '[':
507 eb = pos+1;
508 if (*eb == '!' || *eb == '^')
509 eb++;
510 if (*eb == ']')
511 eb++;
512 while(*eb && *eb != ']')
513 eb++;
514 if (*eb)
515 pos=eb;
516 break;
517 case '\\':
518 quoted = TRUE;
519 break;
520 default:
524 pos++;
525 } while (!postfix);
527 if (current) {
528 char buf[FILENAME_MAX];
529 snprintf(buf, sizeof(buf), "%.*s%.*s%s", prefixlen, pattern, currentlen, current, postfix);
530 ret = csh_match(buf, string, flags);
531 if (ret) {
532 current = nextchoice;
533 level = 1;
534 } else
535 current = NULL;
536 } else
537 ret = fnmatch(pattern, string, flags);
538 } while (current);
540 return ret;
544 * Create an empty store, optionally deallocating
545 * any previously allocated space if store != NULL.
547 struct store *
548 storecreate(struct store *store)
550 int i;
552 if (store == NULL) {
553 store = malloc(sizeof *store);
554 if (store == NULL) {
555 warnx("%s(): malloc() failed", __func__);
556 return NULL;
558 store->currlen = 0;
559 store->store = NULL;
560 } else if (store->store != NULL) {
561 /* Free previously allocated memory */
562 for (i = 0; store->store[i] != NULL; i++)
563 free(store->store[i]);
564 store->store[0] = NULL;
566 store->used = 0;
568 return store;
572 * Append specified element to the provided store.
574 static int
575 storeappend(struct store *store, const char *item)
577 if (store->used + 2 > store->currlen) {
578 store->currlen += 16;
579 store->store = reallocf(store->store,
580 store->currlen * sizeof(*(store->store)));
581 if (store->store == NULL) {
582 store->currlen = 0;
583 warnx("%s(): reallocf() failed", __func__);
584 return 1;
588 asprintf(&(store->store[store->used]), "%s", item);
589 if (store->store[store->used] == NULL) {
590 warnx("%s(): malloc() failed", __func__);
591 return 1;
593 store->used++;
594 store->store[store->used] = NULL;
596 return 0;
599 static int
600 fname_cmp(const FTSENT * const *a, const FTSENT * const *b)
602 return strcmp((*a)->fts_name, (*b)->fts_name);