1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2011 Nathan Whitehorn 5 * Copyright (c) 2014 Devin Teske <dteske@FreeBSD.org> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 __FBSDID("$FreeBSD$"); 32 33 #include <sys/param.h> 34 #include <archive.h> 35 #include <ctype.h> 36 #include <dialog.h> 37 #include <dpv.h> 38 #include <err.h> 39 #include <errno.h> 40 #include <limits.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <unistd.h> 45 46 /* Data to process */ 47 static char *distdir = NULL; 48 static struct archive *archive = NULL; 49 static struct dpv_file_node *dists = NULL; 50 51 /* Function prototypes */ 52 static void sig_int(int sig); 53 static int count_files(const char *file); 54 static int extract_files(struct dpv_file_node *file, int out); 55 56 #define _errx(...) (end_dialog(), errx(__VA_ARGS__)) 57 58 int 59 main(void) 60 { 61 char *chrootdir; 62 char *distributions; 63 int retval; 64 size_t config_size = sizeof(struct dpv_config); 65 size_t file_node_size = sizeof(struct dpv_file_node); 66 size_t span; 67 struct dpv_config *config; 68 struct dpv_file_node *dist = dists; 69 static char backtitle[] = "FreeBSD Installer"; 70 static char title[] = "Archive Extraction"; 71 static char aprompt[] = "\n Overall Progress:"; 72 static char pprompt[] = "Extracting distribution files...\n"; 73 struct sigaction act; 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 = backtitle; 84 dlg_put_backtitle(); 85 86 dialog_msgbox("", 87 "Checking distribution archives.\nPlease wait...", 4, 35, FALSE); 88 89 /* 90 * Parse $DISTRIBUTIONS into dpv(3) 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 99 /* Allocate a new struct for the distribution */ 100 if (dist == NULL) { 101 if ((dist = calloc(1, file_node_size)) == NULL) 102 _errx(EXIT_FAILURE, "Out of memory!"); 103 dists = dist; 104 } else { 105 dist->next = calloc(1, file_node_size); 106 if (dist->next == NULL) 107 _errx(EXIT_FAILURE, "Out of memory!"); 108 dist = dist->next; 109 } 110 111 /* Set path */ 112 if ((dist->path = malloc(span + 1)) == NULL) 113 _errx(EXIT_FAILURE, "Out of memory!"); 114 snprintf(dist->path, span + 1, "%s", distributions); 115 dist->path[span] = '\0'; 116 117 /* Set display name */ 118 dist->name = strrchr(dist->path, '/'); 119 if (dist->name == NULL) 120 dist->name = dist->path; 121 122 /* Set initial length in files (-1 == error) */ 123 dist->length = count_files(dist->path); 124 if (dist->length < 0) { 125 end_dialog(); 126 return (EXIT_FAILURE); 127 } 128 129 distributions += span; 130 } 131 132 /* Optionally chdir(2) into $BSDINSTALL_CHROOT */ 133 chrootdir = getenv("BSDINSTALL_CHROOT"); 134 if (chrootdir != NULL && chdir(chrootdir) != 0) { 135 snprintf(error, sizeof(error), 136 "Could not change to directory %s: %s\n", 137 chrootdir, strerror(errno)); 138 dialog_msgbox("Error", error, 0, 0, TRUE); 139 end_dialog(); 140 return (EXIT_FAILURE); 141 } 142 143 /* Set cleanup routine for Ctrl-C action */ 144 act.sa_handler = sig_int; 145 sigaction(SIGINT, &act, 0); 146 147 /* 148 * Hand off to dpv(3) 149 */ 150 if ((config = calloc(1, config_size)) == NULL) 151 _errx(EXIT_FAILURE, "Out of memory!"); 152 config->backtitle = backtitle; 153 config->title = title; 154 config->pprompt = pprompt; 155 config->aprompt = aprompt; 156 config->options |= DPV_WIDE_MODE; 157 config->label_size = -1; 158 config->action = extract_files; 159 config->status_solo = 160 "%10lli files read @ %'9.1f files/sec."; 161 config->status_many = 162 "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]"; 163 end_dialog(); 164 retval = dpv(config, dists); 165 166 dpv_free(); 167 while ((dist = dists) != NULL) { 168 dists = dist->next; 169 if (dist->path != NULL) 170 free(dist->path); 171 free(dist); 172 } 173 174 return (retval); 175 } 176 177 static void 178 sig_int(int sig __unused) 179 { 180 dpv_interrupt = TRUE; 181 } 182 183 /* 184 * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST 185 * if it exists, otherwise uses archive(3) to read the archive file. 186 */ 187 static int 188 count_files(const char *file) 189 { 190 static FILE *manifest = NULL; 191 char *p; 192 int file_count; 193 int retval; 194 size_t span; 195 struct archive_entry *entry; 196 char line[512]; 197 char path[PATH_MAX]; 198 char errormsg[PATH_MAX + 512]; 199 200 if (manifest == NULL) { 201 snprintf(path, sizeof(path), "%s/MANIFEST", distdir); 202 manifest = fopen(path, "r"); 203 } 204 205 if (manifest != NULL) { 206 rewind(manifest); 207 while (fgets(line, sizeof(line), manifest) != NULL) { 208 p = &line[0]; 209 span = strcspn(p, "\t") ; 210 if (span < 1 || strncmp(p, file, span) != 0) 211 continue; 212 213 /* 214 * We're at the right manifest line. The file count is 215 * in the third element 216 */ 217 span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t"); 218 span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t"); 219 if (span > 0) { 220 file_count = (int)strtol(p, (char **)NULL, 10); 221 if (file_count == 0 && errno == EINVAL) 222 continue; 223 return (file_count); 224 } 225 } 226 } 227 228 /* 229 * Either no manifest, or manifest didn't mention this archive. 230 * Use archive(3) to read the archive, counting files within. 231 */ 232 if ((archive = archive_read_new()) == NULL) { 233 snprintf(errormsg, sizeof(errormsg), 234 "Error: %s\n", archive_error_string(NULL)); 235 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 236 return (-1); 237 } 238 archive_read_support_format_all(archive); 239 archive_read_support_filter_all(archive); 240 snprintf(path, sizeof(path), "%s/%s", distdir, file); 241 retval = archive_read_open_filename(archive, path, 4096); 242 if (retval != ARCHIVE_OK) { 243 snprintf(errormsg, sizeof(errormsg), 244 "Error while extracting %s: %s\n", file, 245 archive_error_string(archive)); 246 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 247 archive = NULL; 248 return (-1); 249 } 250 251 file_count = 0; 252 while (archive_read_next_header(archive, &entry) == ARCHIVE_OK) 253 file_count++; 254 archive_read_free(archive); 255 archive = NULL; 256 257 return (file_count); 258 } 259 260 static int 261 extract_files(struct dpv_file_node *file, int out __unused) 262 { 263 int retval; 264 struct archive_entry *entry; 265 char path[PATH_MAX]; 266 char errormsg[PATH_MAX + 512]; 267 268 /* Open the archive if necessary */ 269 if (archive == NULL) { 270 if ((archive = archive_read_new()) == NULL) { 271 snprintf(errormsg, sizeof(errormsg), 272 "Error: %s\n", archive_error_string(NULL)); 273 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 274 dpv_abort = 1; 275 return (-1); 276 } 277 archive_read_support_format_all(archive); 278 archive_read_support_filter_all(archive); 279 snprintf(path, sizeof(path), "%s/%s", distdir, file->path); 280 retval = archive_read_open_filename(archive, path, 4096); 281 if (retval != 0) { 282 snprintf(errormsg, sizeof(errormsg), 283 "Error opening %s: %s\n", file->name, 284 archive_error_string(archive)); 285 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 286 file->status = DPV_STATUS_FAILED; 287 dpv_abort = 1; 288 return (-1); 289 } 290 } 291 292 /* Read the next archive header */ 293 retval = archive_read_next_header(archive, &entry); 294 295 /* If that went well, perform the extraction */ 296 if (retval == ARCHIVE_OK) 297 retval = archive_read_extract(archive, entry, 298 ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER | 299 ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | 300 ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS); 301 302 /* Test for either EOF or error */ 303 if (retval == ARCHIVE_EOF) { 304 archive_read_free(archive); 305 archive = NULL; 306 file->status = DPV_STATUS_DONE; 307 return (100); 308 } else if (retval != ARCHIVE_OK && 309 !(retval == ARCHIVE_WARN && 310 strcmp(archive_error_string(archive), "Can't restore time") == 0)) { 311 /* 312 * Print any warning/error messages except inability to set 313 * ctime/mtime, which is not fatal, or even interesting, 314 * for our purposes. Would be nice if this were a libarchive 315 * option. 316 */ 317 snprintf(errormsg, sizeof(errormsg), 318 "Error while extracting %s: %s\n", file->name, 319 archive_error_string(archive)); 320 dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE); 321 file->status = DPV_STATUS_FAILED; 322 dpv_abort = 1; 323 return (-1); 324 } 325 326 dpv_overall_read++; 327 file->read++; 328 329 /* Calculate [overall] percentage of completion (if possible) */ 330 if (file->length >= 0) 331 return (file->read * 100 / file->length); 332 else 333 return (-1); 334 } 335