1 /*
2  * FreeBSD install - a package for the installation and maintainance
3  * of non-core utilities.
4  *
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.
13  *
14  * Maxim Sobolev
15  * 24 February 2001
16  *
17  * Routines used to query installed packages.
18  *
19  */
20 
21 #include <sys/cdefs.h>
22 __FBSDID("$FreeBSD: stable/9/usr.sbin/pkg_install/lib/match.c 206043 2010-04-01 14:27:29Z flz $");
23 
24 #include "lib.h"
25 #include <err.h>
26 #include <fnmatch.h>
27 #include <fts.h>
28 #include <regex.h>
29 
30 /*
31  * Simple structure representing argv-like
32  * NULL-terminated list.
33  */
34 struct store {
35     int currlen;
36     int used;
37     char **store;
38 };
39 
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 *);
45 
46 /*
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.
56  */
57 char **
matchinstalled(match_t MatchType,char ** patterns,int * retval)58 matchinstalled(match_t MatchType, char **patterns, int *retval)
59 {
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;
67 
68     store = storecreate(store);
69     if (store == NULL) {
70 	if (retval != NULL)
71 	    *retval = 1;
72 	return NULL;
73     }
74 
75     if (retval != NULL)
76 	*retval = 0;
77 
78     if (!isdir(paths[0])) {
79 	if (retval != NULL)
80 	    *retval = 1;
81 	return NULL;
82 	/* Not reached */
83     }
84 
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;
94     	}
95     } else
96 	len = 0;
97 
98     for (i = 0; i < len; i++)
99 	lmatched[i] = FALSE;
100 
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;
117 			}
118 			if (matched != NULL || errcode != 0)
119 			    break;
120 		    }
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 */
128 		}
129 	    }
130 	}
131 	fts_close(ftsp);
132     }
133 
134     if (MatchType == MATCH_GLOB) {
135 	for (i = 0; i < len; i++)
136 	    if (lmatched[i] == FALSE)
137 		storeappend(store, patterns[i]);
138     }
139 
140     if (store->used == 0)
141 	return NULL;
142     else
143 	return store->store;
144 }
145 
146 int
pattern_match(match_t MatchType,char * pattern,const char * pkgname)147 pattern_match(match_t MatchType, char *pattern, const char *pkgname)
148 {
149     int errcode = 0;
150     const char *fname = pkgname;
151     char basefname[PATH_MAX];
152     char condchar = '\0';
153     char *condition;
154 
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;
169 	}
170     }
171 
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;
189     }
190 
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++;
203 	    }
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;
217 	    }
218 	    /* isolate the version number from the next condition ... */
219 	    nextcondition = strpbrk(condition, "<>=!");
220 	    if (nextcondition) {
221 		condchar = *nextcondition;
222 		*nextcondition = '\0';
223 	    }
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;
230 	}
231     }
232 
233     return errcode;
234 }
235 
236 /*
237  * Synopsis is similar to matchinstalled(), but use origin
238  * as a key for matching packages.
239  */
240 char ***
matchallbyorigin(const char ** origins,int * retval)241 matchallbyorigin(const char **origins, int *retval)
242 {
243     char **installed, **allorigins = NULL;
244     char ***matches = NULL;
245     int i, j;
246 
247     if (retval != NULL)
248 	*retval = 0;
249 
250     installed = matchinstalled(MATCH_ALL, NULL, retval);
251     if (installed == NULL)
252 	return NULL;
253 
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;
259 
260 	allorigins = realloc(allorigins, (i + 1) * sizeof(*allorigins));
261 	allorigins[i] = NULL;
262 
263 	snprintf(tmp, PATH_MAX, "%s/%s", LOG_DIR, installed[i]);
264 	/*
265 	 * SPECIAL CASE: ignore empty dirs, since we can can see them
266 	 * during port installation.
267 	 */
268 	if (isemptydir(tmp))
269 	    continue;
270 	strncat(tmp, "/" CONTENTS_FNAME, PATH_MAX);
271 	fp = fopen(tmp, "r");
272 	if (fp == NULL) {
273 	    warnx("the package info for package '%s' is corrupt", installed[i]);
274 	    continue;
275 	}
276 
277 	cmd = -1;
278 	while (fgets(tmp, sizeof(tmp), fp)) {
279 	    int len = strlen(tmp);
280 
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;
293 	    }
294 	}
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);
298     }
299 
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);
305 
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]);
310 		}
311 	    }
312 	}
313 	if (store->used == 0)
314 	    matches[i] = NULL;
315 	else
316 	    matches[i] = store->store;
317     }
318 
319     if (allorigins) {
320 	for (i = 0; installed[i] != NULL; i++)
321 	    if (allorigins[i])
322 		free(allorigins[i]);
323 	free(allorigins);
324     }
325 
326     return matches;
327 }
328 
329 /*
330  * Synopsis is similar to matchinstalled(), but use origin
331  * as a key for matching packages.
332  */
333 char **
matchbyorigin(const char * origin,int * retval)334 matchbyorigin(const char *origin, int *retval)
335 {
336    const char *origins[2];
337    char ***tmp;
338 
339    origins[0] = origin;
340    origins[1] = NULL;
341 
342    tmp = matchallbyorigin(origins, retval);
343    if (tmp && tmp[0]) {
344 	return tmp[0];
345    } else {
346 	return NULL;
347    }
348 }
349 
350 /*
351  * Small linked list to memoize results of isinstalledpkg().  A hash table
352  * would be faster but for n ~= 1000 may be overkill.
353  */
354 struct iip_memo {
355 	LIST_ENTRY(iip_memo) iip_link;
356 	char	*iip_name;
357 	int	 iip_result;
358 };
359 LIST_HEAD(, iip_memo) iip_memo = LIST_HEAD_INITIALIZER(iip_memo);
360 
361 /*
362  *
363  * Return 1 if the specified package is installed,
364  * 0 if not, and -1 if an error occured.
365  */
366 int
isinstalledpkg(const char * name)367 isinstalledpkg(const char *name)
368 {
369     int result;
370     char *buf, *buf2;
371     struct iip_memo *memo;
372 
373     LIST_FOREACH(memo, &iip_memo, iip_link) {
374 	if (strcmp(memo->iip_name, name) == 0)
375 	    return memo->iip_result;
376     }
377 
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;
388 
389 	if (!isfile(buf2) || access(buf2, R_OK) == FAIL)
390 	    result = -1;
391 	else
392 	    result = 1;
393     }
394 
395     free(buf);
396     buf = strdup(name);
397     if (buf == NULL)
398 	goto errout;
399     free(buf2);
400     buf2 = NULL;
401 
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;
409 
410 errout:
411     if (buf != NULL)
412 	free(buf);
413     if (buf2 != NULL)
414 	free(buf2);
415     return -1;
416 }
417 
418 /*
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).
422  */
423 static int
rex_match(const char * pattern,const char * pkgname,int extended)424 rex_match(const char *pattern, const char *pkgname, int extended)
425 {
426     char errbuf[128];
427     int errcode;
428     int retval;
429     regex_t rex;
430 
431     retval = 0;
432 
433     errcode = regcomp(&rex, pattern, (extended ? REG_EXTENDED : REG_BASIC) | REG_NOSUB);
434     if (errcode == 0)
435 	errcode = regexec(&rex, pkgname, 0, NULL, 0);
436 
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;
443     }
444 
445     regfree(&rex);
446 
447     return retval;
448 }
449 
450 /*
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).
454  */
455 static int
csh_match(const char * pattern,const char * string,int flags)456 csh_match(const char *pattern, const char *string, int flags)
457 {
458     int ret = FNM_NOMATCH;
459 
460 
461     const char *nextchoice = pattern;
462     const char *current = NULL;
463 
464     int prefixlen = -1;
465     int currentlen = 0;
466 
467     int level = 0;
468 
469     do {
470 	const char *pos = nextchoice;
471 	const char *postfix = NULL;
472 
473 	Boolean quoted = FALSE;
474 
475 	nextchoice = NULL;
476 
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;
490 		    }
491 		    break;
492 		case ',':
493 		    if (level == 1 && !nextchoice) {
494 			nextchoice = pos+1;
495 			currentlen = pos-current;
496 		    }
497 		    break;
498 		case '}':
499 		    if (level == 1) {
500 			postfix = pos+1;
501 			if (!nextchoice)
502 			    currentlen = pos-current;
503 		    }
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:
521 		    ;
522 		}
523 	    }
524 	    pos++;
525 	} while (!postfix);
526 
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);
539 
540     return ret;
541 }
542 
543 /*
544  * Create an empty store, optionally deallocating
545  * any previously allocated space if store != NULL.
546  */
547 struct store *
storecreate(struct store * store)548 storecreate(struct store *store)
549 {
550     int i;
551 
552     if (store == NULL) {
553 	store = malloc(sizeof *store);
554 	if (store == NULL) {
555 	    warnx("%s(): malloc() failed", __func__);
556 	    return NULL;
557 	}
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;
565     }
566     store->used = 0;
567 
568     return store;
569 }
570 
571 /*
572  * Append specified element to the provided store.
573  */
574 static int
storeappend(struct store * store,const char * item)575 storeappend(struct store *store, const char *item)
576 {
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;
585 	}
586     }
587 
588     asprintf(&(store->store[store->used]), "%s", item);
589     if (store->store[store->used] == NULL) {
590 	warnx("%s(): malloc() failed", __func__);
591 	return 1;
592     }
593     store->used++;
594     store->store[store->used] = NULL;
595 
596     return 0;
597 }
598 
599 static int
fname_cmp(const FTSENT * const * a,const FTSENT * const * b)600 fname_cmp(const FTSENT * const *a, const FTSENT * const *b)
601 {
602     return strcmp((*a)->fts_name, (*b)->fts_name);
603 }
604