mirror of
https://github.com/opnsense/src.git
synced 2026-05-28 04:12:45 -04:00
- Merge recently added into pkg_info(1) regex/glob matching functionality into
pkg_delete(1) as well; - add a new `-a' option for pkg_delete(1) to delete all installed packages; - add a new `-i' option for pkg_delete(1) to request simple rm(1)-like interactive confirmation before attempting to delete each package. Silently approved by: jkh, -ports
This commit is contained in:
parent
b1fe97412b
commit
29114b9283
9 changed files with 337 additions and 167 deletions
|
|
@ -24,10 +24,12 @@
|
|||
#define _INST_DELETE_H_INCLUDE
|
||||
|
||||
extern char *Prefix;
|
||||
extern Boolean NoDeInstall;
|
||||
extern Boolean CleanDirs;
|
||||
extern Boolean Interactive;
|
||||
extern Boolean NoDeInstall;
|
||||
extern Boolean Force;
|
||||
extern char *Directory;
|
||||
extern char *PkgName;
|
||||
extern match_t MatchType;
|
||||
|
||||
#endif /* _INST_DELETE_H_INCLUDE */
|
||||
|
|
|
|||
|
|
@ -30,11 +30,13 @@ static const char rcsid[] =
|
|||
#include "lib.h"
|
||||
#include "delete.h"
|
||||
|
||||
static char Options[] = "hvDdnfp:";
|
||||
static char Options[] = "adDfGhinp:vx";
|
||||
|
||||
char *Prefix = NULL;
|
||||
Boolean NoDeInstall = FALSE;
|
||||
Boolean CleanDirs = FALSE;
|
||||
Boolean Interactive = FALSE;
|
||||
Boolean NoDeInstall = FALSE;
|
||||
match_t MatchType = MATCH_GLOB;
|
||||
|
||||
static void usage __P((void));
|
||||
|
||||
|
|
@ -75,6 +77,22 @@ main(int argc, char **argv)
|
|||
Verbose = TRUE;
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
MatchType = MATCH_ALL;
|
||||
break;
|
||||
|
||||
case 'G':
|
||||
MatchType = MATCH_EXACT;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
MatchType = MATCH_REGEX;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
Interactive = TRUE;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
case '?':
|
||||
default:
|
||||
|
|
@ -87,23 +105,26 @@ main(int argc, char **argv)
|
|||
|
||||
/* Get all the remaining package names, if any */
|
||||
while (*argv) {
|
||||
while ((pkgs_split = strrchr(*argv, (int)'/')) != NULL) {
|
||||
*pkgs_split++ = '\0';
|
||||
/*
|
||||
* If character after the '/' is alphanumeric, then we've found the
|
||||
* package name. Otherwise we've come across a trailing '/' and
|
||||
* need to continue our quest.
|
||||
*/
|
||||
if (isalpha(*pkgs_split)) {
|
||||
*argv = pkgs_split;
|
||||
break;
|
||||
/* Don't try to apply heuristics if arguments are regexs */
|
||||
if (MatchType != MATCH_REGEX)
|
||||
while ((pkgs_split = strrchr(*argv, (int)'/')) != NULL) {
|
||||
*pkgs_split++ = '\0';
|
||||
/*
|
||||
* If character after the '/' is alphanumeric, then we've found the
|
||||
* package name. Otherwise we've come across a trailing '/' and
|
||||
* need to continue our quest.
|
||||
*/
|
||||
if (isalpha(*pkgs_split) || ((MatchType == MATCH_GLOB) && \
|
||||
strpbrk(pkgs_split, "*?[]") != NULL)) {
|
||||
*argv = pkgs_split;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*pkgs++ = *argv++;
|
||||
}
|
||||
|
||||
/* If no packages, yelp */
|
||||
if (pkgs == start)
|
||||
if (pkgs == start && MatchType != MATCH_ALL)
|
||||
warnx("missing package name(s)"), usage();
|
||||
*pkgs = NULL;
|
||||
tmp = getenv(PKG_DBDIR) ? getenv(PKG_DBDIR) : DEF_LOG_DIR;
|
||||
|
|
@ -126,6 +147,8 @@ main(int argc, char **argv)
|
|||
static void
|
||||
usage()
|
||||
{
|
||||
fprintf(stderr, "usage: pkg_delete [-vDdnf] [-p prefix] pkg-name ...\n");
|
||||
fprintf(stderr, "%s\n%s\n",
|
||||
"usage: pkg_delete [-dDfGinvx] [-p prefix] pkg-name ...",
|
||||
" pkg_delete -a [flags]");
|
||||
exit(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,10 +37,33 @@ static char LogDir[FILENAME_MAX];
|
|||
int
|
||||
pkg_perform(char **pkgs)
|
||||
{
|
||||
char **matched;
|
||||
char *tmp;
|
||||
int i, j;
|
||||
int err_cnt = 0;
|
||||
int loop_cnt;
|
||||
int loop_cnt, errcode;
|
||||
|
||||
if (MatchType != MATCH_EXACT) {
|
||||
matched = matchinstalled(MatchType, pkgs, &errcode);
|
||||
if (errcode != 0)
|
||||
return 1;
|
||||
/* Not reached */
|
||||
|
||||
if (matched != NULL)
|
||||
pkgs = matched;
|
||||
else switch (MatchType) {
|
||||
case MATCH_GLOB:
|
||||
break;
|
||||
case MATCH_ALL:
|
||||
warnx("no packages installed");
|
||||
return 0;
|
||||
case MATCH_REGEX:
|
||||
warnx("no packages match pattern(s)");
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; pkgs[i]; i++) {
|
||||
/*
|
||||
|
|
@ -122,6 +145,19 @@ pkg_do(char *pkg)
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (Interactive == TRUE) {
|
||||
int first, ch;
|
||||
|
||||
(void)fprintf(stderr, "delete %s? ", pkg);
|
||||
(void)fflush(stderr);
|
||||
first = ch = getchar();
|
||||
while (ch != '\n' && ch != EOF)
|
||||
ch = getchar();
|
||||
if (first != 'y' && first != 'Y')
|
||||
return 0;
|
||||
/* Not reached */
|
||||
}
|
||||
|
||||
if (!isemptyfile(REQUIRED_BY_FNAME)) {
|
||||
char buf[512];
|
||||
warnx("package `%s' is required by these other packages\n"
|
||||
|
|
|
|||
|
|
@ -25,9 +25,12 @@
|
|||
.Nd a utility for deleting previously installed software package distributions
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl vDdnf
|
||||
.Op Fl dDfGinvx
|
||||
.Op Fl p Ar prefix
|
||||
.Ar pkg-name ...
|
||||
.Nm
|
||||
.Fl a
|
||||
.Op Ar flags
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
|
|
@ -68,6 +71,12 @@ The following command line options are supported:
|
|||
.Bl -tag -width indent
|
||||
.It Ar pkg-name ...
|
||||
The named packages are deinstalled.
|
||||
.It Fl a
|
||||
Unconditionally delete all currently installed packages.
|
||||
.It Fl i
|
||||
Request confirmation before attempting to delete each package,
|
||||
regardless whether or not the standard input device is a
|
||||
terminal.
|
||||
.It Fl v
|
||||
Turn on verbose output.
|
||||
.It Fl D
|
||||
|
|
@ -94,6 +103,22 @@ the package.
|
|||
.It Fl f
|
||||
Force removal of the package, even if a dependency is recorded or the
|
||||
deinstall or require script fails.
|
||||
.It Fl G
|
||||
Do not try to expand shell glob patterns in the
|
||||
.Ar pkg-name
|
||||
when selecting packages to be deleted (by default
|
||||
.Nm
|
||||
automatically expands shell glob patterns in the
|
||||
.Ar pkg-name ) .
|
||||
.It Fl x
|
||||
Treat the
|
||||
.Ar pkg-name
|
||||
as a regular expression and delete all packages whose names match
|
||||
that regular expression. Multiple regular expressions could be
|
||||
provided, in that case
|
||||
.Nm
|
||||
deletes all packages that match at least one
|
||||
regular expression from the list.
|
||||
.El
|
||||
.Sh TECHNICAL DETAILS
|
||||
.Nm
|
||||
|
|
|
|||
|
|
@ -47,12 +47,6 @@
|
|||
#define SHOW_ORIGIN 0x2000
|
||||
#define SHOW_CKSUM 0x4000
|
||||
|
||||
enum _match_t {
|
||||
MATCH_ALL, MATCH_EXACT, MATCH_GLOB, MATCH_REGEX
|
||||
};
|
||||
|
||||
typedef enum _match_t match_t;
|
||||
|
||||
extern int Flags;
|
||||
extern Boolean Quiet;
|
||||
extern char *InfoPrefix;
|
||||
|
|
|
|||
|
|
@ -28,26 +28,20 @@ static const char rcsid[] =
|
|||
|
||||
#include <sys/types.h>
|
||||
#include <err.h>
|
||||
#include <glob.h>
|
||||
#include <fts.h>
|
||||
#include <regex.h>
|
||||
#include <signal.h>
|
||||
|
||||
static int fname_cmp(const FTSENT **, const FTSENT **);
|
||||
static int pkg_do(char *);
|
||||
static int rexs_match(char **, char *);
|
||||
|
||||
int
|
||||
pkg_perform(char **pkgs)
|
||||
{
|
||||
int i, err_cnt = 0;
|
||||
char **matched;
|
||||
char *tmp;
|
||||
int err_cnt = 0;
|
||||
int i, errcode;
|
||||
|
||||
signal(SIGINT, cleanup);
|
||||
|
||||
tmp = getenv(PKG_DBDIR);
|
||||
if (!tmp)
|
||||
tmp = DEF_LOG_DIR;
|
||||
/* Overriding action? */
|
||||
if (CheckPkg) {
|
||||
char buf[FILENAME_MAX];
|
||||
|
|
@ -57,90 +51,33 @@ pkg_perform(char **pkgs)
|
|||
/* Not reached */
|
||||
}
|
||||
|
||||
switch (MatchType) {
|
||||
case MATCH_ALL:
|
||||
case MATCH_REGEX:
|
||||
{
|
||||
FTS *ftsp;
|
||||
FTSENT *f;
|
||||
char *paths[2];
|
||||
int errcode;
|
||||
if (MatchType != MATCH_EXACT) {
|
||||
matched = matchinstalled(MatchType, pkgs, &errcode);
|
||||
if (errcode != 0)
|
||||
return 1;
|
||||
/* Not reached */
|
||||
|
||||
if (!isdir(tmp))
|
||||
if (matched != NULL)
|
||||
pkgs = matched;
|
||||
else switch (MatchType) {
|
||||
case MATCH_GLOB:
|
||||
break;
|
||||
case MATCH_ALL:
|
||||
warnx("no packages installed");
|
||||
return 0;
|
||||
/* Not reached */
|
||||
case MATCH_REGEX:
|
||||
warnx("no packages match pattern(s)");
|
||||
return 1;
|
||||
paths[0] = tmp;
|
||||
paths[1] = NULL;
|
||||
ftsp = fts_open(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);
|
||||
if (MatchType == MATCH_REGEX) {
|
||||
errcode = rexs_match(pkgs, f->fts_name);
|
||||
if (errcode == -1) {
|
||||
err_cnt += 1;
|
||||
break;
|
||||
}
|
||||
else if (errcode == 0)
|
||||
continue;
|
||||
}
|
||||
err_cnt += pkg_do(f->fts_name);
|
||||
}
|
||||
}
|
||||
fts_close(ftsp);
|
||||
}
|
||||
/* Not reached */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MATCH_GLOB:
|
||||
{
|
||||
glob_t g;
|
||||
char *gexpr;
|
||||
char *cp;
|
||||
int gflags;
|
||||
int prev_matchc;
|
||||
|
||||
gflags = GLOB_ERR;
|
||||
prev_matchc = 0;
|
||||
for (i = 0; pkgs[i]; i++) {
|
||||
asprintf(&gexpr, "%s/%s", tmp, pkgs[i]);
|
||||
|
||||
if (glob(gexpr, gflags, NULL, &g) != 0) {
|
||||
warn("%s: error encountered when matching glob", pkgs[i]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* If glob doesn't match try to use pkgs[i] directly - it
|
||||
* could be name of the tarball.
|
||||
*/
|
||||
if (g.gl_matchc == prev_matchc)
|
||||
err_cnt += pkg_do(pkgs[i]);
|
||||
|
||||
prev_matchc = g.gl_matchc;
|
||||
gflags |= GLOB_APPEND;
|
||||
free(gexpr);
|
||||
}
|
||||
|
||||
for (i = 0; i < g.gl_matchc; i++) {
|
||||
cp = strrchr(g.gl_pathv[i], '/');
|
||||
if (cp == NULL)
|
||||
cp = g.gl_pathv[i];
|
||||
else
|
||||
cp++;
|
||||
|
||||
err_cnt += pkg_do(cp);
|
||||
}
|
||||
|
||||
globfree(&g);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
for (i = 0; pkgs[i]; i++)
|
||||
err_cnt += pkg_do(pkgs[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; pkgs[i]; i++)
|
||||
err_cnt += pkg_do(pkgs[i]);
|
||||
|
||||
return err_cnt;
|
||||
}
|
||||
|
||||
|
|
@ -299,57 +236,3 @@ cleanup(int sig)
|
|||
exit(1);
|
||||
}
|
||||
|
||||
static int
|
||||
fname_cmp(const FTSENT **a, const FTSENT **b)
|
||||
{
|
||||
return strcmp((*a)->fts_name, (*b)->fts_name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns 1 if specified pkgname matches at least one
|
||||
* of the RE from patterns. Otherwise return 0 if no
|
||||
* matches were found or -1 if RE engine reported an
|
||||
* error (usually invalid syntax).
|
||||
*/
|
||||
static int
|
||||
rexs_match(char **patterns, char *pkgname)
|
||||
{
|
||||
Boolean matched;
|
||||
char errbuf[128];
|
||||
int i;
|
||||
int errcode;
|
||||
int retval;
|
||||
regex_t rex;
|
||||
|
||||
errcode = 0;
|
||||
retval = 0;
|
||||
matched = FALSE;
|
||||
for (i = 0; patterns[i]; i++) {
|
||||
errcode = regcomp(&rex, patterns[i], REG_BASIC | REG_NOSUB);
|
||||
if (errcode != 0)
|
||||
break;
|
||||
|
||||
errcode = regexec(&rex, pkgname, 0, NULL, 0);
|
||||
if (errcode == 0) {
|
||||
matched = TRUE;
|
||||
retval = 1;
|
||||
break;
|
||||
}
|
||||
else if (errcode != REG_NOMATCH)
|
||||
break;
|
||||
|
||||
regfree(&rex);
|
||||
errcode = 0;
|
||||
}
|
||||
|
||||
if (errcode != 0) {
|
||||
regerror(errcode, &rex, errbuf, sizeof(errbuf));
|
||||
warnx("%s: %s", patterns[i], errbuf);
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
if ((errcode != 0) || (matched == TRUE))
|
||||
regfree(&rex);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# $FreeBSD$
|
||||
|
||||
LIB= install
|
||||
SRCS= file.c msg.c plist.c str.c exec.c global.c pen.c
|
||||
SRCS= file.c msg.c plist.c str.c exec.c global.c pen.c match.c
|
||||
CFLAGS+= ${DEBUG}
|
||||
NOPROFILE= yes
|
||||
NOPIC= yes
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ enum _plist_t {
|
|||
};
|
||||
typedef enum _plist_t plist_t;
|
||||
|
||||
enum _match_t {
|
||||
MATCH_ALL, MATCH_EXACT, MATCH_GLOB, MATCH_REGEX
|
||||
};
|
||||
typedef enum _match_t match_t;
|
||||
|
||||
/* Types */
|
||||
typedef unsigned int Boolean;
|
||||
|
||||
|
|
@ -169,6 +174,9 @@ Boolean make_preserve_name(char *, int, char *, char *);
|
|||
/* For all */
|
||||
int pkg_perform(char **);
|
||||
|
||||
/* Query installed packages */
|
||||
char **matchinstalled(match_t, char **, int *);
|
||||
|
||||
/* Externs */
|
||||
extern Boolean Verbose;
|
||||
extern Boolean Fake;
|
||||
|
|
|
|||
197
usr.sbin/pkg_install/lib/match.c
Normal file
197
usr.sbin/pkg_install/lib/match.c
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
#ifndef lint
|
||||
static const char rcsid[] =
|
||||
"$FreeBSD$";
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 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 "lib.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(char *, char *);
|
||||
static void storeappend(struct store *, const char *);
|
||||
static int fname_cmp(const FTSENT **, const FTSENT **);
|
||||
|
||||
/*
|
||||
* Function to query names of installed packages.
|
||||
* MatchType - one of MATCH_ALL, MATCH_REGEX, MATCH_GLOB;
|
||||
* 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, matched, errcode;
|
||||
char *tmp;
|
||||
char *paths[2];
|
||||
static struct store *store = NULL;
|
||||
FTS *ftsp;
|
||||
FTSENT *f;
|
||||
|
||||
if (store == NULL) {
|
||||
store = malloc(sizeof *store);
|
||||
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->used = 0;
|
||||
|
||||
if (retval != NULL)
|
||||
*retval = 0;
|
||||
|
||||
tmp = getenv(PKG_DBDIR);
|
||||
if (!tmp)
|
||||
tmp = DEF_LOG_DIR;
|
||||
if (!isdir(tmp)) {
|
||||
if (retval != NULL)
|
||||
*retval = 1;
|
||||
return NULL;
|
||||
/* Not reached */
|
||||
}
|
||||
|
||||
paths[0] = tmp;
|
||||
paths[1] = NULL;
|
||||
ftsp = fts_open(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);
|
||||
if (MatchType == MATCH_ALL) {
|
||||
storeappend(store, f->fts_name);
|
||||
continue;
|
||||
}
|
||||
for (i = 0; patterns[i]; i++) {
|
||||
matched = 0;
|
||||
switch (MatchType) {
|
||||
case MATCH_REGEX:
|
||||
errcode = rex_match(patterns[i], f->fts_name);
|
||||
if (errcode == 1) {
|
||||
storeappend(store, f->fts_name);
|
||||
matched = 1;
|
||||
} else if (errcode == -1) {
|
||||
if (retval != NULL)
|
||||
*retval = 1;
|
||||
return NULL;
|
||||
/* Not reached */
|
||||
}
|
||||
break;
|
||||
case MATCH_GLOB:
|
||||
if (fnmatch(patterns[i], f->fts_name, 0) == 0) {
|
||||
storeappend(store, f->fts_name);
|
||||
matched = 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (matched == 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fts_close(ftsp);
|
||||
}
|
||||
|
||||
if (store->used == 0)
|
||||
return NULL;
|
||||
else
|
||||
return store->store;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(char *pattern, char *pkgname)
|
||||
{
|
||||
char errbuf[128];
|
||||
int errcode;
|
||||
int retval;
|
||||
regex_t rex;
|
||||
|
||||
retval = 0;
|
||||
|
||||
errcode = regcomp(&rex, pattern, 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;
|
||||
}
|
||||
|
||||
static void
|
||||
storeappend(struct store *store, const char *item)
|
||||
{
|
||||
char **tmp;
|
||||
|
||||
if (store->used + 2 > store->currlen) {
|
||||
tmp = store->store;
|
||||
store->currlen += 16;
|
||||
store->store = malloc(store->currlen * sizeof(*(store->store)));
|
||||
memcpy(store->store, tmp, store->used * sizeof(*(store->store)));
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
asprintf(&(store->store[store->used]), "%s", item);
|
||||
store->used++;
|
||||
store->store[store->used] = NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
fname_cmp(const FTSENT **a, const FTSENT **b)
|
||||
{
|
||||
return strcmp((*a)->fts_name, (*b)->fts_name);
|
||||
}
|
||||
Loading…
Reference in a new issue