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
33static 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
42static int strategy_count = 0;
43
44/* default compression level */
45static int compression_level = GZIP_DEFAULT_COMPRESSION_LEVEL;
46
47/* default window size */
48static 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 */
65static 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
137failed:
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 */
150static 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 */
172static 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 */
224static 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
271failed:
272	fprintf(stderr, "gzip: error reading stored compressor options from "
273		"filesystem!\n");
274
275	return -1;
276}
277
278
279void 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
325failed:
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 */
338static 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
387failed2:
388	for(i = 1; i < stream->strategies; i++)
389		free(stream->strategy[i].buffer);
390	free(stream);
391failed:
392	return -1;
393}
394
395
396static 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
444failed:
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
454static 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
470void 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
487struct 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