diff options
Diffstat (limited to 'usr.sbin/pkg_install/lib')
| -rw-r--r-- | usr.sbin/pkg_install/lib/Makefile | 11 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/deps.c | 195 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/exec.c | 106 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/file.c | 431 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/global.c | 31 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/lib.h | 232 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/match.c | 516 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/msg.c | 75 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/pen.c | 176 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/pkgwrap.c | 89 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/plist.c | 579 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/str.c | 129 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/url.c | 154 | ||||
| -rw-r--r-- | usr.sbin/pkg_install/lib/version.c | 328 |
14 files changed, 3052 insertions, 0 deletions
diff --git a/usr.sbin/pkg_install/lib/Makefile b/usr.sbin/pkg_install/lib/Makefile new file mode 100644 index 0000000000000..84a41b8d7f156 --- /dev/null +++ b/usr.sbin/pkg_install/lib/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +LIB= install +INTERNALLIB= +SRCS= file.c msg.c plist.c str.c exec.c global.c pen.c match.c \ + deps.c version.c pkgwrap.c url.c + +WARNS?= 3 +WFORMAT?= 1 + +.include <bsd.lib.mk> diff --git a/usr.sbin/pkg_install/lib/deps.c b/usr.sbin/pkg_install/lib/deps.c new file mode 100644 index 0000000000000..9df8448d490f9 --- /dev/null +++ b/usr.sbin/pkg_install/lib/deps.c @@ -0,0 +1,195 @@ +/* + * 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 + * 14 March 2001 + * + * Routines used to do various operations with dependencies + * among installed packages. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <err.h> +#include <stdio.h> + +/* + * Sort given NULL-terminated list of installed packages (pkgs) in + * such a way that if package A depends on package B then after + * sorting A will be listed before B no matter how they were + * originally positioned in the list. + */ +int +sortdeps(char **pkgs) +{ + char *tmp; + int i, j, loop_cnt; + int err_cnt = 0; + + if (pkgs[0] == NULL || pkgs[1] == NULL) + return (0); + + for (i = 0; pkgs[i + 1]; i++) { + /* + * Check to see if any other package in pkgs[i+1:] depends + * on pkgs[i] and swap those two packages if so. + */ + loop_cnt = 0; + for (j = i + 1; pkgs[j]; j++) { + if (chkifdepends(pkgs[j], pkgs[i]) == 1) { + /* + * Try to avoid deadlock if package A depends on B which in + * turn depends on C and C due to an error depends on A. + * Use ugly but simple method, becase it Should Never + * Happen[tm] in the real life anyway. + */ + if (loop_cnt > 4096) { + warnx("dependency loop detected for package %s", pkgs[j]); + err_cnt++; + break; + } + loop_cnt++; + tmp = pkgs[i]; + pkgs[i] = pkgs[j]; + pkgs[j] = tmp; + /* + * Another iteration requred to check if new pkgs[i] + * itself has any packages that depend on it + */ + j = i + 1; + } + } + } + return err_cnt; +} + +/* + * Check to see if pkgname1 depends on pkgname2. + * Returns 1 if depends, 0 if not, and -1 if error occured. + */ +int +chkifdepends(const char *pkgname1, const char *pkgname2) +{ + char *cp1, *cp2; + int errcode; + struct reqr_by_entry *rb_entry; + struct reqr_by_head *rb_list; + + cp2 = strchr(pkgname2, ':'); + if (cp2 != NULL) + *cp2 = '\0'; + cp1 = strchr(pkgname1, ':'); + if (cp1 != NULL) + *cp1 = '\0'; + + errcode = 0; + /* Check that pkgname2 is actually installed */ + if (isinstalledpkg(pkgname2) <= 0) + goto exit; + + errcode = requiredby(pkgname2, &rb_list, FALSE, TRUE); + if (errcode < 0) + goto exit; + + errcode = 0; + STAILQ_FOREACH(rb_entry, rb_list, link) { + if (strcmp(rb_entry->pkgname, pkgname1) == 0) { /* match */ + errcode = 1; + break; + } + } + +exit: + if (cp1 != NULL) + *cp1 = ':'; + if (cp2 != NULL) + *cp2 = ':'; + return errcode; +} + +/* + * Load +REQUIRED_BY file and return a list with names of + * packages that require package reffered to by `pkgname'. + * + * Optionally check that packages listed there are actually + * installed and filter out those that don't (filter == TRUE). + * + * strict argument controls whether the caller want warnings + * to be emitted when there are some non-fatal conditions, + * i.e. package doesn't have +REQUIRED_BY file or some packages + * listed in +REQUIRED_BY don't exist. + * + * Result returned in the **list, while return value is equal + * to the number of entries in the resulting list. Print error + * message and return -1 on error. + */ +int +requiredby(const char *pkgname, struct reqr_by_head **list, Boolean strict, Boolean filter) +{ + FILE *fp; + char fbuf[FILENAME_MAX], fname[FILENAME_MAX]; + int retval; + struct reqr_by_entry *rb_entry; + static struct reqr_by_head rb_list = STAILQ_HEAD_INITIALIZER(rb_list); + + *list = &rb_list; + /* Deallocate any previously allocated space */ + while (!STAILQ_EMPTY(&rb_list)) { + rb_entry = STAILQ_FIRST(&rb_list); + STAILQ_REMOVE_HEAD(&rb_list, link); + free(rb_entry); + } + + if (isinstalledpkg(pkgname) <= 0) { + if (strict == TRUE) + warnx("no such package '%s' installed", pkgname); + return -1; + } + + snprintf(fname, sizeof(fname), "%s/%s/%s", LOG_DIR, pkgname, + REQUIRED_BY_FNAME); + fp = fopen(fname, "r"); + if (fp == NULL) { + /* Probably pkgname doesn't have any packages that depend on it */ + if (strict == TRUE) + warnx("couldn't open dependency file '%s'", fname); + return 0; + } + + retval = 0; + while (fgets(fbuf, sizeof(fbuf), fp) != NULL) { + if (fbuf[strlen(fbuf) - 1] == '\n') + fbuf[strlen(fbuf) - 1] = '\0'; + if (filter == TRUE && isinstalledpkg(fbuf) <= 0) { + if (strict == TRUE) + warnx("package '%s' is recorded in the '%s' but isn't " + "actually installed", fbuf, fname); + continue; + } + retval++; + rb_entry = malloc(sizeof(*rb_entry)); + if (rb_entry == NULL) { + warnx("%s(): malloc() failed", __func__); + retval = -1; + break; + } + strlcpy(rb_entry->pkgname, fbuf, sizeof(rb_entry->pkgname)); + STAILQ_INSERT_TAIL(&rb_list, rb_entry, link); + } + fclose(fp); + + return retval; +} diff --git a/usr.sbin/pkg_install/lib/exec.c b/usr.sbin/pkg_install/lib/exec.c new file mode 100644 index 0000000000000..86285fb22e213 --- /dev/null +++ b/usr.sbin/pkg_install/lib/exec.c @@ -0,0 +1,106 @@ +/* + * 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 + * + * Miscellaneous system routines. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <err.h> + +/* + * Unusual system() substitute. Accepts format string and args, + * builds and executes command. Returns exit code. + */ + +int +vsystem(const char *fmt, ...) +{ + va_list args; + char *cmd; + int ret, maxargs; + + maxargs = sysconf(_SC_ARG_MAX); + maxargs -= 32; /* some slop for the sh -c */ + cmd = malloc(maxargs); + if (!cmd) { + warnx("vsystem can't alloc arg space"); + return 1; + } + + va_start(args, fmt); + if (vsnprintf(cmd, maxargs, fmt, args) > maxargs) { + warnx("vsystem args are too long"); + return 1; + } +#ifdef DEBUG +printf("Executing %s\n", cmd); +#endif + ret = system(cmd); + va_end(args); + free(cmd); + return ret; +} + +char * +vpipe(const char *fmt, ...) +{ + FILE *fp; + char *cmd, *rp; + int maxargs; + va_list args; + + rp = malloc(MAXPATHLEN); + if (!rp) { + warnx("vpipe can't alloc buffer space"); + return NULL; + } + maxargs = sysconf(_SC_ARG_MAX); + maxargs -= 32; /* some slop for the sh -c */ + cmd = alloca(maxargs); + if (!cmd) { + warnx("vpipe can't alloc arg space"); + return NULL; + } + + va_start(args, fmt); + if (vsnprintf(cmd, maxargs, fmt, args) > maxargs) { + warnx("vsystem args are too long"); + return NULL; + } +#ifdef DEBUG + fprintf(stderr, "Executing %s\n", cmd); +#endif + fflush(NULL); + fp = popen(cmd, "r"); + if (fp == NULL) { + warnx("popen() failed"); + return NULL; + } + get_string(rp, MAXPATHLEN, fp); +#ifdef DEBUG + fprintf(stderr, "Returned %s\n", rp); +#endif + va_end(args); + if (pclose(fp) || (strlen(rp) == 0)) { + free(rp); + return NULL; + } + return rp; +} diff --git a/usr.sbin/pkg_install/lib/file.c b/usr.sbin/pkg_install/lib/file.c new file mode 100644 index 0000000000000..e406caa89704d --- /dev/null +++ b/usr.sbin/pkg_install/lib/file.c @@ -0,0 +1,431 @@ +/* + * 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 + * + * Miscellaneous file access utilities. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <err.h> +#include <pwd.h> +#include <time.h> +#include <sys/wait.h> + +/* Quick check to see if a file exists */ +Boolean +fexists(const char *fname) +{ + struct stat dummy; + if (!lstat(fname, &dummy)) + return TRUE; + return FALSE; +} + +/* Quick check to see if something is a directory or symlink to a directory */ +Boolean +isdir(const char *fname) +{ + struct stat sb; + + if (lstat(fname, &sb) != FAIL && S_ISDIR(sb.st_mode)) + return TRUE; + else if (lstat(strconcat(fname, "/."), &sb) != FAIL && S_ISDIR(sb.st_mode)) + return TRUE; + else + return FALSE; +} + +/* Check to see if file is a dir or symlink to a dir, and is empty */ +Boolean +isemptydir(const char *fname) +{ + if (isdir(fname)) { + DIR *dirp; + struct dirent *dp; + + dirp = opendir(fname); + if (!dirp) + return FALSE; /* no perms, leave it alone */ + for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { + if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) { + closedir(dirp); + return FALSE; + } + } + (void)closedir(dirp); + return TRUE; + } + return FALSE; +} + +/* + * Returns TRUE if file is a regular file or symlink pointing to a regular + * file + */ +Boolean +isfile(const char *fname) +{ + struct stat sb; + if (stat(fname, &sb) != FAIL && S_ISREG(sb.st_mode)) + return TRUE; + return FALSE; +} + +/* + * Check to see if file is a file or symlink pointing to a file and is empty. + * If nonexistent or not a file, say "it's empty", otherwise return TRUE if + * zero sized. + */ +Boolean +isemptyfile(const char *fname) +{ + struct stat sb; + if (stat(fname, &sb) != FAIL && S_ISREG(sb.st_mode)) { + if (sb.st_size != 0) + return FALSE; + } + return TRUE; +} + +/* Returns TRUE if file is a symbolic link. */ +Boolean +issymlink(const char *fname) +{ + struct stat sb; + if (lstat(fname, &sb) != FAIL && S_ISLNK(sb.st_mode)) + return TRUE; + return FALSE; +} + +/* Returns TRUE if file is a URL specification */ +Boolean +isURL(const char *fname) +{ + /* + * I'm sure there are other types of URL specifications that I could + * also be looking for here, but for now I'll just be happy to get ftp + * and http working. + */ + if (!fname) + return FALSE; + while (isspace(*fname)) + ++fname; + if (!strncmp(fname, "ftp://", 6) || !strncmp(fname, "http://", 7) || + !strncmp(fname, "https://", 8) || !strncmp(fname, "file://", 7)) + return TRUE; + return FALSE; +} + +char * +fileFindByPath(const char *base, const char *fname) +{ + static char tmp[FILENAME_MAX]; + char *cp; + const char *suffixes[] = {".tbz", ".tgz", ".tar", NULL}; + int i; + + if (fexists(fname) && isfile(fname)) { + strcpy(tmp, fname); + return tmp; + } + if (base) { + strcpy(tmp, base); + + cp = strrchr(tmp, '/'); + if (cp) { + *cp = '\0'; /* chop name */ + cp = strrchr(tmp, '/'); + } + if (cp) + for (i = 0; suffixes[i] != NULL; i++) { + *(cp + 1) = '\0'; + strcat(cp, "All/"); + strcat(cp, fname); + strcat(cp, suffixes[i]); + if (fexists(tmp)) + return tmp; + } + } + + cp = getenv("PKG_PATH"); + while (cp) { + char *cp2 = strsep(&cp, ":"); + + for (i = 0; suffixes[i] != NULL; i++) { + snprintf(tmp, FILENAME_MAX, "%s/%s%s", cp2 ? cp2 : cp, fname, suffixes[i]); + if (fexists(tmp) && isfile(tmp)) + return tmp; + } + } + return NULL; +} + +char * +fileGetContents(const char *fname) +{ + char *contents; + struct stat sb; + int fd; + + if (stat(fname, &sb) == FAIL) { + cleanup(0); + errx(2, "%s: can't stat '%s'", __func__, fname); + } + + contents = (char *)malloc(sb.st_size + 1); + fd = open(fname, O_RDONLY, 0); + if (fd == FAIL) { + cleanup(0); + errx(2, "%s: unable to open '%s' for reading", __func__, fname); + } + if (read(fd, contents, sb.st_size) != sb.st_size) { + cleanup(0); + errx(2, "%s: short read on '%s' - did not get %lld bytes", __func__, + fname, (long long)sb.st_size); + } + close(fd); + contents[sb.st_size] = '\0'; + return contents; +} + +/* + * Takes a filename and package name, returning (in "try") the + * canonical "preserve" name for it. + */ +Boolean +make_preserve_name(char *try, int max, const char *name, const char *file) +{ + int len, i; + + if ((len = strlen(file)) == 0) + return FALSE; + else + i = len - 1; + strncpy(try, file, max); + if (try[i] == '/') /* Catch trailing slash early and save checking in the loop */ + --i; + for (; i; i--) { + if (try[i] == '/') { + try[i + 1]= '.'; + strncpy(&try[i + 2], &file[i + 1], max - i - 2); + break; + } + } + if (!i) { + try[0] = '.'; + strncpy(try + 1, file, max - 1); + } + /* I should probably be called rude names for these inline assignments */ + strncat(try, ".", max -= strlen(try)); + strncat(try, name, max -= strlen(name)); + strncat(try, ".", max--); + strncat(try, "backup", max -= 6); + return TRUE; +} + +/* Write the contents of "str" to a file */ +void +write_file(const char *name, const char *str) +{ + FILE *fp; + size_t len; + + fp = fopen(name, "w"); + if (!fp) { + cleanup(0); + errx(2, "%s: cannot fopen '%s' for writing", __func__, name); + } + len = strlen(str); + if (fwrite(str, 1, len, fp) != len) { + cleanup(0); + errx(2, "%s: short fwrite on '%s', tried to write %ld bytes", + __func__, name, (long)len); + } + if (fclose(fp)) { + cleanup(0); + errx(2, "%s: failure to fclose '%s'", __func__, name); + } +} + +void +copy_file(const char *dir, const char *fname, const char *to) +{ + char cmd[FILENAME_MAX]; + + if (fname[0] == '/') + snprintf(cmd, FILENAME_MAX, "/bin/cp -r %s %s", fname, to); + else + snprintf(cmd, FILENAME_MAX, "/bin/cp -r %s/%s %s", dir, fname, to); + if (vsystem(cmd)) { + cleanup(0); + errx(2, "%s: could not perform '%s'", __func__, cmd); + } +} + +void +move_file(const char *dir, const char *fname, const char *to) +{ + char cmd[FILENAME_MAX]; + + if (fname[0] == '/') + snprintf(cmd, FILENAME_MAX, "/bin/mv %s %s", fname, to); + else + snprintf(cmd, FILENAME_MAX, "/bin/mv %s/%s %s", dir, fname, to); + if (vsystem(cmd)) { + cleanup(0); + errx(2, "%s: could not perform '%s'", __func__, cmd); + } +} + +/* + * Copy a hierarchy (possibly from dir) to the current directory, or + * if "to" is TRUE, from the current directory to a location someplace + * else. + * + * Though slower, using tar to copy preserves symlinks and everything + * without me having to write some big hairy routine to do it. + */ +void +copy_hierarchy(const char *dir, const char *fname, Boolean to) +{ + char cmd[FILENAME_MAX * 3]; + + if (!to) { + /* If absolute path, use it */ + if (*fname == '/') + dir = "/"; + snprintf(cmd, FILENAME_MAX * 3, "/usr/bin/tar cf - -C %s %s | /usr/bin/tar xpf -", + dir, fname); + } + else + snprintf(cmd, FILENAME_MAX * 3, "/usr/bin/tar cf - %s | /usr/bin/tar xpf - -C %s", + fname, dir); +#ifdef DEBUG + printf("Using '%s' to copy trees.\n", cmd); +#endif + if (system(cmd)) { + cleanup(0); + errx(2, "%s: could not perform '%s'", __func__, cmd); + } +} + +/* Unpack a tar file */ +int +unpack(const char *pkg, const char *flist) +{ + const char *comp, *cp; + char suff[80]; + + comp = ""; + /* + * Figure out by a crude heuristic whether this or not this is probably + * compressed and whichever compression utility was used (gzip or bzip2). + */ + if (strcmp(pkg, "-")) { + cp = strrchr(pkg, '.'); + if (cp) { + strcpy(suff, cp + 1); + if (strchr(suff, 'z') || strchr(suff, 'Z')) { + if (strchr(suff, 'b')) + comp = "-j"; + else + comp = "-z"; + } + } + } + else +#if defined(__FreeBSD_version) && __FreeBSD_version >= 500039 + comp = "-j"; +#else + comp = "-z"; +#endif + if (vsystem("/usr/bin/tar -xp %s -f '%s' %s", comp, pkg, flist ? flist : "")) { + warnx("tar extract of %s failed!", pkg); + return 1; + } + return 0; +} + +/* + * Using fmt, replace all instances of: + * + * %F With the parameter "name" + * %D With the parameter "dir" + * %B Return the directory part ("base") of %D/%F + * %f Return the filename part of %D/%F + * + * Does not check for overflow - caution! + * + */ +void +format_cmd(char *buf, int max, const char *fmt, const char *dir, const char *name) +{ + char *cp, scratch[FILENAME_MAX * 2]; + int l; + + while (*fmt && max > 0) { + if (*fmt == '%') { + switch (*++fmt) { + case 'F': + strncpy(buf, name, max); + l = strlen(name); + buf += l, max -= l; + break; + + case 'D': + strncpy(buf, dir, max); + l = strlen(dir); + buf += l, max -= l; + break; + + case 'B': + snprintf(scratch, FILENAME_MAX * 2, "%s/%s", dir, name); + cp = &scratch[strlen(scratch) - 1]; + while (cp != scratch && *cp != '/') + --cp; + *cp = '\0'; + strncpy(buf, scratch, max); + l = strlen(scratch); + buf += l, max -= l; + break; + + case 'f': + snprintf(scratch, FILENAME_MAX * 2, "%s/%s", dir, name); + cp = &scratch[strlen(scratch) - 1]; + while (cp != scratch && *(cp - 1) != '/') + --cp; + strncpy(buf, cp, max); + l = strlen(cp); + buf += l, max -= l; + break; + + default: + *buf++ = *fmt; + --max; + break; + } + ++fmt; + } + else { + *buf++ = *fmt++; + --max; + } + } + *buf = '\0'; +} diff --git a/usr.sbin/pkg_install/lib/global.c b/usr.sbin/pkg_install/lib/global.c new file mode 100644 index 0000000000000..291f48b42552c --- /dev/null +++ b/usr.sbin/pkg_install/lib/global.c @@ -0,0 +1,31 @@ +/* + * 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 + * + * Semi-convenient place to stick some needed globals. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" + +/* These are global for all utils */ +Boolean Verbose = FALSE; +Boolean Fake = FALSE; +Boolean Force = FALSE; +int AutoAnswer = FALSE; diff --git a/usr.sbin/pkg_install/lib/lib.h b/usr.sbin/pkg_install/lib/lib.h new file mode 100644 index 0000000000000..c50a944d01568 --- /dev/null +++ b/usr.sbin/pkg_install/lib/lib.h @@ -0,0 +1,232 @@ +/* $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 <sys/param.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/queue.h> +#include <ctype.h> +#include <dirent.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* 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 "/bin/rm" + +/* Usually "rm", but often "echo" during debugging! */ +#define RMDIR_CMD "/bin/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" + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 500036 +#define INDEX_FNAME "INDEX-5" +#else +#define INDEX_FNAME "INDEX" +#endif + +#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 20040629 + +#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_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; + const char *name; + const 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_ */ diff --git a/usr.sbin/pkg_install/lib/match.c b/usr.sbin/pkg_install/lib/match.c new file mode 100644 index 0000000000000..06e34cf0727d8 --- /dev/null +++ b/usr.sbin/pkg_install/lib/match.c @@ -0,0 +1,516 @@ +/* + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <err.h> +#include <fnmatch.h> +#include <fts.h> +#include <regex.h> + +/* + * Simple structure representing argv-like + * NULL-terminated list. + */ +struct store { + int currlen; + int used; + char **store; +}; + +static int rex_match(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_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 = NULL; + + 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++) { + 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) { + 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 (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 1 if the specified package is installed, + * 0 if not, and -1 if an error occured. + */ +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 0; + + snprintf(buf2, sizeof(buf2), "%s/%s", buf, CONTENTS_FNAME); + if (!isfile(buf2) || access(buf2, R_OK) == FAIL) + return -1; + + return 1; +} + +/* + * 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, int extended) +{ + char errbuf[128]; + int errcode; + int retval; + regex_t rex; + + retval = 0; + + 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); +} diff --git a/usr.sbin/pkg_install/lib/msg.c b/usr.sbin/pkg_install/lib/msg.c new file mode 100644 index 0000000000000..5b17624016d0e --- /dev/null +++ b/usr.sbin/pkg_install/lib/msg.c @@ -0,0 +1,75 @@ +/* + * 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 + * + * Miscellaneous message routines. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <err.h> +#include <paths.h> + +/* Die a relatively simple death */ +void +upchuck(const char *message) +{ + cleanup(0); + errx(1, "fatal error during execution: %s", message); +} + +/* + * As a yes/no question, prompting from the varargs string and using + * default if user just hits return. + */ +Boolean +y_or_n(Boolean def, const char *msg, ...) +{ + va_list args; + int ch = 0; + FILE *tty; + + va_start(args, msg); + /* + * Need to open /dev/tty because file collection may have been + * collected on stdin + */ + tty = fopen(_PATH_TTY, "r"); + if (!tty) { + cleanup(0); + errx(2, "can't open %s!", _PATH_TTY); + } + while (ch != 'Y' && ch != 'N') { + vfprintf(stderr, msg, args); + if (def) + fprintf(stderr, " [yes]? "); + else + fprintf(stderr, " [no]? "); + fflush(stderr); + if (AutoAnswer) { + ch = (AutoAnswer == YES) ? 'Y' : 'N'; + fprintf(stderr, "%c\n", ch); + } + else + ch = toupper(fgetc(tty)); + if (ch == '\n') + ch = (def) ? 'Y' : 'N'; + } + fclose(tty) ; + return (ch == 'Y') ? TRUE : FALSE; +} diff --git a/usr.sbin/pkg_install/lib/pen.c b/usr.sbin/pkg_install/lib/pen.c new file mode 100644 index 0000000000000..06379b69c7f96 --- /dev/null +++ b/usr.sbin/pkg_install/lib/pen.c @@ -0,0 +1,176 @@ +/* + * 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 + * + * Routines for managing the "play pen". + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <err.h> +#include <libgen.h> +#include <sys/signal.h> +#include <sys/param.h> +#include <sys/mount.h> + +/* For keeping track of where we are */ +static char PenLocation[FILENAME_MAX]; +static char Previous[FILENAME_MAX]; + +char * +where_playpen(void) +{ + return PenLocation; +} + +/* Find a good place to play. */ +static char * +find_play_pen(char *pen, off_t sz) +{ + char *cp; + struct stat sb; + + if (pen[0] && isdir(dirname(pen)) == TRUE && (min_free(dirname(pen)) >= sz)) + return pen; + else if ((cp = getenv("PKG_TMPDIR")) != NULL && stat(cp, &sb) != FAIL && (min_free(cp) >= sz)) + sprintf(pen, "%s/instmp.XXXXXX", cp); + else if ((cp = getenv("TMPDIR")) != NULL && stat(cp, &sb) != FAIL && (min_free(cp) >= sz)) + sprintf(pen, "%s/instmp.XXXXXX", cp); + else if (stat("/var/tmp", &sb) != FAIL && min_free("/var/tmp") >= sz) + strcpy(pen, "/var/tmp/instmp.XXXXXX"); + else if (stat("/tmp", &sb) != FAIL && min_free("/tmp") >= sz) + strcpy(pen, "/tmp/instmp.XXXXXX"); + else if ((stat("/usr/tmp", &sb) == SUCCESS || mkdir("/usr/tmp", 01777) == SUCCESS) && min_free("/usr/tmp") >= sz) + strcpy(pen, "/usr/tmp/instmp.XXXXXX"); + else { + cleanup(0); + errx(2, +"%s: can't find enough temporary space to extract the files, please set your\n" +"PKG_TMPDIR environment variable to a location with at least %ld bytes\n" +"free", __func__, (long)sz); + return NULL; + } + return pen; +} + +#define MAX_STACK 20 +static char *pstack[MAX_STACK]; +static int pdepth = -1; + +static void +pushPen(const char *pen) +{ + if (++pdepth == MAX_STACK) + errx(2, "%s: stack overflow.\n", __func__); + pstack[pdepth] = strdup(pen); +} + +static void +popPen(char *pen) +{ + if (pdepth == -1) { + pen[0] = '\0'; + return; + } + strcpy(pen, pstack[pdepth]); + free(pstack[pdepth--]); +} + +/* + * Make a temporary directory to play in and chdir() to it, returning + * pathname of previous working directory. + */ +char * +make_playpen(char *pen, off_t sz) +{ + if (!find_play_pen(pen, sz)) + return NULL; + + if (!mkdtemp(pen)) { + cleanup(0); + errx(2, "%s: can't mktemp '%s'", __func__, pen); + } + if (chmod(pen, 0700) == FAIL) { + cleanup(0); + errx(2, "%s: can't mkdir '%s'", __func__, pen); + } + + if (Verbose) { + if (sz) + fprintf(stderr, "Requested space: %d bytes, free space: %lld bytes in %s\n", (int)sz, (long long)min_free(pen), pen); + } + + if (min_free(pen) < sz) { + rmdir(pen); + cleanup(0); + errx(2, "%s: not enough free space to create '%s'.\n" + "Please set your PKG_TMPDIR environment variable to a location\n" + "with more space and\ntry the command again", __func__, pen); + } + + if (!getcwd(Previous, FILENAME_MAX)) { + upchuck("getcwd"); + return NULL; + } + + if (chdir(pen) == FAIL) { + cleanup(0); + errx(2, "%s: can't chdir to '%s'", __func__, pen); + } + + if (PenLocation[0]) + pushPen(PenLocation); + + strcpy(PenLocation, pen); + return Previous; +} + +/* Convenience routine for getting out of playpen */ +void +leave_playpen() +{ + void (*oldsig)(int); + + /* Don't interrupt while we're cleaning up */ + oldsig = signal(SIGINT, SIG_IGN); + if (Previous[0]) { + if (chdir(Previous) == FAIL) { + cleanup(0); + errx(2, "%s: can't chdir back to '%s'", __func__, Previous); + } + Previous[0] = '\0'; + } + if (PenLocation[0]) { + if (PenLocation[0] == '/' && vsystem("/bin/rm -rf %s", PenLocation)) + warnx("couldn't remove temporary dir '%s'", PenLocation); + popPen(PenLocation); + } + signal(SIGINT, oldsig); +} + +off_t +min_free(const char *tmpdir) +{ + struct statfs buf; + + if (statfs(tmpdir, &buf) != 0) { + warn("statfs"); + return -1; + } + return (off_t)buf.f_bavail * (off_t)buf.f_bsize; +} diff --git a/usr.sbin/pkg_install/lib/pkgwrap.c b/usr.sbin/pkg_install/lib/pkgwrap.c new file mode 100644 index 0000000000000..cbd15cd7b445c --- /dev/null +++ b/usr.sbin/pkg_install/lib/pkgwrap.c @@ -0,0 +1,89 @@ +/* + * 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. + * + * Maxim Sobolev + * 8 September 2002 + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#undef main + +#define SEPARATORS " \t" + +extern char **environ; + +int +main(int argc, char **argv) +{ + FILE *f; + char buffer[FILENAME_MAX], *cp, *verstr; + int len; + + if (getenv("PKG_NOWRAP") != NULL) + goto nowrap; + f = fopen(PKG_WRAPCONF_FNAME, "r"); + if (f == NULL) + goto nowrap; + cp = fgets(buffer, 256, f); + fclose(f); + if (cp == NULL) + goto nowrap; + len = strlen(cp); + if (cp[len - 1] == '\n') + cp[len - 1] = '\0'; + while (strchr(SEPARATORS, *cp) != NULL) + cp++; + verstr = cp; + cp = strpbrk(cp, SEPARATORS); + if (cp == NULL) + goto nowrap; + *cp = '\0'; + for (cp = verstr; *cp != '\0'; cp++) + if (isdigit(*cp) == 0) + goto nowrap; + if (atoi(verstr) < PKG_INSTALL_VERSION) + goto nowrap; + cp++; + while (*cp != '\0' && strchr(SEPARATORS, *cp) != NULL) + cp++; + if (*cp == '\0') + goto nowrap; + bcopy(cp, buffer, strlen(cp) + 1); + cp = strpbrk(buffer, SEPARATORS); + if (cp != NULL) + *cp = '\0'; + if (!isdir(buffer)) + goto nowrap; + cp = strrchr(argv[0], '/'); + if (cp == NULL) + cp = argv[0]; + else + cp++; + strlcat(buffer, "/", sizeof(buffer)); + strlcat(buffer, cp, sizeof(buffer)); + setenv("PKG_NOWRAP", "1", 1); + execve(buffer, argv, environ); + +nowrap: + unsetenv("PKG_NOWRAP"); + return(real_main(argc, argv)); +} diff --git a/usr.sbin/pkg_install/lib/plist.c b/usr.sbin/pkg_install/lib/plist.c new file mode 100644 index 0000000000000..95d9ac4a370c9 --- /dev/null +++ b/usr.sbin/pkg_install/lib/plist.c @@ -0,0 +1,579 @@ +/* + * 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 + * + * General packing list routines. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <err.h> +#include <md5.h> + +/* Add an item to a packing list */ +void +add_plist(Package *p, plist_t type, const char *arg) +{ + PackingList tmp; + + tmp = new_plist_entry(); + tmp->name = copy_string(arg); + tmp->type = type; + + if (!p->head) + p->head = p->tail = tmp; + else { + tmp->prev = p->tail; + p->tail->next = tmp; + p->tail = tmp; + } + switch (type) { + case PLIST_NAME: + p->name = tmp->name; + break; + + case PLIST_ORIGIN: + p->origin = tmp->name; + break; + + default: + break; + } +} + +void +add_plist_top(Package *p, plist_t type, const char *arg) +{ + PackingList tmp; + + tmp = new_plist_entry(); + tmp->name = copy_string(arg); + tmp->type = type; + + if (!p->head) + p->head = p->tail = tmp; + else { + tmp->next = p->head; + p->head->prev = tmp; + p->head = tmp; + } +} + +/* Return the last (most recent) entry in a packing list */ +PackingList +last_plist(Package *p) +{ + return p->tail; +} + +/* Mark all items in a packing list to prevent iteration over them */ +void +mark_plist(Package *pkg) +{ + PackingList p = pkg->head; + + while (p) { + p->marked = TRUE; + p = p->next; + } +} + +/* Find a given item in a packing list and, if so, return it (else NULL) */ +PackingList +find_plist(Package *pkg, plist_t type) +{ + PackingList p = pkg->head; + + while (p) { + if (p->type == type) + return p; + p = p->next; + } + return NULL; +} + +/* Look for a specific boolean option argument in the list */ +char * +find_plist_option(Package *pkg, const char *name) +{ + PackingList p = pkg->head; + + while (p) { + if (p->type == PLIST_OPTION && !strcmp(p->name, name)) + return p->name; + p = p->next; + } + return NULL; +} + +/* + * Delete plist item 'type' in the list (if 'name' is non-null, match it + * too.) If 'all' is set, delete all items, not just the first occurance. + */ +void +delete_plist(Package *pkg, Boolean all, plist_t type, const char *name) +{ + PackingList p = pkg->head; + + while (p) { + PackingList pnext = p->next; + + if (p->type == type && (!name || !strcmp(name, p->name))) { + free(p->name); + if (p->prev) + p->prev->next = pnext; + else + pkg->head = pnext; + if (pnext) + pnext->prev = p->prev; + else + pkg->tail = p->prev; + free(p); + if (!all) + return; + p = pnext; + } + else + p = p->next; + } +} + +/* Allocate a new packing list entry */ +PackingList +new_plist_entry(void) +{ + PackingList ret; + + ret = (PackingList)malloc(sizeof(struct _plist)); + bzero(ret, sizeof(struct _plist)); + return ret; +} + +/* Free an entire packing list */ +void +free_plist(Package *pkg) +{ + PackingList p = pkg->head; + + while (p) { + PackingList p1 = p->next; + + free(p->name); + free(p); + p = p1; + } + pkg->head = pkg->tail = NULL; +} + +/* + * For an ascii string denoting a plist command, return its code and + * optionally its argument(s) + */ +int +plist_cmd(const char *s, char **arg) +{ + char cmd[FILENAME_MAX + 20]; /* 20 == fudge for max cmd len */ + char *cp; + const char *sp; + + strcpy(cmd, s); + str_lowercase(cmd); + cp = cmd; + sp = s; + while (*cp) { + if (isspace(*cp)) { + *cp = '\0'; + while (isspace(*sp)) /* Never sure if macro, increment later */ + ++sp; + break; + } + ++cp, ++sp; + } + if (arg) + *arg = (char *)sp; + if (!strcmp(cmd, "cwd")) + return PLIST_CWD; + else if (!strcmp(cmd, "srcdir")) + return PLIST_SRC; + else if (!strcmp(cmd, "cd")) + return PLIST_CWD; + else if (!strcmp(cmd, "exec")) + return PLIST_CMD; + else if (!strcmp(cmd, "unexec")) + return PLIST_UNEXEC; + else if (!strcmp(cmd, "mode")) + return PLIST_CHMOD; + else if (!strcmp(cmd, "owner")) + return PLIST_CHOWN; + else if (!strcmp(cmd, "group")) + return PLIST_CHGRP; + else if (!strcmp(cmd, "comment")) { + if (!strncmp(*arg, "ORIGIN:", 7)) { + *arg += 7; + return PLIST_ORIGIN; + } else if (!strncmp(*arg, "DEPORIGIN:", 10)) { + *arg += 10; + return PLIST_DEPORIGIN; + } + return PLIST_COMMENT; + } else if (!strcmp(cmd, "ignore")) + return PLIST_IGNORE; + else if (!strcmp(cmd, "ignore_inst")) + return PLIST_IGNORE_INST; + else if (!strcmp(cmd, "name")) + return PLIST_NAME; + else if (!strcmp(cmd, "display")) + return PLIST_DISPLAY; + else if (!strcmp(cmd, "pkgdep")) + return PLIST_PKGDEP; + else if (!strcmp(cmd, "conflicts")) + return PLIST_CONFLICTS; + else if (!strcmp(cmd, "mtree")) + return PLIST_MTREE; + else if (!strcmp(cmd, "dirrm")) + return PLIST_DIR_RM; + else if (!strcmp(cmd, "option")) + return PLIST_OPTION; + else + return FAIL; +} + +/* Read a packing list from a file */ +void +read_plist(Package *pkg, FILE *fp) +{ + char *cp, pline[FILENAME_MAX]; + int cmd, major, minor; + + pkg->fmtver_maj = 1; + pkg->fmtver_mnr = 0; + pkg->origin = NULL; + while (fgets(pline, FILENAME_MAX, fp)) { + int len = strlen(pline); + + while (len && isspace(pline[len - 1])) + pline[--len] = '\0'; + if (!len) + continue; + cp = pline; + if (pline[0] != CMD_CHAR) { + cmd = PLIST_FILE; + goto bottom; + } + cmd = plist_cmd(pline + 1, &cp); + if (cmd == FAIL) { + warnx("%s: unknown command '%s' (package tools out of date?)", + __func__, pline); + goto bottom; + } + if (*cp == '\0') { + cp = NULL; + goto bottom; + } + if (cmd == PLIST_COMMENT && sscanf(cp, "PKG_FORMAT_REVISION:%d.%d\n", + &major, &minor) == 2) { + pkg->fmtver_maj = major; + pkg->fmtver_mnr = minor; + if (verscmp(pkg, PLIST_FMT_VER_MAJOR, PLIST_FMT_VER_MINOR) <= 0) + goto bottom; + + warnx("plist format revision (%d.%d) is higher than supported" + "(%d.%d)", pkg->fmtver_maj, pkg->fmtver_mnr, + PLIST_FMT_VER_MAJOR, PLIST_FMT_VER_MINOR); + if (pkg->fmtver_maj > PLIST_FMT_VER_MAJOR) { + cleanup(0); + exit(2); + } + } +bottom: + add_plist(pkg, cmd, cp); + } +} + +/* Write a packing list to a file, converting commands to ascii equivs */ +void +write_plist(Package *pkg, FILE *fp) +{ + PackingList plist = pkg->head; + + while (plist) { + switch(plist->type) { + case PLIST_FILE: + fprintf(fp, "%s\n", plist->name); + break; + + case PLIST_CWD: + fprintf(fp, "%ccwd %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_SRC: + fprintf(fp, "%csrcdir %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_CMD: + fprintf(fp, "%cexec %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_UNEXEC: + fprintf(fp, "%cunexec %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_CHMOD: + fprintf(fp, "%cmode %s\n", CMD_CHAR, plist->name ? plist->name : ""); + break; + + case PLIST_CHOWN: + fprintf(fp, "%cowner %s\n", CMD_CHAR, plist->name ? plist->name : ""); + break; + + case PLIST_CHGRP: + fprintf(fp, "%cgroup %s\n", CMD_CHAR, plist->name ? plist->name : ""); + break; + + case PLIST_COMMENT: + fprintf(fp, "%ccomment %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_IGNORE: + case PLIST_IGNORE_INST: /* a one-time non-ignored file */ + fprintf(fp, "%cignore\n", CMD_CHAR); + break; + + case PLIST_NAME: + fprintf(fp, "%cname %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_DISPLAY: + fprintf(fp, "%cdisplay %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_PKGDEP: + fprintf(fp, "%cpkgdep %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_CONFLICTS: + fprintf(fp, "%cconflicts %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_MTREE: + fprintf(fp, "%cmtree %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_DIR_RM: + fprintf(fp, "%cdirrm %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_OPTION: + fprintf(fp, "%coption %s\n", CMD_CHAR, plist->name); + break; + + case PLIST_ORIGIN: + fprintf(fp, "%ccomment ORIGIN:%s\n", CMD_CHAR, plist->name); + break; + + case PLIST_DEPORIGIN: + fprintf(fp, "%ccomment DEPORIGIN:%s\n", CMD_CHAR, plist->name); + break; + + default: + cleanup(0); + errx(2, "%s: unknown command type %d (%s)", __func__, + plist->type, plist->name); + break; + } + plist = plist->next; + } +} + +/* + * Delete the results of a package installation. + * + * This is here rather than in the pkg_delete code because pkg_add needs to + * run it too in cases of failure. + */ +int +delete_package(Boolean ign_err, Boolean nukedirs, Package *pkg) +{ + PackingList p; + const char *Where = ".", *last_file = ""; + Boolean fail = SUCCESS; + Boolean preserve; + char tmp[FILENAME_MAX], *name = NULL; + + preserve = find_plist_option(pkg, "preserve") ? TRUE : FALSE; + for (p = pkg->head; p; p = p->next) { + switch (p->type) { + case PLIST_NAME: + name = p->name; + break; + + case PLIST_IGNORE: + p = p->next; + break; + + case PLIST_CWD: + Where = p->name; + if (Verbose) + printf("Change working directory to %s\n", Where); + break; + + case PLIST_UNEXEC: + format_cmd(tmp, FILENAME_MAX, p->name, Where, last_file); + if (Verbose) + printf("Execute '%s'\n", tmp); + if (!Fake && system(tmp)) { + warnx("unexec command for '%s' failed", tmp); + fail = FAIL; + } + break; + + case PLIST_FILE: + last_file = p->name; + sprintf(tmp, "%s/%s", Where, p->name); + if (isdir(tmp) && fexists(tmp) && !issymlink(tmp)) { + warnx("cannot delete specified file '%s' - it is a directory!\n" + "this packing list is incorrect - ignoring delete request", tmp); + } + else { + if (p->next && p->next->type == PLIST_COMMENT && !strncmp(p->next->name, "MD5:", 4)) { + char *cp = NULL, buf[33]; + + /* + * For packing lists whose version is 1.1 or greater, the md5 + * hash for a symlink is calculated on the string returned + * by readlink(). + */ + if (issymlink(tmp) && verscmp(pkg, 1, 0) > 0) { + int len; + char linkbuf[FILENAME_MAX]; + + if ((len = readlink(tmp, linkbuf, FILENAME_MAX)) > 0) + cp = MD5Data((unsigned char *)linkbuf, len, buf); + } else if (isfile(tmp) || verscmp(pkg, 1, 1) < 0) + cp = MD5File(tmp, buf); + + if (cp != NULL) { + /* Mismatch? */ + if (strcmp(cp, p->next->name + 4)) { + warnx("'%s' fails original MD5 checksum - %s", + tmp, Force ? "deleted anyway." : "not deleted."); + if (!Force) { + fail = FAIL; + continue; + } + } + } + } + if (Verbose) + printf("Delete file %s\n", tmp); + if (!Fake) { + if (delete_hierarchy(tmp, ign_err, nukedirs)) + fail = FAIL; + if (preserve && name) { + char tmp2[FILENAME_MAX]; + + if (make_preserve_name(tmp2, FILENAME_MAX, name, tmp)) { + if (fexists(tmp2)) { + if (rename(tmp2, tmp)) + warn("preserve: unable to restore %s as %s", + tmp2, tmp); + } + } + } + } + } + break; + + case PLIST_DIR_RM: + sprintf(tmp, "%s/%s", Where, p->name); + if (!isdir(tmp) && fexists(tmp)) { + warnx("cannot delete specified directory '%s' - it is a file!\n" + "this packing list is incorrect - ignoring delete request", tmp); + } + else { + if (Verbose) + printf("Delete directory %s\n", tmp); + if (!Fake && delete_hierarchy(tmp, ign_err, FALSE)) { + warnx("unable to completely remove directory '%s'", tmp); + fail = FAIL; + } + } + last_file = p->name; + break; + + default: + break; + } + } + return fail; +} + +#ifdef DEBUG +#define RMDIR(dir) vsystem("%s %s", RMDIR_CMD, dir) +#define REMOVE(dir,ie) vsystem("%s %s%s", REMOVE_CMD, (ie ? "-f " : ""), dir) +#else +#define RMDIR rmdir +#define REMOVE(file,ie) (remove(file) && !(ie)) +#endif + +/* Selectively delete a hierarchy */ +int +delete_hierarchy(const char *dir, Boolean ign_err, Boolean nukedirs) +{ + char *cp1, *cp2; + + cp1 = cp2 = strdup(dir); + if (!fexists(dir)) { + if (!ign_err) + warnx("%s '%s' doesn't really exist", + isdir(dir) ? "directory" : "file", dir); + return !ign_err; + } + else if (nukedirs) { + if (vsystem("%s -r%s %s", REMOVE_CMD, (ign_err ? "f" : ""), dir)) + return 1; + } + else if (isdir(dir) && !issymlink(dir)) { + if (RMDIR(dir) && !ign_err) + return 1; + } + else { + if (REMOVE(dir, ign_err)) + return 1; + } + + if (!nukedirs) + return 0; + while (cp2) { + if ((cp2 = strrchr(cp1, '/')) != NULL) + *cp2 = '\0'; + if (!isemptydir(dir)) + return 0; + if (RMDIR(dir) && !ign_err) { + if (!fexists(dir)) + warnx("directory '%s' doesn't really exist", dir); + else + return 1; + } + /* back up the pathname one component */ + if (cp2) { + cp1 = strdup(dir); + } + } + return 0; +} diff --git a/usr.sbin/pkg_install/lib/str.c b/usr.sbin/pkg_install/lib/str.c new file mode 100644 index 0000000000000..0d9e288b75794 --- /dev/null +++ b/usr.sbin/pkg_install/lib/str.c @@ -0,0 +1,129 @@ +/* + * 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 + * + * Miscellaneous string utilities. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" + +char * +strconcat(const char *s1, const char *s2) +{ + static char tmp[FILENAME_MAX]; + + tmp[0] = '\0'; + strncpy(tmp, s1 ? s1 : s2, FILENAME_MAX); /* XXX: what if both are NULL? */ + if (s1 && s2) + strncat(tmp, s2, FILENAME_MAX - strlen(tmp)); + return tmp; +} + +/* Get a string parameter as a file spec or as a "contents follow -" spec */ +char * +get_dash_string(char **str) +{ + char *s = *str; + + if (*s == '-') + *str = copy_string_adds_newline(s + 1); + else + *str = fileGetContents(s); + return *str; +} + +/* Rather Obvious */ +char * +copy_string(const char *str) +{ + return (str ? strdup(str) : NULL); +} + +/* Rather Obvious but adds a trailing \n newline */ +char * +copy_string_adds_newline(const char *str) +{ + if (str == NULL) { + return (NULL); + } else { + char *copy; + size_t line_length; + + line_length = strlen(str) + 2; + if ((copy = malloc(line_length)) == NULL) + return (NULL); + memcpy(copy, str, line_length - 2); + copy[line_length - 2] = '\n'; /* Adds trailing \n */ + copy[line_length - 1] = '\0'; + + return (copy); + } +} + +/* Return TRUE if 'str' ends in suffix 'suff' */ +Boolean +suffix(const char *str, const char *suff) +{ + char *idx; + Boolean ret = FALSE; + + idx = strrchr(str, '.'); + if (idx && !strcmp(idx + 1, suff)) + ret = TRUE; + return ret; +} + +/* Assuming str has a suffix, brutally murder it! */ +void +nuke_suffix(char *str) +{ + char *idx; + + idx = strrchr(str, '.'); + if (idx) + *idx = '\0'; /* Yow! Don't try this on a const! */ +} + +/* Lowercase a whole string */ +void +str_lowercase(char *str) +{ + while (*str) { + *str = tolower(*str); + ++str; + } +} + +char * +get_string(char *str, int max, FILE *fp) +{ + int len; + + if (!str) + return NULL; + str[0] = '\0'; + while (fgets(str, max, fp)) { + len = strlen(str); + while (len && isspace(str[len - 1])) + str[--len] = '\0'; + if (len) + return str; + } + return NULL; +} diff --git a/usr.sbin/pkg_install/lib/url.c b/usr.sbin/pkg_install/lib/url.c new file mode 100644 index 0000000000000..41017b4c8dea9 --- /dev/null +++ b/usr.sbin/pkg_install/lib/url.c @@ -0,0 +1,154 @@ +/* + * 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 + * + * URL file access utilities. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <err.h> +#include <fetch.h> +#include <sys/wait.h> + +/* + * Try and fetch a file by URL, returning the directory name for where + * it's unpacked, if successful. + */ +char * +fileGetURL(const char *base, const char *spec) +{ + char *cp, *rp; + char fname[FILENAME_MAX]; + char pen[FILENAME_MAX]; + char buf[8192]; + FILE *ftp; + pid_t tpid; + int pfd[2], pstat, r, w = 0; + char *hint; + int fd; + + rp = NULL; + /* Special tip that sysinstall left for us */ + hint = getenv("PKG_ADD_BASE"); + if (!isURL(spec)) { + if (!base && !hint) + return NULL; + /* + * We've been given an existing URL (that's known-good) and now we need + * to construct a composite one out of that and the basename we were + * handed as a dependency. + */ + if (base) { + strcpy(fname, base); + /* + * Advance back two slashes to get to the root of the package + * hierarchy + */ + cp = strrchr(fname, '/'); + if (cp) { + *cp = '\0'; /* chop name */ + cp = strrchr(fname, '/'); + } + if (cp) { + *(cp + 1) = '\0'; + strcat(cp, "All/"); + strcat(cp, spec); +#if defined(__FreeBSD_version) && __FreeBSD_version >= 500039 + strcat(cp, ".tbz"); +#else + strcat(cp, ".tgz"); +#endif + } + else + return NULL; + } + else { + /* + * Otherwise, we've been given an environment variable hinting + * at the right location from sysinstall + */ + strcpy(fname, hint); + strcat(fname, spec); +#if defined(__FreeBSD_version) && __FreeBSD_version >= 500039 + strcat(fname, ".tbz"); +#else + strcat(fname, ".tgz"); +#endif + } + } + else + strcpy(fname, spec); + + if ((ftp = fetchGetURL(fname, Verbose ? "v" : NULL)) == NULL) { + printf("Error: FTP Unable to get %s: %s\n", + fname, fetchLastErrString); + return NULL; + } + + if (isatty(0) || Verbose) + printf("Fetching %s...", fname), fflush(stdout); + pen[0] = '\0'; + if ((rp = make_playpen(pen, 0)) == NULL) { + printf("Error: Unable to construct a new playpen for FTP!\n"); + fclose(ftp); + return NULL; + } + if (pipe(pfd) == -1) { + warn("pipe()"); + cleanup(0); + exit(2); + } + if ((tpid = fork()) == -1) { + warn("pipe()"); + cleanup(0); + exit(2); + } + if (!tpid) { + dup2(pfd[0], 0); + for (fd = getdtablesize() - 1; fd >= 3; --fd) + close(fd); + execl("/usr/bin/tar", "tar", +#if defined(__FreeBSD_version) && __FreeBSD_version >= 500039 + Verbose ? "-xjvf" : "-xjf", +#else + Verbose ? "-xzvf" : "-xzf", +#endif + "-", (char *)0); + _exit(2); + } + close(pfd[0]); + for (;;) { + if ((r = fread(buf, 1, sizeof buf, ftp)) < 1) + break; + if ((w = write(pfd[1], buf, r)) != r) + break; + } + if (ferror(ftp)) + warn("warning: error reading from server"); + fclose(ftp); + close(pfd[1]); + if (w == -1) + warn("warning: error writing to tar"); + tpid = waitpid(tpid, &pstat, 0); + if (Verbose) + printf("tar command returns %d status\n", WEXITSTATUS(pstat)); + if (rp && (isatty(0) || Verbose)) + printf(" Done.\n"); + return rp; +} diff --git a/usr.sbin/pkg_install/lib/version.c b/usr.sbin/pkg_install/lib/version.c new file mode 100644 index 0000000000000..d9c4fe7e070c7 --- /dev/null +++ b/usr.sbin/pkg_install/lib/version.c @@ -0,0 +1,328 @@ +/* + * 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. + * + * Maxim Sobolev + * 31 July 2001 + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "lib.h" +#include <err.h> + +/* + * Routines to assist with PLIST_FMT_VER numbers in the packing + * lists. + * + * Following is the PLIST_FMT_VER history: + * 1.0 - Initial revision; + * 1.1 - When recording/checking checksum of symlink use hash of readlink() + * value instead of the hash of an object this links points to. + * + */ +int +verscmp(Package *pkg, int major, int minor) +{ + int rval = 0; + + if ((pkg->fmtver_maj < major) || (pkg->fmtver_maj == major && + pkg->fmtver_mnr < minor)) + rval = -1; + else if ((pkg->fmtver_maj > major) || (pkg->fmtver_maj == major && + pkg->fmtver_mnr > minor)) + rval = 1; + + return rval; +} + +/* + * split_version(pkgname, endname, epoch, revision) returns a pointer to + * the version portion of a package name and the two special components. + * + * Syntax is: ${PORTNAME}-${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}] + * + * Written by Oliver Eikemeier + * Based on work of Jeremy D. Lea. + */ +static const char * +split_version(const char *pkgname, const char **endname, unsigned long *epoch, unsigned long *revision) +{ + char *ch; + const char *versionstr; + const char *endversionstr; + + if (pkgname == NULL) + errx(2, "%s: Passed NULL pkgname.", __func__); + + /* Look for the last '-' the the pkgname */ + ch = strrchr(pkgname, '-'); + /* Cheat if we are just passed a version, not a valid package name */ + versionstr = ch ? ch + 1 : pkgname; + + /* Look for the last '_' in the version string, advancing the end pointer */ + ch = strrchr(versionstr, '_'); + if (revision != NULL) { + *revision = ch ? strtoul(ch + 1, NULL, 10) : 0; + } + endversionstr = ch; + + /* Look for the last ',' in the remaining version string */ + ch = strrchr(endversionstr ? endversionstr + 1 : versionstr, ','); + if (epoch != NULL) { + *epoch = ch ? strtoul(ch + 1, NULL, 10) : 0; + } + if (ch && !endversionstr) + endversionstr = ch; + + /* set the pointer behind the last character of the version without revision or epoch */ + if (endname) + *endname = endversionstr ? endversionstr : strrchr(versionstr, '\0'); + + return versionstr; +} + +/* + * PORTVERSIONs are composed of components separated by dots. A component + * consists of a version number, a letter and a patchlevel number. This does + * not conform to the porter's handbook, but let us formulate rules that + * fit the current practice and are far simpler than to make decisions + * based on the order of netters and lumbers. Besides, people use versions + * like 10b2 in the ports... + */ + +typedef struct { +#ifdef __LONG_LONG_SUPPORTED + long long n; + long long pl; +#else + long n; + long pl; +#endif + int a; +} version_component; + +/* + * get_component(position, component) gets the value of the next component + * (number - letter - number triple) and returns a pointer to the next character + * after any leading separators + * + * - components are separated by dots + * - characters !~ [a-zA-Z0-9.+*] are treated as separators + * (1.0:2003.09.16 = 1.0.2003.09.16), this may not be what you expect: + * 1.0.1:2003.09.16 < 1.0:2003.09.16 + * - consecutive separators are collapsed (10..1 = 10.1) + * - missing separators are inserted, essentially + * letter number letter => letter number . letter (10a1b2 = 10a1.b2) + * - missing components are assumed to be equal to 0 (10 = 10.0 = 10.0.0) + * - the letter sort order is: [none], a, b, ..., z; numbers without letters + * sort first (10 < 10a < 10b) + * - missing version numbers (in components starting with a letter) sort as -1 + * (a < 0, 10.a < 10) + * - a separator is inserted before the special strings "pl", "alpha", "beta", + * "pre" and "rc". + * - "pl" sorts before every other letter, "alpha", "beta", "pre" and "rc" + * sort as a, b, p and r. (10alpha = 10.a < 10, but 10 < 10a; pl11 < alpha3 + * < 0.1beta2 = 0.1.b2 < 0.1) + * - other strings use only the first letter for sorting, case is ignored + * (1.d2 = 1.dev2 = 1.Development2) + * - The special component `*' is guaranteed to be the smallest possible + * component (2.* < 2pl1 < 2alpha3 < 2.9f7 < 3.*) + * - components separated by `+' are handled by version_cmp below + * + * Oliver Eikemeier + */ + +static const struct { + const char *name; + size_t namelen; + int value; +} stage[] = { + { "pl", 2, 0 }, + { "alpha", 5, 'a'-'a'+1 }, + { "beta", 4, 'b'-'a'+1 }, + { "pre", 3, 'p'-'a'+1 }, + { "rc", 2, 'r'-'a'+1 }, + { NULL, 0, -1 } +}; + +static const char * +get_component(const char *position, version_component *component) +{ + const char *pos = position; + int hasstage = 0, haspatchlevel = 0; + + if (!pos) + errx(2, "%s: Passed NULL position.", __func__); + + /* handle version number */ + if (isdigit(*pos)) { + char *endptr; +#ifdef __LONG_LONG_SUPPORTED + component->n = strtoll(pos, &endptr, 10); +#else + component->n = strtol(pos, &endptr, 10); +#endif + /* should we test for errno == ERANGE? */ + pos = endptr; + } else if (*pos == '*') { + component->n = -2; + do { + pos++; + } while(*pos && *pos != '+'); + } else { + component->n = -1; + hasstage = 1; + } + + /* handle letter */ + if (isalpha(*pos)) { + int c = tolower(*pos); + haspatchlevel = 1; + /* handle special suffixes */ + if (isalpha(pos[1])) { + int i; + for (i = 0; stage[i].name; i++) { + if (strncasecmp(pos, stage[i].name, stage[i].namelen) == 0 + && !isalpha(pos[stage[i].namelen])) { + if (hasstage) { + /* stage to value */ + component->a = stage[i].value; + pos += stage[i].namelen; + } else { + /* insert dot */ + component->a = 0; + haspatchlevel = 0; + } + c = 0; + break; + } + } + } + /* unhandled above */ + if (c) { + /* use the first letter and skip following */ + component->a = c - 'a' + 1; + do { + ++pos; + } while (isalpha(*pos)); + } + } else { + component->a = 0; + haspatchlevel = 0; + } + + if (haspatchlevel) { + /* handle patch number */ + if (isdigit(*pos)) { + char *endptr; +#ifdef __LONG_LONG_SUPPORTED + component->pl = strtoll(pos, &endptr, 10); +#else + component->pl = strtol(pos, &endptr, 10); +#endif + /* should we test for errno == ERANGE? */ + pos = endptr; + } else { + component->pl = -1; + } + } else { + component->pl = 0; + } + + /* skip trailing separators */ + while (*pos && !isdigit(*pos) && !isalpha(*pos) && *pos != '+' && *pos != '*') { + pos++; + } + + return pos; +} + +/* + * version_cmp(pkg1, pkg2) returns -1, 0 or 1 depending on if the version + * components of pkg1 is less than, equal to or greater than pkg2. No + * comparison of the basenames is done. + * + * The port version is defined by: + * ${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}] + * ${PORTEPOCH} supersedes ${PORTVERSION} supersedes ${PORTREVISION}. + * See the commit log for revision 1.349 of ports/Mk/bsd.port.mk + * for more information. + * + * The epoch and revision are defined to be a single number, while the rest + * of the version should conform to the porting guidelines. It can contain + * multiple components, separated by a period, including letters. + * + * The tests allow for significantly more latitude in the version numbers + * than is allowed in the guidelines. No point in enforcing them here. + * That's what portlint is for. + * + * Jeremy D. Lea. + * reimplemented by Oliver Eikemeier + */ +int +version_cmp(const char *pkg1, const char *pkg2) +{ + const char *v1, *v2, *ve1, *ve2; + unsigned long e1, e2, r1, r2; + int result = 0; + + v1 = split_version(pkg1, &ve1, &e1, &r1); + v2 = split_version(pkg2, &ve2, &e2, &r2); + + /* Check epoch, port version, and port revision, in that order. */ + if (e1 != e2) { + result = (e1 < e2 ? -1 : 1); + } + + /* Shortcut check for equality before invoking the parsing routines. */ + if (result == 0 && (ve1 - v1 != ve2 - v2 || strncasecmp(v1, v2, ve1 - v1) != 0)) { + /* Loop over different components (the parts separated by dots). + * If any component differs, we have the basis for an inequality. */ + while(result == 0 && (v1 < ve1 || v2 < ve2)) { + int block_v1 = 0; + int block_v2 = 0; + version_component vc1 = {0, 0, 0}; + version_component vc2 = {0, 0, 0}; + if (v1 < ve1 && *v1 != '+') { + v1 = get_component(v1, &vc1); + } else { + block_v1 = 1; + } + if (v2 < ve2 && *v2 != '+') { + v2 = get_component(v2, &vc2); + } else { + block_v2 = 1; + } + if (block_v1 && block_v2) { + if (v1 < ve1) + v1++; + if (v2 < ve2) + v2++; + } else if (vc1.n != vc2.n) { + result = (vc1.n < vc2.n ? -1 : 1); + } else if (vc1.a != vc2.a) { + result = (vc1.a < vc2.a ? -1 : 1); + } else if (vc1.pl != vc2.pl) { + result = (vc1.pl < vc2.pl ? -1 : 1); + } + } + } + + /* Compare FreeBSD revision numbers. */ + if (result == 0 && r1 != r2) { + result = (r1 < r2 ? -1 : 1); + } + return result; +} |
