1/*-
2 * Copyright (c) 2011 Nathan Whitehorn
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 */
28
29#include <sys/param.h>
30#include <stdio.h>
31#include <errno.h>
32#include <limits.h>
33#include <archive.h>
34#include <dialog.h>
35
36static int extract_files(int nfiles, const char **files);
37
38int
39main(void)
40{
41	char *diststring;
42	const char **dists;
43	int i, retval, ndists = 0;
44
45	if (getenv("DISTRIBUTIONS") == NULL) {
46		fprintf(stderr, "DISTRIBUTIONS variable is not set\n");
47		return (1);
48	}
49
50	diststring = strdup(getenv("DISTRIBUTIONS"));
51	for (i = 0; diststring[i] != 0; i++)
52		if (isspace(diststring[i]) && !isspace(diststring[i+1]))
53			ndists++;
54	ndists++; /* Last one */
55
56	dists = calloc(ndists, sizeof(const char *));
57	if (dists == NULL) {
58		fprintf(stderr, "Out of memory!\n");
59		free(diststring);
60		return (1);
61	}
62
63	for (i = 0; i < ndists; i++)
64		dists[i] = strsep(&diststring, " \t");
65
66	init_dialog(stdin, stdout);
67	dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer");
68	dlg_put_backtitle();
69
70	if (chdir(getenv("BSDINSTALL_CHROOT")) != 0) {
71		char error[512];
72		sprintf(error, "Could could change to directory %s: %s\n",
73		    getenv("BSDINSTALL_DISTDIR"), strerror(errno));
74		dialog_msgbox("Error", error, 0, 0, TRUE);
75		end_dialog();
76		return (1);
77	}
78
79	retval = extract_files(ndists, dists);
80
81	end_dialog();
82
83	free(diststring);
84	free(dists);
85
86	return (retval);
87}
88
89static int
90count_files(const char *file)
91{
92	struct archive *archive;
93	struct archive_entry *entry;
94	static FILE *manifest = NULL;
95	char path[MAXPATHLEN];
96	char errormsg[512];
97	int file_count, err;
98
99	if (manifest == NULL) {
100		sprintf(path, "%s/MANIFEST", getenv("BSDINSTALL_DISTDIR"));
101		manifest = fopen(path, "r");
102	}
103
104	if (manifest != NULL) {
105		char line[512];
106		char *tok1, *tok2;
107
108		rewind(manifest);
109		while (fgets(line, sizeof(line), manifest) != NULL) {
110			tok2 = line;
111			tok1 = strsep(&tok2, "\t");
112			if (tok1 == NULL || strcmp(tok1, file) != 0)
113				continue;
114
115			/*
116			 * We're at the right manifest line. The file count is
117			 * in the third element
118			 */
119			tok1 = strsep(&tok2, "\t");
120			tok1 = strsep(&tok2, "\t");
121			if (tok1 != NULL)
122				return atoi(tok1);
123		}
124	}
125
126	/* Either we didn't have a manifest, or this archive wasn't there */
127	archive = archive_read_new();
128	archive_read_support_format_all(archive);
129	archive_read_support_filter_all(archive);
130	sprintf(path, "%s/%s", getenv("BSDINSTALL_DISTDIR"), file);
131	err = archive_read_open_filename(archive, path, 4096);
132	if (err != ARCHIVE_OK) {
133		snprintf(errormsg, sizeof(errormsg),
134		    "Error while extracting %s: %s\n", file,
135		    archive_error_string(archive));
136		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
137		return (-1);
138	}
139
140	file_count = 0;
141	while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
142		file_count++;
143	archive_read_free(archive);
144
145	return (file_count);
146}
147
148static int
149extract_files(int nfiles, const char **files)
150{
151	const char *items[nfiles*2];
152	char path[PATH_MAX];
153	int archive_files[nfiles];
154	int total_files, current_files, archive_file;
155	struct archive *archive;
156	struct archive_entry *entry;
157	char errormsg[512];
158	char status[8];
159	int i, err, progress, last_progress;
160
161	err = 0;
162	progress = 0;
163
164	/* Make the transfer list for dialog */
165	for (i = 0; i < nfiles; i++) {
166		items[i*2] = strrchr(files[i], '/');
167		if (items[i*2] != NULL)
168			items[i*2]++;
169		else
170			items[i*2] = files[i];
171		items[i*2 + 1] = "Pending";
172	}
173
174	dialog_msgbox("",
175	    "Checking distribution archives.\nPlease wait...", 0, 0, FALSE);
176
177	/* Count all the files */
178	total_files = 0;
179	for (i = 0; i < nfiles; i++) {
180		archive_files[i] = count_files(files[i]);
181		if (archive_files[i] < 0)
182			return (-1);
183		total_files += archive_files[i];
184	}
185
186	current_files = 0;
187
188	for (i = 0; i < nfiles; i++) {
189		archive = archive_read_new();
190		archive_read_support_format_all(archive);
191		archive_read_support_filter_all(archive);
192		sprintf(path, "%s/%s", getenv("BSDINSTALL_DISTDIR"), files[i]);
193		err = archive_read_open_filename(archive, path, 4096);
194
195		items[i*2 + 1] = "In Progress";
196		archive_file = 0;
197
198		while ((err = archive_read_next_header(archive, &entry)) ==
199		    ARCHIVE_OK) {
200			last_progress = progress;
201			progress = (current_files*100)/total_files;
202
203			sprintf(status, "-%d",
204			    (archive_file*100)/archive_files[i]);
205			items[i*2 + 1] = status;
206
207			if (progress > last_progress)
208				dialog_mixedgauge("Archive Extraction",
209				    "Extracting distribution files...", 0, 0,
210				    progress, nfiles,
211				    __DECONST(char **, items));
212
213			err = archive_read_extract(archive, entry,
214			    ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
215			    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
216			    ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
217
218			if (err != ARCHIVE_OK)
219				break;
220
221			archive_file++;
222			current_files++;
223		}
224
225		items[i*2 + 1] = "Done";
226
227		if (err != ARCHIVE_EOF) {
228			snprintf(errormsg, sizeof(errormsg),
229			    "Error while extracting %s: %s\n", items[i*2],
230			    archive_error_string(archive));
231			items[i*2 + 1] = "Failed";
232			dialog_msgbox("Extract Error", errormsg, 0, 0,
233			    TRUE);
234			return (err);
235		}
236
237		archive_read_free(archive);
238	}
239
240	return (0);
241}
242