- match package version numbers with relational operators

- use glob patterns when matching packages by origin

- csh-style {...} choices in glob matching

- pkg_info: new flag -E (list matching package names only)

- pkg_version: new flag -T (test if a given name matches a pattern)

- new flag -X (interpret pattern as an extended regular expression)

PR:		56961
This commit is contained in:
eik 2004-06-29 18:54:47 +00:00
parent 649576111e
commit 7923356ae6
13 changed files with 403 additions and 49 deletions

View File

@ -28,7 +28,7 @@ __FBSDID("$FreeBSD$");
#include "lib.h"
#include "delete.h"
static char Options[] = "adDfGhinp:rvx";
static char Options[] = "adDfGhinp:rvxX";
char *Prefix = NULL;
Boolean CleanDirs = FALSE;
@ -88,6 +88,10 @@ main(int argc, char **argv)
MatchType = MATCH_REGEX;
break;
case 'X':
MatchType = MATCH_EREGEX;
break;
case 'i':
Interactive = TRUE;
break;
@ -151,7 +155,7 @@ static void
usage()
{
fprintf(stderr, "%s\n%s\n",
"usage: pkg_delete [-dDfGinrvx] [-p prefix] pkg-name ...",
"usage: pkg_delete [-dDfGinrvxX] [-p prefix] pkg-name ...",
" pkg_delete -a [flags]");
exit(1);
}

View File

@ -64,6 +64,7 @@ pkg_perform(char **pkgs)
case MATCH_ALL:
warnx("no packages installed");
return 0;
case MATCH_EREGEX:
case MATCH_REGEX:
warnx("no packages match pattern(s)");
return 1;

View File

@ -17,7 +17,7 @@
.\" @(#)pkg_delete.1
.\" $FreeBSD$
.\"
.Dd November 25, 1994
.Dd June 29, 2004
.Dt PKG_DELETE 1
.Os
.Sh NAME
@ -25,7 +25,7 @@
.Nd a utility for deleting previously installed software package distributions
.Sh SYNOPSIS
.Nm
.Op Fl dDfGinrvx
.Op Fl dDfGinrvxX
.Op Fl p Ar prefix
.Ar pkg-name ...
.Nm
@ -119,6 +119,12 @@ provided, in that case
.Nm
deletes all packages that match at least one
regular expression from the list.
.It Fl X
Like
.Fl x ,
but treats the
.Ar pkg-name
as an extended regular expression.
.It Fl r
Recursive removal. In addition to specified packages, delete all
packages that depend on those packages as well.
@ -271,6 +277,7 @@ Default location of the installed package database.
.Sh AUTHORS
.An Jordan Hubbard
.Sh CONTRIBUTORS
.An John Kohl Aq jtk@rational.com
.An John Kohl Aq jtk@rational.com ,
.An Oliver Eikemeier Aq eik@FreeBSD.org
.Sh BUGS
Sure to be some.

View File

@ -50,6 +50,8 @@
#define SHOW_CKSUM 0x04000
#define SHOW_FMTREV 0x08000
#define SHOW_PTREV 0x10000
#define SHOW_DEPEND 0x20000
#define SHOW_PKGNAME 0x40000
struct which_entry {
TAILQ_ENTRY(which_entry) next;

View File

@ -26,7 +26,7 @@ __FBSDID("$FreeBSD$");
#include "info.h"
#include <err.h>
static char Options[] = "abcdDe:fgGhiIkl:LmoO:pPqQrRst:vVW:x";
static char Options[] = "abcdDe:EfgGhiIkl:LmoO:pPqQrRst:vVW:xX";
int Flags = 0;
match_t MatchType = MATCH_GLOB;
@ -75,6 +75,10 @@ main(int argc, char **argv)
SHOW_DEINSTALL | SHOW_REQUIRE | SHOW_DISPLAY | SHOW_MTREE;
break;
case 'E':
Flags |= SHOW_PKGNAME;
break;
case 'I':
Flags |= SHOW_INDEX;
break;
@ -170,6 +174,10 @@ main(int argc, char **argv)
MatchType = MATCH_REGEX;
break;
case 'X':
MatchType = MATCH_EREGEX;
break;
case 'e':
CheckPkg = optarg;
break;
@ -220,7 +228,7 @@ main(int argc, char **argv)
* Don't try to apply heuristics if arguments are regexs or if
* the argument refers to an existing file.
*/
if (MatchType != MATCH_REGEX && !isfile(*argv))
if (MatchType != MATCH_REGEX && MatchType != MATCH_EREGEX && !isfile(*argv))
while ((pkgs_split = strrchr(*argv, (int)'/')) != NULL) {
*pkgs_split++ = '\0';
/*
@ -250,7 +258,7 @@ static void
usage()
{
fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n",
"usage: pkg_info [-bcdDfgGiIkLmopPqQrRsvVx] [-e package] [-l prefix]",
"usage: pkg_info [-bcdDEfgGiIjLmopPqQrRsvVxX] [-e package] [-l prefix]",
" [-t template] -a | pkg-name ...",
" pkg_info [-qQ] -W filename",
" pkg_info [-qQ] -O origin",

View File

@ -31,6 +31,7 @@ static int find_pkg(struct which_head *);
static int cmp_path(const char *, const char *, const char *);
static char *abspath(const char *);
static int find_pkgs_by_origin(const char *);
static int matched_packages(char **pkgs);
int
pkg_perform(char **pkgs)
@ -42,7 +43,9 @@ pkg_perform(char **pkgs)
signal(SIGINT, cleanup);
/* Overriding action? */
if (CheckPkg) {
if (Flags & SHOW_PKGNAME) {
return matched_packages(pkgs);
} else if (CheckPkg) {
return isinstalledpkg(CheckPkg) == TRUE ? 0 : 1;
/* Not reached */
} else if (!TAILQ_EMPTY(whead)) {
@ -67,6 +70,7 @@ pkg_perform(char **pkgs)
return 0;
/* Not reached */
case MATCH_REGEX:
case MATCH_EREGEX:
warnx("no packages match pattern(s)");
return 1;
/* Not reached */
@ -451,3 +455,27 @@ find_pkgs_by_origin(const char *origin)
return 0;
}
/*
* List only the matching package names.
* Mainly intended for scripts.
*/
static int
matched_packages(char **pkgs)
{
char **matched;
int i, errcode;
matched = matchinstalled(MatchType == MATCH_GLOB ? MATCH_NGLOB : MatchType, pkgs, &errcode);
if (errcode != 0 || matched == NULL)
return 1;
for (i = 0; matched[i]; i++)
if (!Quiet)
printf("%s\n", matched[i]);
else if (QUIET)
printf("%s%s\n", InfoPrefix, matched[i]);
return 0;
}

View File

@ -25,7 +25,7 @@
.Nd a utility for displaying information on software packages
.Sh SYNOPSIS
.Nm
.Op Fl bcdDfgGiIkLmopPqQrRsvVx
.Op Fl bcdDEfgGiIkLmopPqQrRsvVxX
.Op Fl e Ar package
.Op Fl l Ar prefix
.Op Fl t Ar template
@ -54,6 +54,15 @@ The following command line options are supported:
The named packages are described. A package name may either be the name of
an installed package, the pathname to a package distribution file or a
URL to an FTP available package.
Package version numbers can also be matched in a relational manner using the
.Pa \*[Ge], \*[Le], \*[Gt]
and
.Pa \*[Lt]
operators. For example,
.Pa pkg_info 'portupgrade\*[Ge]20030723'
will match versions 20030723 and later of the
.Pa portupgrade
package.
.It Fl a
Show all currently installed packages.
.It Fl b
@ -140,12 +149,22 @@ expressions could be provided, in that case
.Nm
displays information about all packages that match at least one
regular expression from the list.
.It Fl X
Like
.Fl x ,
but treats the
.Ar pkg-name
as an extended regular expression.
.It Fl e Ar pkg-name
If the package identified by
.Ar pkg-name
is currently installed, return 0, otherwise return 1. This option
allows you to easily test for the presence of another (perhaps
prerequisite) package from a script.
.It Fl E
Show only matching package names. This option takes
precedence over all other package formatting options.
If any packages match, return 0, otherwise return 1.
.It Fl l Ar str
Prefix each information category header (see
.Fl q )
@ -236,6 +255,7 @@ Default location of the installed package database.
.Sh AUTHORS
.An Jordan Hubbard
.Sh CONTRIBUTORS
.An John Kohl Aq jtk@rational.com
.An John Kohl Aq jtk@rational.com ,
.An Oliver Eikemeier Aq eik@FreeBSD.org
.Sh BUGS
Sure to be some.

View File

@ -105,7 +105,7 @@ enum _plist_t {
typedef enum _plist_t plist_t;
enum _match_t {
MATCH_ALL, MATCH_EXACT, MATCH_GLOB, MATCH_REGEX
MATCH_ALL, MATCH_EXACT, MATCH_GLOB, MATCH_NGLOB, MATCH_EREGEX, MATCH_REGEX
};
typedef enum _match_t match_t;
@ -206,6 +206,7 @@ int real_main(int, char **);
char **matchinstalled(match_t, char **, int *);
char **matchbyorigin(const char *, int *);
int isinstalledpkg(const char *name);
int pattern_match(match_t MatchType, char *pattern, const char *pkgname);
/* Dependencies */
int sortdeps(char **);

View File

@ -37,14 +37,15 @@ struct store {
char **store;
};
static int rex_match(const char *, const char *);
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_REGEX, MATCH_GLOB;
* 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
@ -108,22 +109,11 @@ matchinstalled(match_t MatchType, char **patterns, int *retval)
matched = f->fts_name;
else
for (i = 0; patterns[i]; i++) {
switch (MatchType) {
case MATCH_REGEX:
errcode = rex_match(patterns[i], f->fts_name);
if (errcode == 1) {
matched = f->fts_name;
errcode = 0;
}
break;
case MATCH_GLOB:
if (fnmatch(patterns[i], f->fts_name, 0) == 0) {
matched = f->fts_name;
lmatched[i] = TRUE;
}
break;
default:
break;
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;
@ -153,6 +143,96 @@ matchinstalled(match_t MatchType, char **patterns, int *retval)
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.
@ -193,10 +273,8 @@ matchbyorigin(const char *origin, int *retval)
snprintf(tmp, PATH_MAX, "%s/%s", tmp, CONTENTS_FNAME);
fp = fopen(tmp, "r");
if (fp == NULL) {
warn("%s", tmp);
if (retval != NULL)
*retval = 1;
return NULL;
warnx("the package info for package '%s' is corrupt", installed[i]);
continue;
}
cmd = -1;
@ -212,7 +290,7 @@ matchbyorigin(const char *origin, int *retval)
continue;
cmd = plist_cmd(tmp + 1, &cp);
if (cmd == PLIST_ORIGIN) {
if (strcmp(origin, cp) == 0)
if (csh_match(origin, cp, FNM_PATHNAME) == 0)
storeappend(store, installed[i]);
break;
}
@ -255,7 +333,7 @@ isinstalledpkg(const char *name)
* engine reported an error (usually invalid syntax).
*/
static int
rex_match(const char *pattern, const char *pkgname)
rex_match(const char *pattern, const char *pkgname, int extended)
{
char errbuf[128];
int errcode;
@ -264,7 +342,7 @@ rex_match(const char *pattern, const char *pkgname)
retval = 0;
errcode = regcomp(&rex, pattern, REG_BASIC | REG_NOSUB);
errcode = regcomp(&rex, pattern, (extended ? REG_EXTENDED : REG_BASIC) | REG_NOSUB);
if (errcode == 0)
errcode = regexec(&rex, pkgname, 0, NULL, 0);
@ -281,6 +359,99 @@ rex_match(const char *pattern, const char *pkgname)
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.

View File

@ -25,11 +25,12 @@ __FBSDID("$FreeBSD$");
#include "version.h"
#include <err.h>
static char Options[] = "dhl:L:s:tv";
static char Options[] = "dhl:L:s:XtTv";
char *LimitChars = NULL;
char *PreventChars = NULL;
char *MatchName = NULL;
Boolean RegexExtended = FALSE;
static void usage __P((void));
@ -43,6 +44,10 @@ main(int argc, char **argv)
printf(cmp > 0 ? ">\n" : (cmp < 0 ? "<\n" : "=\n"));
exit(0);
}
else if (argc == 4 && !strcmp(argv[1], "-T")) {
cmp = version_match(argv[3], argv[2]);
exit(cmp == 1 ? 0 : 1);
}
else while ((ch = getopt(argc, argv, Options)) != -1) {
switch(ch) {
case 'v':
@ -65,6 +70,14 @@ main(int argc, char **argv)
errx(2, "Invalid -t usage.");
break;
case 'T':
errx(2, "Invalid -T usage.");
break;
case 'X':
RegexExtended = TRUE;
break;
case 'h':
case '?':
default:
@ -82,8 +95,9 @@ main(int argc, char **argv)
static void
usage()
{
fprintf(stderr, "%s\n%s\n",
"usage: pkg_version [-hv] [-l limchar] [-L limchar] [-s string] index",
" pkg_version -t v1 v2");
fprintf(stderr, "%s\n%s\n%s\n",
"usage: pkg_version [-hv] [-l limchar] [-L limchar] [[-X] -s string] index",
" pkg_version -t v1 v2",
" pkg_version -T name pattern");
exit(1);
}

View File

@ -67,7 +67,7 @@ pkg_perform(char **indexarg)
if (MatchName != NULL) {
pat[0] = MatchName;
pat[1] = NULL;
MatchType = MATCH_REGEX;
MatchType = RegexExtended ? MATCH_EREGEX : MATCH_REGEX;
patterns = pat;
}
else {
@ -83,6 +83,7 @@ pkg_perform(char **indexarg)
case MATCH_ALL:
warnx("no packages installed");
return (0);
case MATCH_EREGEX:
case MATCH_REGEX:
warnx("no packages match pattern");
return (1);
@ -308,6 +309,80 @@ show_version(const char *installed, const char *latest, const char *source)
}
}
int
version_match(char *pattern, const char *pkgname)
{
int ret = 0;
int matchstream = 0;
FILE *fp = NULL;
Boolean isTMP = FALSE;
if (isURL(pkgname)) {
fp = fetchGetURL(pkgname, "");
isTMP = TRUE;
matchstream = 1;
if (fp == NULL)
errx(2, "Unable to open %s.", pkgname);
} else if (pkgname[0] == '/') {
fp = fopen(pkgname, "r");
isTMP = TRUE;
matchstream = 1;
if (fp == NULL)
errx(2, "Unable to open %s.", pkgname);
} else if (strcmp(pkgname, "-") == 0) {
fp = stdin;
matchstream = 1;
} else if (isURL(pattern)) {
fp = fetchGetURL(pattern, "");
isTMP = TRUE;
matchstream = -1;
if (fp == NULL)
errx(2, "Unable to open %s.", pattern);
} else if (pattern[0] == '/') {
fp = fopen(pattern, "r");
isTMP = TRUE;
matchstream = -1;
if (fp == NULL)
errx(2, "Unable to open %s.", pattern);
} else if (strcmp(pattern, "-") == 0) {
fp = stdin;
matchstream = -1;
} else {
ret = pattern_match(MATCH_GLOB, pattern, pkgname);
}
if (fp != NULL) {
size_t len;
char *line;
while ((line = fgetln(fp, &len)) != NULL) {
int match;
char *ch, ln[2048];
size_t lnlen;
if (len > 0 && line[len-1] == '\n')
len --;
lnlen = len;
if (lnlen > sizeof(ln)-1)
lnlen = sizeof(ln)-1;
memcpy(ln, line, lnlen);
ln[lnlen] = '\0';
if ((ch = strchr(ln, '|')) != NULL)
ch[0] = '\0';
if (matchstream > 0)
match = pattern_match(MATCH_GLOB, pattern, ln);
else
match = pattern_match(MATCH_GLOB, ln, pkgname);
if (match == 1) {
ret = 1;
printf("%.*s\n", (int)len, line);
}
}
if (isTMP)
fclose(fp);
}
return ret;
}
void
cleanup(int sig)
{

View File

@ -24,7 +24,7 @@
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.\" $FreeBSD$
.Dd July 17, 1998
.Dd June 29, 2004
.Dt PKG_VERSION 1
.Os
.Sh NAME
@ -35,10 +35,15 @@
.Op Fl hv
.Op Fl l Ar limchar
.Op Fl L Ar limchar
.Op Fl s Ar string
.Oo
.Op Fl X
.Fl s Ar string
.Oc
.Op Ar index
.Nm
.Op Fl t Ar version1 version2
.Nm
.Op Fl T Ar pkgname pattern
.Sh DESCRIPTION
The
.Nm
@ -134,6 +139,10 @@ with single quotes.
.It Fl s
Limit the output to those packages whose names match a given
.Ar string .
.It Fl X
Interpret
.Ar string
as a extended regular expression.
.It Fl t
Test a pair of version number strings and exit.
The output consists of one of the single characters
@ -144,6 +153,16 @@ The output consists of one of the single characters
.Li \&>
(left-hand number greater) on standard output.
This flag is mostly useful for scripts or for testing.
.It Fl T
Test whether
.Ar pkgname
is matched by
.Ar pattern
and set the exit code accordingly.
.Fl T
can also be used in `filter mode':
When one of the arguments is `-', standard input is used, and lines
with matching package names/patterns are echoed to standard output.
.It Fl v
Enable verbose output. Verbose output includes some English-text
interpretations of the version number comparisons, as well as the
@ -157,7 +176,7 @@ URL understandable by
can be used here. If no
.Ar index
file is specified on the command line,
.Pa /usr/ports/INDEX
.Pa /usr/ports/INDEX-5
is used.
.El
.Sh COMPATIBILITY
@ -171,8 +190,8 @@ option has been deprecated and is no longer supported.
.Xr pkg_delete 1 ,
.Xr pkg_info 1
.Sh FILES
.Bl -tag -width /usr/ports/INDEX -compact
.It Pa /usr/ports/INDEX
.Bl -tag -width /usr/ports/INDEX-5 -compact
.It Pa /usr/ports/INDEX-5
Default index file.
.El
.Sh EXAMPLES
@ -186,7 +205,7 @@ index file:
The command below generates a report against
the version numbers in the on-line ports collection:
.Pp
.Dl % pkg_version ftp://ftp.FreeBSD.org/pub/FreeBSD/branches/-current/ports/INDEX
.Dl % pkg_version http://www.FreeBSD.org/ports/INDEX-5
.Pp
The following command compares two package version strings:
.Pp
@ -203,4 +222,5 @@ partially based on a Perl script written by
.An Dominic Mitchell Aq dom@palmerharvey.co.uk ,
.An Mark Ovens Aq marko@FreeBSD.org ,
.An Doug Barton Aq DougB@gorean.org ,
.An Akinori MUSHA Aq knu@FreeBSD.org
.An Akinori MUSHA Aq knu@FreeBSD.org ,
.An Oliver Eikemeier Aq eik@FreeBSD.org

View File

@ -40,5 +40,8 @@ SLIST_HEAD(index_head, index_entry);
extern char *LimitChars;
extern char *PreventChars;
extern char *MatchName;
extern Boolean RegexExtended;
extern int version_match(char *, const char *);
#endif /* _INST_VERSION_H_INCLUDE */