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