Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bsdinstall/distextract/distextract.c
/*- | /*- | ||||
* Copyright (c) 2011 Nathan Whitehorn | * Copyright (c) 2011 Nathan Whitehorn | ||||
* Copyright (c) 2014 Devin Teske <dteske@FreeBSD.org> | |||||
* All rights reserved. | * All rights reserved. | ||||
* | * | ||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
* notice, this list of conditions and the following disclaimer. | * notice, this list of conditions and the following disclaimer. | ||||
* 2. Redistributions in binary form must reproduce the above copyright | * 2. Redistributions in binary form must reproduce the above copyright | ||||
* notice, this list of conditions and the following disclaimer in the | * notice, this list of conditions and the following disclaimer in the | ||||
* documentation and/or other materials provided with the distribution. | * documentation and/or other materials provided with the distribution. | ||||
* | * | ||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | ||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | ||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | ||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | * 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 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||||
* SUCH DAMAGE. | * SUCH DAMAGE. | ||||
* | |||||
* $FreeBSD$ | |||||
*/ | */ | ||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <stdio.h> | |||||
#include <errno.h> | |||||
#include <limits.h> | |||||
#include <archive.h> | #include <archive.h> | ||||
#include <ctype.h> | |||||
#include <dialog.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> | |||||
static int extract_files(int nfiles, const char **files); | /* Data to process */ | ||||
static char *distdir = NULL; | |||||
static struct archive *archive = NULL; | |||||
static struct dpv_file_node *file_list = NULL; | |||||
/* Data processing */ | |||||
static int extract_flags = 0; | |||||
/* Extra display information */ | |||||
static char backtitle[] = "FreeBSD Installer"; | |||||
static char title[] = "Archive Extraction"; | |||||
static char pprompt[] = "Extracting distribution files...\n"; | |||||
static char aprompt[] = "\n Overall Progress:"; | |||||
/* Function prototypes for private functions (see style(9)) */ | |||||
static void sig_int(int sig); | |||||
static int count_archive_files(const char *file); | |||||
static int extract(struct dpv_file_node *file, int out); | |||||
int main(void); | |||||
#if __FreeBSD_version <= 1000008 /* r232154: bump for libarchive update */ | |||||
#define archive_read_support_filter_all(x) \ | |||||
archive_read_support_compression_all(x) | |||||
#endif | |||||
#define _errx(...) (end_dialog(), errx(__VA_ARGS__)) | |||||
int | int | ||||
main(void) | main(void) | ||||
{ | { | ||||
char *diststring; | char *chrootdir; | ||||
const char **dists; | char *distributions; | ||||
int i, retval, ndists = 0; | int ndists = 0; | ||||
int rv; | |||||
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 *curfile = file_list; | |||||
struct sigaction act; | |||||
char error[PATH_MAX + 512]; | |||||
if (getenv("DISTRIBUTIONS") == NULL) { | if ((distributions = getenv("DISTRIBUTIONS")) == NULL) | ||||
fprintf(stderr, "DISTRIBUTIONS variable is not set\n"); | errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set"); | ||||
return (1); | if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL) | ||||
} | distdir = __DECONST(char *, ""); | ||||
diststring = strdup(getenv("DISTRIBUTIONS")); | /* Initialize dialog(3) */ | ||||
for (i = 0; diststring[i] != 0; i++) | init_dialog(stdin, stdout); | ||||
if (isspace(diststring[i]) && !isspace(diststring[i+1])) | 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; | |||||
} | |||||
ndists++; | ndists++; | ||||
ndists++; /* Last one */ | |||||
dists = calloc(ndists, sizeof(const char *)); | /* Allocate a new struct for the distribution */ | ||||
if (dists == NULL) { | if (curfile == NULL) { | ||||
fprintf(stderr, "Out of memory!\n"); | if ((curfile = calloc(1, file_node_size)) == NULL) | ||||
free(diststring); | _errx(EXIT_FAILURE, "Out of memory?!"); | ||||
return (1); | file_list = curfile; | ||||
} else { | |||||
if ((curfile->next = calloc(1, file_node_size)) | |||||
== NULL) _errx(EXIT_FAILURE, "Out of memory?!"); | |||||
curfile = curfile->next; | |||||
} | } | ||||
for (i = 0; i < ndists; i++) | /* Set path */ | ||||
dists[i] = strsep(&diststring, " \t"); | if ((curfile->path = malloc(span + 1)) == NULL) | ||||
_errx(EXIT_FAILURE, "Out of memory?!"); | |||||
snprintf(curfile->path, span + 1, "%s", distributions); | |||||
curfile->path[span] = '\0'; | |||||
init_dialog(stdin, stdout); | /* Set display name */ | ||||
dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer"); | curfile->name = strrchr(curfile->path, '/'); | ||||
dlg_put_backtitle(); | if (curfile->name == NULL) | ||||
curfile->name = curfile->path; | |||||
if (chdir(getenv("BSDINSTALL_CHROOT")) != 0) { | /* Set initial length in bytes (-1 == unknown) */ | ||||
char error[512]; | curfile->length = count_archive_files(curfile->path); | ||||
sprintf(error, "Could could change to directory %s: %s\n", | if (curfile->length < 0) { | ||||
getenv("BSDINSTALL_DISTDIR"), strerror(errno)); | 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 could change to directory %s: %s\n", | |||||
chrootdir, strerror(errno)); | |||||
dialog_msgbox("Error", error, 0, 0, TRUE); | dialog_msgbox("Error", error, 0, 0, TRUE); | ||||
end_dialog(); | end_dialog(); | ||||
return (1); | return (EXIT_FAILURE); | ||||
} | } | ||||
retval = extract_files(ndists, dists); | /* | ||||
* 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; | |||||
config->status_solo = | |||||
"%10lli files read @ %'9.1f files/sec."; | |||||
config->status_many = | |||||
"%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]"; | |||||
extract_flags |= ARCHIVE_EXTRACT_TIME; | |||||
extract_flags |= ARCHIVE_EXTRACT_OWNER; | |||||
extract_flags |= ARCHIVE_EXTRACT_PERM; | |||||
extract_flags |= ARCHIVE_EXTRACT_ACL; | |||||
extract_flags |= ARCHIVE_EXTRACT_XATTR; | |||||
extract_flags |= ARCHIVE_EXTRACT_FFLAGS; | |||||
end_dialog(); | end_dialog(); | ||||
rv = dpv(config, file_list) | |||||
dpv_free(); | |||||
free(diststring); | return (rv); | ||||
free(dists); | } | ||||
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 | static int | ||||
count_files(const char *file) | count_archive_files(const char *file) | ||||
{ | { | ||||
struct archive *archive; | |||||
struct archive_entry *entry; | |||||
static FILE *manifest = NULL; | static FILE *manifest = NULL; | ||||
char path[MAXPATHLEN]; | char *p; | ||||
int count, rv; | |||||
size_t span; | |||||
struct archive_entry *entry; | |||||
char errormsg[512]; | char errormsg[512]; | ||||
int file_count, err; | char line[512]; | ||||
char path[PATH_MAX]; | |||||
if (manifest == NULL) { | if (manifest == NULL) { | ||||
sprintf(path, "%s/MANIFEST", getenv("BSDINSTALL_DISTDIR")); | snprintf(path, PATH_MAX, "%s/MANIFEST", distdir); | ||||
manifest = fopen(path, "r"); | manifest = fopen(path, "r"); | ||||
} | } | ||||
if (manifest != NULL) { | if (manifest != NULL) { | ||||
char line[512]; | |||||
char *tok1, *tok2; | |||||
rewind(manifest); | rewind(manifest); | ||||
while (fgets(line, sizeof(line), manifest) != NULL) { | while (fgets(line, sizeof(line), manifest) != NULL) { | ||||
tok2 = line; | p = &line[0]; | ||||
tok1 = strsep(&tok2, "\t"); | span = strcspn(p, "\t") ; | ||||
if (tok1 == NULL || strcmp(tok1, file) != 0) | |||||
if (span < 1 || strncmp(p, file, span) != 0) | |||||
continue; | continue; | ||||
/* | /* | ||||
* We're at the right manifest line. The file count is | * We're at the right manifest line. | ||||
* in the third element | * The file count is in the third element | ||||
*/ | */ | ||||
tok1 = strsep(&tok2, "\t"); | span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t"); | ||||
tok1 = strsep(&tok2, "\t"); | span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t"); | ||||
if (tok1 != NULL) | if (span > 0) { | ||||
return atoi(tok1); | count = (int)strtol(p, (char **)NULL, 10); | ||||
if (count == 0 && errno == EINVAL) | |||||
continue; | |||||
return (count); | |||||
} | } | ||||
} | } | ||||
} | |||||
/* Either we didn't have a manifest, or this archive wasn't there */ | /* | ||||
archive = archive_read_new(); | * 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_format_all(archive); | ||||
archive_read_support_filter_all(archive); | archive_read_support_filter_all(archive); | ||||
sprintf(path, "%s/%s", getenv("BSDINSTALL_DISTDIR"), file); | snprintf(path, PATH_MAX, "%s/%s", distdir, file); | ||||
err = archive_read_open_filename(archive, path, 4096); | rv = archive_read_open_filename(archive, path, 4096); | ||||
if (err != ARCHIVE_OK) { | if (rv != ARCHIVE_OK) { | ||||
snprintf(errormsg, sizeof(errormsg), | snprintf(errormsg, sizeof(errormsg), | ||||
"Error while extracting %s: %s\n", file, | "Error while extracting %s: %s\n", file, | ||||
archive_error_string(archive)); | archive_error_string(archive)); | ||||
dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); | dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); | ||||
archive = NULL; | |||||
return (-1); | return (-1); | ||||
} | } | ||||
file_count = 0; | count = 0; | ||||
while (archive_read_next_header(archive, &entry) == ARCHIVE_OK) | while (archive_read_next_header(archive, &entry) == ARCHIVE_OK) | ||||
file_count++; | count++; | ||||
archive_read_free(archive); | archive_read_free(archive); | ||||
archive = NULL; | |||||
return (file_count); | return (count); | ||||
} | } | ||||
static int | static int | ||||
extract_files(int nfiles, const char **files) | extract(struct dpv_file_node *file, int out __unused) | ||||
{ | { | ||||
const char *items[nfiles*2]; | int rv; | ||||
char path[PATH_MAX]; | |||||
int archive_files[nfiles]; | |||||
int total_files, current_files, archive_file; | |||||
struct archive *archive; | |||||
struct archive_entry *entry; | struct archive_entry *entry; | ||||
char errormsg[512]; | char errormsg[512]; | ||||
char status[8]; | char path[PATH_MAX]; | ||||
int i, err, progress, last_progress; | |||||
err = 0; | /* Open the archive if necessary */ | ||||
progress = 0; | if (archive == NULL) { | ||||
if ((archive = archive_read_new()) == NULL) { | |||||
/* Make the transfer list for dialog */ | snprintf(errormsg, sizeof(errormsg), | ||||
for (i = 0; i < nfiles; i++) { | "Error: %s\n", archive_error_string(NULL)); | ||||
items[i*2] = strrchr(files[i], '/'); | dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); | ||||
if (items[i*2] != NULL) | dpv_abort = 1; | ||||
items[i*2]++; | |||||
else | |||||
items[i*2] = files[i]; | |||||
items[i*2 + 1] = "Pending"; | |||||
} | |||||
dialog_msgbox("", | |||||
"Checking distribution archives.\nPlease wait...", 0, 0, FALSE); | |||||
/* Count all the files */ | |||||
total_files = 0; | |||||
for (i = 0; i < nfiles; i++) { | |||||
archive_files[i] = count_files(files[i]); | |||||
if (archive_files[i] < 0) | |||||
return (-1); | return (-1); | ||||
total_files += archive_files[i]; | |||||
} | } | ||||
current_files = 0; | |||||
for (i = 0; i < nfiles; i++) { | |||||
archive = archive_read_new(); | |||||
archive_read_support_format_all(archive); | archive_read_support_format_all(archive); | ||||
archive_read_support_filter_all(archive); | archive_read_support_filter_all(archive); | ||||
sprintf(path, "%s/%s", getenv("BSDINSTALL_DISTDIR"), files[i]); | snprintf(path, PATH_MAX, "%s/%s", distdir, file->path); | ||||
err = archive_read_open_filename(archive, path, 4096); | rv = archive_read_open_filename(archive, path, 4096); | ||||
if (rv != 0) { | |||||
items[i*2 + 1] = "In Progress"; | snprintf(errormsg, sizeof(errormsg), | ||||
archive_file = 0; | "Error opening %s: %s\n", file->name, | ||||
archive_error_string(archive)); | |||||
while ((err = archive_read_next_header(archive, &entry)) == | dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); | ||||
ARCHIVE_OK) { | file->status = DPV_STATUS_FAILED; | ||||
last_progress = progress; | dpv_abort = 1; | ||||
progress = (current_files*100)/total_files; | return (-1); | ||||
sprintf(status, "-%d", | |||||
(archive_file*100)/archive_files[i]); | |||||
items[i*2 + 1] = status; | |||||
if (progress > last_progress) | |||||
dialog_mixedgauge("Archive Extraction", | |||||
"Extracting distribution files...", 0, 0, | |||||
progress, nfiles, | |||||
__DECONST(char **, items)); | |||||
err = archive_read_extract(archive, entry, | |||||
ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER | | |||||
ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | | |||||
ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS); | |||||
if (err != ARCHIVE_OK) | |||||
break; | |||||
archive_file++; | |||||
current_files++; | |||||
} | } | ||||
} | |||||
items[i*2 + 1] = "Done"; | /* Read the next archive header */ | ||||
rv = archive_read_next_header(archive, &entry); | |||||
if (err != ARCHIVE_EOF) { | /* If that went well, perform the extraction */ | ||||
if (rv == ARCHIVE_OK) | |||||
rv = archive_read_extract(archive, entry, extract_flags); | |||||
/* Test for either EOF or error */ | |||||
if (rv == ARCHIVE_EOF) { | |||||
archive_read_free(archive); | |||||
archive = NULL; | |||||
file->status = DPV_STATUS_DONE; | |||||
return (100); | |||||
} else if (rv != ARCHIVE_OK) { | |||||
snprintf(errormsg, sizeof(errormsg), | snprintf(errormsg, sizeof(errormsg), | ||||
"Error while extracting %s: %s\n", items[i*2], | "Error while extracting %s: %s\n", file->name, | ||||
archive_error_string(archive)); | archive_error_string(archive)); | ||||
items[i*2 + 1] = "Failed"; | dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); | ||||
dialog_msgbox("Extract Error", errormsg, 0, 0, | file->status = DPV_STATUS_FAILED; | ||||
TRUE); | dpv_abort = 1; | ||||
return (err); | return (-1); | ||||
} | } | ||||
archive_read_free(archive); | overall_read++; | ||||
} | file->read++; | ||||
return (0); | /* Calculate [overall] percentage of completion (if possible) */ | ||||
if (file->length >= 0) | |||||
return (file->read * 100 / file->length); | |||||
else | |||||
return (-1); | |||||
} | } |