/* * FreeBSD install - a package for the installation and maintenance * 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 __FBSDID("$FreeBSD$"); #include "lib.h" #include #include #include #include /* * 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 && ( Verbose || 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 occurred. */ 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); }