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