Index: head/usr.sbin/pkg_install/delete/main.c =================================================================== --- head/usr.sbin/pkg_install/delete/main.c (revision 131274) +++ head/usr.sbin/pkg_install/delete/main.c (revision 131275) @@ -1,157 +1,161 @@ /* * * 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. * * Jordan K. Hubbard * 18 July 1993 * * This is the delete module. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include "lib.h" #include "delete.h" -static char Options[] = "adDfGhinp:rvx"; +static char Options[] = "adDfGhinp:rvxX"; char *Prefix = NULL; Boolean CleanDirs = FALSE; Boolean Interactive = FALSE; Boolean NoDeInstall = FALSE; Boolean Recursive = FALSE; match_t MatchType = MATCH_GLOB; static void usage __P((void)); int main(int argc, char **argv) { int ch, error; char **pkgs, **start; char *pkgs_split; const char *tmp; struct stat stat_s; pkgs = start = argv; while ((ch = getopt(argc, argv, Options)) != -1) switch(ch) { case 'v': Verbose = TRUE; break; case 'f': Force = TRUE; break; case 'p': Prefix = optarg; break; case 'D': NoDeInstall = TRUE; break; case 'd': CleanDirs = TRUE; break; case 'n': Fake = TRUE; Verbose = TRUE; break; case 'a': MatchType = MATCH_ALL; break; case 'G': MatchType = MATCH_EXACT; break; case 'x': MatchType = MATCH_REGEX; break; + case 'X': + MatchType = MATCH_EREGEX; + break; + case 'i': Interactive = TRUE; break; case 'r': Recursive = TRUE; break; case 'h': case '?': default: usage(); break; } argc -= optind; argv += optind; /* Get all the remaining package names, if any */ while (*argv) { /* 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 && MatchType != MATCH_ALL) warnx("missing package name(s)"), usage(); *pkgs = NULL; tmp = LOG_DIR; (void) stat(tmp, &stat_s); if (!Fake && getuid() && geteuid() != stat_s.st_uid) { if (!Force) errx(1, "you do not own %s, use -f to force", tmp); else warnx("you do not own %s (proceeding anyways)", tmp); } if ((error = pkg_perform(start)) != 0) { if (Verbose) warnx("%d package deletion(s) failed", error); return error; } else return 0; } 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); } Index: head/usr.sbin/pkg_install/delete/perform.c =================================================================== --- head/usr.sbin/pkg_install/delete/perform.c (revision 131274) +++ head/usr.sbin/pkg_install/delete/perform.c (revision 131275) @@ -1,381 +1,382 @@ /* * 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. * * Jordan K. Hubbard * 18 July 1993 * * This is the main body of the delete module. * */ #include __FBSDID("$FreeBSD$"); #include #include "lib.h" #include "delete.h" static int pkg_do(char *); static void sanity_check(char *); static void undepend(char *, char *); static char LogDir[FILENAME_MAX]; int pkg_perform(char **pkgs) { char **matched, **rb, **rbtmp; int errcode, i, j; int err_cnt = 0; struct reqr_by_entry *rb_entry; struct reqr_by_head *rb_list; if (MatchType != MATCH_EXACT) { matched = matchinstalled(MatchType, pkgs, &errcode); if (errcode != 0) return 1; /* Not reached */ /* * Copy matched[] into pkgs[], because we'll need to use * matchinstalled() later on. */ if (matched != NULL) { pkgs = NULL; for (i = 0; matched[i] != NULL; i++) { pkgs = realloc(pkgs, sizeof(*pkgs) * (i + 2)); pkgs[i] = strdup(matched[i]); } pkgs[i] = NULL; } else switch (MatchType) { case MATCH_GLOB: break; case MATCH_ALL: warnx("no packages installed"); return 0; + case MATCH_EREGEX: case MATCH_REGEX: warnx("no packages match pattern(s)"); return 1; default: break; } } err_cnt += sortdeps(pkgs); for (i = 0; pkgs[i]; i++) { if (Recursive == TRUE) { errcode = requiredby(pkgs[i], &rb_list, FALSE, TRUE); if (errcode < 0) { err_cnt++; } else if (errcode > 0) { /* * Copy values from the rb_list queue into argv-like NULL * terminated list because requiredby() uses some static * storage, while pkg_do() below will call this function, * thus blowing our rb_list away. */ rbtmp = rb = alloca((errcode + 1) * sizeof(*rb)); if (rb == NULL) { warnx("%s(): alloca() failed", __func__); err_cnt++; continue; } STAILQ_FOREACH(rb_entry, rb_list, link) { *rbtmp = alloca(strlen(rb_entry->pkgname) + 1); if (*rbtmp == NULL) { warnx("%s(): alloca() failed", __func__); err_cnt++; continue; } strcpy(*rbtmp, rb_entry->pkgname); rbtmp++; } *rbtmp = NULL; err_cnt += sortdeps(rb); for (j = 0; rb[j]; j++) err_cnt += pkg_do(rb[j]); } } err_cnt += pkg_do(pkgs[i]); } return err_cnt; } static Package Plist; /* This is seriously ugly code following. Written very fast! */ static int pkg_do(char *pkg) { FILE *cfile; char *deporigin, **depnames, home[FILENAME_MAX]; PackingList p; int i, len; /* support for separate pre/post install scripts */ int new_m = 0; const char *pre_script = DEINSTALL_FNAME; const char *post_script, *pre_arg, *post_arg; struct reqr_by_entry *rb_entry; struct reqr_by_head *rb_list; if (!pkg || !(len = strlen(pkg))) return 1; if (pkg[len - 1] == '/') pkg[len - 1] = '\0'; /* Reset some state */ if (Plist.head) free_plist(&Plist); if (!isinstalledpkg(pkg)) { warnx("no such package '%s' installed", pkg); return 1; } if (!getcwd(home, FILENAME_MAX)) { cleanup(0); errx(2, "%s: unable to get current working directory!", __func__); } sprintf(LogDir, "%s/%s", LOG_DIR, pkg); if (chdir(LogDir) == FAIL) { warnx("unable to change directory to %s! deinstall failed", LogDir); 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 (requiredby(pkg, &rb_list, FALSE, TRUE) < 0) return 1; if (!STAILQ_EMPTY(rb_list)) { warnx("package '%s' is required by these other packages\n" "and may not be deinstalled%s:", pkg, Force ? " (but I'll delete it anyway)" : ""); STAILQ_FOREACH(rb_entry, rb_list, link) fprintf(stderr, "%s\n", rb_entry->pkgname); if (!Force) return 1; } sanity_check(LogDir); cfile = fopen(CONTENTS_FNAME, "r"); if (!cfile) { warnx("unable to open '%s' file", CONTENTS_FNAME); return 1; } /* If we have a prefix, add it now */ if (Prefix) add_plist(&Plist, PLIST_CWD, Prefix); read_plist(&Plist, cfile); fclose(cfile); p = find_plist(&Plist, PLIST_CWD); if (!p) { warnx("package '%s' doesn't have a prefix", pkg); return 1; } setenv(PKG_PREFIX_VNAME, p->name, 1); if (fexists(REQUIRE_FNAME)) { if (Verbose) printf("Executing 'require' script.\n"); vsystem("chmod +x %s", REQUIRE_FNAME); /* be sure */ if (vsystem("./%s %s DEINSTALL", REQUIRE_FNAME, pkg)) { warnx("package %s fails requirements %s", pkg, Force ? "" : "- not deleted"); if (!Force) return 1; } } /* * Test whether to use the old method of passing tokens to deinstallation * scripts, and set appropriate variables.. */ if (fexists(POST_DEINSTALL_FNAME)) { new_m = 1; post_script = POST_DEINSTALL_FNAME; pre_arg = post_arg = ""; } else if (fexists(DEINSTALL_FNAME)) { post_script = DEINSTALL_FNAME; pre_arg = "DEINSTALL"; post_arg = "POST-DEINSTALL"; } else { post_script = pre_arg = post_arg = NULL; } if (!NoDeInstall && pre_script != NULL && fexists(pre_script)) { if (Fake) printf("Would execute de-install script at this point.\n"); else { vsystem("chmod +x %s", pre_script); /* make sure */ if (vsystem("./%s %s %s", pre_script, pkg, pre_arg)) { warnx("deinstall script returned error status"); if (!Force) return 1; } } } if (chdir(home) == FAIL) { cleanup(0); errx(2, "%s: unable to return to working directory %s!", __func__, home); } /* * Some packages aren't packed right, so we need to just ignore * delete_package()'s status. Ugh! :-( */ if (delete_package(FALSE, CleanDirs, &Plist) == FAIL) warnx( "couldn't entirely delete package (perhaps the packing list is\n" "incorrectly specified?)"); if (chdir(LogDir) == FAIL) { warnx("unable to change directory to %s! deinstall failed", LogDir); return 1; } if (!NoDeInstall && post_script != NULL && fexists(post_script)) { if (Fake) printf("Would execute post-deinstall script at this point.\n"); else { vsystem("chmod +x %s", post_script); /* make sure */ if (vsystem("./%s %s %s", post_script, pkg, post_arg)) { warnx("post-deinstall script returned error status"); if (!Force) return 1; } } } if (chdir(home) == FAIL) { cleanup(0); errx(2, "%s: unable to return to working directory %s!", __func__, home); } if (!Fake) { if (vsystem("%s -r%c %s", REMOVE_CMD, Force ? 'f' : ' ', LogDir)) { warnx("couldn't remove log entry in %s, deinstall failed", LogDir); if (!Force) return 1; } } for (p = Plist.head; p ; p = p->next) { if (p->type != PLIST_PKGDEP) continue; deporigin = (p->next->type == PLIST_DEPORIGIN) ? p->next->name : NULL; if (Verbose) { printf("Trying to remove dependency on package '%s'", p->name); if (deporigin != NULL) printf(" with '%s' origin", deporigin); printf(".\n"); } if (!Fake) { depnames = (deporigin != NULL) ? matchbyorigin(deporigin, NULL) : NULL; if (depnames == NULL) { depnames = alloca(sizeof(*depnames) * 2); depnames[0] = p->name; depnames[1] = NULL; } for (i = 0; depnames[i] != NULL; i++) undepend(depnames[i], pkg); } } return 0; } static void sanity_check(char *pkg) { if (!fexists(CONTENTS_FNAME)) { cleanup(0); errx(2, "%s: installed package %s has no %s file!", __func__, pkg, CONTENTS_FNAME); } } void cleanup(int sig) { if (sig) exit(1); } static void undepend(char *p, char *pkgname) { char fname[FILENAME_MAX], ftmp[FILENAME_MAX]; FILE *fpwr; int s; struct reqr_by_entry *rb_entry; struct reqr_by_head *rb_list; if (requiredby(p, &rb_list, Verbose, FALSE) <= 0) return; snprintf(fname, sizeof(fname), "%s/%s/%s", LOG_DIR, p, REQUIRED_BY_FNAME); snprintf(ftmp, sizeof(ftmp), "%s.XXXXXX", fname); s = mkstemp(ftmp); if (s == -1) { warnx("couldn't open temp file '%s'", ftmp); return; } fpwr = fdopen(s, "w"); if (fpwr == NULL) { close(s); warnx("couldn't fdopen temp file '%s'", ftmp); goto cleanexit; } STAILQ_FOREACH(rb_entry, rb_list, link) if (strcmp(rb_entry->pkgname, pkgname)) /* no match */ fputs(rb_entry->pkgname, fpwr), putc('\n', fpwr); if (fchmod(s, 0644) == FAIL) { warnx("error changing permission of temp file '%s'", ftmp); fclose(fpwr); goto cleanexit; } if (fclose(fpwr) == EOF) { warnx("error closing temp file '%s'", ftmp); goto cleanexit; } if (rename(ftmp, fname) == -1) warnx("error renaming '%s' to '%s'", ftmp, fname); cleanexit: remove(ftmp); return; } Index: head/usr.sbin/pkg_install/delete/pkg_delete.1 =================================================================== --- head/usr.sbin/pkg_install/delete/pkg_delete.1 (revision 131274) +++ head/usr.sbin/pkg_install/delete/pkg_delete.1 (revision 131275) @@ -1,276 +1,283 @@ .\" .\" 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. .\" .\" Jordan K. Hubbard .\" .\" .\" @(#)pkg_delete.1 .\" $FreeBSD$ .\" -.Dd November 25, 1994 +.Dd June 29, 2004 .Dt PKG_DELETE 1 .Os .Sh NAME .Nm pkg_delete .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 .Fl a .Op Ar flags .Sh DESCRIPTION The .Nm command is used to delete packages that have been previously installed with the .Xr pkg_add 1 command. .Sh WARNING .Bf -emphasis Since the .Nm command may execute scripts or programs provided by a package file, your system may be susceptible to .Dq Em trojan horses or other subtle attacks from miscreants who create dangerous package files. .Pp You are advised to verify the competence and identity of those who provide installable package files. For extra protection, examine all the package control files in the package record directory .Pa ( /var/db/pkg// ) . Pay particular attention to any +INSTALL, +POST-INSTALL, +DEINSTALL, +POST-DEINSTALL, +REQUIRE or +MTREE_DIRS files, and inspect the +CONTENTS file for .Cm @cwd , .Cm @mode (check for setuid), .Cm @dirrm , .Cm @exec , and .Cm @unexec directives, and/or use the .Xr pkg_info 1 command to examine the installed package control files. .Ef .Sh OPTIONS 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 If a deinstallation script exists for a given package, do not execute it. .It Fl n Don't actually deinstall a package, just report the steps that would be taken if it were. .It Fl p Ar prefix Set .Ar prefix as the directory in which to delete files from any installed packages which do not explicitly set theirs. For most packages, the prefix will be set automatically to the installed location by .Xr pkg_add 1 . .It Fl d Remove empty directories created by file cleanup. By default, only files/directories explicitly listed in a package's contents (either as normal files/directories or with the .Cm @dirrm directive) will be removed at deinstallation time. This option tells .Nm to also remove any directories that were emptied as a result of removing 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. +.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. .El .Sh TECHNICAL DETAILS The .Nm utility does pretty much what it says. It examines installed package records in .Pa /var/db/pkg/ , deletes the package contents, and finally removes the package records. If the environment variable .Ev PKG_DBDIR is set, this overrides the .Pa /var/db/pkg/ path shown above. .Pp If a package is required by other installed packages, .Nm will list those dependent packages and refuse to delete the package (unless the .Fl f option is given). .Pp If the package contains a .Ar require file (see .Xr pkg_create 1 ) , then this is executed first as .Bd -ragged -offset indent -compact .Cm require .Ar .Ar DEINSTALL .Ed (where .Ar pkg-name is the name of the package in question and .Ar DEINSTALL is a keyword denoting that this is a deinstallation) to see whether or not deinstallation should continue. A non-zero exit status means no, unless the .Fl f option is specified. .Pp If a .Cm deinstall script exists for the package, it is executed before any files are removed. It is this script's responsibility to clean up any additional messy details around the package's installation, since all .Nm knows how to do is delete the files created in the original distribution. The .Nm deinstall script is called as: .Bd -ragged -offset indent -compact .Cm script .Ar .Ar DEINSTALL .Ed where .Ar pkg-name is the name of the package in question and .Ar DEINSTALL is a keyword denoting this as the pre-deinstallation phase. .Pp .Sy Note : The .Ar DEINSTALL keyword will not appear if separate scripts for deinstall and post-deinstall are given during package creation time (using the .Fl k and .Fl K flags to .Xr pkg_create 1 ) . .Pp If a .Cm post-deinstall script exists for the package, it is executed .Cm after all files are removed. It is this script's responsibility to clean up any additional messy details around the package's installation, and leave the system (hopefully) in the same state that it was prior to the installation of the package. .Pp The .Nm post-deinstall script is called as: .Bd -ragged -offset indent -compact .Cm script .Ar .Ar POST-DEINSTALL .Ed where .Ar pkg-name is the name of the package in question and .Ar POST-DEINSTALL is a keyword denoting this as the post-deinstallation phase. .Pp .Sy Note : The .Ar POST-DEINSTALL keyword will not appear if separate scripts for deinstall and post-deinstall are given during package creation time (using the .Fl k and .Fl K flags to .Xr pkg_create 1 ) . .Pp Reasoning behind passing keywords such as .Ar DEINSTALL and .Ar POST-DEINSTALL is that it lets you potentially write only one program/script that handles all aspects of installation and deletion. .Pp But experience has proved that this is a lot more difficult to maintain and is not as advantageous as having separate scripts that handle each aspect of installation and deinstallation. .Pp All scripts are called with the environment variable .Ev PKG_PREFIX set to the installation prefix (see the .Fl p option above). This allows a package author to write a script that reliably performs some action on the directory where the package is installed, even if the user might have changed it by specifying the .Fl p option when running .Nm or .Cm pkg_add . .Sh ENVIRONMENT The environment variable .Ev PKG_DBDIR specifies an alternative location for the installed package database. .Sh FILES .Bl -tag -width /var/db/pkg -compact .It Pa /var/db/pkg Default location of the installed package database. .El .Sh SEE ALSO .Xr pkg_add 1 , .Xr pkg_create 1 , .Xr pkg_info 1 , .Xr pkg_version 1 , .Xr mktemp 3 , .Xr mtree 8 .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. Index: head/usr.sbin/pkg_install/info/info.h =================================================================== --- head/usr.sbin/pkg_install/info/info.h (revision 131274) +++ head/usr.sbin/pkg_install/info/info.h (revision 131275) @@ -1,82 +1,84 @@ /* $FreeBSD$ */ /* * 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. * * Jordan K. Hubbard * 23 August 1993 * * Include and define various things wanted by the info command. * */ #ifndef _INST_INFO_H_INCLUDE #define _INST_INFO_H_INCLUDE #include #ifndef MAXINDEXSIZE #define MAXINDEXSIZE 59 #endif #ifndef MAXNAMESIZE #define MAXNAMESIZE 20 #endif #define SHOW_COMMENT 0x00001 #define SHOW_DESC 0x00002 #define SHOW_PLIST 0x00004 #define SHOW_INSTALL 0x00008 #define SHOW_DEINSTALL 0x00010 #define SHOW_REQUIRE 0x00020 #define SHOW_PREFIX 0x00040 #define SHOW_INDEX 0x00080 #define SHOW_FILES 0x00100 #define SHOW_DISPLAY 0x00200 #define SHOW_REQBY 0x00400 #define SHOW_MTREE 0x00800 #define SHOW_SIZE 0x01000 #define SHOW_ORIGIN 0x02000 #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; char file[PATH_MAX]; char package[PATH_MAX]; Boolean skip; }; TAILQ_HEAD(which_head, which_entry); extern int Flags; extern Boolean Quiet; extern Boolean QUIET; extern Boolean UseBlkSz; extern char *InfoPrefix; extern char PlayPen[]; extern char *CheckPkg; extern char *LookUpOrigin; extern match_t MatchType; extern struct which_head *whead; extern void show_file(const char *, const char *); extern void show_plist(const char *, Package *, plist_t, Boolean); extern void show_files(const char *, Package *); extern void show_index(const char *, const char *); extern void show_size(const char *, Package *); extern void show_cksum(const char *, Package *); extern void show_origin(const char *, Package *); extern void show_fmtrev(const char *, Package *); #endif /* _INST_INFO_H_INCLUDE */ Index: head/usr.sbin/pkg_install/info/main.c =================================================================== --- head/usr.sbin/pkg_install/info/main.c (revision 131274) +++ head/usr.sbin/pkg_install/info/main.c (revision 131275) @@ -1,259 +1,267 @@ /* * * 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. * * Jordan K. Hubbard * 18 July 1993 * * This is the info module. * */ #include __FBSDID("$FreeBSD$"); #include "lib.h" #include "info.h" #include -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; Boolean Quiet = FALSE; Boolean QUIET = FALSE; Boolean UseBlkSz = FALSE; char *InfoPrefix = (char *)(uintptr_t)""; char PlayPen[FILENAME_MAX]; char *CheckPkg = NULL; char *LookUpOrigin = NULL; struct which_head *whead; static void usage __P((void)); int main(int argc, char **argv) { int ch; char **pkgs, **start; char *pkgs_split; whead = malloc(sizeof(struct which_head)); if (whead == NULL) err(2, NULL); TAILQ_INIT(whead); pkgs = start = argv; if (argc == 1) { MatchType = MATCH_ALL; Flags = SHOW_INDEX; } else while ((ch = getopt(argc, argv, Options)) != -1) { switch(ch) { case 'a': MatchType = MATCH_ALL; break; case 'b': UseBlkSz = TRUE; break; case 'v': Verbose = TRUE; /* Reasonable definition of 'everything' */ Flags = SHOW_COMMENT | SHOW_DESC | SHOW_PLIST | SHOW_INSTALL | SHOW_DEINSTALL | SHOW_REQUIRE | SHOW_DISPLAY | SHOW_MTREE; break; + case 'E': + Flags |= SHOW_PKGNAME; + break; + case 'I': Flags |= SHOW_INDEX; break; case 'p': Flags |= SHOW_PREFIX; break; case 'c': Flags |= SHOW_COMMENT; break; case 'd': Flags |= SHOW_DESC; break; case 'D': Flags |= SHOW_DISPLAY; break; case 'f': Flags |= SHOW_PLIST; break; case 'g': Flags |= SHOW_CKSUM; break; case 'G': MatchType = MATCH_EXACT; break; case 'i': Flags |= SHOW_INSTALL; break; case 'k': Flags |= SHOW_DEINSTALL; break; case 'r': Flags |= SHOW_REQUIRE; break; case 'R': Flags |= SHOW_REQBY; break; case 'L': Flags |= SHOW_FILES; break; case 'm': Flags |= SHOW_MTREE; break; case 's': Flags |= SHOW_SIZE; break; case 'o': Flags |= SHOW_ORIGIN; break; case 'O': LookUpOrigin = strdup(optarg); if (LookUpOrigin == NULL) err(2, NULL); break; case 'V': Flags |= SHOW_FMTREV; break; case 'l': InfoPrefix = optarg; break; case 'q': Quiet = TRUE; break; case 'Q': Quiet = TRUE; QUIET = TRUE; break; case 't': strlcpy(PlayPen, optarg, sizeof(PlayPen)); break; case 'x': MatchType = MATCH_REGEX; break; + case 'X': + MatchType = MATCH_EREGEX; + break; + case 'e': CheckPkg = optarg; break; case 'W': { struct which_entry *entp; entp = calloc(1, sizeof(struct which_entry)); if (entp == NULL) err(2, NULL); strlcpy(entp->file, optarg, PATH_MAX); entp->skip = FALSE; TAILQ_INSERT_TAIL(whead, entp, next); break; } case 'P': Flags = SHOW_PTREV; break; case 'h': case '?': default: usage(); break; } } argc -= optind; argv += optind; if (Flags & SHOW_PTREV) { if (!Quiet) printf("Package tools revision: "); printf("%d\n", PKG_INSTALL_VERSION); exit(0); } /* Set some reasonable defaults */ if (!Flags) Flags = SHOW_COMMENT | SHOW_DESC | SHOW_REQBY; /* Get all the remaining package names, if any */ while (*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'; /* * If character after the '/' is alphanumeric or shell * metachar, 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 && MatchType != MATCH_ALL && !CheckPkg && TAILQ_EMPTY(whead) && LookUpOrigin == NULL) warnx("missing package name(s)"), usage(); *pkgs = NULL; return pkg_perform(start); } 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", " pkg_info"); exit(1); } Index: head/usr.sbin/pkg_install/info/perform.c =================================================================== --- head/usr.sbin/pkg_install/info/perform.c (revision 131274) +++ head/usr.sbin/pkg_install/info/perform.c (revision 131275) @@ -1,453 +1,481 @@ /* * 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. * * Jordan K. Hubbard * 23 Aug 1993 * * This is the main body of the info module. * */ #include __FBSDID("$FreeBSD$"); #include "lib.h" #include "info.h" #include #include static int pkg_do(char *); 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) { char **matched; int err_cnt = 0; int i, errcode; 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)) { return find_pkg(whead); } else if (LookUpOrigin != NULL) { return find_pkgs_by_origin(LookUpOrigin); } 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; /* Not reached */ case MATCH_REGEX: + case MATCH_EREGEX: warnx("no packages match pattern(s)"); return 1; /* Not reached */ default: break; } } for (i = 0; pkgs[i]; i++) err_cnt += pkg_do(pkgs[i]); return err_cnt; } static char *Home; static int pkg_do(char *pkg) { Boolean installed = FALSE, isTMP = FALSE; char log_dir[FILENAME_MAX]; char fname[FILENAME_MAX], extrlist[FILENAME_MAX]; Package plist; FILE *fp; struct stat sb; char *cp = NULL; int code = 0; if (isURL(pkg)) { if ((cp = fileGetURL(NULL, pkg)) != NULL) { strcpy(fname, cp); isTMP = TRUE; } } else if (fexists(pkg) && isfile(pkg)) { int len; if (*pkg != '/') { if (!getcwd(fname, FILENAME_MAX)) upchuck("getcwd"); len = strlen(fname); snprintf(&fname[len], FILENAME_MAX - len, "/%s", pkg); } else strcpy(fname, pkg); cp = fname; } else { if ((cp = fileFindByPath(NULL, pkg)) != NULL) strncpy(fname, cp, FILENAME_MAX); } if (cp) { /* * Apply a crude heuristic to see how much space the package will * take up once it's unpacked. I've noticed that most packages * compress an average of 75%, but we're only unpacking the + files so * be very optimistic. */ if (stat(fname, &sb) == FAIL) { warnx("can't stat package file '%s'", fname); code = 1; goto bail; } Home = make_playpen(PlayPen, sb.st_size / 2); snprintf(extrlist, sizeof(extrlist), "--fast-read %s %s %s", CONTENTS_FNAME, COMMENT_FNAME, DESC_FNAME); if (Flags & SHOW_DISPLAY) snprintf(extrlist, sizeof(extrlist), "%s %s", extrlist, DISPLAY_FNAME); if (Flags & SHOW_INSTALL) snprintf(extrlist, sizeof(extrlist), "%s %s %s", extrlist, INSTALL_FNAME, POST_INSTALL_FNAME); if (Flags & SHOW_DEINSTALL) snprintf(extrlist, sizeof(extrlist), "%s %s %s", extrlist, DEINSTALL_FNAME, POST_DEINSTALL_FNAME); if (Flags & SHOW_MTREE) snprintf(extrlist, sizeof(extrlist), "%s %s", extrlist, MTREE_FNAME); if (unpack(fname, extrlist)) { warnx("error during unpacking, no info for '%s' available", pkg); code = 1; goto bail; } } /* It's not an ininstalled package, try and find it among the installed */ else { if (!isinstalledpkg(pkg)) { warnx("can't find package '%s' installed or in a file!", pkg); return 1; } sprintf(log_dir, "%s/%s", LOG_DIR, pkg); if (chdir(log_dir) == FAIL) { warnx("can't change directory to '%s'!", log_dir); return 1; } installed = TRUE; } /* Suck in the contents list */ plist.head = plist.tail = NULL; fp = fopen(CONTENTS_FNAME, "r"); if (!fp) { warnx("unable to open %s file", CONTENTS_FNAME); code = 1; goto bail; } /* If we have a prefix, add it now */ read_plist(&plist, fp); fclose(fp); /* * Index is special info type that has to override all others to make * any sense. */ if (Flags & SHOW_INDEX) { char tmp[FILENAME_MAX]; snprintf(tmp, FILENAME_MAX, "%-19s ", pkg); show_index(tmp, COMMENT_FNAME); } else { /* Start showing the package contents */ if (!Quiet) printf("%sInformation for %s:\n\n", InfoPrefix, pkg); else if (QUIET) printf("%s%s:", InfoPrefix, pkg); if (Flags & SHOW_COMMENT) show_file("Comment:\n", COMMENT_FNAME); if (Flags & SHOW_REQUIRE) show_plist("Depends on:\n", &plist, PLIST_PKGDEP, FALSE); if ((Flags & SHOW_REQBY) && !isemptyfile(REQUIRED_BY_FNAME)) show_file("Required by:\n", REQUIRED_BY_FNAME); if (Flags & SHOW_DESC) show_file("Description:\n", DESC_FNAME); if ((Flags & SHOW_DISPLAY) && fexists(DISPLAY_FNAME)) show_file("Install notice:\n", DISPLAY_FNAME); if (Flags & SHOW_PLIST) show_plist("Packing list:\n", &plist, (plist_t)0, TRUE); if ((Flags & SHOW_INSTALL) && fexists(INSTALL_FNAME)) show_file("Install script:\n", INSTALL_FNAME); if ((Flags & SHOW_INSTALL) && fexists(POST_INSTALL_FNAME)) show_file("Post-Install script:\n", POST_INSTALL_FNAME); if ((Flags & SHOW_DEINSTALL) && fexists(DEINSTALL_FNAME)) show_file("De-Install script:\n", DEINSTALL_FNAME); if ((Flags & SHOW_DEINSTALL) && fexists(POST_DEINSTALL_FNAME)) show_file("Post-DeInstall script:\n", POST_DEINSTALL_FNAME); if ((Flags & SHOW_MTREE) && fexists(MTREE_FNAME)) show_file("mtree file:\n", MTREE_FNAME); if (Flags & SHOW_PREFIX) show_plist("Prefix(s):\n", &plist, PLIST_CWD, FALSE); if (Flags & SHOW_FILES) show_files("Files:\n", &plist); if ((Flags & SHOW_SIZE) && installed) show_size("Package Size:\n", &plist); if ((Flags & SHOW_CKSUM) && installed) show_cksum("Mismatched Checksums:\n", &plist); if (Flags & SHOW_ORIGIN) show_origin("Origin:\n", &plist); if (Flags & SHOW_FMTREV) show_fmtrev("Packing list format revision:\n", &plist); if (!Quiet) puts(InfoPrefix); } free_plist(&plist); bail: leave_playpen(); if (isTMP) unlink(fname); return code; } void cleanup(int sig) { static int in_cleanup = 0; if (!in_cleanup) { in_cleanup = 1; leave_playpen(); } if (sig) exit(1); } /* * Return an absolute path, additionally removing all .'s, ..'s, and extraneous * /'s, as realpath() would, but without resolving symlinks, because that can * potentially screw up our comparisons later. */ static char * abspath(const char *pathname) { char *tmp, *tmp1, *resolved_path; char *cwd = NULL; int len; if (pathname[0] != '/') { cwd = getcwd(NULL, MAXPATHLEN); asprintf(&resolved_path, "%s/%s/", cwd, pathname); } else asprintf(&resolved_path, "%s/", pathname); if (resolved_path == NULL) errx(2, NULL); if (cwd != NULL) free(cwd); while ((tmp = strstr(resolved_path, "//")) != NULL) strcpy(tmp, tmp + 1); while ((tmp = strstr(resolved_path, "/./")) != NULL) strcpy(tmp, tmp + 2); while ((tmp = strstr(resolved_path, "/../")) != NULL) { *tmp = '\0'; if ((tmp1 = strrchr(resolved_path, '/')) == NULL) tmp1 = resolved_path; strcpy(tmp1, tmp + 3); } len = strlen(resolved_path); if (len > 1 && resolved_path[len - 1] == '/') resolved_path[len - 1] = '\0'; return resolved_path; } /* * Comparison to see if the path we're on matches the * one we are looking for. */ static int cmp_path(const char *target, const char *current, const char *cwd) { char *resolved, *temp; int rval; asprintf(&temp, "%s/%s", cwd, current); if (temp == NULL) errx(2, NULL); /* * Make sure there's no multiple /'s or other weird things in the PLIST, * since some plists seem to have them and it could screw up our strncmp. */ resolved = abspath(temp); if (strcmp(target, resolved) == 0) rval = 1; else rval = 0; free(temp); free(resolved); return rval; } /* * Look through package dbs in LOG_DIR and find which * packages installed the files in which_list. */ static int find_pkg(struct which_head *which_list) { char **installed; int errcode, i; struct which_entry *wp; TAILQ_FOREACH(wp, which_list, next) { const char *msg = "file cannot be found"; char *tmp; wp->skip = TRUE; /* If it's not a file, we'll see if it's an executable. */ if (isfile(wp->file) == FALSE) { if (strchr(wp->file, '/') == NULL) { tmp = vpipe("/usr/bin/which %s", wp->file); if (tmp != NULL) { strlcpy(wp->file, tmp, PATH_MAX); wp->skip = FALSE; free(tmp); } else msg = "file is not in PATH"; } } else { tmp = abspath(wp->file); if (isfile(tmp)) { strlcpy(wp->file, tmp, PATH_MAX); wp->skip = FALSE; } free(tmp); } if (wp->skip == TRUE) warnx("%s: %s", wp->file, msg); } installed = matchinstalled(MATCH_ALL, NULL, &errcode); if (installed == NULL) return errcode; for (i = 0; installed[i] != NULL; i++) { FILE *fp; Package pkg; PackingList itr; char *cwd = NULL; char tmp[PATH_MAX]; snprintf(tmp, PATH_MAX, "%s/%s/%s", LOG_DIR, installed[i], CONTENTS_FNAME); fp = fopen(tmp, "r"); if (fp == NULL) { warn("%s", tmp); return 1; } pkg.head = pkg.tail = NULL; read_plist(&pkg, fp); fclose(fp); for (itr = pkg.head; itr != pkg.tail; itr = itr->next) { if (itr->type == PLIST_CWD) { cwd = itr->name; } else if (itr->type == PLIST_FILE) { TAILQ_FOREACH(wp, which_list, next) { if (wp->skip == TRUE) continue; if (!cmp_path(wp->file, itr->name, cwd)) continue; if (wp->package[0] != '\0') { warnx("both %s and %s claim to have installed %s\n", wp->package, installed[i], wp->file); } else { strlcpy(wp->package, installed[i], PATH_MAX); } } } } free_plist(&pkg); } TAILQ_FOREACH(wp, which_list, next) { if (wp->package[0] != '\0') { if (Quiet) puts(wp->package); else printf("%s was installed by package %s\n", \ wp->file, wp->package); } } while (!TAILQ_EMPTY(which_list)) { wp = TAILQ_FIRST(which_list); TAILQ_REMOVE(which_list, wp, next); free(wp); } free(which_list); return 0; } /* * Look through package dbs in LOG_DIR and find which * packages have the given origin. Don't use read_plist() * because this increases time necessary for lookup by 40 * times, as we don't really have to parse all plist to * get origin. */ static int find_pkgs_by_origin(const char *origin) { char **matched; int errcode, i; if (!Quiet) printf("The following installed package(s) has %s origin:\n", origin); matched = matchbyorigin(origin, &errcode); if (matched == NULL) return errcode; for (i = 0; matched[i] != NULL; i++) puts(matched[i]); + + 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; } Index: head/usr.sbin/pkg_install/info/pkg_info.1 =================================================================== --- head/usr.sbin/pkg_install/info/pkg_info.1 (revision 131274) +++ head/usr.sbin/pkg_install/info/pkg_info.1 (revision 131275) @@ -1,241 +1,261 @@ .\" .\" 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. .\" .\" Jordan K. Hubbard .\" .\" .\" @(#)pkg_info.1 .\" $FreeBSD$ .\" .Dd February 8, 2001 .Dt PKG_INFO 1 .Os .Sh NAME .Nm pkg_info .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 .Fl a | Ar pkg-name ... .Nm .Op Fl qQ .Fl W Ar filename .Nm .Op Fl qQ .Fl O Ar origin .Nm .Sh DESCRIPTION The .Nm command is used to dump out information for packages, either packed up in files with the .Xr pkg_create 1 command or already installed on the system with the .Xr pkg_add 1 command. .Sh OPTIONS The following command line options are supported: .Bl -tag -width indent .It Ar pkg-name ... 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 Use the BLOCKSIZE environment variable for output even when the .Fl q or .Fl Q flag is present. .It Fl v Turn on verbose output. .It Fl p Show the installation prefix for each package. .It Fl q Be .Dq quiet in emitting report headers and such, just dump the raw info (basically, assume a non-human reading). .It Fl Q Be .Dq quiet as above but print preface output with the package name. .It Fl c Show the (one line) comment field for each package. .It Fl d Show the long description field for each package. .It Fl D Show the install-message file for each package. .It Fl f Show the packing list instructions for each package. .It Fl g Show files that don't match the recorded checksum. .It Fl i Show the install script (if any) for each package. .It Fl I Show an index line for each package. This option takes precedence over all other package formatting options. .It Fl k Show the de-install script (if any) for each package. .It Fl r Show the requirements script (if any) for each package. .It Fl R Show the list of installed packages which require each package. .It Fl m Show the mtree file (if any) for each package. .It Fl L Show the files within each package. This is different from just viewing the packing list, since full pathnames for everything are generated. .It Fl s Show the total size occupied by files installed within each package. .It Fl o Show the .Dq origin path recorded on package generation. This path intended to give an idea as to where the underlying port, from which package was generated, is located in the .Fx .Em "Ports Collection" . .It Fl G Do not try to expand shell glob patterns in the .Ar pkg-name when selecting packages to be displayed (by default .Nm automatically expands shell glob patterns in the .Ar pkg-name ) . .It Fl W For the specified .Ar filename argument show which package it belongs to. If the file is not in the current directory, and does not have an absolute path, then the .Ev PATH is searched using .Xr which 1 . .It Fl O For the specified .Ar origin argument list all packages having this origin. .It Fl x Treat the .Ar pkg-name as a regular expression and display information only for packages whose names match that regular expression. Multiple regular 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 ) shown with .Ar str . This is primarily of use to front-end programs who want to request a lot of different information fields at once for a package, but don't necessary want the output intermingled in such a way that they can't organize it. This lets you add a special token to the start of each field. .It Fl t Ar template Use .Ar template as the input to .Xr mktemp 3 when creating a .Dq staging area . By default, this is the string .Pa /tmp/instmp.XXXXXX , but it may be necessary to override it in the situation where space in your .Pa /tmp directory is limited. Be sure to leave some number of `X' characters for .Xr mktemp 3 to fill in with a unique ID. .Bd -ragged -offset indent -compact Note: This should really not be necessary with .Nm , since very little information is extracted from each package and one would have to have a very small .Pa /tmp indeed to overflow it. .Ed .It Fl V Show revision number of the packing list format. .It Fl P Show revision number of package tools. .El .Sh TECHNICAL DETAILS Package info is either extracted from package files named on the command line, or from already installed package information in .Pa /var/db/pkg/ Ns Aq Ar pkg-name . .Sh ENVIRONMENT .Bl -tag -width PKG_TMPDIR .It Ev BLOCKSIZE If the environment variable .Ev BLOCKSIZE is set the block counts will be displayed in units of that size block. .It Ev PKG_TMPDIR Points to the directory where .Nm creates its temporary files. If this variable is not set, .Ev TMPDIR is used. If both are unset, the builtin defaults are used. .It Ev PKG_DBDIR Specifies an alternative location for the installed package database. .El .Sh FILES .Bl -tag -width /var/db/pkg -compact .It Pa /var/tmp Used if the environment variables .Ev PKG_TMPDIR and .Ev TMPDIR are not set, or if the directories named have insufficient space. .It Pa /tmp The next choice if .Pa /var/tmp does not exist or has insufficient space. .It Pa /usr/tmp The last choice if .Pa /tmp is unsuitable. .It Pa /var/db/pkg Default location of the installed package database. .El .Sh SEE ALSO .Xr pkg_add 1 , .Xr pkg_create 1 , .Xr pkg_delete 1 , .Xr pkg_version 1 , .Xr mktemp 3 , .Xr mtree 8 .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. Index: head/usr.sbin/pkg_install/lib/lib.h =================================================================== --- head/usr.sbin/pkg_install/lib/lib.h (revision 131274) +++ head/usr.sbin/pkg_install/lib/lib.h (revision 131275) @@ -1,225 +1,226 @@ /* $FreeBSD$ */ /* * 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. * * Jordan K. Hubbard * 18 July 1993 * * Include and define various things wanted by the library routines. * */ #ifndef _INST_LIB_LIB_H_ #define _INST_LIB_LIB_H_ /* Includes */ #include #include #include #include #include #include #include #include #include #include #include /* Macros */ #define SUCCESS (0) #define FAIL (-1) #ifndef TRUE #define TRUE (1) #endif #ifndef FALSE #define FALSE (0) #endif #define YES 2 #define NO 1 /* Usually "rm", but often "echo" during debugging! */ #define REMOVE_CMD "rm" /* Usually "rm", but often "echo" during debugging! */ #define RMDIR_CMD "rmdir" /* Where we put logging information by default, else ${PKG_DBDIR} if set */ #define DEF_LOG_DIR "/var/db/pkg" /* just in case we change the environment variable name */ #define PKG_DBDIR "PKG_DBDIR" /* macro to get name of directory where we put logging information */ #define LOG_DIR (getenv(PKG_DBDIR) ? getenv(PKG_DBDIR) : DEF_LOG_DIR) /* The names of our "special" files */ #define CONTENTS_FNAME "+CONTENTS" #define COMMENT_FNAME "+COMMENT" #define DESC_FNAME "+DESC" #define INSTALL_FNAME "+INSTALL" #define POST_INSTALL_FNAME "+POST-INSTALL" #define DEINSTALL_FNAME "+DEINSTALL" #define POST_DEINSTALL_FNAME "+POST-DEINSTALL" #define REQUIRE_FNAME "+REQUIRE" #define REQUIRED_BY_FNAME "+REQUIRED_BY" #define DISPLAY_FNAME "+DISPLAY" #define MTREE_FNAME "+MTREE_DIRS" #define CMD_CHAR '@' /* prefix for extended PLIST cmd */ /* The name of the "prefix" environment variable given to scripts */ #define PKG_PREFIX_VNAME "PKG_PREFIX" /* * Version of the package tools - increase only when some * functionality used by bsd.port.mk is changed, added or removed */ #define PKG_INSTALL_VERSION 20030417 #define PKG_WRAPCONF_FNAME "/var/db/pkg_install.conf" #define main(argc, argv) real_main(argc, argv) /* Version numbers to assist with changes in package file format */ #define PLIST_FMT_VER_MAJOR 1 #define PLIST_FMT_VER_MINOR 1 enum _plist_t { PLIST_FILE, PLIST_CWD, PLIST_CMD, PLIST_CHMOD, PLIST_CHOWN, PLIST_CHGRP, PLIST_COMMENT, PLIST_IGNORE, PLIST_NAME, PLIST_UNEXEC, PLIST_SRC, PLIST_DISPLAY, PLIST_PKGDEP, PLIST_CONFLICTS, PLIST_MTREE, PLIST_DIR_RM, PLIST_IGNORE_INST, PLIST_OPTION, PLIST_ORIGIN, PLIST_DEPORIGIN }; 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; /* Types */ typedef unsigned int Boolean; struct _plist { struct _plist *prev, *next; char *name; Boolean marked; plist_t type; }; typedef struct _plist *PackingList; struct _pack { struct _plist *head, *tail; char *name; char *origin; int fmtver_maj, fmtver_mnr; }; typedef struct _pack Package; struct reqr_by_entry { STAILQ_ENTRY(reqr_by_entry) link; char pkgname[PATH_MAX]; }; STAILQ_HEAD(reqr_by_head, reqr_by_entry); /* Prototypes */ /* Misc */ int vsystem(const char *, ...); char *vpipe(const char *, ...); void cleanup(int); char *make_playpen(char *, off_t); char *where_playpen(void); void leave_playpen(void); off_t min_free(const char *); /* String */ char *get_dash_string(char **); char *copy_string(const char *); char *copy_string_adds_newline(const char *); Boolean suffix(const char *, const char *); void nuke_suffix(char *); void str_lowercase(char *); char *strconcat(const char *, const char *); char *get_string(char *, int, FILE *); /* File */ Boolean fexists(const char *); Boolean isdir(const char *); Boolean isemptydir(const char *fname); Boolean isemptyfile(const char *fname); Boolean isfile(const char *); Boolean isempty(const char *); Boolean issymlink(const char *); Boolean isURL(const char *); char *fileGetURL(const char *, const char *); char *fileFindByPath(const char *, const char *); char *fileGetContents(const char *); void write_file(const char *, const char *); void copy_file(const char *, const char *, const char *); void move_file(const char *, const char *, const char *); void copy_hierarchy(const char *, const char *, Boolean); int delete_hierarchy(const char *, Boolean, Boolean); int unpack(const char *, const char *); void format_cmd(char *, int, const char *, const char *, const char *); /* Msg */ void upchuck(const char *); void barf(const char *, ...); void whinge(const char *, ...); Boolean y_or_n(Boolean, const char *, ...); /* Packing list */ PackingList new_plist_entry(void); PackingList last_plist(Package *); PackingList find_plist(Package *, plist_t); char *find_plist_option(Package *, const char *name); void plist_delete(Package *, Boolean, plist_t, const char *); void free_plist(Package *); void mark_plist(Package *); void csum_plist_entry(char *, PackingList); void add_plist(Package *, plist_t, const char *); void add_plist_top(Package *, plist_t, const char *); void delete_plist(Package *pkg, Boolean all, plist_t type, const char *name); void write_plist(Package *, FILE *); void read_plist(Package *, FILE *); int plist_cmd(const char *, char **); int delete_package(Boolean, Boolean, Package *); Boolean make_preserve_name(char *, int, const char *, const char *); /* For all */ int pkg_perform(char **); int real_main(int, char **); /* Query installed packages */ 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 **); int chkifdepends(const char *, const char *); int requiredby(const char *, struct reqr_by_head **, Boolean, Boolean); /* Version */ int verscmp(Package *, int, int); int version_cmp(const char *, const char *); /* Externs */ extern Boolean Verbose; extern Boolean Fake; extern Boolean Force; extern int AutoAnswer; #endif /* _INST_LIB_LIB_H_ */ Index: head/usr.sbin/pkg_install/lib/match.c =================================================================== --- head/usr.sbin/pkg_install/lib/match.c (revision 131274) +++ head/usr.sbin/pkg_install/lib/match.c (revision 131275) @@ -1,344 +1,515 @@ /* * 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 __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 *); +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 * 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; 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++) { - 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; } 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 ** matchbyorigin(const char *origin, int *retval) { char **installed; int i; static struct store *store = NULL; store = storecreate(store); if (store == NULL) { if (retval != NULL) *retval = 1; return NULL; } if (retval != NULL) *retval = 0; installed = matchinstalled(MATCH_ALL, NULL, retval); if (installed == NULL) return NULL; for (i = 0; installed[i] != NULL; i++) { FILE *fp; char *cp, tmp[PATH_MAX]; int cmd; 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; 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; 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) { - if (strcmp(origin, cp) == 0) + if (csh_match(origin, cp, FNM_PATHNAME) == 0) storeappend(store, installed[i]); break; } } if (cmd != PLIST_ORIGIN) warnx("package %s has no origin recorded", installed[i]); fclose(fp); } if (store->used == 0) return NULL; else return store->store; } /* * Return TRUE if the specified package is installed, * or FALSE otherwise. */ int isinstalledpkg(const char *name) { char buf[FILENAME_MAX]; char buf2[FILENAME_MAX]; snprintf(buf, sizeof(buf), "%s/%s", LOG_DIR, name); if (!isdir(buf) || access(buf, R_OK) == FAIL) return FALSE; snprintf(buf2, sizeof(buf2), "%s/%s", buf, CONTENTS_FNAME); if (!isfile(buf2) || access(buf2, R_OK) == FAIL) return FALSE; return TRUE; } /* * 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) +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, 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); 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); } Index: head/usr.sbin/pkg_install/version/main.c =================================================================== --- head/usr.sbin/pkg_install/version/main.c (revision 131274) +++ head/usr.sbin/pkg_install/version/main.c (revision 131275) @@ -1,89 +1,103 @@ /* * 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. * * Jeremy D. Lea. * 11 May 2002 * * This is the version module. Based on pkg_version.pl by Bruce A. Mah. * */ #include __FBSDID("$FreeBSD$"); #include "lib.h" #include "version.h" #include -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)); int main(int argc, char **argv) { int ch, cmp = 0; if (argc == 4 && !strcmp(argv[1], "-t")) { cmp = version_cmp(argv[2], argv[3]); 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': Verbose = TRUE; break; case 'l': LimitChars = optarg; break; case 'L': PreventChars = optarg; break; case 's': MatchName = optarg; break; case 't': errx(2, "Invalid -t usage."); break; + case 'T': + errx(2, "Invalid -T usage."); + break; + + case 'X': + RegexExtended = TRUE; + break; + case 'h': case '?': default: usage(); break; } } argc -= optind; argv += optind; return pkg_perform(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); } Index: head/usr.sbin/pkg_install/version/perform.c =================================================================== --- head/usr.sbin/pkg_install/version/perform.c (revision 131274) +++ head/usr.sbin/pkg_install/version/perform.c (revision 131275) @@ -1,316 +1,391 @@ /* * 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. * * Jeremy D. Lea. * 11 May 2002 * * This is the version module. Based on pkg_version.pl by Bruce A. Mah. * */ #include __FBSDID("$FreeBSD$"); #include "lib.h" #include "version.h" #include #include #include FILE *IndexFile; struct index_head Index = SLIST_HEAD_INITIALIZER(Index); static int pkg_do(char *); static void show_version(const char *, const char *, const char *); /* * This is the traditional pkg_perform, except that the argument is _not_ * a list of packages. It is the index file from the command line. * * We loop over the installed packages, matching them with the -s flag * if needed and calling pkg_do(). Before hand we set up a few things, * and after we tear them down... */ int pkg_perform(char **indexarg) { char tmp[PATH_MAX], **pkgs, *pat[2], **patterns; struct index_entry *ie; int i, err_cnt = 0; int MatchType; /* * Try to find and open the INDEX. We only check IndexFile != NULL * later, if we actually need the INDEX. * XXX This should not be hard-coded to INDEX-5. */ if (*indexarg == NULL) snprintf(tmp, PATH_MAX, "%s/INDEX-5", PORTS_DIR); else strlcpy(tmp, *indexarg, PATH_MAX); if (isURL(tmp)) IndexFile = fetchGetURL(tmp, ""); else IndexFile = fopen(tmp, "r"); /* Get either a list of matching or all packages */ if (MatchName != NULL) { pat[0] = MatchName; pat[1] = NULL; - MatchType = MATCH_REGEX; + MatchType = RegexExtended ? MATCH_EREGEX : MATCH_REGEX; patterns = pat; } else { MatchType = MATCH_ALL; patterns = NULL; } pkgs = matchinstalled(MatchType, patterns, &err_cnt); if (err_cnt != 0) errx(2, "Unable to find package database directory!"); if (pkgs == NULL) { switch (MatchType) { case MATCH_ALL: warnx("no packages installed"); return (0); + case MATCH_EREGEX: case MATCH_REGEX: warnx("no packages match pattern"); return (1); default: break; } } for (i = 0; pkgs[i] != NULL; i++) err_cnt += pkg_do(pkgs[i]); /* If we opened the INDEX in pkg_do(), clean up. */ while (!SLIST_EMPTY(&Index)) { ie = SLIST_FIRST(&Index); SLIST_REMOVE_HEAD(&Index, next); if (ie->name != NULL) free(ie->name); if (ie->origin != NULL) free(ie->origin); free(ie); } if (IndexFile != NULL) fclose(IndexFile); return err_cnt; } /* * Traditional pkg_do(). We take the package name we are passed and * first slurp in the CONTENTS file, getting name and origin, then * we look for it's corresponding Makefile. If that fails we pull in * the INDEX, and check there. */ static int pkg_do(char *pkg) { char *ch, tmp[PATH_MAX], tmp2[PATH_MAX], *latest = NULL; Package plist; struct index_entry *ie; FILE *fp; size_t len; /* Suck in the contents list. */ plist.head = plist.tail = NULL; plist.name = plist.origin = NULL; snprintf(tmp, PATH_MAX, "%s/%s/%s", LOG_DIR, pkg, CONTENTS_FNAME); fp = fopen(tmp, "r"); if (!fp) { warnx("unable to open %s file", CONTENTS_FNAME); return 1; } read_plist(&plist, fp); fclose(fp); if (plist.name == NULL) { warnx("%s does not appear to be a valid package!", pkg); return 1; } /* * First we check if the installed package has an origin, and try * looking for it's Makefile. If we find the Makefile we get the * latest version from there. If we fail, we start looking in the * INDEX, first matching the origin and then the package name. */ if (plist.origin != NULL) { snprintf(tmp, PATH_MAX, "%s/%s", PORTS_DIR, plist.origin); if (isdir(tmp) && chdir(tmp) != FAIL && isfile("Makefile")) { if ((latest = vpipe("make -V PKGNAME", tmp)) == NULL) warnx("Failed to get PKGNAME from %s/Makefile!", tmp); else show_version(plist.name, latest, "port"); } } if (latest == NULL) { /* We only pull in the INDEX once, if needed. */ if (SLIST_EMPTY(&Index)) { if (!IndexFile) errx(2, "Unable to open INDEX in %s.", __func__); while ((ch = fgetln(IndexFile, &len)) != NULL) { /* * Don't use strlcpy() because fgetln() doesn't * return a valid C string. */ strncpy(tmp, ch, MIN(len, PATH_MAX)); tmp[PATH_MAX-1] = '\0'; /* The INDEX has pkgname|portdir|... */ if ((ch = strchr(tmp, '|')) != NULL) ch[0] = '\0'; if (ch != NULL && (ch = strchr(&ch[1], '|')) != NULL) ch[0] = '\0'; /* Look backwards for the last two dirs = origin */ while (ch != NULL && *--ch != '/') if (ch[0] == '\0') ch = NULL; while (ch != NULL && *--ch != '/') if (ch[0] == '\0') ch = NULL; if (ch == NULL) errx(2, "The INDEX does not appear to be valid!"); if ((ie = malloc(sizeof(struct index_entry))) == NULL) errx(2, "Unable to allocate memory in %s.", __func__); bzero(ie, sizeof(struct index_entry)); ie->name = strdup(tmp); ie->origin = strdup(&ch[1]); /* Who really cares if we reverse the index... */ SLIST_INSERT_HEAD(&Index, ie, next); } } /* Now that we've slurped in the INDEX... */ SLIST_FOREACH(ie, &Index, next) { if (plist.origin != NULL) { if (strcmp(plist.origin, ie->origin) == 0) latest = strdup(ie->name); } else { strlcpy(tmp, ie->name, PATH_MAX); strlcpy(tmp2, plist.name, PATH_MAX); /* Chop off the versions and compare. */ if ((ch = strrchr(tmp, '-')) == NULL) errx(2, "The INDEX does not appear to be valid!"); ch[0] = '\0'; if ((ch = strrchr(tmp2, '-')) == NULL) warnx("%s is not a valid package!", plist.name); else ch[0] = '\0'; if (strcmp(tmp2, tmp) == 0) { if (latest != NULL) { /* Multiple matches */ snprintf(tmp, PATH_MAX, "%s|%s", latest, ie->name); free(latest); latest = strdup(tmp); } else latest = strdup(ie->name); } } } if (latest == NULL) show_version(plist.name, NULL, plist.origin); else show_version(plist.name, latest, "index"); } if (latest != NULL) free(latest); free_plist(&plist); return 0; } #define OUTPUT(c) ((PreventChars != NULL && !strchr(PreventChars, (c))) || \ (LimitChars != NULL && strchr(LimitChars, (c))) || \ (PreventChars == NULL && LimitChars == NULL)) /* * Do the work of comparing and outputing. Ugly, but well that's what * You get when you try to match perl output in C ;-). */ void show_version(const char *installed, const char *latest, const char *source) { char *ch, tmp[PATH_MAX]; const char *ver; int cmp = 0; if (!installed || strlen(installed) == 0) return; strlcpy(tmp, installed, PATH_MAX); if (!Verbose) { if ((ch = strrchr(tmp, '-')) != NULL) ch[0] = '\0'; } if (latest == NULL) { if (source == NULL && OUTPUT('!')) { printf("%-34s !", tmp); if (Verbose) printf(" Comparison failed"); printf("\n"); } else if (source != NULL && OUTPUT('?')) { printf("%-34s ?", tmp); if (Verbose) printf(" orphaned: %s", source); printf("\n"); } } else if (strchr(latest,'|') != NULL) { if (OUTPUT('*')) { printf("%-34s *", tmp); if (Verbose) { strlcpy(tmp, latest, PATH_MAX); ch = strchr(tmp, '|'); ch[0] = '\0'; ver = strrchr(tmp, '-'); ver = ver ? &ver[1] : tmp; printf(" multiple versions (index has %s", ver); do { ver = strrchr(&ch[1], '-'); ver = ver ? &ver[1] : &ch[1]; if ((ch = strchr(&ch[1], '|')) != NULL) ch[0] = '\0'; printf(", %s", ver); } while (ch != NULL); printf(")"); } printf("\n"); } } else { cmp = version_cmp(installed, latest); ver = strrchr(latest, '-'); ver = ver ? &ver[1] : latest; if (cmp < 0 && OUTPUT('<')) { printf("%-34s <", tmp); if (Verbose) printf(" needs updating (%s has %s)", source, ver); printf("\n"); } else if (cmp == 0 && OUTPUT('=')) { printf("%-34s =", tmp); if (Verbose) printf(" up-to-date with %s", source); printf("\n"); } else if (cmp > 0 && OUTPUT('>')) { printf("%-34s >", tmp); if (Verbose) printf(" succeeds %s (%s has %s)", source, source, ver); printf("\n"); } } +} + +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) { if (sig) exit(1); } Index: head/usr.sbin/pkg_install/version/pkg_version.1 =================================================================== --- head/usr.sbin/pkg_install/version/pkg_version.1 (revision 131274) +++ head/usr.sbin/pkg_install/version/pkg_version.1 (revision 131275) @@ -1,206 +1,226 @@ .\" .\" Copyright 1998 Bruce A. Mah .\" .\" All rights reserved. .\" .\" 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. .\" .\" THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE DEVELOPERS BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" 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 .Nm pkg_version .Nd summarize installed versions of packages .Sh SYNOPSIS .Nm .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 command is used to produce a report of non-base software packages installed using the .Xr pkg_add 1 command. .Pp Each package's version number is checked against one of two sources to see if that package may require updating. If the package contains information about its origin in the .Fx ports tree, and a version number can be determined from the port's .Pa Makefile , then the version number from the .Pa Makefile will be used to determine whether the installed package is up-to-date or requires updating. .Pp If no origin for a package can be found, or if the port's .Pa Makefile cannot be located, .Nm will search for the package in the ports collection index file (typically .Pa /usr/ports/INDEX-5 ) . Any matching version number(s) there will be used to determine whether the installed package is up-to-date or requires updating. .Pp Generally, using the version number from a port's .Pa Makefile will provide a more accurate result, since, unlike the index file, it provides an unambiguous current version number, even when multiple versions of a port exist in the ports collection. Moreover, the ports collection index file is only updated at intervals, meaning that it may not completely reflect the version numbers of the software contained in the ports collection. .Pp Each package name is printed, along with a one-character status flag: .Bl -tag -width indent .It Li = The installed version of the package is current. .It Li \&< The installed version of the package is older than the current version. .It Li \&> The installed version of the package is newer than the current version. This situation can arise with an out-of-date index file, or when testing new ports. .It Li \&? The installed package does not appear in the index. This could be due to an out of date index or a package taken from a PR that has not yet been committed. .It Li * There are multiple versions of a particular software package listed in the index file. Examples from the .Fx ports collection are the Tcl toolkit or the .Tn EMACS editor. .It Li \&! The installed package exists in the index but for some reason, .Nm was unable to compare the version number of the installed package with the corresponding entry in the index. .El .Sh OPTIONS The .Nm utility supports several command-line arguments: .Bl -tag -width indent .It Fl h Print help message. .It Fl l Limit the output to those packages whose status flag matches the character(s) in .Ar limchar . More than one character can be specified in .Ar limchar . Note that because some of the status flag characters are also special to the shell, it is best to quote .Ar limchar with single quotes. .It Fl L Limit the output to those packages whose status flag doesn't match .Ar limchar . You may specify more than one character to match in .Ar limchar . Note that because some of the status flag characters are also special to the shell, it is best to quote .Ar limchar 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 .Li = (equal), .Li \&< (right-hand number greater), or .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 version numbers compared for each package. Non-verbose output is probably easier for programs or scripts to parse. .It Ar index Specify the index to be used as a basis of comparison. This index can be specified as a filename (in the local file system) or a URL. Any URL understandable by .Xr fetch 1 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 The .Fl c option has been deprecated and is no longer supported. .Sh SEE ALSO .Xr fetch 1 , .Xr pkg_add 1 , .Xr pkg_create 1 , .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 The following is a typical invocation of the .Nm command, which checks the installed packages against the local ports index file: .Pp .Dl % pkg_version -v .Pp 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 .Dl % pkg_version -t 1.5 1.5.1 .Sh AUTHORS The .Nm utility was written by .An Jeremy D. Lea Aq reg@FreeBSD.org , partially based on a Perl script written by .An Bruce A. Mah Aq bmah@FreeBSD.org . .Sh CONTRIBUTORS .An Nik Clayton Aq nik@FreeBSD.org , .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 Index: head/usr.sbin/pkg_install/version/version.h =================================================================== --- head/usr.sbin/pkg_install/version/version.h (revision 131274) +++ head/usr.sbin/pkg_install/version/version.h (revision 131275) @@ -1,44 +1,47 @@ /* $FreeBSD$ */ /* * 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. * * Jeremy D. Lea. * 11 May 2002 * * This is the version module. Based on pkg_version.pl by Bruce A. Mah. * */ #ifndef _INST_VERSION_H_INCLUDE #define _INST_VERSION_H_INCLUDE /* Where the ports lives by default */ #define DEF_PORTS_DIR "/usr/ports" /* just in case we change the environment variable name */ #define PORTSDIR "PORTSDIR" /* macro to get name of directory where we put logging information */ #define PORTS_DIR (getenv(PORTSDIR) ? getenv(PORTSDIR) : DEF_PORTS_DIR) struct index_entry { SLIST_ENTRY(index_entry) next; char *name; char *origin; }; 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 */