1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2011 Nathan Whitehorn
5 * Copyright (c) 2014 Devin Teske <dteske@FreeBSD.org>
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD: stable/11/usr.sbin/bsdinstall/distextract/distextract.c 330449 2018-03-05 07:26:05Z eadler $");
32
33#include <sys/param.h>
34#include <archive.h>
35#include <ctype.h>
36#include <dialog.h>
37#include <dpv.h>
38#include <err.h>
39#include <errno.h>
40#include <limits.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45
46/* Data to process */
47static char *distdir = NULL;
48static struct archive *archive = NULL;
49static struct dpv_file_node *dists = NULL;
50
51/* Function prototypes */
52static void	sig_int(int sig);
53static int	count_files(const char *file);
54static int	extract_files(struct dpv_file_node *file, int out);
55
56#if __FreeBSD_version <= 1000008 /* r232154: bump for libarchive update */
57#define archive_read_support_filter_all(x) \
58	archive_read_support_compression_all(x)
59#endif
60
61#define _errx(...) (end_dialog(), errx(__VA_ARGS__))
62
63int
64main(void)
65{
66	char *chrootdir;
67	char *distributions;
68	int retval;
69	size_t config_size = sizeof(struct dpv_config);
70	size_t file_node_size = sizeof(struct dpv_file_node);
71	size_t span;
72	struct dpv_config *config;
73	struct dpv_file_node *dist = dists;
74	static char backtitle[] = "FreeBSD Installer";
75	static char title[] = "Archive Extraction";
76	static char aprompt[] = "\n  Overall Progress:";
77	static char pprompt[] = "Extracting distribution files...\n";
78	struct sigaction act;
79	char error[PATH_MAX + 512];
80
81	if ((distributions = getenv("DISTRIBUTIONS")) == NULL)
82		errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
83	if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)
84		distdir = __DECONST(char *, "");
85
86	/* Initialize dialog(3) */
87	init_dialog(stdin, stdout);
88	dialog_vars.backtitle = backtitle;
89	dlg_put_backtitle();
90
91	dialog_msgbox("",
92	    "Checking distribution archives.\nPlease wait...", 4, 35, FALSE);
93
94	/*
95	 * Parse $DISTRIBUTIONS into dpv(3) linked-list
96	 */
97	while (*distributions != '\0') {
98		span = strcspn(distributions, "\t\n\v\f\r ");
99		if (span < 1) { /* currently on whitespace */
100			distributions++;
101			continue;
102		}
103
104		/* Allocate a new struct for the distribution */
105		if (dist == NULL) {
106			if ((dist = calloc(1, file_node_size)) == NULL)
107				_errx(EXIT_FAILURE, "Out of memory!");
108			dists = dist;
109		} else {
110			dist->next = calloc(1, file_node_size);
111			if (dist->next == NULL)
112				_errx(EXIT_FAILURE, "Out of memory!");
113			dist = dist->next;
114		}
115
116		/* Set path */
117		if ((dist->path = malloc(span + 1)) == NULL)
118			_errx(EXIT_FAILURE, "Out of memory!");
119		snprintf(dist->path, span + 1, "%s", distributions);
120		dist->path[span] = '\0';
121
122		/* Set display name */
123		dist->name = strrchr(dist->path, '/');
124		if (dist->name == NULL)
125			dist->name = dist->path;
126
127		/* Set initial length in files (-1 == error) */
128		dist->length = count_files(dist->path);
129		if (dist->length < 0) {
130			end_dialog();
131			return (EXIT_FAILURE);
132		}
133
134		distributions += span;
135	}
136
137	/* Optionally chdir(2) into $BSDINSTALL_CHROOT */
138	chrootdir = getenv("BSDINSTALL_CHROOT");
139	if (chrootdir != NULL && chdir(chrootdir) != 0) {
140		snprintf(error, sizeof(error),
141		    "Could not change to directory %s: %s\n",
142		    chrootdir, strerror(errno));
143		dialog_msgbox("Error", error, 0, 0, TRUE);
144		end_dialog();
145		return (EXIT_FAILURE);
146	}
147
148	/* Set cleanup routine for Ctrl-C action */
149	act.sa_handler = sig_int;
150	sigaction(SIGINT, &act, 0);
151
152	/*
153	 * Hand off to dpv(3)
154	 */
155	if ((config = calloc(1, config_size)) == NULL)
156		_errx(EXIT_FAILURE, "Out of memory!");
157	config->backtitle	= backtitle;
158	config->title		= title;
159	config->pprompt		= pprompt;
160	config->aprompt		= aprompt;
161	config->options		|= DPV_WIDE_MODE;
162	config->label_size	= -1;
163	config->action		= extract_files;
164	config->status_solo	=
165	    "%10lli files read @ %'9.1f files/sec.";
166	config->status_many	=
167	    "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]";
168	end_dialog();
169	retval = dpv(config, dists);
170
171	dpv_free();
172	while ((dist = dists) != NULL) {
173		dists = dist->next;
174		if (dist->path != NULL)
175			free(dist->path);
176		free(dist);
177	}
178
179	return (retval);
180}
181
182static void
183sig_int(int sig __unused)
184{
185	dpv_interrupt = TRUE;
186}
187
188/*
189 * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST
190 * if it exists, otherwise uses archive(3) to read the archive file.
191 */
192static int
193count_files(const char *file)
194{
195	static FILE *manifest = NULL;
196	char *p;
197	int file_count;
198	int retval;
199	size_t span;
200	struct archive_entry *entry;
201	char line[512];
202	char path[PATH_MAX];
203	char errormsg[PATH_MAX + 512];
204
205	if (manifest == NULL) {
206		snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
207		manifest = fopen(path, "r");
208	}
209
210	if (manifest != NULL) {
211		rewind(manifest);
212		while (fgets(line, sizeof(line), manifest) != NULL) {
213			p = &line[0];
214			span = strcspn(p, "\t") ;
215			if (span < 1 || strncmp(p, file, span) != 0)
216				continue;
217
218			/*
219			 * We're at the right manifest line. The file count is
220			 * in the third element
221			 */
222			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
223			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
224			if (span > 0) {
225				file_count = (int)strtol(p, (char **)NULL, 10);
226				if (file_count == 0 && errno == EINVAL)
227					continue;
228				return (file_count);
229			}
230		}
231	}
232
233	/*
234	 * Either no manifest, or manifest didn't mention this archive.
235	 * Use archive(3) to read the archive, counting files within.
236	 */
237	if ((archive = archive_read_new()) == NULL) {
238		snprintf(errormsg, sizeof(errormsg),
239		    "Error: %s\n", archive_error_string(NULL));
240		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
241		return (-1);
242	}
243	archive_read_support_format_all(archive);
244	archive_read_support_filter_all(archive);
245	snprintf(path, sizeof(path), "%s/%s", distdir, file);
246	retval = archive_read_open_filename(archive, path, 4096);
247	if (retval != ARCHIVE_OK) {
248		snprintf(errormsg, sizeof(errormsg),
249		    "Error while extracting %s: %s\n", file,
250		    archive_error_string(archive));
251		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
252		archive = NULL;
253		return (-1);
254	}
255
256	file_count = 0;
257	while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
258		file_count++;
259	archive_read_free(archive);
260	archive = NULL;
261
262	return (file_count);
263}
264
265static int
266extract_files(struct dpv_file_node *file, int out __unused)
267{
268	int retval;
269	struct archive_entry *entry;
270	char path[PATH_MAX];
271	char errormsg[PATH_MAX + 512];
272
273	/* Open the archive if necessary */
274	if (archive == NULL) {
275		if ((archive = archive_read_new()) == NULL) {
276			snprintf(errormsg, sizeof(errormsg),
277			    "Error: %s\n", archive_error_string(NULL));
278			dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
279			dpv_abort = 1;
280			return (-1);
281		}
282		archive_read_support_format_all(archive);
283		archive_read_support_filter_all(archive);
284		snprintf(path, sizeof(path), "%s/%s", distdir, file->path);
285		retval = archive_read_open_filename(archive, path, 4096);
286		if (retval != 0) {
287			snprintf(errormsg, sizeof(errormsg),
288			    "Error opening %s: %s\n", file->name,
289			    archive_error_string(archive));
290			dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
291			file->status = DPV_STATUS_FAILED;
292			dpv_abort = 1;
293			return (-1);
294		}
295	}
296
297	/* Read the next archive header */
298	retval = archive_read_next_header(archive, &entry);
299
300	/* If that went well, perform the extraction */
301	if (retval == ARCHIVE_OK)
302		retval = archive_read_extract(archive, entry,
303		    ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
304		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
305		    ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
306
307	/* Test for either EOF or error */
308	if (retval == ARCHIVE_EOF) {
309		archive_read_free(archive);
310		archive = NULL;
311		file->status = DPV_STATUS_DONE;
312		return (100);
313	} else if (retval != ARCHIVE_OK) {
314		snprintf(errormsg, sizeof(errormsg),
315		    "Error while extracting %s: %s\n", file->name,
316		    archive_error_string(archive));
317		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
318		file->status = DPV_STATUS_FAILED;
319		dpv_abort = 1;
320		return (-1);
321	}
322
323	dpv_overall_read++;
324	file->read++;
325
326	/* Calculate [overall] percentage of completion (if possible) */
327	if (file->length >= 0)
328		return (file->read * 100 / file->length);
329	else
330		return (-1);
331}
332