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