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 <dpv.h> 36 #include <err.h> 37 #include <errno.h> 38 #include <limits.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 44 /* Data to process */ 45 static char *distdir = NULL; 46 static struct archive *archive = NULL; 47 static struct dpv_file_node *dists = NULL; 48 49 /* Function prototypes */ 50 static void sig_int(int sig); 51 static int count_files(const char *file); 52 static int extract_files(struct dpv_file_node *file, int out); 53 54 #if __FreeBSD_version <= 1000008 /* r232154: bump for libarchive update */ 55 #define archive_read_support_filter_all(x) \ 56 archive_read_support_compression_all(x) 57 #endif 58 59 #define _errx(...) (end_dialog(), errx(__VA_ARGS__)) 60 61 int 62 main(void) 63 { 64 char *chrootdir; 65 char *distributions; 66 int retval; 67 size_t config_size = sizeof(struct dpv_config); 68 size_t file_node_size = sizeof(struct dpv_file_node); 69 size_t span; 70 struct dpv_config *config; 71 struct dpv_file_node *dist = dists; 72 static char backtitle[] = "FreeBSD Installer"; 73 static char title[] = "Archive Extraction"; 74 static char aprompt[] = "\n Overall Progress:"; 75 static char pprompt[] = "Extracting distribution files...\n"; 76 struct sigaction act; 77 char error[PATH_MAX + 512]; 78 79 if ((distributions = getenv("DISTRIBUTIONS")) == NULL) 80 errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set"); 81 if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL) 82 distdir = __DECONST(char *, ""); 83 84 /* Initialize dialog(3) */ 85 init_dialog(stdin, stdout); 86 dialog_vars.backtitle = backtitle; 87 dlg_put_backtitle(); 88 89 dialog_msgbox("", 90 "Checking distribution archives.\nPlease wait...", 4, 35, FALSE); 91 92 /* 93 * Parse $DISTRIBUTIONS into dpv(3) linked-list 94 */ 95 while (*distributions != '\0') { 96 span = strcspn(distributions, "\t\n\v\f\r "); 97 if (span < 1) { /* currently on whitespace */ 98 distributions++; 99 continue; 100 } 101 102 /* Allocate a new struct for the distribution */ 103 if (dist == NULL) { 104 if ((dist = calloc(1, file_node_size)) == NULL) 105 _errx(EXIT_FAILURE, "Out of memory!"); 106 dists = dist; 107 } else { 108 dist->next = calloc(1, file_node_size); 109 if (dist->next == NULL) 110 _errx(EXIT_FAILURE, "Out of memory!"); 111 dist = dist->next; 112 } 113 114 /* Set path */ 115 if ((dist->path = malloc(span + 1)) == NULL) 116 _errx(EXIT_FAILURE, "Out of memory!"); 117 snprintf(dist->path, span + 1, "%s", distributions); 118 dist->path[span] = '\0'; 119 120 /* Set display name */ 121 dist->name = strrchr(dist->path, '/'); 122 if (dist->name == NULL) 123 dist->name = dist->path; 124 125 /* Set initial length in files (-1 == error) */ 126 dist->length = count_files(dist->path); 127 if (dist->length < 0) { 128 end_dialog(); 129 return (EXIT_FAILURE); 130 } 131 132 distributions += span; 133 } 134 135 /* Optionally chdir(2) into $BSDINSTALL_CHROOT */ 136 chrootdir = getenv("BSDINSTALL_CHROOT"); 137 if (chrootdir != NULL && chdir(chrootdir) != 0) { 138 snprintf(error, sizeof(error), 139 "Could not change to directory %s: %s\n", 140 chrootdir, strerror(errno)); 141 dialog_msgbox("Error", error, 0, 0, TRUE); 142 end_dialog(); 143 return (EXIT_FAILURE); 144 } 145 146 /* Set cleanup routine for Ctrl-C action */ 147 act.sa_handler = sig_int; 148 sigaction(SIGINT, &act, 0); 149 150 /* 151 * Hand off to dpv(3) 152 */ 153 if ((config = calloc(1, config_size)) == NULL) 154 _errx(EXIT_FAILURE, "Out of memory!"); 155 config->backtitle = backtitle; 156 config->title = title; 157 config->pprompt = pprompt; 158 config->aprompt = aprompt; 159 config->options |= DPV_WIDE_MODE; 160 config->label_size = -1; 161 config->action = extract_files; 162 config->status_solo = 163 "%10lli files read @ %'9.1f files/sec."; 164 config->status_many = 165 "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]"; 166 end_dialog(); 167 retval = dpv(config, dists); 168 169 dpv_free(); 170 while ((dist = dists) != NULL) { 171 dists = dist->next; 172 if (dist->path != NULL) 173 free(dist->path); 174 free(dist); 175 } 176 177 return (retval); 178 } 179 180 static void 181 sig_int(int sig __unused) 182 { 183 dpv_interrupt = TRUE; 184 } 185 186 /* 187 * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST 188 * if it exists, otherwise uses archive(3) to read the archive file. 189 */ 190 static int 191 count_files(const char *file) 192 { 193 static FILE *manifest = NULL; 194 char *p; 195 int file_count; 196 int retval; 197 size_t span; 198 struct archive_entry *entry; 199 char line[512]; 200 char path[PATH_MAX]; 201 char errormsg[PATH_MAX + 512]; 202 203 if (manifest == NULL) { 204 snprintf(path, sizeof(path), "%s/MANIFEST", distdir); 205 manifest = fopen(path, "r"); 206 } 207 208 if (manifest != NULL) { 209 rewind(manifest); 210 while (fgets(line, sizeof(line), manifest) != NULL) { 211 p = &line[0]; 212 span = strcspn(p, "\t") ; 213 if (span < 1 || strncmp(p, file, span) != 0) 214 continue; 215 216 /* 217 * We're at the right manifest line. The file count is 218 * in the third element 219 */ 220 span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t"); 221 span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t"); 222 if (span > 0) { 223 file_count = (int)strtol(p, (char **)NULL, 10); 224 if (file_count == 0 && errno == EINVAL) 225 continue; 226 return (file_count); 227 } 228 } 229 } 230 231 /* 232 * Either no manifest, or manifest didn't mention this archive. 233 * Use archive(3) to read the archive, counting files within. 234 */ 235 if ((archive = archive_read_new()) == NULL) { 236 snprintf(errormsg, sizeof(errormsg), 237 "Error: %s\n", archive_error_string(NULL)); 238 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 239 return (-1); 240 } 241 archive_read_support_format_all(archive); 242 archive_read_support_filter_all(archive); 243 snprintf(path, sizeof(path), "%s/%s", distdir, file); 244 retval = archive_read_open_filename(archive, path, 4096); 245 if (retval != ARCHIVE_OK) { 246 snprintf(errormsg, sizeof(errormsg), 247 "Error while extracting %s: %s\n", file, 248 archive_error_string(archive)); 249 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 250 archive = NULL; 251 return (-1); 252 } 253 254 file_count = 0; 255 while (archive_read_next_header(archive, &entry) == ARCHIVE_OK) 256 file_count++; 257 archive_read_free(archive); 258 archive = NULL; 259 260 return (file_count); 261 } 262 263 static int 264 extract_files(struct dpv_file_node *file, int out __unused) 265 { 266 int retval; 267 struct archive_entry *entry; 268 char path[PATH_MAX]; 269 char errormsg[PATH_MAX + 512]; 270 271 /* Open the archive if necessary */ 272 if (archive == NULL) { 273 if ((archive = archive_read_new()) == NULL) { 274 snprintf(errormsg, sizeof(errormsg), 275 "Error: %s\n", archive_error_string(NULL)); 276 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 277 dpv_abort = 1; 278 return (-1); 279 } 280 archive_read_support_format_all(archive); 281 archive_read_support_filter_all(archive); 282 snprintf(path, sizeof(path), "%s/%s", distdir, file->path); 283 retval = archive_read_open_filename(archive, path, 4096); 284 if (retval != 0) { 285 snprintf(errormsg, sizeof(errormsg), 286 "Error opening %s: %s\n", file->name, 287 archive_error_string(archive)); 288 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 289 file->status = DPV_STATUS_FAILED; 290 dpv_abort = 1; 291 return (-1); 292 } 293 } 294 295 /* Read the next archive header */ 296 retval = archive_read_next_header(archive, &entry); 297 298 /* If that went well, perform the extraction */ 299 if (retval == ARCHIVE_OK) 300 retval = archive_read_extract(archive, entry, 301 ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER | 302 ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | 303 ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS); 304 305 /* Test for either EOF or error */ 306 if (retval == ARCHIVE_EOF) { 307 archive_read_free(archive); 308 archive = NULL; 309 file->status = DPV_STATUS_DONE; 310 return (100); 311 } else if (retval != ARCHIVE_OK) { 312 snprintf(errormsg, sizeof(errormsg), 313 "Error while extracting %s: %s\n", file->name, 314 archive_error_string(archive)); 315 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 316 file->status = DPV_STATUS_FAILED; 317 dpv_abort = 1; 318 return (-1); 319 } 320 321 dpv_overall_read++; 322 file->read++; 323 324 /* Calculate [overall] percentage of completion (if possible) */ 325 if (file->length >= 0) 326 return (file->read * 100 / file->length); 327 else 328 return (-1); 329 } 330