2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2011 Nathan Whitehorn
5 * Copyright (c) 2014 Devin Teske <dteske@FreeBSD.org>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
33 #include <sys/param.h>
37 #include <bsddialog.h>
38 #include <bsddialog_progressview.h>
48 #include "opt_osname.h"
51 static const char *distdir = NULL;
52 static struct archive *archive = NULL;
54 /* Function prototypes */
55 static void sig_int(int sig);
56 static int count_files(const char *file);
57 static int extract_files(struct bsddialog_fileminibar *file);
59 #define _errx(...) (bsddialog_end(), errx(__VA_ARGS__))
66 char *distribs, *distrib;
68 size_t minibar_size = sizeof(struct bsddialog_fileminibar);
69 unsigned int nminibars;
70 struct bsddialog_fileminibar *dists;
71 struct bsddialog_progviewconf pvconf;
72 struct bsddialog_conf conf;
74 char error[PATH_MAX + 512];
76 if ((distributions = getenv("DISTRIBUTIONS")) == NULL)
77 errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
78 if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)
80 if ((distribs = strdup(distributions)) == NULL)
81 errx(EXIT_FAILURE, "memory error");
83 if (bsddialog_init() == BSDDIALOG_ERROR)
84 errx(EXIT_FAILURE, "Error libbsdialog: %s",
85 bsddialog_geterror());
86 bsddialog_initconf(&conf);
87 bsddialog_backtitle(&conf, OSNAME " Installer");
88 bsddialog_infobox(&conf,
89 "Checking distribution archives.\nPlease wait...", 4, 35);
91 /* Parse $DISTRIBUTIONS */
94 while ((distrib = strsep(&distribs, "\t\n\v\f\r ")) != NULL) {
95 if (strlen(distrib) == 0)
98 /* Allocate a new struct for the distribution */
99 dists = realloc(dists, (nminibars + 1) * minibar_size);
101 _errx(EXIT_FAILURE, "Out of memory!");
104 dists[nminibars].path = distrib;
106 /* Set mini bar label */
107 dists[nminibars].label = strrchr(dists[nminibars].path, '/');
108 if (dists[nminibars].label == NULL)
109 dists[nminibars].label = dists[nminibars].path;
111 /* Set initial length in files (-1 == error) */
112 dists[nminibars].size = count_files(dists[nminibars].path);
113 if (dists[nminibars].size < 0) {
115 return (EXIT_FAILURE);
118 /* Set initial status and implicitly miniperc to pending */
119 dists[nminibars].status = BSDDIALOG_MG_PENDING;
121 /* Set initial read */
122 dists[nminibars].read = 0;
127 /* Optionally chdir(2) into $BSDINSTALL_CHROOT */
128 chrootdir = getenv("BSDINSTALL_CHROOT");
129 if (chrootdir != NULL && chdir(chrootdir) != 0) {
130 snprintf(error, sizeof(error),
131 "Could not change to directory %s: %s\n",
132 chrootdir, strerror(errno));
133 conf.title = "Error";
134 bsddialog_msgbox(&conf, error, 0, 0);
136 return (EXIT_FAILURE);
139 /* Set cleanup routine for Ctrl-C action */
140 act.sa_handler = sig_int;
141 sigaction(SIGINT, &act, 0);
143 conf.title = "Archive Extraction";
144 conf.auto_minwidth = 40;
145 pvconf.callback = extract_files;
147 pvconf.fmtbottomstr = "%10lli files read @ %'9.1f files/sec.";
148 bsddialog_total_progview = 0;
149 bsddialog_interruptprogview = bsddialog_abortprogview = false;
150 retval = bsddialog_progressview(&conf,
151 "\nExtracting distribution files...\n", 0, 0,
152 &pvconf, nminibars, dists);
154 if (retval == BSDDIALOG_ERROR) {
155 fprintf(stderr, "progressview error: %s\n",
156 bsddialog_geterror());
168 sig_int(int sig __unused)
170 bsddialog_interruptprogview = true;
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.
178 count_files(const char *file)
180 static FILE *manifest = NULL;
185 struct archive_entry *entry;
188 char errormsg[PATH_MAX + 512];
189 struct bsddialog_conf conf;
191 if (manifest == NULL) {
192 snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
193 manifest = fopen(path, "r");
196 if (manifest != NULL) {
198 while (fgets(line, sizeof(line), manifest) != NULL) {
200 span = strcspn(p, "\t") ;
201 if (span < 1 || strncmp(p, file, span) != 0)
205 * We're at the right manifest line. The file count is
206 * in the third element
208 span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
209 span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
211 file_count = (int)strtol(p, (char **)NULL, 10);
212 if (file_count == 0 && errno == EINVAL)
220 * Either no manifest, or manifest didn't mention this archive.
221 * Use archive(3) to read the archive, counting files within.
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 = "Extract Error";
228 bsddialog_msgbox(&conf, errormsg, 0, 0);
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 = "Extract Error";
240 bsddialog_msgbox(&conf, errormsg, 0, 0);
246 while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
248 archive_read_free(archive);
255 extract_files(struct bsddialog_fileminibar *file)
258 struct archive_entry *entry;
260 char errormsg[PATH_MAX + 512];
261 struct bsddialog_conf conf;
263 bsddialog_initconf(&conf);
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 = "Extract Error";
271 bsddialog_msgbox(&conf, errormsg, 0, 0);
272 bsddialog_abortprogview = true;
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);
280 snprintf(errormsg, sizeof(errormsg),
281 "Error opening %s: %s\n", file->label,
282 archive_error_string(archive));
283 conf.title = "Extract Error";
284 bsddialog_msgbox(&conf, errormsg, 0, 0);
285 file->status = BSDDIALOG_MG_FAILED;
286 bsddialog_abortprogview = true;
291 /* Read the next archive header */
292 retval = archive_read_next_header(archive, &entry);
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);
301 /* Test for either EOF or error */
302 if (retval == ARCHIVE_EOF) {
303 archive_read_free(archive);
305 file->status = BSDDIALOG_MG_DONE; /*Done*/;
307 } else if (retval != ARCHIVE_OK &&
308 !(retval == ARCHIVE_WARN &&
309 strcmp(archive_error_string(archive), "Can't restore time") == 0)) {
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
316 snprintf(errormsg, sizeof(errormsg),
317 "Error while extracting %s: %s\n", file->label,
318 archive_error_string(archive));
319 conf.title = "Extract Error";
320 bsddialog_msgbox(&conf, errormsg, 0, 0);
321 file->status = BSDDIALOG_MG_FAILED; /* Failed */
322 bsddialog_abortprogview = true;
326 bsddialog_total_progview++;
329 /* Calculate [overall] percentage of completion (if possible) */
331 return (file->read * 100 / file->size);