Home | History | Annotate | Line # | Download | only in undist
      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