12118f387SNathan Whitehorn /*- 21de7b4b8SPedro F. Giffuni * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 31de7b4b8SPedro F. Giffuni * 42118f387SNathan Whitehorn * Copyright (c) 2011 Nathan Whitehorn 582ac9f2bSDevin Teske * Copyright (c) 2014 Devin Teske <dteske@FreeBSD.org> 62118f387SNathan Whitehorn * All rights reserved. 72118f387SNathan Whitehorn * 82118f387SNathan Whitehorn * Redistribution and use in source and binary forms, with or without 92118f387SNathan Whitehorn * modification, are permitted provided that the following conditions 102118f387SNathan Whitehorn * are met: 112118f387SNathan Whitehorn * 1. Redistributions of source code must retain the above copyright 122118f387SNathan Whitehorn * notice, this list of conditions and the following disclaimer. 132118f387SNathan Whitehorn * 2. Redistributions in binary form must reproduce the above copyright 142118f387SNathan Whitehorn * notice, this list of conditions and the following disclaimer in the 152118f387SNathan Whitehorn * documentation and/or other materials provided with the distribution. 162118f387SNathan Whitehorn * 172118f387SNathan Whitehorn * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 182118f387SNathan Whitehorn * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 192118f387SNathan Whitehorn * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 202118f387SNathan Whitehorn * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 212118f387SNathan Whitehorn * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 222118f387SNathan Whitehorn * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 232118f387SNathan Whitehorn * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 242118f387SNathan Whitehorn * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 252118f387SNathan Whitehorn * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 262118f387SNathan Whitehorn * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 272118f387SNathan Whitehorn * SUCH DAMAGE. 282118f387SNathan Whitehorn */ 292118f387SNathan Whitehorn 3082ac9f2bSDevin Teske #include <sys/cdefs.h> 3182ac9f2bSDevin Teske __FBSDID("$FreeBSD$"); 3282ac9f2bSDevin Teske 3346cc2c02SNathan Whitehorn #include <sys/param.h> 3482ac9f2bSDevin Teske #include <archive.h> 3582ac9f2bSDevin Teske #include <ctype.h> 3682ac9f2bSDevin Teske #include <dialog.h> 374ff1fc3bSDevin Teske #include <dpv.h> 3882ac9f2bSDevin Teske #include <err.h> 392118f387SNathan Whitehorn #include <errno.h> 402118f387SNathan Whitehorn #include <limits.h> 4182ac9f2bSDevin Teske #include <stdio.h> 4282ac9f2bSDevin Teske #include <stdlib.h> 4382ac9f2bSDevin Teske #include <string.h> 4482ac9f2bSDevin Teske #include <unistd.h> 452118f387SNathan Whitehorn 46bd1df636SDevin Teske /* Data to process */ 47bd1df636SDevin Teske static char *distdir = NULL; 484ff1fc3bSDevin Teske static struct archive *archive = NULL; 494ff1fc3bSDevin Teske static struct dpv_file_node *dists = NULL; 50bd1df636SDevin Teske 51bd1df636SDevin Teske /* Function prototypes */ 524ff1fc3bSDevin Teske static void sig_int(int sig); 5382ac9f2bSDevin Teske static int count_files(const char *file); 544ff1fc3bSDevin Teske static int extract_files(struct dpv_file_node *file, int out); 55bd1df636SDevin Teske 56bd1df636SDevin Teske #if __FreeBSD_version <= 1000008 /* r232154: bump for libarchive update */ 57bd1df636SDevin Teske #define archive_read_support_filter_all(x) \ 58bd1df636SDevin Teske archive_read_support_compression_all(x) 59bd1df636SDevin Teske #endif 60bd1df636SDevin Teske 61bd1df636SDevin Teske #define _errx(...) (end_dialog(), errx(__VA_ARGS__)) 622118f387SNathan Whitehorn 632118f387SNathan Whitehorn int 642118f387SNathan Whitehorn main(void) 652118f387SNathan Whitehorn { 66bd1df636SDevin Teske char *chrootdir; 67bd1df636SDevin Teske char *distributions; 6882ac9f2bSDevin Teske int retval; 694ff1fc3bSDevin Teske size_t config_size = sizeof(struct dpv_config); 704ff1fc3bSDevin Teske size_t file_node_size = sizeof(struct dpv_file_node); 71bd1df636SDevin Teske size_t span; 724ff1fc3bSDevin Teske struct dpv_config *config; 734ff1fc3bSDevin Teske struct dpv_file_node *dist = dists; 744ff1fc3bSDevin Teske static char backtitle[] = "FreeBSD Installer"; 754ff1fc3bSDevin Teske static char title[] = "Archive Extraction"; 764ff1fc3bSDevin Teske static char aprompt[] = "\n Overall Progress:"; 774ff1fc3bSDevin Teske static char pprompt[] = "Extracting distribution files...\n"; 784ff1fc3bSDevin Teske struct sigaction act; 7982ac9f2bSDevin Teske char error[PATH_MAX + 512]; 80bfd258f7SNathan Whitehorn 81bd1df636SDevin Teske if ((distributions = getenv("DISTRIBUTIONS")) == NULL) 8282ac9f2bSDevin Teske errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set"); 83bd1df636SDevin Teske if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL) 84bd1df636SDevin Teske distdir = __DECONST(char *, ""); 85bfd258f7SNathan Whitehorn 86bd1df636SDevin Teske /* Initialize dialog(3) */ 8757bda1b6SNathan Whitehorn init_dialog(stdin, stdout); 884ff1fc3bSDevin Teske dialog_vars.backtitle = backtitle; 8957bda1b6SNathan Whitehorn dlg_put_backtitle(); 9057bda1b6SNathan Whitehorn 91bd1df636SDevin Teske dialog_msgbox("", 92bd1df636SDevin Teske "Checking distribution archives.\nPlease wait...", 4, 35, FALSE); 93bd1df636SDevin Teske 94bd1df636SDevin Teske /* 954ff1fc3bSDevin Teske * Parse $DISTRIBUTIONS into dpv(3) linked-list 96bd1df636SDevin Teske */ 97bd1df636SDevin Teske while (*distributions != '\0') { 98bd1df636SDevin Teske span = strcspn(distributions, "\t\n\v\f\r "); 99bd1df636SDevin Teske if (span < 1) { /* currently on whitespace */ 100bd1df636SDevin Teske distributions++; 101bd1df636SDevin Teske continue; 102bd1df636SDevin Teske } 103bd1df636SDevin Teske 104bd1df636SDevin Teske /* Allocate a new struct for the distribution */ 105bd1df636SDevin Teske if (dist == NULL) { 106bd1df636SDevin Teske if ((dist = calloc(1, file_node_size)) == NULL) 107bd1df636SDevin Teske _errx(EXIT_FAILURE, "Out of memory!"); 108bd1df636SDevin Teske dists = dist; 109bd1df636SDevin Teske } else { 110bd1df636SDevin Teske dist->next = calloc(1, file_node_size); 111bd1df636SDevin Teske if (dist->next == NULL) 112bd1df636SDevin Teske _errx(EXIT_FAILURE, "Out of memory!"); 113bd1df636SDevin Teske dist = dist->next; 114bd1df636SDevin Teske } 115bd1df636SDevin Teske 116bd1df636SDevin Teske /* Set path */ 117bd1df636SDevin Teske if ((dist->path = malloc(span + 1)) == NULL) 118bd1df636SDevin Teske _errx(EXIT_FAILURE, "Out of memory!"); 119bd1df636SDevin Teske snprintf(dist->path, span + 1, "%s", distributions); 120bd1df636SDevin Teske dist->path[span] = '\0'; 121bd1df636SDevin Teske 122bd1df636SDevin Teske /* Set display name */ 123bd1df636SDevin Teske dist->name = strrchr(dist->path, '/'); 124bd1df636SDevin Teske if (dist->name == NULL) 125bd1df636SDevin Teske dist->name = dist->path; 126bd1df636SDevin Teske 127bd1df636SDevin Teske /* Set initial length in files (-1 == error) */ 128bd1df636SDevin Teske dist->length = count_files(dist->path); 129bd1df636SDevin Teske if (dist->length < 0) { 130bd1df636SDevin Teske end_dialog(); 131bd1df636SDevin Teske return (EXIT_FAILURE); 132bd1df636SDevin Teske } 133bd1df636SDevin Teske 134bd1df636SDevin Teske distributions += span; 135bd1df636SDevin Teske } 136bd1df636SDevin Teske 137bd1df636SDevin Teske /* Optionally chdir(2) into $BSDINSTALL_CHROOT */ 138bd1df636SDevin Teske chrootdir = getenv("BSDINSTALL_CHROOT"); 139bd1df636SDevin Teske if (chrootdir != NULL && chdir(chrootdir) != 0) { 14082ac9f2bSDevin Teske snprintf(error, sizeof(error), 141bd1df636SDevin Teske "Could not change to directory %s: %s\n", 142bd1df636SDevin Teske chrootdir, strerror(errno)); 14357bda1b6SNathan Whitehorn dialog_msgbox("Error", error, 0, 0, TRUE); 14457bda1b6SNathan Whitehorn end_dialog(); 14582ac9f2bSDevin Teske return (EXIT_FAILURE); 14657bda1b6SNathan Whitehorn } 14757bda1b6SNathan Whitehorn 1484ff1fc3bSDevin Teske /* Set cleanup routine for Ctrl-C action */ 1494ff1fc3bSDevin Teske act.sa_handler = sig_int; 1504ff1fc3bSDevin Teske sigaction(SIGINT, &act, 0); 1512118f387SNathan Whitehorn 1524ff1fc3bSDevin Teske /* 1534ff1fc3bSDevin Teske * Hand off to dpv(3) 1544ff1fc3bSDevin Teske */ 1554ff1fc3bSDevin Teske if ((config = calloc(1, config_size)) == NULL) 1564ff1fc3bSDevin Teske _errx(EXIT_FAILURE, "Out of memory!"); 1574ff1fc3bSDevin Teske config->backtitle = backtitle; 1584ff1fc3bSDevin Teske config->title = title; 1594ff1fc3bSDevin Teske config->pprompt = pprompt; 1604ff1fc3bSDevin Teske config->aprompt = aprompt; 1614ff1fc3bSDevin Teske config->options |= DPV_WIDE_MODE; 1624ff1fc3bSDevin Teske config->label_size = -1; 1634ff1fc3bSDevin Teske config->action = extract_files; 1644ff1fc3bSDevin Teske config->status_solo = 1654ff1fc3bSDevin Teske "%10lli files read @ %'9.1f files/sec."; 1664ff1fc3bSDevin Teske config->status_many = 1674ff1fc3bSDevin Teske "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]"; 16857bda1b6SNathan Whitehorn end_dialog(); 1694ff1fc3bSDevin Teske retval = dpv(config, dists); 17057bda1b6SNathan Whitehorn 1714ff1fc3bSDevin Teske dpv_free(); 172bd1df636SDevin Teske while ((dist = dists) != NULL) { 173bd1df636SDevin Teske dists = dist->next; 174bd1df636SDevin Teske if (dist->path != NULL) 175bd1df636SDevin Teske free(dist->path); 176bd1df636SDevin Teske free(dist); 177bd1df636SDevin Teske } 1782118f387SNathan Whitehorn 1792118f387SNathan Whitehorn return (retval); 1802118f387SNathan Whitehorn } 1812118f387SNathan Whitehorn 1824ff1fc3bSDevin Teske static void 1834ff1fc3bSDevin Teske sig_int(int sig __unused) 1844ff1fc3bSDevin Teske { 1854ff1fc3bSDevin Teske dpv_interrupt = TRUE; 1864ff1fc3bSDevin Teske } 1874ff1fc3bSDevin Teske 188bd1df636SDevin Teske /* 189bd1df636SDevin Teske * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST 190bd1df636SDevin Teske * if it exists, otherwise uses archive(3) to read the archive file. 191bd1df636SDevin Teske */ 1922118f387SNathan Whitehorn static int 19346cc2c02SNathan Whitehorn count_files(const char *file) 19446cc2c02SNathan Whitehorn { 19582ac9f2bSDevin Teske static FILE *manifest = NULL; 196bd1df636SDevin Teske char *p; 19782ac9f2bSDevin Teske int file_count; 19882ac9f2bSDevin Teske int retval; 199bd1df636SDevin Teske size_t span; 20046cc2c02SNathan Whitehorn struct archive_entry *entry; 20182ac9f2bSDevin Teske char line[512]; 20282ac9f2bSDevin Teske char path[PATH_MAX]; 20382ac9f2bSDevin Teske char errormsg[PATH_MAX + 512]; 20446cc2c02SNathan Whitehorn 20546cc2c02SNathan Whitehorn if (manifest == NULL) { 206bd1df636SDevin Teske snprintf(path, sizeof(path), "%s/MANIFEST", distdir); 20746cc2c02SNathan Whitehorn manifest = fopen(path, "r"); 20846cc2c02SNathan Whitehorn } 20946cc2c02SNathan Whitehorn 21046cc2c02SNathan Whitehorn if (manifest != NULL) { 211896a9484SNathan Whitehorn rewind(manifest); 21246cc2c02SNathan Whitehorn while (fgets(line, sizeof(line), manifest) != NULL) { 213bd1df636SDevin Teske p = &line[0]; 214bd1df636SDevin Teske span = strcspn(p, "\t") ; 215bd1df636SDevin Teske if (span < 1 || strncmp(p, file, span) != 0) 21646cc2c02SNathan Whitehorn continue; 21746cc2c02SNathan Whitehorn 21846cc2c02SNathan Whitehorn /* 21946cc2c02SNathan Whitehorn * We're at the right manifest line. The file count is 22046cc2c02SNathan Whitehorn * in the third element 22146cc2c02SNathan Whitehorn */ 222bd1df636SDevin Teske span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t"); 223bd1df636SDevin Teske span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t"); 224bd1df636SDevin Teske if (span > 0) { 225bd1df636SDevin Teske file_count = (int)strtol(p, (char **)NULL, 10); 226bd1df636SDevin Teske if (file_count == 0 && errno == EINVAL) 227bd1df636SDevin Teske continue; 228bd1df636SDevin Teske return (file_count); 229bd1df636SDevin Teske } 23046cc2c02SNathan Whitehorn } 23146cc2c02SNathan Whitehorn } 23246cc2c02SNathan Whitehorn 233bd1df636SDevin Teske /* 234bd1df636SDevin Teske * Either no manifest, or manifest didn't mention this archive. 235bd1df636SDevin Teske * Use archive(3) to read the archive, counting files within. 236bd1df636SDevin Teske */ 237bd1df636SDevin Teske if ((archive = archive_read_new()) == NULL) { 238bd1df636SDevin Teske snprintf(errormsg, sizeof(errormsg), 239bd1df636SDevin Teske "Error: %s\n", archive_error_string(NULL)); 240bd1df636SDevin Teske dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 241bd1df636SDevin Teske return (-1); 242bd1df636SDevin Teske } 24346cc2c02SNathan Whitehorn archive_read_support_format_all(archive); 244ebb8fc42SMartin Matuska archive_read_support_filter_all(archive); 245bd1df636SDevin Teske snprintf(path, sizeof(path), "%s/%s", distdir, file); 24682ac9f2bSDevin Teske retval = archive_read_open_filename(archive, path, 4096); 24782ac9f2bSDevin Teske if (retval != ARCHIVE_OK) { 24846cc2c02SNathan Whitehorn snprintf(errormsg, sizeof(errormsg), 24946cc2c02SNathan Whitehorn "Error while extracting %s: %s\n", file, 25046cc2c02SNathan Whitehorn archive_error_string(archive)); 25146cc2c02SNathan Whitehorn dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 2524ff1fc3bSDevin Teske archive = NULL; 25346cc2c02SNathan Whitehorn return (-1); 25446cc2c02SNathan Whitehorn } 25546cc2c02SNathan Whitehorn 25646cc2c02SNathan Whitehorn file_count = 0; 25746cc2c02SNathan Whitehorn while (archive_read_next_header(archive, &entry) == ARCHIVE_OK) 25846cc2c02SNathan Whitehorn file_count++; 25946cc2c02SNathan Whitehorn archive_read_free(archive); 2604ff1fc3bSDevin Teske archive = NULL; 26146cc2c02SNathan Whitehorn 26246cc2c02SNathan Whitehorn return (file_count); 26346cc2c02SNathan Whitehorn } 26446cc2c02SNathan Whitehorn 26546cc2c02SNathan Whitehorn static int 2664ff1fc3bSDevin Teske extract_files(struct dpv_file_node *file, int out __unused) 2672118f387SNathan Whitehorn { 26882ac9f2bSDevin Teske int retval; 2692118f387SNathan Whitehorn struct archive_entry *entry; 27082ac9f2bSDevin Teske char path[PATH_MAX]; 27182ac9f2bSDevin Teske char errormsg[PATH_MAX + 512]; 2722118f387SNathan Whitehorn 2734ff1fc3bSDevin Teske /* Open the archive if necessary */ 2744ff1fc3bSDevin Teske if (archive == NULL) { 275bd1df636SDevin Teske if ((archive = archive_read_new()) == NULL) { 276bd1df636SDevin Teske snprintf(errormsg, sizeof(errormsg), 277bd1df636SDevin Teske "Error: %s\n", archive_error_string(NULL)); 278bd1df636SDevin Teske dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 2794ff1fc3bSDevin Teske dpv_abort = 1; 2804ff1fc3bSDevin Teske return (-1); 2812118f387SNathan Whitehorn } 2822118f387SNathan Whitehorn archive_read_support_format_all(archive); 283ebb8fc42SMartin Matuska archive_read_support_filter_all(archive); 284bd1df636SDevin Teske snprintf(path, sizeof(path), "%s/%s", distdir, file->path); 28582ac9f2bSDevin Teske retval = archive_read_open_filename(archive, path, 4096); 286bd1df636SDevin Teske if (retval != 0) { 287bd1df636SDevin Teske snprintf(errormsg, sizeof(errormsg), 288bd1df636SDevin Teske "Error opening %s: %s\n", file->name, 289bd1df636SDevin Teske archive_error_string(archive)); 290bd1df636SDevin Teske dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 2914ff1fc3bSDevin Teske file->status = DPV_STATUS_FAILED; 2924ff1fc3bSDevin Teske dpv_abort = 1; 2934ff1fc3bSDevin Teske return (-1); 2944ff1fc3bSDevin Teske } 295bd1df636SDevin Teske } 2962118f387SNathan Whitehorn 2974ff1fc3bSDevin Teske /* Read the next archive header */ 2984ff1fc3bSDevin Teske retval = archive_read_next_header(archive, &entry); 2992118f387SNathan Whitehorn 3004ff1fc3bSDevin Teske /* If that went well, perform the extraction */ 3014ff1fc3bSDevin Teske if (retval == ARCHIVE_OK) 30282ac9f2bSDevin Teske retval = archive_read_extract(archive, entry, 3032118f387SNathan Whitehorn ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER | 3042118f387SNathan Whitehorn ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | 3052118f387SNathan Whitehorn ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS); 3062118f387SNathan Whitehorn 3074ff1fc3bSDevin Teske /* Test for either EOF or error */ 3084ff1fc3bSDevin Teske if (retval == ARCHIVE_EOF) { 3092118f387SNathan Whitehorn archive_read_free(archive); 3104ff1fc3bSDevin Teske archive = NULL; 3114ff1fc3bSDevin Teske file->status = DPV_STATUS_DONE; 3124ff1fc3bSDevin Teske return (100); 313*a5837f2cSNathan Whitehorn } else if (retval != ARCHIVE_OK && 314*a5837f2cSNathan Whitehorn !(retval == ARCHIVE_WARN && 315*a5837f2cSNathan Whitehorn strcmp(archive_error_string(archive), "Can't restore time") == 0)) { 316*a5837f2cSNathan Whitehorn /* 317*a5837f2cSNathan Whitehorn * Print any warning/error messages except inability to set 318*a5837f2cSNathan Whitehorn * ctime/mtime, which is not fatal, or even interesting, 319*a5837f2cSNathan Whitehorn * for our purposes. Would be nice if this were a libarchive 320*a5837f2cSNathan Whitehorn * option. 321*a5837f2cSNathan Whitehorn */ 3224ff1fc3bSDevin Teske snprintf(errormsg, sizeof(errormsg), 3234ff1fc3bSDevin Teske "Error while extracting %s: %s\n", file->name, 3244ff1fc3bSDevin Teske archive_error_string(archive)); 3254ff1fc3bSDevin Teske dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 3264ff1fc3bSDevin Teske file->status = DPV_STATUS_FAILED; 3274ff1fc3bSDevin Teske dpv_abort = 1; 3284ff1fc3bSDevin Teske return (-1); 3292118f387SNathan Whitehorn } 3302118f387SNathan Whitehorn 3314ff1fc3bSDevin Teske dpv_overall_read++; 3324ff1fc3bSDevin Teske file->read++; 3334ff1fc3bSDevin Teske 3344ff1fc3bSDevin Teske /* Calculate [overall] percentage of completion (if possible) */ 3354ff1fc3bSDevin Teske if (file->length >= 0) 3364ff1fc3bSDevin Teske return (file->read * 100 / file->length); 3374ff1fc3bSDevin Teske else 3384ff1fc3bSDevin Teske return (-1); 3392118f387SNathan Whitehorn } 340