imagezip.c 36.5 KB
Newer Older
1 2 3 4
/*
 * An image zipper.
 */
#include <err.h>
Mac Newbold's avatar
Mac Newbold committed
5
#include <fcntl.h>
6 7 8
#include <fstab.h>
#include <stdio.h>
#include <stdlib.h>
Mac Newbold's avatar
Mac Newbold committed
9
#include <unistd.h>
10 11 12 13
#include <assert.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/stat.h>
Mac Newbold's avatar
Mac Newbold committed
14
#include <zlib.h>
15 16 17 18 19 20 21 22 23
#define DKTYPENAMES
#ifndef linux
#include <sys/disklabel.h>
#include <ufs/ffs/fs.h>
#endif
#include "ext2_fs.h"
#include "imagehdr.h"

#define min(a,b) ((a) <= (b) ? (a) : (b))
Mac Newbold's avatar
Mac Newbold committed
24

25 26 27
/* Why is this not defined in a public header file? */
#define ACTIVE		0x80
#define BOOT_MAGIC	0xAA55
28 29 30 31 32
#ifndef DOSPTYP_LINSWP
#define	DOSPTYP_LINSWP	0x82	/* Linux swap partition */
#define	DOSPTYP_LINUX	0x83	/* Linux partition */
#define	DOSPTYP_EXT	5	/* DOS extended partition */
#endif
33 34 35

int	infd, outfd;
int	secsize	  = 512;	/* XXX bytes. */
36
int	debug	  = 1;
37 38
int     info      = 0;
int     slicemode = 0;
39
int     maxmode   = 0;
40 41 42 43 44 45 46 47 48 49 50 51 52 53
int     slice     = 0;
long	dev_bsize = 1;

/*
 * We want to be able to compress slices by themselves, so we need
 * to know where the slice starts when reading the input file for
 * compression. 
 *
 * These numbers are in sectors.
 */
long	inputminsec	= 0;
long    inputmaxsec	= 0;	/* 0 means the entire input image */

/*
54
 * A list of data ranges. 
55
 */
56
struct range {
57 58
	unsigned long	start;		/* In sectors */
	unsigned long	size;		/* In sectors */
59
	struct range	*next;
60
};
61 62 63 64 65 66
struct range	*ranges, *skips;
int		numranges, numskips;
void	addskip(unsigned long start, unsigned long size);
void	dumpskips(void);
void	sortskips(void);
void    makeranges(void);
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

/* Forward decls */
#ifndef linux
int	read_image(void);
int	read_bsdslice(int slice, u_int32_t start, u_int32_t size);
int	read_bsdpartition(struct disklabel *dlabel, int part);
int	read_bsdcg(struct fs *fsp, struct cg *cgp, unsigned int dboff);
#endif
int	read_linuxslice(int slice, u_int32_t start, u_int32_t size);
int	read_linuxswap(int slice, u_int32_t start, u_int32_t size);
int	read_linuxgroup(struct ext2_super_block *super,
			struct ext2_group_desc	*group,
			int index, u_int32_t diskoffset);
int	compress_image(void);
void	usage(void);

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
#ifdef linux
#define devlseek	lseek
#define devread		read
#else
static inline off_t devlseek(int fd, off_t off, int whence)
{
	off_t noff;
	assert((off & (DEV_BSIZE-1)) == 0);
	noff = lseek(fd, off, whence);
	assert(noff == (off_t)-1 || (noff & (DEV_BSIZE-1)) == 0);
	return noff;
}

static inline int devread(int fd, void *buf, size_t size)
{
	assert((size & (DEV_BSIZE-1)) == 0);
	return read(fd, buf, size);
}
#endif

103 104 105 106 107 108 109 110
/*
 * Wrap up write in a retry mechanism to protect against transient NFS
 * errors causing a fatal error. 
 */
ssize_t
mywrite(int fd, const void *buf, size_t nbytes)
{
	int		cc, i, count = 0;
111 112 113 114 115 116 117
	off_t		startoffset, endoffset;
	int		maxretries = 10;
	
	if ((startoffset = lseek(fd, (off_t) 0, SEEK_CUR)) < 0) {
		perror("mywrite: seeking to get output file ptr");
		exit(1);
	}
118

119 120
	for (i = 0; i < maxretries; i++) {
		while (nbytes) {
121 122 123 124 125 126
			cc = write(fd, buf, nbytes);

			if (cc > 0) {
				nbytes -= cc;
				buf    += cc;
				count  += cc;
127
				continue;
128 129 130 131
			}

			if (i == 0)
				perror("write error: will retry");
132
	
133
			sleep(1);
134 135 136 137
			nbytes += count;
			buf    -= count;
			count   = 0;
			goto again;
138
		}
139 140 141 142 143 144 145 146 147
		if (fsync(fd) < 0) {
			perror("fsync error: will retry");
			sleep(1);
			nbytes += count;
			buf    -= count;
			count   = 0;
			goto again;
		}
		return count;
148
	again:
149 150 151 152
		if (lseek(fd, startoffset, SEEK_SET) < 0) {
			perror("mywrite: seeking to set file ptr");
			exit(1);
		}
153
	}
154 155 156
	perror("write error: busted for too long");
	fflush(stderr);
	exit(1);
157 158
}

159 160
/* Map partition number to letter */
#define BSDPARTNAME(i)       ("ABCDEFGHIJK"[(i)])
Mac Newbold's avatar
Mac Newbold committed
161 162

int
163 164 165
main(argc, argv)
	int argc;
	char *argv[];
Mac Newbold's avatar
Mac Newbold committed
166
{
167 168 169 170 171
	int	ch, rval;
	char	*outfilename = 0;
	int	linuxfs	  = 0;
	int	bsdfs     = 0;
	int	rawmode	  = 0;
Mac Newbold's avatar
Mac Newbold committed
172

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
	while ((ch = getopt(argc, argv, "lbdihrs:c:")) != -1)
		switch(ch) {
		case 'i':
			info++;
			break;
		case 'd':
			debug++;
			break;
		case 'l':
			linuxfs++;
			break;
		case 'b':
			bsdfs++;
			break;
		case 'r':
			rawmode++;
			break;
		case 's':
			slicemode = 1;
			slice = atoi(optarg);
			break;
		case 'c':
195
			maxmode     = 1;
196 197 198 199 200 201 202 203 204
			inputmaxsec = atoi(optarg);
			break;
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;
Mac Newbold's avatar
Mac Newbold committed
205

206 207
	if (argc < 1 || argc > 2)
		usage();
Mac Newbold's avatar
Mac Newbold committed
208

209 210 211 212
	if (slicemode && (slice < 1 || slice > 4)) {
		fprintf(stderr, "Slice must be a DOS partition (1-4)\n\n");
		usage();
	}
213 214 215
	if (maxmode && slicemode) {
		fprintf(stderr, "Count option (-c) cannot be used with "
			"the slice (-s) option\n\n");
216 217 218 219 220 221 222 223 224
		usage();
	}
	if (linuxfs && bsdfs) {
		fprintf(stderr, "Cannot specify linux and bsd together!\n\n");
		usage();
	}
	if (!info && argc != 2) {
		fprintf(stderr, "Must specify an output filename!\n\n");
		usage();
Mac Newbold's avatar
Mac Newbold committed
225
	}
226 227
	else
		outfilename = argv[1];
Mac Newbold's avatar
Mac Newbold committed
228

229 230 231 232
	if (info && !debug)
		debug++;

	if ((infd = open(argv[0], O_RDONLY, 0)) < 0) {
Mac Newbold's avatar
Mac Newbold committed
233 234 235
		perror("opening input file");
		exit(1);
	}
236 237 238 239 240 241 242 243 244 245 246

	if (linuxfs)
		rval = read_linuxslice(0, 0, 0);
#ifndef linux
	else if (bsdfs)
		rval = read_bsdslice(0, 0, 0);
	else if (rawmode)
		rval = read_raw();
	else
		rval = read_image();
#endif
247
	sortskips();
248
	if (debug > 2)
249 250
		dumpskips();
	makeranges();
251 252 253 254 255 256

	if (info) {
		close(infd);
		exit(0);
	}

257
	if ((outfd = open(outfilename, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0) {
Mac Newbold's avatar
Mac Newbold committed
258 259 260
		perror("opening output file");
		exit(1);
	}
261 262 263 264
	compress_image();
	
	close(infd);
	close(outfd);
265
	exit(0);
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
}

#ifndef linux
/*
 * Parse the DOS partition table and dispatch to the individual readers.
 */
int
read_image()
{
	int		i, cc, rval = 0;
	struct doslabel {
		char		align[sizeof(short)];	/* Force alignment */
		char		pad2[DOSPARTOFF];
		struct dos_partition parts[NDOSPART];
		unsigned short  magic;
	} doslabel;
#define DOSPARTSIZE \
	(DOSPARTOFF + sizeof(doslabel.parts) + sizeof(doslabel.magic))

285
	if ((cc = devread(infd, doslabel.pad2, DOSPARTSIZE)) < 0) {
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
		warn("Could not read DOS label");
		return 1;
	}
	if (cc != DOSPARTSIZE) {
		warnx("Could not get the entire DOS label");
 		return 1;
	}
	if (doslabel.magic != BOOT_MAGIC) {
		warnx("Wrong magic number in DOS partition table");
 		return 1;
	}

	if (debug) {
		printf("DOS Partitions:\n");
		for (i = 0; i < NDOSPART; i++) {
			printf("  P%d: ", i + 1 /* DOS Numbering */);
			switch (doslabel.parts[i].dp_typ) {
			case DOSPTYP_386BSD:
				printf("  BSD   ");
				break;
			case DOSPTYP_LINSWP:
				printf("  LSWAP ");
				break;
			case DOSPTYP_LINUX:
				printf("  LINUX ");
				break;
			case DOSPTYP_EXT:
				printf("  DOS   ");
				break;
			default:
				printf("  UNKNO ");
			}
			printf("  start %9d, size %9d\n", 
			       doslabel.parts[i].dp_start,
			       doslabel.parts[i].dp_size);
		}
		printf("\n");
	}

	/*
	 * In slicemode, we need to set the bounds of compression.
327 328 329 330 331 332
	 * Slice is a DOS partition number (1-4). If not in slicemode,
	 * we have to set the bounds according to the doslabel since its
	 * possible that someone (Mike!) will create a disk with empty
	 * space before the first partition or after the last partition!
	 * However, do not set the inputminsec since we usually want the
	 * stuff before the first partition, which is the boot stuff.
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
	 */
	if (slicemode) {
		inputminsec = doslabel.parts[slice-1].dp_start;
		inputmaxsec = doslabel.parts[slice-1].dp_start +
			      doslabel.parts[slice-1].dp_size;
	}

	/*
	 * Now operate on individual slices. 
	 */
	for (i = 0; i < NDOSPART; i++) {
		unsigned char	type  = doslabel.parts[i].dp_typ;
		u_int32_t	start = doslabel.parts[i].dp_start;
		u_int32_t	size  = doslabel.parts[i].dp_size;

		if (slicemode && i != (slice - 1) /* DOS Numbering */)
			continue;
		
		switch (type) {
		case DOSPTYP_386BSD:
			rval = read_bsdslice(i, start, size);
			break;
		case DOSPTYP_LINSWP:
			rval = read_linuxswap(i, start, size);
			break;
		case DOSPTYP_LINUX:
			rval = read_linuxslice(i, start, size);
			break;
361 362 363
		default:
			printf("  Slice %d is an unknown type. Skipping ...\n",
			       i + 1 /* DOS Numbering */);
364 365
			if (start != size)
				addskip(start, size);
366
		}
367 368 369
		if (rval) {
			warnx("Stopping zip at Slice %d",
			      i + 1 /* DOS Number */);
370
			return rval;
371
		}
372 373 374 375 376
		
		if (!slicemode && !maxmode) {
			if (start + size > inputmaxsec)
				inputmaxsec = start + size;
		}
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
	}

	return rval;
}

/*
 * Operate on a BSD slice
 */
int
read_bsdslice(int slice, u_int32_t start, u_int32_t size)
{
	int		cc, i, rval = 0;
	union {
		struct disklabel	label;
		char			pad[BBSIZE];
	} dlabel;

	if (debug)
		printf("  P%d (BSD Slice)\n", slice + 1 /* DOS Numbering */);
	
397
	if (devlseek(infd, ((off_t)start) * secsize, SEEK_SET) < 0) {
398 399 400 401 402 403 404
		warn("Could not seek to beginning of BSD slice");
		return 1;
	}

	/*
	 * Then seek ahead to the disklabel.
	 */
405
	if (devlseek(infd, (off_t)(LABELSECTOR * secsize), SEEK_CUR) < 0) {
406 407 408 409
		warn("Could not seek to beginning of BSD disklabel");
		return 1;
	}

410
	if ((cc = devread(infd, &dlabel, sizeof(dlabel))) < 0) {
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
		warn("Could not read BSD disklabel");
		return 1;
	}
	if (cc != sizeof(dlabel)) {
		warnx("Could not get the entire BSD disklabel");
 		return 1;
	}

	/*
	 * Be sure both magic numbers are correct.
	 */
	if (dlabel.label.d_magic  != DISKMAGIC ||
	    dlabel.label.d_magic2 != DISKMAGIC) {
		warnx("Wrong magic number is BSD disklabel");
 		return 1;
	}

	/*
	 * Now scan partitions.
	 */
	for (i = 0; i < MAXPARTITIONS; i++) {
		if (! dlabel.label.d_partitions[i].p_size)
			continue;

		if (dlabel.label.d_partitions[i].p_fstype == FS_UNUSED)
			continue;

		if (debug) {
			printf("    %c ", BSDPARTNAME(i));

			printf("  start %9d, size %9d\t(%s)\n",
			   dlabel.label.d_partitions[i].p_offset,
			   dlabel.label.d_partitions[i].p_size,
			   fstypenames[dlabel.label.d_partitions[i].p_fstype]);
		}

		rval = read_bsdpartition(&dlabel.label, i);
		if (rval)
			return rval;
	}
	
	return 0;
}

/*
 * BSD partition table offsets are relative to the start of the raw disk.
 * Very convenient.
 */
int
read_bsdpartition(struct disklabel *dlabel, int part)
{
	int		i, cc, rval = 0;
	union {
		struct fs fs;
		char pad[MAXBSIZE];
	} fs;
	union {
		struct cg cg;
		char pad[MAXBSIZE];
	} cg;
	u_int32_t	size, offset;

	offset = dlabel->d_partitions[part].p_offset;
	size   = dlabel->d_partitions[part].p_size;
	
	if (dlabel->d_partitions[part].p_fstype == FS_SWAP) {
477
		addskip(offset, size);
478 479 480 481 482 483 484 485
		return 0;
	}

	if (dlabel->d_partitions[part].p_fstype != FS_BSDFFS) {
		warnx("BSD Partition %d: Not a BSD Filesystem", part);
		return 1;
	}

486
	if (devlseek(infd, ((off_t)offset * (off_t) secsize) + SBOFF,
487 488 489 490 491
		  SEEK_SET) < 0) {
		warnx("BSD Partition %d: Could not seek to superblock", part);
		return 1;
	}

492
	if ((cc = devread(infd, &fs, SBSIZE)) < 0) {
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
		warn("BSD Partition %d: Could not read superblock", part);
		return 1;
	}
	if (cc != SBSIZE) {
		warnx("BSD Partition %d: Truncated superblock", part);
		return 1;
	}
 	if (fs.fs.fs_magic != FS_MAGIC) {
		warnx("BSD Partition %d: Bad magic number in superblock",part);
 		return (1);
 	}

	if (debug) {
		printf("        bfree %9d, size %9d\n",
		       fs.fs.fs_cstotal.cs_nbfree, fs.fs.fs_bsize);
	}

	for (i = 0; i < fs.fs.fs_ncg; i++) {
		unsigned long	cgoff, dboff;

		cgoff = fsbtodb(&fs.fs, cgtod(&fs.fs, i)) + offset;
		dboff = fsbtodb(&fs.fs, cgstart(&fs.fs, i)) + offset;

516
		if (devlseek(infd, (off_t)cgoff * (off_t)secsize, SEEK_SET) < 0) {
517 518 519 520
			warn("BSD Partition %d: Could not seek to cg %d",
			     part, i);
			return 1;
		}
521
		if ((cc = devread(infd, &cg, fs.fs.fs_bsize)) < 0) {
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
			warn("BSD Partition %d: Could not read cg %d",
			     part, i);
			return 1;
		}
		if (cc != fs.fs.fs_bsize) {
			warn("BSD Partition %d: Truncated cg %d", part, i);
			return 1;
		}
		if (debug > 1) {
			printf("        CG%d\t offset %9d, bfree %6d\n",
			       i, cgoff, cg.cg.cg_cs.cs_nbfree);
		}
		
		rval = read_bsdcg(&fs.fs, &cg.cg, dboff);
		if (rval)
			return rval;
	}

	return rval;
}

int
read_bsdcg(struct fs *fsp, struct cg *cgp, unsigned int dbstart)
{
	int  i, max;
	char *p;
	int count, j;

	max = fsp->fs_fpg;
	p   = cg_blksfree(cgp);

	/*
	 * XXX The bitmap is fragments, not FS blocks.
	 *
	 * The block bitmap lists blocks relative to the start of the
	 * cylinder group. cgdmin() is the first actual datablock, but
	 * the bitmap includes all the blocks used for all the blocks
	 * comprising the cg. These include superblock/cg/inodes/datablocks.
	 * The "dbstart" parameter is thus the beginning of the cg, to which
	 * we add the bitmap offset. All blocks before cgdmin() will always
	 * be allocated, but we scan them anyway. 
	 */

	if (debug > 2)
		printf("                 ");
	for (count = i = 0; i < max; i++)
		if (isset(p, i)) {
			j = i;
			while ((i+1)<max && isset(p, i+1))
				i++;
#if 1
			if (i != j && i-j >= 31) {
#else
			if (i-j >= 0) {
#endif
				unsigned long	dboff =
					dbstart + fsbtodb(fsp, j);

				unsigned long	dbcount =
					fsbtodb(fsp, (i-j) + 1);
					
				if (debug > 2) {
					if (count)
						printf(",%s",
						       count % 4 ? " " :
						       "\n                 ");
					printf("%u:%d", dboff, dbcount);
				}
590
				addskip(dboff, dbcount);
591 592 593 594 595 596 597 598 599 600 601
				count++;
			}
		}
	if (debug > 2)
		printf("\n");
	return 0;
}
#endif

/*
 * Operate on a linux slice. I actually don't have a clue what a linux
602
 * slice looks like. I just know that in our images, the linux slice
603 604 605 606 607 608 609 610 611 612 613 614
 * has the boot block in the first sector, part of the boot in the
 * second sector, and then the superblock for the one big filesystem
 * in the 3rd sector. Just start there and move on.
 *
 * Unlike BSD partitions, linux block offsets are from the start of the
 * slice, so we have to add the starting sector to everything. 
 */
int
read_linuxslice(int slice, u_int32_t start, u_int32_t size)
{
#define LINUX_SUPERBLOCK_OFFSET	1024
#define LINUX_SUPERBLOCK_SIZE 	1024
615 616
#define LINUX_GRPSPERBLK	\
	(LINUX_SUPERBLOCK_SIZE/sizeof(struct ext2_group_desc))
617

618 619 620
	int			cc, i, numgroups, rval = 0;
	struct ext2_super_block	fs;
	struct ext2_group_desc	groups[LINUX_GRPSPERBLK];
621
	int			dosslice = slice + 1; /* DOS Numbering */
622 623 624

	assert((sizeof(fs) & ~LINUX_SUPERBLOCK_SIZE) == 0);
	assert((sizeof(groups) & ~LINUX_SUPERBLOCK_SIZE) == 0);
625 626

	if (debug)
627
		printf("  P%d (Linux Slice)\n", dosslice);
628 629 630 631
	
	/*
	 * Skip ahead to the superblock.
	 */
632
	if (devlseek(infd, (((off_t)start) * secsize) +
633
		  LINUX_SUPERBLOCK_OFFSET, SEEK_SET) < 0) {
634 635
		warnx("Linux Slice %d: Could not seek to superblock",
		      dosslice);
636 637 638
		return 1;
	}

639
	if ((cc = devread(infd, &fs, LINUX_SUPERBLOCK_SIZE)) < 0) {
640
		warn("Linux Slice %d: Could not read superblock", dosslice);
641 642 643
		return 1;
	}
	if (cc != LINUX_SUPERBLOCK_SIZE) {
644
		warnx("Linux Slice %d: Truncated superblock", dosslice);
645 646
		return 1;
	}
647
 	if (fs.s_magic != EXT2_SUPER_MAGIC) {
648 649
		warnx("Linux Slice %d: Bad magic number in superblock",
		      dosslice);
650 651 652 653 654
 		return (1);
 	}

	if (debug) {
		printf("        bfree %9d, size %9d\n",
655
		       fs.s_free_blocks_count, EXT2_BLOCK_SIZE(&fs));
656
	}
657
	numgroups = fs.s_blocks_count / fs.s_blocks_per_group;
658 659 660 661 662 663 664 665 666
	
	/*
	 * Read each group descriptor. It says where the free block bitmap
	 * lives. The absolute block numbers a group descriptor refers to
	 * is determined by its index * s_blocks_per_group. Once we know where
	 * the bitmap lives, we can go out to the bitmap and see what blocks
	 * are free.
	 */
	for (i = 0; i <= numgroups; i++) {
667 668 669 670 671 672 673 674 675 676
		int gix;
		off_t soff;

		gix = (i % LINUX_GRPSPERBLK);
		if (gix == 0) {
			soff = ((off_t)start * secsize) +
				((off_t)(EXT2_BLOCK_SIZE(&fs) +
					 (i * sizeof(struct ext2_group_desc))));
			if (devlseek(infd, soff, SEEK_SET) < 0) {
				warnx("Linux Slice %d: "
677 678
				      "Could not seek to Group %d",
				      dosslice, i);
679 680
				return 1;
			}
681

682 683
			if ((cc = devread(infd, groups, sizeof(groups))) < 0) {
				warn("Linux Slice %d: "
684 685
				     "Could not read Group %d",
				     dosslice, i);
686 687 688 689
				return 1;
			}
			if (cc != sizeof(groups)) {
				warnx("Linux Slice %d: "
690
				      "Truncated Group %d", dosslice, i);
691 692
				return 1;
			}
693
		}
694

695 696 697 698 699
		if (debug) {
			printf("        Group:%-2d\tBitmap %9d, bfree %9d\n",
			       i, groups[gix].bg_block_bitmap,
			       groups[gix].bg_free_blocks_count);
		}
700

701
		rval = read_linuxgroup(&fs, &groups[gix], i, start);
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
	}
	
	return 0;
}

/*
 * A group descriptor says where on the disk the block bitmap is. Its
 * a 1bit per block map, where each bit is a FS block (instead of a
 * fragment like in BSD). Since linux offsets are relative to the start
 * of the slice, need to adjust the numbers using the slice offset.
 */
int
read_linuxgroup(struct ext2_super_block *super,
		struct ext2_group_desc	*group,
		int index,
		u_int32_t sliceoffset /* Sector offset of slice */)
{
	char	*p, bitmap[0x1000];
	int	i, cc, max;
	int	count, j;
	off_t	offset;

	offset  = (off_t)sliceoffset * secsize;
	offset += (off_t)EXT2_BLOCK_SIZE(super) * group->bg_block_bitmap;
726
	if (devlseek(infd, offset, SEEK_SET) < 0) {
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
		warn("Linux Group %d: "
		     "Could not seek to Group bitmap %d", i);
		return 1;
	}

	/*
	 * The bitmap is how big? Just make sure that its what we expect
	 * since I don't know what the rules are.
	 */
	if (EXT2_BLOCK_SIZE(super) != 0x1000 ||
	    super->s_blocks_per_group  != 0x8000) {
		warnx("Linux Group %d: "
		      "Bitmap size not what I expect it to be!", i);
		return 1;
	}

743
	if ((cc = devread(infd, bitmap, sizeof(bitmap))) < 0) {
744 745 746 747 748
		warn("Linux Group %d: "
		     "Could not read Group bitmap", i);
		return 1;
	}
	if (cc != sizeof(bitmap)) {
749
		warnx("Linux Group %d: Truncated Group bitmap", index);
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
		return 1;
	}

	max = super->s_blocks_per_group;
	p   = bitmap;

	/*
	 * XXX The bitmap is FS blocks.
	 *     The bitmap is an "inuse" map, not a free map.
	 */
#define LINUX_FSBTODB(count) \
	((EXT2_BLOCK_SIZE(super) / secsize) * (count))

	if (debug > 2)
		printf("                 ");
	for (count = i = 0; i < max; i++)
		if (!isset(p, i)) {
			j = i;
			while ((i+1)<max && !isset(p, i+1))
				i++;
			if (i != j && i-j >= 7) {
				unsigned long	dboff;
				int		dbcount;

				/*
				 * The offset of this free range, relative
				 * to the start of the disk, is the slice
				 * offset, plus the offset of the group itself,
				 * plus the index of the first free block in
				 * the current range.
				 */
				dboff = sliceoffset +
					LINUX_FSBTODB(index * max) +
					LINUX_FSBTODB(j);

				dbcount = LINUX_FSBTODB((i-j) + 1);
					
				if (debug > 2) {
					if (count)
						printf(",%s",
						       count % 4 ? " " :
						       "\n                 ");
					printf("%u:%d %d:%d",
					       dboff, dbcount, j, i);
				}
795
				addskip(dboff, dbcount);
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
				count++;
			}
		}
	if (debug > 2)
		printf("\n");

	return 0;
}

/*
 * For a linux swap partition, all that matters is the first little
 * bit of it. The rest of it does not need to be written to disk.
 */
int
read_linuxswap(int slice, u_int32_t start, u_int32_t size)
{
	if (debug) {
		printf("  P%d (Linux Swap)\n", slice + 1 /* DOS Numbering */);
		printf("        start %12d, size %9d\n",
		       start, size);
	}

	start += 0x8000 / secsize;
	size  -= 0x8000 / secsize;
	
821
	addskip(start, size);
822 823 824 825 826
	return 0;
}

/*
 * For a raw image (something we know nothing about), we report the size
827
 * and compress the entire thing (that is, there are no skip ranges).
828 829 830 831 832 833
 */
int
read_raw(void)
{
	off_t	size;

834
	if ((size = devlseek(infd, (off_t) 0, SEEK_END)) < 0) {
835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850
		warn("lseeking to end of raw image");
		return 1;
	}

	if (debug) {
		printf("  Raw Image\n");
		printf("        start %12d, size %12qd\n", 0, size);
	}
	return 0;
}

char *usagestr = 
 "usage: imagezip [-idlbhr] [-s #] [-c #] <image | device> [outputfilename]\n"
 " -i              Info mode only. Do not write an output file\n"
 " -d              Turn on debugging. Multiple -d options increase output\n"
 " -r              A `raw' image. No FS compression is attempted\n"
851
 " -c count	   Compress <count> number of sectors (not with slice mode)\n"
852 853 854
 " -s slice        Compress a particular slice (DOS numbering 1-4)\n"
 " -h              Print this help message\n"
 " image | device  The input image or a device special file (ie: /dev/rad2)\n"
855 856 857 858 859
 " outputfilename  The output file, when -i is not specified\n"
 "\n"
 " Debugging options (not to be used by mere mortals!)\n"
 " -l              Linux slice only. Input must be a Linux slice image\n"
 " -b              FreeBSD slice only. Input must be a FreeBSD slice image\n";
860 861 862 863 864 865 866 867 868

void
usage()
{
	fprintf(stderr, usagestr);
	exit(1);
}

void
869
addskip(unsigned long start, unsigned long size)
870
{
871
	struct range	   *skip;
872

873
	if ((skip = (struct range *) malloc(sizeof(*skip))) == NULL) {
874 875 876 877
		fprintf(stderr, "Out of memory!\n");
		exit(1);
	}
	
878 879 880 881 882
	skip->start = start;
	skip->size  = size;
	skip->next  = skips;
	skips       = skip;
	numskips++;
883 884 885 886 887 888
}

/*
 * A very dumb bubblesort!
 */
void
889
sortskips(void)
890
{
891
	struct range	*pskip, tmp, *ptmp;
892 893
	int		changed = 1;

894
	if (!skips)
895 896 897 898 899
		return;
	
	while (changed) {
		changed = 0;

900 901 902 903 904 905
		pskip = skips;
		while (pskip) {
			if (pskip->next &&
			    pskip->start > pskip->next->start) {
				tmp.start = pskip->start;
				tmp.size  = pskip->size;
906

907 908 909 910
				pskip->start = pskip->next->start;
				pskip->size  = pskip->next->size;
				pskip->next->start = tmp.start;
				pskip->next->size  = tmp.size;
911 912 913

				changed = 1;
			}
914
			pskip  = pskip->next;
915 916 917 918 919 920
		}
	}

	/*
	 * No look for contiguous free regions and combine them.
	 */
921 922
	pskip = skips;
	while (pskip) {
923
	again:
924 925 926
		if (pskip->next &&
		    pskip->start + pskip->size == pskip->next->start) {
			pskip->size += pskip->next->size;
927
			
928 929
			ptmp        = pskip->next;
			pskip->next = pskip->next->next;
930 931 932
			free(ptmp);
			goto again;
		}
933
		pskip  = pskip->next;
934 935 936 937
	}
}

void
938
dumpskips(void)
939
{
940
	struct range	*pskip;
941 942
	unsigned long	total = 0;

943
	if (!skips)
944 945
		return;
	
946
	printf("Skip ranges (start/size) in sectors:\n");
947
	
948 949 950 951 952
	pskip = skips;
	while (pskip) {
		printf("  %12d    %9d\n", pskip->start, pskip->size);
		total += pskip->size;
		pskip  = pskip->next;
953 954 955 956 957 958 959
	}
	
	printf("\nTotal Number of Free Sectors: %d (bytes %qd)\n",
	       total, (off_t)total * (off_t)secsize);
}

/*
960 961
 * Life is easier if I think in terms of the valid ranges instead of
 * the free ranges. So, convert them.
962
 */
963 964
void
makeranges(void)
965
{
966 967 968 969 970
	struct range	*pskip, *ptmp, *range, *lastrange = 0;
	unsigned long	offset;
	
	if (!skips)
		return;
971

972 973 974 975
	if (inputminsec)
		offset = inputminsec;
	else
		offset = 0;
976

977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
	pskip = skips;
	while (pskip) {
		if ((range = (struct range *)
		             malloc(sizeof(*range))) == NULL) {
			fprintf(stderr, "Out of memory!\n");
			exit(1);
		}
		if (! ranges)
			ranges = range;
			
		range->start = offset;
		range->size  = pskip->start - offset;
		range->next  = 0;
		offset       = pskip->start + pskip->size;
		
		if (lastrange)
			lastrange->next = range;
		lastrange = range;
		numranges++;

		ptmp  = pskip;
		pskip = pskip->next;
		free(ptmp);
	}
1001
	/*
1002
	 * Last piece, but only if there is something to compress.
1003
	 */
1004 1005 1006 1007 1008 1009
	if (!inputmaxsec || (inputmaxsec - offset)) {
		if ((range = (struct range *) malloc(sizeof(*range))) == NULL){
			fprintf(stderr, "Out of memory!\n");
			exit(1);
		}
		range->start = offset;
1010
	
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
		/*
		 * A bug in FreeBSD causes lseek on a device special file to
		 * return 0 all the time! Well we want to be able to read
		 * directly out of a raw disk (/dev/rad0), so we need to
		 * use the compressor to figure out the actual size when it
		 * isn't known beforehand.
		 *
		 * Mark the last range with 0 so compression goes to end
		 * if we don't know where it is.
		 */
		if (inputmaxsec) {
			range->size = inputmaxsec - offset;
		}
		else
			range->size = 0;
		lastrange->next = range;
		lastrange = range;
		numranges++;
	}
1030

1031
	if (debug > 2) {
1032 1033 1034 1035 1036
		range = ranges;
		while (range) {
			printf("  %12d    %9d\n", range->start, range->size);
			range  = range->next;
		}
1037
	}
1038 1039 1040 1041 1042
}

/*
 * Compress the image.
 */
1043 1044 1045
static u_char   output_buffer[SUBBLOCKSIZE];
static int	buffer_offset;

1046 1047 1048 1049
int
compress_image(void)
{
	int		cc, partial, i, count;
1050
	off_t		inputoffset, size, outputoffset;
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
	off_t		tmpoffset, rangesize, totalinput = 0;
	struct range	*prange = ranges;
	struct blockhdr *blkhdr;
	struct region	*curregion, *regions;
	off_t		compress_chunk(off_t, int *, unsigned long *);
	int		compress_finish(unsigned long *subblksize);
	struct timeval  stamp, estamp;
	char		buf[DEFAULTREGIONSIZE];

	gettimeofday(&stamp, 0);

	bzero(buf, sizeof(buf));
	blkhdr    = (struct blockhdr *) buf;
	regions   = (struct region *) (blkhdr + 1);
	curregion = regions;
1066 1067

	/*
1068
	 * No free blocks? Compress the entire input file. 
1069
	 */
1070
	if (!prange) {
1071
		if (inputminsec)
1072
			inputoffset = (off_t) inputminsec * (off_t) secsize;
1073
		else
1074 1075
			inputoffset = (off_t) 0;

1076
		if (devlseek(infd, inputoffset, SEEK_SET) < 0) {
1077
			warn("Could not seek to start of image");
Mac Newbold's avatar
Mac Newbold committed
1078 1079
			exit(1);
		}
1080
		
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
		/*
		 * We keep a seperate output offset counter, which is always
		 * relative to zero since images are generally layed down in a
		 * partition or slice, and where it came from in the input
		 * image is irrelevant.
		 */
		outputoffset = (off_t) 0;
	again:
		/*
		 * Reserve room for the subblock hdr and the region pairs.
		 * We go back and fill it it later after the subblock is
1092 1093
		 * done and we know much input data was compressed into
		 * the block. 
1094
		 */
1095
		buffer_offset = DEFAULTREGIONSIZE;
1096
		
1097
		if (debug) {
1098
			printf("Compressing range: %14qd --> ", inputoffset);
1099 1100
			fflush(stdout);
		}
Mac Newbold's avatar
Mac Newbold committed
1101

1102 1103 1104 1105 1106 1107 1108
		/*
		 * A bug in FreeBSD causes lseek on a device special file to
		 * return 0 all the time! Well we want to be able to read
		 * directly out of a raw disk (/dev/rad0), so we need to
		 * use the compressor to figure out the actual size when it
		 * isn't known beforehand.
		 */
1109 1110 1111
		if (inputmaxsec)
			size = ((inputmaxsec - inputminsec) * (off_t) secsize)
				- totalinput;
1112
		else
1113 1114 1115
			size = 0;

		size = compress_chunk(size, &partial, &blkhdr->size);
1116 1117
	
		if (debug)
1118
			printf("%12qd\n", inputoffset + size);
Mac Newbold's avatar
Mac Newbold committed
1119

1120 1121
		totalinput += size;

1122
		/*
1123 1124 1125
		 * The last subblock is special if inputmaxsec is nonzero.
		 * In that case, we have to finish off the compression block
		 * since the compressor will not have known to do that.
1126
		 */
1127 1128
		if (! partial)
			compress_finish(&blkhdr->size);
Mac Newbold's avatar
Mac Newbold committed
1129

1130
		/*
1131 1132 1133
		 * Go back and stick in the block header and the region
		 * information. Since there are no skip ranges, there
		 * is but a single region and it covers the entire block.
1134
		 */
1135 1136 1137 1138 1139
		blkhdr->magic       = COMPRESSED_MAGIC;
		blkhdr->regionsize  = DEFAULTREGIONSIZE;
		blkhdr->regioncount = 1;
		curregion->start    = outputoffset / secsize;
		curregion->size     = size / secsize;
1140
		curregion++;
1141 1142

		/*
1143
		 * Stash the region info into the beginning of the block.
1144
		 */
1145
		memcpy(output_buffer, buf, sizeof(buf));
1146 1147

		if (partial) {
1148 1149 1150 1151 1152 1153
			/*
			 * Write out the finished chunk to disk, and
			 * start over from the beginning of the buffer.
			 */
			mywrite(outfd, output_buffer, sizeof(output_buffer));
			buffer_offset = 0;
1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
			
			outputoffset += size;
			inputoffset  += size;
			curregion     = regions;
			goto again;
		}

		/*
		 * We cannot allow the image to end on a non-sector boundry!
		 */
		if (totalinput & (secsize - 1)) {
			fprintf(stderr,
				"  Input image size (%qd) is not a multiple "
				"of the sector size (%d)\n",
				inputoffset, secsize);
			return 1;
		}
1171 1172 1173 1174 1175 1176 1177 1178 1179
		goto done;
	}

	/*
	 * Loop through the image, compressing the stuff between the
	 * the free block ranges.
	 */

	/*
1180 1181
	 * Reserve room for the subblock hdr and the region pairs.
	 * We go back and fill it it later after the subblock is
1182 1183
	 * done and we know much input data was compressed into
	 * the block.
1184
	 */
1185 1186
	buffer_offset = DEFAULTREGIONSIZE;
	
1187 1188
	while (prange) {
		inputoffset = (off_t) prange->start * (off_t) secsize;
1189 1190 1191 1192

		/*
		 * Seek to the beginning of the data range to compress.
		 */
1193
		devlseek(infd, (off_t) inputoffset, SEEK_SET);
1194 1195

		/*
1196 1197
		 * The amount to compress is the size of the range, which
		 * might be zero if its the last one (size unknown).
1198
		 */
1199
		rangesize = (off_t) prange->size * (off_t) secsize;
1200 1201 1202 1203

		/*
		 * Compress the chunk.
		 */
1204
		if (debug > 0 && debug < 3) {
1205
			printf("Compressing range: %14qd --> ", inputoffset);
1206
			fflush(stdout);
Mac Newbold's avatar
Mac Newbold committed
1207 1208
		}

1209 1210
		size = compress_chunk(rangesize, &partial, &blkhdr->size);
	
1211 1212 1213 1214 1215 1216 1217 1218
		if (debug >= 3) {
			printf("%14qd -> %12qd %10d %10d %10d %d\n",
			       inputoffset, inputoffset + size,
			       prange->start - inputminsec,
			       (int) (size / secsize), 
			       blkhdr->size, partial);
		}
		else if (debug) {
1219 1220
			gettimeofday(&estamp, 0);
			estamp.tv_sec -= stamp.tv_sec;
1221
			printf("%12qd in %ld seconds.\n",
1222
			       inputoffset + size, estamp