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