xref: /netbsd-src/usr.bin/vndcompress/vndcompress.c (revision 404fbe5fb94ca1e054339640cabb2801ce52dd30)
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