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