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
main(void)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
sig_int(int sig __unused)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
count_files(const char * file)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
extract_files(struct bsddialog_fileminibar * file)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