]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/bsdinstall/distextract/distextract.c
Import device-tree files from Linux 6.2
[FreeBSD/FreeBSD.git] / usr.sbin / bsdinstall / distextract / distextract.c
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/cdefs.h>
31 __FBSDID("$FreeBSD$");
32
33 #include <sys/param.h>
34
35 #include <archive.h>
36 #include <ctype.h>
37 #include <bsddialog.h>
38 #include <bsddialog_progressview.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <limits.h>
42 #include <signal.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47
48 #include "opt_osname.h"
49
50 /* Data to process */
51 static const char *distdir = NULL;
52 static struct archive *archive = NULL;
53
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);
58
59 #define _errx(...) (bsddialog_end(), errx(__VA_ARGS__))
60
61 int
62 main(void)
63 {
64         char *chrootdir;
65         char *distributions;
66         char *distribs, *distrib;
67         int retval;
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;
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 = "";
80         if ((distribs = strdup(distributions)) == NULL)
81                 errx(EXIT_FAILURE, "memory error");
82
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);
90
91         /* Parse $DISTRIBUTIONS */
92         nminibars = 0;
93         dists = NULL;
94         while ((distrib = strsep(&distribs, "\t\n\v\f\r ")) != NULL) {
95                 if (strlen(distrib) == 0)
96                         continue;
97
98                 /* Allocate a new struct for the distribution */
99                 dists = realloc(dists, (nminibars + 1) * minibar_size);
100                 if (dists == NULL)
101                         _errx(EXIT_FAILURE, "Out of memory!");
102
103                 /* Set file path */
104                 dists[nminibars].path = distrib;
105
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;
110
111                 /* Set initial length in files (-1 == error) */
112                 dists[nminibars].size = count_files(dists[nminibars].path);
113                 if (dists[nminibars].size < 0) {
114                         bsddialog_end();
115                         return (EXIT_FAILURE);
116                 }
117
118                 /* Set initial status and implicitly miniperc to pending */
119                 dists[nminibars].status = BSDDIALOG_MG_PENDING;
120
121                 /* Set initial read */
122                 dists[nminibars].read = 0;
123
124                 nminibars += 1;
125         }
126
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);
135                 bsddialog_end();
136                 return (EXIT_FAILURE);
137         }
138
139         /* Set cleanup routine for Ctrl-C action */
140         act.sa_handler = sig_int;
141         sigaction(SIGINT, &act, 0);
142
143         conf.title = "Archive Extraction";
144         conf.auto_minwidth = 40;
145         pvconf.callback = extract_files;
146         pvconf.refresh = 1;
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);
153
154         if (retval == BSDDIALOG_ERROR) {
155                 fprintf(stderr, "progressview error: %s\n",
156                     bsddialog_geterror());
157         }
158
159         bsddialog_end();
160
161         free(distribs);
162         free(dists);
163
164         return (retval);
165 }
166
167 static void
168 sig_int(int sig __unused)
169 {
170         bsddialog_interruptprogview = true;
171 }
172
173 /*
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.
176  */
177 static int
178 count_files(const char *file)
179 {
180         static FILE *manifest = NULL;
181         char *p;
182         int file_count;
183         int retval;
184         size_t span;
185         struct archive_entry *entry;
186         char line[512];
187         char path[PATH_MAX];
188         char errormsg[PATH_MAX + 512];
189         struct bsddialog_conf conf;
190
191         if (manifest == NULL) {
192                 snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
193                 manifest = fopen(path, "r");
194         }
195
196         if (manifest != NULL) {
197                 rewind(manifest);
198                 while (fgets(line, sizeof(line), manifest) != NULL) {
199                         p = &line[0];
200                         span = strcspn(p, "\t") ;
201                         if (span < 1 || strncmp(p, file, span) != 0)
202                                 continue;
203
204                         /*
205                          * We're at the right manifest line. The file count is
206                          * in the third element
207                          */
208                         span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
209                         span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
210                         if (span > 0) {
211                                 file_count = (int)strtol(p, (char **)NULL, 10);
212                                 if (file_count == 0 && errno == EINVAL)
213                                         continue;
214                                 return (file_count);
215                         }
216                 }
217         }
218
219         /*
220          * Either no manifest, or manifest didn't mention this archive.
221          * Use archive(3) to read the archive, counting files within.
222          */
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);
229                 return (-1);
230         }
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);
241                 archive = NULL;
242                 return (-1);
243         }
244
245         file_count = 0;
246         while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
247                 file_count++;
248         archive_read_free(archive);
249         archive = NULL;
250
251         return (file_count);
252 }
253
254 static int
255 extract_files(struct bsddialog_fileminibar *file)
256 {
257         int retval;
258         struct archive_entry *entry;
259         char path[PATH_MAX];
260         char errormsg[PATH_MAX + 512];
261         struct bsddialog_conf conf;
262
263         bsddialog_initconf(&conf);
264
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;
273                         return (-1);
274                 }
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);
279                 if (retval != 0) {
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;
287                         return (-1);
288                 }
289         }
290
291         /* Read the next archive header */
292         retval = archive_read_next_header(archive, &entry);
293
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);
300
301         /* Test for either EOF or error */
302         if (retval == ARCHIVE_EOF) {
303                 archive_read_free(archive);
304                 archive = NULL;
305                 file->status = BSDDIALOG_MG_DONE; /*Done*/;
306                 return (100);
307         } else if (retval != ARCHIVE_OK &&
308             !(retval == ARCHIVE_WARN &&
309             strcmp(archive_error_string(archive), "Can't restore time") == 0)) {
310                 /*
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
314                  * option.
315                  */
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;
323                 return (-1);
324         }
325
326         bsddialog_total_progview++;
327         file->read++;
328
329         /* Calculate [overall] percentage of completion (if possible) */
330         if (file->size >= 0)
331                 return (file->read * 100 / file->size);
332         else
333                 return (-1);
334 }