xref: /netbsd-src/usr.bin/vndcompress/vndcompress.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /* $Id: vndcompress.c,v 1.6 2009/04/14 07:36:16 lukem 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 
52 enum 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  */
61 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";
62 
63 int opmode;
64 
65 /*
66  * Print usage information, then exit program
67  */
68 void
69 usage(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  */
83 void
84 vndcompress(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  */
246 uint64_t *
247 readheader(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  */
276 void
277 vnduncompress(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  */
352 int
353 main(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