]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - usr.sbin/bsdinstall/distextract/distextract.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / usr.sbin / bsdinstall / distextract / distextract.c
1 /*-
2  * Copyright (c) 2011 Nathan Whitehorn
3  * Copyright (c) 2014 Devin Teske <dteske@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30
31 #include <sys/param.h>
32 #include <archive.h>
33 #include <ctype.h>
34 #include <dialog.h>
35 #include <dpv.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <limits.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43
44 /* Data to process */
45 static char *distdir = NULL;
46 static struct archive *archive = NULL;
47 static struct dpv_file_node *dists = NULL;
48
49 /* Function prototypes */
50 static void     sig_int(int sig);
51 static int      count_files(const char *file);
52 static int      extract_files(struct dpv_file_node *file, int out);
53
54 #if __FreeBSD_version <= 1000008 /* r232154: bump for libarchive update */
55 #define archive_read_support_filter_all(x) \
56         archive_read_support_compression_all(x)
57 #endif
58
59 #define _errx(...) (end_dialog(), errx(__VA_ARGS__))
60
61 int
62 main(void)
63 {
64         char *chrootdir;
65         char *distributions;
66         int retval;
67         size_t config_size = sizeof(struct dpv_config);
68         size_t file_node_size = sizeof(struct dpv_file_node);
69         size_t span;
70         struct dpv_config *config;
71         struct dpv_file_node *dist = dists;
72         static char backtitle[] = "FreeBSD Installer";
73         static char title[] = "Archive Extraction";
74         static char aprompt[] = "\n  Overall Progress:";
75         static char pprompt[] = "Extracting distribution files...\n";
76         struct sigaction act;
77         char error[PATH_MAX + 512];
78
79         if ((distributions = getenv("DISTRIBUTIONS")) == NULL)
80                 errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
81         if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)
82                 distdir = __DECONST(char *, "");
83
84         /* Initialize dialog(3) */
85         init_dialog(stdin, stdout);
86         dialog_vars.backtitle = backtitle;
87         dlg_put_backtitle();
88
89         dialog_msgbox("",
90             "Checking distribution archives.\nPlease wait...", 4, 35, FALSE);
91
92         /*
93          * Parse $DISTRIBUTIONS into dpv(3) linked-list
94          */
95         while (*distributions != '\0') {
96                 span = strcspn(distributions, "\t\n\v\f\r ");
97                 if (span < 1) { /* currently on whitespace */
98                         distributions++;
99                         continue;
100                 }
101
102                 /* Allocate a new struct for the distribution */
103                 if (dist == NULL) {
104                         if ((dist = calloc(1, file_node_size)) == NULL)
105                                 _errx(EXIT_FAILURE, "Out of memory!");
106                         dists = dist;
107                 } else {
108                         dist->next = calloc(1, file_node_size);
109                         if (dist->next == NULL)
110                                 _errx(EXIT_FAILURE, "Out of memory!");
111                         dist = dist->next;
112                 }
113
114                 /* Set path */
115                 if ((dist->path = malloc(span + 1)) == NULL)
116                         _errx(EXIT_FAILURE, "Out of memory!");
117                 snprintf(dist->path, span + 1, "%s", distributions);
118                 dist->path[span] = '\0';
119
120                 /* Set display name */
121                 dist->name = strrchr(dist->path, '/');
122                 if (dist->name == NULL)
123                         dist->name = dist->path;
124
125                 /* Set initial length in files (-1 == error) */
126                 dist->length = count_files(dist->path);
127                 if (dist->length < 0) {
128                         end_dialog();
129                         return (EXIT_FAILURE);
130                 }
131
132                 distributions += span;
133         }
134
135         /* Optionally chdir(2) into $BSDINSTALL_CHROOT */
136         chrootdir = getenv("BSDINSTALL_CHROOT");
137         if (chrootdir != NULL && chdir(chrootdir) != 0) {
138                 snprintf(error, sizeof(error),
139                     "Could not change to directory %s: %s\n",
140                     chrootdir, strerror(errno));
141                 dialog_msgbox("Error", error, 0, 0, TRUE);
142                 end_dialog();
143                 return (EXIT_FAILURE);
144         }
145
146         /* Set cleanup routine for Ctrl-C action */
147         act.sa_handler = sig_int;
148         sigaction(SIGINT, &act, 0);
149
150         /*
151          * Hand off to dpv(3)
152          */
153         if ((config = calloc(1, config_size)) == NULL)
154                 _errx(EXIT_FAILURE, "Out of memory!");
155         config->backtitle       = backtitle;
156         config->title           = title;
157         config->pprompt         = pprompt;
158         config->aprompt         = aprompt;
159         config->options         |= DPV_WIDE_MODE;
160         config->label_size      = -1;
161         config->action          = extract_files;
162         config->status_solo     =
163             "%10lli files read @ %'9.1f files/sec.";
164         config->status_many     = 
165             "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]";
166         end_dialog();
167         retval = dpv(config, dists);
168
169         dpv_free();
170         while ((dist = dists) != NULL) {
171                 dists = dist->next;
172                 if (dist->path != NULL)
173                         free(dist->path);
174                 free(dist);
175         }
176
177         return (retval);
178 }
179
180 static void
181 sig_int(int sig __unused)
182 {
183         dpv_interrupt = TRUE;
184 }
185
186 /*
187  * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST
188  * if it exists, otherwise uses archive(3) to read the archive file.
189  */
190 static int
191 count_files(const char *file)
192 {
193         static FILE *manifest = NULL;
194         char *p;
195         int file_count;
196         int retval;
197         size_t span;
198         struct archive_entry *entry;
199         char line[512];
200         char path[PATH_MAX];
201         char errormsg[PATH_MAX + 512];
202
203         if (manifest == NULL) {
204                 snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
205                 manifest = fopen(path, "r");
206         }
207
208         if (manifest != NULL) {
209                 rewind(manifest);
210                 while (fgets(line, sizeof(line), manifest) != NULL) {
211                         p = &line[0];
212                         span = strcspn(p, "\t") ;
213                         if (span < 1 || strncmp(p, file, span) != 0)
214                                 continue;
215
216                         /*
217                          * We're at the right manifest line. The file count is
218                          * in the third element
219                          */
220                         span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
221                         span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
222                         if (span > 0) {
223                                 file_count = (int)strtol(p, (char **)NULL, 10);
224                                 if (file_count == 0 && errno == EINVAL)
225                                         continue;
226                                 return (file_count);
227                         }
228                 }
229         }
230
231         /*
232          * Either no manifest, or manifest didn't mention this archive.
233          * Use archive(3) to read the archive, counting files within.
234          */
235         if ((archive = archive_read_new()) == NULL) {
236                 snprintf(errormsg, sizeof(errormsg),
237                     "Error: %s\n", archive_error_string(NULL));
238                 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
239                 return (-1);
240         }
241         archive_read_support_format_all(archive);
242         archive_read_support_filter_all(archive);
243         snprintf(path, sizeof(path), "%s/%s", distdir, file);
244         retval = archive_read_open_filename(archive, path, 4096);
245         if (retval != ARCHIVE_OK) {
246                 snprintf(errormsg, sizeof(errormsg),
247                     "Error while extracting %s: %s\n", file,
248                     archive_error_string(archive));
249                 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
250                 archive = NULL;
251                 return (-1);
252         }
253
254         file_count = 0;
255         while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
256                 file_count++;
257         archive_read_free(archive);
258         archive = NULL;
259
260         return (file_count);
261 }
262
263 static int
264 extract_files(struct dpv_file_node *file, int out __unused)
265 {
266         int retval;
267         struct archive_entry *entry;
268         char path[PATH_MAX];
269         char errormsg[PATH_MAX + 512];
270
271         /* Open the archive if necessary */
272         if (archive == NULL) {
273                 if ((archive = archive_read_new()) == NULL) {
274                         snprintf(errormsg, sizeof(errormsg),
275                             "Error: %s\n", archive_error_string(NULL));
276                         dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
277                         dpv_abort = 1;
278                         return (-1);
279                 }
280                 archive_read_support_format_all(archive);
281                 archive_read_support_filter_all(archive);
282                 snprintf(path, sizeof(path), "%s/%s", distdir, file->path);
283                 retval = archive_read_open_filename(archive, path, 4096);
284                 if (retval != 0) {
285                         snprintf(errormsg, sizeof(errormsg),
286                             "Error opening %s: %s\n", file->name,
287                             archive_error_string(archive));
288                         dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
289                         file->status = DPV_STATUS_FAILED;
290                         dpv_abort = 1;
291                         return (-1);
292                 }
293         }
294
295         /* Read the next archive header */
296         retval = archive_read_next_header(archive, &entry);
297
298         /* If that went well, perform the extraction */
299         if (retval == ARCHIVE_OK)
300                 retval = archive_read_extract(archive, entry,
301                     ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
302                     ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
303                     ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
304
305         /* Test for either EOF or error */
306         if (retval == ARCHIVE_EOF) {
307                 archive_read_free(archive);
308                 archive = NULL;
309                 file->status = DPV_STATUS_DONE;
310                 return (100);
311         } else if (retval != ARCHIVE_OK) {
312                 snprintf(errormsg, sizeof(errormsg),
313                     "Error while extracting %s: %s\n", file->name,
314                     archive_error_string(archive));
315                 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
316                 file->status = DPV_STATUS_FAILED;
317                 dpv_abort = 1;
318                 return (-1);
319         }
320
321         dpv_overall_read++;
322         file->read++;
323
324         /* Calculate [overall] percentage of completion (if possible) */
325         if (file->length >= 0)
326                 return (file->read * 100 / file->length);
327         else
328                 return (-1);
329 }