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