1/* $Id: vndcompress.c,v 1.7 2011/09/06 18:45:04 joerg Exp $ */ 2 3/* 4 * Copyright (c) 2005 by Florian Stoehr <netbsd@wolfnode.de> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by Florian Stoehr 18 * 4. The name of Florian Stoehr may not be used to endorse or promote 19 * products derived from this software without specific prior written 20 * permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY FLORIAN STOEHR ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 24 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 25 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 26 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 * POSSIBILITY OF SUCH DAMAGE. 33 */ 34 35/* 36 * cloop2 - Compressed filesystem images 37 * vndcompress program - Compress/decompress filesystem images to 38 * the cloop2 format 39 */ 40#include <err.h> 41#include <fcntl.h> 42#include <inttypes.h> 43#include <stdarg.h> 44#include <stdio.h> 45#include <stdlib.h> 46#include <string.h> 47#include <unistd.h> 48#include <zlib.h> 49 50#include "vndcompress.h" 51 52enum opermodes { 53 OM_COMPRESS, /* Compress a fs */ 54 OM_UNCOMPRESS, /* Uncompress an image */ 55}; 56 57/* 58 * This is the original header of the Linux files. It is useless 59 * on NetBSD and integrated for compatibility issues only. 60 */ 61static 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"; 62 63static int opmode; 64 65/* 66 * Print usage information, then exit program 67 */ 68__dead static void 69usage(void) 70{ 71 if (opmode == OM_COMPRESS) { 72 printf("usage: vndcompress [-cd] disk/fs-image compressed-image [blocksize]\n"); 73 } else { 74 printf("usage: vnduncompress [-cd] compressed-image disk/fs-image\n"); 75 } 76 77 exit(EXIT_FAILURE); 78} 79 80/* 81 * Compress a given file system 82 */ 83static void 84vndcompress(const char *fs, const char *comp, uint32_t blocksize) 85{ 86 int fd_in, fd_out; 87 int total_blocks, offtable_size; 88 int i; 89 int read_blocksize; 90 off_t fsize, diffatom, cursize; 91 struct cloop_header clh; 92 uint64_t *offtable; 93 uint64_t curoff; 94 unsigned long complen; 95 unsigned char *cb, *ucb; 96 97 fd_in = open(fs, O_RDONLY); 98 99 if (fd_in < 0) 100 err(EXIT_FAILURE, "Cannot open input file \"%s\"", fs); 101 /* NOTREACHED */ 102 103 fd_out = open(comp, O_CREAT | O_TRUNC | O_WRONLY, 104 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 105 106 if (fd_out < 0) 107 err(EXIT_FAILURE, "Cannot create output file \"%s\"", comp); 108 /* NOTREACHED */ 109 110 if ((blocksize % ATOMBLOCK) || (blocksize < ATOMBLOCK)) 111 errx(EXIT_FAILURE, "Invalid block size: %d (Block size must be "\ 112 "a multiple of %d Bytes)", blocksize, ATOMBLOCK); 113 /* NOTREACHED */ 114 115 /* 116 * Init the compression 117 */ 118 119 /* Determine number of total input blocks, round up to complete blocks */ 120 fsize = lseek(fd_in, 0, SEEK_END); 121 lseek(fd_in, 0, SEEK_SET); 122 total_blocks = fsize / blocksize; 123 124 printf("Using blocksize: %d ", blocksize); 125 126 if (fsize % blocksize) { 127 printf("(%d complete and 1 zero-padded blocks)\n", total_blocks); 128 total_blocks++; 129 } else { 130 printf("(%d complete blocks)\n", total_blocks); 131 } 132 133 /* Struct fillup */ 134 memset(&clh, 0, sizeof(struct cloop_header)); 135 memcpy(clh.sh, cloop_sh, strlen(cloop_sh)); 136 137 /* Remember the header is also in network format! */ 138 clh.block_size = SWAPPER32(blocksize); 139 clh.num_blocks = SWAPPER32(total_blocks); 140 141 /* Prepare the offset table (unsigned 64-bit big endian offsets) */ 142 offtable_size = (total_blocks + 1) * sizeof(uint64_t); 143 offtable = (uint64_t *)malloc(offtable_size); 144 145 /* 146 * Setup block buffers. 147 * Since compression may actually stretch a block in bad cases, 148 * make the "compressed" space large enough here. 149 */ 150 ucb = (unsigned char *)malloc(blocksize); 151 cb = (unsigned char *)malloc(blocksize * 2); 152 153 /* 154 * Compression 155 * 156 * We'll leave file caching to the operating system and write 157 * first the (fixed-size) header with dummy-data, then the compressed 158 * blocks block-by-block to disk. After that, we overwrite the offset 159 * table in the image file with the real offset table. 160 */ 161 if ((size_t)write(fd_out, &clh, sizeof(struct cloop_header)) 162 != sizeof(struct cloop_header)) 163 err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); 164 /* NOTREACHED */ 165 166 if (write(fd_out, offtable, offtable_size) < offtable_size) 167 err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); 168 /* NOTREACHED */ 169 170 /* 171 * Offsets are relative to the beginning of the file, not 172 * relative to offset table start 173 */ 174 curoff = sizeof(struct cloop_header) + offtable_size; 175 176 /* Compression loop */ 177 for (i = 0; i < total_blocks; i++) { 178 179 /* By default, assume to read blocksize bytes */ 180 read_blocksize = blocksize; 181 182 /* 183 * However, the last block may be smaller than block size. 184 * If this is the case, pad uncompressed buffer with zeros 185 * (by zero-filling before the read() call) 186 */ 187 if (i == total_blocks - 1) { 188 if (fsize % blocksize) { 189 read_blocksize = fsize % blocksize; 190 memset(ucb, 0x00, blocksize); 191 } 192 } 193 194 if (read(fd_in, ucb, read_blocksize) < read_blocksize) 195 err(EXIT_FAILURE, "Cannot read input file \"%s\"", fs); 196 /* NOTREACHED */ 197 198 complen = blocksize * 2; 199 200 if (compress2(cb, &complen, ucb, blocksize, Z_BEST_COMPRESSION) != Z_OK) 201 errx(EXIT_FAILURE, "Compression failed in block %d", i); 202 /* NOTREACHED */ 203 204 if ((unsigned long)write(fd_out, cb, complen) != complen) 205 err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); 206 /* NOTREACHED */ 207 208 *(offtable + i) = SWAPPER(curoff); 209 curoff += complen; 210 } 211 212 /* Always write +1 block to determine (compressed) block size */ 213 *(offtable + total_blocks) = SWAPPER(curoff); 214 215 /* Fixup compression table */ 216 lseek(fd_out, sizeof(struct cloop_header), SEEK_SET); 217 218 if (write(fd_out, offtable, offtable_size) < offtable_size) 219 err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); 220 /* NOTREACHED */ 221 222 /* Finally, align the image size to be a multiple of ATOMBLOCK bytes */ 223 cursize = lseek(fd_out, 0, SEEK_END); 224 225 if (cursize % ATOMBLOCK) { 226 /* 227 * Reusing the cb buffer here. It *IS* large enough since 228 * blocksize may not be smaller than ATOMBLOCK 229 */ 230 diffatom = (((cursize / ATOMBLOCK) + 1) * ATOMBLOCK) - cursize; 231 memset(cb, 0, blocksize * 2); 232 write(fd_out, cb, diffatom); 233 } 234 235 free(cb); 236 free(ucb); 237 free(offtable); 238 239 close(fd_in); 240 close(fd_out); 241} 242 243/* 244 * Read in header and offset table from compressed image 245 */ 246static uint64_t * 247readheader(int fd, struct cloop_header *clh, off_t *dstart) 248{ 249 uint32_t offtable_size; 250 uint64_t *offt; 251 252 if ((size_t)read(fd, clh, sizeof(struct cloop_header)) 253 != sizeof(struct cloop_header)) 254 return NULL; 255 256 /* Convert endianness */ 257 clh->block_size = SWAPPER32(clh->block_size); 258 clh->num_blocks = SWAPPER32(clh->num_blocks); 259 260 offtable_size = (clh->num_blocks + 1) * sizeof(uint64_t); 261 offt = (uint64_t *)malloc(offtable_size); 262 263 if ((uint32_t)read(fd, offt, offtable_size) != offtable_size) { 264 free(offt); 265 return NULL; 266 } 267 268 *dstart = offtable_size + sizeof(struct cloop_header); 269 270 return offt; 271} 272 273/* 274 * Decompress a given file system image 275 */ 276static void 277vnduncompress(const char *comp, const char *fs) 278{ 279 int fd_in, fd_out; 280 uint32_t i; 281 struct cloop_header clh; 282 uint64_t *offtable; 283 off_t imgofs, datastart; 284 unsigned long complen, uncomplen; 285 unsigned char *cb, *ucb; 286 287 /* 288 * Setup decompression, read in header tables 289 */ 290 fd_in = open(comp, O_RDONLY); 291 292 if (fd_in < 0) 293 err(EXIT_FAILURE, "Cannot open input file \"%s\"", comp); 294 /* NOTREACHED */ 295 296 fd_out = open(fs, O_CREAT | O_TRUNC | O_WRONLY, 297 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 298 299 if (fd_out < 0) 300 err(EXIT_FAILURE, "Cannot create output file \"%s\"", fs); 301 /* NOTREACHED */ 302 303 offtable = readheader(fd_in, &clh, &datastart); 304 305 if (offtable == NULL) 306 errx(EXIT_FAILURE, "Input file \"%s\": Size mismatch, too small to be a valid image", comp); 307 /* NOTREACHED */ 308 309 /* As for the compression, alloc the compressed block bigger */ 310 ucb = (unsigned char *)malloc(clh.block_size); 311 cb = (unsigned char *)malloc(clh.block_size * 2); 312 313 /* 314 * Perform the actual decompression 315 */ 316 for (i = 0; i < clh.num_blocks; i++) { 317 int rc; 318 319 imgofs = SWAPPER(*(offtable + i)); 320 lseek(fd_in, imgofs, SEEK_SET); 321 322 complen = SWAPPER(*(offtable + i + 1)) 323 - SWAPPER(*(offtable + i)); 324 325 if ((unsigned long)read(fd_in, cb, complen) != complen) 326 err(EXIT_FAILURE, "Cannot read compressed block %"PRIu32" from \"%s\"", i, comp); 327 /* NOTREACHED */ 328 329 uncomplen = clh.block_size; 330 rc = uncompress(ucb, &uncomplen, cb, complen); 331 if (rc != Z_OK) 332 errx(EXIT_FAILURE, "Cannot decompress block %"PRIu32" from \"%s\" (rc=%d)", 333 i, comp, rc); 334 /* NOTREACHED */ 335 336 write(fd_out, ucb, clh.block_size); 337 } 338 339 free(cb); 340 free(ucb); 341 free(offtable); 342 343 close(fd_in); 344 close(fd_out); 345} 346 347/* 348 * vndcompress: Handle cloop2-compatible compressed file systems; This is the 349 * user-level configuration program, to be used in conjunction 350 * with the vnd(4) driver. 351 */ 352int 353main(int argc, char **argv) 354{ 355 char *ep, *p; 356 int ch; 357 358 setprogname(argv[0]); 359 360 if ((p = strrchr(argv[0], '/')) == NULL) 361 p = argv[0]; 362 else 363 ++p; 364 if (strcmp(p, "vnduncompress") == 0) 365 opmode = OM_UNCOMPRESS; 366 else if (strcmp(p, "vndcompress") == 0) 367 opmode = OM_COMPRESS; 368 else 369 warnx("unknown program name '%s'", p); 370 371 /* Process command-line options */ 372 while ((ch = getopt(argc, argv, "cd")) != -1) { 373 switch (ch) { 374 case 'c': 375 opmode = OM_COMPRESS; 376 break; 377 378 case 'd': 379 opmode = OM_UNCOMPRESS; 380 break; 381 382 default: 383 usage(); 384 /* NOTREACHED */ 385 } 386 } 387 388 argc -= optind; 389 argv += optind; 390 391 if (argc < 2) { 392 usage(); 393 /* NOTREACHED */ 394 } 395 396 switch (opmode) { 397 case OM_COMPRESS: 398 if (argc > 2) { 399 vndcompress(argv[0], argv[1], strtoul(argv[2], &ep, 10)); 400 } else { 401 vndcompress(argv[0], argv[1], DEF_BLOCKSIZE); 402 } 403 break; 404 405 case OM_UNCOMPRESS: 406 vnduncompress(argv[0], argv[1]); 407 break; 408 } 409 410 exit(EXIT_SUCCESS); 411} 412