aboutsummaryrefslogtreecommitdiff
path: root/bsdinstall/distextract/distextract.c
diff options
context:
space:
mode:
Diffstat (limited to 'bsdinstall/distextract/distextract.c')
-rw-r--r--bsdinstall/distextract/distextract.c334
1 files changed, 334 insertions, 0 deletions
diff --git a/bsdinstall/distextract/distextract.c b/bsdinstall/distextract/distextract.c
new file mode 100644
index 000000000000..8ad6c7b2c64b
--- /dev/null
+++ b/bsdinstall/distextract/distextract.c
@@ -0,0 +1,334 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2011 Nathan Whitehorn
+ * Copyright (c) 2014 Devin Teske <dteske@FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <archive.h>
+#include <ctype.h>
+#include <dialog.h>
+#include <dpv.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Data to process */
+static char *distdir = NULL;
+static struct archive *archive = NULL;
+static struct dpv_file_node *dists = NULL;
+
+/* Function prototypes */
+static void sig_int(int sig);
+static int count_files(const char *file);
+static int extract_files(struct dpv_file_node *file, int out);
+
+#define _errx(...) (end_dialog(), errx(__VA_ARGS__))
+
+int
+main(void)
+{
+ char *chrootdir;
+ char *distributions;
+ int retval;
+ size_t config_size = sizeof(struct dpv_config);
+ size_t file_node_size = sizeof(struct dpv_file_node);
+ size_t span;
+ struct dpv_config *config;
+ struct dpv_file_node *dist = dists;
+ static char backtitle[] = "FreeBSD Installer";
+ static char title[] = "Archive Extraction";
+ static char aprompt[] = "\n Overall Progress:";
+ static char pprompt[] = "Extracting distribution files...\n";
+ struct sigaction act;
+ char error[PATH_MAX + 512];
+
+ if ((distributions = getenv("DISTRIBUTIONS")) == NULL)
+ errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
+ if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)
+ distdir = __DECONST(char *, "");
+
+ /* Initialize dialog(3) */
+ init_dialog(stdin, stdout);
+ dialog_vars.backtitle = backtitle;
+ dlg_put_backtitle();
+
+ dialog_msgbox("",
+ "Checking distribution archives.\nPlease wait...", 4, 35, FALSE);
+
+ /*
+ * Parse $DISTRIBUTIONS into dpv(3) linked-list
+ */
+ while (*distributions != '\0') {
+ span = strcspn(distributions, "\t\n\v\f\r ");
+ if (span < 1) { /* currently on whitespace */
+ distributions++;
+ continue;
+ }
+
+ /* Allocate a new struct for the distribution */
+ if (dist == NULL) {
+ if ((dist = calloc(1, file_node_size)) == NULL)
+ _errx(EXIT_FAILURE, "Out of memory!");
+ dists = dist;
+ } else {
+ dist->next = calloc(1, file_node_size);
+ if (dist->next == NULL)
+ _errx(EXIT_FAILURE, "Out of memory!");
+ dist = dist->next;
+ }
+
+ /* Set path */
+ if ((dist->path = malloc(span + 1)) == NULL)
+ _errx(EXIT_FAILURE, "Out of memory!");
+ snprintf(dist->path, span + 1, "%s", distributions);
+ dist->path[span] = '\0';
+
+ /* Set display name */
+ dist->name = strrchr(dist->path, '/');
+ if (dist->name == NULL)
+ dist->name = dist->path;
+
+ /* Set initial length in files (-1 == error) */
+ dist->length = count_files(dist->path);
+ if (dist->length < 0) {
+ end_dialog();
+ return (EXIT_FAILURE);
+ }
+
+ distributions += span;
+ }
+
+ /* Optionally chdir(2) into $BSDINSTALL_CHROOT */
+ chrootdir = getenv("BSDINSTALL_CHROOT");
+ if (chrootdir != NULL && chdir(chrootdir) != 0) {
+ snprintf(error, sizeof(error),
+ "Could not change to directory %s: %s\n",
+ chrootdir, strerror(errno));
+ dialog_msgbox("Error", error, 0, 0, TRUE);
+ end_dialog();
+ return (EXIT_FAILURE);
+ }
+
+ /* Set cleanup routine for Ctrl-C action */
+ act.sa_handler = sig_int;
+ sigaction(SIGINT, &act, 0);
+
+ /*
+ * Hand off to dpv(3)
+ */
+ if ((config = calloc(1, config_size)) == NULL)
+ _errx(EXIT_FAILURE, "Out of memory!");
+ config->backtitle = backtitle;
+ config->title = title;
+ config->pprompt = pprompt;
+ config->aprompt = aprompt;
+ config->options |= DPV_WIDE_MODE;
+ config->label_size = -1;
+ config->action = extract_files;
+ config->status_solo =
+ "%10lli files read @ %'9.1f files/sec.";
+ config->status_many =
+ "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]";
+ end_dialog();
+ retval = dpv(config, dists);
+
+ dpv_free();
+ while ((dist = dists) != NULL) {
+ dists = dist->next;
+ if (dist->path != NULL)
+ free(dist->path);
+ free(dist);
+ }
+
+ return (retval);
+}
+
+static void
+sig_int(int sig __unused)
+{
+ dpv_interrupt = TRUE;
+}
+
+/*
+ * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST
+ * if it exists, otherwise uses archive(3) to read the archive file.
+ */
+static int
+count_files(const char *file)
+{
+ static FILE *manifest = NULL;
+ char *p;
+ int file_count;
+ int retval;
+ size_t span;
+ struct archive_entry *entry;
+ char line[512];
+ char path[PATH_MAX];
+ char errormsg[PATH_MAX + 512];
+
+ if (manifest == NULL) {
+ snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
+ manifest = fopen(path, "r");
+ }
+
+ if (manifest != NULL) {
+ rewind(manifest);
+ while (fgets(line, sizeof(line), manifest) != NULL) {
+ p = &line[0];
+ span = strcspn(p, "\t") ;
+ if (span < 1 || strncmp(p, file, span) != 0)
+ continue;
+
+ /*
+ * We're at the right manifest line. The file count is
+ * in the third element
+ */
+ span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
+ span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
+ if (span > 0) {
+ file_count = (int)strtol(p, (char **)NULL, 10);
+ if (file_count == 0 && errno == EINVAL)
+ continue;
+ return (file_count);
+ }
+ }
+ }
+
+ /*
+ * Either no manifest, or manifest didn't mention this archive.
+ * Use archive(3) to read the archive, counting files within.
+ */
+ if ((archive = archive_read_new()) == NULL) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error: %s\n", archive_error_string(NULL));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ return (-1);
+ }
+ archive_read_support_format_all(archive);
+ archive_read_support_filter_all(archive);
+ snprintf(path, sizeof(path), "%s/%s", distdir, file);
+ retval = archive_read_open_filename(archive, path, 4096);
+ if (retval != ARCHIVE_OK) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error while extracting %s: %s\n", file,
+ archive_error_string(archive));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ archive = NULL;
+ return (-1);
+ }
+
+ file_count = 0;
+ while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
+ file_count++;
+ archive_read_free(archive);
+ archive = NULL;
+
+ return (file_count);
+}
+
+static int
+extract_files(struct dpv_file_node *file, int out __unused)
+{
+ int retval;
+ struct archive_entry *entry;
+ char path[PATH_MAX];
+ char errormsg[PATH_MAX + 512];
+
+ /* Open the archive if necessary */
+ if (archive == NULL) {
+ if ((archive = archive_read_new()) == NULL) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error: %s\n", archive_error_string(NULL));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ dpv_abort = 1;
+ return (-1);
+ }
+ archive_read_support_format_all(archive);
+ archive_read_support_filter_all(archive);
+ snprintf(path, sizeof(path), "%s/%s", distdir, file->path);
+ retval = archive_read_open_filename(archive, path, 4096);
+ if (retval != 0) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error opening %s: %s\n", file->name,
+ archive_error_string(archive));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ file->status = DPV_STATUS_FAILED;
+ dpv_abort = 1;
+ return (-1);
+ }
+ }
+
+ /* Read the next archive header */
+ retval = archive_read_next_header(archive, &entry);
+
+ /* If that went well, perform the extraction */
+ if (retval == ARCHIVE_OK)
+ retval = archive_read_extract(archive, entry,
+ ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
+ ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
+ ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
+
+ /* Test for either EOF or error */
+ if (retval == ARCHIVE_EOF) {
+ archive_read_free(archive);
+ archive = NULL;
+ file->status = DPV_STATUS_DONE;
+ return (100);
+ } else if (retval != ARCHIVE_OK &&
+ !(retval == ARCHIVE_WARN &&
+ strcmp(archive_error_string(archive), "Can't restore time") == 0)) {
+ /*
+ * Print any warning/error messages except inability to set
+ * ctime/mtime, which is not fatal, or even interesting,
+ * for our purposes. Would be nice if this were a libarchive
+ * option.
+ */
+ snprintf(errormsg, sizeof(errormsg),
+ "Error while extracting %s: %s\n", file->name,
+ archive_error_string(archive));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ file->status = DPV_STATUS_FAILED;
+ dpv_abort = 1;
+ return (-1);
+ }
+
+ dpv_overall_read++;
+ file->read++;
+
+ /* Calculate [overall] percentage of completion (if possible) */
+ if (file->length >= 0)
+ return (file->read * 100 / file->length);
+ else
+ return (-1);
+}