/* $Id: vndcompress.c,v 1.6 2009/04/14 07:36:16 lukem Exp $ */ /* * Copyright (c) 2005 by Florian Stoehr * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Florian Stoehr * 4. The name of Florian Stoehr may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY FLORIAN STOEHR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * cloop2 - Compressed filesystem images * vndcompress program - Compress/decompress filesystem images to * the cloop2 format */ #include #include #include #include #include #include #include #include #include #include "vndcompress.h" enum opermodes { OM_COMPRESS, /* Compress a fs */ OM_UNCOMPRESS, /* Uncompress an image */ }; /* * This is the original header of the Linux files. It is useless * on NetBSD and integrated for compatibility issues only. */ static const char *cloop_sh = "#!/bin/sh\n" "#V2.0 Format\n" "insmod cloop.o file=$0 && mount -r -t iso9660 /dev/cloop $1\n" "exit $?\n"; int opmode; /* * Print usage information, then exit program */ void usage(void) { if (opmode == OM_COMPRESS) { printf("usage: vndcompress [-cd] disk/fs-image compressed-image [blocksize]\n"); } else { printf("usage: vnduncompress [-cd] compressed-image disk/fs-image\n"); } exit(EXIT_FAILURE); } /* * Compress a given file system */ void vndcompress(const char *fs, const char *comp, uint32_t blocksize) { int fd_in, fd_out; int total_blocks, offtable_size; int i; int read_blocksize; off_t fsize, diffatom, cursize; struct cloop_header clh; uint64_t *offtable; uint64_t curoff; unsigned long complen; unsigned char *cb, *ucb; fd_in = open(fs, O_RDONLY); if (fd_in < 0) err(EXIT_FAILURE, "Cannot open input file \"%s\"", fs); /* NOTREACHED */ fd_out = open(comp, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd_out < 0) err(EXIT_FAILURE, "Cannot create output file \"%s\"", comp); /* NOTREACHED */ if ((blocksize % ATOMBLOCK) || (blocksize < ATOMBLOCK)) errx(EXIT_FAILURE, "Invalid block size: %d (Block size must be "\ "a multiple of %d Bytes)", blocksize, ATOMBLOCK); /* NOTREACHED */ /* * Init the compression */ /* Determine number of total input blocks, round up to complete blocks */ fsize = lseek(fd_in, 0, SEEK_END); lseek(fd_in, 0, SEEK_SET); total_blocks = fsize / blocksize; printf("Using blocksize: %d ", blocksize); if (fsize % blocksize) { printf("(%d complete and 1 zero-padded blocks)\n", total_blocks); total_blocks++; } else { printf("(%d complete blocks)\n", total_blocks); } /* Struct fillup */ memset(&clh, 0, sizeof(struct cloop_header)); memcpy(clh.sh, cloop_sh, strlen(cloop_sh)); /* Remember the header is also in network format! */ clh.block_size = SWAPPER32(blocksize); clh.num_blocks = SWAPPER32(total_blocks); /* Prepare the offset table (unsigned 64-bit big endian offsets) */ offtable_size = (total_blocks + 1) * sizeof(uint64_t); offtable = (uint64_t *)malloc(offtable_size); /* * Setup block buffers. * Since compression may actually stretch a block in bad cases, * make the "compressed" space large enough here. */ ucb = (unsigned char *)malloc(blocksize); cb = (unsigned char *)malloc(blocksize * 2); /* * Compression * * We'll leave file caching to the operating system and write * first the (fixed-size) header with dummy-data, then the compressed * blocks block-by-block to disk. After that, we overwrite the offset * table in the image file with the real offset table. */ if ((size_t)write(fd_out, &clh, sizeof(struct cloop_header)) != sizeof(struct cloop_header)) err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); /* NOTREACHED */ if (write(fd_out, offtable, offtable_size) < offtable_size) err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); /* NOTREACHED */ /* * Offsets are relative to the beginning of the file, not * relative to offset table start */ curoff = sizeof(struct cloop_header) + offtable_size; /* Compression loop */ for (i = 0; i < total_blocks; i++) { /* By default, assume to read blocksize bytes */ read_blocksize = blocksize; /* * However, the last block may be smaller than block size. * If this is the case, pad uncompressed buffer with zeros * (by zero-filling before the read() call) */ if (i == total_blocks - 1) { if (fsize % blocksize) { read_blocksize = fsize % blocksize; memset(ucb, 0x00, blocksize); } } if (read(fd_in, ucb, read_blocksize) < read_blocksize) err(EXIT_FAILURE, "Cannot read input file \"%s\"", fs); /* NOTREACHED */ complen = blocksize * 2; if (compress2(cb, &complen, ucb, blocksize, Z_BEST_COMPRESSION) != Z_OK) errx(EXIT_FAILURE, "Compression failed in block %d", i); /* NOTREACHED */ if ((unsigned long)write(fd_out, cb, complen) != complen) err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); /* NOTREACHED */ *(offtable + i) = SWAPPER(curoff); curoff += complen; } /* Always write +1 block to determine (compressed) block size */ *(offtable + total_blocks) = SWAPPER(curoff); /* Fixup compression table */ lseek(fd_out, sizeof(struct cloop_header), SEEK_SET); if (write(fd_out, offtable, offtable_size) < offtable_size) err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); /* NOTREACHED */ /* Finally, align the image size to be a multiple of ATOMBLOCK bytes */ cursize = lseek(fd_out, 0, SEEK_END); if (cursize % ATOMBLOCK) { /* * Reusing the cb buffer here. It *IS* large enough since * blocksize may not be smaller than ATOMBLOCK */ diffatom = (((cursize / ATOMBLOCK) + 1) * ATOMBLOCK) - cursize; memset(cb, 0, blocksize * 2); write(fd_out, cb, diffatom); } free(cb); free(ucb); free(offtable); close(fd_in); close(fd_out); } /* * Read in header and offset table from compressed image */ uint64_t * readheader(int fd, struct cloop_header *clh, off_t *dstart) { uint32_t offtable_size; uint64_t *offt; if ((size_t)read(fd, clh, sizeof(struct cloop_header)) != sizeof(struct cloop_header)) return NULL; /* Convert endianness */ clh->block_size = SWAPPER32(clh->block_size); clh->num_blocks = SWAPPER32(clh->num_blocks); offtable_size = (clh->num_blocks + 1) * sizeof(uint64_t); offt = (uint64_t *)malloc(offtable_size); if ((uint32_t)read(fd, offt, offtable_size) != offtable_size) { free(offt); return NULL; } *dstart = offtable_size + sizeof(struct cloop_header); return offt; } /* * Decompress a given file system image */ void vnduncompress(const char *comp, const char *fs) { int fd_in, fd_out; uint32_t i; struct cloop_header clh; uint64_t *offtable; off_t imgofs, datastart; unsigned long complen, uncomplen; unsigned char *cb, *ucb; /* * Setup decompression, read in header tables */ fd_in = open(comp, O_RDONLY); if (fd_in < 0) err(EXIT_FAILURE, "Cannot open input file \"%s\"", comp); /* NOTREACHED */ fd_out = open(fs, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd_out < 0) err(EXIT_FAILURE, "Cannot create output file \"%s\"", fs); /* NOTREACHED */ offtable = readheader(fd_in, &clh, &datastart); if (offtable == NULL) errx(EXIT_FAILURE, "Input file \"%s\": Size mismatch, too small to be a valid image", comp); /* NOTREACHED */ /* As for the compression, alloc the compressed block bigger */ ucb = (unsigned char *)malloc(clh.block_size); cb = (unsigned char *)malloc(clh.block_size * 2); /* * Perform the actual decompression */ for (i = 0; i < clh.num_blocks; i++) { int rc; imgofs = SWAPPER(*(offtable + i)); lseek(fd_in, imgofs, SEEK_SET); complen = SWAPPER(*(offtable + i + 1)) - SWAPPER(*(offtable + i)); if ((unsigned long)read(fd_in, cb, complen) != complen) err(EXIT_FAILURE, "Cannot read compressed block %"PRIu32" from \"%s\"", i, comp); /* NOTREACHED */ uncomplen = clh.block_size; rc = uncompress(ucb, &uncomplen, cb, complen); if (rc != Z_OK) errx(EXIT_FAILURE, "Cannot decompress block %"PRIu32" from \"%s\" (rc=%d)", i, comp, rc); /* NOTREACHED */ write(fd_out, ucb, clh.block_size); } free(cb); free(ucb); free(offtable); close(fd_in); close(fd_out); } /* * vndcompress: Handle cloop2-compatible compressed file systems; This is the * user-level configuration program, to be used in conjunction * with the vnd(4) driver. */ int main(int argc, char **argv) { char *ep, *p; int ch; setprogname(argv[0]); if ((p = strrchr(argv[0], '/')) == NULL) p = argv[0]; else ++p; if (strcmp(p, "vnduncompress") == 0) opmode = OM_UNCOMPRESS; else if (strcmp(p, "vndcompress") == 0) opmode = OM_COMPRESS; else warnx("unknown program name '%s'", p); /* Process command-line options */ while ((ch = getopt(argc, argv, "cd")) != -1) { switch (ch) { case 'c': opmode = OM_COMPRESS; break; case 'd': opmode = OM_UNCOMPRESS; break; default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc < 2) { usage(); /* NOTREACHED */ } switch (opmode) { case OM_COMPRESS: if (argc > 2) { vndcompress(argv[0], argv[1], strtoul(argv[2], &ep, 10)); } else { vndcompress(argv[0], argv[1], DEF_BLOCKSIZE); } break; case OM_UNCOMPRESS: vnduncompress(argv[0], argv[1]); break; } exit(EXIT_SUCCESS); }