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