Path: blob/main/usr.sbin/bsdinstall/distextract/distextract.c
105518 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2011 Nathan Whitehorn4* Copyright (c) 2014 Devin Teske <[email protected]>5* All rights reserved.6*7* Redistribution and use in source and binary forms, with or without8* modification, are permitted provided that the following conditions9* are met:10* 1. Redistributions of source code must retain the above copyright11* notice, this list of conditions and the following disclaimer.12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15*16* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND17* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE18* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE19* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE20* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL21* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS22* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)23* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT24* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY25* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF26* SUCH DAMAGE.27*/2829#include <sys/param.h>3031#include <archive.h>32#include <ctype.h>33#include <bsddialog.h>34#include <bsddialog_progressview.h>35#include <err.h>36#include <errno.h>37#include <limits.h>38#include <signal.h>39#include <stdio.h>40#include <stdlib.h>41#include <string.h>42#include <unistd.h>4344#include "opt_osname.h"4546/* Data to process */47static const char *distdir = NULL;48static struct archive *archive = NULL;4950/* Function prototypes */51static void sig_int(int sig);52static int count_files(const char *file);53static int extract_files(struct bsddialog_fileminibar *file);5455#define _errx(...) (bsddialog_end(), errx(__VA_ARGS__))5657int58main(void)59{60char *chrootdir;61char *distributions;62char *distribs, *distrib;63int retval;64size_t minibar_size = sizeof(struct bsddialog_fileminibar);65unsigned int nminibars;66struct bsddialog_fileminibar *dists;67struct bsddialog_progviewconf pvconf;68struct bsddialog_conf conf;69struct sigaction act;70char error[PATH_MAX + 512];7172if ((distributions = getenv("DISTRIBUTIONS")) == NULL)73errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");74if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)75distdir = "";76if ((distribs = strdup(distributions)) == NULL)77errx(EXIT_FAILURE, "memory error");7879if (bsddialog_init() == BSDDIALOG_ERROR)80errx(EXIT_FAILURE, "Error libbsdialog: %s",81bsddialog_geterror());82bsddialog_initconf(&conf);83bsddialog_backtitle(&conf, OSNAME " Installer");84bsddialog_infobox(&conf,85"Checking distribution archives.\nPlease wait...", 4, 35);8687/* Parse $DISTRIBUTIONS */88nminibars = 0;89dists = NULL;90while ((distrib = strsep(&distribs, "\t\n\v\f\r ")) != NULL) {91if (strlen(distrib) == 0)92continue;9394/* Allocate a new struct for the distribution */95dists = realloc(dists, (nminibars + 1) * minibar_size);96if (dists == NULL)97_errx(EXIT_FAILURE, "Out of memory!");9899/* Set file path */100dists[nminibars].path = distrib;101102/* Set mini bar label */103dists[nminibars].label = strrchr(dists[nminibars].path, '/');104if (dists[nminibars].label == NULL)105dists[nminibars].label = dists[nminibars].path;106107/* Set initial length in files (-1 == error) */108dists[nminibars].size = count_files(dists[nminibars].path);109if (dists[nminibars].size < 0) {110bsddialog_end();111return (EXIT_FAILURE);112}113114/* Set initial status and implicitly miniperc to pending */115dists[nminibars].status = BSDDIALOG_MG_PENDING;116117/* Set initial read */118dists[nminibars].read = 0;119120nminibars += 1;121}122123/* Optionally chdir(2) into $BSDINSTALL_CHROOT */124chrootdir = getenv("BSDINSTALL_CHROOT");125if (chrootdir != NULL && chdir(chrootdir) != 0) {126snprintf(error, sizeof(error),127"Could not change to directory %s: %s\n",128chrootdir, strerror(errno));129conf.title = "Error";130bsddialog_msgbox(&conf, error, 0, 0);131bsddialog_end();132return (EXIT_FAILURE);133}134135/* Set cleanup routine for Ctrl-C action */136act.sa_handler = sig_int;137sigaction(SIGINT, &act, 0);138139conf.title = "Archive Extraction";140conf.auto_minwidth = 40;141pvconf.callback = extract_files;142pvconf.refresh = 1;143pvconf.fmtbottomstr = "%10lli files read @ %'9.1f files/sec.";144bsddialog_total_progview = 0;145bsddialog_interruptprogview = bsddialog_abortprogview = false;146retval = bsddialog_progressview(&conf,147"\nExtracting distribution files...\n", 0, 0,148&pvconf, nminibars, dists);149150if (retval == BSDDIALOG_ERROR) {151fprintf(stderr, "progressview error: %s\n",152bsddialog_geterror());153}154155bsddialog_end();156157free(distribs);158free(dists);159160return (retval);161}162163static void164sig_int(int sig __unused)165{166bsddialog_interruptprogview = true;167}168169/*170* Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST171* if it exists, otherwise uses archive(3) to read the archive file.172*/173static int174count_files(const char *file)175{176static FILE *manifest = NULL;177char *p;178int file_count;179int retval;180size_t span;181struct archive_entry *entry;182char line[512];183char path[PATH_MAX];184char errormsg[PATH_MAX + 512];185struct bsddialog_conf conf;186187if (manifest == NULL) {188snprintf(path, sizeof(path), "%s/MANIFEST", distdir);189manifest = fopen(path, "r");190}191192if (manifest != NULL) {193rewind(manifest);194while (fgets(line, sizeof(line), manifest) != NULL) {195p = &line[0];196span = strcspn(p, "\t") ;197if (span < 1 || strncmp(p, file, span) != 0)198continue;199200/*201* We're at the right manifest line. The file count is202* in the third element203*/204span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");205span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");206if (span > 0) {207file_count = (int)strtol(p, (char **)NULL, 10);208if (file_count == 0 && errno == EINVAL)209continue;210return (file_count);211}212}213}214215/*216* Either no manifest, or manifest didn't mention this archive.217* Use archive(3) to read the archive, counting files within.218*/219bsddialog_initconf(&conf);220if ((archive = archive_read_new()) == NULL) {221snprintf(errormsg, sizeof(errormsg),222"Error: %s\n", archive_error_string(NULL));223conf.title = "Extract Error";224bsddialog_msgbox(&conf, errormsg, 0, 0);225return (-1);226}227archive_read_support_format_all(archive);228archive_read_support_filter_all(archive);229snprintf(path, sizeof(path), "%s/%s", distdir, file);230retval = archive_read_open_filename(archive, path, 4096);231if (retval != ARCHIVE_OK) {232snprintf(errormsg, sizeof(errormsg),233"Error while extracting %s: %s\n", file,234archive_error_string(archive));235conf.title = "Extract Error";236bsddialog_msgbox(&conf, errormsg, 0, 0);237archive = NULL;238return (-1);239}240241file_count = 0;242while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)243file_count++;244archive_read_free(archive);245archive = NULL;246247return (file_count);248}249250static int251extract_files(struct bsddialog_fileminibar *file)252{253int retval;254struct archive_entry *entry;255char path[PATH_MAX];256char errormsg[PATH_MAX + 512];257struct bsddialog_conf conf;258259bsddialog_initconf(&conf);260261/* Open the archive if necessary */262if (archive == NULL) {263if ((archive = archive_read_new()) == NULL) {264snprintf(errormsg, sizeof(errormsg),265"Error: %s\n", archive_error_string(NULL));266conf.title = "Extract Error";267bsddialog_msgbox(&conf, errormsg, 0, 0);268bsddialog_abortprogview = true;269return (-1);270}271archive_read_support_format_all(archive);272archive_read_support_filter_all(archive);273snprintf(path, sizeof(path), "%s/%s", distdir, file->path);274retval = archive_read_open_filename(archive, path, 4096);275if (retval != 0) {276snprintf(errormsg, sizeof(errormsg),277"Error opening %s: %s\n", file->label,278archive_error_string(archive));279conf.title = "Extract Error";280bsddialog_msgbox(&conf, errormsg, 0, 0);281file->status = BSDDIALOG_MG_FAILED;282bsddialog_abortprogview = true;283return (-1);284}285}286287/* Read the next archive header */288retval = archive_read_next_header(archive, &entry);289290/* If that went well, perform the extraction */291if (retval == ARCHIVE_OK)292retval = archive_read_extract(archive, entry,293ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |294ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |295ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);296297/* Test for either EOF or error */298if (retval == ARCHIVE_EOF) {299archive_read_free(archive);300archive = NULL;301file->status = BSDDIALOG_MG_DONE; /*Done*/;302return (100);303} else if (retval != ARCHIVE_OK &&304!(retval == ARCHIVE_WARN &&305strcmp(archive_error_string(archive), "Can't restore time") == 0)) {306/*307* Print any warning/error messages except inability to set308* ctime/mtime, which is not fatal, or even interesting,309* for our purposes. Would be nice if this were a libarchive310* option.311*/312snprintf(errormsg, sizeof(errormsg),313"Error while extracting %s: %s\n", file->label,314archive_error_string(archive));315conf.title = "Extract Error";316bsddialog_msgbox(&conf, errormsg, 0, 0);317file->status = BSDDIALOG_MG_FAILED; /* Failed */318bsddialog_abortprogview = true;319return (-1);320}321322bsddialog_total_progview++;323file->read++;324325/* Calculate [overall] percentage of completion (if possible) */326if (file->size >= 0)327return (file->read * 100 / file->size);328else329return (-1);330}331332333