1 /* $Id: vndcompress.c,v 1.4 2008/02/18 03:34:04 dyoung 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 <stdarg.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <unistd.h> 47 #include <zlib.h> 48 49 #include "vndcompress.h" 50 51 enum opermodes { 52 OM_COMPRESS, /* Compress a fs */ 53 OM_UNCOMPRESS, /* Uncompress an image */ 54 }; 55 56 /* 57 * This is the original header of the Linux files. It is useless 58 * on NetBSD and integrated for compatibility issues only. 59 */ 60 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"; 61 62 int opmode; 63 64 /* 65 * Print usage information, then exit program 66 */ 67 void 68 usage(void) 69 { 70 if (opmode == OM_COMPRESS) { 71 printf("usage: vndcompress [-cd] disk/fs-image compressed-image [blocksize]\n"); 72 } else { 73 printf("usage: vnduncompress [-cd] compressed-image disk/fs-image\n"); 74 } 75 76 exit(EXIT_FAILURE); 77 } 78 79 /* 80 * Compress a given file system 81 */ 82 void 83 vndcompress(const char *fs, const char *comp, uint32_t blocksize) 84 { 85 int fd_in, fd_out; 86 int total_blocks, offtable_size; 87 int i; 88 int read_blocksize; 89 off_t fsize, diffatom, cursize; 90 struct cloop_header clh; 91 uint64_t *offtable; 92 uint64_t curoff; 93 unsigned long complen; 94 unsigned char *cb, *ucb; 95 96 fd_in = open(fs, O_RDONLY); 97 98 if (fd_in < 0) 99 err(EXIT_FAILURE, "Cannot open input file \"%s\"", fs); 100 /* NOTREACHED */ 101 102 fd_out = open(comp, O_CREAT | O_TRUNC | O_WRONLY, 103 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 104 105 if (fd_out < 0) 106 err(EXIT_FAILURE, "Cannot create output file \"%s\"", comp); 107 /* NOTREACHED */ 108 109 if ((blocksize % ATOMBLOCK) || (blocksize < ATOMBLOCK)) 110 errx(EXIT_FAILURE, "Invalid block size: %d (Block size must be "\ 111 "a multiple of %d Bytes)", blocksize, ATOMBLOCK); 112 /* NOTREACHED */ 113 114 /* 115 * Init the compression 116 */ 117 118 /* Determine number of total input blocks, round up to complete blocks */ 119 fsize = lseek(fd_in, 0, SEEK_END); 120 lseek(fd_in, 0, SEEK_SET); 121 total_blocks = fsize / blocksize; 122 123 printf("Using blocksize: %d ", blocksize); 124 125 if (fsize % blocksize) { 126 printf("(%d complete and 1 zero-padded blocks)\n", total_blocks); 127 total_blocks++; 128 } else { 129 printf("(%d complete blocks)\n", total_blocks); 130 } 131 132 /* Struct fillup */ 133 memset(&clh, 0, sizeof(struct cloop_header)); 134 memcpy(clh.sh, cloop_sh, strlen(cloop_sh)); 135 136 /* Remember the header is also in network format! */ 137 clh.block_size = SWAPPER32(blocksize); 138 clh.num_blocks = SWAPPER32(total_blocks); 139 140 /* Prepare the offset table (unsigned 64-bit big endian offsets) */ 141 offtable_size = (total_blocks + 1) * sizeof(uint64_t); 142 offtable = (uint64_t *)malloc(offtable_size); 143 144 /* 145 * Setup block buffers. 146 * Since compression may actually stretch a block in bad cases, 147 * make the "compressed" space large enough here. 148 */ 149 ucb = (unsigned char *)malloc(blocksize); 150 cb = (unsigned char *)malloc(blocksize * 2); 151 152 /* 153 * Compression 154 * 155 * We'll leave file caching to the operating system and write 156 * first the (fixed-size) header with dummy-data, then the compressed 157 * blocks block-by-block to disk. After that, we overwrite the offset 158 * table in the image file with the real offset table. 159 */ 160 if (write(fd_out, &clh, sizeof(struct cloop_header)) 161 < sizeof(struct cloop_header)) 162 err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); 163 /* NOTREACHED */ 164 165 if (write(fd_out, offtable, offtable_size) < offtable_size) 166 err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); 167 /* NOTREACHED */ 168 169 /* 170 * Offsets are relative to the beginning of the file, not 171 * relative to offset table start 172 */ 173 curoff = sizeof(struct cloop_header) + offtable_size; 174 175 /* Compression loop */ 176 for (i = 0; i < total_blocks; i++) { 177 178 /* By default, assume to read blocksize bytes */ 179 read_blocksize = blocksize; 180 181 /* 182 * However, the last block may be smaller than block size. 183 * If this is the case, pad uncompressed buffer with zeros 184 * (by zero-filling before the read() call) 185 */ 186 if (i == total_blocks - 1) { 187 if (fsize % blocksize) { 188 read_blocksize = fsize % blocksize; 189 memset(ucb, 0x00, blocksize); 190 } 191 } 192 193 if (read(fd_in, ucb, read_blocksize) < read_blocksize) 194 err(EXIT_FAILURE, "Cannot read input file \"%s\"", fs); 195 /* NOTREACHED */ 196 197 complen = blocksize * 2; 198 199 if (compress2(cb, &complen, ucb, blocksize, Z_BEST_COMPRESSION) != Z_OK) 200 errx(EXIT_FAILURE, "Compression failed in block %d", i); 201 /* NOTREACHED */ 202 203 if (write(fd_out, cb, complen) < complen) 204 err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); 205 /* NOTREACHED */ 206 207 *(offtable + i) = SWAPPER(curoff); 208 curoff += complen; 209 } 210 211 /* Always write +1 block to determine (compressed) block size */ 212 *(offtable + total_blocks) = SWAPPER(curoff); 213 214 /* Fixup compression table */ 215 lseek(fd_out, sizeof(struct cloop_header), SEEK_SET); 216 217 if (write(fd_out, offtable, offtable_size) < offtable_size) 218 err(EXIT_FAILURE, "Cannot write to output file \"%s\"", comp); 219 /* NOTREACHED */ 220 221 /* Finally, align the image size to be a multiple of ATOMBLOCK bytes */ 222 cursize = lseek(fd_out, 0, SEEK_END); 223 224 if (cursize % ATOMBLOCK) { 225 /* 226 * Reusing the cb buffer here. It *IS* large enough since 227 * blocksize may not be smaller than ATOMBLOCK 228 */ 229 diffatom = (((cursize / ATOMBLOCK) + 1) * ATOMBLOCK) - cursize; 230 memset(cb, 0, blocksize * 2); 231 write(fd_out, cb, diffatom); 232 } 233 234 free(cb); 235 free(ucb); 236 free(offtable); 237 238 close(fd_in); 239 close(fd_out); 240 } 241 242 /* 243 * Read in header and offset table from compressed image 244 */ 245 uint64_t * 246 readheader(int fd, struct cloop_header *clh, off_t *dstart) 247 { 248 uint32_t offtable_size; 249 uint64_t *offt; 250 251 if (read(fd, clh, sizeof(struct cloop_header)) 252 < sizeof(struct cloop_header)) 253 return NULL; 254 255 /* Convert endianness */ 256 clh->block_size = SWAPPER32(clh->block_size); 257 clh->num_blocks = SWAPPER32(clh->num_blocks); 258 259 offtable_size = (clh->num_blocks + 1) * sizeof(uint64_t); 260 offt = (uint64_t *)malloc(offtable_size); 261 262 if (read(fd, offt, offtable_size) < offtable_size) { 263 free(offt); 264 return NULL; 265 } 266 267 *dstart = offtable_size + sizeof(struct cloop_header); 268 269 return offt; 270 } 271 272 /* 273 * Decompress a given file system image 274 */ 275 void 276 vnduncompress(const char *comp, const char *fs) 277 { 278 int fd_in, fd_out; 279 int i; 280 struct cloop_header clh; 281 uint64_t *offtable; 282 off_t imgofs, datastart; 283 unsigned long complen, uncomplen; 284 unsigned char *cb, *ucb; 285 286 /* 287 * Setup decompression, read in header tables 288 */ 289 fd_in = open(comp, O_RDONLY); 290 291 if (fd_in < 0) 292 err(EXIT_FAILURE, "Cannot open input file \"%s\"", comp); 293 /* NOTREACHED */ 294 295 fd_out = open(fs, O_CREAT | O_TRUNC | O_WRONLY, 296 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 297 298 if (fd_out < 0) 299 err(EXIT_FAILURE, "Cannot create output file \"%s\"", fs); 300 /* NOTREACHED */ 301 302 offtable = readheader(fd_in, &clh, &datastart); 303 304 if (offtable == NULL) 305 errx(EXIT_FAILURE, "Input file \"%s\": Size mismatch, too small to be a valid image", comp); 306 /* NOTREACHED */ 307 308 /* As for the compression, alloc the compressed block bigger */ 309 ucb = (unsigned char *)malloc(clh.block_size); 310 cb = (unsigned char *)malloc(clh.block_size * 2); 311 312 /* 313 * Perform the actual decompression 314 */ 315 for (i = 0; i < clh.num_blocks; i++) { 316 int rc; 317 318 imgofs = SWAPPER(*(offtable + i)); 319 lseek(fd_in, imgofs, SEEK_SET); 320 321 complen = SWAPPER(*(offtable + i + 1)) 322 - SWAPPER(*(offtable + i)); 323 324 if (read(fd_in, cb, complen) < complen) 325 err(EXIT_FAILURE, "Cannot read compressed block %d from \"%s\"", i, comp); 326 /* NOTREACHED */ 327 328 uncomplen = clh.block_size; 329 rc = uncompress(ucb, &uncomplen, cb, complen); 330 if (rc != Z_OK) 331 errx(EXIT_FAILURE, "Cannot decompress block %d from \"%s\" (rc=%d)", 332 i, comp, rc); 333 /* NOTREACHED */ 334 335 write(fd_out, ucb, clh.block_size); 336 } 337 338 free(cb); 339 free(ucb); 340 free(offtable); 341 342 close(fd_in); 343 close(fd_out); 344 } 345 346 /* 347 * vndcompress: Handle cloop2-compatible compressed file systems; This is the 348 * user-level configuration program, to be used in conjunction 349 * with the vnd(4) driver. 350 */ 351 int 352 main(int argc, char **argv) 353 { 354 char *ep, *p; 355 int ch; 356 357 setprogname(argv[0]); 358 359 if ((p = strrchr(argv[0], '/')) == NULL) 360 p = argv[0]; 361 else 362 ++p; 363 if (strcmp(p, "vnduncompress") == 0) 364 opmode = OM_UNCOMPRESS; 365 else if (strcmp(p, "vndcompress") == 0) 366 opmode = OM_COMPRESS; 367 else 368 warnx("unknown program name '%s'", p); 369 370 /* Process command-line options */ 371 while ((ch = getopt(argc, argv, "cd")) != -1) { 372 switch (ch) { 373 case 'c': 374 opmode = OM_COMPRESS; 375 break; 376 377 case 'd': 378 opmode = OM_UNCOMPRESS; 379 break; 380 381 default: 382 usage(); 383 /* NOTREACHED */ 384 } 385 } 386 387 argc -= optind; 388 argv += optind; 389 390 if (argc < 2) { 391 usage(); 392 /* NOTREACHED */ 393 } 394 395 switch (opmode) { 396 case OM_COMPRESS: 397 if (argc > 2) { 398 vndcompress(argv[0], argv[1], strtoul(argv[2], &ep, 10)); 399 } else { 400 vndcompress(argv[0], argv[1], DEF_BLOCKSIZE); 401 } 402 break; 403 404 case OM_UNCOMPRESS: 405 vnduncompress(argv[0], argv[1]); 406 break; 407 } 408 409 exit(EXIT_SUCCESS); 410 } 411