173134Ssobomax/* 2228990Suqs * FreeBSD install - a package for the installation and maintenance 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$"); 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; 66131285Seik Boolean *lmatched = NULL; 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 */ 240178753Spavchar *** 241178103Spavmatchallbyorigin(const char **origins, int *retval) 24296613Ssobomax{ 243178753Spav char **installed, **allorigins = NULL; 244178753Spav char ***matches = NULL; 245178103Spav int i, j; 24696613Ssobomax 24796613Ssobomax if (retval != NULL) 24896613Ssobomax *retval = 0; 24996613Ssobomax 25096613Ssobomax installed = matchinstalled(MATCH_ALL, NULL, retval); 25196613Ssobomax if (installed == NULL) 25296613Ssobomax return NULL; 25396613Ssobomax 254178103Spav /* Gather origins for all installed packages */ 25596613Ssobomax for (i = 0; installed[i] != NULL; i++) { 25696613Ssobomax FILE *fp; 257178103Spav char *buf, *cp, tmp[PATH_MAX]; 25896613Ssobomax int cmd; 25996613Ssobomax 260178103Spav allorigins = realloc(allorigins, (i + 1) * sizeof(*allorigins)); 261178103Spav allorigins[i] = NULL; 262178103Spav 26396613Ssobomax snprintf(tmp, PATH_MAX, "%s/%s", LOG_DIR, installed[i]); 26496613Ssobomax /* 26596613Ssobomax * SPECIAL CASE: ignore empty dirs, since we can can see them 26696613Ssobomax * during port installation. 26796613Ssobomax */ 26896613Ssobomax if (isemptydir(tmp)) 26996613Ssobomax continue; 270206043Sflz strncat(tmp, "/" CONTENTS_FNAME, PATH_MAX); 27196613Ssobomax fp = fopen(tmp, "r"); 27296613Ssobomax if (fp == NULL) { 273131275Seik warnx("the package info for package '%s' is corrupt", installed[i]); 274131275Seik continue; 27596613Ssobomax } 27696613Ssobomax 27796613Ssobomax cmd = -1; 27896613Ssobomax while (fgets(tmp, sizeof(tmp), fp)) { 27996613Ssobomax int len = strlen(tmp); 28096613Ssobomax 28196613Ssobomax while (len && isspace(tmp[len - 1])) 28296613Ssobomax tmp[--len] = '\0'; 28396613Ssobomax if (!len) 28496613Ssobomax continue; 28596613Ssobomax cp = tmp; 28696613Ssobomax if (tmp[0] != CMD_CHAR) 28796613Ssobomax continue; 28896613Ssobomax cmd = plist_cmd(tmp + 1, &cp); 28996613Ssobomax if (cmd == PLIST_ORIGIN) { 290178103Spav asprintf(&buf, "%s", cp); 291178103Spav allorigins[i] = buf; 29296613Ssobomax break; 29396613Ssobomax } 29496613Ssobomax } 295150530Skrion if (cmd != PLIST_ORIGIN && ( Verbose || 0 != strncmp("bsdpan-", installed[i], 7 ) ) ) 29696613Ssobomax warnx("package %s has no origin recorded", installed[i]); 29796613Ssobomax fclose(fp); 29896613Ssobomax } 29996613Ssobomax 300178103Spav /* Resolve origins into package names, retaining the sequence */ 301178103Spav for (i = 0; origins[i] != NULL; i++) { 302178103Spav matches = realloc(matches, (i + 1) * sizeof(*matches)); 303178753Spav struct store *store = NULL; 304178753Spav store = storecreate(store); 305178103Spav 306178103Spav for (j = 0; installed[j] != NULL; j++) { 307178103Spav if (allorigins[j]) { 308178103Spav if (csh_match(origins[i], allorigins[j], FNM_PATHNAME) == 0) { 309178753Spav storeappend(store, installed[j]); 310178103Spav } 311178103Spav } 312178103Spav } 313178753Spav if (store->used == 0) 314178753Spav matches[i] = NULL; 315178753Spav else 316178753Spav matches[i] = store->store; 317178103Spav } 318178103Spav 319178103Spav if (allorigins) { 320178103Spav for (i = 0; installed[i] != NULL; i++) 321178103Spav if (allorigins[i]) 322178103Spav free(allorigins[i]); 323178103Spav free(allorigins); 324178103Spav } 325178103Spav 326178103Spav return matches; 327178103Spav} 328178103Spav 329178103Spav/* 330178103Spav * Synopsis is similar to matchinstalled(), but use origin 331178103Spav * as a key for matching packages. 332178103Spav */ 333178103Spavchar ** 334178103Spavmatchbyorigin(const char *origin, int *retval) 335178103Spav{ 336178103Spav const char *origins[2]; 337178753Spav char ***tmp; 338178103Spav 339178103Spav origins[0] = origin; 340178103Spav origins[1] = NULL; 341178103Spav 342178103Spav tmp = matchallbyorigin(origins, retval); 343178103Spav if (tmp && tmp[0]) { 344178753Spav return tmp[0]; 345178103Spav } else { 34696613Ssobomax return NULL; 347178103Spav } 34896613Ssobomax} 34996613Ssobomax 35096613Ssobomax/* 351173291Skrion * Small linked list to memoize results of isinstalledpkg(). A hash table 352173291Skrion * would be faster but for n ~= 1000 may be overkill. 353173291Skrion */ 354173291Skrionstruct iip_memo { 355173291Skrion LIST_ENTRY(iip_memo) iip_link; 356173291Skrion char *iip_name; 357173291Skrion int iip_result; 358173291Skrion}; 359173291SkrionLIST_HEAD(, iip_memo) iip_memo = LIST_HEAD_INITIALIZER(iip_memo); 360173291Skrion 361173291Skrion/* 362131280Seik * 363131280Seik * Return 1 if the specified package is installed, 364228990Suqs * 0 if not, and -1 if an error occurred. 36596613Ssobomax */ 36696613Ssobomaxint 36796613Ssobomaxisinstalledpkg(const char *name) 36896613Ssobomax{ 369173291Skrion int result; 370173291Skrion char *buf, *buf2; 371173291Skrion struct iip_memo *memo; 37296613Ssobomax 373173291Skrion LIST_FOREACH(memo, &iip_memo, iip_link) { 374173291Skrion if (strcmp(memo->iip_name, name) == 0) 375173291Skrion return memo->iip_result; 376173291Skrion } 377173291Skrion 378173291Skrion buf2 = NULL; 379173291Skrion asprintf(&buf, "%s/%s", LOG_DIR, name); 380173291Skrion if (buf == NULL) 381173291Skrion goto errout; 382173291Skrion if (!isdir(buf) || access(buf, R_OK) == FAIL) { 383173291Skrion result = 0; 384173291Skrion } else { 385173291Skrion asprintf(&buf2, "%s/%s", buf, CONTENTS_FNAME); 386173291Skrion if (buf2 == NULL) 387173291Skrion goto errout; 38896613Ssobomax 389173291Skrion if (!isfile(buf2) || access(buf2, R_OK) == FAIL) 390173291Skrion result = -1; 391173291Skrion else 392173291Skrion result = 1; 393173291Skrion } 39496613Ssobomax 395173291Skrion free(buf); 396173291Skrion buf = strdup(name); 397173291Skrion if (buf == NULL) 398173291Skrion goto errout; 399173291Skrion free(buf2); 400173291Skrion buf2 = NULL; 401173291Skrion 402173291Skrion memo = malloc(sizeof *memo); 403173291Skrion if (memo == NULL) 404173291Skrion goto errout; 405173291Skrion memo->iip_name = buf; 406173291Skrion memo->iip_result = result; 407173291Skrion LIST_INSERT_HEAD(&iip_memo, memo, iip_link); 408173291Skrion return result; 409173291Skrion 410173291Skrionerrout: 411173291Skrion if (buf != NULL) 412173291Skrion free(buf); 413173291Skrion if (buf2 != NULL) 414173291Skrion free(buf2); 415173291Skrion return -1; 41696613Ssobomax} 41796613Ssobomax 41896613Ssobomax/* 41973134Ssobomax * Returns 1 if specified pkgname matches RE pattern. 42073134Ssobomax * Otherwise returns 0 if doesn't match or -1 if RE 42173134Ssobomax * engine reported an error (usually invalid syntax). 42273134Ssobomax */ 42373134Ssobomaxstatic int 424131275Seikrex_match(const char *pattern, const char *pkgname, int extended) 42573134Ssobomax{ 42673134Ssobomax char errbuf[128]; 42773134Ssobomax int errcode; 42873134Ssobomax int retval; 42973134Ssobomax regex_t rex; 43073134Ssobomax 43173134Ssobomax retval = 0; 43273134Ssobomax 433131275Seik errcode = regcomp(&rex, pattern, (extended ? REG_EXTENDED : REG_BASIC) | REG_NOSUB); 43473134Ssobomax if (errcode == 0) 43573134Ssobomax errcode = regexec(&rex, pkgname, 0, NULL, 0); 43673134Ssobomax 43773134Ssobomax if (errcode == 0) { 43873134Ssobomax retval = 1; 43973134Ssobomax } else if (errcode != REG_NOMATCH) { 44073134Ssobomax regerror(errcode, &rex, errbuf, sizeof(errbuf)); 44173134Ssobomax warnx("%s: %s", pattern, errbuf); 44273134Ssobomax retval = -1; 44373134Ssobomax } 44473134Ssobomax 44573134Ssobomax regfree(&rex); 44673134Ssobomax 44773134Ssobomax return retval; 44873134Ssobomax} 44973134Ssobomax 45096613Ssobomax/* 451131275Seik * Match string by a csh-style glob pattern. Returns 0 on 452131275Seik * match and FNM_NOMATCH otherwise, to be compatible with 453131275Seik * fnmatch(3). 454131275Seik */ 455131275Seikstatic int 456131275Seikcsh_match(const char *pattern, const char *string, int flags) 457131275Seik{ 458131275Seik int ret = FNM_NOMATCH; 459131275Seik 460131275Seik 461131275Seik const char *nextchoice = pattern; 462131275Seik const char *current = NULL; 463131275Seik 464131275Seik int prefixlen = -1; 465131275Seik int currentlen = 0; 466131275Seik 467131275Seik int level = 0; 468131275Seik 469131275Seik do { 470131275Seik const char *pos = nextchoice; 471131275Seik const char *postfix = NULL; 472131275Seik 473131275Seik Boolean quoted = FALSE; 474131275Seik 475131275Seik nextchoice = NULL; 476131275Seik 477131275Seik do { 478131275Seik const char *eb; 479131275Seik if (!*pos) { 480131275Seik postfix = pos; 481131275Seik } else if (quoted) { 482131275Seik quoted = FALSE; 483131275Seik } else { 484131275Seik switch (*pos) { 485131275Seik case '{': 486131275Seik ++level; 487131275Seik if (level == 1) { 488131275Seik current = pos+1; 489131275Seik prefixlen = pos-pattern; 490131275Seik } 491131275Seik break; 492131275Seik case ',': 493131275Seik if (level == 1 && !nextchoice) { 494131275Seik nextchoice = pos+1; 495131275Seik currentlen = pos-current; 496131275Seik } 497131275Seik break; 498131275Seik case '}': 499131275Seik if (level == 1) { 500131275Seik postfix = pos+1; 501131275Seik if (!nextchoice) 502131275Seik currentlen = pos-current; 503131275Seik } 504131275Seik level--; 505131275Seik break; 506131275Seik case '[': 507131275Seik eb = pos+1; 508131275Seik if (*eb == '!' || *eb == '^') 509131275Seik eb++; 510131275Seik if (*eb == ']') 511131275Seik eb++; 512131275Seik while(*eb && *eb != ']') 513131275Seik eb++; 514131275Seik if (*eb) 515131275Seik pos=eb; 516131275Seik break; 517131275Seik case '\\': 518131275Seik quoted = TRUE; 519131275Seik break; 520131275Seik default: 521131275Seik ; 522131275Seik } 523131275Seik } 524131275Seik pos++; 525131275Seik } while (!postfix); 526131275Seik 527131275Seik if (current) { 528131275Seik char buf[FILENAME_MAX]; 529131275Seik snprintf(buf, sizeof(buf), "%.*s%.*s%s", prefixlen, pattern, currentlen, current, postfix); 530131275Seik ret = csh_match(buf, string, flags); 531131275Seik if (ret) { 532131275Seik current = nextchoice; 533131275Seik level = 1; 534131275Seik } else 535131275Seik current = NULL; 536131275Seik } else 537131275Seik ret = fnmatch(pattern, string, flags); 538131275Seik } while (current); 539131275Seik 540131275Seik return ret; 541131275Seik} 542131275Seik 543131275Seik/* 54496613Ssobomax * Create an empty store, optionally deallocating 54596613Ssobomax * any previously allocated space if store != NULL. 54696613Ssobomax */ 54796613Ssobomaxstruct store * 54896613Ssobomaxstorecreate(struct store *store) 54996613Ssobomax{ 55096613Ssobomax int i; 55196613Ssobomax 55296613Ssobomax if (store == NULL) { 55396613Ssobomax store = malloc(sizeof *store); 55496613Ssobomax if (store == NULL) { 55596613Ssobomax warnx("%s(): malloc() failed", __func__); 55696613Ssobomax return NULL; 55796613Ssobomax } 55896613Ssobomax store->currlen = 0; 55996613Ssobomax store->store = NULL; 56097097Ssobomax } else if (store->store != NULL) { 56196613Ssobomax /* Free previously allocated memory */ 56296613Ssobomax for (i = 0; store->store[i] != NULL; i++) 56396613Ssobomax free(store->store[i]); 56497097Ssobomax store->store[0] = NULL; 56597097Ssobomax } 56696613Ssobomax store->used = 0; 56796613Ssobomax 56896613Ssobomax return store; 56996613Ssobomax} 57096613Ssobomax 57196613Ssobomax/* 57296613Ssobomax * Append specified element to the provided store. 57396613Ssobomax */ 57473144Ssobomaxstatic int 57573134Ssobomaxstoreappend(struct store *store, const char *item) 57673134Ssobomax{ 57773134Ssobomax if (store->used + 2 > store->currlen) { 57873134Ssobomax store->currlen += 16; 57980042Ssobomax store->store = reallocf(store->store, 58080042Ssobomax store->currlen * sizeof(*(store->store))); 58173144Ssobomax if (store->store == NULL) { 58280042Ssobomax store->currlen = 0; 58396392Salfred warnx("%s(): reallocf() failed", __func__); 58473144Ssobomax return 1; 58573144Ssobomax } 58673134Ssobomax } 58773134Ssobomax 58873134Ssobomax asprintf(&(store->store[store->used]), "%s", item); 58973144Ssobomax if (store->store[store->used] == NULL) { 59096392Salfred warnx("%s(): malloc() failed", __func__); 59173144Ssobomax return 1; 59273144Ssobomax } 59373134Ssobomax store->used++; 59473134Ssobomax store->store[store->used] = NULL; 59573144Ssobomax 59673144Ssobomax return 0; 59773134Ssobomax} 59873134Ssobomax 59973134Ssobomaxstatic int 600103726Swollmanfname_cmp(const FTSENT * const *a, const FTSENT * const *b) 60173134Ssobomax{ 60273134Ssobomax return strcmp((*a)->fts_name, (*b)->fts_name); 60373134Ssobomax} 604