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$");
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 */
47 static char *distdir = NULL;
48 static struct archive *archive = NULL;
49 static struct dpv_file_node *dists = NULL;
50 
51 /* Function prototypes */
52 static void	sig_int(int sig);
53 static int	count_files(const char *file);
54 static int	extract_files(struct dpv_file_node *file, int out);
55 
56 #define _errx(...) (end_dialog(), errx(__VA_ARGS__))
57 
58 int
main(void)59 main(void)
60 {
61 	char *chrootdir;
62 	char *distributions;
63 	int retval;
64 	size_t config_size = sizeof(struct dpv_config);
65 	size_t file_node_size = sizeof(struct dpv_file_node);
66 	size_t span;
67 	struct dpv_config *config;
68 	struct dpv_file_node *dist = dists;
69 	static char backtitle[] = "FreeBSD Installer";
70 	static char title[] = "Archive Extraction";
71 	static char aprompt[] = "\n  Overall Progress:";
72 	static char pprompt[] = "Extracting distribution files...\n";
73 	struct sigaction act;
74 	char error[PATH_MAX + 512];
75 
76 	if ((distributions = getenv("DISTRIBUTIONS")) == NULL)
77 		errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
78 	if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)
79 		distdir = __DECONST(char *, "");
80 
81 	/* Initialize dialog(3) */
82 	init_dialog(stdin, stdout);
83 	dialog_vars.backtitle = backtitle;
84 	dlg_put_backtitle();
85 
86 	dialog_msgbox("",
87 	    "Checking distribution archives.\nPlease wait...", 4, 35, FALSE);
88 
89 	/*
90 	 * Parse $DISTRIBUTIONS into dpv(3) linked-list
91 	 */
92 	while (*distributions != '\0') {
93 		span = strcspn(distributions, "\t\n\v\f\r ");
94 		if (span < 1) { /* currently on whitespace */
95 			distributions++;
96 			continue;
97 		}
98 
99 		/* Allocate a new struct for the distribution */
100 		if (dist == NULL) {
101 			if ((dist = calloc(1, file_node_size)) == NULL)
102 				_errx(EXIT_FAILURE, "Out of memory!");
103 			dists = dist;
104 		} else {
105 			dist->next = calloc(1, file_node_size);
106 			if (dist->next == NULL)
107 				_errx(EXIT_FAILURE, "Out of memory!");
108 			dist = dist->next;
109 		}
110 
111 		/* Set path */
112 		if ((dist->path = malloc(span + 1)) == NULL)
113 			_errx(EXIT_FAILURE, "Out of memory!");
114 		snprintf(dist->path, span + 1, "%s", distributions);
115 		dist->path[span] = '\0';
116 
117 		/* Set display name */
118 		dist->name = strrchr(dist->path, '/');
119 		if (dist->name == NULL)
120 			dist->name = dist->path;
121 
122 		/* Set initial length in files (-1 == error) */
123 		dist->length = count_files(dist->path);
124 		if (dist->length < 0) {
125 			end_dialog();
126 			return (EXIT_FAILURE);
127 		}
128 
129 		distributions += span;
130 	}
131 
132 	/* Optionally chdir(2) into $BSDINSTALL_CHROOT */
133 	chrootdir = getenv("BSDINSTALL_CHROOT");
134 	if (chrootdir != NULL && chdir(chrootdir) != 0) {
135 		snprintf(error, sizeof(error),
136 		    "Could not change to directory %s: %s\n",
137 		    chrootdir, strerror(errno));
138 		dialog_msgbox("Error", error, 0, 0, TRUE);
139 		end_dialog();
140 		return (EXIT_FAILURE);
141 	}
142 
143 	/* Set cleanup routine for Ctrl-C action */
144 	act.sa_handler = sig_int;
145 	sigaction(SIGINT, &act, 0);
146 
147 	/*
148 	 * Hand off to dpv(3)
149 	 */
150 	if ((config = calloc(1, config_size)) == NULL)
151 		_errx(EXIT_FAILURE, "Out of memory!");
152 	config->backtitle	= backtitle;
153 	config->title		= title;
154 	config->pprompt		= pprompt;
155 	config->aprompt		= aprompt;
156 	config->options		|= DPV_WIDE_MODE;
157 	config->label_size	= -1;
158 	config->action		= extract_files;
159 	config->status_solo	=
160 	    "%10lli files read @ %'9.1f files/sec.";
161 	config->status_many	=
162 	    "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]";
163 	end_dialog();
164 	retval = dpv(config, dists);
165 
166 	dpv_free();
167 	while ((dist = dists) != NULL) {
168 		dists = dist->next;
169 		if (dist->path != NULL)
170 			free(dist->path);
171 		free(dist);
172 	}
173 
174 	return (retval);
175 }
176 
177 static void
sig_int(int sig __unused)178 sig_int(int sig __unused)
179 {
180 	dpv_interrupt = TRUE;
181 }
182 
183 /*
184  * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST
185  * if it exists, otherwise uses archive(3) to read the archive file.
186  */
187 static int
count_files(const char * file)188 count_files(const char *file)
189 {
190 	static FILE *manifest = NULL;
191 	char *p;
192 	int file_count;
193 	int retval;
194 	size_t span;
195 	struct archive_entry *entry;
196 	char line[512];
197 	char path[PATH_MAX];
198 	char errormsg[PATH_MAX + 512];
199 
200 	if (manifest == NULL) {
201 		snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
202 		manifest = fopen(path, "r");
203 	}
204 
205 	if (manifest != NULL) {
206 		rewind(manifest);
207 		while (fgets(line, sizeof(line), manifest) != NULL) {
208 			p = &line[0];
209 			span = strcspn(p, "\t") ;
210 			if (span < 1 || strncmp(p, file, span) != 0)
211 				continue;
212 
213 			/*
214 			 * We're at the right manifest line. The file count is
215 			 * in the third element
216 			 */
217 			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
218 			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
219 			if (span > 0) {
220 				file_count = (int)strtol(p, (char **)NULL, 10);
221 				if (file_count == 0 && errno == EINVAL)
222 					continue;
223 				return (file_count);
224 			}
225 		}
226 	}
227 
228 	/*
229 	 * Either no manifest, or manifest didn't mention this archive.
230 	 * Use archive(3) to read the archive, counting files within.
231 	 */
232 	if ((archive = archive_read_new()) == NULL) {
233 		snprintf(errormsg, sizeof(errormsg),
234 		    "Error: %s\n", archive_error_string(NULL));
235 		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
236 		return (-1);
237 	}
238 	archive_read_support_format_all(archive);
239 	archive_read_support_filter_all(archive);
240 	snprintf(path, sizeof(path), "%s/%s", distdir, file);
241 	retval = archive_read_open_filename(archive, path, 4096);
242 	if (retval != ARCHIVE_OK) {
243 		snprintf(errormsg, sizeof(errormsg),
244 		    "Error while extracting %s: %s\n", file,
245 		    archive_error_string(archive));
246 		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
247 		archive = NULL;
248 		return (-1);
249 	}
250 
251 	file_count = 0;
252 	while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
253 		file_count++;
254 	archive_read_free(archive);
255 	archive = NULL;
256 
257 	return (file_count);
258 }
259 
260 static int
extract_files(struct dpv_file_node * file,int out __unused)261 extract_files(struct dpv_file_node *file, int out __unused)
262 {
263 	int retval;
264 	struct archive_entry *entry;
265 	char path[PATH_MAX];
266 	char errormsg[PATH_MAX + 512];
267 
268 	/* Open the archive if necessary */
269 	if (archive == NULL) {
270 		if ((archive = archive_read_new()) == NULL) {
271 			snprintf(errormsg, sizeof(errormsg),
272 			    "Error: %s\n", archive_error_string(NULL));
273 			dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
274 			dpv_abort = 1;
275 			return (-1);
276 		}
277 		archive_read_support_format_all(archive);
278 		archive_read_support_filter_all(archive);
279 		snprintf(path, sizeof(path), "%s/%s", distdir, file->path);
280 		retval = archive_read_open_filename(archive, path, 4096);
281 		if (retval != 0) {
282 			snprintf(errormsg, sizeof(errormsg),
283 			    "Error opening %s: %s\n", file->name,
284 			    archive_error_string(archive));
285 			dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
286 			file->status = DPV_STATUS_FAILED;
287 			dpv_abort = 1;
288 			return (-1);
289 		}
290 	}
291 
292 	/* Read the next archive header */
293 	retval = archive_read_next_header(archive, &entry);
294 
295 	/* If that went well, perform the extraction */
296 	if (retval == ARCHIVE_OK)
297 		retval = archive_read_extract(archive, entry,
298 		    ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
299 		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
300 		    ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
301 
302 	/* Test for either EOF or error */
303 	if (retval == ARCHIVE_EOF) {
304 		archive_read_free(archive);
305 		archive = NULL;
306 		file->status = DPV_STATUS_DONE;
307 		return (100);
308 	} else if (retval != ARCHIVE_OK &&
309 	    !(retval == ARCHIVE_WARN &&
310 	    strcmp(archive_error_string(archive), "Can't restore time") == 0)) {
311 		/*
312 		 * Print any warning/error messages except inability to set
313 		 * ctime/mtime, which is not fatal, or even interesting,
314 		 * for our purposes. Would be nice if this were a libarchive
315 		 * option.
316 		 */
317 		snprintf(errormsg, sizeof(errormsg),
318 		    "Error while extracting %s: %s\n", file->name,
319 		    archive_error_string(archive));
320 		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
321 		file->status = DPV_STATUS_FAILED;
322 		dpv_abort = 1;
323 		return (-1);
324 	}
325 
326 	dpv_overall_read++;
327 	file->read++;
328 
329 	/* Calculate [overall] percentage of completion (if possible) */
330 	if (file->length >= 0)
331 		return (file->read * 100 / file->length);
332 	else
333 		return (-1);
334 }
335