diff options
author | joerg <joerg> | 2008-04-26 17:40:01 +0000 |
---|---|---|
committer | joerg <joerg> | 2008-04-26 17:40:01 +0000 |
commit | 1c812f825c9653c8d251df6fd9116a453d7e326b (patch) | |
tree | dce0a9974c00fc1ce3fa404ec789177cd84fcdea /pkgtools | |
parent | d671da09d6c59a5e042137a26598ea8267073c87 (diff) | |
download | pkgsrc-1c812f825c9653c8d251df6fd9116a453d7e326b.tar.gz |
Revert last change, it was not intended to go into HEAD.
Diffstat (limited to 'pkgtools')
20 files changed, 3587 insertions, 1289 deletions
diff --git a/pkgtools/pkg_install/files/add/Makefile.in b/pkgtools/pkg_install/files/add/Makefile.in index 3cf26e92a48..39cdc1b6767 100644 --- a/pkgtools/pkg_install/files/add/Makefile.in +++ b/pkgtools/pkg_install/files/add/Makefile.in @@ -1,4 +1,4 @@ -# $NetBSD: Makefile.in,v 1.17 2008/04/26 14:56:33 joerg Exp $ +# $NetBSD: Makefile.in,v 1.18 2008/04/26 17:40:01 joerg Exp $ srcdir= @srcdir@ @@ -13,9 +13,9 @@ cat1dir= $(mandir)/cat1 CC= @CC@ CCLD= $(CC) -LIBS= -linstall -lfetch -larchive -lbz2 -lz @LIBS@ +LIBS= -linstall @LIBS@ CPPFLAGS= @CPPFLAGS@ -I. -I$(srcdir) -I../lib -DEFS= @DEFS@ -DOPSYS_NAME=\"$(OPSYS)\" -DMACHINE_ARCH=\"$(MACHINE_ARCH)\" -DBINDIR=\"$(sbindir)\" +DEFS= @DEFS@ -DOPSYS_NAME=\"$(OPSYS)\" -DMACHINE_ARCH=\"$(MACHINE_ARCH)\" -DBINDIR=\"$(sbindir)\" -DTAR_CMD=\"@tar@\" -DPAX_CMD=\"@pax@\" CFLAGS= @CFLAGS@ LDFLAGS= @LDFLAGS@ -L../lib @@ -23,7 +23,7 @@ INSTALL= @INSTALL@ PROG= pkg_add -OBJS= main.o perform.o verify.o +OBJS= main.o perform.o futil.o extract.o verify.o all: $(PROG) diff --git a/pkgtools/pkg_install/files/add/add.h b/pkgtools/pkg_install/files/add/add.h index 3f9002b5604..9fbba20125a 100644 --- a/pkgtools/pkg_install/files/add/add.h +++ b/pkgtools/pkg_install/files/add/add.h @@ -1,4 +1,4 @@ -/* $NetBSD: add.h,v 1.11 2008/04/26 14:56:33 joerg Exp $ */ +/* $NetBSD: add.h,v 1.12 2008/04/26 17:40:01 joerg Exp $ */ /* from FreeBSD Id: add.h,v 1.8 1997/02/22 16:09:15 peter Exp */ @@ -35,8 +35,15 @@ extern Boolean NoRecord; extern Boolean Force; extern Boolean Automatic; extern int Replace; +extern char *Mode; +extern char *Owner; +extern char *Group; +extern char *Directory; +extern char *PkgName; +extern char FirstPen[]; int make_hierarchy(char *); +int extract_plist(char *, package_t *); void apply_perms(char *, char **, int); int pkg_perform(lpkg_head_t *); diff --git a/pkgtools/pkg_install/files/add/extract.c b/pkgtools/pkg_install/files/add/extract.c new file mode 100644 index 00000000000..0a303e701dd --- /dev/null +++ b/pkgtools/pkg_install/files/add/extract.c @@ -0,0 +1,340 @@ +/* $NetBSD: extract.c,v 1.18 2008/04/26 17:40:01 joerg Exp $ */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <nbcompat.h> +#if HAVE_SYS_CDEFS_H +#include <sys/cdefs.h> +#endif +#if HAVE_SYS_QUEUE_H +#include <sys/queue.h> +#endif +#ifndef lint +#if 0 +static const char *rcsid = "FreeBSD - Id: extract.c,v 1.17 1997/10/08 07:45:35 charnier Exp"; +#else +__RCSID("$NetBSD: extract.c,v 1.18 2008/04/26 17:40:01 joerg Exp $"); +#endif +#endif + +/* + * FreeBSD install - a package for the installation and maintainance + * of non-core utilities. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Jordan K. Hubbard + * 18 July 1993 + * + * This is the package extraction code for the add module. + * + */ + +#if HAVE_ERR_H +#include <err.h> +#endif +#include "lib.h" +#include "add.h" + +lfile_head_t files; +lfile_head_t perms; + +/* + * Copy files from staging area to todir. + * This is only used when the files cannot be directory rename()ed. + */ +static void +pushout(char *todir) +{ + pipe_to_system_t *pipe_to; + char *file_args[4]; + char **perm_argv; + int perm_argc = 1; + lfile_t *lfp; + int count; + + /* set up arguments to run "pax -r -w -p e" */ + file_args[0] = (char *)strrchr(PAX_CMD, '/'); + if (file_args[0] == NULL) + file_args[0] = PAX_CMD; + else + file_args[0]++; + file_args[1] = "-rwpe"; + file_args[2] = todir; + file_args[3] = NULL; + + /* count entries for files */ + count = 0; + TAILQ_FOREACH(lfp, &files, lf_link) + count++; + + if (count > 0) { + /* open pipe, feed it files, close pipe */ + pipe_to = pipe_to_system_begin(PAX_CMD, file_args, NULL); + while ((lfp = TAILQ_FIRST(&files)) != NULL) { + fprintf(pipe_to->fp, "%s\n", lfp->lf_name); + TAILQ_REMOVE(&files, lfp, lf_link); + free(lfp); + } + pipe_to_system_end(pipe_to); + } + + /* count entries for permissions */ + count = 0; + TAILQ_FOREACH(lfp, &perms, lf_link) + count++; + + if (count > 0) { + perm_argv = malloc((count + 1) * sizeof(char *)); + perm_argc = 0; + TAILQ_FOREACH(lfp, &perms, lf_link) + perm_argv[perm_argc++] = lfp->lf_name; + perm_argv[perm_argc] = NULL; + apply_perms(todir, perm_argv, perm_argc); + + /* empty the perm list */ + while ((lfp = TAILQ_FIRST(&perms)) != NULL) { + TAILQ_REMOVE(&perms, lfp, lf_link); + free(lfp); + } + free(perm_argv); + } +} + +static void +rollback(char *name, char *home, plist_t *start, plist_t *stop) +{ + plist_t *q; + char try[MaxPathSize], bup[MaxPathSize], *dir; + + dir = home; + for (q = start; q != stop; q = q->next) { + if (q->type == PLIST_FILE) { + (void) snprintf(try, sizeof(try), "%s/%s", dir, q->name); + if (make_preserve_name(bup, sizeof(bup), name, try) && fexists(bup)) { +#if HAVE_CHFLAGS + (void) chflags(try, 0); +#endif + (void) unlink(try); + if (rename(bup, try)) + warnx("rollback: unable to rename %s back to %s", bup, try); + } + } else if (q->type == PLIST_CWD) { + if (strcmp(q->name, ".")) + dir = q->name; + else + dir = home; + } + } +} + + +/* + * Return 0 on error, 1 for success. + */ +int +extract_plist(char *home, package_t *pkg) +{ + plist_t *p = pkg->head; + char *last_file; + char *last_chdir; + Boolean preserve; + lfile_t *lfp; + + TAILQ_INIT(&files); + TAILQ_INIT(&perms); + + last_chdir = 0; + preserve = find_plist_option(pkg, "preserve") ? TRUE : FALSE; + + /* Reset the world */ + Owner = NULL; + Group = NULL; + Mode = NULL; + last_file = NULL; + Directory = home; + + if (!NoRecord) { + /* Open Package Database for writing */ + if (!pkgdb_open(ReadWrite)) { + cleanup(0); + err(EXIT_FAILURE, "can't open pkgdb"); + } + } + /* Do it */ + while (p) { + char cmd[MaxPathSize]; + + switch (p->type) { + case PLIST_NAME: + PkgName = p->name; + if (Verbose) + printf("extract: Package name is %s\n", p->name); + break; + + case PLIST_FILE: + last_file = p->name; + if (Verbose) + printf("extract: %s/%s\n", Directory, p->name); + if (!Fake) { + char try[MaxPathSize]; + + if (strrchr(p->name, '\'')) { + cleanup(0); + errx(2, "Bogus filename \"%s\"", p->name); + } + + /* first try to rename it into place */ + (void) snprintf(try, sizeof(try), "%s/%s", Directory, p->name); + if (fexists(try)) { +#if HAVE_CHFLAGS + (void) chflags(try, 0); /* XXX hack - if truly immutable, rename fails */ +#endif + if (preserve && PkgName) { + char pf[MaxPathSize]; + + if (make_preserve_name(pf, sizeof(pf), PkgName, try)) { + if (rename(try, pf)) { + warnx( + "unable to back up %s to %s, aborting pkg_add", + try, pf); + rollback(PkgName, home, pkg->head, p); + return 0; + } + } + } + } + if (rename(p->name, try) == 0) { + if (!NoRecord) { + /* note in pkgdb */ + char *s, t[MaxPathSize]; + int rc; + + (void) snprintf(t, sizeof(t), "%s/%s", Directory, p->name); + + s = pkgdb_retrieve(t); +#ifdef PKGDB_DEBUG + printf("pkgdb_retrieve(\"%s\")=\"%s\"\n", t, s); /* pkgdb-debug - HF */ +#endif + if (s) + warnx("Overwriting %s - pkg %s bogus/conflicting?", t, s); + else { + rc = pkgdb_store(t, PkgName); +#ifdef PKGDB_DEBUG + printf("pkgdb_store(\"%s\", \"%s\") = %d\n", t, PkgName, rc); /* pkgdb-debug - HF */ +#endif + + } + } + + /* try to add to list of perms to be changed and run in bulk. */ + if (p->name[0] == '/') + pushout(Directory); + + LFILE_ADD(&perms, lfp, p->name); + } else { + /* rename failed, try copying with a big tar command */ + if (last_chdir != Directory) { + if (last_chdir != NULL) + pushout(last_chdir); + last_chdir = Directory; + } else if (p->name[0] == '/') { + pushout(Directory); + } + + if (!NoRecord) { + /* note in pkgdb */ + /* XXX would be better to store in PUSHOUT, but + * that would probably affect too much code I prefer + * not to touch - HF */ + + char *s, t[MaxPathSize]; + int rc; + + LFILE_ADD(&files, lfp, p->name); + LFILE_ADD(&perms, lfp, p->name); + if (p->name[0] == '/') + errx(EXIT_FAILURE, "File names must not be absolute (%s).", p->name); + else { + (void) snprintf(t, sizeof(t), "%s/%s", Directory, p->name); + } + + s = pkgdb_retrieve(t); +#ifdef PKGDB_DEBUG + printf("pkgdb_retrieve(\"%s\")=\"%s\"\n", t, s); /* pkgdb-debug - HF */ +#endif + if (s) + warnx("Overwriting %s - pkg %s bogus/conflicting?", t, s); + else { + rc = pkgdb_store(t, PkgName); +#ifdef PKGDB_DEBUG + printf("pkgdb_store(\"%s\", \"%s\") = %d\n", t, PkgName, rc); /* pkgdb-debug - HF */ +#endif + } + } + } + } + break; + + case PLIST_CWD: + if (Verbose) + printf("extract: CWD to %s\n", p->name); + pushout(Directory); + if (strcmp(p->name, ".")) { + if (!Fake && make_hierarchy(p->name) == FAIL) { + cleanup(0); + errx(2, "unable to make directory '%s'", p->name); + } + Directory = p->name; + } else + Directory = home; + break; + + case PLIST_CMD: + format_cmd(cmd, sizeof(cmd), p->name, Directory, last_file); + pushout(Directory); + printf("Executing '%s'\n", cmd); + if (!Fake && system(cmd)) + warnx("command '%s' failed", cmd); + break; + + case PLIST_CHMOD: + pushout(Directory); + Mode = p->name; + break; + + case PLIST_CHOWN: + pushout(Directory); + Owner = p->name; + break; + + case PLIST_CHGRP: + pushout(Directory); + Group = p->name; + break; + + case PLIST_COMMENT: + break; + + case PLIST_IGNORE: + p = p->next; + break; + + default: + break; + } + p = p->next; + } + pushout(Directory); + if (!NoRecord) + pkgdb_close(); + return 1; +} diff --git a/pkgtools/pkg_install/files/add/futil.c b/pkgtools/pkg_install/files/add/futil.c new file mode 100644 index 00000000000..ee4c2984fd3 --- /dev/null +++ b/pkgtools/pkg_install/files/add/futil.c @@ -0,0 +1,151 @@ +/* $NetBSD: futil.c,v 1.11 2008/04/26 17:40:01 joerg Exp $ */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <nbcompat.h> +#if HAVE_SYS_CDEFS_H +#include <sys/cdefs.h> +#endif +#ifndef lint +#if 0 +static const char *rcsid = "from FreeBSD Id: futil.c,v 1.7 1997/10/08 07:45:39 charnier Exp"; +#else +__RCSID("$NetBSD: futil.c,v 1.11 2008/04/26 17:40:01 joerg Exp $"); +#endif +#endif + +/* + * FreeBSD install - a package for the installation and maintainance + * of non-core utilities. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Jordan K. Hubbard + * 18 July 1993 + * + * Miscellaneous file access utilities. + * + */ + +#if HAVE_ERR_H +#include <err.h> +#endif +#include "lib.h" +#include "add.h" + +/* + * Assuming dir is a desired directory name, make it and all intervening + * directories necessary. + */ +int +make_hierarchy(char *dir) +{ + char *cp1, *cp2; + char *argv[2]; + + argv[0] = dir; + argv[1] = NULL; + + if (dir[0] == '/') + cp1 = cp2 = dir + 1; + else + cp1 = cp2 = dir; + while (cp2) { + if ((cp2 = strchr(cp1, '/')) != NULL) + *cp2 = '\0'; + if (fexists(dir)) { + if (!(isdir(dir) || islinktodir(dir))) + return FAIL; + } else { + if (fexec("mkdir", dir, NULL)) + return FAIL; + apply_perms(NULL, argv, 1); + } + /* Put it back */ + if (cp2) { + *cp2 = '/'; + cp1 = cp2 + 1; + } + } + return SUCCESS; +} + +/* + * Using permission defaults, apply them as necessary + */ +void +apply_perms(char *dir, char **args, int nargs) +{ + char *cd_to; + char owner_group[128]; + const char **argv; + int i; + + argv = malloc((nargs + 4) * sizeof(char *)); + /* + * elements 0..2 are set later depending on Mode. + * args is a NULL terminated list of file names. + * by appending them to argv, argv becomes NULL terminated also. + */ + for (i = 0; i <= nargs; i++) + argv[i + 3] = args[i]; + + if (!dir || args[0][0] == '/') /* absolute path? */ + cd_to = "/"; + else + cd_to = dir; + + if (Mode) { + argv[0] = CHMOD_CMD; + argv[1] = "-R"; + argv[2] = Mode; + if (pfcexec(cd_to, argv[0], argv)) + warnx("couldn't change modes of '%s' ... to '%s'", + args[0], Mode); + } + if (Owner != NULL && Group != NULL) { + if (snprintf(owner_group, sizeof(owner_group), + "%s:%s", Owner, Group) > sizeof(owner_group)) { + warnx("'%s:%s' is too long (%lu max)", + Owner, Group, (unsigned long) sizeof(owner_group)); + free(argv); + return; + } + argv[0] = CHOWN_CMD; + argv[1] = "-R"; + argv[2] = owner_group; + if (pfcexec(cd_to, argv[0], argv)) + warnx("couldn't change owner/group of '%s' ... to '%s:%s'", + args[0], Owner, Group); + free(argv); + return; + } + if (Owner != NULL) { + argv[0] = CHOWN_CMD; + argv[1] = "-R"; + argv[2] = Owner; + if (pfcexec(cd_to, argv[0], argv)) + warnx("couldn't change owner of '%s' ... to '%s'", + args[0], Owner); + free(argv); + + return; + } + if (Group != NULL) { + argv[0] = CHGRP_CMD; + argv[1] = "-R"; + argv[2] = Group; + if (pfcexec(cd_to, argv[0], argv)) + warnx("couldn't change group of '%s' ... to '%s'", + args[0], Group); + } + free(argv); +} diff --git a/pkgtools/pkg_install/files/add/main.c b/pkgtools/pkg_install/files/add/main.c index 1e68005cff9..0a722e9b168 100644 --- a/pkgtools/pkg_install/files/add/main.c +++ b/pkgtools/pkg_install/files/add/main.c @@ -1,4 +1,4 @@ -/* $NetBSD: main.c,v 1.15 2008/04/26 14:56:34 joerg Exp $ */ +/* $NetBSD: main.c,v 1.16 2008/04/26 17:40:01 joerg Exp $ */ #if HAVE_CONFIG_H #include "config.h" @@ -11,7 +11,7 @@ #if 0 static char *rcsid = "from FreeBSD Id: main.c,v 1.16 1997/10/08 07:45:43 charnier Exp"; #else -__RCSID("$NetBSD: main.c,v 1.15 2008/04/26 14:56:34 joerg Exp $"); +__RCSID("$NetBSD: main.c,v 1.16 2008/04/26 17:40:01 joerg Exp $"); #endif #endif @@ -60,6 +60,12 @@ Boolean NoInstall = FALSE; Boolean NoRecord = FALSE; Boolean Automatic = FALSE; +char *Mode = NULL; +char *Owner = NULL; +char *Group = NULL; +char *PkgName = NULL; +char *Directory = NULL; +char FirstPen[MaxPathSize]; int Replace = 0; static void @@ -67,7 +73,7 @@ usage(void) { (void) fprintf(stderr, "%s\n%s\n%s\n", "usage: pkg_add [-AfhILnRuVv] [-K pkg_dbdir] [-m machine] [-p prefix]", - " [-s verification-type] [-W viewbase] [-w view]", + " [-s verification-type] [-t template] [-W viewbase] [-w view]", " [[ftp|http]://[user[:password]@]host[:port]][/path/]pkg-name ..."); exit(1); } @@ -120,11 +126,13 @@ main(int argc, char **argv) Prefix = optarg; break; -#if 0 case 's': set_verification(optarg); break; -#endif + + case 't': + strlcpy(FirstPen, optarg, sizeof(FirstPen)); + break; case 'u': Replace++; diff --git a/pkgtools/pkg_install/files/add/perform.c b/pkgtools/pkg_install/files/add/perform.c index fa72d03576e..60696fb67ca 100644 --- a/pkgtools/pkg_install/files/add/perform.c +++ b/pkgtools/pkg_install/files/add/perform.c @@ -1,4 +1,5 @@ -/* $NetBSD: perform.c,v 1.71 2008/04/26 14:56:34 joerg Exp $ */ +/* $NetBSD: perform.c,v 1.72 2008/04/26 17:40:01 joerg Exp $ */ + #if HAVE_CONFIG_H #include "config.h" #endif @@ -6,1223 +7,1047 @@ #if HAVE_SYS_CDEFS_H #include <sys/cdefs.h> #endif -__RCSID("$NetBSD: perform.c,v 1.71 2008/04/26 14:56:34 joerg Exp $"); - -/*- - * Copyright (c) 2003 Grant Beattie <grant@NetBSD.org> - * Copyright (c) 2005 Dieter Baron <dillo@NetBSD.org> - * Copyright (c) 2007 Roland Illig <rillig@NetBSD.org> - * Copyright (c) 2008 Joerg Sonnenberger <joerg@NetBSD.org> - * All rights reserved. +#if HAVE_SYS_QUEUE_H +#include <sys/queue.h> +#endif +#ifndef lint +#if 0 +static const char *rcsid = "from FreeBSD Id: perform.c,v 1.44 1997/10/13 15:03:46 jkh Exp"; +#else +__RCSID("$NetBSD: perform.c,v 1.72 2008/04/26 17:40:01 joerg Exp $"); +#endif +#endif + +/* + * FreeBSD install - a package for the installation and maintainance + * of non-core utilities. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: - * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. + * 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 add module. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``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 - * COPYRIGHT HOLDERS OR CONTRIBUTORS 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. */ -#include <sys/utsname.h> -#include <archive.h> -#include <archive_entry.h> +#if HAVE_ASSERT_H +#include <assert.h> +#endif +#if HAVE_ERR_H #include <err.h> +#endif +#if HAVE_ERRNO_H #include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - +#endif +#include "defs.h" #include "lib.h" #include "add.h" +#include "verify.h" -struct pkg_meta { - char *meta_contents; - char *meta_comment; - char *meta_desc; - char *meta_mtree; - char *meta_build_version; - char *meta_build_info; - char *meta_size_pkg; - char *meta_size_all; - char *meta_required_by; - char *meta_display; - char *meta_install; - char *meta_deinstall; - char *meta_preserve; - char *meta_views; - char *meta_installed_info; -}; - -struct pkg_task { - const char *pkgname; - - const char *prefix; - const char *install_prefix; - - char *logdir; - char *other_version; - - package_t plist; - - struct pkg_meta meta_data; +#if HAVE_INTTYPES_H +#include <inttypes.h> +#endif +#if HAVE_SIGNAL_H +#include <signal.h> +#endif +#if HAVE_STRING_H +#include <string.h> +#endif +#if HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if HAVE_SYS_UTSNAME_H +#include <sys/utsname.h> +#endif - struct archive *archive; - struct archive_entry *entry; +static char LogDir[MaxPathSize]; +static int zapLogDir; /* Should we delete LogDir? */ - char *buildinfo[BI_ENUM_COUNT]; +static package_t Plist; +static char *Home; - size_t dep_length, dep_allocated; - char **dependencies; -}; +static lfile_head_t files; -static const struct pkg_meta_desc { - size_t entry_offset; - const char *entry_filename; - int required_file; - mode_t perm; -} pkg_meta_descriptors[] = { - { offsetof(struct pkg_meta, meta_contents), CONTENTS_FNAME, 1, 0644 }, - { offsetof(struct pkg_meta, meta_comment), COMMENT_FNAME, 1, 0444}, - { offsetof(struct pkg_meta, meta_desc), DESC_FNAME, 1, 0444}, - { offsetof(struct pkg_meta, meta_install), INSTALL_FNAME, 0, 0555 }, - { offsetof(struct pkg_meta, meta_deinstall), DEINSTALL_FNAME, 0, 0555 }, - { offsetof(struct pkg_meta, meta_display), DISPLAY_FNAME, 0, 0444 }, - { offsetof(struct pkg_meta, meta_mtree), MTREE_FNAME, 0, 0444 }, - { offsetof(struct pkg_meta, meta_build_version), BUILD_VERSION_FNAME, 0, 0444 }, - { offsetof(struct pkg_meta, meta_build_info), BUILD_INFO_FNAME, 0, 0444 }, - { offsetof(struct pkg_meta, meta_size_pkg), SIZE_PKG_FNAME, 0, 0444 }, - { offsetof(struct pkg_meta, meta_size_all), SIZE_ALL_FNAME, 0, 0444 }, - { offsetof(struct pkg_meta, meta_preserve), PRESERVE_FNAME, 0, 0444 }, - { offsetof(struct pkg_meta, meta_views), VIEWS_FNAME, 0, 0444 }, - { offsetof(struct pkg_meta, meta_required_by), REQUIRED_BY_FNAME, 0, 0644 }, - { offsetof(struct pkg_meta, meta_installed_info), INSTALLED_INFO_FNAME, 0, 0644 }, - { 0, NULL, 0 }, +/* used in build information */ +enum { + Good, + Missing, + Warning, + Fatal }; -static int pkg_do(const char *, int); - -static int -mkdir_p(const char *path) +static void +normalise_platform(struct utsname *host_name) { - return fexec(MKDIR_CMD, "-p", path, (void *)NULL); +#ifdef NUMERIC_VERSION_ONLY + size_t span; + + span = strspn(host_name->release, "0123456789."); + host_name->release[span] = '\0'; +#endif } -/* - * Read meta data from archive. - * Bail out if a required entry is missing or entries are in the wrong order. - */ +/* Read package build information */ static int -read_meta_data(struct pkg_task *pkg) +read_buildinfo(char **buildinfo) { - const struct pkg_meta_desc *descr, *last_descr; - const char *fname; - char **target; - int64_t size; - int r, found_required; - - found_required = 0; - - last_descr = 0; - while ((r = archive_read_next_header(pkg->archive, &pkg->entry)) == - ARCHIVE_OK) { - fname = archive_entry_pathname(pkg->entry); - - for (descr = pkg_meta_descriptors; descr->entry_filename; - ++descr) { - if (strcmp(descr->entry_filename, fname) == 0) - break; - } - if (descr->entry_filename == NULL) - break; + char *key; + char *line; + size_t len; + FILE *fp; - if (descr->required_file) - ++found_required; - - target = (char **)((char *)&pkg->meta_data + - descr->entry_offset); - if (*target) { - warnx("duplicate entry, package corrupt"); - return -1; - } - if (descr < last_descr) { - warnx("misordered package"); - return -1; - } - last_descr = descr; - - size = archive_entry_size(pkg->entry); - if (size > SSIZE_MAX - 1) { - warnx("package meta data too large to process"); - return -1; - } - if ((*target = malloc(size + 1)) == NULL) - err(2, "cannot allocate meta data"); - if (archive_read_data(pkg->archive, *target, size) != size) { - warn("cannot read package meta data"); - return -1; - } - (*target)[size] = '\0'; + if ((fp = fopen(BUILD_INFO_FNAME, "r")) == NULL) { + warnx("unable to open %s file.", BUILD_INFO_FNAME); + return 0; } - if (r != ARCHIVE_OK) - pkg->entry = NULL; + while ((line = fgetln(fp, &len)) != NULL) { + if (line[len - 1] == '\n') + line[len - 1] = '\0'; - for (descr = pkg_meta_descriptors; descr->entry_filename; ++descr) { - if (descr->required_file) - --found_required; - } + if ((key = strsep(&line, "=")) == NULL) + continue; - return !found_required ? 0 : -1; -} + /* + * pkgsrc used to create the BUILDINFO file using + * "key= value", so skip the space if it's there. + */ + if (line == NULL) + continue; + if (line[0] == ' ') + line += sizeof(char); -/* - * Free meta data. - */ -static void -free_meta_data(struct pkg_task *pkg) -{ - const struct pkg_meta_desc *descr; - char **target; - - for (descr = pkg_meta_descriptors; descr->entry_filename; ++descr) { - target = (char **)((char *)&pkg->meta_data + - descr->entry_offset); - free(*target); - *target = NULL; + /* + * we only care about opsys, arch, version, and + * dependency recommendations + */ + if (line[0] != '\0') { + if (strcmp(key, "OPSYS") == 0) + buildinfo[BI_OPSYS] = strdup(line); + else if (strcmp(key, "OS_VERSION") == 0) + buildinfo[BI_OS_VERSION] = strdup(line); + else if (strcmp(key, "MACHINE_ARCH") == 0) + buildinfo[BI_MACHINE_ARCH] = strdup(line); + else if (strcmp(key, "IGNORE_RECOMMENDED") == 0) + buildinfo[BI_IGNORE_RECOMMENDED] = strdup(line); + else if (strcmp(key, "USE_ABI_DEPENDS") == 0) + buildinfo[BI_USE_ABI_DEPENDS] = strdup(line); + } + } + (void) fclose(fp); + if (buildinfo[BI_OPSYS] == NULL || + buildinfo[BI_OS_VERSION] == NULL || + buildinfo[BI_MACHINE_ARCH] == NULL) { + warnx("couldn't extract build information from package."); + return 0; } + return 1; } -/* - * Parse PLIST and populate pkg. - */ static int -pkg_parse_plist(struct pkg_task *pkg) +sanity_check(const char *pkg) { - plist_t *p; - - parse_plist(&pkg->plist, pkg->meta_data.meta_contents); - if ((p = find_plist(&pkg->plist, PLIST_NAME)) == NULL) { - warnx("Invalid PLIST: missing @name"); - return -1; - } - pkg->pkgname = p->name; - if ((p = find_plist(&pkg->plist, PLIST_CWD)) == NULL) { - warnx("Invalid PLIST: missing @cwd"); - return -1; + int errc = 0; + + if (!fexists(CONTENTS_FNAME)) { + warnx("package %s has no CONTENTS file!", pkg); + errc = 1; + } else if (!fexists(COMMENT_FNAME)) { + warnx("package %s has no COMMENT file!", pkg); + errc = 1; + } else if (!fexists(DESC_FNAME)) { + warnx("package %s has no DESC file!", pkg); + errc = 1; } - /* XXX change first @cwd in PLIST? */ - pkg->prefix = p->name; - pkg->install_prefix = Prefix != NULL ? Prefix : pkg->prefix; - - return 0; -} - -/* - * Helper function to extract value from a string of the - * form key=value ending at eol. - */ -static char * -dup_value(const char *line, const char *eol) -{ - const char *key; - char *val; - - key = strchr(line, '='); - val = malloc(eol - key); - if (val == NULL) - err(2, "malloc failed"); - memcpy(val, key + 1, eol - key - 1); - val[eol - key - 1] = '\0'; - return val; + return errc; } +/* install a pre-requisite package. Returns 1 if it installed it */ static int -check_already_installed(struct pkg_task *pkg) +installprereq(const char *name, int *errc, int doupdate) { - char *filename; - int fd; - - if (Force) - return -1; - - filename = pkgdb_pkg_file(pkg->pkgname, CONTENTS_FNAME); - fd = open(filename, O_RDONLY); - free(filename); - if (fd == -1) - return -1; + int ret; + ret = 0; - /* We can only arrive here for explicitly requested packages. */ - if (!Automatic && is_automatic_installed(pkg->pkgname)) { - if (Fake || - mark_as_automatic_installed(pkg->pkgname, 0) == 0) - warnx("package `%s' was already installed as " - "dependency, now marked as installed " - "manually", pkg->pkgname); + if (Verbose) + printf("Loading it from %s.\n", name); + path_setenv("PKG_PATH"); + + if (fexec_skipempty(BINDIR "/pkg_add", "-K", _pkgdb_getPKGDB_DIR(), + "-s", get_verification(), + doupdate > 1 ? "-uu" : (doupdate ? "-u" : ""), + Fake ? "-n" : "", + NoView ? "-L" : "", + View ? "-w" : "", View ? View : "", + Viewbase ? "-W" : "", Viewbase ? Viewbase : "", + Force ? "-f" : "", + Prefix ? "-p" : "", Prefix ? Prefix : "", + Verbose ? "-v" : "", + OverrideMachine ? "-m" : "", + OverrideMachine ? OverrideMachine : "", + NoInstall ? "-I" : "", + "-A", name, NULL)) { + warnx("autoload of dependency `%s' failed%s", + name, Force ? " (proceeding anyway)" : "!"); + if (!Force) + ++(*errc); } else { - warnx("package `%s' already recorded as installed", - pkg->pkgname); + ret = 1; } - return 0; + return ret; } static int -check_other_installed(struct pkg_task *pkg) +pkg_do_installed(int *replacing, char replace_via[MaxPathSize], char replace_to[MaxPathSize], + Boolean is_depoted_pkg, const char *dbdir) { - FILE *f, *f_pkg; - size_t len; - char *pkgbase, *iter, *filename; - package_t plist; - plist_t *p; - int status; + char replace_from[MaxPathSize]; + char *s; + char buf[MaxPathSize]; + char *best_installed; - if ((pkgbase = strdup(pkg->pkgname)) == NULL) { - warnx("strdup failed"); - return -1; - } - if ((iter = strrchr(pkgbase, '-')) == NULL) { - free(pkgbase); - warnx("Invalid package name %s", pkg->pkgname); - return -1; - } - *iter = '\0'; - pkg->other_version = find_best_matching_installed_pkg(pkgbase); - free(pkgbase); - if (pkg->other_version == NULL) - return 0; + const size_t replace_via_size = MaxPathSize; + const size_t replace_to_size = MaxPathSize; - if (!Replace) { - /* XXX This is redundant to the implicit conflict check. */ - warnx("A different version of %s is already installed: %s", - pkg->pkgname, pkg->other_version); + if ((s = strrchr(PkgName, '-')) == NULL) { + warnx("Package name %s does not contain a version, bailing out", PkgName); return -1; } + + /* + * See if the pkg is already installed. If so, we might want to + * upgrade/replace it. Otherwise, just return and let pkg_do work. + */ + (void) snprintf(buf, sizeof(buf), "%.*s[0-9]*", + (int)(s - PkgName) + 1, PkgName); + best_installed = find_best_matching_installed_pkg(buf); + if (best_installed == NULL) + return 0; - filename = pkgdb_pkg_file(pkg->other_version, REQUIRED_BY_FNAME); - errno = 0; - f = fopen(filename, "r"); - free(filename); - if (f == NULL) { - if (errno == ENOENT) { - /* No packages depend on this, so everything is well. */ - return 0; + if (!Replace || Fake) { + if (is_depoted_pkg) { + free(best_installed); + return 0; + } else { + warnx("other version '%s' already installed", best_installed); + free(best_installed); + return 1; /* close enough for government work */ } - warnx("Can't open +REQUIRED_BY of %s", pkg->other_version); - return -1; } - status = 0; - - while ((iter = fgetln(f, &len)) != NULL) { - if (iter[len - 1] == '\n') - iter[len - 1] = '\0'; - filename = pkgdb_pkg_file(iter, CONTENTS_FNAME); - if ((f_pkg = fopen(filename, "r")) == NULL) { - warnx("Can't open +CONTENTS of depending package %s", - iter); - fclose(f); - return -1; + /* XXX Should list the steps in Fake mode */ + snprintf(replace_from, sizeof(replace_from), "%s/%s/" REQUIRED_BY_FNAME, + dbdir, best_installed); + snprintf(replace_via, replace_via_size, "%s/.%s." REQUIRED_BY_FNAME, + dbdir, best_installed); + snprintf(replace_to, replace_to_size, "%s/%s/" REQUIRED_BY_FNAME, + dbdir, PkgName); + + if (Verbose) + printf("Upgrading %s to %s.\n", best_installed, PkgName); + + if (fexists(replace_from)) { /* Are there any dependencies? */ + /* + * Upgrade step 1/4: Check if the new version is ok with all pkgs + * (from +REQUIRED_BY) that require this pkg + */ + FILE *rb; /* +REQUIRED_BY file */ + char pkg2chk[MaxPathSize]; + + rb = fopen(replace_from, "r"); + if (! rb) { + warnx("Cannot open '%s' for reading%s", replace_from, + Force ? " (proceeding anyways)" : ""); + if (Force) + goto ignore_replace_depends_check; + else + return -1; } - plist.head = plist.tail = NULL; - read_plist(&plist, f_pkg); - fclose(f_pkg); - for (p = plist.head; p != NULL; p = p->next) { - if (p->type == PLIST_IGNORE) { - p = p->next; - continue; - } else if (p->type != PLIST_PKGDEP) - continue; - /* - * XXX This is stricter than necessary. - * XXX One pattern might be fulfilled by - * XXX a different package and still need this - * XXX one for a different pattern. + while (fgets(pkg2chk, sizeof(pkg2chk), rb)) { + package_t depPlist; + FILE *depf; + plist_t *depp; + char depC[MaxPathSize]; + + depPlist.head = depPlist.tail = NULL; + + s = strrchr(pkg2chk, '\n'); + if (s) + *s = '\0'; /* strip trailing '\n' */ + + /* + * step into pkg2chk, read it's +CONTENTS file and see if + * all @pkgdep lines agree with PkgName (using pkg_match()) */ - if (pkg_match(p->name, pkg->other_version) == 0) - continue; - if (pkg_match(p->name, pkg->pkgname) == 1) - continue; /* Both match, ok. */ - warnx("Dependency of %s fulfilled by %s, but not by %s", - iter, pkg->other_version, pkg->pkgname); - if (!Force) - status = -1; - break; + snprintf(depC, sizeof(depC), "%s/%s/%s", dbdir, pkg2chk, CONTENTS_FNAME); + depf = fopen(depC , "r"); + if (depf == NULL) { + warnx("Cannot check depends in '%s'%s", depC, + Force ? " (proceeding anyways)" : "!" ); + if (Force) + goto ignore_replace_depends_check; + else + return -1; + } + read_plist(&depPlist, depf); + fclose(depf); + + for (depp = depPlist.head; depp; depp = depp->next) { + char base_new[MaxPathSize]; + char base_exist[MaxPathSize]; + char *s2; + + if (depp->type != PLIST_PKGDEP) + continue; + + /* + * Prepare basename (no versions) of both pkgs, + * to see if we want to compare against that + * one at all. + */ + strlcpy(base_new, PkgName, sizeof(base_new)); + s2 = strpbrk(base_new, "<>[]?*{"); /* } */ + if (s2) + *s2 = '\0'; + else { + s2 = strrchr(base_new, '-'); + if (s2) + *s2 = '\0'; + } + strlcpy(base_exist, depp->name, sizeof(base_exist)); + s2 = strpbrk(base_exist, "<>[]?*{"); /* } */ + if (s2) + *s2 = '\0'; + else { + s2 = strrchr(base_exist, '-'); + if (s2) + *s2 = '\0'; + } + if (strcmp(base_new, base_exist) == 0) { + /* Same pkg, so do the interesting compare */ + if (pkg_match(depp->name, PkgName)) { + if (Verbose) + printf("@pkgdep check: %s is ok for %s (in %s pkg)\n", + PkgName, depp->name, pkg2chk); + } else { + printf("Package %s requires %s, \n\tCannot replace with %s%s\n", + pkg2chk, depp->name, PkgName, + Force? " (proceeding anyways)" : "!"); + if (! Force) + return -1; + } + } + } } - free_plist(&plist); - } + fclose(rb); - fclose(f); - - return status; -} - -/* - * Read package build information from meta data. - */ -static int -read_buildinfo(struct pkg_task *pkg) -{ - const char *data, *eol, *next_line; - - data = pkg->meta_data.meta_build_info; +ignore_replace_depends_check: + /* + * Upgrade step 2/4: Do the actual update by moving aside + * the +REQUIRED_BY file, deinstalling the old pkg, adding + * the new one and moving the +REQUIRED_BY file back + * into place (finished in step 3/4) + */ + if (Verbose) + printf("mv %s %s\n", replace_from, replace_via); + if (rename(replace_from, replace_via) != 0) + err(EXIT_FAILURE, "renaming \"%s\" to \"%s\" failed", replace_from, replace_via); - for (; *data != '\0'; data = next_line) { - if ((eol = strchr(data, '\n')) == NULL) { - eol = data + strlen(data); - next_line = eol; - } else - next_line = eol + 1; - - if (strncmp(data, "OPSYS=", 6) == 0) - pkg->buildinfo[BI_OPSYS] = dup_value(data, eol); - else if (strncmp(data, "OS_VERSION=", 11) == 0) - pkg->buildinfo[BI_OS_VERSION] = dup_value(data, eol); - else if (strncmp(data, "MACHINE_ARCH=", 13) == 0) - pkg->buildinfo[BI_MACHINE_ARCH] = dup_value(data, eol); - else if (strncmp(data, "IGNORE_RECOMMENDED=", 19) == 0) - pkg->buildinfo[BI_IGNORE_RECOMMENDED] = dup_value(data, - eol); - else if (strncmp(data, "USE_ABI_DEPENDS=", 16) == 0) - pkg->buildinfo[BI_USE_ABI_DEPENDS] = dup_value(data, - eol); - } - if (pkg->buildinfo[BI_OPSYS] == NULL || - pkg->buildinfo[BI_OS_VERSION] == NULL || - pkg->buildinfo[BI_MACHINE_ARCH] == NULL) { - warnx("Not all required build information are present."); - return -1; + *replacing = 1; } - if ((pkg->buildinfo[BI_USE_ABI_DEPENDS] != NULL && - strcasecmp(pkg->buildinfo[BI_USE_ABI_DEPENDS], "YES") != 0) || - (pkg->buildinfo[BI_IGNORE_RECOMMENDED] != NULL && - strcasecmp(pkg->buildinfo[BI_IGNORE_RECOMMENDED], "NO") != 0)) { - warnx("%s was built to ignore ABI dependencies", pkg->pkgname); + if (Verbose) { + printf("%s/pkg_delete -K %s '%s'\n", + BINDIR, dbdir, best_installed); } + fexec(BINDIR "/pkg_delete", "-K", dbdir, best_installed, NULL); - return 0; -} + free(best_installed); -/* - * Free buildinfo. - */ -static void -free_buildinfo(struct pkg_task *pkg) -{ - size_t i; - - for (i = 0; i < BI_ENUM_COUNT; ++i) { - free(pkg->buildinfo[i]); - pkg->buildinfo[i] = NULL; - } + return 0; } /* - * Write meta data files to pkgdb after creating the directory. + * Install a single package + * Returns 0 if everything is ok, >0 else */ static int -write_meta_data(struct pkg_task *pkg) +pkg_do(const char *pkg, lpkg_head_t *pkgs) { - const struct pkg_meta_desc *descr; - char *filename, **target; - size_t len; - ssize_t ret; - int fd; - - if (Fake) - return 0; - - if (mkdir_p(pkg->logdir)) { - warn("Can't create pkgdb entry: %s", pkg->logdir); - return -1; + char playpen[MaxPathSize]; + char replace_via[MaxPathSize]; + char replace_to[MaxPathSize]; + char *buildinfo[BI_ENUM_COUNT]; + int replacing = 0; + char dbdir[MaxPathSize]; + const char *tmppkg; + FILE *cfile; + int errc, err_prescan; + plist_t *p; + struct stat sb; + struct utsname host_uname; + uint64_t needed; + Boolean is_depoted_pkg = FALSE; + lfile_t *lfp; + int result; + + errc = 0; + zapLogDir = 0; + LogDir[0] = '\0'; + strlcpy(playpen, FirstPen, sizeof(playpen)); + memset(buildinfo, '\0', sizeof(buildinfo)); + + umask(DEF_UMASK); + + tmppkg = fileFindByPath(pkg); + if (tmppkg == NULL) { + warnx("no pkg found for '%s', sorry.", pkg); + return 1; } - for (descr = pkg_meta_descriptors; descr->entry_filename; ++descr) { - target = (char **)((char *)&pkg->meta_data + - descr->entry_offset); - if (*target == NULL) - continue; - if (asprintf(&filename, "%s/%s", pkg->logdir, - descr->entry_filename) == -1) { - warn("asprintf failed"); - return -1; + pkg = tmppkg; + + if (IS_URL(pkg)) { + Home = fileGetURL(pkg); + if (Home == NULL) { + warnx("unable to fetch `%s' by URL", pkg); } - (void)unlink(filename); - fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, descr->perm); - if (fd == -1) { - warn("Can't open meta data file: %s", filename); - return -1; + + /* make sure the pkg is verified */ + if (!verify(pkg)) { + warnx("Package %s will not be extracted", pkg); + goto bomb; } - len = strlen(*target); - do { - ret = write(fd, *target, len); - if (ret == -1) { - warn("Can't write meta data file: %s", - filename); - free(filename); - return -1; + } else { /* local */ + if (!IS_STDIN(pkg)) { + /* not stdin */ + if (!ispkgpattern(pkg)) { + if (stat(pkg, &sb) == FAIL) { + warnx("can't stat package file '%s'", pkg); + goto bomb; + } + /* make sure the pkg is verified */ + if (!verify(pkg)) { + warnx("Package %s will not be extracted", pkg); + goto bomb; + } } - len -= ret; - } while (ret > 0); - if (close(fd) == -1) { - warn("Can't close meta data file: %s", filename); - free(filename); - return -1; + LFILE_ADD(&files, lfp, CONTENTS_FNAME); + } else { + /* some values for stdin */ + sb.st_size = 100000; /* Make up a plausible average size */ } - free(filename); - } - - return 0; -} - -/* - * Helper function for extract_files. - */ -static int -copy_data_to_disk(struct archive *reader, struct archive *writer, - const char *filename) -{ - int r; - const void *buff; - size_t size; - off_t offset; - - for (;;) { - r = archive_read_data_block(reader, &buff, &size, &offset); - if (r == ARCHIVE_EOF) - return 0; - if (r != ARCHIVE_OK) { - warnx("Read error for %s: %s", filename, - archive_error_string(reader)); - return -1; + Home = make_playpen(playpen, sizeof(playpen), sb.st_size * 4); + if (!Home) + warnx("unable to make playpen for %ld bytes", + (long) (sb.st_size * 4)); + result = unpack(pkg, &files); + while ((lfp = TAILQ_FIRST(&files)) != NULL) { + TAILQ_REMOVE(&files, lfp, lf_link); + free(lfp); } - r = archive_write_data_block(writer, buff, size, offset); - if (r != ARCHIVE_OK) { - warnx("Write error for %s: %s", filename, - archive_error_string(writer)); - return -1; + if (result) { + warnx("unable to extract table of contents file from `%s' - not a package?", + pkg); + goto bomb; } } -} -/* - * Extract package. - * Any misordered, missing or unlisted file in the package is an error. - */ - -static const int extract_flags = /* ARCHIVE_EXTRACT_OWNER | */ - ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_UNLINK | - ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR; + cfile = fopen(CONTENTS_FNAME, "r"); + if (!cfile) { + warnx("unable to open table of contents file `%s' - not a package?", + CONTENTS_FNAME); + goto bomb; + } + read_plist(&Plist, cfile); + fclose(cfile); -static int -extract_files(struct pkg_task *pkg) -{ - char cmd[MaxPathSize]; - const char *owner, *group, *permissions; - struct archive *writer; - int r; - plist_t *p; - const char *last_file; - char *fullpath; + if (!IS_URL(pkg)) { + /* + * 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%, so multiply by 4 for good measure. + */ - if (Fake) - return 0; + needed = 4 * (uint64_t) sb.st_size; + if (min_free(playpen) < needed) { + warnx("projected size of %" MY_PRIu64 " bytes exceeds available free space\n" + "in %s. Please set your PKG_TMPDIR variable to point\n" + "to a location with more free space and try again.", + needed, playpen); + goto bomb; + } - if (mkdir_p(pkg->install_prefix)) { - warn("Can't create prefix: %s", pkg->install_prefix); - return -1; + /* Finally unpack the whole mess */ + if (unpack(pkg, NULL)) { + warnx("unable to extract `%s'!", pkg); + goto bomb; + } } - if (chdir(pkg->install_prefix) == -1) { - warn("Can't change into prefix: %s", pkg->install_prefix); - return -1; - } + /* Check for sanity */ + if (sanity_check(pkg)) + goto bomb; - if (!NoRecord && !pkgdb_open(ReadWrite)) { - warn("Can't open pkgdb for writing"); - return -1; + /* Read the OS, version and architecture from BUILD_INFO file */ + if (!read_buildinfo(buildinfo)) { + warn("can't read build information from %s", BUILD_INFO_FNAME); + if (!Force) { + warnx("aborting."); + goto bomb; + } } - writer = archive_write_disk_new(); - archive_write_disk_set_options(writer, extract_flags); - archive_write_disk_set_standard_lookup(writer); - - owner = NULL; - group = NULL; - permissions = NULL; - last_file = NULL; - - r = -1; - - for (p = pkg->plist.head; p != NULL; p = p->next) { - switch (p->type) { - case PLIST_FILE: - last_file = p->name; - if (pkg->entry == NULL) { - warnx("PLIST entry not in package (%s)", - archive_entry_pathname(pkg->entry)); - goto out; - } - if (strcmp(p->name, archive_entry_pathname(pkg->entry))) { - warnx("PLIST entry and package don't match (%s vs %s)", - p->name, archive_entry_pathname(pkg->entry)); - goto out; - } - if (asprintf(&fullpath, "%s/%s", pkg->install_prefix, p->name) == -1) { - warnx("asprintf failed"); - goto out; - } - pkgdb_store(fullpath, pkg->pkgname); - free(fullpath); - if (Verbose) - printf("%s", p->name); - break; - - case PLIST_CMD: - if (format_cmd(cmd, sizeof(cmd), p->name, pkg->install_prefix, last_file)) - return -1; - printf("Executing '%s'\n", cmd); - if (!Fake && system(cmd)) - warnx("command '%s' failed", cmd); /* XXX bail out? */ - continue; - - case PLIST_CHMOD: - permissions = p->name; - continue; - - case PLIST_CHOWN: - owner = p->name; - continue; - - case PLIST_CHGRP: - group = p->name; - continue; + if (uname(&host_uname) < 0) { + warnx("uname() failed."); + if (!Force) { + warnx("aborting."); + goto bomb; + } + } else { + int status = Good; - case PLIST_IGNORE: - p = p->next; - continue; + normalise_platform(&host_uname); - default: - continue; + /* check that we have read some values from buildinfo */ + if (buildinfo[BI_OPSYS] == NULL) { + warnx("Missing operating system value from build information"); + status = Missing; } - - r = archive_write_header(writer, pkg->entry); - if (r != ARCHIVE_OK) { - warnx("Failed to write %s: %s", - archive_entry_pathname(pkg->entry), - archive_error_string(writer)); - goto out; + if (buildinfo[BI_MACHINE_ARCH] == NULL) { + warnx("Missing machine architecture value from build information"); + status = Missing; } - - if (owner != NULL) - archive_entry_set_uname(pkg->entry, owner); - if (group != NULL) - archive_entry_set_uname(pkg->entry, group); - if (permissions != NULL) { - mode_t mode; - - mode = archive_entry_mode(pkg->entry); - mode = getmode(setmode(permissions), mode); - archive_entry_set_mode(pkg->entry, mode); + if (buildinfo[BI_OS_VERSION] == NULL) { + warnx("Missing operating system version value from build information"); + status = Missing; } - r = copy_data_to_disk(pkg->archive, writer, - archive_entry_pathname(pkg->entry)); - if (r) - goto out; - if (Verbose) - printf("\n"); - - r = archive_read_next_header(pkg->archive, &pkg->entry); - if (r == ARCHIVE_EOF) { - pkg->entry = NULL; - continue; - } - if (r != ARCHIVE_OK) { - warnx("Failed to read from archive: %s", - archive_error_string(pkg->archive)); - goto out; + if (status == Good) { + const char *effective_arch; + + if (OverrideMachine != NULL) + effective_arch = OverrideMachine; + else + effective_arch = MACHINE_ARCH; + + /* If either the OS or arch are different, bomb */ + if (strcmp(OPSYS_NAME, buildinfo[BI_OPSYS]) != 0) + status = Fatal; + if (strcmp(effective_arch, buildinfo[BI_MACHINE_ARCH]) != 0) + status = Fatal; + + /* If OS and arch are the same, warn if version differs */ + if (status == Good && + strcmp(host_uname.release, buildinfo[BI_OS_VERSION]) != 0) + status = Warning; + + if (status != Good) { + warnx("Warning: package `%s' was built for a different version of the OS:", pkg); + warnx("%s/%s %s (pkg) vs. %s/%s %s (this host)", + buildinfo[BI_OPSYS], + buildinfo[BI_MACHINE_ARCH], + buildinfo[BI_OS_VERSION], + OPSYS_NAME, + effective_arch, + host_uname.release); + } } - } - if (pkg->entry != NULL) { - warnx("Package contains entries not in PLIST: %s", - archive_entry_pathname(pkg->entry)); - goto out; + if (!Force && status == Fatal) { + warnx("aborting."); + goto bomb; + } } - r = 0; - -out: - if (!NoRecord) - pkgdb_close(); - archive_write_close(writer); - archive_write_finish(writer); - - return r; -} - -/* - * Register dependencies after sucessfully installing the package. - */ -static void -pkg_register_depends(struct pkg_task *pkg) -{ - int fd; - size_t text_len, i; - char *required_by, *text; - - if (Fake) - return; - - if (pkg->other_version != NULL) - return; /* XXX It's using the old dependencies. */ - - if (asprintf(&text, "%s\n", pkg->pkgname) == -1) - err(2, "asprintf failed"); - text_len = strlen(text); + /* Check if USE_ABI_DEPENDS or IGNORE_RECOMMENDED was set + * when this package was built. IGNORE_RECOMMENDED is historical. */ - for (i = 0; i < pkg->dep_length; ++i) { - required_by = pkgdb_pkg_file(pkg->dependencies[i], REQUIRED_BY_FNAME); - - fd = open(required_by, O_WRONLY | O_APPEND | O_CREAT, 644); - if (fd == -1) - warn("can't open dependency file '%s'," - "registration is incomplete!", required_by); - else if (write(fd, text, text_len) != text_len) - warn("can't write to dependency file `%s'", required_by); - else if (close(fd) == -1) - warn("cannot close file %s", required_by); - - free(required_by); + if ((buildinfo[BI_USE_ABI_DEPENDS] != NULL && + strcasecmp(buildinfo[BI_USE_ABI_DEPENDS], "YES") != 0) || + (buildinfo[BI_IGNORE_RECOMMENDED] != NULL && + strcasecmp(buildinfo[BI_IGNORE_RECOMMENDED], "NO") != 0)) { + warnx("%s was built", pkg); + warnx("\tto ignore recommended ABI dependencies, this may cause problems!\n"); } - free(text); -} - -/* - * Reduce the result from uname(3) to a canonical form. - */ -static void -normalise_platform(struct utsname *host_name) -{ -#ifdef NUMERIC_VERSION_ONLY - size_t span; - - span = strspn(host_name->release, "0123456789."); - host_name->release[span] = '\0'; -#endif -} - -/* - * Check build platform of the package against local host. - */ -static int -check_platform(struct pkg_task *pkg) -{ - struct utsname host_uname; - const char *effective_arch; - int fatal; - - if (uname(&host_uname) < 0) { - if (Force) { - warnx("uname() failed, continuing."); - return 0; - } else { - warnx("uname() failed, aborting."); - return -1; - } + /* + * If we have a prefix, delete the first one we see and add this + * one in place of it. + */ + if (Prefix) { + delete_plist(&Plist, FALSE, PLIST_CWD, NULL); + add_plist_top(&Plist, PLIST_CWD, Prefix); } - normalise_platform(&host_uname); - - if (OverrideMachine != NULL) - effective_arch = OverrideMachine; - else - effective_arch = MACHINE_ARCH; - - /* If either the OS or arch are different, bomb */ - if (strcmp(OPSYS_NAME, pkg->buildinfo[BI_OPSYS]) || - strcmp(effective_arch, pkg->buildinfo[BI_MACHINE_ARCH]) != 0) - fatal = 1; - else - fatal = 0; - - if (fatal || - strcmp(host_uname.release, pkg->buildinfo[BI_OS_VERSION]) != 0) { - warnx("Warning: package `%s' was built for a platform:", - pkg->pkgname); - warnx("%s/%s %s (pkg) vs. %s/%s %s (this host)", - pkg->buildinfo[BI_OPSYS], - pkg->buildinfo[BI_MACHINE_ARCH], - pkg->buildinfo[BI_OS_VERSION], - OPSYS_NAME, - effective_arch, - host_uname.release); - if (!Force && fatal) - return -1; + /* Protect against old packages with bogus @name fields */ + p = find_plist(&Plist, PLIST_NAME); + if (p->name == NULL) { + warnx("PLIST contains no @name field"); + goto bomb; + } + PkgName = p->name; + + if (fexists(VIEWS_FNAME)) + is_depoted_pkg = TRUE; + + /* + * Depoted packages' dbdir is the same as DEPOTBASE. Non-depoted + * packages' dbdir comes from the command-line or the environment. + */ + if (is_depoted_pkg) { + p = find_plist(&Plist, PLIST_CWD); + if (p == NULL) { + warn("no @cwd in +CONTENTS file?! aborting."); + goto bomb; + } + (void) strlcpy(dbdir, dirname_of(p->name), sizeof(dbdir)); + (void) strlcpy(LogDir, p->name, sizeof(LogDir)); + } else { + (void) strlcpy(dbdir, _pkgdb_getPKGDB_DIR(), sizeof(dbdir)); + (void) snprintf(LogDir, sizeof(LogDir), "%s/%s", dbdir, PkgName); } - return 0; -} - -/* - * Run the install script. - */ -static int -run_install_script(struct pkg_task *pkg, const char *argument) -{ - int ret; - char *filename; - - if (pkg->meta_data.meta_install == NULL || NoInstall) - return 0; - setenv(PKG_PREFIX_VNAME, pkg->install_prefix, 1); /* XXX or prefix? */ - setenv(PKG_METADATA_DIR_VNAME, pkg->logdir, 1); + /* Set environment variables expected by the +INSTALL script. */ + setenv(PKG_PREFIX_VNAME, (p = find_plist(&Plist, PLIST_CWD)) ? p->name : ".", 1); + setenv(PKG_METADATA_DIR_VNAME, LogDir, 1); setenv(PKG_REFCOUNT_DBDIR_VNAME, pkgdb_refcount_dir(), 1); - - filename = pkgdb_pkg_file(pkg->pkgname, INSTALL_FNAME); - if (Verbose) - printf("Running install with PRE-INSTALL for %s.\n", pkg->pkgname); - if (Fake) { - free(filename); - return 0; + + /* make sure dbdir actually exists! */ + if (!(isdir(dbdir) || islinktodir(dbdir))) { + if (fexec("mkdir", "-p", dbdir, NULL)) { + errx(EXIT_FAILURE, + "Database-dir %s cannot be generated, aborting.", + dbdir); + } } - ret = 0; - - if (chdir(pkg->logdir) == -1) { - warn("Can't change to %s", pkg->logdir); - ret = -1; + /* See if this package (exact version) is already registered */ + if (isdir(LogDir) && !Force) { + if (!Automatic && is_automatic_installed(PkgName)) { + if (mark_as_automatic_installed(PkgName, 0) == 0) + warnx("package `%s' was already installed as " + "dependency, now marked as installed " + "manually", PkgName); + } else { + warnx("package `%s' already recorded as installed", + PkgName); + } + goto success; /* close enough for government work */ } - errno = 0; - if (ret == 0 && fexec(filename, pkg->pkgname, argument, (void *)NULL)) { - if (errno != 0) - warn("exec of install script failed"); - else - warnx("install script returned error status"); - ret = -1; + /* See if some other version of us is already installed */ + switch (pkg_do_installed(&replacing, replace_via, replace_to, is_depoted_pkg, dbdir)) { + case 0: + break; + case 1: + errc = 1; + goto success; + case -1: + goto bomb; } - free(filename); - return ret; -} + /* See if there are conflicting packages installed */ + for (p = Plist.head; p; p = p->next) { + char *best_installed; -static int -check_explicit_conflict(struct pkg_task *pkg) -{ - char *installed, *installed_pattern; - plist_t *p; - int status; - - status = 0; - - for (p = pkg->plist.head; p != NULL; p = p->next) { - if (p->type == PLIST_IGNORE) { - p = p->next; - continue; - } else if (p->type != PLIST_PKGCFL) + if (p->type != PLIST_PKGCFL) continue; - installed = find_best_matching_installed_pkg(p->name); - if (installed) { + if (Verbose) + printf("Package `%s' conflicts with `%s'.\n", PkgName, p->name); + best_installed = find_best_matching_installed_pkg(p->name); + if (best_installed) { warnx("Package `%s' conflicts with `%s', and `%s' is installed.", - pkg->pkgname, p->name, installed); - free(installed); - status = -1; + PkgName, p->name, best_installed); + free(best_installed); + ++errc; } } - if (some_installed_package_conflicts_with(pkg->pkgname, - pkg->other_version, &installed, &installed_pattern)) { - warnx("Installed package `%s' conflicts with `%s' when trying to install `%s'.", - installed, installed_pattern, pkg->pkgname); - free(installed); - free(installed_pattern); - status = -1; - } + /* See if any of the installed packages conflicts with this one. */ + { + char *inst_pkgname, *inst_pattern; - return status; -} - -static int -check_implicit_conflict(struct pkg_task *pkg) -{ - plist_t *p; - char *fullpath, *existing; - int status; - - if (!pkgdb_open(ReadOnly)) { -#if notyet /* XXX empty pkgdb without database? */ - warn("Can't open pkgdb for reading"); - return -1; -#else - return 0; -#endif + if (some_installed_package_conflicts_with(PkgName, &inst_pkgname, &inst_pattern)) { + warnx("Installed package `%s' conflicts with `%s' when trying to install `%s'.", + inst_pkgname, inst_pattern, PkgName); + free(inst_pkgname); + free(inst_pattern); + errc++; + } } - status = 0; - - for (p = pkg->plist.head; p != NULL; p = p->next) { - if (p->type == PLIST_IGNORE) { - p = p->next; - continue; - } else if (p->type != PLIST_FILE) - continue; - - if (asprintf(&fullpath, "%s/%s", pkg->install_prefix, p->name) == -1) { - warnx("asprintf failed"); - status = -1; - break; - } - existing = pkgdb_retrieve(fullpath); - free(fullpath); - if (existing == NULL) - continue; - if (pkg->other_version != NULL && - strcmp(pkg->other_version, existing) == 0) + /* Quick pre-check if any conflicting dependencies are installed + * (e.g. version X is installed, but version Y is required) + */ + err_prescan=0; + for (p = Plist.head; p; p = p->next) { + char *best_installed; + + if (p->type != PLIST_PKGDEP) continue; - - warnx("Conflicting PLIST with %s: %s", existing, p->name); - if (!Force) { - status = -1; - if (!Verbose) - break; + if (Verbose) + printf("Depends pre-scan: `%s' required.\n", p->name); + best_installed = find_best_matching_installed_pkg(p->name); + if (best_installed == NULL) { + /* + * required pkg not found. look if it's available with a more liberal + * pattern. If so, this will lead to problems later (check on "some + * other version of us is already installed" will fail, see above), + * and we better stop right now. + */ + char *s; + int skip = -1; + + /* doing this right required to parse the full version(s), + * do a 99% solution here for now */ + if (strchr(p->name, '{')) + continue; /* would remove trailing '}' else */ + + if ((s = strpbrk(p->name, "<>")) != NULL) { + skip = 0; + } else if (((s = strstr(p->name, "-[0-9]*")) != NULL) && + (*(s + sizeof("-[0-9]*") - 1) == '\0')) { + /* -[0-9]* already present so no need to */ + /* add it a second time */ + skip = -1; + } else if ((s = strrchr(p->name, '-')) != NULL) { + skip = 1; + } + + if (skip >= 0) { + char buf[MaxPathSize]; + + (void) snprintf(buf, sizeof(buf), + skip ? "%.*s[0-9]*" : "%.*s-[0-9]*", + (int)(s - p->name) + skip, p->name); + best_installed = find_best_matching_installed_pkg(buf); + if (best_installed) { + int done = 0; + + if (Replace > 1) + { + int errc0 = 0; + char tmp[MaxPathSize]; + + warnx("Attempting to update `%s' using binary package\n", p->name); + /* Yes, append .tgz after the version so the */ + /* pattern can match a filename. */ + snprintf(tmp, sizeof(tmp), "%s.tgz", p->name); + done = installprereq(tmp, &errc0, 2); + } + else if (Replace) + { + warnx("To perform necessary upgrades on required packages specify -u twice.\n"); + } + + if (!done) + { + warnx("pkg `%s' required, but `%s' found installed.", + p->name, best_installed); + if (Force) { + warnx("Proceeding anyway."); + } else { + err_prescan++; + } + } + free(best_installed); + } + } + } else { + free(best_installed); } } - - pkgdb_close(); - return status; -} - -static int -check_dependencies(struct pkg_task *pkg) -{ - plist_t *p; - char *best_installed; - int status; - size_t i; - - status = 0; - - for (p = pkg->plist.head; p != NULL; p = p->next) { - if (p->type == PLIST_IGNORE) { - p = p->next; - continue; - } else if (p->type != PLIST_PKGDEP) + if (err_prescan > 0) { + warnx("Please resolve this conflict!"); + errc += err_prescan; + goto success; /* close enough */ + } + + + /* Now check the packing list for dependencies */ + for (p = Plist.head; p; p = p->next) { + char *best_installed; + + if (p->type != PLIST_PKGDEP) continue; + if (Verbose) + printf("Package `%s' depends on `%s'.\n", PkgName, p->name); best_installed = find_best_matching_installed_pkg(p->name); if (best_installed == NULL) { - /* XXX check cyclic dependencies? */ - if (Fake || NoRecord) { - if (!Force) { - warnx("Missing dependency %s\n", - p->name); - status = -1; - break; + /* required pkg not found - need to pull in */ + + if (Fake) { + /* fake install (???) */ + if (Verbose) + printf("Package dependency %s for %s not installed%s\n", p->name, pkg, + Force ? " (proceeding anyway)" : "!"); + } else { + int done = 0; + int errc0 = 0; + + done = installprereq(p->name, &errc0, (Replace > 1) ? 2 : 0); + if (!done && !Force) { + errc += errc0; } - warnx("Missing dependency %s, continuing", - p->name); - continue; - } - if (pkg_do(p->name, 1)) { - warnx("Can't install dependency %s", p->name); - status = -1; - break; } - best_installed = find_best_matching_installed_pkg(p->name); - if (best_installed == NULL && Force) { - warnx("Missing dependency %s ignored", p->name); - continue; - } else if (best_installed == NULL) { - warnx("Just installed dependency %s disappeared", p->name); - status = -1; - break; - } - } - for (i = 0; i < pkg->dep_length; ++i) { - if (strcmp(best_installed, pkg->dependencies[i]) == 0) - break; - } - if (i < pkg->dep_length) { - /* Already used as dependency, so skip it. */ + } else { + if (Verbose) + printf(" - %s already installed.\n", best_installed); free(best_installed); - continue; - } - if (pkg->dep_length + 1 >= pkg->dep_allocated) { - char **tmp; - pkg->dep_allocated = 2 * pkg->dep_allocated + 1; - tmp = realloc(pkg->dependencies, - pkg->dep_allocated * sizeof(*tmp)); - if (tmp == NULL) { - warnx("realloc failed"); - free(pkg->dependencies); - pkg->dependencies = NULL; - pkg->dep_length = pkg->dep_allocated = 0; - free(best_installed); - return -1; - } - pkg->dependencies = tmp; } - pkg->dependencies[pkg->dep_length++] = best_installed; } - return status; -} + if (errc != 0) + goto bomb; -/* - * If this package uses pkg_views, register it in the default view. - */ -static void -pkg_register_views(struct pkg_task *pkg) -{ - if (Fake || NoView || pkg->meta_data.meta_views == NULL) - return; - - if (Verbose) { - printf("%s/pkg_view -d %s %s%s %s%s %sadd %s\n", - BINDIR, _pkgdb_getPKGDB_DIR(), - View ? "-w " : "", View ? View : "", - Viewbase ? "-W " : "", Viewbase ? Viewbase : "", - Verbose ? "-v " : "", pkg->pkgname); + /* If we're really installing, and have an installation file, run it */ + if (!NoInstall && fexists(INSTALL_FNAME)) { + (void) fexec(CHMOD_CMD, "+x", INSTALL_FNAME, NULL); /* make sure */ + if (Verbose) + printf("Running install with PRE-INSTALL for %s.\n", PkgName); + errno = 0; + if (!Fake && fexec("./" INSTALL_FNAME, PkgName, "PRE-INSTALL", NULL)) { + if (errno != 0) + warn("exec of install script failed"); + else + warnx("install script returned error status"); + errc = 1; + goto success; /* nothing to uninstall yet */ + } } - fexec_skipempty(BINDIR "/pkg_view", "-d", _pkgdb_getPKGDB_DIR(), - View ? "-w " : "", View ? View : "", - Viewbase ? "-W " : "", Viewbase ? Viewbase : "", - Verbose ? "-v " : "", "add", pkg->pkgname, - (void *)NULL); -} - -static int -start_replacing(struct pkg_task *pkg) -{ - char *old_required_by, *new_required_by; - - old_required_by = pkgdb_pkg_file(pkg->other_version, - REQUIRED_BY_FNAME); - new_required_by = pkgdb_pkg_file(pkg->pkgname, - REQUIRED_BY_FNAME); - + /* + * Now finally extract the entire show if we're not going direct. + * We need to reset the package dbdir so that extract_plist() + * updates the correct pkgdb.byfile.db database. + */ if (!Fake) { - if (rename(old_required_by, new_required_by) == -1 && - errno != ENOENT) { - warn("Can't move +REQUIRED_BY from %s to %s", - old_required_by, new_required_by); - return -1; + _pkgdb_setPKGDB_DIR(dbdir); + if (!extract_plist(".", &Plist)) { + errc = 1; + goto fail; } } - if (Verbose || Fake) { - printf("%s/pkg_delete -K %s -p %s '%s'\n", - BINDIR, _pkgdb_getPKGDB_DIR(), pkg->install_prefix, - pkg->other_version); + if (!Fake && fexists(MTREE_FNAME)) { + warnx("Mtree file ignored for package %s", PkgName); } - if (!Fake) - fexec(BINDIR "/pkg_delete", "-K", _pkgdb_getPKGDB_DIR(), - "-p", pkg->install_prefix, - pkg->other_version, NULL); - - /* XXX Check return value and do what? */ - return 0; -} -/* - * Install a single package. - */ -static int -pkg_do(const char *pkgpath, int mark_automatic) -{ - int status; - void *archive_cookie; - struct pkg_task *pkg; - - if ((pkg = calloc(1, sizeof(*pkg))) == NULL) - err(2, "malloc failed"); - - status = -1; - - if ((pkg->archive = find_archive(pkgpath, &archive_cookie)) == NULL) { - warnx("no pkg found for '%s', sorry.", pkgpath); - goto clean_memory; + /* Run the installation script one last time? */ + if (!NoInstall && fexists(INSTALL_FNAME)) { + if (Verbose) + printf("Running install with POST-INSTALL for %s.\n", PkgName); + if (!Fake && fexec("./" INSTALL_FNAME, PkgName, "POST-INSTALL", NULL)) { + warnx("install script returned error status"); + errc = 1; + goto fail; + } } - if (read_meta_data(pkg)) - goto clean_memory; - - /* Parse PLIST early, so that messages can use real package name. */ - if (pkg_parse_plist(pkg)) - goto clean_memory; - if (pkg->meta_data.meta_mtree != NULL) - warnx("mtree specification in pkg `%s' ignored", pkg->pkgname); + /* Time to record the deed? */ + if (!NoRecord && !Fake) { + char contents[MaxPathSize]; - if (pkg->meta_data.meta_views != NULL) { - if ((pkg->logdir = strdup(pkg->install_prefix)) == NULL) - err(EXIT_FAILURE, "strdup failed"); - _pkgdb_setPKGDB_DIR(dirname_of(pkg->logdir)); - } else { - if (asprintf(&pkg->logdir, "%s/%s", _pkgdb_getPKGDB_DIR(), - pkg->pkgname) == -1) - err(EXIT_FAILURE, "asprintf failed"); - } + if (!PkgName) { + warnx("no package name! can't record package, sorry"); + errc = 1; + goto success; /* well, partial anyway */ + } + (void) snprintf(LogDir, sizeof(LogDir), "%s/%s", dbdir, PkgName); + zapLogDir = 1; /* LogDir contains something valid now */ + if (Verbose) + printf("Attempting to record package into %s.\n", LogDir); + if (make_hierarchy(LogDir)) { + warnx("can't record package into '%s', you're on your own!", + LogDir); + memset(LogDir, 0, sizeof(LogDir)); + errc = 1; + goto success; /* close enough for government work */ + } + /* Make sure pkg_info can read the entry */ + (void) fexec(CHMOD_CMD, "a+rx", LogDir, NULL); + + /* Move all of the +-files into place */ + move_files(".", "+*", LogDir); + + /* Generate the +CONTENTS file in-place from the Plist */ + (void) snprintf(contents, sizeof(contents), "%s/%s", LogDir, CONTENTS_FNAME); + cfile = fopen(contents, "w"); + if (!cfile) { + warnx("can't open new contents file '%s'! can't register pkg", + contents); + goto success; /* can't log, but still keep pkg */ + } + write_plist(&Plist, cfile, NULL); + fclose(cfile); + + /* register dependencies */ + /* we could save some cycles here if we remembered what we + * installed above (in case we got a wildcard dependency) */ + /* XXX remembering in p->name would NOT be good! */ + for (p = Plist.head; p; p = p->next) { + if (p->type != PLIST_PKGDEP) + continue; + if (Verbose) + printf("Attempting to record dependency on package `%s'\n", p->name); + (void) snprintf(contents, sizeof(contents), "%s/%s", dbdir, + basename_of(p->name)); + if (ispkgpattern(p->name)) { + char *s; - if (NoRecord && !Fake) { - const char *tmpdir; + s = find_best_matching_installed_pkg(p->name); - tmpdir = getenv("TMPDIR"); - if (tmpdir == NULL) - tmpdir = "/tmp"; + if (s == NULL) + errx(EXIT_FAILURE, "Where did our dependency go?!"); - free(pkg->logdir); - if (asprintf(&pkg->logdir, "%s/pkg_install.XXXXXX", tmpdir) == -1) - err(EXIT_FAILURE, "asprintf failed"); - /* XXX pkg_add -u... */ - if (mkdtemp(pkg->logdir) == NULL) { - warn("mkdtemp failed"); - goto clean_memory; + (void) snprintf(contents, sizeof(contents), "%s/%s", dbdir, s); + free(s); + } + strlcat(contents, "/", sizeof(contents)); + strlcat(contents, REQUIRED_BY_FNAME, sizeof(contents)); + + cfile = fopen(contents, "a"); + if (!cfile) + warnx("can't open dependency file '%s'!\n" + "dependency registration is incomplete", contents); + else { + fprintf(cfile, "%s\n", PkgName); + if (fclose(cfile) == EOF) + warnx("cannot properly close file %s", contents); + } } + if (Automatic) + mark_as_automatic_installed(PkgName, 1); + if (Verbose) + printf("Package %s registered in %s\n", PkgName, LogDir); } - if (check_already_installed(pkg) == 0) { - status = 0; - goto clean_memory; + if ((p = find_plist(&Plist, PLIST_DISPLAY)) != NULL) { + FILE *fp; + char buf[BUFSIZ]; + + (void) snprintf(buf, sizeof(buf), "%s/%s", LogDir, p->name); + fp = fopen(buf, "r"); + if (fp) { + putc('\n', stdout); + while (fgets(buf, sizeof(buf), fp)) + fputs(buf, stdout); + putc('\n', stdout); + (void) fclose(fp); + } else + warnx("cannot open %s as display file", buf); } - if (read_buildinfo(pkg)) - goto clean_memory; + /* Add the package to a default view. */ + if (!Fake && !NoView && is_depoted_pkg) { + if (Verbose) { + printf("%s/pkg_view -d %s %s%s %s%s %sadd %s\n", + BINDIR, dbdir, + View ? "-w " : "", View ? View : "", + Viewbase ? "-W " : "", Viewbase ? Viewbase : "", + Verbose ? "-v " : "", PkgName); + } + + fexec_skipempty(BINDIR "/pkg_view", "-d", dbdir, + View ? "-w " : "", View ? View : "", + Viewbase ? "-W " : "", Viewbase ? Viewbase : "", + Verbose ? "-v " : "", "add", PkgName, NULL); + } - if (check_platform(pkg)) - goto clean_memory; + goto success; - if (check_other_installed(pkg)) - goto clean_memory; +bomb: + errc = 1; + goto success; - if (check_explicit_conflict(pkg)) - goto clean_memory; +fail: + /* Nuke the whole (installed) show, XXX but don't clean directories */ + if (!Fake) + delete_package(FALSE, FALSE, &Plist, FALSE); - if (check_implicit_conflict(pkg)) - goto clean_memory; +success: + /* delete the packing list contents */ + free_plist(&Plist); + leave_playpen(Home); - if (pkg->other_version != NULL) { + if (replacing) { /* - * Replacing an existing package. - * Write meta-data, get rid of the old version, - * install/update dependencies and finally extract. + * Upgrade step 3/4: move back +REQUIRED_BY file + * (see also step 2/4) */ - if (write_meta_data(pkg)) - goto nuke_pkgdb; - - if (start_replacing(pkg)) - goto nuke_pkgdb; - - if (check_dependencies(pkg)) - goto nuke_pkgdb; - } else { + if (rename(replace_via, replace_to) != 0) + err(EXIT_FAILURE, "renaming \"%s\" to \"%s\" failed", replace_via, replace_to); + /* - * Normal installation. - * Install/update dependencies first and - * write the current package to disk afterwards. - */ - if (check_dependencies(pkg)) - goto clean_memory; - - if (write_meta_data(pkg)) - goto nuke_pkgdb; - } - - if (run_install_script(pkg, "PRE-INSTALL")) - goto nuke_pkgdb; - - if (extract_files(pkg)) - goto nuke_pkg; - - if (run_install_script(pkg, "POST-INSTALL")) - goto nuke_pkgdb; - - /* XXX keep +INSTALL_INFO for updates? */ - /* XXX keep +PRESERVE for updates? */ - if (mark_automatic) - mark_as_automatic_installed(pkg->pkgname, 1); - - pkg_register_depends(pkg); - - if (Verbose) - printf("Package %s registered in %s\n", pkg->pkgname, pkg->logdir); - - if (pkg->meta_data.meta_display != NULL) - fputs(pkg->meta_data.meta_display, stdout); - - pkg_register_views(pkg); - - status = 0; - goto clean_memory; - -nuke_pkg: - if (!Fake) { - if (pkg->other_version) { - warnx("Updating of %s to %s failed.", - pkg->other_version, pkg->pkgname); - warnx("Remember to run pkg_admin rebuild-tree after fixing this."); - } - delete_package(FALSE, FALSE, &pkg->plist, FALSE); + * Upgrade step 4/4: Fix pkgs that depend on us to + * depend on the new version instead of the old + * one by fixing @pkgdep lines in +CONTENTS files. + */ + /* TODO */ } -nuke_pkgdb: - if (!Fake) { - (void) fexec(REMOVE_CMD, "-fr", pkg->logdir, (void *)NULL); - free(pkg->logdir); - pkg->logdir = NULL; - } + return errc; +} -clean_memory: - if (pkg->logdir != NULL && NoRecord && !Fake) - (void) fexec(REMOVE_CMD, "-fr", pkg->logdir, (void *)NULL); - free(pkg->logdir); - free_buildinfo(pkg); - free_plist(&pkg->plist); - free_meta_data(pkg); - if (pkg->archive) { - archive_read_close(pkg->archive); - close_archive(archive_cookie); +void +cleanup(int signo) +{ + static int alreadyCleaning; + void (*oldint) (int); + void (*oldhup) (int); + int saved_errno; + + saved_errno = errno; + oldint = signal(SIGINT, SIG_IGN); + oldhup = signal(SIGHUP, SIG_IGN); + + if (!alreadyCleaning) { + alreadyCleaning = 1; + if (signo) + printf("Signal %d received, cleaning up.\n", signo); + if (!Fake && zapLogDir && LogDir[0]) + (void) fexec(REMOVE_CMD, "-fr", LogDir, NULL); + leave_playpen(Home); + if (signo) + exit(1); } - free(pkg->other_version); - free(pkg); - return status; + signal(SIGINT, oldint); + signal(SIGHUP, oldhup); + errno = saved_errno; } int pkg_perform(lpkg_head_t *pkgs) { - int errors = 0; + int err_cnt = 0; lpkg_t *lpp; + signal(SIGINT, cleanup); + signal(SIGHUP, cleanup); + + TAILQ_INIT(&files); + while ((lpp = TAILQ_FIRST(pkgs)) != NULL) { path_prepend_from_pkgname(lpp->lp_name); - if (pkg_do(lpp->lp_name, Automatic)) - ++errors; + err_cnt += pkg_do(lpp->lp_name, pkgs); path_prepend_clear(); TAILQ_REMOVE(pkgs, lpp, lp_link); free_lpkg(lpp); } - - return errors; + + ftp_stop(); + + return err_cnt; } diff --git a/pkgtools/pkg_install/files/add/pkg_add.1 b/pkgtools/pkg_install/files/add/pkg_add.1 index 69df18cfb43..ced8fd613c9 100644 --- a/pkgtools/pkg_install/files/add/pkg_add.1 +++ b/pkgtools/pkg_install/files/add/pkg_add.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: pkg_add.1,v 1.29 2008/04/26 14:56:34 joerg Exp $ +.\" $NetBSD: pkg_add.1,v 1.30 2008/04/26 17:40:01 joerg Exp $ .\" .\" FreeBSD install - a package for the installation and maintenance .\" of non-core utilities. @@ -30,6 +30,7 @@ .Op Fl m Ar machine .Op Fl p Ar prefix .Op Fl s Ar verification-type +.Op Fl t Ar template .Op Fl W Ar viewbase .Op Fl w Ar view .Ar \fR[[ftp|http]://[\fIuser\fR[:\fIpassword]\fR@]\fIhost\fR[:\fIport\fR]][/\fIpath/\fR]pkg-name ... @@ -159,8 +160,6 @@ into .Ar pkgdb . .It Fl R Do not record the installation of a package. -This implies -.Fl I . This means that you cannot deinstall it later, so only use this option if you know what you are doing! .It Fl s Ar verification-type @@ -184,6 +183,30 @@ the verification feature when using to add a binary package via a URL - the package, and the related detached signature file, must be local for the verification to work. +.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 /var/tmp/instmp.XXXXXX , +but it may be necessary to override it in the situation where +space in your +.Pa /var/tmp +directory is limited. +Be sure to leave some number of +.Sq X +characters for +.Xr mktemp 3 +to fill in with a unique ID. +.Pp +You can get a performance boost by setting the staging area +.Ar template +to reside on the same disk partition as target directories for package +file installation; often this is +.Pa /usr . .It Fl u If the package that's being installed is already installed, either in the same or a different version, an update is performed. @@ -252,10 +275,11 @@ passive mode ftp. .Sh TECHNICAL DETAILS .Nm -extracts each package's meta data (including the -.Dq packing list ) -to memory and then runs through the following sequence to fully extract -the contents of the package: +extracts each package's +.Dq packing list +into a special staging directory in /var/tmp (or $PKG_TMPDIR if set) +and then runs through the following sequence to fully extract the contents +of the package: .Bl -enum -offset indent .It A check is made to determine if the package or another version of it diff --git a/pkgtools/pkg_install/files/create/perform.c b/pkgtools/pkg_install/files/create/perform.c index 7c8d1b29e5f..066a8898c21 100644 --- a/pkgtools/pkg_install/files/create/perform.c +++ b/pkgtools/pkg_install/files/create/perform.c @@ -1,4 +1,4 @@ -/* $NetBSD: perform.c,v 1.20 2008/04/26 14:56:34 joerg Exp $ */ +/* $NetBSD: perform.c,v 1.21 2008/04/26 17:40:01 joerg Exp $ */ #if HAVE_CONFIG_H #include "config.h" @@ -11,7 +11,7 @@ #if 0 static const char *rcsid = "from FreeBSD Id: perform.c,v 1.38 1997/10/13 15:03:51 jkh Exp"; #else -__RCSID("$NetBSD: perform.c,v 1.20 2008/04/26 14:56:34 joerg Exp $"); +__RCSID("$NetBSD: perform.c,v 1.21 2008/04/26 17:40:01 joerg Exp $"); #endif #endif @@ -91,38 +91,6 @@ register_depends(package_t *plist, char *deps, int build_only) } /* - * Expect "fname" to point at a file, and read it into - * the buffer returned. - */ -static char * -fileGetContents(char *fname) -{ - char *contents; - struct stat sb; - int fd; - - if (stat(fname, &sb) == FAIL) { - cleanup(0); - errx(2, "can't stat '%s'", fname); - } - - contents = (char *) malloc((size_t) (sb.st_size) + 1); - fd = open(fname, O_RDONLY, 0); - if (fd == FAIL) { - cleanup(0); - errx(2, "unable to open '%s' for reading", fname); - } - if (read(fd, contents, (size_t) sb.st_size) != (size_t) sb.st_size) { - cleanup(0); - errx(2, "short read on '%s' - did not get %lld bytes", - fname, (long long) sb.st_size); - } - close(fd); - contents[(size_t) sb.st_size] = '\0'; - return contents; -} - -/* * Get a string parameter as a file spec or as a "contents follow -" spec */ static void diff --git a/pkgtools/pkg_install/files/info/Makefile.in b/pkgtools/pkg_install/files/info/Makefile.in index fed22801e5a..967ead0f550 100644 --- a/pkgtools/pkg_install/files/info/Makefile.in +++ b/pkgtools/pkg_install/files/info/Makefile.in @@ -1,4 +1,4 @@ -# $NetBSD: Makefile.in,v 1.15 2008/04/26 14:56:34 joerg Exp $ +# $NetBSD: Makefile.in,v 1.16 2008/04/26 17:40:01 joerg Exp $ srcdir= @srcdir@ @@ -16,7 +16,7 @@ BOOTSTRAP= @bootstrap@ CC= @CC@ CCLD= $(CC) .if empty(BOOTSTRAP) -LIBS= -linstall -larchive -lfetch -lbz2 -lz @LIBS@ +LIBS= -linstall -larchive -lbz2 -lfetch -lz @LIBS@ CPPFLAGS= @CPPFLAGS@ -I. -I$(srcdir) -I../lib .else LIBS= -linstall @LIBS@ diff --git a/pkgtools/pkg_install/files/info/perform.c b/pkgtools/pkg_install/files/info/perform.c index 2acde1f1337..cdf4b563f6b 100644 --- a/pkgtools/pkg_install/files/info/perform.c +++ b/pkgtools/pkg_install/files/info/perform.c @@ -1,4 +1,4 @@ -/* $NetBSD: perform.c,v 1.47 2008/04/26 14:56:34 joerg Exp $ */ +/* $NetBSD: perform.c,v 1.48 2008/04/26 17:40:01 joerg Exp $ */ #if HAVE_CONFIG_H #include "config.h" @@ -17,7 +17,7 @@ #if 0 static const char *rcsid = "from FreeBSD Id: perform.c,v 1.23 1997/10/13 15:03:53 jkh Exp"; #else -__RCSID("$NetBSD: perform.c,v 1.47 2008/04/26 14:56:34 joerg Exp $"); +__RCSID("$NetBSD: perform.c,v 1.48 2008/04/26 17:40:01 joerg Exp $"); #endif #endif @@ -126,7 +126,7 @@ static const struct pkg_meta_desc { int entry_mask; int required_file; } pkg_meta_descriptors[] = { - { offsetof(struct pkg_meta, meta_contents), CONTENTS_FNAME, + { offsetof(struct pkg_meta, meta_contents), CONTENTS_FNAME , LOAD_CONTENTS, 1}, { offsetof(struct pkg_meta, meta_comment), COMMENT_FNAME, LOAD_COMMENT, 1 }, @@ -302,19 +302,30 @@ pkg_do(const char *pkg) int code = 0; const char *binpkgfile = NULL; - if (IS_URL(pkg) || (fexists(pkg) && isfile(pkg))) { + if (IS_URL(pkg)) { +#ifdef BOOTSTRAP + errx(2, "Remote access not supported during bootstrap"); +#else + struct archive *archive; + void *remote_archive_cookie; + + archive = open_remote_archive(pkg, &remote_archive_cookie); + + meta = read_meta_data_from_archive(archive); + close_remote_archive(remote_archive_cookie); +#endif + } else if (fexists(pkg) && isfile(pkg)) { #ifdef BOOTSTRAP errx(2, "Binary packages not supported during bootstrap"); #else struct archive *archive; - void *archive_cookie; + void *remote_archive_cookie; - archive = open_archive(pkg, &archive_cookie); + archive = open_local_archive(pkg, &remote_archive_cookie); meta = read_meta_data_from_archive(archive); - close_archive(archive_cookie); - if (!IS_URL(pkg)) - binpkgfile = pkg; + close_local_archive(remote_archive_cookie); + binpkgfile = pkg; #endif } else { /* diff --git a/pkgtools/pkg_install/files/lib/Makefile.in b/pkgtools/pkg_install/files/lib/Makefile.in index 8a10272a905..e513a89a7d4 100644 --- a/pkgtools/pkg_install/files/lib/Makefile.in +++ b/pkgtools/pkg_install/files/lib/Makefile.in @@ -1,4 +1,4 @@ -# $NetBSD: Makefile.in,v 1.22 2008/04/26 14:56:34 joerg Exp $ +# $NetBSD: Makefile.in,v 1.23 2008/04/26 17:40:01 joerg Exp $ srcdir= @srcdir@ @@ -20,7 +20,7 @@ RANLIB= @RANLIB@ AR= @AR@ CC= @CC@ CPPFLAGS= @CPPFLAGS@ -I. -I$(srcdir) -DEFS= @DEFS@ -DDEF_LOG_DIR=\"$(pkgdbdir)\" +DEFS= @DEFS@ -DDEF_LOG_DIR=\"$(pkgdbdir)\" -DTAR_CMD=\"$(tar)\" -DFTP_CMD=\"$(ftp)\" CFLAGS= @CFLAGS@ INSTALL= @INSTALL@ @@ -28,8 +28,8 @@ INSTALL= @INSTALL@ LIB= libinstall.a OBJS= automatic.o conflicts.o decompress.o dewey.o fexec.o file.o \ - global.o iterate.o lpkg.o opattern.o \ - path.o pkgdb.o plist.o \ + ftpio.o global.o iterate.o lpkg.o opattern.o \ + path.o pen.o pexec.o pkgdb.o plist.o \ str.o var.o version.o vulnerabilities-file.o .if !empty(BOOTSTRAP) diff --git a/pkgtools/pkg_install/files/lib/conflicts.c b/pkgtools/pkg_install/files/lib/conflicts.c index 01468ce0ff6..35dd204ccf7 100644 --- a/pkgtools/pkg_install/files/lib/conflicts.c +++ b/pkgtools/pkg_install/files/lib/conflicts.c @@ -30,7 +30,6 @@ */ struct package_conflict { const char *pkgname; - const char *skip_pkgname; char **conflicting_pkgname; char **conflicting_pattern; }; @@ -71,10 +70,6 @@ check_package_conflict(const char *pkgname, void *v) FILE *f; int rv; - if (conflict->skip_pkgname != NULL && - strcmp(conflict->skip_pkgname, pkgname) == 0) - return 0; - rv = 0; pkg.head = NULL; pkg.tail = NULL; @@ -107,14 +102,12 @@ check_package_conflict(const char *pkgname, void *v) * variables are set to NULL. */ int -some_installed_package_conflicts_with(const char *pkgname, - const char *skip_pkgname, char **inst_pkgname, char **inst_pattern) +some_installed_package_conflicts_with(const char *pkgname, char **inst_pkgname, char **inst_pattern) { struct package_conflict cfl; int rv; cfl.pkgname = pkgname; - cfl.skip_pkgname = skip_pkgname; *inst_pkgname = NULL; *inst_pattern = NULL; cfl.conflicting_pkgname = inst_pkgname; diff --git a/pkgtools/pkg_install/files/lib/file.c b/pkgtools/pkg_install/files/lib/file.c index 17322df70e3..47146a31cac 100644 --- a/pkgtools/pkg_install/files/lib/file.c +++ b/pkgtools/pkg_install/files/lib/file.c @@ -1,4 +1,4 @@ -/* $NetBSD: file.c,v 1.24 2008/04/26 14:56:34 joerg Exp $ */ +/* $NetBSD: file.c,v 1.25 2008/04/26 17:40:01 joerg Exp $ */ #if HAVE_CONFIG_H #include "config.h" @@ -17,7 +17,7 @@ #if 0 static const char *rcsid = "from FreeBSD Id: file.c,v 1.29 1997/10/08 07:47:54 charnier Exp"; #else -__RCSID("$NetBSD: file.c,v 1.24 2008/04/26 14:56:34 joerg Exp $"); +__RCSID("$NetBSD: file.c,v 1.25 2008/04/26 17:40:01 joerg Exp $"); #endif #endif @@ -218,6 +218,263 @@ URLlength(const char *fname) } /* + * Returns the host part of a URL + */ +const char * +fileURLHost(const char *fname, char *where, int max) +{ + const char *ret; + int i; + + assert(where != NULL); + assert(max > 0); + + if ((i = URLlength(fname)) < 0) { /* invalid URL? */ + errx(EXIT_FAILURE, "fileURLhost called with a bad URL: `%s'", fname); + } + fname += i; + /* Do we have a place to stick our work? */ + ret = where; + while (*fname && *fname != '/' && --max) + *where++ = *fname++; + *where = '\0'; + + return ret; +} + +/* + * Returns the filename part of a URL + */ +const char * +fileURLFilename(const char *fname, char *where, int max) +{ + const char *ret; + int i; + + assert(where != NULL); + assert(max > 0); + + if ((i = URLlength(fname)) < 0) { /* invalid URL? */ + errx(EXIT_FAILURE, "fileURLFilename called with a bad URL: `%s'", fname); + } + fname += i; + /* Do we have a place to stick our work? */ + ret = where; + while (*fname && *fname != '/') + ++fname; + if (*fname == '/') { + while (*fname && --max) + *where++ = *fname++; + } + *where = '\0'; + + return ret; +} + +/* + * Try and fetch a file by URL, returning the directory name for where + * it's unpacked, if successful. To be handed to leave_playpen() later. + */ +char * +fileGetURL(const char *spec) +{ + char host[MAXHOSTNAMELEN], file[MaxPathSize]; + const char *cp; + char *rp; + char pen[MaxPathSize]; + int rc; + + rp = NULL; + if (!IS_URL(spec)) { + errx(EXIT_FAILURE, "fileGetURL was called with non-URL arg '%s'", spec); + } + + /* Some sanity checks on the URL */ + cp = fileURLHost(spec, host, MAXHOSTNAMELEN); + if (!*cp) { + warnx("URL `%s' has bad host part!", spec); + return NULL; + } + cp = fileURLFilename(spec, file, MaxPathSize); + if (!*cp) { + warnx("URL `%s' has bad filename part!", spec); + return NULL; + } + + if (Verbose) + printf("Trying to fetch %s.\n", spec); + + pen[0] = '\0'; + rp = make_playpen(pen, sizeof(pen), 0); + if (rp == NULL) { + printf("Error: Unable to construct a new playpen for FTP!\n"); + return NULL; + } + + rp = strdup(pen); + rc = unpackURL(spec, pen); + if (rc < 0) { + leave_playpen(rp); /* Don't leave dir hang around! */ + + printf("Error on unpackURL('%s', '%s')\n", spec, pen); + return NULL; + } + return rp; +} + +static char * +resolvepattern1(const char *name) +{ + static char tmp[MaxPathSize]; + char *cp; + + if (IS_URL(name)) { + /* some package depends on a wildcard pkg */ + int rc; + + rc = expandURL(tmp, name); + if (rc < 0) { + return NULL; + } + if (Verbose) + printf("'%s' expanded to '%s'\n", name, tmp); + return tmp; /* return expanded URL w/ corrent pkg */ + } + else if (ispkgpattern(name)) { + cp = find_best_matching_file(dirname_of(name), basename_of(name), 1, 0); + if (cp) { + snprintf(tmp, sizeof(tmp), "%s/%s", dirname_of(name), cp); + free(cp); + return tmp; + } + } else { + if (isfile(name)) { + strlcpy(tmp, name, sizeof(tmp)); + return tmp; + } + } + + return NULL; +} + +static char * +resolvepattern(const char *name) +{ + char tmp[MaxPathSize]; + char *cp; + const char *suf; + + cp = resolvepattern1(name); + if (cp != NULL) + return cp; + + if (ispkgpattern(name)) + return NULL; + + suf = suffix_of(name); + if (!strcmp(suf, "tbz") || !strcmp(suf, "tgz")) + return NULL; + + /* add suffix and try */ + snprintf(tmp, sizeof(tmp), "%s.tbz", name); + cp = resolvepattern1(tmp); + if (cp != NULL) + return cp; + snprintf(tmp, sizeof(tmp), "%s.tgz", name); + cp = resolvepattern1(tmp); + if (cp != NULL) + return cp; + + /* add version number wildcard and try */ + snprintf(tmp, sizeof(tmp), "%s-[0-9]*", name); + return resolvepattern1(tmp); +} + +/* + * Look for filename/pattern "fname" in + * Returns a full path/URL where the pkg can be found + */ +char * +fileFindByPath(const char *fname) +{ + char tmp[MaxPathSize]; + struct path *path; + + /* + * 1. if fname is an absolute pathname or a URL, + * just use it. + */ + if (IS_FULLPATH(fname) || IS_URL(fname)) + return resolvepattern(fname); + + /* + * 2. otherwise, use PKG_PATH. + */ + TAILQ_FOREACH(path, &PkgPath, pl_entry) { + char *cp; + const char *cp2 = path->pl_path; + + if (Verbose) + printf("trying PKG_PATH %s\n", cp2); + + if (IS_FULLPATH(cp2) || IS_URL(cp2)) { + snprintf(tmp, sizeof(tmp), "%s/%s", cp2, fname); + } + else { + char cwdtmp[MaxPathSize]; + if (getcwd(cwdtmp, sizeof(cwdtmp)) == NULL) + errx(EXIT_FAILURE, "getcwd"); + snprintf(tmp, sizeof(tmp), "%s/%s/%s", cwdtmp, cp2, fname); + } + cp = resolvepattern(tmp); + if (cp) + return cp; + } + +#if 0 + /* + * 3. finally, search current directory. + */ + snprintf(tmp, sizeof(tmp), "./%s", fname); + return resolvepattern(tmp); +#else + return NULL; +#endif +} + +/* + * Expect "fname" to point at a file, and read it into + * the buffer returned. + */ +char * +fileGetContents(char *fname) +{ + char *contents; + struct stat sb; + int fd; + + if (stat(fname, &sb) == FAIL) { + cleanup(0); + errx(2, "can't stat '%s'", fname); + } + + contents = (char *) malloc((size_t) (sb.st_size) + 1); + fd = open(fname, O_RDONLY, 0); + if (fd == FAIL) { + cleanup(0); + errx(2, "unable to open '%s' for reading", fname); + } + if (read(fd, contents, (size_t) sb.st_size) != (size_t) sb.st_size) { + cleanup(0); + errx(2, "short read on '%s' - did not get %lld bytes", + fname, (long long) sb.st_size); + } + close(fd); + contents[(size_t) sb.st_size] = '\0'; + return contents; +} + +/* * Takes a filename and package name, returning (in "try") the canonical * "preserve" name for it. */ @@ -252,6 +509,97 @@ make_preserve_name(char *try, size_t max, char *name, char *file) return TRUE; } +/* + * Write the contents of "str" to a file + */ +void +write_file(char *name, char *str) +{ + size_t len; + FILE *fp; + + if ((fp = fopen(name, "w")) == (FILE *) NULL) { + cleanup(0); + errx(2, "cannot fopen '%s' for writing", name); + } + len = strlen(str); + if (fwrite(str, 1, len, fp) != len) { + cleanup(0); + errx(2, "short fwrite on '%s', tried to write %ld bytes", + name, (long) len); + } + if (fclose(fp)) { + cleanup(0); + errx(2, "failure to fclose '%s'", name); + } +} + +void +copy_file(char *dir, char *fname, char *to) +{ + char fpath[MaxPathSize]; + + (void) snprintf(fpath, sizeof(fpath), "%s%s%s", + (fname[0] != '/') ? dir : "", + (fname[0] != '/') ? "/" : "", + fname); + if (fexec("cp", "-r", fpath, to, NULL)) { + cleanup(0); + errx(2, "could not perform 'cp -r %s %s'", fpath, to); + } +} + +void +move_file(char *dir, char *fname, char *to) +{ + char fpath[MaxPathSize]; + + (void) snprintf(fpath, sizeof(fpath), "%s%s%s", + (fname[0] != '/') ? dir : "", + (fname[0] != '/') ? "/" : "", + fname); + if (fexec("mv", fpath, to, NULL)) { + cleanup(0); + errx(2, "could not perform 'mv %s %s'", fpath, to); + } +} + +void +move_files(const char *dir, const char *pattern, const char *to) +{ + char fpath[MaxPathSize]; + glob_t globbed; + size_t i; + + (void) snprintf(fpath, sizeof(fpath), "%s/%s", dir, pattern); + if ((i=glob(fpath, GLOB_NOSORT, NULL, &globbed)) != 0) { + switch(i) { + case GLOB_NOMATCH: + warn("no files matching ``%s'' found", fpath); + break; + case GLOB_ABORTED: + warn("globbing aborted"); + break; + case GLOB_NOSPACE: + warn("out-of-memory during globbing"); + break; + default: + warn("unknown error during globbing"); + break; + } + return; + } + + /* Moving globbed files -- we just use mv(1) to do the job */ + for (i=0; i<globbed.gl_pathc; i++) + if (fexec("mv", globbed.gl_pathv[i], to, NULL)) { + cleanup(0); + errx(2, "could not perform 'mv %s %s'", globbed.gl_pathv[i], to); + } + + return; +} + void remove_files(const char *path, const char *pattern) { @@ -287,6 +635,74 @@ remove_files(const char *path, const char *pattern) } /* + * Unpack a tar file + */ +int +unpack(const char *pkg, const lfile_head_t *filesp) +{ + const char *decompress_cmd = NULL; + const char *suf; + int count = 0; + lfile_t *lfp; + char **up_argv; + int up_argc = 7; + int i = 0; + int result; + + if (filesp != NULL) + TAILQ_FOREACH(lfp, filesp, lf_link) + count++; + up_argc += count; + up_argv = malloc((count + up_argc + 1) * sizeof(char *)); + if (!IS_STDIN(pkg)) { + suf = suffix_of(pkg); + if (!strcmp(suf, "tbz") || !strcmp(suf, "bz2")) + decompress_cmd = BZIP2_CMD; + else if (!strcmp(suf, "tgz") || !strcmp(suf, "gz")) + decompress_cmd = GZIP_CMD; + else if (!strcmp(suf, "tar")) + ; /* do nothing */ + else + errx(EXIT_FAILURE, "don't know how to decompress %s, sorry", pkg); + } else + decompress_cmd = GZIP_CMD; + + up_argv[i] = (char *)strrchr(TAR_CMD, '/'); + if (up_argv[i] == NULL) + up_argv[i] = TAR_CMD; + else + up_argv[i]++; /* skip / character */ + if (count > 0) + up_argv[++i] = "--fast-read"; + if (decompress_cmd != NULL) { + up_argv[++i] = "--use-compress-program"; + up_argv[++i] = (char *)decompress_cmd; + } + up_argv[++i] = "-xpf"; + up_argv[++i] = (char *)pkg; + if (count > 0) + TAILQ_FOREACH(lfp, filesp, lf_link) + up_argv[++i] = lfp->lf_name; + up_argv[++i] = NULL; + + if (Verbose) { + printf("running: %s", TAR_CMD); + for (i = 1; up_argv[i] != NULL; i++) + printf(" %s", up_argv[i]); + printf("\n"); + } + + result = pfcexec(NULL, TAR_CMD, (const char **)up_argv); + free(up_argv); + if (result != 0) { + warnx("extract of %s failed", pkg); + return 1; + } + + return 0; +} + +/* * Using fmt, replace all instances of: * * %F With the parameter "name" @@ -296,8 +712,8 @@ remove_files(const char *path, const char *pattern) * * Check that no overflows can occur. */ -int -format_cmd(char *buf, size_t size, const char *fmt, const char *dir, const char *name) +void +format_cmd(char *buf, size_t size, char *fmt, char *dir, char *name) { char scratch[MaxPathSize * 2]; char *bufp; @@ -306,8 +722,8 @@ format_cmd(char *buf, size_t size, const char *fmt, const char *dir, const char for (bufp = buf; (int) (bufp - buf) < size && *fmt;) { if (*fmt == '%') { if (*++fmt != 'D' && name == NULL) { - warnx("no last file available for '%s' command", buf); - return -1; + cleanup(0); + errx(2, "no last file available for '%s' command", buf); } switch (*fmt) { case 'F': @@ -352,5 +768,4 @@ format_cmd(char *buf, size_t size, const char *fmt, const char *dir, const char } } *bufp = '\0'; - return 0; } diff --git a/pkgtools/pkg_install/files/lib/ftpio.c b/pkgtools/pkg_install/files/lib/ftpio.c new file mode 100644 index 00000000000..17142056b26 --- /dev/null +++ b/pkgtools/pkg_install/files/lib/ftpio.c @@ -0,0 +1,1259 @@ +/* $NetBSD: ftpio.c,v 1.28 2008/04/26 17:40:01 joerg Exp $ */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <nbcompat.h> +#if HAVE_SYS_CDEFS_H +#include <sys/cdefs.h> +#endif +#ifndef lint +__RCSID("$NetBSD: ftpio.c,v 1.28 2008/04/26 17:40:01 joerg Exp $"); +#endif + +/*- + * Copyright (c) 1999-2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Hubert Feyrer <hubert@feyrer.de> and Thomas Klausner. + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#if HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#if HAVE_SYS_POLL_H +#include <sys/poll.h> +#endif +#if HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#if HAVE_SIGNAL_H +#include <signal.h> +#endif +#if HAVE_ASSERT_H +#include <assert.h> +#endif +#if HAVE_CTYPE_H +#include <ctype.h> +#endif +#if HAVE_ERR_H +#include <err.h> +#endif +#if HAVE_ERRNO_H +#include <errno.h> +#endif +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#if HAVE_NETDB_H +#include <netdb.h> +#endif +#if HAVE_REGEX_H +#include <regex.h> +#endif +#if HAVE_STRING_H +#include <string.h> +#endif +#if HAVE_STDIO_H +#include <stdio.h> +#endif +#if HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if HAVE_TERMCAP_H +#include <termcap.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../lib/lib.h" + +/* + * Names of environment variables used to pass things to + * subprocesses, for connection caching. + */ +#define PKG_FTPIO_COMMAND "PKG_FTPIO_COMMAND" +#define PKG_FTPIO_ANSWER "PKG_FTPIO_ANSWER" +#define PKG_FTPIO_CNT "PKG_FTPIO_CNT" +#define PKG_FTPIO_CURRENTHOST "PKG_FTPIO_CURRENTHOST" +#define PKG_FTPIO_CURRENTDIR "PKG_FTPIO_CURRENTDIR" + +#undef STANDALONE /* define for standalone debugging */ + +/* File descriptors */ +typedef struct { + int command; + int answer; +} fds; + + +static int needclose=0; +static int ftp_started=0; +static fds ftpio; +static int ftp_pid; +static char term[1024]; +static char bold_on[1024]; +static char bold_off[1024]; + +static char *ftp_expand_URL(const char *, char *); +static int hexvalue(char); +static char *http_expand_URL(const char *, char *); +static int http_extract_fn(char *, char *, size_t); +static void URL_decode(char *); + +/* + * expect "str" (a regular expression) on file descriptor "fd", storing + * the FTP return code of the command in the integer "ftprc". The "str" + * string is expected to match some FTP return codes after a '\n', e.g. + * "\n(550|226).*\n" + */ +static int +expect(int fd, const char *str, int *ftprc) +{ + int rc; + char buf[256]; + regex_t rstr; + int done; + struct pollfd set[1]; + int retval; + regmatch_t match; + int verbose_expect=0; + + if (regcomp(&rstr, str, REG_EXTENDED) != 0) + err(EXIT_FAILURE, "expect: regcomp() failed"); + + memset(buf, '\n', sizeof(buf)); + + done=0; + retval=0; + set[0].fd = fd; + set[0].events = POLLIN; + while(!done) { + rc = poll(set, 1, 60*60*1000); /* seconds until next message from tar */ + switch (rc) { + case -1: + if (errno == EINTR) + break; + warn("expect: poll() failed (probably ftp died because of bad args)"); + done = 1; + retval = -1; + break; + case 0: + warnx("expect: poll() timeout"); + /* need to send ftp coprocess SIGINT to make it stop + * downloading into dir that we'll blow away in a second */ + kill(ftp_pid, SIGINT); + + /* Wait until ftp coprocess is responsive again + * XXX Entering recursion here! + */ + rc = ftp_cmd("cd .\n", "\n(550|250).*\n"); + if (rc != 250) { + /* now we have a really good reason to bail out ;) */ + } + /* ftp is at command prompt again, and will wait for our + * next command. If we were downloading, we can now safely + * continue and remove the dir that the tar command was + * expanding to */ + + done = 1; /* hope that's ok */ + retval = -1; + break; + default: + if (set[0].revents & POLLHUP) { + done = 1; + retval = -1; + break; + } + + rc = read(fd, &buf[sizeof(buf) - 1], 1); + if (rc <= 0) { + done = 1; + retval = -1; + break; + } + + if (verbose_expect) + putchar(buf[sizeof(buf)-1]); + + if (regexec(&rstr, buf, 1, &match, 0) == 0) { + if (ftprc && isdigit((unsigned char)buf[match.rm_so+1])) + *ftprc = atoi(buf+match.rm_so+1); + + done=1; + retval=0; + } + + memmove(buf, buf+1, sizeof(buf)-1); /* yes, this is non-performant */ + break; + } + } + + return retval; +} + +/* + * send a certain ftp-command "cmd" to our FTP coprocess, and wait for + * "expectstr" to be returned. Return numeric FTP return code or -1 + * in case of an error (usually expect() timeout) + */ +int +ftp_cmd(const char *cmd, const char *expectstr) +{ + int rc=0, verbose_ftp=0; + int len; + + if (Verbose) + verbose_ftp=1; + + if (verbose_ftp) + fprintf(stderr, "\n%sftp> %s%s", bold_on, cmd, bold_off); + + fflush(stdout); + len = write(ftpio.command, cmd, strlen(cmd)); + if (len == strlen(cmd)) { + if (expectstr) { + /* set "rc" to the FTP error code: */ + if (expect(ftpio.answer, expectstr, &rc) == -1) + rc = -1; /* some error occurred */ + } + } else { + if (Verbose) + warn("short write"); + } + + return rc; +} + + +/* + * Really fire up FTP coprocess + */ +static int +setupCoproc(const char *base) +{ + int command_pipe[2]; + int answer_pipe[2]; + int rc1, rc2; + char buf[20]; + char *argv0 = (char *)strrchr(FTP_CMD, '/'); + if (argv0 == NULL) + argv0 = FTP_CMD; + else + argv0++; + + rc1 = pipe(command_pipe); + rc2 = pipe(answer_pipe); + + if(rc1==-1 || rc2==-1) { + warn("setupCoproc: pipe() failed"); + return -1; + } + + if (command_pipe[0] == -1 || command_pipe[1] == -1 || + answer_pipe[0] == -1 || answer_pipe[1] == -1 ) { + warn("setupCoproc: pipe() returned bogus descriptor"); + return -1; + } + + rc1 = fork(); + switch (rc1) { + case -1: + /* Error */ + + warn("setupCoproc: fork() failed"); + return -1; + break; + + case 0: + /* Child */ + + (void) close(command_pipe[1]); + rc1 = dup2(command_pipe[0], 0); + if (rc1 == -1) { + err(EXIT_FAILURE, "setupCoproc: dup2 failed (command_pipe[0])"); + } + (void) close(command_pipe[0]); + + (void) close(answer_pipe[0]); + rc1 = dup2(answer_pipe[1], 1); + if (rc1 == -1) { + err(EXIT_FAILURE, "setupCoproc: dup2 failed (answer_pipe[1])"); + } + (void) close(answer_pipe[1]); + + setbuf(stdout, NULL); + + if (Verbose) + fprintf(stderr, "%sftp -detv %s%s\n", bold_on, base, bold_off); + rc1 = execlp(FTP_CMD, argv0, "-detv", base, NULL); + warn("setupCoproc: execlp() failed"); + exit(1); + break; + default: + /* Parent */ + (void) close(command_pipe[0]); + (void) close(answer_pipe[1]); + + (void) snprintf(buf, sizeof(buf), "%d", command_pipe[1]); + setenv(PKG_FTPIO_COMMAND, buf, 1); + (void) snprintf(buf, sizeof(buf), "%d", answer_pipe[0]); + setenv(PKG_FTPIO_ANSWER, buf, 1); + + ftpio.command = command_pipe[1]; + ftpio.answer = answer_pipe[0]; + ftp_pid = rc1; /* to ^C transfers */ + + fcntl(ftpio.command, F_SETFL, O_NONBLOCK); + fcntl(ftpio.answer , F_SETFL, O_NONBLOCK); + + break; + } + + return 0; +} + + +/* + * Dummy signal handler to detect if the ftp(1) coprocess or + * and of the processes of the tar/gzip pipeline dies. + */ +static void +sigchld_handler (int n) +{ + /* Make poll(2) return EINTR */ +} + + +/* + * SIGPIPE only happens when there's something wrong with the FTP + * coprocess. In that case, set mark to not try to close shut down + * the coprocess. + */ +static void +sigpipe_handler(int n) +{ + /* aparently our ftp companion died */ + if (Verbose) + fprintf(stderr, "SIGPIPE!\n"); + needclose = 0; +} + + +/* + * Close the FTP coprocess' current connection, but + * keep the process itself alive. + */ +void +ftp_stop(void) +{ +#if defined(__svr4__) && defined(__sun__) + char env[BUFSIZ]; +#endif + const char *tmp1, *tmp2; + + if (!ftp_started) + return; + + tmp1=getenv(PKG_FTPIO_COMMAND); + tmp2=getenv(PKG_FTPIO_ANSWER); + + /* (Only) the last one closes the link */ + if (tmp1 != NULL && tmp2 != NULL) { + if (needclose) + ftp_cmd("close\n", "\n(221 .*|Not connected.)\n"); + + (void) close(ftpio.command); + (void) close(ftpio.answer); + } + +#if defined(__svr4__) && defined(__sun__) + (void) snprintf(env, sizeof(env), "%s=", PKG_FTPIO_COMMAND); + putenv(env); + (void) snprintf(env, sizeof(env), "%s=", PKG_FTPIO_ANSWER); + putenv(env); +#else + unsetenv(PKG_FTPIO_COMMAND); + unsetenv(PKG_FTPIO_ANSWER); +#endif +} + + +/* + * (Start and re-)Connect the FTP coprocess to some host/dir. + * If the requested host/dir is different than the one that the + * coprocess is currently at, close first. + */ +int +ftp_start(const char *base) +{ + const char *tmp1, *tmp2; + char *p; + int rc; + char newHost[MAXHOSTNAMELEN]; + const char *newDir; + const char *currentHost=getenv(PKG_FTPIO_CURRENTHOST); + const char *currentDir=getenv(PKG_FTPIO_CURRENTDIR); + int urllen; + + /* talk to termcap for bold on/off escape sequences */ + if (getenv("TERM") != NULL && tgetent(term, getenv("TERM")) > 0) { + p = bold_on; tgetstr("md", &p); + p = bold_off; tgetstr("me", &p); + } else { + bold_on[0] = '\0'; + bold_off[0] = '\0'; + } + + fileURLHost(base, newHost, sizeof(newHost)); + urllen = URLlength(base); + if (urllen < 0 || !(newDir = strchr(base + URLlength(base), '/'))) + errx(EXIT_FAILURE, "ftp_start: bad URL '%s'", base); + newDir++; + if (currentHost + && currentDir + && ( strcmp(newHost, currentHost) != 0 + || strcmp(newDir, currentDir) != 0)) { /* could handle new dir case better here, w/o reconnect */ + if (Verbose) { + printf("ftp_start: new host or dir, stopping previous connect...\n"); + printf("currentHost='%s', newHost='%s'\n", currentHost, newHost); + printf("currentDir='%s', newDir='%s'\n", currentDir, newDir); + } + + ftp_stop(); + + if (Verbose) + printf("ftp stopped\n"); + } + setenv(PKG_FTPIO_CURRENTHOST, newHost, 1); /* need to update this in the environment */ + setenv(PKG_FTPIO_CURRENTDIR, newDir, 1); /* for subprocesses to have this available */ + + tmp1=getenv(PKG_FTPIO_COMMAND); + tmp2=getenv(PKG_FTPIO_ANSWER); + if(tmp1==NULL || tmp2==NULL || *tmp1=='\0' || *tmp2=='\0') { + /* no FTP coprocess running yet */ + + if (Verbose) + printf("Spawning FTP coprocess\n"); + + rc = setupCoproc(base); + if (rc == -1) { + warnx("setupCoproc() failed"); + return -1; + } + + needclose=1; + signal(SIGPIPE, sigpipe_handler); + signal(SIGCHLD, sigchld_handler); + + if ((expect(ftpio.answer, "\n(221|250|221|550).*\n", &rc) != 0) + || rc != 250) { + warnx("expect1 failed, rc=%d", rc); + return -1; + } + + /* nbftp now issues a CWD for each part of the path + * and will return a code for each of them. No idea how to + * deal with that other than to issue a 'prompt off' to + * get something that we can wait for and that does NOT + * look like a CWD command's output */ + rc = ftp_cmd("prompt off\n", "\n(Interactive mode off|221).*\n"); + if ((rc == 221) || (rc == -1)) { + /* something is wrong */ + ftp_started=1; /* not really, but for ftp_stop() */ + ftp_stop(); + warnx("prompt failed - wrong dir?"); + return -1; + } + + ftp_started=1; + } else { + /* get FDs of our coprocess */ + + ftpio.command = dup(atoi(tmp1)); + if (ftpio.command == -1 ) { + warnx("command dup() failed, increase 'descriptors' limit"); + return -1; + } + ftpio.answer = dup(atoi(tmp2)); + if (ftpio.answer == -1 ) { + warnx("answer dup() failed, increase 'descriptors' limit"); + return -1; + } + + if (Verbose) + printf("Reusing FDs %s/%s for communication to FTP coprocess\n", tmp1, tmp2); + + fcntl(ftpio.command, F_SETFL, O_NONBLOCK); + fcntl(ftpio.answer , F_SETFL, O_NONBLOCK); + } + + return 0; +} + + +/* + * Expand the given wildcard URL "wildcardurl" if possible, and store the + * expanded value into "expandedurl". return 0 if successful, -1 else. + */ +int +expandURL(char *expandedurl, const char *wildcardurl) +{ + char *pattern; + char *bestmatch; + char base[MaxPathSize]; + + pattern=strrchr(wildcardurl, '/'); + if (pattern == NULL){ + warnx("expandURL: no '/' in URL %s?!", wildcardurl); + return -1; + } + if (pattern-strchr(wildcardurl, '/') < 2) { + /* only one or two slashes in total */ + warnx("expandURL: not enough '/' in URL %s", wildcardurl); + return -1; + } + (void) snprintf(base, sizeof(base), "%.*s/", + (int)(pattern-wildcardurl), wildcardurl); + pattern++; + + if (strncmp(wildcardurl, "ftp://", 6) == 0) + bestmatch=ftp_expand_URL(base, pattern); + else if (strncmp(wildcardurl, "http://", 7) == 0) + bestmatch=http_expand_URL(base, pattern); + else { + warnx("expandURL: unknown protocol in URL `%s'", wildcardurl); + return -1; + } + + /* no match found */ + if (bestmatch == NULL) + return -1; + + snprintf(expandedurl, MaxPathSize, "%s%s", base, bestmatch); + if (Verbose) + printf("best match: '%s'\n", expandedurl); + + return 0; +} + +/* for a given wildcard ftp:// URL, find the best matching pkg */ +static char * +ftp_expand_URL(const char *base, char *pattern) +{ + char *s, buf[MaxPathSize]; + char tmpname[MaxPathSize]; + char best[MaxPathSize]; + char s_best[MaxPathSize]; + int rc, got_list, tfd, retry_tbz; + + retry_tbz = 0; + best[0]='\0'; + s_best[0]='\0'; + + rc = ftp_start(base); + if (rc == -1) { + warnx("ftp_start() failed"); + return NULL; + } + + strlcpy(tmpname, "/var/tmp/pkg.XXXXXX", sizeof(tmpname)); + tfd=mkstemp(tmpname); + if (tfd == -1) { + warnx("Cannot generate temp file for ftp(1)'s nlist output"); + return NULL; + } + close(tfd); /* We don't need the file descriptor, but will use + the file in a second */ + + s=strpbrk(pattern, "<>[]?*{"); /* Could leave out "[]?*" here; + * ftp(1) is not that stupid */ + if (!s) { + /* This should only happen when getting here with (only) a package + * name specified to pkg_add, and PKG_PATH containing some URL. + */ + (void) snprintf(buf, sizeof(buf), "nlist %s %s\n", pattern, tmpname); + } else { + /* replace possible version(wildcard) given with "-*". + * we can't use the pkg wildcards here as dewey compare + * and alternates won't be handled by ftp(1); sort + * out later, using pkg_match() */ + if (retry_tbz) { +retry_with_tbz: + (void) snprintf(buf, sizeof(buf), "nlist %.*s*.tbz %s\n", + (int)(s-pattern), pattern, tmpname); + retry_tbz = 0; + } else { + (void) snprintf(buf, sizeof(buf), "nlist %.*s*.tgz %s\n", + (int)(s-pattern), pattern, tmpname); + retry_tbz = 1; + } + } + + rc = ftp_cmd(buf, "\n(550|450|226).*\n"); /* catch errors */ + if (rc != 226) + got_list = 0; + else + got_list = 1; + + /* Sync - don't remove */ + rc = ftp_cmd("cd .\n", "\n(550|250|257).*\n"); + if (rc != 250) { + warnx("chdir failed!"); + unlink(tmpname); /* remove clutter */ + return NULL; + } + + if (got_list == 1 && access(tmpname, R_OK)==0) { + FILE *f; + char filename[MaxPathSize]; + + f=fopen(tmpname, "r"); + if (f == NULL) { + warn("fopen"); + unlink(tmpname); /* remove clutter */ + return NULL; + } + /* The following loop is basically the same as the readdir() loop + * in findmatchingname() */ + while (fgets(filename, sizeof(filename), f)) { + + /* + * We need to strip off any .t[bg]z etc. + * suffix here + */ + + char s_filename[MaxPathSize]; + char s_pattern[MaxPathSize]; + + filename[strlen(filename)-1] = '\0'; + + strip_txz(s_filename, NULL, filename); + strip_txz(s_pattern, NULL, pattern); + + if (pkg_order(s_pattern, s_filename, + s_best[0] != '\0' ? s_best : NULL) == 1) { + strlcpy(s_best, s_filename, sizeof(s_best)); + strlcpy(best, filename, sizeof(best)); + } + } + (void) fclose(f); + } + + if (retry_tbz) + goto retry_with_tbz; + + if (best[0] == '\0' && Verbose) + warnx("nothing appropriate found"); + + unlink(tmpname); + + if (best[0] == '\0') + return NULL; + + return strdup(best); +} + +/* for a given wildcard http:// URL, find the best matching pkg */ +static char * +http_expand_URL(const char *base, char *pattern) +{ + char best[MaxPathSize]; + char s_best[MaxPathSize]; + char line[BUFSIZ]; + char filename[MaxPathSize]; + FILE *fp; + int pipefds[2]; + int state; + pid_t pid; + + *best = '\0'; + *s_best = '\0'; + + /* Set up a pipe for getting the file list */ + if (pipe(pipefds) == -1) { + warnx("cannot create pipe"); + return NULL; + } + if ((pid = fork()) == -1) { + warnx("cannot fork ftp process"); + return NULL; + } + if (pid == 0) { /* The child */ + if (dup2(pipefds[1], STDOUT_FILENO) == -1) { + warnx("dup2 failed before starting ftp"); + _exit(2); + } + close(pipefds[0]); + close(pipefds[1]); + /* get URL contents to stdout and thus to parent, + * silently */ + execlp("ftp", "ftp", "-V", "-o", "-", base, NULL); + warnx("failed to execute ftp"); + _exit(2); + } + + /* parent */ + close(pipefds[1]); + + if ((fp=fdopen(pipefds[0], "r")) == NULL) + warn("can't fdopen pipe end"); + else { + char s_pattern[MaxPathSize]; + int len, offset; + + /* strip of .t[bg]z for comparison */ + strip_txz(s_pattern, NULL, pattern); + + /* initialize http_extract_fn internal state */ + http_extract_fn(NULL, NULL, 0); + + /* read line from HTTP output and extract filenames */ + while (fgets(line, sizeof(line), fp) != NULL) { + len = offset = 0; + while ((len=http_extract_fn(line+offset, filename, + sizeof(filename))) > 0) { + char s_filename[MaxPathSize]; + + offset += len; + strip_txz(s_filename, NULL, filename); + + if (pkg_order(s_pattern, s_filename, + *s_best != '\0' ? s_best : NULL) == 1) { + strlcpy(best, filename, sizeof(best)); + strlcpy(s_best, s_filename, sizeof(best)); + } + } + } + + } + + fclose(fp); + + /* wait for child to exit */ + if (waitpid(pid, &state, 0) < 0) { + /* error has been reported by child */ + return NULL; + } + + if (best[0] == '\0') { + if (Verbose) + warnx("nothing appropriate found"); + return NULL; + } + + return strdup(best); + +} + +enum http_states { + ST_NONE, + ST_LT, ST_LTA, ST_TAGA, ST_H, ST_R, ST_E, ST_F, ST_HREF, + ST_TAG, ST_TAGAX +}; + +/* return any hrefs found */ +static int +http_extract_fn(char *input, char *outbuf, size_t outbuflen) +{ + /* partial copied hrefs from previous calls are saved here */ + static char tempbuf[MaxPathSize]; + /* fill state of tempbuf */ + static int tempbuffill = 0; + /* parsing state information */ + static enum http_states state; + /* currently in double quotes (in parsing) */ + static int dqflag; + char p; + int offset, found; + + if (outbuf == NULL) { + /* init */ + dqflag = tempbuffill = 0; + state = ST_NONE; + return 0; + } + + offset = 0; + found = 0; + while ((p=input[offset++]) != '\0') { + /* handle anything that's inside double quotes */ + if (dqflag) { + /* incomplete href */ + if (state == ST_HREF) { + /* check if space left in output + * buffer */ + if (tempbuffill >= sizeof(tempbuf)) { + warnx("href starting with `%.*s'" + " too long", 60, tempbuf); + /* ignore remainder */ + tempbuffill = 0; + /* need space before "href" + * can start again (invalidly, + * of course, but we don't + * care) */ + state = ST_TAGAX; + } + + /* href complete */ + if (p == '\"') { + /* complete */ + dqflag = 0; + tempbuf[tempbuffill++] = '\0'; + /* need space before "href" + * can start again (invalidly, + * of course, but we don't + * care) */ + state = ST_TAGAX; + found = 1; + break; + } else { + /* copy one more char */ + tempbuf[tempbuffill++] = p; + } + } else { + /* leaving double quotes */ + if (p == '\"') + dqflag = 0; + } + continue; + } + + /* + * entering double quotes? (only relevant inside a tag) + */ + if (state != ST_NONE && p == '\"') { + dqflag = 1; + continue; + } + + /* other cases */ + switch (state) { + case ST_NONE: + /* plain text, not in markup */ + if (p == '<') + state = ST_LT; + break; + case ST_LT: + /* in tag -- "<" already found */ + if (p == '>') + state = ST_NONE; + else if (p == 'a' || p == 'A') + state = ST_LTA; + else if (!isspace((unsigned char)p)) + state = ST_TAG; + break; + case ST_LTA: + /* in tag -- "<a" already found */ + if (p == '>') + state = ST_NONE; + else if (isspace((unsigned char)p)) + state = ST_TAGA; + else + state = ST_TAG; + break; + case ST_TAG: + /* in tag, but not "<a" -- disregard */ + if (p == '>') + state = ST_NONE; + break; + case ST_TAGA: + /* in a-tag -- "<a " already found */ + if (p == '>') + state = ST_NONE; + else if (p == 'h' || p == 'H') + state = ST_H; + else if (!isspace((unsigned char)p)) + state = ST_TAGAX; + break; + case ST_TAGAX: + /* in unknown keyword in a-tag */ + if (p == '>') + state = ST_NONE; + else if (isspace((unsigned char)p)) + state = ST_TAGA; + break; + case ST_H: + /* in a-tag -- "<a h" already found */ + if (p == '>') + state = ST_NONE; + else if (p == 'r' || p == 'R') + state = ST_R; + else if (isspace((unsigned char)p)) + state = ST_TAGA; + else + state = ST_TAGAX; + break; + case ST_R: + /* in a-tag -- "<a hr" already found */ + if (p == '>') + state = ST_NONE; + else if (p == 'e' || p == 'E') + state = ST_E; + else if (isspace((unsigned char)p)) + state = ST_TAGA; + else + state = ST_TAGAX; + break; + case ST_E: + /* in a-tag -- "<a hre" already found */ + if (p == '>') + state = ST_NONE; + else if (p == 'f' || p == 'F') + state = ST_F; + else if (isspace((unsigned char)p)) + state = ST_TAGA; + else + state = ST_TAGAX; + break; + case ST_F: + /* in a-tag -- "<a href" already found */ + if (p == '>') + state = ST_NONE; + else if (p == '=') + state = ST_HREF; + else if (!isspace((unsigned char)p)) + state = ST_TAGAX; + break; + case ST_HREF: + /* in a-tag -- "<a href=" already found */ + /* XXX: handle missing double quotes? */ + if (p == '>') + state = ST_NONE; + /* skip spaces before URL */ + else if (!isspace((unsigned char)p)) + state = ST_TAGA; + break; + /* no default case by purpose */ + } + } + + if (p == '\0') + return -1; + + if (found) { + char *q; + + URL_decode(tempbuf); + + /* strip path (XXX) */ + if ((q=strrchr(tempbuf, '/')) == NULL) + q = tempbuf; + + (void)strlcpy(outbuf, q, outbuflen); + tempbuffill = 0; + } + + return offset; +} + + +static int +hexvalue(char p) +{ + if (p >= '0' && p <= '9') + return (p-'0'); + else if (p >= 'a' && p <= 'f') + return (p-'a'+10); + else if (p >= 'A' && p <= 'F') + return (p-'A'+10); + else + return -1; +} + +/* fetch and extract URL url into directory path */ +static int +http_fetch(const char *url, const char *path) +{ + int pipefds[2]; + int stateftp, state; + pid_t pidftp, pid; + + /* Set up a pipe for passing the fetched contents. */ + if (pipe(pipefds) == -1) { + warn("cannot create pipe"); + return -1; + } + /* fork ftp child */ + if ((pidftp = fork()) == -1) { + warn("cannot fork process for ftp"); + return -1; + } + if (pidftp == 0) { + /* child */ + if (dup2(pipefds[1], STDOUT_FILENO) == -1) { + warn("dup2 failed before executing ftp"); + _exit(2); + } + close(pipefds[0]); + close(pipefds[1]); + execlp(FTP_CMD, FTP_CMD, "-o", "-", url, NULL); + warnx("failed to execute ftp"); + _exit(2); + } + + /* fork unpack child */ + if ((pid = fork()) == -1) { + warn("cannot fork unpack process"); + return -1; + } + if (pid == 0) { + /* child */ + if (dup2(pipefds[0], STDIN_FILENO) == -1) { + warn("dup2 failed before unpack"); + _exit(2); + } + close(pipefds[0]); + close(pipefds[1]); + if ((path != NULL) && (chdir(path) < 0)) + _exit(127); + + if (unpack("-", NULL) != 0) { + warnx("unpack failed"); + _exit(2); + } + + _exit(0); + } + + close(pipefds[0]); + close(pipefds[1]); + + /* wait for unpack to exit */ + while (waitpid(pid, &state, 0) < 0) { + if (errno != EINTR) { + (void)waitpid(pidftp, &stateftp, 0); + return -1; + } + } + while (waitpid(pidftp, &stateftp, 0) < 0) { + if (errno != EINTR) { + return -1; + } + } + + if (!WIFEXITED(state) || !WIFEXITED(stateftp)) + return -1; + + if (WEXITSTATUS(state) != 0 || WEXITSTATUS(stateftp) != 0) + return -1; + + return 0; +} + +static void +URL_decode(char *URL) +{ + char *in, *out; + + in = out = URL; + + while (*in != '\0') { + if (in[0] == '%' && in[1] != '\0' && in[2] != '\0') { + /* URL-decode character */ + if (hexvalue(in[1]) != -1 && hexvalue(in[2]) != -1) { + *out++ = hexvalue(in[1])*16+hexvalue(in[2]); + } + /* skip invalid encoded signs too */ + in += 3; + } + else + *out++ = *in++; + } + + *out = '\0'; + + return; +} +/* + * extract the given (expanded) URL "url" to the given directory "dir" + * return -1 on error, 0 else; + */ +int +unpackURL(const char *url, const char *dir) +{ + char *pkg; + int rc; + char base[MaxPathSize]; + char pkg_path[MaxPathSize]; + + { + /* Verify if the URL is really ok */ + char expnd[MaxPathSize]; + + rc=expandURL(expnd, url); + if (rc == -1) { + warnx("unpackURL: verification expandURL failed"); + return -1; + } + if (strcmp(expnd, url) != 0) { + warnx("unpackURL: verification expandURL failed, '%s'!='%s'", + expnd, url); + return -1; + } + } + + pkg=strrchr(url, '/'); + if (pkg == NULL){ + warnx("unpackURL: no '/' in URL %s?!", url); + return -1; + } + (void) snprintf(base, sizeof(base), "%.*s/", (int)(pkg-url), url); + (void) snprintf(pkg_path, sizeof(pkg_path), "%.*s", + (int)(pkg-url), url); /* no trailing '/' */ + pkg++; + + /* Leave a hint for any depending pkgs that may need it */ + if (getenv("PKG_PATH") == NULL) { + setenv("PKG_PATH", pkg_path, 1); +#if 0 + path_create(pkg_path); /* XXX */ +#endif + if (Verbose) + printf("setenv PKG_PATH='%s'\n", pkg_path); + } + + if (strncmp(url, "http://", 7) == 0) + return http_fetch(url, dir); + + rc = ftp_start(base); + if (rc == -1) { + warnx("ftp_start() failed"); + return -1; /* error */ + } + + { + char cmd[1024]; + const char *decompress_cmd = NULL; + const char *suf; + + if (Verbose) + printf("unpackURL '%s' to '%s'\n", url, dir); + + suf = suffix_of(pkg); + if (!strcmp(suf, "tbz") || !strcmp(suf, "bz2")) + decompress_cmd = BZIP2_CMD; + else if (!strcmp(suf, "tgz") || !strcmp(suf, "gz")) + decompress_cmd = GZIP_CMD; + else if (!strcmp(suf, "tar")) + ; /* do nothing */ + else + errx(EXIT_FAILURE, "don't know how to decompress %s, sorry", pkg); + + /* yes, this is gross, but needed for borken ftp(1) */ + (void) snprintf(cmd, sizeof(cmd), "get %s \"| ( cd %s; " TAR_CMD " %s %s -vvxp -f - | tee %s )\"\n", + pkg, dir, + decompress_cmd != NULL ? "--use-compress-program" : "", + decompress_cmd != NULL ? decompress_cmd : "", + Verbose ? "/dev/stderr" : "/dev/null"); + + rc = ftp_cmd(cmd, "\n(226|550).*\n"); + if (rc != 226) { + warnx("Cannot fetch file (%d!=226)!", rc); + return -1; + } + } + + return 0; +} + + +#ifdef STANDALONE +static void +usage(void) +{ + errx(EXIT_FAILURE, "Usage: foo [-v] ftp://-pattern"); +} + + +int +main(int argc, char *argv[]) +{ + int rc, ch; + char *argv0 = argv[0]; + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + Verbose=1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc<1) + usage(); + + while(argv[0] != NULL) { + char newurl[MaxPathSize]; + + printf("Expand %s:\n", argv[0]); + rc = expandURL(newurl, argv[0]); + if (rc==-1) + warnx("Cannot expand %s", argv[0]); + else + printf("Expanded URL: %s\n", newurl); + + /* test out connection caching */ + if (1) { + char *s, buf[MaxPathSize]; + + if ((s=getenv(PKG_FTPIO_CNT)) && atoi(s)>0){ + (void) snprintf(buf, sizeof(buf),"%d", atoi(s)-1); + setenv(PKG_FTPIO_CNT, buf, 1); + + printf("%s>>> %s -v %s\n", s, argv0, argv[0]); + fexec(argv0, "-v", argv[0], NULL); + } + } + + printf("\n\n\n"); + argv++; + } + + ftp_stop(); + + return 0; +} + +void +cleanup(int i) +{ +} +#endif /* STANDALONE */ diff --git a/pkgtools/pkg_install/files/lib/lib.h b/pkgtools/pkg_install/files/lib/lib.h index e9ed05d68d7..35292dd327c 100644 --- a/pkgtools/pkg_install/files/lib/lib.h +++ b/pkgtools/pkg_install/files/lib/lib.h @@ -1,4 +1,4 @@ -/* $NetBSD: lib.h,v 1.43 2008/04/26 14:56:34 joerg Exp $ */ +/* $NetBSD: lib.h,v 1.44 2008/04/26 17:40:01 joerg Exp $ */ /* from FreeBSD Id: lib.h,v 1.25 1997/10/08 07:48:03 charnier Exp */ @@ -86,18 +86,48 @@ #define DEF_UMASK 022 #endif -#define MKDIR_CMD "mkdir" - /* Usually "rm", but often "echo" during debugging! */ #define REMOVE_CMD "rm" /* Usually "rm", but often "echo" during debugging! */ #define RMDIR_CMD "rmdir" +/* Define tar as a string, in case it's called gtar or something */ +#ifndef TAR_CMD +#define TAR_CMD "tar" +#endif + +/* Define pax as a string, used to copy files from staging area */ +#ifndef PAX_CMD +#define PAX_CMD "pax" +#endif + +/* Define gzip and bzip2, used to unpack binary packages */ +#ifndef GZIP_CMD +#define GZIP_CMD "gzip" +#endif + +#ifndef BZIP2_CMD +#define BZIP2_CMD "bzip2" +#endif + +/* Define ftp as a string, in case the ftp client is called something else */ +#ifndef FTP_CMD +#define FTP_CMD "ftp" +#endif + +#ifndef CHOWN_CMD +#define CHOWN_CMD "chown" +#endif + #ifndef CHMOD_CMD #define CHMOD_CMD "chmod" #endif +#ifndef CHGRP_CMD +#define CHGRP_CMD "chgrp" +#endif + /* some operating systems don't have this */ #ifndef MAXPATHLEN #define MAXPATHLEN 1024 @@ -128,8 +158,13 @@ enum { /* The names of special variables */ #define AUTOMATIC_VARNAME "automatic" -/* Prefix for extended PLIST cmd */ -#define CMD_CHAR '@' +/* + * files which we expect to be in every package, passed to + * tar --fast-read. + */ +#define ALL_FNAMES CONTENTS_FNAME" "COMMENT_FNAME" "DESC_FNAME" "MTREE_FNAME" "BUILD_VERSION_FNAME" "BUILD_INFO_FNAME" "SIZE_PKG_FNAME" "SIZE_ALL_FNAME + +#define CMD_CHAR '@' /* prefix for extended PLIST cmd */ /* The name of the "prefix" environment variable given to scripts */ #define PKG_PREFIX_VNAME "PKG_PREFIX" @@ -238,6 +273,14 @@ typedef struct _lpkg_t { TAILQ_HEAD(_lpkg_head_t, _lpkg_t); typedef struct _lpkg_head_t lpkg_head_t; +/* This structure describes a pipe to a child process */ +typedef struct { + int fds[2]; /* pipe, 0=child stdin, 1=parent output */ + FILE *fp; /* output from parent process */ + pid_t pid; /* process id of child process */ + void (*cleanup)(void); /* called on non-zero child exit status */ +} pipe_to_system_t; + struct pkg_vulnerabilities { size_t entries; char **vulnerability; @@ -253,17 +296,25 @@ struct pkg_vulnerabilities { #define IS_FULLPATH(str) ((str) != NULL && (str)[0] == '/') /* Conflict handling (conflicts.c) */ -int some_installed_package_conflicts_with(const char *, const char *, char **, char **); +int some_installed_package_conflicts_with(const char *, char **, char **); /* Prototypes */ /* Misc */ void cleanup(int); +char *make_playpen(char *, size_t, size_t); +char *where_playpen(void); +void leave_playpen(char *); +uint64_t min_free(const char *); +void save_dirs(char **, char **); +void restore_dirs(char *, char *); void show_version(void); int fexec(const char *, ...); int fexec_skipempty(const char *, ...); int fcexec(const char *, const char *, ...); int pfcexec(const char *, const char *, const char **); +pipe_to_system_t *pipe_to_system_begin(const char *, char *const *, void (*)(void)); +int pipe_to_system_end(pipe_to_system_t *); /* variables file handling */ @@ -284,6 +335,7 @@ const char *suffix_of(const char *); int pkg_match(const char *, const char *); int pkg_order(const char *, const char *, const char *); int ispkgpattern(const char *); +void strip_txz(char *, char *, const char *); /* Iterator functions */ int iterate_pkg_generic_src(int (*)(const char *, void *), void *, @@ -309,17 +361,35 @@ Boolean isfile(const char *); Boolean isbrokenlink(const char *); Boolean isempty(const char *); int URLlength(const char *); +char *fileGetURL(const char *); +const char *fileURLFilename(const char *, char *, int); +const char *fileURLHost(const char *, char *, int); +char *fileFindByPath(const char *); +char *fileGetContents(char *); Boolean make_preserve_name(char *, size_t, char *, char *); +void write_file(char *, char *); +void copy_file(char *, char *, char *); +void move_file(char *, char *, char *); +void move_files(const char *, const char *, const char *); void remove_files(const char *, const char *); int delete_hierarchy(char *, Boolean, Boolean); -int format_cmd(char *, size_t, const char *, const char *, const char *); +int unpack(const char *, const lfile_head_t *); +void format_cmd(char *, size_t, char *, char *, char *); + +/* ftpio.c: FTP handling */ +int expandURL(char *, const char *); +int unpackURL(const char *, const char *); +int ftp_cmd(const char *, const char *); +int ftp_start(const char *); +void ftp_stop(void); /* pkg_io.c: Local and remote archive handling */ struct archive; -struct archive *open_archive(const char *, void **); -void close_archive(void *); -struct archive *find_archive(const char *, void **); +struct archive *open_remote_archive(const char *, void **); +void close_remote_archive(void *); +struct archive *open_local_archive(const char *, void **); +void close_local_archive(void *); /* Packing list */ plist_t *new_plist_entry(void); diff --git a/pkgtools/pkg_install/files/lib/pen.c b/pkgtools/pkg_install/files/lib/pen.c new file mode 100644 index 00000000000..65451e7e4e7 --- /dev/null +++ b/pkgtools/pkg_install/files/lib/pen.c @@ -0,0 +1,206 @@ +/* $NetBSD: pen.c,v 1.24 2008/04/26 17:40:01 joerg Exp $ */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <nbcompat.h> +#if HAVE_SYS_CDEFS_H +#include <sys/cdefs.h> +#endif +#ifndef lint +#if 0 +static const char *rcsid = "from FreeBSD Id: pen.c,v 1.25 1997/10/08 07:48:12 charnier Exp"; +#else +__RCSID("$NetBSD: pen.c,v 1.24 2008/04/26 17:40:01 joerg Exp $"); +#endif +#endif + +/* + * FreeBSD install - a package for the installation and maintainance + * of non-core utilities. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Jordan K. Hubbard + * 18 July 1993 + * + * Routines for managing the "play pen". + * + */ + +#if HAVE_ERR_H +#include <err.h> +#endif +#include "lib.h" +#if HAVE_SYS_SIGNAL_H +#include <sys/signal.h> +#endif + +/* For keeping track of where we are */ +static char Current[MaxPathSize]; +static char Previous[MaxPathSize]; +static int CurrentSet; /* rm -fr Current only if it's really set! */ + /* CurrentSet is set to 0 before strcpy()s + * to prevent rm'ing of a partial string + * when interrupted by ^C */ + +char * +where_playpen(void) +{ + return Current; +} + +/* + * Find a good place to play. + */ +static char * +find_play_pen(char *pen, size_t pensize, size_t sz) +{ + const char **cp; + struct stat sb; + char *r; + const char *tmpdir[] = { + "PKG_TMPDIR", + "TMPDIR", + "/var/tmp", + "/tmp", + "/usr/tmp", + NULL + }; + + if (pen == NULL) { + cleanup(0); + errx(2, "find_play_pen(): 'pen' variable is NULL\n" + "(this should not happen, please report!)"); + return NULL; + } + + if (pen[0] && (r = strrchr(pen, '/')) != NULL) { + *r = '\0'; + if (stat(pen, &sb) != FAIL && (min_free(pen) >= sz)) { + *r = '/'; + return pen; + } + } + + for (cp = tmpdir; *cp; cp++) { + const char *d = (**cp == '/') ? *cp : getenv(*cp); + + if (d == NULL || stat(d, &sb) == FAIL || min_free(d) < sz) + continue; + + (void)snprintf(pen, pensize, "%s/instmp.XXXXXX", d); + return pen; + } + + cleanup(0); + errx(2, "Can't find enough temporary space to extract the files.\n" + "Please set your PKG_TMPDIR environment variable to a location " + "with at least %zu bytes free", sz); + return NULL; +} + +/* + * Make a temporary directory to play in and chdir() to it, returning + * pathname of previous working directory. + */ +char * +make_playpen(char *pen, size_t pensize, size_t sz) +{ + if (!find_play_pen(pen, pensize, sz)) + return NULL; + + if (!mkdtemp(pen)) { + cleanup(0); + errx(2, "can't mkdtemp '%s'", pen); + } + + /* + * On at least NetBSD, the temporary directory may have a group + * that isn't in the group list of the current user. In that + * case, it is impossible to extract setgid binaries from the + * package, since chmod(2) doesn't allow to set the S_ISGID bit + * for a group that isn't yours. + */ + (void)chown(pen, -1, getegid()); + + if (Verbose) { + if (sz) + fprintf(stderr, + "Requested space: %lu bytes, free space: %lld bytes in %s\n", + (u_long) sz, (long long) min_free(pen), pen); + } + if (min_free(pen) < sz) { + rmdir(pen); + cleanup(0); + errx(2, "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", pen); + } + if (Current[0]) + strlcpy(Previous, Current, sizeof(Previous)); + else if (!getcwd(Previous, MaxPathSize)) { + cleanup(0); + err(EXIT_FAILURE, "fatal error during execution: getcwd"); + } + if (chdir(pen) == FAIL) { + cleanup(0); + errx(2, "can't chdir to '%s'", pen); + } + CurrentSet = 0; strlcpy(Current, pen, sizeof(Current)); CurrentSet = 1; + + return Previous; +} + +/* + * Convenience routine for getting out of playpen + */ +void +leave_playpen(char *save) +{ + void (*oldsig) (int); + + /* Make us interruptable while we're cleaning up - just in case... */ + oldsig = signal(SIGINT, SIG_DFL); + if (Previous[0] && chdir(Previous) == FAIL) { + cleanup(0); + errx(2, "can't chdir back to '%s'", Previous); + } else if (CurrentSet && Current[0] && strcmp(Current, Previous)) { + if (strcmp(Current, "/") == 0) { + fprintf(stderr, "PANIC: About to rm -fr / (not doing so, aborting)\n"); + abort(); + } + if (fexec("rm", "-fr", Current, NULL)) + warnx("couldn't remove temporary dir '%s'", Current); + strlcpy(Current, Previous, sizeof(Current)); + } + if (save) + strlcpy(Previous, save, sizeof(Previous)); + else + Previous[0] = '\0'; + signal(SIGINT, oldsig); +} + +/* + * Return free disk space (in bytes) on given file system. + * Returns size in a uint64_t since off_t isn't 64 bits on all + * operating systems. + */ +uint64_t +min_free(const char *tmpdir) +{ + struct statvfs buf; + + if (statvfs(tmpdir, &buf) != 0) { + warn("statvfs"); + return 0; + } + return (uint64_t)buf.f_bavail * buf.f_bsize; +} diff --git a/pkgtools/pkg_install/files/lib/pexec.c b/pkgtools/pkg_install/files/lib/pexec.c new file mode 100644 index 00000000000..1358aedb910 --- /dev/null +++ b/pkgtools/pkg_install/files/lib/pexec.c @@ -0,0 +1,110 @@ +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <nbcompat.h> +#if HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#if HAVE_ERR_H +#include <err.h> +#endif +#if HAVE_ERRNO_H +#include <errno.h> +#endif +#if HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_STDIO_H +#include <stdio.h> +#endif + +#include "lib.h" + +/* + * If the supplied callback is not NULL, then call it. + */ +static void call_callback(void (*callback)(void)) +{ + if (callback != NULL) { + callback(); + } +} + +/* + * create pipe, fork and exec file with arguments in argv + * child takes stdin from pipe, set up fp for parent to + * output to pipe, and return this information. + */ +pipe_to_system_t *pipe_to_system_begin(const char *file, char *const argv[], + void (*cleanup_callback)(void)) +{ + pipe_to_system_t *retval; + + retval = malloc(sizeof(pipe_to_system_t)); + if (retval == NULL) { + call_callback(cleanup_callback); + errx(2, "can't get pipe space"); + } + + retval->cleanup = cleanup_callback; + + if (pipe(retval->fds) == -1) { + call_callback(retval->cleanup); + errx(2, "cannot create pipe"); + } + + retval->pid = fork(); + if (retval->pid == -1) { + call_callback(retval->cleanup); + errx(2, "cannot fork process for %s", file); + } + + if (retval->pid == 0) { /* The child */ + if (retval->fds[0] != 0) { + dup2(retval->fds[0], 0); + close(retval->fds[0]); + } + close(retval->fds[1]); + execvp(file, argv); + warn("failed to execute %s command", file); + _exit(2); + } + + /* Meanwhile, back in the parent process ... */ + close(retval->fds[0]); + retval->fp = fdopen(retval->fds[1], "w"); + if (retval->fp == NULL) { + call_callback(retval->cleanup); + errx(2, "fdopen failed"); + } + return retval; +} + +/* + * close pipe and wait for child to exit. on non-zero exit status, + * call cleanup callback. return exit status. + */ +int pipe_to_system_end(pipe_to_system_t *to_pipe) +{ + int status; + int wait_ret; + + fclose(to_pipe->fp); + do { + wait_ret = waitpid(to_pipe->pid, &status, 0); + } while (wait_ret == -1 && errno == EINTR); + + if (wait_ret < 0) { + call_callback(to_pipe->cleanup); + errx(2, "waitpid returned failure"); + } + if (!WIFEXITED(status)) { + call_callback(to_pipe->cleanup); + errx(2, "waitpid: process terminated abnormally"); + } + free(to_pipe); + return WEXITSTATUS(status); +} diff --git a/pkgtools/pkg_install/files/lib/pkg_io.c b/pkgtools/pkg_install/files/lib/pkg_io.c index 0a87bfc5a64..e8496eb2c2f 100644 --- a/pkgtools/pkg_install/files/lib/pkg_io.c +++ b/pkgtools/pkg_install/files/lib/pkg_io.c @@ -1,4 +1,4 @@ -/* $NetBSD: pkg_io.c,v 1.2 2008/04/26 14:56:34 joerg Exp $ */ +/* $NetBSD: pkg_io.c,v 1.3 2008/04/26 17:40:01 joerg Exp $ */ /*- * Copyright (c) 2008 Joerg Sonnenberger <joerg@NetBSD.org>. * All rights reserved. @@ -36,7 +36,7 @@ #include <sys/cdefs.h> #endif -__RCSID("$NetBSD: pkg_io.c,v 1.2 2008/04/26 14:56:34 joerg Exp $"); +__RCSID("$NetBSD: pkg_io.c,v 1.3 2008/04/26 17:40:01 joerg Exp $"); #include <archive.h> #include <archive_entry.h> @@ -52,7 +52,7 @@ __RCSID("$NetBSD: pkg_io.c,v 1.2 2008/04/26 14:56:34 joerg Exp $"); #include "lib.h" struct fetch_archive { - struct url *url; + const char *url; fetchIO *fetch; char buffer[32768]; }; @@ -62,7 +62,7 @@ fetch_archive_open(struct archive *a, void *client_data) { struct fetch_archive *f = client_data; - f->fetch = fetchGet(f->url, ""); + f->fetch = fetchGetURL(f->url, ""); if (f->fetch == NULL) return ENOENT; return 0; @@ -88,181 +88,52 @@ fetch_archive_close(struct archive *a, void *client_data) return 0; } -static struct archive * -open_archive_by_url(struct url *url, void **cookie) +struct archive * +open_remote_archive(const char *url, void **cookie) { struct fetch_archive *f; - struct archive *a; + struct archive *archive; f = malloc(sizeof(*f)); if (f == NULL) err(2, "cannot allocate memory for remote archive"); f->url = url; - a = archive_read_new(); - archive_read_support_compression_all(a); - archive_read_support_format_all(a); - if (archive_read_open(a, f, fetch_archive_open, fetch_archive_read, - fetch_archive_close)) { - archive_read_close(a); - free(f); - return NULL; - } + archive = archive_read_new(); + archive_read_support_compression_all(archive); + archive_read_support_format_all(archive); + if (archive_read_open(archive, f, fetch_archive_open, fetch_archive_read, + fetch_archive_close)) + errx(2, "cannot open archive: %s", archive_error_string(archive)); *cookie = f; - return a; -} - -struct archive * -open_archive(const char *url, void **cookie) -{ - struct url *u; - struct archive *a; - - if (!IS_URL(url)) { - a = archive_read_new(); - archive_read_support_compression_all(a); - archive_read_support_format_all(a); - if (archive_read_open_filename(a, url, 1024)) { - archive_read_close(a); - return NULL; - } - *cookie = NULL; - return a; - } - - if ((u = fetchParseURL(url)) == NULL) - return NULL; - a = open_archive_by_url(u, cookie); - - fetchFreeURL(u); - return a; + return archive; } void -close_archive(void *cookie) +close_remote_archive(void *cookie) { free(cookie); } -static int -strip_suffix(char *filename) -{ - size_t len; - - len = strlen(filename); - if (len <= 4) - return 0; - if (strcmp(filename + len - 4, ".tgz") == 0 || - strcmp(filename + len - 4, ".tbz") == 0) { - filename[len - 4] = '\0'; - return 1; - } else - return 0; -} - -static int -find_best_package(struct url *url, const char *pattern, struct url **best_url) +struct archive * +open_local_archive(const char *path, void **cookie) { - char *cur_match, *best_match = NULL; - struct url_list ue; - size_t i; - - if (*best_url) { - if ((best_match = fetchUnquoteFilename(*best_url)) == NULL) - return -1; - } else - best_match = NULL; + struct archive *archive; - if (best_match && strip_suffix(best_match) == 0) { - free(best_match); - return -1; - } + archive = archive_read_new(); + archive_read_support_compression_all(archive); + archive_read_support_format_all(archive); + if (archive_read_open_filename(archive, path, 1024)) + errx(2, "cannot open archive: %s", + archive_error_string(archive)); + *cookie = NULL; - fetchInitURLList(&ue); - if (fetchList(&ue, url, NULL, "")) { - fetchFreeURLList(&ue); - return -1; - } - for (i = 0; i < ue.length; ++i) { - cur_match = fetchUnquoteFilename(ue.urls + i); - - if (cur_match == NULL) { - free(best_match); - fetchFreeURLList(&ue); - return -1; - } - if (strip_suffix(cur_match) == 0) { - free(cur_match); - continue; - } - if (pkg_order(pattern, cur_match, best_match) == 1) { - if (*best_url) - fetchFreeURL(*best_url); - *best_url = fetchCopyURL(ue.urls + i); - free(best_match); - best_match = cur_match; - cur_match = NULL; - if (*best_url == NULL) { - free(best_match); - return -1; - } - } - free(cur_match); - } - free(best_match); - fetchFreeURLList(&ue); - return 0; + return archive; } -struct archive * -find_archive(const char *fname, void **cookie) +void +close_local_archive(void *cookie) { - struct archive *a; - struct path *path; - const char *cur_path; - struct url *url, *best_match; - char tmp[MaxPathSize]; - - best_match = NULL; - - a = open_archive(fname, cookie); - if (a != NULL) - return a; - - if (strchr(fname, '/') != NULL) { - const char *last_slash; - - last_slash = strrchr(fname, '/'); - snprintf(tmp, sizeof(tmp), "%s%.*s", - IS_URL(fname) ? "" : "file://", - (int)(last_slash - fname + 1), fname); - url = fetchParseURL(tmp); - if (url == NULL) - return NULL; - fname = last_slash + 1; /* XXX fetchUnquoteFilename */ - find_best_package(url, fname, &best_match); - fetchFreeURL(url); - } else { - TAILQ_FOREACH(path, &PkgPath, pl_entry) { - cur_path = path->pl_path; - if (!IS_URL(cur_path)) { - snprintf(tmp, sizeof(tmp), "file://%s", cur_path); - cur_path = tmp; - } - url = fetchParseURL(cur_path); - if (url == NULL) - continue; - find_best_package(url, fname, &best_match); - /* XXX Check return value and complain */ - fetchFreeURL(url); - } - } - - if (best_match == NULL) - return NULL; - a = open_archive_by_url(best_match, cookie); - fetchFreeURL(best_match); - return a; } diff --git a/pkgtools/pkg_install/files/lib/plist.c b/pkgtools/pkg_install/files/lib/plist.c index 8539272be75..0b24734012d 100644 --- a/pkgtools/pkg_install/files/lib/plist.c +++ b/pkgtools/pkg_install/files/lib/plist.c @@ -1,4 +1,4 @@ -/* $NetBSD: plist.c,v 1.18 2008/04/26 14:56:34 joerg Exp $ */ +/* $NetBSD: plist.c,v 1.19 2008/04/26 17:40:01 joerg Exp $ */ #if HAVE_CONFIG_H #include "config.h" @@ -11,7 +11,7 @@ #if 0 static const char *rcsid = "from FreeBSD Id: plist.c,v 1.24 1997/10/08 07:48:15 charnier Exp"; #else -__RCSID("$NetBSD: plist.c,v 1.18 2008/04/26 14:56:34 joerg Exp $"); +__RCSID("$NetBSD: plist.c,v 1.19 2008/04/26 17:40:01 joerg Exp $"); #endif #endif @@ -539,7 +539,6 @@ delete_package(Boolean ign_err, Boolean nukedirs, package_t *pkg, Boolean NoDele if (NoDeleteFiles) break; format_cmd(tmp, sizeof(tmp), p->name, Where, last_file); - /* XXX cleanup(0); */ printf("Executing `%s'\n", tmp); if (!Fake && system(tmp)) { warnx("unexec command for `%s' failed", tmp); diff --git a/pkgtools/pkg_install/files/lib/str.c b/pkgtools/pkg_install/files/lib/str.c index be5a156319a..ccd00059b07 100644 --- a/pkgtools/pkg_install/files/lib/str.c +++ b/pkgtools/pkg_install/files/lib/str.c @@ -1,4 +1,4 @@ -/* $NetBSD: str.c,v 1.23 2008/04/26 14:56:34 joerg Exp $ */ +/* $NetBSD: str.c,v 1.24 2008/04/26 17:40:01 joerg Exp $ */ #if HAVE_CONFIG_H #include "config.h" @@ -11,7 +11,7 @@ #if 0 static const char *rcsid = "Id: str.c,v 1.5 1997/10/08 07:48:21 charnier Exp"; #else -__RCSID("$NetBSD: str.c,v 1.23 2008/04/26 14:56:34 joerg Exp $"); +__RCSID("$NetBSD: str.c,v 1.24 2008/04/26 17:40:01 joerg Exp $"); #endif #endif @@ -106,3 +106,44 @@ ispkgpattern(const char *pkg) { return strpbrk(pkg, "<>[]?*{") != NULL; } + +/* + * Strip off any .tgz, .tbz or .t[bg]z suffix from fname, + * and copy into buffer "buf", the suffix is stored in "sfx" + * if "sfx" is not NULL. If no suffix is found, "sfx" is set + * to an empty string. + */ +void +strip_txz(char *buf, char *sfx, const char *fname) +{ + static const char *const suffixes[] = { + ".tgz", ".tbz", ".t[bg]z", 0}; + const char *const *suffixp; + size_t len; + + len = strlen(fname); + assert(len < PKG_PATTERN_MAX); + + if (sfx) + sfx[0] = '\0'; + + for (suffixp = suffixes; *suffixp; suffixp++) { + size_t suffixlen = strlen(*suffixp); + + if (memcmp(&fname[len - suffixlen], *suffixp, suffixlen)) + continue; + + /* matched! */ + memcpy(buf, fname, len - suffixlen); + buf[len - suffixlen] = 0; + if (sfx) { + if (suffixlen >= PKG_SUFFIX_MAX) + errx(EXIT_FAILURE, "too long suffix '%s'", fname); + memcpy(sfx, *suffixp, suffixlen+1); + return; + } + } + + /* not found */ + memcpy(buf, fname, len+1); +} |