1 /* 2 * Copyright (c) 2009, 2010, 2013, 2014 3 * Phillip Lougher <phillip (at) 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 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 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 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