xref: /freebsd/usr.sbin/bsdinstall/distextract/distextract.c (revision 7661de35d15f582ab33e3bd6b8d909601557e436)
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;
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 
89 static int
90 count_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 
148 static int
149 extract_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