1 /*
2  * Copyright (c) 2009, 2010, 2013, 2014
3  * Phillip Lougher <phillip@squashfs.org.uk>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2,
8  * or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  *
19  * gzip_wrapper.c
20  *
21  * Support for ZLIB compression http://www.zlib.net
22  */
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <zlib.h>
28 
29 #include "squashfs_fs.h"
30 #include "gzip_wrapper.h"
31 #include "compressor.h"
32 
33 static struct strategy strategy[] = {
34 	{ "default", Z_DEFAULT_STRATEGY, 0 },
35 	{ "filtered", Z_FILTERED, 0 },
36 	{ "huffman_only", Z_HUFFMAN_ONLY, 0 },
37 	{ "run_length_encoded", Z_RLE, 0 },
38 	{ "fixed", Z_FIXED, 0 },
39 	{ NULL, 0, 0 }
40 };
41 
42 static int strategy_count = 0;
43 
44 /* default compression level */
45 static int compression_level = GZIP_DEFAULT_COMPRESSION_LEVEL;
46 
47 /* default window size */
48 static int window_size = GZIP_DEFAULT_WINDOW_SIZE;
49 
50 /*
51  * This function is called by the options parsing code in mksquashfs.c
52  * to parse any -X compressor option.
53  *
54  * This function returns:
55  *	>=0 (number of additional args parsed) on success
56  *	-1 if the option was unrecognised, or
57  *	-2 if the option was recognised, but otherwise bad in
58  *	   some way (e.g. invalid parameter)
59  *
60  * Note: this function sets internal compressor state, but does not
61  * pass back the results of the parsing other than success/failure.
62  * The gzip_dump_options() function is called later to get the options in
63  * a format suitable for writing to the filesystem.
64  */
gzip_options(char * argv[],int argc)65 static int gzip_options(char *argv[], int argc)
66 {
67 	if(strcmp(argv[0], "-Xcompression-level") == 0) {
68 		if(argc < 2) {
69 			fprintf(stderr, "gzip: -Xcompression-level missing "
70 				"compression level\n");
71 			fprintf(stderr, "gzip: -Xcompression-level it "
72 				"should be 1 >= n <= 9\n");
73 			goto failed;
74 		}
75 
76 		compression_level = atoi(argv[1]);
77 		if(compression_level < 1 || compression_level > 9) {
78 			fprintf(stderr, "gzip: -Xcompression-level invalid, it "
79 				"should be 1 >= n <= 9\n");
80 			goto failed;
81 		}
82 
83 		return 1;
84 	} else if(strcmp(argv[0], "-Xwindow-size") == 0) {
85 		if(argc < 2) {
86 			fprintf(stderr, "gzip: -Xwindow-size missing window "
87 				"	size\n");
88 			fprintf(stderr, "gzip: -Xwindow-size <window-size>\n");
89 			goto failed;
90 		}
91 
92 		window_size = atoi(argv[1]);
93 		if(window_size < 8 || window_size > 15) {
94 			fprintf(stderr, "gzip: -Xwindow-size invalid, it "
95 				"should be 8 >= n <= 15\n");
96 			goto failed;
97 		}
98 
99 		return 1;
100 	} else if(strcmp(argv[0], "-Xstrategy") == 0) {
101 		char *name;
102 		int i;
103 
104 		if(argc < 2) {
105 			fprintf(stderr, "gzip: -Xstrategy missing "
106 							"strategies\n");
107 			goto failed;
108 		}
109 
110 		name = argv[1];
111 		while(name[0] != '\0') {
112 			for(i = 0; strategy[i].name; i++) {
113 				int n = strlen(strategy[i].name);
114 				if((strncmp(name, strategy[i].name, n) == 0) &&
115 						(name[n] == '\0' ||
116 						 name[n] == ',')) {
117 					if(strategy[i].selected == 0) {
118 				 		strategy[i].selected = 1;
119 						strategy_count++;
120 					}
121 					name += name[n] == ',' ? n + 1 : n;
122 					break;
123 				}
124 			}
125 			if(strategy[i].name == NULL) {
126 				fprintf(stderr, "gzip: -Xstrategy unrecognised "
127 					"strategy\n");
128 				goto failed;
129 			}
130 		}
131 
132 		return 1;
133 	}
134 
135 	return -1;
136 
137 failed:
138 	return -2;
139 }
140 
141 
142 /*
143  * This function is called after all options have been parsed.
144  * It is used to do post-processing on the compressor options using
145  * values that were not expected to be known at option parse time.
146  *
147  * This function returns 0 on successful post processing, or
148  *			-1 on error
149  */
gzip_options_post(int block_size)150 static int gzip_options_post(int block_size)
151 {
152 	if(strategy_count == 1 && strategy[0].selected) {
153 		strategy_count = 0;
154 		strategy[0].selected = 0;
155 	}
156 
157 	return 0;
158 }
159 
160 
161 /*
162  * This function is called by mksquashfs to dump the parsed
163  * compressor options in a format suitable for writing to the
164  * compressor options field in the filesystem (stored immediately
165  * after the superblock).
166  *
167  * This function returns a pointer to the compression options structure
168  * to be stored (and the size), or NULL if there are no compression
169  * options
170  *
171  */
gzip_dump_options(int block_size,int * size)172 static void *gzip_dump_options(int block_size, int *size)
173 {
174 	static struct gzip_comp_opts comp_opts;
175 	int i, strategies = 0;
176 
177 	/*
178 	 * If default compression options of:
179 	 * compression-level: 8 and
180 	 * window-size: 15 and
181 	 * strategy_count == 0 then
182 	 * don't store a compression options structure (this is compatible
183 	 * with the legacy implementation of GZIP for Squashfs)
184 	 */
185 	if(compression_level == GZIP_DEFAULT_COMPRESSION_LEVEL &&
186 				window_size == GZIP_DEFAULT_WINDOW_SIZE &&
187 				strategy_count == 0)
188 		return NULL;
189 
190 	for(i = 0; strategy[i].name; i++)
191 		strategies |= strategy[i].selected << i;
192 
193 	comp_opts.compression_level = compression_level;
194 	comp_opts.window_size = window_size;
195 	comp_opts.strategy = strategies;
196 
197 	SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
198 
199 	*size = sizeof(comp_opts);
200 	return &comp_opts;
201 }
202 
203 
204 /*
205  * This function is a helper specifically for the append mode of
206  * mksquashfs.  Its purpose is to set the internal compressor state
207  * to the stored compressor options in the passed compressor options
208  * structure.
209  *
210  * In effect this function sets up the compressor options
211  * to the same state they were when the filesystem was originally
212  * generated, this is to ensure on appending, the compressor uses
213  * the same compression options that were used to generate the
214  * original filesystem.
215  *
216  * Note, even if there are no compressor options, this function is still
217  * called with an empty compressor structure (size == 0), to explicitly
218  * set the default options, this is to ensure any user supplied
219  * -X options on the appending mksquashfs command line are over-ridden
220  *
221  * This function returns 0 on sucessful extraction of options, and
222  *			-1 on error
223  */
gzip_extract_options(int block_size,void * buffer,int size)224 static int gzip_extract_options(int block_size, void *buffer, int size)
225 {
226 	struct gzip_comp_opts *comp_opts = buffer;
227 	int i;
228 
229 	if(size == 0) {
230 		/* Set default values */
231 		compression_level = GZIP_DEFAULT_COMPRESSION_LEVEL;
232 		window_size = GZIP_DEFAULT_WINDOW_SIZE;
233 		strategy_count = 0;
234 		return 0;
235 	}
236 
237 	/* we expect a comp_opts structure of sufficient size to be present */
238 	if(size < sizeof(*comp_opts))
239 		goto failed;
240 
241 	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
242 
243 	/* Check comp_opts structure for correctness */
244 	if(comp_opts->compression_level < 1 ||
245 			comp_opts->compression_level > 9) {
246 		fprintf(stderr, "gzip: bad compression level in "
247 			"compression options structure\n");
248 		goto failed;
249 	}
250 	compression_level = comp_opts->compression_level;
251 
252 	if(comp_opts->window_size < 8 ||
253 			comp_opts->window_size > 15) {
254 		fprintf(stderr, "gzip: bad window size in "
255 			"compression options structure\n");
256 		goto failed;
257 	}
258 	window_size = comp_opts->window_size;
259 
260 	strategy_count = 0;
261 	for(i = 0; strategy[i].name; i++) {
262 		if((comp_opts->strategy >> i) & 1) {
263 			strategy[i].selected = 1;
264 			strategy_count ++;
265 		} else
266 			strategy[i].selected = 0;
267 	}
268 
269 	return 0;
270 
271 failed:
272 	fprintf(stderr, "gzip: error reading stored compressor options from "
273 		"filesystem!\n");
274 
275 	return -1;
276 }
277 
278 
gzip_display_options(void * buffer,int size)279 void gzip_display_options(void *buffer, int size)
280 {
281 	struct gzip_comp_opts *comp_opts = buffer;
282 	int i, printed;
283 
284 	/* we expect a comp_opts structure of sufficient size to be present */
285 	if(size < sizeof(*comp_opts))
286 		goto failed;
287 
288 	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
289 
290 	/* Check comp_opts structure for correctness */
291 	if(comp_opts->compression_level < 1 ||
292 			comp_opts->compression_level > 9) {
293 		fprintf(stderr, "gzip: bad compression level in "
294 			"compression options structure\n");
295 		goto failed;
296 	}
297 	printf("\tcompression-level %d\n", comp_opts->compression_level);
298 
299 	if(comp_opts->window_size < 8 ||
300 			comp_opts->window_size > 15) {
301 		fprintf(stderr, "gzip: bad window size in "
302 			"compression options structure\n");
303 		goto failed;
304 	}
305 	printf("\twindow-size %d\n", comp_opts->window_size);
306 
307 	for(i = 0, printed = 0; strategy[i].name; i++) {
308 		if((comp_opts->strategy >> i) & 1) {
309 			if(printed)
310 				printf(", ");
311 			else
312 				printf("\tStrategies selected: ");
313 			printf("%s", strategy[i].name);
314 			printed = 1;
315 		}
316 	}
317 
318 	if(!printed)
319 		printf("\tStrategies selected: default\n");
320 	else
321 		printf("\n");
322 
323 	return;
324 
325 failed:
326 	fprintf(stderr, "gzip: error reading stored compressor options from "
327 		"filesystem!\n");
328 }
329 
330 
331 /*
332  * This function is called by mksquashfs to initialise the
333  * compressor, before compress() is called.
334  *
335  * This function returns 0 on success, and
336  *			-1 on error
337  */
gzip_init(void ** strm,int block_size,int datablock)338 static int gzip_init(void **strm, int block_size, int datablock)
339 {
340 	int i, j, res;
341 	struct gzip_stream *stream;
342 
343 	if(!datablock || !strategy_count) {
344 		stream = malloc(sizeof(*stream) + sizeof(struct gzip_strategy));
345 		if(stream == NULL)
346 			goto failed;
347 
348 		stream->strategies = 1;
349 		stream->strategy[0].strategy = Z_DEFAULT_STRATEGY;
350 	} else {
351 		stream = malloc(sizeof(*stream) +
352 			sizeof(struct gzip_strategy) * strategy_count);
353 		if(stream == NULL)
354 			goto failed;
355 
356 		memset(stream->strategy, 0, sizeof(struct gzip_strategy) *
357 			strategy_count);
358 
359 		stream->strategies = strategy_count;
360 
361 		for(i = 0, j = 0; strategy[i].name; i++) {
362 			if(!strategy[i].selected)
363 				continue;
364 
365 			stream->strategy[j].strategy = strategy[i].strategy;
366 			if(j) {
367 				stream->strategy[j].buffer = malloc(block_size);
368 				if(stream->strategy[j].buffer == NULL)
369 					goto failed2;
370 			}
371 			j++;
372 		}
373 	}
374 
375 	stream->stream.zalloc = Z_NULL;
376 	stream->stream.zfree = Z_NULL;
377 	stream->stream.opaque = 0;
378 
379 	res = deflateInit2(&stream->stream, compression_level, Z_DEFLATED,
380 		window_size, 8, stream->strategy[0].strategy);
381 	if(res != Z_OK)
382 		goto failed2;
383 
384 	*strm = stream;
385 	return 0;
386 
387 failed2:
388 	for(i = 1; i < stream->strategies; i++)
389 		free(stream->strategy[i].buffer);
390 	free(stream);
391 failed:
392 	return -1;
393 }
394 
395 
gzip_compress(void * strm,void * d,void * s,int size,int block_size,int * error)396 static int gzip_compress(void *strm, void *d, void *s, int size, int block_size,
397 		int *error)
398 {
399 	int i, res;
400 	struct gzip_stream *stream = strm;
401 	struct gzip_strategy *selected = NULL;
402 
403 	stream->strategy[0].buffer = d;
404 
405 	for(i = 0; i < stream->strategies; i++) {
406 		struct gzip_strategy *strategy = &stream->strategy[i];
407 
408 		res = deflateReset(&stream->stream);
409 		if(res != Z_OK)
410 			goto failed;
411 
412 		stream->stream.next_in = s;
413 		stream->stream.avail_in = size;
414 		stream->stream.next_out = strategy->buffer;
415 		stream->stream.avail_out = block_size;
416 
417 		if(stream->strategies > 1) {
418 			res = deflateParams(&stream->stream,
419 				compression_level, strategy->strategy);
420 			if(res != Z_OK)
421 				goto failed;
422 		}
423 
424 		res = deflate(&stream->stream, Z_FINISH);
425 		strategy->length = stream->stream.total_out;
426 		if(res == Z_STREAM_END) {
427 			if(!selected || selected->length > strategy->length)
428 				selected = strategy;
429 		} else if(res != Z_OK)
430 			goto failed;
431 	}
432 
433 	if(!selected)
434 		/*
435 		 * Output buffer overflow.  Return out of buffer space
436 		 */
437 		return 0;
438 
439 	if(selected->buffer != d)
440 		memcpy(d, selected->buffer, selected->length);
441 
442 	return (int) selected->length;
443 
444 failed:
445 	/*
446 	 * All other errors return failure, with the compressor
447 	 * specific error code in *error
448 	 */
449 	*error = res;
450 	return -1;
451 }
452 
453 
gzip_uncompress(void * d,void * s,int size,int outsize,int * error)454 static int gzip_uncompress(void *d, void *s, int size, int outsize, int *error)
455 {
456 	int res;
457 	unsigned long bytes = outsize;
458 
459 	res = uncompress(d, &bytes, s, size);
460 
461 	if(res == Z_OK)
462 		return (int) bytes;
463 	else {
464 		*error = res;
465 		return -1;
466 	}
467 }
468 
469 
gzip_usage()470 void gzip_usage()
471 {
472 	fprintf(stderr, "\t  -Xcompression-level <compression-level>\n");
473 	fprintf(stderr, "\t\t<compression-level> should be 1 .. 9 (default "
474 		"%d)\n", GZIP_DEFAULT_COMPRESSION_LEVEL);
475 	fprintf(stderr, "\t  -Xwindow-size <window-size>\n");
476 	fprintf(stderr, "\t\t<window-size> should be 8 .. 15 (default "
477 		"%d)\n", GZIP_DEFAULT_WINDOW_SIZE);
478 	fprintf(stderr, "\t  -Xstrategy strategy1,strategy2,...,strategyN\n");
479 	fprintf(stderr, "\t\tCompress using strategy1,strategy2,...,strategyN"
480 		" in turn\n");
481 	fprintf(stderr, "\t\tand choose the best compression.\n");
482 	fprintf(stderr, "\t\tAvailable strategies: default, filtered, "
483 		"huffman_only,\n\t\trun_length_encoded and fixed\n");
484 }
485 
486 
487 struct compressor gzip_comp_ops = {
488 	.init = gzip_init,
489 	.compress = gzip_compress,
490 	.uncompress = gzip_uncompress,
491 	.options = gzip_options,
492 	.options_post = gzip_options_post,
493 	.dump_options = gzip_dump_options,
494 	.extract_options = gzip_extract_options,
495 	.display_options = gzip_display_options,
496 	.usage = gzip_usage,
497 	.id = ZLIB_COMPRESSION,
498 	.name = "gzip",
499 	.supported = 1
500 };
501