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