1/* 2 * Copyright (c) 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 * lzo_wrapper.c 20 * 21 * Support for LZO compression http://www.oberhumer.com/opensource/lzo 22 */ 23 24#include <stdio.h> 25#include <string.h> 26#include <stdlib.h> 27#include <lzo/lzoconf.h> 28#include <lzo/lzo1x.h> 29 30#include "squashfs_fs.h" 31#include "lzo_wrapper.h" 32#include "compressor.h" 33 34static struct lzo_algorithm lzo[] = { 35 { "lzo1x_1", LZO1X_1_MEM_COMPRESS, lzo1x_1_compress }, 36 { "lzo1x_1_11", LZO1X_1_11_MEM_COMPRESS, lzo1x_1_11_compress }, 37 { "lzo1x_1_12", LZO1X_1_12_MEM_COMPRESS, lzo1x_1_12_compress }, 38 { "lzo1x_1_15", LZO1X_1_15_MEM_COMPRESS, lzo1x_1_15_compress }, 39 { "lzo1x_999", LZO1X_999_MEM_COMPRESS, lzo1x_999_wrapper }, 40 { NULL, 0, NULL } 41}; 42 43/* default LZO compression algorithm and compression level */ 44static int algorithm = SQUASHFS_LZO1X_999; 45static int compression_level = SQUASHFS_LZO1X_999_COMP_DEFAULT; 46 47/* user specified compression level */ 48static int user_comp_level = -1; 49 50 51/* 52 * This function is called by the options parsing code in mksquashfs.c 53 * to parse any -X compressor option. 54 * 55 * This function returns: 56 * >=0 (number of additional args parsed) on success 57 * -1 if the option was unrecognised, or 58 * -2 if the option was recognised, but otherwise bad in 59 * some way (e.g. invalid parameter) 60 * 61 * Note: this function sets internal compressor state, but does not 62 * pass back the results of the parsing other than success/failure. 63 * The lzo_dump_options() function is called later to get the options in 64 * a format suitable for writing to the filesystem. 65 */ 66static int lzo_options(char *argv[], int argc) 67{ 68 int i; 69 70 if(strcmp(argv[0], "-Xalgorithm") == 0) { 71 if(argc < 2) { 72 fprintf(stderr, "lzo: -Xalgorithm missing algorithm\n"); 73 fprintf(stderr, "lzo: -Xalgorithm <algorithm>\n"); 74 goto failed2; 75 } 76 77 for(i = 0; lzo[i].name; i++) { 78 if(strcmp(argv[1], lzo[i].name) == 0) { 79 algorithm = i; 80 return 1; 81 } 82 } 83 84 fprintf(stderr, "lzo: -Xalgorithm unrecognised algorithm\n"); 85 goto failed2; 86 } else if(strcmp(argv[0], "-Xcompression-level") == 0) { 87 if(argc < 2) { 88 fprintf(stderr, "lzo: -Xcompression-level missing " 89 "compression level\n"); 90 fprintf(stderr, "lzo: -Xcompression-level it " 91 "should be 1 >= n <= 9\n"); 92 goto failed; 93 } 94 95 user_comp_level = atoi(argv[1]); 96 if(user_comp_level < 1 || user_comp_level > 9) { 97 fprintf(stderr, "lzo: -Xcompression-level invalid, it " 98 "should be 1 >= n <= 9\n"); 99 goto failed; 100 } 101 102 return 1; 103 } 104 105 return -1; 106 107failed: 108 return -2; 109 110failed2: 111 fprintf(stderr, "lzo: compression algorithm should be one of:\n"); 112 for(i = 0; lzo[i].name; i++) 113 fprintf(stderr, "\t%s\n", lzo[i].name); 114 return -2; 115} 116 117 118/* 119 * This function is called after all options have been parsed. 120 * It is used to do post-processing on the compressor options using 121 * values that were not expected to be known at option parse time. 122 * 123 * In this case the LZO algorithm may not be known until after the 124 * compression level has been set (-Xalgorithm used after -Xcompression-level) 125 * 126 * This function returns 0 on successful post processing, or 127 * -1 on error 128 */ 129static int lzo_options_post(int block_size) 130{ 131 /* 132 * Use of compression level only makes sense for 133 * LZO1X_999 algorithm 134 */ 135 if(user_comp_level != -1) { 136 if(algorithm != SQUASHFS_LZO1X_999) { 137 fprintf(stderr, "lzo: -Xcompression-level not " 138 "supported by selected %s algorithm\n", 139 lzo[algorithm].name); 140 fprintf(stderr, "lzo: -Xcompression-level is only " 141 "applicable for the lzo1x_999 algorithm\n"); 142 goto failed; 143 } 144 compression_level = user_comp_level; 145 } 146 147 return 0; 148 149failed: 150 return -1; 151} 152 153 154/* 155 * This function is called by mksquashfs to dump the parsed 156 * compressor options in a format suitable for writing to the 157 * compressor options field in the filesystem (stored immediately 158 * after the superblock). 159 * 160 * This function returns a pointer to the compression options structure 161 * to be stored (and the size), or NULL if there are no compression 162 * options 163 * 164 */ 165static void *lzo_dump_options(int block_size, int *size) 166{ 167 static struct lzo_comp_opts comp_opts; 168 169 /* 170 * If default compression options of SQUASHFS_LZO1X_999 and 171 * compression level of SQUASHFS_LZO1X_999_COMP_DEFAULT then 172 * don't store a compression options structure (this is compatible 173 * with the legacy implementation of LZO for Squashfs) 174 */ 175 if(algorithm == SQUASHFS_LZO1X_999 && 176 compression_level == SQUASHFS_LZO1X_999_COMP_DEFAULT) 177 return NULL; 178 179 comp_opts.algorithm = algorithm; 180 comp_opts.compression_level = algorithm == SQUASHFS_LZO1X_999 ? 181 compression_level : 0; 182 183 SQUASHFS_INSWAP_COMP_OPTS(&comp_opts); 184 185 *size = sizeof(comp_opts); 186 return &comp_opts; 187} 188 189 190/* 191 * This function is a helper specifically for the append mode of 192 * mksquashfs. Its purpose is to set the internal compressor state 193 * to the stored compressor options in the passed compressor options 194 * structure. 195 * 196 * In effect this function sets up the compressor options 197 * to the same state they were when the filesystem was originally 198 * generated, this is to ensure on appending, the compressor uses 199 * the same compression options that were used to generate the 200 * original filesystem. 201 * 202 * Note, even if there are no compressor options, this function is still 203 * called with an empty compressor structure (size == 0), to explicitly 204 * set the default options, this is to ensure any user supplied 205 * -X options on the appending mksquashfs command line are over-ridden 206 * 207 * This function returns 0 on sucessful extraction of options, and 208 * -1 on error 209 */ 210static int lzo_extract_options(int block_size, void *buffer, int size) 211{ 212 struct lzo_comp_opts *comp_opts = buffer; 213 214 if(size == 0) { 215 /* Set default values */ 216 algorithm = SQUASHFS_LZO1X_999; 217 compression_level = SQUASHFS_LZO1X_999_COMP_DEFAULT; 218 return 0; 219 } 220 221 /* we expect a comp_opts structure of sufficient size to be present */ 222 if(size < sizeof(*comp_opts)) 223 goto failed; 224 225 SQUASHFS_INSWAP_COMP_OPTS(comp_opts); 226 227 /* Check comp_opts structure for correctness */ 228 switch(comp_opts->algorithm) { 229 case SQUASHFS_LZO1X_1: 230 case SQUASHFS_LZO1X_1_11: 231 case SQUASHFS_LZO1X_1_12: 232 case SQUASHFS_LZO1X_1_15: 233 if(comp_opts->compression_level != 0) { 234 fprintf(stderr, "lzo: bad compression level in " 235 "compression options structure\n"); 236 goto failed; 237 } 238 break; 239 case SQUASHFS_LZO1X_999: 240 if(comp_opts->compression_level < 1 || 241 comp_opts->compression_level > 9) { 242 fprintf(stderr, "lzo: bad compression level in " 243 "compression options structure\n"); 244 goto failed; 245 } 246 compression_level = comp_opts->compression_level; 247 break; 248 default: 249 fprintf(stderr, "lzo: bad algorithm in compression options " 250 "structure\n"); 251 goto failed; 252 } 253 254 algorithm = comp_opts->algorithm; 255 256 return 0; 257 258failed: 259 fprintf(stderr, "lzo: error reading stored compressor options from " 260 "filesystem!\n"); 261 262 return -1; 263} 264 265 266void lzo_display_options(void *buffer, int size) 267{ 268 struct lzo_comp_opts *comp_opts = buffer; 269 270 /* we expect a comp_opts structure of sufficient size to be present */ 271 if(size < sizeof(*comp_opts)) 272 goto failed; 273 274 SQUASHFS_INSWAP_COMP_OPTS(comp_opts); 275 276 /* Check comp_opts structure for correctness */ 277 switch(comp_opts->algorithm) { 278 case SQUASHFS_LZO1X_1: 279 case SQUASHFS_LZO1X_1_11: 280 case SQUASHFS_LZO1X_1_12: 281 case SQUASHFS_LZO1X_1_15: 282 printf("\talgorithm %s\n", lzo[comp_opts->algorithm].name); 283 break; 284 case SQUASHFS_LZO1X_999: 285 if(comp_opts->compression_level < 1 || 286 comp_opts->compression_level > 9) { 287 fprintf(stderr, "lzo: bad compression level in " 288 "compression options structure\n"); 289 goto failed; 290 } 291 printf("\talgorithm %s\n", lzo[comp_opts->algorithm].name); 292 printf("\tcompression level %d\n", 293 comp_opts->compression_level); 294 break; 295 default: 296 fprintf(stderr, "lzo: bad algorithm in compression options " 297 "structure\n"); 298 goto failed; 299 } 300 301 return; 302 303failed: 304 fprintf(stderr, "lzo: error reading stored compressor options from " 305 "filesystem!\n"); 306} 307 308 309/* 310 * This function is called by mksquashfs to initialise the 311 * compressor, before compress() is called. 312 * 313 * This function returns 0 on success, and 314 * -1 on error 315 */ 316static int squashfs_lzo_init(void **strm, int block_size, int datablock) 317{ 318 struct lzo_stream *stream; 319 320 stream = *strm = malloc(sizeof(struct lzo_stream)); 321 if(stream == NULL) 322 goto failed; 323 324 stream->workspace = malloc(lzo[algorithm].size); 325 if(stream->workspace == NULL) 326 goto failed2; 327 328 stream->buffer = malloc(LZO_MAX_EXPANSION(block_size)); 329 if(stream->buffer != NULL) 330 return 0; 331 332 free(stream->workspace); 333failed2: 334 free(stream); 335failed: 336 return -1; 337} 338 339 340static int lzo_compress(void *strm, void *dest, void *src, int size, 341 int block_size, int *error) 342{ 343 int res; 344 lzo_uint compsize, orig_size = size; 345 struct lzo_stream *stream = strm; 346 347 res = lzo[algorithm].compress(src, size, stream->buffer, &compsize, 348 stream->workspace); 349 if(res != LZO_E_OK) 350 goto failed; 351 352 /* Successful compression, however, we need to check that 353 * the compressed size is not larger than the available 354 * buffer space. Normally in other compressor APIs they take 355 * a destination buffer size, and overflows return an error. 356 * With LZO it lacks a destination size and so we must output 357 * to a temporary buffer large enough to accomodate any 358 * result, and explictly check here for overflow 359 */ 360 if(compsize > block_size) 361 return 0; 362 363 res = lzo1x_optimize(stream->buffer, compsize, src, &orig_size, NULL); 364 365 if (res != LZO_E_OK || orig_size != size) 366 goto failed; 367 368 memcpy(dest, stream->buffer, compsize); 369 return compsize; 370 371failed: 372 /* fail, compressor specific error code returned in error */ 373 *error = res; 374 return -1; 375} 376 377 378static int lzo_uncompress(void *dest, void *src, int size, int outsize, 379 int *error) 380{ 381 int res; 382 lzo_uint outlen = outsize; 383 384 res = lzo1x_decompress_safe(src, size, dest, &outlen, NULL); 385 if(res != LZO_E_OK) { 386 *error = res; 387 return -1; 388 } 389 390 return outlen; 391} 392 393 394void lzo_usage() 395{ 396 int i; 397 398 fprintf(stderr, "\t -Xalgorithm <algorithm>\n"); 399 fprintf(stderr, "\t\tWhere <algorithm> is one of:\n"); 400 401 for(i = 0; lzo[i].name; i++) 402 fprintf(stderr, "\t\t\t%s%s\n", lzo[i].name, 403 i == SQUASHFS_LZO1X_999 ? " (default)" : ""); 404 405 fprintf(stderr, "\t -Xcompression-level <compression-level>\n"); 406 fprintf(stderr, "\t\t<compression-level> should be 1 .. 9 (default " 407 "%d)\n", SQUASHFS_LZO1X_999_COMP_DEFAULT); 408 fprintf(stderr, "\t\tOnly applies to lzo1x_999 algorithm\n"); 409} 410 411 412/* 413 * Helper function for lzo1x_999 compression algorithm. 414 * All other lzo1x_xxx compressors do not take a compression level, 415 * so we need to wrap lzo1x_999 to pass the compression level which 416 * is applicable to it 417 */ 418int lzo1x_999_wrapper(const lzo_bytep src, lzo_uint src_len, lzo_bytep dst, 419 lzo_uintp compsize, lzo_voidp workspace) 420{ 421 return lzo1x_999_compress_level(src, src_len, dst, compsize, 422 workspace, NULL, 0, 0, compression_level); 423} 424 425 426struct compressor lzo_comp_ops = { 427 .init = squashfs_lzo_init, 428 .compress = lzo_compress, 429 .uncompress = lzo_uncompress, 430 .options = lzo_options, 431 .options_post = lzo_options_post, 432 .dump_options = lzo_dump_options, 433 .extract_options = lzo_extract_options, 434 .display_options = lzo_display_options, 435 .usage = lzo_usage, 436 .id = LZO_COMPRESSION, 437 .name = "lzo", 438 .supported = 1 439}; 440