xref: /freebsd/usr.sbin/bsdinstall/distextract/distextract.c (revision 734e82fe33aa764367791a7d603b383996c6b40b)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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 #include <sys/param.h>
32 
33 #include <archive.h>
34 #include <ctype.h>
35 #include <bsddialog.h>
36 #include <bsddialog_progressview.h>
37 #include <err.h>
38 #include <errno.h>
39 #include <limits.h>
40 #include <signal.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 #include "opt_osname.h"
47 
48 /* Data to process */
49 static const char *distdir = NULL;
50 static struct archive *archive = NULL;
51 
52 /* Function prototypes */
53 static void	sig_int(int sig);
54 static int	count_files(const char *file);
55 static int	extract_files(struct bsddialog_fileminibar *file);
56 
57 #define _errx(...) (bsddialog_end(), errx(__VA_ARGS__))
58 
59 int
60 main(void)
61 {
62 	char *chrootdir;
63 	char *distributions;
64 	char *distribs, *distrib;
65 	int retval;
66 	size_t minibar_size = sizeof(struct bsddialog_fileminibar);
67 	unsigned int nminibars;
68 	struct bsddialog_fileminibar *dists;
69 	struct bsddialog_progviewconf pvconf;
70 	struct bsddialog_conf conf;
71 	struct sigaction act;
72 	char error[PATH_MAX + 512];
73 
74 	if ((distributions = getenv("DISTRIBUTIONS")) == NULL)
75 		errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
76 	if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)
77 		distdir = "";
78 	if ((distribs = strdup(distributions)) == NULL)
79 		errx(EXIT_FAILURE, "memory error");
80 
81 	if (bsddialog_init() == BSDDIALOG_ERROR)
82 		errx(EXIT_FAILURE, "Error libbsdialog: %s",
83 		    bsddialog_geterror());
84 	bsddialog_initconf(&conf);
85 	bsddialog_backtitle(&conf, OSNAME " Installer");
86 	bsddialog_infobox(&conf,
87 	    "Checking distribution archives.\nPlease wait...", 4, 35);
88 
89 	/* Parse $DISTRIBUTIONS */
90 	nminibars = 0;
91 	dists = NULL;
92 	while ((distrib = strsep(&distribs, "\t\n\v\f\r ")) != NULL) {
93 		if (strlen(distrib) == 0)
94 			continue;
95 
96 		/* Allocate a new struct for the distribution */
97 		dists = realloc(dists, (nminibars + 1) * minibar_size);
98 		if (dists == NULL)
99 			_errx(EXIT_FAILURE, "Out of memory!");
100 
101 		/* Set file path */
102 		dists[nminibars].path = distrib;
103 
104 		/* Set mini bar label */
105 		dists[nminibars].label = strrchr(dists[nminibars].path, '/');
106 		if (dists[nminibars].label == NULL)
107 			dists[nminibars].label = dists[nminibars].path;
108 
109 		/* Set initial length in files (-1 == error) */
110 		dists[nminibars].size = count_files(dists[nminibars].path);
111 		if (dists[nminibars].size < 0) {
112 			bsddialog_end();
113 			return (EXIT_FAILURE);
114 		}
115 
116 		/* Set initial status and implicitly miniperc to pending */
117 		dists[nminibars].status = BSDDIALOG_MG_PENDING;
118 
119 		/* Set initial read */
120 		dists[nminibars].read = 0;
121 
122 		nminibars += 1;
123 	}
124 
125 	/* Optionally chdir(2) into $BSDINSTALL_CHROOT */
126 	chrootdir = getenv("BSDINSTALL_CHROOT");
127 	if (chrootdir != NULL && chdir(chrootdir) != 0) {
128 		snprintf(error, sizeof(error),
129 		    "Could not change to directory %s: %s\n",
130 		    chrootdir, strerror(errno));
131 		conf.title = "Error";
132 		bsddialog_msgbox(&conf, error, 0, 0);
133 		bsddialog_end();
134 		return (EXIT_FAILURE);
135 	}
136 
137 	/* Set cleanup routine for Ctrl-C action */
138 	act.sa_handler = sig_int;
139 	sigaction(SIGINT, &act, 0);
140 
141 	conf.title = "Archive Extraction";
142 	conf.auto_minwidth = 40;
143 	pvconf.callback	= extract_files;
144 	pvconf.refresh = 1;
145 	pvconf.fmtbottomstr = "%10lli files read @ %'9.1f files/sec.";
146 	bsddialog_total_progview = 0;
147 	bsddialog_interruptprogview = bsddialog_abortprogview = false;
148 	retval = bsddialog_progressview(&conf,
149 	    "\nExtracting distribution files...\n", 0, 0,
150 	    &pvconf, nminibars, dists);
151 
152 	if (retval == BSDDIALOG_ERROR) {
153 		fprintf(stderr, "progressview error: %s\n",
154 		    bsddialog_geterror());
155 	}
156 
157 	bsddialog_end();
158 
159 	free(distribs);
160 	free(dists);
161 
162 	return (retval);
163 }
164 
165 static void
166 sig_int(int sig __unused)
167 {
168 	bsddialog_interruptprogview = true;
169 }
170 
171 /*
172  * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST
173  * if it exists, otherwise uses archive(3) to read the archive file.
174  */
175 static int
176 count_files(const char *file)
177 {
178 	static FILE *manifest = NULL;
179 	char *p;
180 	int file_count;
181 	int retval;
182 	size_t span;
183 	struct archive_entry *entry;
184 	char line[512];
185 	char path[PATH_MAX];
186 	char errormsg[PATH_MAX + 512];
187 	struct bsddialog_conf conf;
188 
189 	if (manifest == NULL) {
190 		snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
191 		manifest = fopen(path, "r");
192 	}
193 
194 	if (manifest != NULL) {
195 		rewind(manifest);
196 		while (fgets(line, sizeof(line), manifest) != NULL) {
197 			p = &line[0];
198 			span = strcspn(p, "\t") ;
199 			if (span < 1 || strncmp(p, file, span) != 0)
200 				continue;
201 
202 			/*
203 			 * We're at the right manifest line. The file count is
204 			 * in the third element
205 			 */
206 			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
207 			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
208 			if (span > 0) {
209 				file_count = (int)strtol(p, (char **)NULL, 10);
210 				if (file_count == 0 && errno == EINVAL)
211 					continue;
212 				return (file_count);
213 			}
214 		}
215 	}
216 
217 	/*
218 	 * Either no manifest, or manifest didn't mention this archive.
219 	 * Use archive(3) to read the archive, counting files within.
220 	 */
221 	bsddialog_initconf(&conf);
222 	if ((archive = archive_read_new()) == NULL) {
223 		snprintf(errormsg, sizeof(errormsg),
224 		    "Error: %s\n", archive_error_string(NULL));
225 		conf.title = "Extract Error";
226 		bsddialog_msgbox(&conf, errormsg, 0, 0);
227 		return (-1);
228 	}
229 	archive_read_support_format_all(archive);
230 	archive_read_support_filter_all(archive);
231 	snprintf(path, sizeof(path), "%s/%s", distdir, file);
232 	retval = archive_read_open_filename(archive, path, 4096);
233 	if (retval != ARCHIVE_OK) {
234 		snprintf(errormsg, sizeof(errormsg),
235 		    "Error while extracting %s: %s\n", file,
236 		    archive_error_string(archive));
237 		conf.title = "Extract Error";
238 		bsddialog_msgbox(&conf, errormsg, 0, 0);
239 		archive = NULL;
240 		return (-1);
241 	}
242 
243 	file_count = 0;
244 	while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
245 		file_count++;
246 	archive_read_free(archive);
247 	archive = NULL;
248 
249 	return (file_count);
250 }
251 
252 static int
253 extract_files(struct bsddialog_fileminibar *file)
254 {
255 	int retval;
256 	struct archive_entry *entry;
257 	char path[PATH_MAX];
258 	char errormsg[PATH_MAX + 512];
259 	struct bsddialog_conf conf;
260 
261 	bsddialog_initconf(&conf);
262 
263 	/* Open the archive if necessary */
264 	if (archive == NULL) {
265 		if ((archive = archive_read_new()) == NULL) {
266 			snprintf(errormsg, sizeof(errormsg),
267 			    "Error: %s\n", archive_error_string(NULL));
268 			conf.title = "Extract Error";
269 			bsddialog_msgbox(&conf, errormsg, 0, 0);
270 			bsddialog_abortprogview = true;
271 			return (-1);
272 		}
273 		archive_read_support_format_all(archive);
274 		archive_read_support_filter_all(archive);
275 		snprintf(path, sizeof(path), "%s/%s", distdir, file->path);
276 		retval = archive_read_open_filename(archive, path, 4096);
277 		if (retval != 0) {
278 			snprintf(errormsg, sizeof(errormsg),
279 			    "Error opening %s: %s\n", file->label,
280 			    archive_error_string(archive));
281 			conf.title = "Extract Error";
282 			bsddialog_msgbox(&conf, errormsg, 0, 0);
283 			file->status = BSDDIALOG_MG_FAILED;
284 			bsddialog_abortprogview = true;
285 			return (-1);
286 		}
287 	}
288 
289 	/* Read the next archive header */
290 	retval = archive_read_next_header(archive, &entry);
291 
292 	/* If that went well, perform the extraction */
293 	if (retval == ARCHIVE_OK)
294 		retval = archive_read_extract(archive, entry,
295 		    ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
296 		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
297 		    ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
298 
299 	/* Test for either EOF or error */
300 	if (retval == ARCHIVE_EOF) {
301 		archive_read_free(archive);
302 		archive = NULL;
303 		file->status = BSDDIALOG_MG_DONE; /*Done*/;
304 		return (100);
305 	} else if (retval != ARCHIVE_OK &&
306 	    !(retval == ARCHIVE_WARN &&
307 	    strcmp(archive_error_string(archive), "Can't restore time") == 0)) {
308 		/*
309 		 * Print any warning/error messages except inability to set
310 		 * ctime/mtime, which is not fatal, or even interesting,
311 		 * for our purposes. Would be nice if this were a libarchive
312 		 * option.
313 		 */
314 		snprintf(errormsg, sizeof(errormsg),
315 		    "Error while extracting %s: %s\n", file->label,
316 		    archive_error_string(archive));
317 		conf.title = "Extract Error";
318 		bsddialog_msgbox(&conf, errormsg, 0, 0);
319 		file->status = BSDDIALOG_MG_FAILED; /* Failed */
320 		bsddialog_abortprogview = true;
321 		return (-1);
322 	}
323 
324 	bsddialog_total_progview++;
325 	file->read++;
326 
327 	/* Calculate [overall] percentage of completion (if possible) */
328 	if (file->size >= 0)
329 		return (file->read * 100 / file->size);
330 	else
331 		return (-1);
332 }
333