/*- * Copyright 2010. Ivan Voras * * 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. */ /* * FreeBSD install - a package for the installation and maintainance * of non-core utilities. * * This is a module allowing mass update of packages over HTTP. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include "pkg_patch.h" #include "updateweb.h" #include "applypatch.h" #include "hashjob.h" TAILQ_HEAD(patchrec_list, patchrec); struct patchrec { char source[PKGNAME_MAX]; char target[PKGNAME_MAX]; char patch_name[PATH_MAX]; time_t patch_timestamp; Package *plist; Boolean match; Boolean found_deps; Boolean shuffled; TAILQ_ENTRY(patchrec) linkage; }; struct patch_index { int ver_major; int ver_minor; char *pkg_repo_url; struct patchrec_list prlist; }; static int read_pkgpatchindex_file(struct patch_index *pindex, char *fname) { char line[4*PKGNAME_MAX]; char *cmd, *param; FILE *f; char *p; struct patchrec_list *prlist; prlist = &pindex->prlist; f = fopen(fname, "r"); if (f == NULL) return (-1); while (fgets(line, sizeof(line), f) != NULL) { p = strchr(line, '#'); if (p != NULL) *p = '\0'; while (line[strlen(line)-1] == '\n' || line[strlen(line)-1] == ' ') line[strlen(line)-1] = '\0'; if (line[0] == '\0') continue; if (line[0] != '@') { warn("Line is not a command: %s", line); break; } cmd = line + 1; p = strchr(cmd, ' '); *p = '\0'; param = p + 1; if (strcmp(cmd, "version") == 0) { int vmajor, vminor; if (sscanf(param, "%d.%d", &vmajor, &vminor) != 2) { warnx("Cannot parse version: %s", param); break; } pindex->ver_major = vmajor; pindex->ver_minor = vminor; } else if (strcmp(cmd, "pkgrepo") == 0) { pindex->pkg_repo_url = strdup(param); } else if (strcmp(cmd, "havepatch") == 0) { char src[PKGNAME_MAX], tgt[PKGNAME_MAX], patch[PATH_MAX], tstamp[PKGNAME_MAX]; struct patchrec *pr; if (sscanf(param, "%s %s %s %s", src, tgt, patch, tstamp) != 4) { warnx("Cannot parse havepatch line: %s", line); break; } pr = calloc(1, sizeof(*pr)); strncpy(pr->source, src, PKGNAME_MAX); strncpy(pr->target, tgt, PKGNAME_MAX); strncpy(pr->patch_name, patch, PATH_MAX); pr->patch_timestamp = iso8601_to_time(tstamp); TAILQ_INSERT_TAIL(prlist, pr, linkage); } } fclose(f); return (0); } static void extract_filename(char *path, char *fname, int len) { strncpy(fname, strrchr(path, '/')+1, len); } static int download_file(char *url, char *fname) { FILE *fin, *fout; size_t bs = 128*1024; char *buf; int er; fout = fopen(fname, "w"); if (fout == NULL) return (-1); fin = fetchGetURL(url, ""); if (fin == NULL) return (-1); buf = malloc(bs); while (bs > 0) { if (Verbose) baton_twirl(); bs = fread(buf, 1, bs, fin); if (bs > 0) fwrite(buf, 1, bs, fout); } free(buf); er = ferror(fin); fclose(fin); fclose(fout); if (er) { warn("Error reading %s", url); return (-1); } return (0); } static int pkg_add(struct patch_index *pindex, char *pkg_name) { char url[PATH_MAX]; int exit_code; snprintf(url, sizeof(url), "%s/%s.%s", pindex->pkg_repo_url, pkg_name, PKG_FORMAT_EXT); if (Verbose > 1) printf("Trying to install %s.\n", url); exit_code = vsystem("%s %s", _PATH_PKG_ADD, url); if (exit_code != 0) printf("Exit code %d running %s %s\n", exit_code, _PATH_PKG_ADD, url); return (exit_code); } void perform_updateweb(const char *in_url) { char url_base[PATH_MAX], url_index[PATH_MAX], index_fname[PATH_MAX]; char local_index[PATH_MAX]; struct patch_index pindex; struct patchrec *pr; char **instpkg; int er, i, pcount = 0, scount; memset(&pindex, 0, sizeof(pindex)); if (in_url == NULL) in_url = PKGPATCH_SITE_URL; if (strendswith(in_url, PKGPATCHINDEX_FNAME) == 0) { /* We have been given an URL ending with PKGPATCHINDEX_FNAME. * Technically this is an error, but we'll just strip it and * continue as we would with only the base url. */ strncpy(url_base, in_url, strlen(in_url) - strlen(PKGPATCHINDEX_FNAME)); strncpy(url_index, in_url, PATH_MAX); } else { strncpy(url_base, in_url, PATH_MAX); if (url_base[strlen(url_base)-1] == '/') url_base[strlen(url_base)-1] = '\0'; snprintf(url_index, PATH_MAX, "%s/%s", url_base, PKGPATCHINDEX_FNAME); } if (Verbose > 1) printf("Patching from %s (index file: %s)\n", url_base, url_index); extract_filename(url_index, index_fname, PATH_MAX); snprintf(local_index, PATH_MAX, "%s/%s", my_tmp, index_fname); if (Verbose > 2) printf("Downloading pkgpatchindex to %s\n", local_index); if (download_file(url_index, local_index) != 0) err(1, "Cannot download %s to %s", url_index, local_index); if (strendswith(local_index, ".gz") == 0) { vsystem("%s -d %s", _PATH_GZIP, local_index); local_index[strlen(local_index)-3] = '\0'; if (Verbose > 2) printf("Decompressed index to: %s\n", local_index); } if (strendswith(local_index, ".bz2") == 0) { vsystem("%s -d %s", _PATH_BZIP2, local_index); local_index[strlen(local_index)-4] = '\0'; if (Verbose > 2) printf("Decompressed index to: %s\n", local_index); } TAILQ_INIT(&pindex.prlist); if (read_pkgpatchindex_file(&pindex, local_index) != 0) err(1, "Cannot read pkgpatchindex: %s", local_index); if (pindex.ver_major == 0) errx(1, "Missing version number. Patch index %s corrupted?", url_index); if (pindex.ver_major != PKGPATCH_VERSION_MAJOR) errx(1, "Major version mismatch: got %d expected %d", pindex.ver_major, PKGPATCH_VERSION_MAJOR); if (pindex.ver_minor > PKGPATCH_VERSION_MINOR) errx(1, "Minor version cannot be handled: %d", pindex.ver_minor); if (pindex.pkg_repo_url == NULL) warnx("Target package repo URL is not present in patch repo " "metadata."); if (Verbose > 2) TAILQ_FOREACH(pr, &pindex.prlist, linkage) printf("Available: %s to %s via %s\n", pr->source, pr->target, pr->patch_name); instpkg = matchinstalled(MATCH_ALL, NULL, &er); if (instpkg == NULL || er != 0) err(1, "Error getting a list of installed packages"); for (i = 0; instpkg[i] != NULL; i++) { if (Verbose > 2) printf("Installed: %s\n", instpkg[i]); TAILQ_FOREACH(pr, &pindex.prlist, linkage) if (strncmp(instpkg[i], pr->source, PKGNAME_MAX) == 0) { pr->match = TRUE; pcount++; break; } } if (pcount == 0) { if (Verbose) printf("No package update candidates.\n"); return; } /* Show this information even if we're in non-verbose mode, it's * important! */ printf("Patch candidates:\n"); TAILQ_FOREACH(pr, &pindex.prlist, linkage) { if (pr->match) printf("%s\t", pr->source); } printf("\n"); if (!Force) { if (!y_or_n(FALSE, "Patch %d packages", pcount)) return; } /* Ok now, fetch the patches */ if (Verbose) printf("Downloading: "); TAILQ_FOREACH(pr, &pindex.prlist, linkage) { char local_file[PATH_MAX], remote_file[PATH_MAX]; if (pr->match) { if (Verbose) { printf("%s\t", pr->patch_name); fflush(stdout); } snprintf(local_file, PATH_MAX, "%s/%s", my_tmp, pr->patch_name); snprintf(remote_file, PATH_MAX, "%s/%s", url_base, pr->patch_name); if (Verbose > 3) printf("%s to %s", remote_file, local_file); if (download_file(remote_file, local_file) != 0) err(1, "Cannot download %s", remote_file); } } if (Verbose) printf(".\n"); /* Read the package patches' plists */ TAILQ_FOREACH(pr, &pindex.prlist, linkage) { char local_file[PATH_MAX]; if (!pr->match) continue; snprintf(local_file, PATH_MAX, "%s/%s", my_tmp, pr->patch_name); pr->plist = pkg_read_plist(local_file); if (pr->plist == NULL) err(1, "Cannot read %s file", CONTENTS_FNAME); er = check_conflicts(pr->plist, instpkg); if (er != 0) { if (er < 0) err(1, "Error processing package conflicts"); else if (er > 0) errx(1, "Package %s conflicts with %s", pr->source, instpkg[er-1]); } } if (Verbose > 3) printf("Pondering dependancies...\n"); scount = 1; while (scount != 0) { int ndeps = 0, ndeps_found = 0; PackingList pl; scount = 0; /* Sort the package patches by dependancies */ TAILQ_FOREACH(pr, &pindex.prlist, linkage) { struct patchrec *pr2 = NULL; int foundit; if (!pr->match) continue; pl = pr->plist->head; while (pl != NULL) { if (pl->type != PLIST_PKGDEP) { pl = pl->next; continue; } ndeps++; foundit = FALSE; /* Search deps in installed live packages */ for (i = 0; instpkg[i] != NULL; i++) if (strncmp(pl->name, instpkg[i], PKGNAME_MAX) == 0) { ndeps_found++; foundit = TRUE; break; } if (foundit) { if (Verbose > 2) printf("[live] Found %s -> %s\n", pr->target, instpkg[i]); pl = pl->next; continue; } TAILQ_FOREACH(pr2, &pindex.prlist, linkage) { if (strncmp(pl->name, pr2->target, PKGNAME_MAX) == 0) { ndeps_found++; foundit = TRUE; if (Verbose > 2) printf("[patch] " "Found %s -> %s\n", pr->target, pr2->target); break; } } if (!foundit) { printf("Dependancy cannot be satisfied " "%s -> %s - trying to install\n", pr->target, pl->name); if (pkg_add(&pindex, pl->name) != 0) err(1, "Cannot install %s\n", pl->name); /* * XXX: memory leak. libpkg doesn't have * an API for freeing matchinstalled() * data? */ instpkg = matchinstalled(MATCH_ALL, NULL, &er); if (instpkg == NULL || er != 0) err(1, "Error getting a list of " "installed packages"); foundit = TRUE; ndeps_found++; pl = pl->next; continue; } assert(pr2 != NULL); if (!pr2->shuffled) { if (Verbose > 2) printf("Shuffling %s to head " "of queue\n", pr2->target); TAILQ_REMOVE(&pindex.prlist, pr2, linkage); TAILQ_INSERT_HEAD(&pindex.prlist, pr2, linkage); pr2->shuffled = TRUE; scount++; } pl = pl->next; } } if (ndeps != ndeps_found) errx(1, "Cannot satisfy %d dependancies", ndeps - ndeps_found); } if (Verbose > 1) { printf("Applying patches in the following order:\t"); TAILQ_FOREACH(pr, &pindex.prlist, linkage) { if (!pr->match) continue; printf("%s\t", pr->patch_name); } printf("\n"); } /* Ok, apply the gathered patches now */ TAILQ_FOREACH(pr, &pindex.prlist, linkage) { char local_file[PATH_MAX]; if (!pr->match) continue; snprintf(local_file, PATH_MAX, "%s/%s", my_tmp, pr->patch_name); if (Verbose > 2) printf("Applying patch: %s\n", local_file); perform_applypatch(local_file); } }