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 
36 static int extract_files(int nfiles, const char **files);
37 
38 int
39 main(void)
40 {
41 	char *diststring = strdup(getenv("DISTRIBUTIONS"));
42 	const char **dists;
43 	int i, retval, ndists = 0;
44 	for (i = 0; diststring[i] != 0; i++)
45 		if (isspace(diststring[i]) && !isspace(diststring[i+1]))
46 			ndists++;
47 	ndists++; /* Last one */
48 
49 	dists = calloc(ndists, sizeof(const char *));
50 	if (dists == NULL) {
51 		fprintf(stderr, "Out of memory!\n");
52 		return (1);
53 	}
54 
55 	for (i = 0; i < ndists; i++)
56 		dists[i] = strsep(&diststring, " \t");
57 
58 	init_dialog(stdin, stdout);
59 	dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer");
60 	dlg_put_backtitle();
61 
62 	if (chdir(getenv("BSDINSTALL_CHROOT")) != 0) {
63 		char error[512];
64 		sprintf(error, "Could could change to directory %s: %s\n",
65 		    getenv("BSDINSTALL_DISTDIR"), strerror(errno));
66 		dialog_msgbox("Error", error, 0, 0, TRUE);
67 		end_dialog();
68 		return (1);
69 	}
70 
71 	retval = extract_files(ndists, dists);
72 
73 	end_dialog();
74 
75 	free(diststring);
76 	free(dists);
77 
78 	return (retval);
79 }
80 
81 static int
82 count_files(const char *file)
83 {
84 	struct archive *archive;
85 	struct archive_entry *entry;
86 	static FILE *manifest = NULL;
87 	char path[MAXPATHLEN];
88 	char errormsg[512];
89 	int file_count, err;
90 
91 	if (manifest == NULL) {
92 		sprintf(path, "%s/MANIFEST", getenv("BSDINSTALL_DISTDIR"));
93 		manifest = fopen(path, "r");
94 	}
95 
96 	if (manifest != NULL) {
97 		char line[512];
98 		char *tok1, *tok2;
99 
100 		rewind(manifest);
101 		while (fgets(line, sizeof(line), manifest) != NULL) {
102 			tok2 = line;
103 			tok1 = strsep(&tok2, "\t");
104 			if (tok1 == NULL || strcmp(tok1, file) != 0)
105 				continue;
106 
107 			/*
108 			 * We're at the right manifest line. The file count is
109 			 * in the third element
110 			 */
111 			tok1 = strsep(&tok2, "\t");
112 			tok1 = strsep(&tok2, "\t");
113 			if (tok1 != NULL)
114 				return atoi(tok1);
115 		}
116 	}
117 
118 	/* Either we didn't have a manifest, or this archive wasn't there */
119 	archive = archive_read_new();
120 	archive_read_support_format_all(archive);
121 	archive_read_support_compression_all(archive);
122 	sprintf(path, "%s/%s", getenv("BSDINSTALL_DISTDIR"), file);
123 	err = archive_read_open_filename(archive, path, 4096);
124 	if (err != ARCHIVE_OK) {
125 		snprintf(errormsg, sizeof(errormsg),
126 		    "Error while extracting %s: %s\n", file,
127 		    archive_error_string(archive));
128 		dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
129 		return (-1);
130 	}
131 
132 	file_count = 0;
133 	while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
134 		file_count++;
135 	archive_read_free(archive);
136 
137 	return (file_count);
138 }
139 
140 static int
141 extract_files(int nfiles, const char **files)
142 {
143 	const char *items[nfiles*2];
144 	char path[PATH_MAX];
145 	int archive_files[nfiles];
146 	int total_files, current_files, archive_file;
147 	struct archive *archive;
148 	struct archive_entry *entry;
149 	char errormsg[512];
150 	char status[8];
151 	int i, err, progress, last_progress;
152 
153 	err = 0;
154 	progress = 0;
155 
156 	/* Make the transfer list for dialog */
157 	for (i = 0; i < nfiles; i++) {
158 		items[i*2] = strrchr(files[i], '/');
159 		if (items[i*2] != NULL)
160 			items[i*2]++;
161 		else
162 			items[i*2] = files[i];
163 		items[i*2 + 1] = "Pending";
164 	}
165 
166 	dialog_msgbox("",
167 	    "Checking distribution archives.\nPlease wait...", 0, 0, FALSE);
168 
169 	/* Count all the files */
170 	total_files = 0;
171 	for (i = 0; i < nfiles; i++) {
172 		archive_files[i] = count_files(files[i]);
173 		if (archive_files[i] < 0)
174 			return (-1);
175 		total_files += archive_files[i];
176 	}
177 
178 	current_files = 0;
179 
180 	for (i = 0; i < nfiles; i++) {
181 		archive = archive_read_new();
182 		archive_read_support_format_all(archive);
183 		archive_read_support_compression_all(archive);
184 		sprintf(path, "%s/%s", getenv("BSDINSTALL_DISTDIR"), files[i]);
185 		err = archive_read_open_filename(archive, path, 4096);
186 
187 		items[i*2 + 1] = "In Progress";
188 		archive_file = 0;
189 
190 		while ((err = archive_read_next_header(archive, &entry)) ==
191 		    ARCHIVE_OK) {
192 			last_progress = progress;
193 			progress = (current_files*100)/total_files;
194 
195 			sprintf(status, "-%d",
196 			    (archive_file*100)/archive_files[i]);
197 			items[i*2 + 1] = status;
198 
199 			if (progress > last_progress)
200 				dialog_mixedgauge("Archive Extraction",
201 				    "Extracting distribution files...", 0, 0,
202 				    progress, nfiles,
203 				    __DECONST(char **, items));
204 
205 			err = archive_read_extract(archive, entry,
206 			    ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
207 			    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
208 			    ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
209 
210 			if (err != ARCHIVE_OK)
211 				break;
212 
213 			archive_file++;
214 			current_files++;
215 		}
216 
217 		items[i*2 + 1] = "Done";
218 
219 		if (err != ARCHIVE_EOF) {
220 			snprintf(errormsg, sizeof(errormsg),
221 			    "Error while extracting %s: %s\n", items[i*2],
222 			    archive_error_string(archive));
223 			items[i*2 + 1] = "Failed";
224 			dialog_msgbox("Extract Error", errormsg, 0, 0,
225 			    TRUE);
226 			return (err);
227 		}
228 
229 		archive_read_free(archive);
230 	}
231 
232 	return (0);
233 }
234