match.c revision 131275
173134Ssobomax/*
273134Ssobomax * FreeBSD install - a package for the installation and maintainance
373134Ssobomax * of non-core utilities.
473134Ssobomax *
573134Ssobomax * Redistribution and use in source and binary forms, with or without
673134Ssobomax * modification, are permitted provided that the following conditions
773134Ssobomax * are met:
873134Ssobomax * 1. Redistributions of source code must retain the above copyright
973134Ssobomax *    notice, this list of conditions and the following disclaimer.
1073134Ssobomax * 2. Redistributions in binary form must reproduce the above copyright
1173134Ssobomax *    notice, this list of conditions and the following disclaimer in the
1273134Ssobomax *    documentation and/or other materials provided with the distribution.
1373134Ssobomax *
1473134Ssobomax * Maxim Sobolev
1573134Ssobomax * 24 February 2001
1673134Ssobomax *
1773134Ssobomax * Routines used to query installed packages.
1873134Ssobomax *
1973134Ssobomax */
2073134Ssobomax
2193520Sobrien#include <sys/cdefs.h>
2293520Sobrien__FBSDID("$FreeBSD: head/usr.sbin/pkg_install/lib/match.c 131275 2004-06-29 18:54:47Z eik $");
2393520Sobrien
2473134Ssobomax#include "lib.h"
2573134Ssobomax#include <err.h>
2673134Ssobomax#include <fnmatch.h>
2773134Ssobomax#include <fts.h>
2873134Ssobomax#include <regex.h>
2973134Ssobomax
3073134Ssobomax/*
3173134Ssobomax * Simple structure representing argv-like
3273134Ssobomax * NULL-terminated list.
3373134Ssobomax */
3473134Ssobomaxstruct store {
3573134Ssobomax    int currlen;
3673134Ssobomax    int used;
3773134Ssobomax    char **store;
3873134Ssobomax};
3973134Ssobomax
40131275Seikstatic int rex_match(const char *, const char *, int);
41131275Seikstatic int csh_match(const char *, const char *, int);
4296613Ssobomaxstruct store *storecreate(struct store *);
4373144Ssobomaxstatic int storeappend(struct store *, const char *);
44103726Swollmanstatic int fname_cmp(const FTSENT * const *, const FTSENT * const *);
4573134Ssobomax
4673134Ssobomax/*
4773134Ssobomax * Function to query names of installed packages.
48131275Seik * MatchType	- one of MATCH_ALL, MATCH_EREGEX, MATCH_REGEX, MATCH_GLOB, MATCH_NGLOB;
4973134Ssobomax * patterns	- NULL-terminated list of glob or regex patterns
5073134Ssobomax *		  (could be NULL for MATCH_ALL);
5173134Ssobomax * retval	- return value (could be NULL if you don't want/need
5273134Ssobomax *		  return value).
5373134Ssobomax * Returns NULL-terminated list with matching names.
5473134Ssobomax * Names in list returned are dynamically allocated and should
5573134Ssobomax * not be altered by the caller.
5673134Ssobomax */
5773134Ssobomaxchar **
5873134Ssobomaxmatchinstalled(match_t MatchType, char **patterns, int *retval)
5973134Ssobomax{
6074258Ssobomax    int i, errcode, len;
6184745Ssobomax    char *matched;
6284745Ssobomax    const char *paths[2] = {LOG_DIR, NULL};
6373134Ssobomax    static struct store *store = NULL;
6473134Ssobomax    FTS *ftsp;
6573134Ssobomax    FTSENT *f;
6674258Ssobomax    Boolean *lmatched;
6773134Ssobomax
6896613Ssobomax    store = storecreate(store);
6973134Ssobomax    if (store == NULL) {
7096613Ssobomax	if (retval != NULL)
7196613Ssobomax	    *retval = 1;
7296613Ssobomax	return NULL;
7396613Ssobomax    }
7473134Ssobomax
7573134Ssobomax    if (retval != NULL)
7673134Ssobomax	*retval = 0;
7773134Ssobomax
7884745Ssobomax    if (!isdir(paths[0])) {
7973134Ssobomax	if (retval != NULL)
8073134Ssobomax	    *retval = 1;
8173134Ssobomax	return NULL;
8273134Ssobomax	/* Not reached */
8373134Ssobomax    }
8473134Ssobomax
8574258Ssobomax    /* Count number of patterns */
8674699Ssobomax    if (patterns != NULL) {
8774699Ssobomax	for (len = 0; patterns[len]; len++) {}
8874699Ssobomax	lmatched = alloca(sizeof(*lmatched) * len);
8974699Ssobomax	if (lmatched == NULL) {
9096392Salfred	    warnx("%s(): alloca() failed", __func__);
9174699Ssobomax	    if (retval != NULL)
9274699Ssobomax		*retval = 1;
9374699Ssobomax	    return NULL;
9474699Ssobomax    	}
9574699Ssobomax    } else
9674699Ssobomax	len = 0;
9774699Ssobomax
9874258Ssobomax    for (i = 0; i < len; i++)
9974258Ssobomax	lmatched[i] = FALSE;
10074258Ssobomax
10184745Ssobomax    ftsp = fts_open((char * const *)(uintptr_t)paths, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT, fname_cmp);
10273134Ssobomax    if (ftsp != NULL) {
10373134Ssobomax	while ((f = fts_read(ftsp)) != NULL) {
10473134Ssobomax	    if (f->fts_info == FTS_D && f->fts_level == 1) {
10573134Ssobomax		fts_set(ftsp, f, FTS_SKIP);
10673144Ssobomax		matched = NULL;
10773144Ssobomax		errcode = 0;
10873144Ssobomax		if (MatchType == MATCH_ALL)
10973144Ssobomax		    matched = f->fts_name;
11073144Ssobomax		else
11173144Ssobomax		    for (i = 0; patterns[i]; i++) {
112131275Seik			errcode = pattern_match(MatchType, patterns[i], f->fts_name);
113131275Seik			if (errcode == 1) {
114131275Seik			    matched = f->fts_name;
115131275Seik			    lmatched[i] = TRUE;
116131275Seik			    errcode = 0;
11773134Ssobomax			}
11873144Ssobomax			if (matched != NULL || errcode != 0)
11973144Ssobomax			    break;
12073134Ssobomax		    }
12173144Ssobomax		if (errcode == 0 && matched != NULL)
12273144Ssobomax		    errcode = storeappend(store, matched);
12373144Ssobomax		if (errcode != 0) {
12473144Ssobomax		    if (retval != NULL)
12573144Ssobomax			*retval = 1;
12673144Ssobomax		    return NULL;
12773144Ssobomax		    /* Not reached */
12873134Ssobomax		}
12973134Ssobomax	    }
13073134Ssobomax	}
13173134Ssobomax	fts_close(ftsp);
13273134Ssobomax    }
13373134Ssobomax
13474258Ssobomax    if (MatchType == MATCH_GLOB) {
13574258Ssobomax	for (i = 0; i < len; i++)
13674258Ssobomax	    if (lmatched[i] == FALSE)
13774258Ssobomax		storeappend(store, patterns[i]);
13874258Ssobomax    }
13974258Ssobomax
14073134Ssobomax    if (store->used == 0)
14173134Ssobomax	return NULL;
14273134Ssobomax    else
14373134Ssobomax	return store->store;
14473134Ssobomax}
14573134Ssobomax
146131275Seikint
147131275Seikpattern_match(match_t MatchType, char *pattern, const char *pkgname)
148131275Seik{
149131275Seik    int errcode = 0;
150131275Seik    const char *fname = pkgname;
151131275Seik    char basefname[PATH_MAX];
152131275Seik    char condchar = '\0';
153131275Seik    char *condition;
154131275Seik
155131275Seik    /* do we have an appended condition? */
156131275Seik    condition = strpbrk(pattern, "<>=");
157131275Seik    if (condition) {
158131275Seik	const char *ch;
159131275Seik	/* yes, isolate the pattern from the condition ... */
160131275Seik	if (condition > pattern && condition[-1] == '!')
161131275Seik	    condition--;
162131275Seik	condchar = *condition;
163131275Seik	*condition = '\0';
164131275Seik	/* ... and compare the name without version */
165131275Seik	ch = strrchr(fname, '-');
166131275Seik	if (ch && ch - fname < PATH_MAX) {
167131275Seik	    strlcpy(basefname, fname, ch - fname + 1);
168131275Seik	    fname = basefname;
169131275Seik	}
170131275Seik    }
171131275Seik
172131275Seik    switch (MatchType) {
173131275Seik    case MATCH_EREGEX:
174131275Seik    case MATCH_REGEX:
175131275Seik	errcode = rex_match(pattern, fname, MatchType == MATCH_EREGEX ? 1 : 0);
176131275Seik	break;
177131275Seik    case MATCH_NGLOB:
178131275Seik    case MATCH_GLOB:
179131275Seik	errcode = (csh_match(pattern, fname, 0) == 0) ? 1 : 0;
180131275Seik	break;
181131275Seik    case MATCH_EXACT:
182131275Seik	errcode = (strcmp(pattern, fname) == 0) ? 1 : 0;
183131275Seik	break;
184131275Seik    case MATCH_ALL:
185131275Seik	errcode = 1;
186131275Seik	break;
187131275Seik    default:
188131275Seik	break;
189131275Seik    }
190131275Seik
191131275Seik    /* loop over all appended conditions */
192131275Seik    while (condition) {
193131275Seik	/* restore the pattern */
194131275Seik	*condition = condchar;
195131275Seik	/* parse the condition (fun with bits) */
196131275Seik	if (errcode == 1) {
197131275Seik	    char *nextcondition;
198131275Seik	    /* compare version numbers */
199131275Seik	    int match = 0;
200131275Seik	    if (*++condition == '=') {
201131275Seik		match = 2;
202131275Seik		condition++;
203131275Seik	    }
204131275Seik	    switch(condchar) {
205131275Seik	    case '<':
206131275Seik		match |= 1;
207131275Seik		break;
208131275Seik	    case '>':
209131275Seik		match |= 4;
210131275Seik		break;
211131275Seik	    case '=':
212131275Seik		match |= 2;
213131275Seik		break;
214131275Seik	    case '!':
215131275Seik		match = 5;
216131275Seik		break;
217131275Seik	    }
218131275Seik	    /* isolate the version number from the next condition ... */
219131275Seik	    nextcondition = strpbrk(condition, "<>=!");
220131275Seik	    if (nextcondition) {
221131275Seik		condchar = *nextcondition;
222131275Seik		*nextcondition = '\0';
223131275Seik	    }
224131275Seik	    /* and compare the versions (version_cmp removes the filename for us) */
225131275Seik	    if ((match & (1 << (version_cmp(pkgname, condition) + 1))) == 0)
226131275Seik		errcode = 0;
227131275Seik	    condition = nextcondition;
228131275Seik	} else {
229131275Seik	    break;
230131275Seik	}
231131275Seik    }
232131275Seik
233131275Seik    return errcode;
234131275Seik}
235131275Seik
23673134Ssobomax/*
23796613Ssobomax * Synopsis is similar to matchinstalled(), but use origin
23896613Ssobomax * as a key for matching packages.
23996613Ssobomax */
24096613Ssobomaxchar **
24196613Ssobomaxmatchbyorigin(const char *origin, int *retval)
24296613Ssobomax{
24396613Ssobomax    char **installed;
24496613Ssobomax    int i;
24596613Ssobomax    static struct store *store = NULL;
24696613Ssobomax
24796613Ssobomax    store = storecreate(store);
24896613Ssobomax    if (store == NULL) {
24996613Ssobomax	if (retval != NULL)
25096613Ssobomax	    *retval = 1;
25196613Ssobomax	return NULL;
25296613Ssobomax    }
25396613Ssobomax
25496613Ssobomax    if (retval != NULL)
25596613Ssobomax	*retval = 0;
25696613Ssobomax
25796613Ssobomax    installed = matchinstalled(MATCH_ALL, NULL, retval);
25896613Ssobomax    if (installed == NULL)
25996613Ssobomax	return NULL;
26096613Ssobomax
26196613Ssobomax    for (i = 0; installed[i] != NULL; i++) {
26296613Ssobomax	FILE *fp;
26396613Ssobomax	char *cp, tmp[PATH_MAX];
26496613Ssobomax	int cmd;
26596613Ssobomax
26696613Ssobomax	snprintf(tmp, PATH_MAX, "%s/%s", LOG_DIR, installed[i]);
26796613Ssobomax	/*
26896613Ssobomax	 * SPECIAL CASE: ignore empty dirs, since we can can see them
26996613Ssobomax	 * during port installation.
27096613Ssobomax	 */
27196613Ssobomax	if (isemptydir(tmp))
27296613Ssobomax	    continue;
27396613Ssobomax	snprintf(tmp, PATH_MAX, "%s/%s", tmp, CONTENTS_FNAME);
27496613Ssobomax	fp = fopen(tmp, "r");
27596613Ssobomax	if (fp == NULL) {
276131275Seik	    warnx("the package info for package '%s' is corrupt", installed[i]);
277131275Seik	    continue;
27896613Ssobomax	}
27996613Ssobomax
28096613Ssobomax	cmd = -1;
28196613Ssobomax	while (fgets(tmp, sizeof(tmp), fp)) {
28296613Ssobomax	    int len = strlen(tmp);
28396613Ssobomax
28496613Ssobomax	    while (len && isspace(tmp[len - 1]))
28596613Ssobomax		tmp[--len] = '\0';
28696613Ssobomax	    if (!len)
28796613Ssobomax		continue;
28896613Ssobomax	    cp = tmp;
28996613Ssobomax	    if (tmp[0] != CMD_CHAR)
29096613Ssobomax		continue;
29196613Ssobomax	    cmd = plist_cmd(tmp + 1, &cp);
29296613Ssobomax	    if (cmd == PLIST_ORIGIN) {
293131275Seik		if (csh_match(origin, cp, FNM_PATHNAME) == 0)
29496613Ssobomax		    storeappend(store, installed[i]);
29596613Ssobomax		break;
29696613Ssobomax	    }
29796613Ssobomax	}
29896613Ssobomax	if (cmd != PLIST_ORIGIN)
29996613Ssobomax	    warnx("package %s has no origin recorded", installed[i]);
30096613Ssobomax	fclose(fp);
30196613Ssobomax    }
30296613Ssobomax
30396613Ssobomax    if (store->used == 0)
30496613Ssobomax	return NULL;
30596613Ssobomax    else
30696613Ssobomax	return store->store;
30796613Ssobomax}
30896613Ssobomax
30996613Ssobomax/*
31096613Ssobomax * Return TRUE if the specified package is installed,
31196613Ssobomax * or FALSE otherwise.
31296613Ssobomax */
31396613Ssobomaxint
31496613Ssobomaxisinstalledpkg(const char *name)
31596613Ssobomax{
31696613Ssobomax    char buf[FILENAME_MAX];
317123770Sschweikh    char buf2[FILENAME_MAX];
31896613Ssobomax
31996613Ssobomax    snprintf(buf, sizeof(buf), "%s/%s", LOG_DIR, name);
32096613Ssobomax    if (!isdir(buf) || access(buf, R_OK) == FAIL)
32196613Ssobomax	return FALSE;
32296613Ssobomax
323123770Sschweikh    snprintf(buf2, sizeof(buf2), "%s/%s", buf, CONTENTS_FNAME);
324123770Sschweikh    if (!isfile(buf2) || access(buf2, R_OK) == FAIL)
32596613Ssobomax	return FALSE;
32696613Ssobomax
32796613Ssobomax    return TRUE;
32896613Ssobomax}
32996613Ssobomax
33096613Ssobomax/*
33173134Ssobomax * Returns 1 if specified pkgname matches RE pattern.
33273134Ssobomax * Otherwise returns 0 if doesn't match or -1 if RE
33373134Ssobomax * engine reported an error (usually invalid syntax).
33473134Ssobomax */
33573134Ssobomaxstatic int
336131275Seikrex_match(const char *pattern, const char *pkgname, int extended)
33773134Ssobomax{
33873134Ssobomax    char errbuf[128];
33973134Ssobomax    int errcode;
34073134Ssobomax    int retval;
34173134Ssobomax    regex_t rex;
34273134Ssobomax
34373134Ssobomax    retval = 0;
34473134Ssobomax
345131275Seik    errcode = regcomp(&rex, pattern, (extended ? REG_EXTENDED : REG_BASIC) | REG_NOSUB);
34673134Ssobomax    if (errcode == 0)
34773134Ssobomax	errcode = regexec(&rex, pkgname, 0, NULL, 0);
34873134Ssobomax
34973134Ssobomax    if (errcode == 0) {
35073134Ssobomax	retval = 1;
35173134Ssobomax    } else if (errcode != REG_NOMATCH) {
35273134Ssobomax	regerror(errcode, &rex, errbuf, sizeof(errbuf));
35373134Ssobomax	warnx("%s: %s", pattern, errbuf);
35473134Ssobomax	retval = -1;
35573134Ssobomax    }
35673134Ssobomax
35773134Ssobomax    regfree(&rex);
35873134Ssobomax
35973134Ssobomax    return retval;
36073134Ssobomax}
36173134Ssobomax
36296613Ssobomax/*
363131275Seik * Match string by a csh-style glob pattern. Returns 0 on
364131275Seik * match and FNM_NOMATCH otherwise, to be compatible with
365131275Seik * fnmatch(3).
366131275Seik */
367131275Seikstatic int
368131275Seikcsh_match(const char *pattern, const char *string, int flags)
369131275Seik{
370131275Seik    int ret = FNM_NOMATCH;
371131275Seik
372131275Seik
373131275Seik    const char *nextchoice = pattern;
374131275Seik    const char *current = NULL;
375131275Seik
376131275Seik    int prefixlen = -1;
377131275Seik    int currentlen = 0;
378131275Seik
379131275Seik    int level = 0;
380131275Seik
381131275Seik    do {
382131275Seik	const char *pos = nextchoice;
383131275Seik	const char *postfix = NULL;
384131275Seik
385131275Seik	Boolean quoted = FALSE;
386131275Seik
387131275Seik	nextchoice = NULL;
388131275Seik
389131275Seik	do {
390131275Seik	    const char *eb;
391131275Seik	    if (!*pos) {
392131275Seik		postfix = pos;
393131275Seik	    } else if (quoted) {
394131275Seik		quoted = FALSE;
395131275Seik	    } else {
396131275Seik		switch (*pos) {
397131275Seik		case '{':
398131275Seik		    ++level;
399131275Seik		    if (level == 1) {
400131275Seik			current = pos+1;
401131275Seik			prefixlen = pos-pattern;
402131275Seik		    }
403131275Seik		    break;
404131275Seik		case ',':
405131275Seik		    if (level == 1 && !nextchoice) {
406131275Seik			nextchoice = pos+1;
407131275Seik			currentlen = pos-current;
408131275Seik		    }
409131275Seik		    break;
410131275Seik		case '}':
411131275Seik		    if (level == 1) {
412131275Seik			postfix = pos+1;
413131275Seik			if (!nextchoice)
414131275Seik			    currentlen = pos-current;
415131275Seik		    }
416131275Seik		    level--;
417131275Seik		    break;
418131275Seik		case '[':
419131275Seik		    eb = pos+1;
420131275Seik		    if (*eb == '!' || *eb == '^')
421131275Seik			eb++;
422131275Seik		    if (*eb == ']')
423131275Seik			eb++;
424131275Seik		    while(*eb && *eb != ']')
425131275Seik			eb++;
426131275Seik		    if (*eb)
427131275Seik			pos=eb;
428131275Seik		    break;
429131275Seik		case '\\':
430131275Seik		    quoted = TRUE;
431131275Seik		    break;
432131275Seik		default:
433131275Seik		    ;
434131275Seik		}
435131275Seik	    }
436131275Seik	    pos++;
437131275Seik	} while (!postfix);
438131275Seik
439131275Seik	if (current) {
440131275Seik	    char buf[FILENAME_MAX];
441131275Seik	    snprintf(buf, sizeof(buf), "%.*s%.*s%s", prefixlen, pattern, currentlen, current, postfix);
442131275Seik	    ret = csh_match(buf, string, flags);
443131275Seik	    if (ret) {
444131275Seik		current = nextchoice;
445131275Seik		level = 1;
446131275Seik	    } else
447131275Seik		current = NULL;
448131275Seik	} else
449131275Seik	    ret = fnmatch(pattern, string, flags);
450131275Seik    } while (current);
451131275Seik
452131275Seik    return ret;
453131275Seik}
454131275Seik
455131275Seik/*
45696613Ssobomax * Create an empty store, optionally deallocating
45796613Ssobomax * any previously allocated space if store != NULL.
45896613Ssobomax */
45996613Ssobomaxstruct store *
46096613Ssobomaxstorecreate(struct store *store)
46196613Ssobomax{
46296613Ssobomax    int i;
46396613Ssobomax
46496613Ssobomax    if (store == NULL) {
46596613Ssobomax	store = malloc(sizeof *store);
46696613Ssobomax	if (store == NULL) {
46796613Ssobomax	    warnx("%s(): malloc() failed", __func__);
46896613Ssobomax	    return NULL;
46996613Ssobomax	}
47096613Ssobomax	store->currlen = 0;
47196613Ssobomax	store->store = NULL;
47297097Ssobomax    } else if (store->store != NULL) {
47396613Ssobomax	    /* Free previously allocated memory */
47496613Ssobomax	    for (i = 0; store->store[i] != NULL; i++)
47596613Ssobomax		free(store->store[i]);
47697097Ssobomax	    store->store[0] = NULL;
47797097Ssobomax    }
47896613Ssobomax    store->used = 0;
47996613Ssobomax
48096613Ssobomax    return store;
48196613Ssobomax}
48296613Ssobomax
48396613Ssobomax/*
48496613Ssobomax * Append specified element to the provided store.
48596613Ssobomax */
48673144Ssobomaxstatic int
48773134Ssobomaxstoreappend(struct store *store, const char *item)
48873134Ssobomax{
48973134Ssobomax    if (store->used + 2 > store->currlen) {
49073134Ssobomax	store->currlen += 16;
49180042Ssobomax	store->store = reallocf(store->store,
49280042Ssobomax				store->currlen * sizeof(*(store->store)));
49373144Ssobomax	if (store->store == NULL) {
49480042Ssobomax	    store->currlen = 0;
49596392Salfred	    warnx("%s(): reallocf() failed", __func__);
49673144Ssobomax	    return 1;
49773144Ssobomax	}
49873134Ssobomax    }
49973134Ssobomax
50073134Ssobomax    asprintf(&(store->store[store->used]), "%s", item);
50173144Ssobomax    if (store->store[store->used] == NULL) {
50296392Salfred	warnx("%s(): malloc() failed", __func__);
50373144Ssobomax	return 1;
50473144Ssobomax    }
50573134Ssobomax    store->used++;
50673134Ssobomax    store->store[store->used] = NULL;
50773144Ssobomax
50873144Ssobomax    return 0;
50973134Ssobomax}
51073134Ssobomax
51173134Ssobomaxstatic int
512103726Swollmanfname_cmp(const FTSENT * const *a, const FTSENT * const *b)
51373134Ssobomax{
51473134Ssobomax    return strcmp((*a)->fts_name, (*b)->fts_name);
51573134Ssobomax}
516