xref: /freebsd/usr.sbin/bsdinstall/distextract/distextract.c (revision 4ff1fc3b17053f19a7bf1aced7f929bd5888a4f4)
12118f387SNathan Whitehorn /*-
22118f387SNathan Whitehorn  * Copyright (c) 2011 Nathan Whitehorn
382ac9f2bSDevin Teske  * Copyright (c) 2014 Devin Teske <dteske@FreeBSD.org>
42118f387SNathan Whitehorn  * All rights reserved.
52118f387SNathan Whitehorn  *
62118f387SNathan Whitehorn  * Redistribution and use in source and binary forms, with or without
72118f387SNathan Whitehorn  * modification, are permitted provided that the following conditions
82118f387SNathan Whitehorn  * are met:
92118f387SNathan Whitehorn  * 1. Redistributions of source code must retain the above copyright
102118f387SNathan Whitehorn  *    notice, this list of conditions and the following disclaimer.
112118f387SNathan Whitehorn  * 2. Redistributions in binary form must reproduce the above copyright
122118f387SNathan Whitehorn  *    notice, this list of conditions and the following disclaimer in the
132118f387SNathan Whitehorn  *    documentation and/or other materials provided with the distribution.
142118f387SNathan Whitehorn  *
152118f387SNathan Whitehorn  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
162118f387SNathan Whitehorn  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
172118f387SNathan Whitehorn  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
182118f387SNathan Whitehorn  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
192118f387SNathan Whitehorn  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
202118f387SNathan Whitehorn  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
212118f387SNathan Whitehorn  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
222118f387SNathan Whitehorn  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
232118f387SNathan Whitehorn  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
242118f387SNathan Whitehorn  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
252118f387SNathan Whitehorn  * SUCH DAMAGE.
262118f387SNathan Whitehorn  */
272118f387SNathan Whitehorn 
2882ac9f2bSDevin Teske #include <sys/cdefs.h>
2982ac9f2bSDevin Teske __FBSDID("$FreeBSD$");
3082ac9f2bSDevin Teske 
3146cc2c02SNathan Whitehorn #include <sys/param.h>
3282ac9f2bSDevin Teske #include <archive.h>
3382ac9f2bSDevin Teske #include <ctype.h>
3482ac9f2bSDevin Teske #include <dialog.h>
35*4ff1fc3bSDevin Teske #include <dpv.h>
3682ac9f2bSDevin Teske #include <err.h>
372118f387SNathan Whitehorn #include <errno.h>
382118f387SNathan Whitehorn #include <limits.h>
3982ac9f2bSDevin Teske #include <stdio.h>
4082ac9f2bSDevin Teske #include <stdlib.h>
4182ac9f2bSDevin Teske #include <string.h>
4282ac9f2bSDevin Teske #include <unistd.h>
432118f387SNathan Whitehorn 
44bd1df636SDevin Teske /* Data to process */
45bd1df636SDevin Teske static char *distdir = NULL;
46*4ff1fc3bSDevin Teske static struct archive *archive = NULL;
47*4ff1fc3bSDevin Teske static struct dpv_file_node *dists = NULL;
48bd1df636SDevin Teske 
49bd1df636SDevin Teske /* Function prototypes */
50*4ff1fc3bSDevin Teske static void	sig_int(int sig);
5182ac9f2bSDevin Teske static int	count_files(const char *file);
52*4ff1fc3bSDevin Teske static int	extract_files(struct dpv_file_node *file, int out);
53bd1df636SDevin Teske 
54bd1df636SDevin Teske #if __FreeBSD_version <= 1000008 /* r232154: bump for libarchive update */
55bd1df636SDevin Teske #define archive_read_support_filter_all(x) \
56bd1df636SDevin Teske 	archive_read_support_compression_all(x)
57bd1df636SDevin Teske #endif
58bd1df636SDevin Teske 
59bd1df636SDevin Teske #define _errx(...) (end_dialog(), errx(__VA_ARGS__))
602118f387SNathan Whitehorn 
612118f387SNathan Whitehorn int
622118f387SNathan Whitehorn main(void)
632118f387SNathan Whitehorn {
64bd1df636SDevin Teske 	char *chrootdir;
65bd1df636SDevin Teske 	char *distributions;
6682ac9f2bSDevin Teske 	int retval;
67*4ff1fc3bSDevin Teske 	size_t config_size = sizeof(struct dpv_config);
68*4ff1fc3bSDevin Teske 	size_t file_node_size = sizeof(struct dpv_file_node);
69bd1df636SDevin Teske 	size_t span;
70*4ff1fc3bSDevin Teske 	struct dpv_config *config;
71*4ff1fc3bSDevin Teske 	struct dpv_file_node *dist = dists;
72*4ff1fc3bSDevin Teske 	static char backtitle[] = "FreeBSD Installer";
73*4ff1fc3bSDevin Teske 	static char title[] = "Archive Extraction";
74*4ff1fc3bSDevin Teske 	static char aprompt[] = "\n  Overall Progress:";
75*4ff1fc3bSDevin Teske 	static char pprompt[] = "Extracting distribution files...\n";
76*4ff1fc3bSDevin Teske 	struct sigaction act;
7782ac9f2bSDevin Teske 	char error[PATH_MAX + 512];
78bfd258f7SNathan Whitehorn 
79bd1df636SDevin Teske 	if ((distributions = getenv("DISTRIBUTIONS")) == NULL)
8082ac9f2bSDevin Teske 		errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
81bd1df636SDevin Teske 	if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)
82bd1df636SDevin Teske 		distdir = __DECONST(char *, "");
83bfd258f7SNathan Whitehorn 
84bd1df636SDevin Teske 	/* Initialize dialog(3) */
8557bda1b6SNathan Whitehorn 	init_dialog(stdin, stdout);
86*4ff1fc3bSDevin Teske 	dialog_vars.backtitle = backtitle;
8757bda1b6SNathan Whitehorn 	dlg_put_backtitle();
8857bda1b6SNathan Whitehorn 
89bd1df636SDevin Teske 	dialog_msgbox("",
90bd1df636SDevin Teske 	    "Checking distribution archives.\nPlease wait...", 4, 35, FALSE);
91bd1df636SDevin Teske 
92bd1df636SDevin Teske 	/*
93*4ff1fc3bSDevin Teske 	 * Parse $DISTRIBUTIONS into dpv(3) linked-list
94bd1df636SDevin Teske 	 */
95bd1df636SDevin Teske 	while (*distributions != '\0') {
96bd1df636SDevin Teske 		span = strcspn(distributions, "\t\n\v\f\r ");
97bd1df636SDevin Teske 		if (span < 1) { /* currently on whitespace */
98bd1df636SDevin Teske 			distributions++;
99bd1df636SDevin Teske 			continue;
100bd1df636SDevin Teske 		}
101bd1df636SDevin Teske 
102bd1df636SDevin Teske 		/* Allocate a new struct for the distribution */
103bd1df636SDevin Teske 		if (dist == NULL) {
104bd1df636SDevin Teske 			if ((dist = calloc(1, file_node_size)) == NULL)
105bd1df636SDevin Teske 				_errx(EXIT_FAILURE, "Out of memory!");
106bd1df636SDevin Teske 			dists = dist;
107bd1df636SDevin Teske 		} else {
108bd1df636SDevin Teske 			dist->next = calloc(1, file_node_size);
109bd1df636SDevin Teske 			if (dist->next == NULL)
110bd1df636SDevin Teske 				_errx(EXIT_FAILURE, "Out of memory!");
111bd1df636SDevin Teske 			dist = dist->next;
112bd1df636SDevin Teske 		}
113bd1df636SDevin Teske 
114bd1df636SDevin Teske 		/* Set path */
115bd1df636SDevin Teske 		if ((dist->path = malloc(span + 1)) == NULL)
116bd1df636SDevin Teske 			_errx(EXIT_FAILURE, "Out of memory!");
117bd1df636SDevin Teske 		snprintf(dist->path, span + 1, "%s", distributions);
118bd1df636SDevin Teske 		dist->path[span] = '\0';
119bd1df636SDevin Teske 
120bd1df636SDevin Teske 		/* Set display name */
121bd1df636SDevin Teske 		dist->name = strrchr(dist->path, '/');
122bd1df636SDevin Teske 		if (dist->name == NULL)
123bd1df636SDevin Teske 			dist->name = dist->path;
124bd1df636SDevin Teske 
125bd1df636SDevin Teske 		/* Set initial length in files (-1 == error) */
126bd1df636SDevin Teske 		dist->length = count_files(dist->path);
127bd1df636SDevin Teske 		if (dist->length < 0) {
128bd1df636SDevin Teske 			end_dialog();
129bd1df636SDevin Teske 			return (EXIT_FAILURE);
130bd1df636SDevin Teske 		}
131bd1df636SDevin Teske 
132bd1df636SDevin Teske 		distributions += span;
133bd1df636SDevin Teske 	}
134bd1df636SDevin Teske 
135bd1df636SDevin Teske 	/* Optionally chdir(2) into $BSDINSTALL_CHROOT */
136bd1df636SDevin Teske 	chrootdir = getenv("BSDINSTALL_CHROOT");
137bd1df636SDevin Teske 	if (chrootdir != NULL && chdir(chrootdir) != 0) {
13882ac9f2bSDevin Teske 		snprintf(error, sizeof(error),
139bd1df636SDevin Teske 		    "Could not change to directory %s: %s\n",
140bd1df636SDevin Teske 		    chrootdir, strerror(errno));
14157bda1b6SNathan Whitehorn 		dialog_msgbox("Error", error, 0, 0, TRUE);
14257bda1b6SNathan Whitehorn 		end_dialog();
14382ac9f2bSDevin Teske 		return (EXIT_FAILURE);
14457bda1b6SNathan Whitehorn 	}
14557bda1b6SNathan Whitehorn 
146*4ff1fc3bSDevin Teske 	/* Set cleanup routine for Ctrl-C action */
147*4ff1fc3bSDevin Teske 	act.sa_handler = sig_int;
148*4ff1fc3bSDevin Teske 	sigaction(SIGINT, &act, 0);
1492118f387SNathan Whitehorn 
150*4ff1fc3bSDevin Teske 	/*
151*4ff1fc3bSDevin Teske 	 * Hand off to dpv(3)
152*4ff1fc3bSDevin Teske 	 */
153*4ff1fc3bSDevin Teske 	if ((config = calloc(1, config_size)) == NULL)
154*4ff1fc3bSDevin Teske 		_errx(EXIT_FAILURE, "Out of memory!");
155*4ff1fc3bSDevin Teske 	config->backtitle	= backtitle;
156*4ff1fc3bSDevin Teske 	config->title		= title;
157*4ff1fc3bSDevin Teske 	config->pprompt		= pprompt;
158*4ff1fc3bSDevin Teske 	config->aprompt		= aprompt;
159*4ff1fc3bSDevin Teske 	config->options		|= DPV_WIDE_MODE;
160*4ff1fc3bSDevin Teske 	config->label_size	= -1;
161*4ff1fc3bSDevin Teske 	config->action		= extract_files;
162*4ff1fc3bSDevin Teske 	config->status_solo	=
163*4ff1fc3bSDevin Teske 	    "%10lli files read @ %'9.1f files/sec.";
164*4ff1fc3bSDevin Teske 	config->status_many	=
165*4ff1fc3bSDevin Teske 	    "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]";
16657bda1b6SNathan Whitehorn 	end_dialog();
167*4ff1fc3bSDevin Teske 	retval = dpv(config, dists);
16857bda1b6SNathan Whitehorn 
169*4ff1fc3bSDevin Teske 	dpv_free();
170bd1df636SDevin Teske 	while ((dist = dists) != NULL) {
171bd1df636SDevin Teske 		dists = dist->next;
172bd1df636SDevin Teske 		if (dist->path != NULL)
173bd1df636SDevin Teske 			free(dist->path);
174bd1df636SDevin Teske 		free(dist);
175bd1df636SDevin Teske 	}
1762118f387SNathan Whitehorn 
1772118f387SNathan Whitehorn 	return (retval);
1782118f387SNathan Whitehorn }
1792118f387SNathan Whitehorn 
180*4ff1fc3bSDevin Teske static void
181*4ff1fc3bSDevin Teske sig_int(int sig __unused)
182*4ff1fc3bSDevin Teske {
183*4ff1fc3bSDevin Teske 	dpv_interrupt = TRUE;
184*4ff1fc3bSDevin Teske }
185*4ff1fc3bSDevin Teske 
186bd1df636SDevin Teske /*
187bd1df636SDevin Teske  * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST
188bd1df636SDevin Teske  * if it exists, otherwise uses archive(3) to read the archive file.
189bd1df636SDevin Teske  */
1902118f387SNathan Whitehorn static int
19146cc2c02SNathan Whitehorn count_files(const char *file)
19246cc2c02SNathan Whitehorn {
19382ac9f2bSDevin Teske 	static FILE *manifest = NULL;
194bd1df636SDevin Teske 	char *p;
19582ac9f2bSDevin Teske 	int file_count;
19682ac9f2bSDevin Teske 	int retval;
197bd1df636SDevin Teske 	size_t span;
19846cc2c02SNathan Whitehorn 	struct archive_entry *entry;
19982ac9f2bSDevin Teske 	char line[512];
20082ac9f2bSDevin Teske 	char path[PATH_MAX];
20182ac9f2bSDevin Teske 	char errormsg[PATH_MAX + 512];
20246cc2c02SNathan Whitehorn 
20346cc2c02SNathan Whitehorn 	if (manifest == NULL) {
204bd1df636SDevin Teske 		snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
20546cc2c02SNathan Whitehorn 		manifest = fopen(path, "r");
20646cc2c02SNathan Whitehorn 	}
20746cc2c02SNathan Whitehorn 
20846cc2c02SNathan Whitehorn 	if (manifest != NULL) {
209896a9484SNathan Whitehorn 		rewind(manifest);
21046cc2c02SNathan Whitehorn 		while (fgets(line, sizeof(line), manifest) != NULL) {
211bd1df636SDevin Teske 			p = &line[0];
212bd1df636SDevin Teske 			span = strcspn(p, "\t") ;
213bd1df636SDevin Teske 			if (span < 1 || strncmp(p, file, span) != 0)
21446cc2c02SNathan Whitehorn 				continue;
21546cc2c02SNathan Whitehorn 
21646cc2c02SNathan Whitehorn 			/*
21746cc2c02SNathan Whitehorn 			 * We're at the right manifest line. The file count is
21846cc2c02SNathan Whitehorn 			 * in the third element
21946cc2c02SNathan Whitehorn 			 */
220bd1df636SDevin Teske 			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
221bd1df636SDevin Teske 			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
222bd1df636SDevin Teske 			if (span > 0) {
223bd1df636SDevin Teske 				file_count = (int)strtol(p, (char **)NULL, 10);
224bd1df636SDevin Teske 				if (file_count == 0 && errno == EINVAL)
225bd1df636SDevin Teske 					continue;
226bd1df636SDevin Teske 				return (file_count);
227bd1df636SDevin Teske 			}
22846cc2c02SNathan Whitehorn 		}
22946cc2c02SNathan Whitehorn 	}
23046cc2c02SNathan Whitehorn 
231bd1df636SDevin Teske 	/*
232bd1df636SDevin Teske 	 * Either no manifest, or manifest didn't mention this archive.
233bd1df636SDevin Teske 	 * Use archive(3) to read the archive, counting files within.
234bd1df636SDevin Teske 	 */
235bd1df636SDevin Teske 	if ((archive = archive_read_new()) == NULL) {
236bd1df636SDevin Teske 		snprintf(errormsg, sizeof(errormsg),
237bd1df636SDevin Teske 		    "Error: %s\n", archive_error_string(NULL));
238bd1df636SDevin Teske 		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
239bd1df636SDevin Teske 		return (-1);
240bd1df636SDevin Teske 	}
24146cc2c02SNathan Whitehorn 	archive_read_support_format_all(archive);
242ebb8fc42SMartin Matuska 	archive_read_support_filter_all(archive);
243bd1df636SDevin Teske 	snprintf(path, sizeof(path), "%s/%s", distdir, file);
24482ac9f2bSDevin Teske 	retval = archive_read_open_filename(archive, path, 4096);
24582ac9f2bSDevin Teske 	if (retval != ARCHIVE_OK) {
24646cc2c02SNathan Whitehorn 		snprintf(errormsg, sizeof(errormsg),
24746cc2c02SNathan Whitehorn 		    "Error while extracting %s: %s\n", file,
24846cc2c02SNathan Whitehorn 		    archive_error_string(archive));
24946cc2c02SNathan Whitehorn 		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
250*4ff1fc3bSDevin Teske 		archive = NULL;
25146cc2c02SNathan Whitehorn 		return (-1);
25246cc2c02SNathan Whitehorn 	}
25346cc2c02SNathan Whitehorn 
25446cc2c02SNathan Whitehorn 	file_count = 0;
25546cc2c02SNathan Whitehorn 	while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
25646cc2c02SNathan Whitehorn 		file_count++;
25746cc2c02SNathan Whitehorn 	archive_read_free(archive);
258*4ff1fc3bSDevin Teske 	archive = NULL;
25946cc2c02SNathan Whitehorn 
26046cc2c02SNathan Whitehorn 	return (file_count);
26146cc2c02SNathan Whitehorn }
26246cc2c02SNathan Whitehorn 
26346cc2c02SNathan Whitehorn static int
264*4ff1fc3bSDevin Teske extract_files(struct dpv_file_node *file, int out __unused)
2652118f387SNathan Whitehorn {
26682ac9f2bSDevin Teske 	int retval;
2672118f387SNathan Whitehorn 	struct archive_entry *entry;
26882ac9f2bSDevin Teske 	char path[PATH_MAX];
26982ac9f2bSDevin Teske 	char errormsg[PATH_MAX + 512];
2702118f387SNathan Whitehorn 
271*4ff1fc3bSDevin Teske 	/* Open the archive if necessary */
272*4ff1fc3bSDevin Teske 	if (archive == NULL) {
273bd1df636SDevin Teske 		if ((archive = archive_read_new()) == NULL) {
274bd1df636SDevin Teske 			snprintf(errormsg, sizeof(errormsg),
275bd1df636SDevin Teske 			    "Error: %s\n", archive_error_string(NULL));
276bd1df636SDevin Teske 			dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
277*4ff1fc3bSDevin Teske 			dpv_abort = 1;
278*4ff1fc3bSDevin Teske 			return (-1);
2792118f387SNathan Whitehorn 		}
2802118f387SNathan Whitehorn 		archive_read_support_format_all(archive);
281ebb8fc42SMartin Matuska 		archive_read_support_filter_all(archive);
282bd1df636SDevin Teske 		snprintf(path, sizeof(path), "%s/%s", distdir, file->path);
28382ac9f2bSDevin Teske 		retval = archive_read_open_filename(archive, path, 4096);
284bd1df636SDevin Teske 		if (retval != 0) {
285bd1df636SDevin Teske 			snprintf(errormsg, sizeof(errormsg),
286bd1df636SDevin Teske 			    "Error opening %s: %s\n", file->name,
287bd1df636SDevin Teske 			    archive_error_string(archive));
288bd1df636SDevin Teske 			dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
289*4ff1fc3bSDevin Teske 			file->status = DPV_STATUS_FAILED;
290*4ff1fc3bSDevin Teske 			dpv_abort = 1;
291*4ff1fc3bSDevin Teske 			return (-1);
292*4ff1fc3bSDevin Teske 		}
293bd1df636SDevin Teske 	}
2942118f387SNathan Whitehorn 
295*4ff1fc3bSDevin Teske 	/* Read the next archive header */
296*4ff1fc3bSDevin Teske 	retval = archive_read_next_header(archive, &entry);
2972118f387SNathan Whitehorn 
298*4ff1fc3bSDevin Teske 	/* If that went well, perform the extraction */
299*4ff1fc3bSDevin Teske 	if (retval == ARCHIVE_OK)
30082ac9f2bSDevin Teske 		retval = archive_read_extract(archive, entry,
3012118f387SNathan Whitehorn 		    ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
3022118f387SNathan Whitehorn 		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
3032118f387SNathan Whitehorn 		    ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
3042118f387SNathan Whitehorn 
305*4ff1fc3bSDevin Teske 	/* Test for either EOF or error */
306*4ff1fc3bSDevin Teske 	if (retval == ARCHIVE_EOF) {
3072118f387SNathan Whitehorn 		archive_read_free(archive);
308*4ff1fc3bSDevin Teske 		archive = NULL;
309*4ff1fc3bSDevin Teske 		file->status = DPV_STATUS_DONE;
310*4ff1fc3bSDevin Teske 		return (100);
311*4ff1fc3bSDevin Teske 	} else if (retval != ARCHIVE_OK) {
312*4ff1fc3bSDevin Teske 		snprintf(errormsg, sizeof(errormsg),
313*4ff1fc3bSDevin Teske 		    "Error while extracting %s: %s\n", file->name,
314*4ff1fc3bSDevin Teske 		    archive_error_string(archive));
315*4ff1fc3bSDevin Teske 		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
316*4ff1fc3bSDevin Teske 		file->status = DPV_STATUS_FAILED;
317*4ff1fc3bSDevin Teske 		dpv_abort = 1;
318*4ff1fc3bSDevin Teske 		return (-1);
3192118f387SNathan Whitehorn 	}
3202118f387SNathan Whitehorn 
321*4ff1fc3bSDevin Teske 	dpv_overall_read++;
322*4ff1fc3bSDevin Teske 	file->read++;
323*4ff1fc3bSDevin Teske 
324*4ff1fc3bSDevin Teske 	/* Calculate [overall] percentage of completion (if possible) */
325*4ff1fc3bSDevin Teske 	if (file->length >= 0)
326*4ff1fc3bSDevin Teske 		return (file->read * 100 / file->length);
327*4ff1fc3bSDevin Teske 	else
328*4ff1fc3bSDevin Teske 		return (-1);
3292118f387SNathan Whitehorn }
330