1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2011 Nathan Whitehorn
5 * Copyright (c) 2014 Devin Teske <[email protected]>
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
main(void)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
sig_int(int sig __unused)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
count_files(const char * file)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
extract_files(struct bsddialog_fileminibar * file)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