match.c revision 131285
1166551Smarcel/*
2226647Smarcel * FreeBSD install - a package for the installation and maintainance
3166551Smarcel * of non-core utilities.
4166551Smarcel *
5166551Smarcel * Redistribution and use in source and binary forms, with or without
6166551Smarcel * modification, are permitted provided that the following conditions
7166551Smarcel * are met:
8166551Smarcel * 1. Redistributions of source code must retain the above copyright
9166551Smarcel *    notice, this list of conditions and the following disclaimer.
10166551Smarcel * 2. Redistributions in binary form must reproduce the above copyright
11166551Smarcel *    notice, this list of conditions and the following disclaimer in the
12166551Smarcel *    documentation and/or other materials provided with the distribution.
13166551Smarcel *
14166551Smarcel * Maxim Sobolev
15166551Smarcel * 24 February 2001
16166551Smarcel *
17166551Smarcel * Routines used to query installed packages.
18166551Smarcel *
19166551Smarcel */
20166551Smarcel
21166551Smarcel#include <sys/cdefs.h>
22166551Smarcel__FBSDID("$FreeBSD: head/usr.sbin/pkg_install/lib/match.c 131285 2004-06-29 19:06:42Z eik $");
23166551Smarcel
24166551Smarcel#include "lib.h"
25166551Smarcel#include <err.h>
26166551Smarcel#include <fnmatch.h>
27166551Smarcel#include <fts.h>
28166551Smarcel#include <regex.h>
29166551Smarcel
30166551Smarcel/*
31166551Smarcel * Simple structure representing argv-like
32166551Smarcel * NULL-terminated list.
33166551Smarcel */
34166551Smarcelstruct store {
35166551Smarcel    int currlen;
36166551Smarcel    int used;
37166551Smarcel    char **store;
38166551Smarcel};
39166551Smarcel
40166551Smarcelstatic int rex_match(const char *, const char *, int);
41166551Smarcelstatic int csh_match(const char *, const char *, int);
42166551Smarcelstruct store *storecreate(struct store *);
43166551Smarcelstatic int storeappend(struct store *, const char *);
44219029Snetchildstatic int fname_cmp(const FTSENT * const *, const FTSENT * const *);
45166551Smarcel
46166551Smarcel/*
47166551Smarcel * Function to query names of installed packages.
48166551Smarcel * MatchType	- one of MATCH_ALL, MATCH_EREGEX, MATCH_REGEX, MATCH_GLOB, MATCH_NGLOB;
49166551Smarcel * patterns	- NULL-terminated list of glob or regex patterns
50166551Smarcel *		  (could be NULL for MATCH_ALL);
51219029Snetchild * retval	- return value (could be NULL if you don't want/need
52219029Snetchild *		  return value).
53166551Smarcel * Returns NULL-terminated list with matching names.
54166551Smarcel * Names in list returned are dynamically allocated and should
55166551Smarcel * not be altered by the caller.
56166551Smarcel */
57166551Smarcelchar **
58178180Smarcelmatchinstalled(match_t MatchType, char **patterns, int *retval)
59178180Smarcel{
60166551Smarcel    int i, errcode, len;
61166551Smarcel    char *matched;
62166551Smarcel    const char *paths[2] = {LOG_DIR, NULL};
63166551Smarcel    static struct store *store = NULL;
64166551Smarcel    FTS *ftsp;
65166551Smarcel    FTSENT *f;
66166551Smarcel    Boolean *lmatched = NULL;
67166551Smarcel
68166551Smarcel    store = storecreate(store);
69166551Smarcel    if (store == NULL) {
70166551Smarcel	if (retval != NULL)
71166551Smarcel	    *retval = 1;
72166551Smarcel	return NULL;
73166551Smarcel    }
74166551Smarcel
75166551Smarcel    if (retval != NULL)
76166551Smarcel	*retval = 0;
77166551Smarcel
78178180Smarcel    if (!isdir(paths[0])) {
79199017Srnoland	if (retval != NULL)
80166551Smarcel	    *retval = 1;
81166551Smarcel	return NULL;
82226647Smarcel	/* Not reached */
83166551Smarcel    }
84166551Smarcel
85166551Smarcel    /* Count number of patterns */
86166551Smarcel    if (patterns != NULL) {
87166551Smarcel	for (len = 0; patterns[len]; len++) {}
88166551Smarcel	lmatched = alloca(sizeof(*lmatched) * len);
89166551Smarcel	if (lmatched == NULL) {
90179763Smarcel	    warnx("%s(): alloca() failed", __func__);
91179763Smarcel	    if (retval != NULL)
92223660Sae		*retval = 1;
93179763Smarcel	    return NULL;
94166551Smarcel    	}
95166551Smarcel    } else
96178180Smarcel	len = 0;
97166551Smarcel
98166551Smarcel    for (i = 0; i < len; i++)
99188429Simp	lmatched[i] = FALSE;
100178444Smarcel
101166551Smarcel    ftsp = fts_open((char * const *)(uintptr_t)paths, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT, fname_cmp);
102214352Sae    if (ftsp != NULL) {
103166551Smarcel	while ((f = fts_read(ftsp)) != NULL) {
104188429Simp	    if (f->fts_info == FTS_D && f->fts_level == 1) {
105166551Smarcel		fts_set(ftsp, f, FTS_SKIP);
106166551Smarcel		matched = NULL;
107166551Smarcel		errcode = 0;
108213135Spjd		if (MatchType == MATCH_ALL)
109213135Spjd		    matched = f->fts_name;
110166551Smarcel		else
111166551Smarcel		    for (i = 0; patterns[i]; i++) {
112166551Smarcel			errcode = pattern_match(MatchType, patterns[i], f->fts_name);
113207094Smarcel			if (errcode == 1) {
114207094Smarcel			    matched = f->fts_name;
115214352Sae			    lmatched[i] = TRUE;
116166551Smarcel			    errcode = 0;
117166551Smarcel			}
118166551Smarcel			if (matched != NULL || errcode != 0)
119178180Smarcel			    break;
120166551Smarcel		    }
121166551Smarcel		if (errcode == 0 && matched != NULL)
122178444Smarcel		    errcode = storeappend(store, matched);
123166551Smarcel		if (errcode != 0) {
124166551Smarcel		    if (retval != NULL)
125207094Smarcel			*retval = 1;
126166551Smarcel		    return NULL;
127166551Smarcel		    /* Not reached */
128166551Smarcel		}
129214352Sae	    }
130213135Spjd	}
131166551Smarcel	fts_close(ftsp);
132166551Smarcel    }
133166551Smarcel
134166551Smarcel    if (MatchType == MATCH_GLOB) {
135166551Smarcel	for (i = 0; i < len; i++)
136166551Smarcel	    if (lmatched[i] == FALSE)
137166551Smarcel		storeappend(store, patterns[i]);
138166551Smarcel    }
139166551Smarcel
140166551Smarcel    if (store->used == 0)
141166551Smarcel	return NULL;
142217531Sae    else
143178180Smarcel	return store->store;
144166551Smarcel}
145177510Smarcel
146166551Smarcelint
147200534Srpaulopattern_match(match_t MatchType, char *pattern, const char *pkgname)
148182797Srpaulo{
149200534Srpaulo    int errcode = 0;
150200534Srpaulo    const char *fname = pkgname;
151200534Srpaulo    char basefname[PATH_MAX];
152200534Srpaulo    char condchar = '\0';
153200534Srpaulo    char *condition;
154218014Sae
155166551Smarcel    /* do we have an appended condition? */
156166551Smarcel    condition = strpbrk(pattern, "<>=");
157172940Sjhb    if (condition) {
158236023Smarcel	const char *ch;
159166551Smarcel	/* yes, isolate the pattern from the condition ... */
160166551Smarcel	if (condition > pattern && condition[-1] == '!')
161166551Smarcel	    condition--;
162172857Smarcel	condchar = *condition;
163200534Srpaulo	*condition = '\0';
164200534Srpaulo	/* ... and compare the name without version */
165200534Srpaulo	ch = strrchr(fname, '-');
166166551Smarcel	if (ch && ch - fname < PATH_MAX) {
167234417Smarck	    strlcpy(basefname, fname, ch - fname + 1);
168234417Smarck	    fname = basefname;
169234417Smarck	}
170200539Srpaulo    }
171200539Srpaulo
172200539Srpaulo    switch (MatchType) {
173200539Srpaulo    case MATCH_EREGEX:
174200539Srpaulo    case MATCH_REGEX:
175200539Srpaulo	errcode = rex_match(pattern, fname, MatchType == MATCH_EREGEX ? 1 : 0);
176200539Srpaulo	break;
177200539Srpaulo    case MATCH_NGLOB:
178200539Srpaulo    case MATCH_GLOB:
179200539Srpaulo	errcode = (csh_match(pattern, fname, 0) == 0) ? 1 : 0;
180166551Smarcel	break;
181166551Smarcel    case MATCH_EXACT:
182166551Smarcel	errcode = (strcmp(pattern, fname) == 0) ? 1 : 0;
183200534Srpaulo	break;
184200534Srpaulo    case MATCH_ALL:
185214352Sae	errcode = 1;
186226647Smarcel	break;
187200534Srpaulo    default:
188226647Smarcel	break;
189226647Smarcel    }
190226647Smarcel
191226647Smarcel    /* loop over all appended conditions */
192226647Smarcel    while (condition) {
193226647Smarcel	/* restore the pattern */
194226647Smarcel	*condition = condchar;
195226647Smarcel	/* parse the condition (fun with bits) */
196226647Smarcel	if (errcode == 1) {
197226647Smarcel	    char *nextcondition;
198226647Smarcel	    /* compare version numbers */
199236023Smarcel	    int match = 0;
200226647Smarcel	    if (*++condition == '=') {
201226647Smarcel		match = 2;
202226647Smarcel		condition++;
203226647Smarcel	    }
204226647Smarcel	    switch(condchar) {
205226647Smarcel	    case '<':
206226647Smarcel		match |= 1;
207226647Smarcel		break;
208234417Smarck	    case '>':
209234417Smarck		match |= 4;
210234417Smarck		break;
211226647Smarcel	    case '=':
212226647Smarcel		match |= 2;
213226647Smarcel		break;
214226647Smarcel	    case '!':
215226647Smarcel		match = 5;
216226647Smarcel		break;
217226647Smarcel	    }
218226647Smarcel	    /* isolate the version number from the next condition ... */
219226647Smarcel	    nextcondition = strpbrk(condition, "<>=!");
220226647Smarcel	    if (nextcondition) {
221226647Smarcel		condchar = *nextcondition;
222226647Smarcel		*nextcondition = '\0';
223200534Srpaulo	    }
224200534Srpaulo	    /* and compare the versions (version_cmp removes the filename for us) */
225226647Smarcel	    if ((match & (1 << (version_cmp(pkgname, condition) + 1))) == 0)
226226647Smarcel		errcode = 0;
227226647Smarcel	    condition = nextcondition;
228226647Smarcel	} else {
229226647Smarcel	    break;
230226647Smarcel	}
231226647Smarcel    }
232226647Smarcel
233226647Smarcel    return errcode;
234226647Smarcel}
235226647Smarcel
236226647Smarcel/*
237226647Smarcel * Synopsis is similar to matchinstalled(), but use origin
238226647Smarcel * as a key for matching packages.
239226647Smarcel */
240226647Smarcelchar **
241226647Smarcelmatchbyorigin(const char *origin, int *retval)
242226647Smarcel{
243226647Smarcel    char **installed;
244226647Smarcel    int i;
245226647Smarcel    static struct store *store = NULL;
246226647Smarcel
247226647Smarcel    store = storecreate(store);
248226647Smarcel    if (store == NULL) {
249226647Smarcel	if (retval != NULL)
250226647Smarcel	    *retval = 1;
251226647Smarcel	return NULL;
252226647Smarcel    }
253226647Smarcel
254226647Smarcel    if (retval != NULL)
255226647Smarcel	*retval = 0;
256226647Smarcel
257226647Smarcel    installed = matchinstalled(MATCH_ALL, NULL, retval);
258226647Smarcel    if (installed == NULL)
259226647Smarcel	return NULL;
260226647Smarcel
261226647Smarcel    for (i = 0; installed[i] != NULL; i++) {
262226647Smarcel	FILE *fp;
263251588Smarcel	char *cp, tmp[PATH_MAX];
264251588Smarcel	int cmd;
265251588Smarcel
266251588Smarcel	snprintf(tmp, PATH_MAX, "%s/%s", LOG_DIR, installed[i]);
267251588Smarcel	/*
268251588Smarcel	 * SPECIAL CASE: ignore empty dirs, since we can can see them
269251588Smarcel	 * during port installation.
270251588Smarcel	 */
271251588Smarcel	if (isemptydir(tmp))
272251588Smarcel	    continue;
273226647Smarcel	snprintf(tmp, PATH_MAX, "%s/%s", tmp, CONTENTS_FNAME);
274226647Smarcel	fp = fopen(tmp, "r");
275226647Smarcel	if (fp == NULL) {
276226647Smarcel	    warnx("the package info for package '%s' is corrupt", installed[i]);
277226647Smarcel	    continue;
278226647Smarcel	}
279226647Smarcel
280226647Smarcel	cmd = -1;
281226647Smarcel	while (fgets(tmp, sizeof(tmp), fp)) {
282226647Smarcel	    int len = strlen(tmp);
283226647Smarcel
284226647Smarcel	    while (len && isspace(tmp[len - 1]))
285226647Smarcel		tmp[--len] = '\0';
286226647Smarcel	    if (!len)
287226647Smarcel		continue;
288226647Smarcel	    cp = tmp;
289226647Smarcel	    if (tmp[0] != CMD_CHAR)
290226647Smarcel		continue;
291226647Smarcel	    cmd = plist_cmd(tmp + 1, &cp);
292226647Smarcel	    if (cmd == PLIST_ORIGIN) {
293226647Smarcel		if (csh_match(origin, cp, FNM_PATHNAME) == 0)
294226647Smarcel		    storeappend(store, installed[i]);
295226647Smarcel		break;
296226647Smarcel	    }
297251588Smarcel	}
298226647Smarcel	if (cmd != PLIST_ORIGIN)
299226647Smarcel	    warnx("package %s has no origin recorded", installed[i]);
300226647Smarcel	fclose(fp);
301226647Smarcel    }
302226647Smarcel
303226647Smarcel    if (store->used == 0)
304226647Smarcel	return NULL;
305226647Smarcel    else
306226647Smarcel	return store->store;
307226647Smarcel}
308226647Smarcel
309226647Smarcel/*
310226647Smarcel *
311226647Smarcel * Return 1 if the specified package is installed,
312226647Smarcel * 0 if not, and -1 if an error occured.
313226647Smarcel */
314226647Smarcelint
315226647Smarcelisinstalledpkg(const char *name)
316226647Smarcel{
317226647Smarcel    char buf[FILENAME_MAX];
318226647Smarcel    char buf2[FILENAME_MAX];
319226647Smarcel
320226647Smarcel    snprintf(buf, sizeof(buf), "%s/%s", LOG_DIR, name);
321226647Smarcel    if (!isdir(buf) || access(buf, R_OK) == FAIL)
322226647Smarcel	return 0;
323226647Smarcel
324226647Smarcel    snprintf(buf2, sizeof(buf2), "%s/%s", buf, CONTENTS_FNAME);
325226647Smarcel    if (!isfile(buf2) || access(buf2, R_OK) == FAIL)
326226647Smarcel	return -1;
327226647Smarcel
328226647Smarcel    return 1;
329226647Smarcel}
330226647Smarcel
331226647Smarcel/*
332226647Smarcel * Returns 1 if specified pkgname matches RE pattern.
333226647Smarcel * Otherwise returns 0 if doesn't match or -1 if RE
334226647Smarcel * engine reported an error (usually invalid syntax).
335226647Smarcel */
336226647Smarcelstatic int
337226647Smarcelrex_match(const char *pattern, const char *pkgname, int extended)
338226647Smarcel{
339226647Smarcel    char errbuf[128];
340226647Smarcel    int errcode;
341226647Smarcel    int retval;
342226647Smarcel    regex_t rex;
343226647Smarcel
344226647Smarcel    retval = 0;
345226647Smarcel
346226647Smarcel    errcode = regcomp(&rex, pattern, (extended ? REG_EXTENDED : REG_BASIC) | REG_NOSUB);
347226647Smarcel    if (errcode == 0)
348226647Smarcel	errcode = regexec(&rex, pkgname, 0, NULL, 0);
349226647Smarcel
350226647Smarcel    if (errcode == 0) {
351226647Smarcel	retval = 1;
352226647Smarcel    } else if (errcode != REG_NOMATCH) {
353226647Smarcel	regerror(errcode, &rex, errbuf, sizeof(errbuf));
354251588Smarcel	warnx("%s: %s", pattern, errbuf);
355226647Smarcel	retval = -1;
356226647Smarcel    }
357199017Srnoland
358166551Smarcel    regfree(&rex);
359199017Srnoland
360166551Smarcel    return retval;
361199017Srnoland}
362166551Smarcel
363166551Smarcel/*
364166551Smarcel * Match string by a csh-style glob pattern. Returns 0 on
365166551Smarcel * match and FNM_NOMATCH otherwise, to be compatible with
366166551Smarcel * fnmatch(3).
367166551Smarcel */
368166551Smarcelstatic int
369166551Smarcelcsh_match(const char *pattern, const char *string, int flags)
370214352Sae{
371214352Sae    int ret = FNM_NOMATCH;
372214352Sae
373214352Sae
374214352Sae    const char *nextchoice = pattern;
375214352Sae    const char *current = NULL;
376214352Sae
377214352Sae    int prefixlen = -1;
378214352Sae    int currentlen = 0;
379166551Smarcel
380166551Smarcel    int level = 0;
381166551Smarcel
382199017Srnoland    do {
383199017Srnoland	const char *pos = nextchoice;
384199017Srnoland	const char *postfix = NULL;
385199017Srnoland
386166551Smarcel	Boolean quoted = FALSE;
387166551Smarcel
388199017Srnoland	nextchoice = NULL;
389166551Smarcel
390199017Srnoland	do {
391199017Srnoland	    const char *eb;
392199017Srnoland	    if (!*pos) {
393199017Srnoland		postfix = pos;
394166551Smarcel	    } else if (quoted) {
395199017Srnoland		quoted = FALSE;
396199017Srnoland	    } else {
397199017Srnoland		switch (*pos) {
398199017Srnoland		case '{':
399199017Srnoland		    ++level;
400166551Smarcel		    if (level == 1) {
401166551Smarcel			current = pos+1;
402166551Smarcel			prefixlen = pos-pattern;
403199017Srnoland		    }
404214352Sae		    break;
405199017Srnoland		case ',':
406199017Srnoland		    if (level == 1 && !nextchoice) {
407166551Smarcel			nextchoice = pos+1;
408199017Srnoland			currentlen = pos-current;
409199017Srnoland		    }
410214352Sae		    break;
411214352Sae		case '}':
412214352Sae		    if (level == 1) {
413166551Smarcel			postfix = pos+1;
414166551Smarcel			if (!nextchoice)
415199017Srnoland			    currentlen = pos-current;
416166551Smarcel		    }
417199017Srnoland		    level--;
418199017Srnoland		    break;
419166551Smarcel		case '[':
420199017Srnoland		    eb = pos+1;
421166551Smarcel		    if (*eb == '!' || *eb == '^')
422166551Smarcel			eb++;
423199017Srnoland		    if (*eb == ']')
424199017Srnoland			eb++;
425166551Smarcel		    while(*eb && *eb != ']')
426166551Smarcel			eb++;
427199017Srnoland		    if (*eb)
428199017Srnoland			pos=eb;
429166551Smarcel		    break;
430199017Srnoland		case '\\':
431166551Smarcel		    quoted = TRUE;
432166551Smarcel		    break;
433199017Srnoland		default:
434166551Smarcel		    ;
435166551Smarcel		}
436166551Smarcel	    }
437166551Smarcel	    pos++;
438199017Srnoland	} while (!postfix);
439166551Smarcel
440199017Srnoland	if (current) {
441166551Smarcel	    char buf[FILENAME_MAX];
442166551Smarcel	    snprintf(buf, sizeof(buf), "%.*s%.*s%s", prefixlen, pattern, currentlen, current, postfix);
443199017Srnoland	    ret = csh_match(buf, string, flags);
444199017Srnoland	    if (ret) {
445199017Srnoland		current = nextchoice;
446214352Sae		level = 1;
447214352Sae	    } else
448214352Sae		current = NULL;
449214352Sae	} else
450199017Srnoland	    ret = fnmatch(pattern, string, flags);
451199017Srnoland    } while (current);
452199017Srnoland
453199017Srnoland    return ret;
454199017Srnoland}
455199017Srnoland
456199017Srnoland/*
457199017Srnoland * Create an empty store, optionally deallocating
458166551Smarcel * any previously allocated space if store != NULL.
459166551Smarcel */
460166551Smarcelstruct store *
461166551Smarcelstorecreate(struct store *store)
462166551Smarcel{
463166551Smarcel    int i;
464166551Smarcel
465166551Smarcel    if (store == NULL) {
466166551Smarcel	store = malloc(sizeof *store);
467217531Sae	if (store == NULL) {
468166551Smarcel	    warnx("%s(): malloc() failed", __func__);
469166551Smarcel	    return NULL;
470199017Srnoland	}
471199017Srnoland	store->currlen = 0;
472199017Srnoland	store->store = NULL;
473166551Smarcel    } else if (store->store != NULL) {
474166551Smarcel	    /* Free previously allocated memory */
475166551Smarcel	    for (i = 0; store->store[i] != NULL; i++)
476166551Smarcel		free(store->store[i]);
477166551Smarcel	    store->store[0] = NULL;
478166551Smarcel    }
479217531Sae    store->used = 0;
480217531Sae
481217531Sae    return store;
482217531Sae}
483217531Sae
484217531Sae/*
485217531Sae * Append specified element to the provided store.
486217531Sae */
487217531Saestatic int
488217531Saestoreappend(struct store *store, const char *item)
489217531Sae{
490217531Sae    if (store->used + 2 > store->currlen) {
491217531Sae	store->currlen += 16;
492166551Smarcel	store->store = reallocf(store->store,
493166551Smarcel				store->currlen * sizeof(*(store->store)));
494166551Smarcel	if (store->store == NULL) {
495166551Smarcel	    store->currlen = 0;
496166551Smarcel	    warnx("%s(): reallocf() failed", __func__);
497166551Smarcel	    return 1;
498166551Smarcel	}
499166551Smarcel    }
500166551Smarcel
501166551Smarcel    asprintf(&(store->store[store->used]), "%s", item);
502166551Smarcel    if (store->store[store->used] == NULL) {
503166551Smarcel	warnx("%s(): malloc() failed", __func__);
504166551Smarcel	return 1;
505166551Smarcel    }
506166551Smarcel    store->used++;
507166551Smarcel    store->store[store->used] = NULL;
508166551Smarcel
509166551Smarcel    return 0;
510179763Smarcel}
511179763Smarcel
512166551Smarcelstatic int
513166551Smarcelfname_cmp(const FTSENT * const *a, const FTSENT * const *b)
514166551Smarcel{
515166551Smarcel    return strcmp((*a)->fts_name, (*b)->fts_name);
516166551Smarcel}
517166551Smarcel