summaryrefslogtreecommitdiffstats
path: root/lib/libpkg/match.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libpkg/match.c')
-rw-r--r--lib/libpkg/match.c603
1 files changed, 603 insertions, 0 deletions
diff --git a/lib/libpkg/match.c b/lib/libpkg/match.c
new file mode 100644
index 0000000..ba65442
--- /dev/null
+++ b/lib/libpkg/match.c
@@ -0,0 +1,603 @@
+/*
+ * FreeBSD install - a package for the installation and maintainance
+ * of non-core utilities.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Maxim Sobolev
+ * 24 February 2001
+ *
+ * Routines used to query installed packages.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "pkg.h"
+#include <err.h>
+#include <fnmatch.h>
+#include <fts.h>
+#include <regex.h>
+
+/*
+ * Simple structure representing argv-like
+ * NULL-terminated list.
+ */
+struct store {
+ int currlen;
+ int used;
+ char **store;
+};
+
+static int rex_match(const char *, const char *, int);
+static int csh_match(const char *, const char *, int);
+struct store *storecreate(struct store *);
+static int storeappend(struct store *, const char *);
+static int fname_cmp(const FTSENT * const *, const FTSENT * const *);
+
+/*
+ * Function to query names of installed packages.
+ * MatchType - one of MATCH_ALL, MATCH_EREGEX, MATCH_REGEX, MATCH_GLOB, MATCH_NGLOB;
+ * patterns - NULL-terminated list of glob or regex patterns
+ * (could be NULL for MATCH_ALL);
+ * retval - return value (could be NULL if you don't want/need
+ * return value).
+ * Returns NULL-terminated list with matching names.
+ * Names in list returned are dynamically allocated and should
+ * not be altered by the caller.
+ */
+char **
+matchinstalled(match_t MatchType, char **patterns, int *retval)
+{
+ int i, errcode, len;
+ char *matched;
+ const char *paths[2] = {LOG_DIR, NULL};
+ static struct store *store = NULL;
+ FTS *ftsp;
+ FTSENT *f;
+ Boolean *lmatched = NULL;
+
+ store = storecreate(store);
+ if (store == NULL) {
+ if (retval != NULL)
+ *retval = 1;
+ return NULL;
+ }
+
+ if (retval != NULL)
+ *retval = 0;
+
+ if (!isdir(paths[0])) {
+ if (retval != NULL)
+ *retval = 1;
+ return NULL;
+ /* Not reached */
+ }
+
+ /* Count number of patterns */
+ if (patterns != NULL) {
+ for (len = 0; patterns[len]; len++) {}
+ lmatched = alloca(sizeof(*lmatched) * len);
+ if (lmatched == NULL) {
+ warnx("%s(): alloca() failed", __func__);
+ if (retval != NULL)
+ *retval = 1;
+ return NULL;
+ }
+ } else
+ len = 0;
+
+ for (i = 0; i < len; i++)
+ lmatched[i] = FALSE;
+
+ ftsp = fts_open((char * const *)(uintptr_t)paths, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT, fname_cmp);
+ if (ftsp != NULL) {
+ while ((f = fts_read(ftsp)) != NULL) {
+ if (f->fts_info == FTS_D && f->fts_level == 1) {
+ fts_set(ftsp, f, FTS_SKIP);
+ matched = NULL;
+ errcode = 0;
+ if (MatchType == MATCH_ALL)
+ matched = f->fts_name;
+ else
+ for (i = 0; patterns[i]; i++) {
+ errcode = pattern_match(MatchType, patterns[i], f->fts_name);
+ if (errcode == 1) {
+ matched = f->fts_name;
+ lmatched[i] = TRUE;
+ errcode = 0;
+ }
+ if (matched != NULL || errcode != 0)
+ break;
+ }
+ if (errcode == 0 && matched != NULL)
+ errcode = storeappend(store, matched);
+ if (errcode != 0) {
+ if (retval != NULL)
+ *retval = 1;
+ return NULL;
+ /* Not reached */
+ }
+ }
+ }
+ fts_close(ftsp);
+ }
+
+ if (MatchType == MATCH_GLOB) {
+ for (i = 0; i < len; i++)
+ if (lmatched[i] == FALSE)
+ storeappend(store, patterns[i]);
+ }
+
+ if (store->used == 0)
+ return NULL;
+ else
+ return store->store;
+}
+
+int
+pattern_match(match_t MatchType, char *pattern, const char *pkgname)
+{
+ int errcode = 0;
+ const char *fname = pkgname;
+ char basefname[PATH_MAX];
+ char condchar = '\0';
+ char *condition;
+
+ /* do we have an appended condition? */
+ condition = strpbrk(pattern, "<>=");
+ if (condition) {
+ const char *ch;
+ /* yes, isolate the pattern from the condition ... */
+ if (condition > pattern && condition[-1] == '!')
+ condition--;
+ condchar = *condition;
+ *condition = '\0';
+ /* ... and compare the name without version */
+ ch = strrchr(fname, '-');
+ if (ch && ch - fname < PATH_MAX) {
+ strlcpy(basefname, fname, ch - fname + 1);
+ fname = basefname;
+ }
+ }
+
+ switch (MatchType) {
+ case MATCH_EREGEX:
+ case MATCH_REGEX:
+ errcode = rex_match(pattern, fname, MatchType == MATCH_EREGEX ? 1 : 0);
+ break;
+ case MATCH_NGLOB:
+ case MATCH_GLOB:
+ errcode = (csh_match(pattern, fname, 0) == 0) ? 1 : 0;
+ break;
+ case MATCH_EXACT:
+ errcode = (strcmp(pattern, fname) == 0) ? 1 : 0;
+ break;
+ case MATCH_ALL:
+ errcode = 1;
+ break;
+ default:
+ break;
+ }
+
+ /* loop over all appended conditions */
+ while (condition) {
+ /* restore the pattern */
+ *condition = condchar;
+ /* parse the condition (fun with bits) */
+ if (errcode == 1) {
+ char *nextcondition;
+ /* compare version numbers */
+ int match = 0;
+ if (*++condition == '=') {
+ match = 2;
+ condition++;
+ }
+ switch(condchar) {
+ case '<':
+ match |= 1;
+ break;
+ case '>':
+ match |= 4;
+ break;
+ case '=':
+ match |= 2;
+ break;
+ case '!':
+ match = 5;
+ break;
+ }
+ /* isolate the version number from the next condition ... */
+ nextcondition = strpbrk(condition, "<>=!");
+ if (nextcondition) {
+ condchar = *nextcondition;
+ *nextcondition = '\0';
+ }
+ /* and compare the versions (version_cmp removes the filename for us) */
+ if ((match & (1 << (version_cmp(pkgname, condition) + 1))) == 0)
+ errcode = 0;
+ condition = nextcondition;
+ } else {
+ break;
+ }
+ }
+
+ return errcode;
+}
+
+/*
+ * Synopsis is similar to matchinstalled(), but use origin
+ * as a key for matching packages.
+ */
+char ***
+matchallbyorigin(const char **origins, int *retval)
+{
+ char **installed, **allorigins = NULL;
+ char ***matches = NULL;
+ int i, j;
+
+ if (retval != NULL)
+ *retval = 0;
+
+ installed = matchinstalled(MATCH_ALL, NULL, retval);
+ if (installed == NULL)
+ return NULL;
+
+ /* Gather origins for all installed packages */
+ for (i = 0; installed[i] != NULL; i++) {
+ FILE *fp;
+ char *buf, *cp, tmp[PATH_MAX];
+ int cmd;
+
+ allorigins = realloc(allorigins, (i + 1) * sizeof(*allorigins));
+ allorigins[i] = NULL;
+
+ snprintf(tmp, PATH_MAX, "%s/%s", LOG_DIR, installed[i]);
+ /*
+ * SPECIAL CASE: ignore empty dirs, since we can can see them
+ * during port installation.
+ */
+ if (isemptydir(tmp))
+ continue;
+ strncat(tmp, "/" CONTENTS_FNAME, PATH_MAX);
+ fp = fopen(tmp, "r");
+ if (fp == NULL) {
+ warnx("the package info for package '%s' is corrupt", installed[i]);
+ continue;
+ }
+
+ cmd = -1;
+ while (fgets(tmp, sizeof(tmp), fp)) {
+ int len = strlen(tmp);
+
+ while (len && isspace(tmp[len - 1]))
+ tmp[--len] = '\0';
+ if (!len)
+ continue;
+ cp = tmp;
+ if (tmp[0] != CMD_CHAR)
+ continue;
+ cmd = plist_cmd(tmp + 1, &cp);
+ if (cmd == PLIST_ORIGIN) {
+ asprintf(&buf, "%s", cp);
+ allorigins[i] = buf;
+ break;
+ }
+ }
+ if (cmd != PLIST_ORIGIN && 0 != strncmp("bsdpan-", installed[i], 7))
+ warnx("package %s has no origin recorded", installed[i]);
+ fclose(fp);
+ }
+
+ /* Resolve origins into package names, retaining the sequence */
+ for (i = 0; origins[i] != NULL; i++) {
+ matches = realloc(matches, (i + 1) * sizeof(*matches));
+ struct store *store = NULL;
+ store = storecreate(store);
+
+ for (j = 0; installed[j] != NULL; j++) {
+ if (allorigins[j]) {
+ if (csh_match(origins[i], allorigins[j], FNM_PATHNAME) == 0) {
+ storeappend(store, installed[j]);
+ }
+ }
+ }
+ if (store->used == 0)
+ matches[i] = NULL;
+ else
+ matches[i] = store->store;
+ }
+
+ if (allorigins) {
+ for (i = 0; installed[i] != NULL; i++)
+ if (allorigins[i])
+ free(allorigins[i]);
+ free(allorigins);
+ }
+
+ return matches;
+}
+
+/*
+ * Synopsis is similar to matchinstalled(), but use origin
+ * as a key for matching packages.
+ */
+char **
+matchbyorigin(const char *origin, int *retval)
+{
+ const char *origins[2];
+ char ***tmp;
+
+ origins[0] = origin;
+ origins[1] = NULL;
+
+ tmp = matchallbyorigin(origins, retval);
+ if (tmp && tmp[0]) {
+ return tmp[0];
+ } else {
+ return NULL;
+ }
+}
+
+/*
+ * Small linked list to memoize results of isinstalledpkg(). A hash table
+ * would be faster but for n ~= 1000 may be overkill.
+ */
+struct iip_memo {
+ LIST_ENTRY(iip_memo) iip_link;
+ char *iip_name;
+ int iip_result;
+};
+LIST_HEAD(, iip_memo) iip_memo = LIST_HEAD_INITIALIZER(iip_memo);
+
+/*
+ *
+ * Return 1 if the specified package is installed,
+ * 0 if not, and -1 if an error occured.
+ */
+int
+isinstalledpkg(const char *name)
+{
+ int result;
+ char *buf, *buf2;
+ struct iip_memo *memo;
+
+ LIST_FOREACH(memo, &iip_memo, iip_link) {
+ if (strcmp(memo->iip_name, name) == 0)
+ return memo->iip_result;
+ }
+
+ buf2 = NULL;
+ asprintf(&buf, "%s/%s", LOG_DIR, name);
+ if (buf == NULL)
+ goto errout;
+ if (!isdir(buf) || access(buf, R_OK) == FAIL) {
+ result = 0;
+ } else {
+ asprintf(&buf2, "%s/%s", buf, CONTENTS_FNAME);
+ if (buf2 == NULL)
+ goto errout;
+
+ if (!isfile(buf2) || access(buf2, R_OK) == FAIL)
+ result = -1;
+ else
+ result = 1;
+ }
+
+ free(buf);
+ buf = strdup(name);
+ if (buf == NULL)
+ goto errout;
+ free(buf2);
+ buf2 = NULL;
+
+ memo = malloc(sizeof *memo);
+ if (memo == NULL)
+ goto errout;
+ memo->iip_name = buf;
+ memo->iip_result = result;
+ LIST_INSERT_HEAD(&iip_memo, memo, iip_link);
+ return result;
+
+errout:
+ if (buf != NULL)
+ free(buf);
+ if (buf2 != NULL)
+ free(buf2);
+ return -1;
+}
+
+/*
+ * Returns 1 if specified pkgname matches RE pattern.
+ * Otherwise returns 0 if doesn't match or -1 if RE
+ * engine reported an error (usually invalid syntax).
+ */
+static int
+rex_match(const char *pattern, const char *pkgname, int extended)
+{
+ char errbuf[128];
+ int errcode;
+ int retval;
+ regex_t rex;
+
+ retval = 0;
+
+ errcode = regcomp(&rex, pattern, (extended ? REG_EXTENDED : REG_BASIC) | REG_NOSUB);
+ if (errcode == 0)
+ errcode = regexec(&rex, pkgname, 0, NULL, 0);
+
+ if (errcode == 0) {
+ retval = 1;
+ } else if (errcode != REG_NOMATCH) {
+ regerror(errcode, &rex, errbuf, sizeof(errbuf));
+ warnx("%s: %s", pattern, errbuf);
+ retval = -1;
+ }
+
+ regfree(&rex);
+
+ return retval;
+}
+
+/*
+ * Match string by a csh-style glob pattern. Returns 0 on
+ * match and FNM_NOMATCH otherwise, to be compatible with
+ * fnmatch(3).
+ */
+static int
+csh_match(const char *pattern, const char *string, int flags)
+{
+ int ret = FNM_NOMATCH;
+
+
+ const char *nextchoice = pattern;
+ const char *current = NULL;
+
+ int prefixlen = -1;
+ int currentlen = 0;
+
+ int level = 0;
+
+ do {
+ const char *pos = nextchoice;
+ const char *postfix = NULL;
+
+ Boolean quoted = FALSE;
+
+ nextchoice = NULL;
+
+ do {
+ const char *eb;
+ if (!*pos) {
+ postfix = pos;
+ } else if (quoted) {
+ quoted = FALSE;
+ } else {
+ switch (*pos) {
+ case '{':
+ ++level;
+ if (level == 1) {
+ current = pos+1;
+ prefixlen = pos-pattern;
+ }
+ break;
+ case ',':
+ if (level == 1 && !nextchoice) {
+ nextchoice = pos+1;
+ currentlen = pos-current;
+ }
+ break;
+ case '}':
+ if (level == 1) {
+ postfix = pos+1;
+ if (!nextchoice)
+ currentlen = pos-current;
+ }
+ level--;
+ break;
+ case '[':
+ eb = pos+1;
+ if (*eb == '!' || *eb == '^')
+ eb++;
+ if (*eb == ']')
+ eb++;
+ while(*eb && *eb != ']')
+ eb++;
+ if (*eb)
+ pos=eb;
+ break;
+ case '\\':
+ quoted = TRUE;
+ break;
+ default:
+ ;
+ }
+ }
+ pos++;
+ } while (!postfix);
+
+ if (current) {
+ char buf[FILENAME_MAX];
+ snprintf(buf, sizeof(buf), "%.*s%.*s%s", prefixlen, pattern, currentlen, current, postfix);
+ ret = csh_match(buf, string, flags);
+ if (ret) {
+ current = nextchoice;
+ level = 1;
+ } else
+ current = NULL;
+ } else
+ ret = fnmatch(pattern, string, flags);
+ } while (current);
+
+ return ret;
+}
+
+/*
+ * Create an empty store, optionally deallocating
+ * any previously allocated space if store != NULL.
+ */
+struct store *
+storecreate(struct store *store)
+{
+ int i;
+
+ if (store == NULL) {
+ store = malloc(sizeof *store);
+ if (store == NULL) {
+ warnx("%s(): malloc() failed", __func__);
+ return NULL;
+ }
+ store->currlen = 0;
+ store->store = NULL;
+ } else if (store->store != NULL) {
+ /* Free previously allocated memory */
+ for (i = 0; store->store[i] != NULL; i++)
+ free(store->store[i]);
+ store->store[0] = NULL;
+ }
+ store->used = 0;
+
+ return store;
+}
+
+/*
+ * Append specified element to the provided store.
+ */
+static int
+storeappend(struct store *store, const char *item)
+{
+ if (store->used + 2 > store->currlen) {
+ store->currlen += 16;
+ store->store = reallocf(store->store,
+ store->currlen * sizeof(*(store->store)));
+ if (store->store == NULL) {
+ store->currlen = 0;
+ warnx("%s(): reallocf() failed", __func__);
+ return 1;
+ }
+ }
+
+ asprintf(&(store->store[store->used]), "%s", item);
+ if (store->store[store->used] == NULL) {
+ warnx("%s(): malloc() failed", __func__);
+ return 1;
+ }
+ store->used++;
+ store->store[store->used] = NULL;
+
+ return 0;
+}
+
+static int
+fname_cmp(const FTSENT * const *a, const FTSENT * const *b)
+{
+ return strcmp((*a)->fts_name, (*b)->fts_name);
+}
OpenPOWER on IntegriCloud