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