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