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