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