1 /* $NetBSD: undist.c,v 1.1 2026/06/11 08:29:49 rumble Exp $ */ 2 3 /* 4 * Copyright (c) 2006, 2007, 2025 Stephen M. Rumble <rumble (at) ephemeral.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* 20 * This program extracts files from SGI distributions used with the 'inst' 21 * program. SGI uses manifests in files suffixed with '.idb' to list 22 * the archive contents, as well as various other tidbits such as commands 23 * to run after extraction. 24 * 25 * The formats are rather simple. The .idb plain text files reference 26 * objects in other data files. These data files contain a 13-byte header, 27 * which appears to be an ID, followed by data entries. Each entry contains 28 * a 2-byte big endian string length, followed by a string (which matches 29 * part of the .idb file content), and then the data. Data size is 30 * determined by the .idb file. 31 * 32 * Data contents are either compressed with a Lempel-Ziv algorithm 33 * (compress(1)), or are uncompressed as indicated by the size() and 34 * cmpsize() parameters in the .idb file. 35 * 36 * Sometime after IRIX 6.2 an off() parameter was added to the .idb files, 37 * making it absolutely trivial to extract files. With earlier versions, 38 * however, the offset must be computed. It appears the the file order 39 * always follows the .idb file, but I am unsure that this is always the 40 * case (though it would be needlessly complicated if it were not). In any 41 * event, this code goes through some pains by not assuming proper order. 42 */ 43 44 #include <assert.h> 45 #include <ctype.h> 46 #include <errno.h> 47 #include <fcntl.h> 48 #include <libgen.h> 49 #include <limits.h> 50 #include <stdbool.h> 51 #include <stdio.h> 52 #include <stdlib.h> 53 #include <stdint.h> 54 #include <string.h> 55 #include <unistd.h> 56 57 #include <arpa/inet.h> 58 59 #include <sys/stat.h> 60 #include <sys/types.h> 61 62 #include "util.h" 63 64 #define WS " \f\n\r\t\v" 65 #define BUFFER_SIZE 32768 66 #define MAX_IDB_LINE BUFFER_SIZE 67 68 static int aflag; /* permit extraction to absolute path */ 69 static int lflag; /* list contents, do not extract */ 70 static int oflag; /* ignore idb 'off' & build ourselves */ 71 static int pflag; /* permit relative paths (e.g. '../') */ 72 static int sflag; /* skip extract errors and continue */ 73 static int tflag; /* use idb's temporary directory */ 74 static int vflag; /* verify integrity, do not extract */ 75 static int xflag; /* extract archive */ 76 static char *wantmach; /* -m: only matching mach fields */ 77 static char *fileprefix; /* -P: only matching file prefixes */ 78 static unsigned int totalfiles; 79 static unsigned int totalbytes; 80 static unsigned int totalcompressed; 81 static char idbfile[FILENAME_MAX]; 82 83 struct offset_table_entry { 84 char *type; 85 mode_t mode; 86 char *owner; 87 char *group; 88 char *dstfile; 89 char *tmpfile; 90 char *package; 91 char *mach; 92 int sum; 93 int size; 94 int cmpsize; 95 int off; 96 int flags; 97 }; 98 99 #define F_NORQS 0x00000001 100 #define F_NEEDRQS 0x00000002 101 #define F_NOSTRIP 0x00000004 102 #define F_STRIPDSO 0x00000008 103 #define F_NOHIST 0x00000010 104 #define F_DELHIST 0x00000020 105 #define F_NOSHARE 0x00000040 106 107 static struct offset_table_entry **offset_table; 108 static int offset_table_length; 109 110 #define INVALID_OFFSET (-1) 111 112 #ifndef PATH_MAX 113 #define PATH_MAX 1024 114 #endif 115 116 static bool 117 streql(const char *str1, const char *str2) 118 { 119 if (str1 == NULL || str2 == NULL) 120 return (false); 121 122 return (strcmp(str1, str2) == 0); 123 } 124 125 static char * 126 xstrdup(const char *str) 127 { 128 char *cpy; 129 130 if (str == NULL) 131 return (NULL); 132 133 cpy = strdup(str); 134 if (cpy == NULL) { 135 fprintf(stderr, "error: strdup failed: %s\n", strerror(errno)); 136 exit(1); 137 } 138 139 return (cpy); 140 } 141 142 static void * 143 xmalloc(int bytes) 144 { 145 void *ptr; 146 147 ptr = malloc(bytes); 148 if (ptr == NULL) { 149 fprintf(stderr, "error: malloc failed: %s\n", strerror(errno)); 150 exit(1); 151 } 152 153 return (ptr); 154 } 155 156 static void * 157 xrealloc(void *oldptr, int bytes) 158 { 159 void *ptr; 160 161 ptr = realloc(oldptr, bytes); 162 if (ptr == NULL) { 163 fprintf(stderr, "error: realloc failed: %s\n", strerror(errno)); 164 exit(1); 165 } 166 167 return (ptr); 168 } 169 170 static FILE * 171 xfopen(const char *file, const char *mode) 172 { 173 FILE *infp; 174 175 infp = fopen(file, mode); 176 if (infp == NULL) { 177 fprintf(stderr, "error: failed to fopen file %s: %s\n", 178 file, strerror(errno)); 179 exit(1); 180 } 181 182 return (infp); 183 } 184 185 static void 186 xfseek(FILE *fp, long int offset, int whence, const char *filename) 187 { 188 if (fseek(fp, offset, whence) == -1) { 189 if (filename != NULL) 190 fprintf(stderr, "error: failed to fseek source file " 191 "%s: %s\n", filename, strerror(errno)); 192 else 193 fprintf(stderr, "error: failed to fseek: %s\n", 194 strerror(errno)); 195 exit(1); 196 } 197 } 198 199 static bool 200 xfeof(FILE *fp) 201 { 202 char foo; 203 bool ret; 204 205 ret = (fread(&foo, 1, 1, fp) != 1); 206 xfseek(fp, -1, SEEK_CUR, NULL); 207 208 return (ret); 209 } 210 211 static char * 212 extract_parameter(const char *name, const char *str) 213 { 214 static char buf[512]; 215 int oparen, cparen; 216 const char *start, *end; 217 218 /* ensure we don't confuse 'size(..)' with 'cmpsize(...)' */ 219 start = strstr(str, name); 220 while (start != NULL && start != str && !isspace((int)*(start - 1))) 221 start = strstr(str, name); 222 223 if (start != NULL) { 224 start += strlen(name); 225 if (*start == '(') { 226 end = ++start; 227 oparen = 1; 228 cparen = 0; 229 while (*end != '\0') { 230 if (*end == '(') 231 oparen++; 232 else if (*end == ')') 233 cparen++; 234 235 if (oparen == cparen) 236 break; 237 238 end++; 239 } 240 memcpy(buf, start, end - start); 241 buf[end - start] = '\0'; 242 return (buf); 243 } 244 } 245 246 return (NULL); 247 } 248 249 static unsigned long int 250 extract_parameter_int(const char *name, const char *str, const int line) 251 { 252 char *n; 253 254 n = extract_parameter(name, str); 255 if (n == NULL) { 256 fprintf(stderr, "%s: malformed idb file (line %d)\n", 257 (sflag) ? "warning" : "error", line); 258 if (!sflag) 259 exit(1); 260 } 261 262 return (strtoul(n, NULL, 10)); 263 } 264 265 /* 266 * Given a package name, return the full filename including path. 267 */ 268 static const char * 269 filename_from_package(const char *package) 270 { 271 static char buf[PATH_MAX + 1]; 272 273 char file[PATH_MAX + 1]; 274 struct stat sb; 275 char *dot; 276 277 strlcpy(file, package, sizeof(file)); 278 dot = strchr(file, '.'); 279 if (dot != NULL) { 280 dot = strchr(dot + 1, '.'); 281 if (dot != NULL) 282 *dot = '\0'; 283 } 284 285 strlcpy(buf, dirname(idbfile), sizeof(buf)); 286 strlcat(buf, "/", sizeof(buf)); 287 strlcat(buf, file, sizeof(buf)); 288 289 /* 290 * This won't always work. E.g., IRIX 6.5.25m has a "_6525m" suffix, 291 * though it's not reflected in the package name. So, fall back on the 292 * idb file name and munge it to see if we can get what we want. 293 */ 294 if (stat(buf, &sb) == -1) { 295 char *suffix; 296 297 suffix = strrchr(buf, '.'); 298 if (suffix != NULL) { 299 strlcpy(file, suffix, sizeof(file)); 300 dot = strrchr(idbfile, '.'); 301 if (dot != NULL) { 302 strlcpy(buf, idbfile, sizeof(buf)); 303 dot = strrchr(buf, '.'); 304 assert(dot != NULL); 305 *dot = '\0'; 306 strlcat(buf, file, sizeof(buf)); 307 } 308 } 309 } 310 311 return (buf); 312 } 313 314 315 static void 316 grow_offset_table(int length) 317 { 318 if (offset_table == NULL) { 319 offset_table = xmalloc(sizeof(struct offset_table *) * length); 320 memset(offset_table, 0, sizeof(struct offset_table *) * length); 321 } else { 322 offset_table = xrealloc(offset_table, 323 sizeof(struct offset_table *) * length); 324 memset(&offset_table[offset_table_length], 0, 325 sizeof(struct offset_table *) * 326 (length - offset_table_length)); 327 } 328 329 offset_table_length = length; 330 } 331 332 static struct offset_table_entry * 333 new_offset_table_entry(char *type, mode_t mode, char *owner, char *group, 334 char *dstfile, char *tmpfile, char *package, char *mach, int sum, int size, 335 int cmpsize, int off, int flags) 336 { 337 struct offset_table_entry *ote; 338 339 ote = xmalloc(sizeof(*ote)); 340 ote->type = xstrdup(type); 341 ote->mode = mode; 342 ote->owner = xstrdup(owner); 343 ote->group = xstrdup(group); 344 ote->dstfile = xstrdup(dstfile); 345 ote->tmpfile = xstrdup(tmpfile); 346 ote->package = xstrdup(package); 347 ote->mach = xstrdup(mach); 348 ote->sum = sum; 349 ote->size = size; 350 ote->cmpsize = cmpsize; 351 ote->off = off; 352 ote->flags = flags; 353 354 return (ote); 355 } 356 357 /* 358 * Helper for discover_file_offsets(). Get the next offset_table entry 359 * for 'dstfile'. 360 */ 361 static struct offset_table_entry * 362 get_next_from_dstfile(const char *dstfile, size_t dstlen) 363 { 364 static int lastidx; 365 static char buf[1024]; 366 int i; 367 368 if (!streql(buf, dstfile)) { 369 lastidx = -1; 370 strlcpy(buf, dstfile, dstlen); 371 } 372 373 for (i = lastidx + 1; 374 i < offset_table_length && offset_table[i] != NULL; i++) { 375 lastidx = i; 376 if (streql(dstfile, offset_table[i]->dstfile)) 377 return (offset_table[i]); 378 } 379 380 return (NULL); 381 } 382 383 static int 384 get_filename_length(FILE *fp, const char *fname) 385 { 386 unsigned short filename_len; 387 if (fread(&filename_len, sizeof(filename_len), 1, fp) != 1) { 388 fprintf(stderr, "error: failed to read from file %s: " 389 "%s\n", fname, strerror(errno)); 390 exit(1); 391 } 392 return ntohs(filename_len); 393 } 394 395 static void 396 get_filename(FILE *fp, char *buf, int len) 397 { 398 if (fread(buf, len, 1, fp) != 1) { 399 fprintf(stderr, "error: failed to read: %s\n", strerror(errno)); 400 exit(1); 401 } 402 buf[len] = '\0'; 403 } 404 405 /* 406 * Determine if the data at the current position in fp matches the size 407 * information in 'ent'. We do this heuristically based on the following 408 * rules: 409 * 1) If the file is compressed (ent->cmpsize != 0), then the 410 * EOF or a new valid string length exists ent->cmpsize bytes 411 * offset from the current position. 412 * 413 * 2) If the file is not compressed (ent->cmpsize == 0), then the 414 * EOF or a new valid string length exists ent->size bytes 415 * offset from the current position. 416 * 417 * If 1) or 2) is true and we're not in the EOF case, the next string 418 * must exist in our offset_table, otherwise something is corrupt or 419 * we've read garbage. 420 */ 421 static bool 422 is_entry(FILE *fp, struct offset_table_entry *ent) 423 { 424 int i, len; 425 bool ret; 426 long int oldoff; 427 char buf[PATH_MAX + 1]; 428 unsigned short filename_len; 429 430 ret = false; 431 oldoff = ftell(fp); 432 len = (ent->cmpsize == 0) ? ent->size : ent->cmpsize; 433 434 fseek(fp, len, SEEK_CUR); 435 if (xfeof(fp)) { 436 fseek(fp, -1, SEEK_CUR); 437 if (!xfeof(fp)) { 438 ret = true; 439 goto leave; 440 } 441 442 goto leave; 443 } 444 445 filename_len = get_filename_length(fp, NULL); 446 if (filename_len > PATH_MAX) 447 goto leave; 448 449 get_filename(fp, buf, filename_len); 450 for (i = 0; i < offset_table_length && offset_table[i] != NULL; i++) 451 if (streql(buf, offset_table[i]->dstfile)) 452 ret = true; 453 leave: 454 xfseek(fp, oldoff, SEEK_SET, NULL); 455 return (ret); 456 } 457 458 /* 459 * 'fp' points to the first length field of a data file. Run through the 460 * the and try to match offset_table entries to it, filling in the 461 * offsets as we go along. 462 */ 463 static void 464 discover_all(FILE *fp, const char *fname) 465 { 466 int off; 467 char buf[PATH_MAX + 1]; 468 unsigned short filename_len; 469 struct offset_table_entry *nextent; 470 471 while (!xfeof(fp)) { 472 off = ftell(fp); 473 474 filename_len = get_filename_length(fp, fname); 475 if (filename_len > PATH_MAX) { 476 fprintf(stderr, "error: absurdly long embedded string " 477 "(%d bytes) in %s\n", filename_len, fname); 478 exit(1); 479 } 480 481 get_filename(fp, buf, filename_len); 482 483 /* 484 * 'buf' may match several filenames (e.g. if 'mach') 485 * is used. We could probably assume that the first matching 486 * entry in offset_table (and thus in the idb file) belongs, 487 * but we'll be a bit more careful and ensure that things 488 * line up in 'is_entry()'. 489 * 490 * XXX - duplicate files with same size. should we do a 491 * cksum too? 492 */ 493 while (true) { 494 nextent = get_next_from_dstfile(buf, sizeof(buf)); 495 if (nextent == NULL) 496 return; 497 498 if (nextent->off == INVALID_OFFSET && 499 is_entry(fp, nextent)) { 500 nextent->off = off; 501 fseek(fp, (nextent->cmpsize == 0) ? 502 nextent->size : nextent->cmpsize, SEEK_CUR); 503 break; 504 } 505 } 506 } 507 } 508 509 /* 510 * After our offset_table has been built, go through it and try to 511 * determine the offset for all files. 512 * 513 * The algorithm does not assume that SGI built the idb file in order, 514 * though I suspect they always do. 515 * 516 * The first 13 bytes of each data file appear to be a special 517 * identification tag, for instance: "im001V530P00\0". Immediately 518 * following is a 16-bit big endian string length, the string itself, 519 * and then the data. The string is the same as the 'dstfile' entry of 520 * the idb file, and is not nul-terminated. 521 */ 522 static void 523 discover_file_offsets(void) 524 { 525 int i; 526 FILE *fp; 527 const char *fname; 528 struct offset_table_entry *ent; 529 530 for (i = 0; i < offset_table_length && offset_table[i] != NULL; i++) { 531 ent = offset_table[i]; 532 533 if (ent->off != INVALID_OFFSET) 534 continue; 535 536 fname = filename_from_package(ent->package); 537 fp = xfopen(fname, "r"); 538 xfseek(fp, 13, SEEK_SET, NULL); 539 discover_all(fp, fname); 540 fclose(fp); 541 } 542 543 for (i = 0; i < offset_table_length && offset_table[i] != NULL; i++) { 544 if (offset_table[i]->off == INVALID_OFFSET) { 545 fprintf(stderr, "%s: failed to resolve offset for " 546 "file %s\n", (sflag) ? "warning" : "error", 547 offset_table[i]->dstfile); 548 if (!sflag) 549 exit(1); 550 } 551 } 552 } 553 554 static int 555 cntchr(const char *str, char chr) 556 { 557 int cnt; 558 559 cnt = 0; 560 while (*str != '\0') 561 if (*str++ == chr) 562 cnt++; 563 564 return (cnt); 565 } 566 567 /* 568 * Tokenise based on whitespace, with one exception: if we have a 569 * parameter, do not stop on whitespace, but only on a closing parenthesis. 570 * 571 * Since parameters themselves can take parentheses, we need to only stop 572 * on outter ones. 573 */ 574 static char * 575 idbtok(char *str, bool *isparameter) 576 { 577 static char *next; 578 char tmp, *start, *end; 579 int i, oparens, cparens; 580 581 if (next == NULL && str == NULL) 582 return (NULL); 583 584 if (str != NULL) 585 next = str; 586 587 if (*next == '\0') 588 return (NULL); 589 590 *isparameter = false; 591 592 start = next + strspn(next, WS); 593 end = start + strcspn(start, WS); 594 if (end > start) { 595 tmp = *end; 596 597 if (tmp == '\0') { 598 next = NULL; 599 oparens = cntchr(start, '('); 600 cparens = cntchr(start, ')'); 601 } else { 602 *end = '\0'; 603 604 oparens = cntchr(start, '('); 605 cparens = cntchr(start, ')'); 606 if (oparens != cparens) { 607 *end = tmp; 608 609 oparens = cparens = 0; 610 for (i = 1; start[i] != '\0'; i++) { 611 if (start[i] == '(') 612 oparens++; 613 else if (start[i] == ')') 614 cparens++; 615 else 616 continue; 617 618 if (oparens != 0 && oparens == cparens){ 619 end = start + i + 1; 620 break; 621 } 622 } 623 624 if (*end == '\0') 625 next = NULL; 626 else 627 *end = '\0'; 628 } 629 } 630 631 if (oparens == cparens && oparens != 0 && *start != '(') 632 *isparameter = true; 633 } else 634 next = NULL; 635 636 if (next != NULL) 637 next = end + 1; 638 639 return (start); 640 } 641 642 /* 643 * Construct a table of file offsets for earlier versions of IRIX, which 644 * do not include an 'off()' parameter in the idb file. 645 */ 646 static void 647 build_offset_table(FILE *fp) 648 { 649 mode_t mode; 650 char *buf; 651 bool isparam; 652 char parambuf[1024]; 653 int line, flags, tblidx, off; 654 int size, sum, cmpsize, state; 655 char *type, *smode, *owner, *token, *group; 656 char *dstfile, *tmpfile, *package, *mach, *p; 657 658 tblidx = 0; 659 grow_offset_table(32); 660 buf = xmalloc(MAX_IDB_LINE); 661 662 /* 663 * Most idb archives follow a strict, fixed order. E.g.: 664 * "f 0755 root sys /dst /tmp foo.sw.bar sum(0) size(0) cmpsize(0)" 665 * 666 * Unfortunately, it appears that in some idb files the 'package' 667 * comes after the parameters, making things more complex than they 668 * should be. E.g.: 669 * "f 0755 root sys /dst /tmp sum(0) size(0) cmpsize(0) foo.sw.bar" 670 * 671 * We'll be extra robust here, allowing parameters (distinguished 672 * by having '(' and ')' in their strings) to appear anywhere. 673 */ 674 for (line = 1; fgets(buf, MAX_IDB_LINE, fp) != NULL; line++) { 675 /* immediately skip comments */ 676 if (buf[0] == '#') 677 continue; 678 679 *parambuf = '\0'; 680 flags = state = 0; 681 tmpfile = package = NULL; 682 type = smode = owner = group = dstfile = NULL; 683 684 for (p = buf; (token = idbtok(p, &isparam)) != NULL; p = NULL) { 685 if (isparam) { 686 strlcat(parambuf, " ", sizeof(parambuf)); 687 strlcat(parambuf, token, sizeof(parambuf)); 688 } else if (streql(token, "norqs")) { 689 flags |= F_NORQS; 690 } else if (streql(token, "needrqs")) { 691 flags |= F_NEEDRQS; 692 } else if (streql(token, "nostrip")) { 693 flags |= F_NOSTRIP; 694 } else if (streql(token, "stripdso")) { 695 flags |= F_STRIPDSO; 696 } else if (streql(token, "nohist")) { 697 flags |= F_NOHIST; 698 } else if (streql(token, "delhist")) { 699 flags |= F_DELHIST; 700 } else if (streql(token, "noshare")) { 701 flags |= F_NOSHARE; 702 } else { 703 switch (state++) { 704 case 0: type = token; break; 705 case 1: smode = token; break; 706 case 2: owner = token; break; 707 case 3: group = token; break; 708 case 4: dstfile = token; break; 709 case 5: tmpfile = token; break; 710 case 6: package = token; break; 711 default: 712 fprintf(stderr, "warning: unknown flag " 713 "\"%s\" (line %d)\n", token, line); 714 } 715 } 716 } 717 718 if (state < 7) { 719 fprintf(stderr, "%s: malformed idb file (line %d)\n", 720 (sflag) ? "warning" : "error", line); 721 if (sflag) 722 continue; 723 else 724 exit(1); 725 } 726 727 /* 728 * Skip block/char device, directory, and symbolic link 729 * entries. Be very liberal otherwise, and do not bother to 730 * proof their format. 731 */ 732 if (streql(type, "b") || streql(type, "c") || 733 streql(type, "d") || streql(type, "l")) 734 continue; 735 736 if (!streql(type, "f") || smode == NULL || 737 owner == NULL || group == NULL || dstfile == NULL || 738 tmpfile == NULL || package == NULL || parambuf[0] == '\0') { 739 fprintf(stderr, "%s: malformed idb file (line %d)\n", 740 (sflag) ? "warning" : "error", line); 741 if (sflag) 742 continue; 743 else 744 exit(1); 745 } 746 747 mode = strtoul(smode, NULL, 8); 748 749 sum = extract_parameter_int("sum", parambuf, line); 750 size = extract_parameter_int("size", parambuf, line); 751 cmpsize = extract_parameter_int("cmpsize", parambuf, line); 752 753 if (oflag || extract_parameter("off", parambuf) == NULL) 754 off = INVALID_OFFSET; /* to be discovered */ 755 else 756 off = extract_parameter_int("off", parambuf, line); 757 758 if (tblidx == offset_table_length) 759 grow_offset_table(offset_table_length * 2); 760 761 mach = extract_parameter("mach", parambuf); 762 763 offset_table[tblidx++] = new_offset_table_entry(type, mode, 764 owner, group, dstfile, tmpfile, package, mach, sum, size, 765 cmpsize, off, flags); 766 } 767 768 free(buf); 769 } 770 771 /* 772 * Open a file, creating all directories along the way if we have to. 773 */ 774 static int 775 createpath(const char *file, int flags, mode_t filemode, mode_t dirmode) 776 { 777 int fd; 778 char *start, *slash; 779 780 fd = open(file, flags, filemode); 781 if (fd != -1) 782 return (fd); 783 784 switch (errno) { 785 case EACCES: 786 if (unlink(file) == 0) { 787 fd = open(file, flags, filemode); 788 if (fd != -1) 789 return (fd); 790 } 791 return (-1); 792 break; 793 794 case ENOENT: 795 break; 796 797 default: 798 return (-1); 799 } 800 801 start = (char *)file; 802 while (true) { 803 slash = strchr(start, '/'); 804 if (slash == NULL) 805 break; 806 807 *slash = '\0'; 808 if (mkdir(file, dirmode) != 0 && errno != EEXIST) 809 return (-1); 810 *slash = '/'; 811 start = slash + 1; 812 } 813 814 fd = open(file, flags, filemode); 815 816 return (fd); 817 } 818 819 static bool 820 fcopy(FILE *infp, FILE *outfp, int extractbytes, int *cksum) 821 { 822 uint32_t extract; 823 u_char buf[BUFFER_SIZE]; 824 825 *cksum = 0; 826 while (extractbytes > 0) { 827 if (sizeof(buf) > extractbytes) 828 extract = extractbytes; 829 else 830 extract = sizeof(buf); 831 832 if (fread(buf, extract, 1, infp) != 1) 833 return (false); 834 835 if (outfp != NULL) { 836 if (fwrite(buf, extract, 1, outfp) != 1) 837 return (false); 838 } 839 840 extractbytes -= extract; 841 *cksum = sum1(*cksum, buf, extract); 842 } 843 844 return (true); 845 } 846 847 /* 848 * NB: only returns on success. 849 * modifies globals 'total{files,bytes}' and 'totalcompressed'. 850 */ 851 static void 852 extract_file(struct offset_table_entry *ent) 853 { 854 char *s; 855 int nsum; 856 int outfd; 857 bool success; 858 FILE *infp, *outfp; 859 const char *src, *dst; 860 861 dst = tflag ? ent->tmpfile : ent->dstfile; 862 src = filename_from_package(ent->package); 863 infp = xfopen(src, "r"); 864 865 /* do not allow absolute paths unless explicitly permitted */ 866 if (!aflag) { 867 while (*dst == '/') 868 dst++; 869 } 870 871 /* chew relative paths unless explicitly permitted */ 872 if (!pflag) { 873 while ((s = strstr(dst, "../")) != NULL) 874 memcpy(s, "///", 3); 875 } 876 877 if (vflag) { 878 outfp = NULL; 879 } else { 880 outfd = createpath(dst, O_WRONLY | O_CREAT, ent->mode, 0755); 881 if (outfd == -1) { 882 fprintf(stderr, "error: failed to open destination " 883 "file %s: %s\n", dst, strerror(errno)); 884 exit(1); 885 } 886 887 outfp = fdopen(outfd, "w"); 888 if (outfp == NULL) { 889 fprintf(stderr, "error: fdopen failed: %s\n", 890 strerror(errno)); 891 exit(1); 892 } 893 } 894 895 xfseek(infp, ent->off + 2 + strlen(ent->dstfile), SEEK_SET, src); 896 897 errno = 0; 898 nsum = 0; 899 if (ent->cmpsize == 0) 900 success = fcopy(infp, outfp, ent->size, &nsum); 901 else { 902 success = (decompress(infp, outfp, 903 ent->cmpsize, &nsum) == ent->size); 904 totalcompressed++; 905 } 906 907 if (!success) { 908 if (errno == 0) 909 fprintf(stderr, "%s: %s and/or %s corrupt!\n", 910 (sflag) ? "warning" : "error", idbfile, src); 911 else 912 fprintf(stderr, "%s: failed to extract %s: %s\n", 913 (sflag) ? "warning" : "error", dst, 914 strerror(errno)); 915 916 if (!sflag) 917 exit(1); 918 } 919 920 if (nsum != ent->sum) { 921 fprintf(stderr, "%s: checksum failed for file %s%s%s%s\n", 922 (sflag) ? "warning" : "error", dst, 923 (ent->mach != NULL) ? " [mach(" : "", 924 (ent->mach != NULL) ? ent->mach : "", 925 (ent->mach != NULL) ? ")]" : ""); 926 927 if (!sflag) 928 exit(1); 929 success = false; 930 } 931 932 fclose(infp); 933 if (outfp != NULL) 934 fclose(outfp); 935 936 totalfiles++; 937 totalbytes += ent->size; 938 939 if (success) { 940 if (ent->mach == NULL) { 941 printf(" %s: %s\n", (vflag) ? "verified" : "extracted", 942 dst); 943 } else { 944 printf(" %s: %s [mach(%s)]\n", 945 (vflag) ? "verified" : "extracted", dst, ent->mach); 946 } 947 } 948 } 949 950 static void 951 extract_files(void) 952 { 953 int i; 954 955 for (i = 0; i < offset_table_length && offset_table[i] != NULL; i++) { 956 struct offset_table_entry *ent = offset_table[i]; 957 958 if (wantmach != NULL && ent->mach != NULL && 959 !streql(wantmach, ent->mach)) 960 continue; 961 962 if (fileprefix != NULL && 963 strncmp(ent->dstfile, fileprefix, strlen(fileprefix))) { 964 continue; 965 } 966 967 if (ent->off != INVALID_OFFSET) 968 extract_file(ent); 969 } 970 971 printf("%d bytes in %d files (%d compressed, %d uncompressed)\n", 972 totalbytes, totalfiles, totalcompressed, 973 totalfiles - totalcompressed); 974 } 975 976 static void 977 list_files(void) 978 { 979 int i; 980 981 for (i = 0; i < offset_table_length && offset_table[i] != NULL; i++) { 982 if (wantmach != NULL && offset_table[i]->mach != NULL && 983 !streql(wantmach, offset_table[i]->mach)) 984 continue; 985 986 if (offset_table[i]->off != INVALID_OFFSET) { 987 if (offset_table[i]->mach == NULL) { 988 printf(" %s\n", offset_table[i]->dstfile); 989 } else { 990 printf(" %s [mach(%s)]\n", 991 offset_table[i]->dstfile, 992 offset_table[i]->mach); 993 } 994 995 totalfiles++; 996 totalbytes += offset_table[i]->size; 997 if (offset_table[i]->cmpsize != 0) 998 totalcompressed++; 999 } 1000 } 1001 1002 printf("%d bytes in %d files (%d compressed, %d uncompressed)\n", 1003 totalbytes, totalfiles, totalcompressed, 1004 totalfiles - totalcompressed); 1005 } 1006 1007 static void 1008 usage(void) 1009 { 1010 fprintf(stderr, "usage: %s [-l|-v|-x] [-aopst] [-m machtype] " 1011 "[-f prefix] dist.idb\n", getprogname()); 1012 exit(1); 1013 } 1014 1015 int 1016 main(int argc, char **argv) 1017 { 1018 int ch; 1019 FILE *idb; 1020 extern int optind; 1021 extern char *optarg; 1022 1023 while ((ch = getopt(argc, argv, "alm:opP:stvx")) != -1) { 1024 switch (ch) { 1025 case 'a': 1026 aflag = 1; 1027 break; 1028 1029 case 'l': 1030 lflag = 1; 1031 break; 1032 1033 case 'm': 1034 wantmach = optarg; 1035 break; 1036 1037 case 'o': 1038 oflag = 1; 1039 break; 1040 1041 case 'p': 1042 pflag = 1; 1043 break; 1044 1045 case 'P': 1046 fileprefix = optarg; 1047 break; 1048 1049 case 's': 1050 sflag = 1; 1051 break; 1052 1053 case 't': 1054 tflag = 1; 1055 break; 1056 1057 case 'v': 1058 vflag = 1; 1059 break; 1060 1061 case 'x': 1062 xflag = 1; 1063 break; 1064 1065 default: 1066 usage(); 1067 } 1068 } 1069 argc -= optind; 1070 argv += optind; 1071 1072 if (argc == 0) 1073 usage(); 1074 1075 switch (lflag + vflag + xflag) { 1076 case 0: 1077 fprintf(stderr, "error: no -l, -v or -x operation specified\n"); 1078 usage(); 1079 1080 case 1: 1081 break; 1082 1083 default: 1084 fprintf(stderr, "error: 'l', 'v' and 'x' flags may not be used " 1085 "in conjunction\n"); 1086 usage(); 1087 } 1088 1089 strlcpy(idbfile, argv[0], sizeof(idbfile)); 1090 idb = xfopen(idbfile, "r"); 1091 build_offset_table(idb); 1092 fclose(idb); 1093 1094 discover_file_offsets(); 1095 1096 if (lflag) { 1097 list_files(); 1098 } else { 1099 extract_files(); 1100 } 1101 1102 return (0); 1103 } 1104