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