/* $Id: actions.c,v 1.93 2009/05/07 23:14:42 imil Exp $ */ /* * Copyright (c) 2009 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Emile "iMil" Heitor . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "pkg_dry.h" #include #ifndef HN_AUTOSCALE #include "humanize_number.h" #endif #define LOCALBASE "/usr/pkg" /* see DISCLAIMER below */ const char *pkg_dry_cache = PKG_DRY_CACHE; static int upgrade_type = UPGRADE_NONE; int check_yesno(void) { int c, r = 'n'; if (yesflag) return 1; printf("proceed ? [y/N] "); while((c = getchar()) != '\n' && c != EOF) /* avoid residual char */ if (c == 'y') r = 'y'; if (r == 'y') return 1; else return 0; } /* package removal */ static void do_pkg_remove(Deptreehead *removehead) { Pkgdeptree *premove; SLIST_FOREACH(premove, removehead, next) { if (premove->depname == NULL) /* SLIST corruption, badly installed package */ continue; printf("removing %s...\n", premove->depname); #ifdef DEBUG printf("%s -f %s\n", PKG_DELETE, premove->depname); #else fexec(PKG_DELETE, "-f", premove->depname, NULL); #endif } } static void pkg_download(Deptreehead *installhead) { FILE *fp; Pkgdeptree *pinstall; struct stat sb; Dlfile *dlpkg; char *pkgurl, query[BUFSIZ], pkgpath[BUFSIZ]; printf("downloading packages...\n"); SLIST_FOREACH(pinstall, installhead, next) { snprintf(pkgpath, BUFSIZ, "%s/%s%s", pkg_dry_cache, pinstall->depname, PKG_EXT); if (stat(pkgpath, &sb) < 0) { /* package not downloaded */ XMALLOC(pkgurl, BUFSIZ * sizeof(char)); snprintf(query, BUFSIZ, PKG_URL, pinstall->depname); /* retreive repository for package */ if (drydb_doquery(query, ddb_get_value, pkgurl) != 0) errx(EXIT_FAILURE, "%s has no associated repository", pinstall->depname); snprintf(pkgurl, BUFSIZ, "%s/%s%s", pkgurl, pinstall->depname, PKG_EXT); dlpkg = download_file(pkgurl); XFREE(pkgurl); umask(DEF_UMASK); if ((fp = fopen(pkgpath, "w")) == NULL) err(EXIT_FAILURE, "error opening %s", pkgpath); fwrite(dlpkg->buf, dlpkg->size, 1, fp); fclose(fp); } } /* download loop */ } /* package installation. Don't rely on pkg_add's ability to fetch and * install as we want to keep control on packages installation order. * Besides, pkg_add cannot be used to install an "older" package remotely * i.e. apache 1.3 */ static void do_pkg_install(Deptreehead *installhead) { Pkgdeptree *pinstall; char pkgpath[BUFSIZ]; printf("installing packages...\n"); SLIST_FOREACH(pinstall, installhead, next) { printf("installing %s...\n", pinstall->depname); snprintf(pkgpath, BUFSIZ, "%s/%s%s", pkg_dry_cache, pinstall->depname, PKG_EXT); #ifdef DEBUG printf("%s -f %s\n", PKG_ADD, pkgpath); #else fexec(PKG_ADD, "-f", pkgpath, NULL); #endif } /* installation loop */ } /* build the output line */ char * action_list(char *flatlist, char *str) { int newsize; char *newlist = NULL; if (flatlist == NULL) XSTRDUP(newlist, str); else { if (str == NULL) return flatlist; newsize = strlen(str) + strlen(flatlist) + 2; XREALLOC(newlist, newsize * sizeof(char)); snprintf(newlist, newsize, "%s %s", flatlist, str); XFREE(flatlist); } return newlist; } /* find required files (REQUIRES) from PROVIDES or filename */ static int pkg_met_reqs(Impacthead *impacthead) { int met_reqs = 1, foundreq; Pkgimpact *pimpact, *impactprov; Plisthead *requireshead, *provideshead; Pkglist *requires, *provides; struct stat sb; char query[BUFSIZ]; /* first, parse impact list */ SLIST_FOREACH(pimpact, impacthead, next) { /* retreive requires list for package */ snprintf(query, BUFSIZ, GET_REQUIRES_QUERY, pimpact->pkgname); requireshead = rec_pkglist(query); if (requireshead == NULL) /* empty requires list (very unlikely) */ continue; /* parse requires list */ SLIST_FOREACH(requires, requireshead, next) { foundreq = 0; /* for performance sake, first check basesys */ if ((strstr(requires->pkgname, LOCALBASE)) == NULL) { if (stat(requires->pkgname, &sb) < 0) { printf("%s, needed by %s is not present in this system.\n", requires->pkgname, pimpact->pkgname); met_reqs = 0; } /* was a basysfile, no need to check PROVIDES */ continue; } /* FIXME: the code below actually works, but there's no * point losing performances when some REQUIRES do not match * PROVIDES in pkg_summary(5). This is a known issue and will * hopefuly be fixed. */ continue; /* search what local packages provide */ provideshead = rec_pkglist(LOCAL_PROVIDES); SLIST_FOREACH(provides, provideshead, next) { if (strncmp(provides->pkgname, requires->pkgname, strlen(requires->pkgname)) == 0) { foundreq = 1; /* found, no need to go further*/ break; } /* match */ } /* SLIST_FOREACH LOCAL_PROVIDES */ free_pkglist(provideshead); XFREE(provideshead); /* REQUIRES was not found on local packages, try impact list */ if (!foundreq) { /* re-parse impact list to retreive PROVIDES */ SLIST_FOREACH(impactprov, impacthead, next) { snprintf(query, BUFSIZ, GET_PROVIDES_QUERY, impactprov->pkgname); provideshead = rec_pkglist(query); if (provideshead == NULL) continue; /* then parse provides list for every package */ SLIST_FOREACH(provides, provideshead, next) { if (strncmp(provides->pkgname, requires->pkgname, strlen(requires->pkgname)) == 0) { foundreq = 1; /* found, no need to go further return to impactprov list */ break; } /* match */ } free_pkglist(provideshead); XFREE(provideshead); if (foundreq) /* exit impactprov list loop */ break; } /* SLIST_NEXT impactprov */ } /* if (!foundreq) LOCAL_PROVIDES -> impact list */ /* FIXME: BIG FAT DISCLAIMER * as of 04/2009, some packages described in pkg_summary * have unmet REQUIRES. This is a known bug that makes the * PROVIDES untrustable and some packages uninstallable. * foundreq is forced to 1 for now for every REQUIRES * matching LOCALBASE, which is hardcoded to "/usr/pkg" */ if (!foundreq) { printf("warning: %s is not present in this system (may be installed by this package)\n", requires->pkgname); foundreq = 1; } } /* SLIST_FOREACH requires */ free_pkglist(requireshead); XFREE(requireshead); } /* 1st impact SLIST_FOREACH */ return met_reqs; } /* check for conflicts and if needed files are present */ static int pkg_has_conflicts(Plisthead *conflictshead, Pkgimpact *pimpact) { int has_conflicts = 0; Pkglist *conflicts; /* SLIST conflicts pointer */ char *conflict_pkg, query[BUFSIZ]; if (conflictshead == NULL) return 0; /* check conflicts */ SLIST_FOREACH(conflicts, conflictshead, next) { if (pkg_match(conflicts->pkgname, pimpact->pkgname)) { /* got a conflict, retrieve conflicting local package */ snprintf(query, BUFSIZ, GET_CONFLICT_QUERY, conflicts->pkgname); XMALLOC(conflict_pkg, BUFSIZ * sizeof(char)); if (drydb_doquery(query, ddb_get_value, conflict_pkg) == 0) printf("%s (to be installed) conflicts with installed package %s.\n", pimpact->pkgname, conflict_pkg); XFREE(conflict_pkg); has_conflicts = 1; } /* match conflict */ } /* SLIST_FOREACH conflicts */ return has_conflicts; } #define H_BUF 6 int pkg_dry_install(char **pkgargs) { int installnum = 0, upgradenum = 0, rc = EXIT_FAILURE; uint64_t file_size = 0, size_pkg = 0; Impacthead *impacthead; /* impact head */ Pkgimpact *pimpact; Deptreehead *removehead = NULL, *installhead = NULL; Pkgdeptree *premove, *pinstall; /* not a Pkgdeptree, just for ease */ Plisthead *conflictshead; /* conflicts head */ char *toinstall = NULL, *toupgrade = NULL; char h_psize[H_BUF], h_fsize[H_BUF]; /* full impact list */ if ((impacthead = pkg_impact(pkgargs)) == NULL) { printf("nothing to do.\n"); return rc; } /* check for required files */ if (!pkg_met_reqs(impacthead)) { printf("required files are missing in this system.\n"); return rc; } /* conflicts list */ conflictshead = rec_pkglist(LOCAL_CONFLICTS); /* browse impact tree */ SLIST_FOREACH(pimpact, impacthead, next) { /* check for conflicts */ if (pkg_has_conflicts(conflictshead, pimpact)) { if (!check_yesno()) { free_impact(impacthead); XFREE(impacthead); return rc; } } file_size += pimpact->file_size; size_pkg += pimpact->size_pkg; switch (pimpact->action) { case TOUPGRADE: upgradenum++; installnum++; break; case TOINSTALL: installnum++; break; } } /* free conflicts list */ free_pkglist(conflictshead); XFREE(conflictshead); (void)humanize_number(h_fsize, H_BUF, (int64_t)file_size, "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL); (void)humanize_number(h_psize, H_BUF, (int64_t)size_pkg, "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL); /* check disk space */ if (!fs_has_room(pkg_dry_cache, (int64_t)file_size)) errx(EXIT_FAILURE, "%s has not enough space for download\n", pkg_dry_cache); if (!fs_has_room(LOCALBASE, (int64_t)size_pkg)) errx(EXIT_FAILURE, "%s has not enough space for installation\n", LOCALBASE); if (upgradenum > 0) { /* record ordered remove list before upgrade */ removehead = order_upgrade_remove(impacthead); SLIST_FOREACH(premove, removehead, next) toupgrade = action_list(toupgrade, premove->depname); printf("%d packages to be upgraded: %s\n", upgradenum, toupgrade); } else printf("nothing to upgrade.\n"); if (installnum > 0) { /* record ordered install list */ installhead = order_install(impacthead); SLIST_FOREACH(pinstall, installhead, next) toinstall = action_list(toinstall, pinstall->depname); printf("%d packages to be installed: %s (%s to download, %s to install)\n", installnum, toinstall, h_fsize, h_psize); if (check_yesno()) { /* before erasing anything, download packages */ pkg_download(installhead); /* if there was upgrades, first remove old packages */ if (upgradenum > 0) { printf("removing packages to be upgraded...\n"); do_pkg_remove(removehead); } /* then pass ordered install list */ do_pkg_install(installhead); /* pure install, not called by pkg_dry_upgrade */ if (upgrade_type == UPGRADE_NONE) update_db(LOCAL_SUMMARY, pkgargs); rc = EXIT_SUCCESS; } } else printf("nothing to install.\n"); XFREE(toinstall); XFREE(toupgrade); free_impact(impacthead); XFREE(impacthead); free_deptree(removehead); XFREE(removehead); free_deptree(installhead); XFREE(installhead); return rc; } void pkg_dry_remove(char **pkgargs) { int deletenum = 0, pkglen, exists; Deptreehead pdphead, *removehead; Pkgdeptree *pdp; Plisthead *plisthead; Pkglist *pkglist; char *todelete = NULL, **ppkgargs; SLIST_INIT(&pdphead); plisthead = rec_pkglist(LOCAL_PKGS_QUERY); if (plisthead == NULL) errx(EXIT_FAILURE, "empty local package list."); /* act on every package passed to the command line */ for (ppkgargs = pkgargs; *ppkgargs != NULL; ppkgargs++) { exists = 0; if (exact_pkgfmt(*ppkgargs)) trunc_str(*ppkgargs, '-', STR_BACKWARD); pkglen = strlen(*ppkgargs); /* check package existence */ SLIST_FOREACH(pkglist, plisthead, next) if (strncmp(pkglist->pkgname, *ppkgargs, pkglen) == 0) { exists = 1; break; } if (!exists) { printf("no such installed package %s\n", *ppkgargs); return; } /* record full reverse dependency list for package */ full_dep_tree(*ppkgargs, LOCAL_REVERSE_DEPS, &pdphead, __func__, 0); exists = 0; /* check if package have already been recorded */ SLIST_FOREACH(pdp, &pdphead, next) { if (strncmp(pdp->depname, pkglist->pkgname, strlen(pdp->depname)) == 0) { exists = 1; break; } } if (exists) continue; /* next pkgarg */ /* add package itself */ XMALLOC(pdp, sizeof(Pkgdeptree)); XSTRDUP(pdp->depname, pkglist->pkgname); if (SLIST_EMPTY(&pdphead)) /* identify unique package, don't cut it when ordering */ pdp->level = -1; else pdp->level = 0; XSTRDUP(pdp->matchname, pdp->depname); trunc_str(pdp->matchname, '-', STR_BACKWARD); SLIST_INSERT_HEAD(&pdphead, pdp, next); } /* for pkgargs */ free_pkglist(plisthead); XFREE(plisthead); /* order remove list */ removehead = order_remove(&pdphead); SLIST_FOREACH(pdp, removehead, next) { deletenum++; todelete = action_list(todelete, pdp->depname); } if (todelete != NULL) { printf("%d packages to delete: %s\n", deletenum, todelete); if (check_yesno()) { do_pkg_remove(removehead); update_db(LOCAL_SUMMARY, NULL); } } else printf("no packages to delete\n"); free_deptree(removehead); XFREE(removehead); XFREE(todelete); } /* find closest match for packages to be upgraded */ static char * narrow_match(Plisthead *remoteplisthead, const char *pkgname, const char *fullpkgname) { Pkglist *pkglist; char *rpkgname, *best_match = NULL; int pkglen, fullpkglen, i, matchlen = 0; pkglen = strlen(pkgname); fullpkglen = strlen(fullpkgname); SLIST_FOREACH(pkglist, remoteplisthead, next) { XSTRDUP(rpkgname, pkglist->pkgname); trunc_str(rpkgname, '-', STR_BACKWARD); if (strlen(rpkgname) == pkglen && strncmp(pkgname, rpkgname, pkglen) == 0) { for (i = 0; i < fullpkglen && fullpkgname[i] == pkglist->pkgname[i]; i++); if (i > matchlen) { matchlen = i; XSTRDUP(best_match, pkglist->pkgname); } } XFREE(rpkgname); } /* SLIST_FOREACH remoteplisthead */ return best_match; } static char ** record_upgrades(Plisthead *plisthead, Plisthead *remoteplisthead) { Pkglist *pkglist; int count = 0; char **pkgargs; SLIST_FOREACH(pkglist, plisthead, next) count++; XMALLOC(pkgargs, (count + 2) * sizeof(char *)); pkgargs[count--] = NULL; SLIST_FOREACH(pkglist, plisthead, next) { XSTRDUP(pkgargs[count], pkglist->pkgname); trunc_str(pkgargs[count], '-', STR_BACKWARD); pkgargs[count] = narrow_match(remoteplisthead, pkgargs[count], pkglist->pkgname); count--; } return pkgargs; } void pkg_dry_upgrade(int uptype) { Plisthead *keeplisthead, *localplisthead, *remoteplisthead; char **pkgargs; /* used for pkg_dry_install not to update database, this is done below */ upgrade_type = uptype; /* record keepable packages */ if ((keeplisthead = rec_pkglist(KEEP_LOCAL_PKGS)) == NULL) errx(EXIT_FAILURE, "empty non-autoremovable package list"); if (uptype == UPGRADE_ALL) { if ((localplisthead = rec_pkglist(LOCAL_PKGS_QUERY)) == NULL) errx(EXIT_FAILURE, "empty non-autoremovable package list"); } else localplisthead = keeplisthead; if ((remoteplisthead = rec_pkglist(REMOTE_PKGS_QUERY)) == NULL) errx(EXIT_FAILURE, "empty available packages list"); pkgargs = record_upgrades(localplisthead, remoteplisthead); if (pkg_dry_install(pkgargs) == EXIT_SUCCESS) { if (uptype == UPGRADE_ALL) { free_pkglist(localplisthead); XFREE(localplisthead); free_list(pkgargs); /* record keep list */ pkgargs = record_upgrades(keeplisthead, remoteplisthead); } update_db(LOCAL_SUMMARY, pkgargs); } free_list(pkgargs); free_pkglist(remoteplisthead); XFREE(remoteplisthead); free_pkglist(keeplisthead); XFREE(keeplisthead); }