1/* $Id: vndcompress.c,v 1.7 2011/09/06 18:45:04 joerg 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
52enum 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 */
61static 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
63static int opmode;
64
65/*
66 * Print usage information, then exit program
67 */
68__dead static void
69usage(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 */
83static void
84vndcompress(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 */
246static uint64_t *
247readheader(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 */
276static void
277vnduncompress(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 */
352int
353main(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