xref: /freebsd/usr.sbin/bsdinstall/distextract/distextract.c (revision 0e97acdf58fe27b09c4824a474b0344daf997c5f)
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 <err.h>
36 #include <errno.h>
37 #include <limits.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 /* Data to process */
44 static char *distdir = NULL;
45 struct file_node {
46 	char	*path;
47 	char	*name;
48 	int	length;
49 	struct file_node *next;
50 };
51 static struct file_node *dists = NULL;
52 
53 /* Function prototypes */
54 static int	count_files(const char *file);
55 static int	extract_files(int nfiles, struct file_node *files);
56 
57 #if __FreeBSD_version <= 1000008 /* r232154: bump for libarchive update */
58 #define archive_read_support_filter_all(x) \
59 	archive_read_support_compression_all(x)
60 #endif
61 
62 #define _errx(...) (end_dialog(), errx(__VA_ARGS__))
63 
64 int
65 main(void)
66 {
67 	char *chrootdir;
68 	char *distributions;
69 	int ndists = 0;
70 	int retval;
71 	size_t file_node_size = sizeof(struct file_node);
72 	size_t span;
73 	struct file_node *dist = dists;
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 = __DECONST(char *, "FreeBSD Installer");
84 	dlg_put_backtitle();
85 
86 	dialog_msgbox("",
87 	    "Checking distribution archives.\nPlease wait...", 4, 35, FALSE);
88 
89 	/*
90 	 * Parse $DISTRIBUTIONS into 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 		ndists++;
99 
100 		/* Allocate a new struct for the distribution */
101 		if (dist == NULL) {
102 			if ((dist = calloc(1, file_node_size)) == NULL)
103 				_errx(EXIT_FAILURE, "Out of memory!");
104 			dists = dist;
105 		} else {
106 			dist->next = calloc(1, file_node_size);
107 			if (dist->next == NULL)
108 				_errx(EXIT_FAILURE, "Out of memory!");
109 			dist = dist->next;
110 		}
111 
112 		/* Set path */
113 		if ((dist->path = malloc(span + 1)) == NULL)
114 			_errx(EXIT_FAILURE, "Out of memory!");
115 		snprintf(dist->path, span + 1, "%s", distributions);
116 		dist->path[span] = '\0';
117 
118 		/* Set display name */
119 		dist->name = strrchr(dist->path, '/');
120 		if (dist->name == NULL)
121 			dist->name = dist->path;
122 
123 		/* Set initial length in files (-1 == error) */
124 		dist->length = count_files(dist->path);
125 		if (dist->length < 0) {
126 			end_dialog();
127 			return (EXIT_FAILURE);
128 		}
129 
130 		distributions += span;
131 	}
132 
133 	/* Optionally chdir(2) into $BSDINSTALL_CHROOT */
134 	chrootdir = getenv("BSDINSTALL_CHROOT");
135 	if (chrootdir != NULL && chdir(chrootdir) != 0) {
136 		snprintf(error, sizeof(error),
137 		    "Could not change to directory %s: %s\n",
138 		    chrootdir, strerror(errno));
139 		dialog_msgbox("Error", error, 0, 0, TRUE);
140 		end_dialog();
141 		return (EXIT_FAILURE);
142 	}
143 
144 	retval = extract_files(ndists, dists);
145 
146 	end_dialog();
147 
148 	while ((dist = dists) != NULL) {
149 		dists = dist->next;
150 		if (dist->path != NULL)
151 			free(dist->path);
152 		free(dist);
153 	}
154 
155 	return (retval);
156 }
157 
158 /*
159  * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST
160  * if it exists, otherwise uses archive(3) to read the archive file.
161  */
162 static int
163 count_files(const char *file)
164 {
165 	static FILE *manifest = NULL;
166 	char *p;
167 	int file_count;
168 	int retval;
169 	size_t span;
170 	struct archive *archive;
171 	struct archive_entry *entry;
172 	char line[512];
173 	char path[PATH_MAX];
174 	char errormsg[PATH_MAX + 512];
175 
176 	if (manifest == NULL) {
177 		snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
178 		manifest = fopen(path, "r");
179 	}
180 
181 	if (manifest != NULL) {
182 		rewind(manifest);
183 		while (fgets(line, sizeof(line), manifest) != NULL) {
184 			p = &line[0];
185 			span = strcspn(p, "\t") ;
186 			if (span < 1 || strncmp(p, file, span) != 0)
187 				continue;
188 
189 			/*
190 			 * We're at the right manifest line. The file count is
191 			 * in the third element
192 			 */
193 			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
194 			span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
195 			if (span > 0) {
196 				file_count = (int)strtol(p, (char **)NULL, 10);
197 				if (file_count == 0 && errno == EINVAL)
198 					continue;
199 				return (file_count);
200 			}
201 		}
202 	}
203 
204 	/*
205 	 * Either no manifest, or manifest didn't mention this archive.
206 	 * Use archive(3) to read the archive, counting files within.
207 	 */
208 	if ((archive = archive_read_new()) == NULL) {
209 		snprintf(errormsg, sizeof(errormsg),
210 		    "Error: %s\n", archive_error_string(NULL));
211 		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
212 		return (-1);
213 	}
214 	archive_read_support_format_all(archive);
215 	archive_read_support_filter_all(archive);
216 	snprintf(path, sizeof(path), "%s/%s", distdir, file);
217 	retval = archive_read_open_filename(archive, path, 4096);
218 	if (retval != ARCHIVE_OK) {
219 		snprintf(errormsg, sizeof(errormsg),
220 		    "Error while extracting %s: %s\n", file,
221 		    archive_error_string(archive));
222 		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
223 		return (-1);
224 	}
225 
226 	file_count = 0;
227 	while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
228 		file_count++;
229 	archive_read_free(archive);
230 
231 	return (file_count);
232 }
233 
234 static int
235 extract_files(int nfiles, struct file_node *files)
236 {
237 	int archive_file;
238 	int archive_files[nfiles];
239 	int current_files = 0;
240 	int i;
241 	int last_progress;
242 	int progress = 0;
243 	int retval;
244 	int total_files = 0;
245 	struct archive *archive;
246 	struct archive_entry *entry;
247 	struct file_node *file;
248 	char status[8];
249 	static char title[] = "Archive Extraction";
250 	static char pprompt[] = "Extracting distribution files...\n";
251 	char path[PATH_MAX];
252 	char errormsg[PATH_MAX + 512];
253 	const char *items[nfiles*2];
254 
255 	/* Make the transfer list for dialog */
256 	i = 0;
257 	for (file = files; file != NULL; file = file->next) {
258 		items[i*2] = file->name;
259 		items[i*2 + 1] = "Pending";
260 		archive_files[i] = file->length;
261 
262 		total_files += file->length;
263 		i++;
264 	}
265 
266 	i = 0;
267 	for (file = files; file != NULL; file = file->next) {
268 		if ((archive = archive_read_new()) == NULL) {
269 			snprintf(errormsg, sizeof(errormsg),
270 			    "Error: %s\n", archive_error_string(NULL));
271 			dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
272 			return (EXIT_FAILURE);
273 		}
274 		archive_read_support_format_all(archive);
275 		archive_read_support_filter_all(archive);
276 		snprintf(path, sizeof(path), "%s/%s", distdir, file->path);
277 		retval = archive_read_open_filename(archive, path, 4096);
278 		if (retval != 0) {
279 			snprintf(errormsg, sizeof(errormsg),
280 			    "Error opening %s: %s\n", file->name,
281 			    archive_error_string(archive));
282 			dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
283 			return (EXIT_FAILURE);
284 		}
285 
286 		items[i*2 + 1] = "In Progress";
287 		archive_file = 0;
288 
289 		dialog_mixedgauge(title, pprompt, 0, 0, progress, nfiles,
290 		    __DECONST(char **, items));
291 
292 		while ((retval = archive_read_next_header(archive, &entry)) ==
293 		    ARCHIVE_OK) {
294 			last_progress = progress;
295 			progress = (current_files*100)/total_files;
296 
297 			snprintf(status, sizeof(status), "-%d",
298 			    (archive_file*100)/archive_files[i]);
299 			items[i*2 + 1] = status;
300 
301 			if (progress > last_progress)
302 				dialog_mixedgauge(title, pprompt, 0, 0,
303 				    progress, nfiles,
304 				    __DECONST(char **, items));
305 
306 			retval = archive_read_extract(archive, entry,
307 			    ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
308 			    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
309 			    ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
310 
311 			if (retval != ARCHIVE_OK)
312 				break;
313 
314 			archive_file++;
315 			current_files++;
316 		}
317 
318 		items[i*2 + 1] = "Done";
319 
320 		if (retval != ARCHIVE_EOF) {
321 			snprintf(errormsg, sizeof(errormsg),
322 			    "Error while extracting %s: %s\n", items[i*2],
323 			    archive_error_string(archive));
324 			items[i*2 + 1] = "Failed";
325 			dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
326 			return (retval);
327 		}
328 
329 		progress = (current_files*100)/total_files;
330 		dialog_mixedgauge(title, pprompt, 0, 0, progress, nfiles,
331 		    __DECONST(char **, items));
332 
333 		archive_read_free(archive);
334 		i++;
335 	}
336 
337 	return (EXIT_SUCCESS);
338 }
339