18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * LZO decompressor for the Linux kernel. Code borrowed from the lzo 48c2ecf20Sopenharmony_ci * implementation by Markus Franz Xaver Johannes Oberhumer. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Linux kernel adaptation: 78c2ecf20Sopenharmony_ci * Copyright (C) 2009 88c2ecf20Sopenharmony_ci * Albin Tonnerre, Free Electrons <albin.tonnerre@free-electrons.com> 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * Original code: 118c2ecf20Sopenharmony_ci * Copyright (C) 1996-2005 Markus Franz Xaver Johannes Oberhumer 128c2ecf20Sopenharmony_ci * All Rights Reserved. 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * Markus F.X.J. Oberhumer 158c2ecf20Sopenharmony_ci * <markus@oberhumer.com> 168c2ecf20Sopenharmony_ci * http://www.oberhumer.com/opensource/lzop/ 178c2ecf20Sopenharmony_ci */ 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#ifdef STATIC 208c2ecf20Sopenharmony_ci#define PREBOOT 218c2ecf20Sopenharmony_ci#include "lzo/lzo1x_decompress_safe.c" 228c2ecf20Sopenharmony_ci#else 238c2ecf20Sopenharmony_ci#include <linux/decompress/unlzo.h> 248c2ecf20Sopenharmony_ci#endif 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include <linux/types.h> 278c2ecf20Sopenharmony_ci#include <linux/lzo.h> 288c2ecf20Sopenharmony_ci#include <linux/decompress/mm.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#include <linux/compiler.h> 318c2ecf20Sopenharmony_ci#include <asm/unaligned.h> 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic const unsigned char lzop_magic[] = { 348c2ecf20Sopenharmony_ci 0x89, 0x4c, 0x5a, 0x4f, 0x00, 0x0d, 0x0a, 0x1a, 0x0a }; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define LZO_BLOCK_SIZE (256*1024l) 378c2ecf20Sopenharmony_ci#define HEADER_HAS_FILTER 0x00000800L 388c2ecf20Sopenharmony_ci#define HEADER_SIZE_MIN (9 + 7 + 4 + 8 + 1 + 4) 398c2ecf20Sopenharmony_ci#define HEADER_SIZE_MAX (9 + 7 + 1 + 8 + 8 + 4 + 1 + 255 + 4) 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ciSTATIC inline long INIT parse_header(u8 *input, long *skip, long in_len) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci int l; 448c2ecf20Sopenharmony_ci u8 *parse = input; 458c2ecf20Sopenharmony_ci u8 *end = input + in_len; 468c2ecf20Sopenharmony_ci u8 level = 0; 478c2ecf20Sopenharmony_ci u16 version; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* 508c2ecf20Sopenharmony_ci * Check that there's enough input to possibly have a valid header. 518c2ecf20Sopenharmony_ci * Then it is possible to parse several fields until the minimum 528c2ecf20Sopenharmony_ci * size may have been used. 538c2ecf20Sopenharmony_ci */ 548c2ecf20Sopenharmony_ci if (in_len < HEADER_SIZE_MIN) 558c2ecf20Sopenharmony_ci return 0; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci /* read magic: 9 first bits */ 588c2ecf20Sopenharmony_ci for (l = 0; l < 9; l++) { 598c2ecf20Sopenharmony_ci if (*parse++ != lzop_magic[l]) 608c2ecf20Sopenharmony_ci return 0; 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci /* get version (2bytes), skip library version (2), 638c2ecf20Sopenharmony_ci * 'need to be extracted' version (2) and 648c2ecf20Sopenharmony_ci * method (1) */ 658c2ecf20Sopenharmony_ci version = get_unaligned_be16(parse); 668c2ecf20Sopenharmony_ci parse += 7; 678c2ecf20Sopenharmony_ci if (version >= 0x0940) 688c2ecf20Sopenharmony_ci level = *parse++; 698c2ecf20Sopenharmony_ci if (get_unaligned_be32(parse) & HEADER_HAS_FILTER) 708c2ecf20Sopenharmony_ci parse += 8; /* flags + filter info */ 718c2ecf20Sopenharmony_ci else 728c2ecf20Sopenharmony_ci parse += 4; /* flags */ 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci /* 758c2ecf20Sopenharmony_ci * At least mode, mtime_low, filename length, and checksum must 768c2ecf20Sopenharmony_ci * be left to be parsed. If also mtime_high is present, it's OK 778c2ecf20Sopenharmony_ci * because the next input buffer check is after reading the 788c2ecf20Sopenharmony_ci * filename length. 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_ci if (end - parse < 8 + 1 + 4) 818c2ecf20Sopenharmony_ci return 0; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci /* skip mode and mtime_low */ 848c2ecf20Sopenharmony_ci parse += 8; 858c2ecf20Sopenharmony_ci if (version >= 0x0940) 868c2ecf20Sopenharmony_ci parse += 4; /* skip mtime_high */ 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci l = *parse++; 898c2ecf20Sopenharmony_ci /* don't care about the file name, and skip checksum */ 908c2ecf20Sopenharmony_ci if (end - parse < l + 4) 918c2ecf20Sopenharmony_ci return 0; 928c2ecf20Sopenharmony_ci parse += l + 4; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci *skip = parse - input; 958c2ecf20Sopenharmony_ci return 1; 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ciSTATIC int INIT unlzo(u8 *input, long in_len, 998c2ecf20Sopenharmony_ci long (*fill)(void *, unsigned long), 1008c2ecf20Sopenharmony_ci long (*flush)(void *, unsigned long), 1018c2ecf20Sopenharmony_ci u8 *output, long *posp, 1028c2ecf20Sopenharmony_ci void (*error) (char *x)) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci u8 r = 0; 1058c2ecf20Sopenharmony_ci long skip = 0; 1068c2ecf20Sopenharmony_ci u32 src_len, dst_len; 1078c2ecf20Sopenharmony_ci size_t tmp; 1088c2ecf20Sopenharmony_ci u8 *in_buf, *in_buf_save, *out_buf; 1098c2ecf20Sopenharmony_ci int ret = -1; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (output) { 1128c2ecf20Sopenharmony_ci out_buf = output; 1138c2ecf20Sopenharmony_ci } else if (!flush) { 1148c2ecf20Sopenharmony_ci error("NULL output pointer and no flush function provided"); 1158c2ecf20Sopenharmony_ci goto exit; 1168c2ecf20Sopenharmony_ci } else { 1178c2ecf20Sopenharmony_ci out_buf = malloc(LZO_BLOCK_SIZE); 1188c2ecf20Sopenharmony_ci if (!out_buf) { 1198c2ecf20Sopenharmony_ci error("Could not allocate output buffer"); 1208c2ecf20Sopenharmony_ci goto exit; 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if (input && fill) { 1258c2ecf20Sopenharmony_ci error("Both input pointer and fill function provided, don't know what to do"); 1268c2ecf20Sopenharmony_ci goto exit_1; 1278c2ecf20Sopenharmony_ci } else if (input) { 1288c2ecf20Sopenharmony_ci in_buf = input; 1298c2ecf20Sopenharmony_ci } else if (!fill) { 1308c2ecf20Sopenharmony_ci error("NULL input pointer and missing fill function"); 1318c2ecf20Sopenharmony_ci goto exit_1; 1328c2ecf20Sopenharmony_ci } else { 1338c2ecf20Sopenharmony_ci in_buf = malloc(lzo1x_worst_compress(LZO_BLOCK_SIZE)); 1348c2ecf20Sopenharmony_ci if (!in_buf) { 1358c2ecf20Sopenharmony_ci error("Could not allocate input buffer"); 1368c2ecf20Sopenharmony_ci goto exit_1; 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci in_buf_save = in_buf; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (posp) 1428c2ecf20Sopenharmony_ci *posp = 0; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci if (fill) { 1458c2ecf20Sopenharmony_ci /* 1468c2ecf20Sopenharmony_ci * Start from in_buf + HEADER_SIZE_MAX to make it possible 1478c2ecf20Sopenharmony_ci * to use memcpy() to copy the unused data to the beginning 1488c2ecf20Sopenharmony_ci * of the buffer. This way memmove() isn't needed which 1498c2ecf20Sopenharmony_ci * is missing from pre-boot environments of most archs. 1508c2ecf20Sopenharmony_ci */ 1518c2ecf20Sopenharmony_ci in_buf += HEADER_SIZE_MAX; 1528c2ecf20Sopenharmony_ci in_len = fill(in_buf, HEADER_SIZE_MAX); 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci if (!parse_header(in_buf, &skip, in_len)) { 1568c2ecf20Sopenharmony_ci error("invalid header"); 1578c2ecf20Sopenharmony_ci goto exit_2; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci in_buf += skip; 1608c2ecf20Sopenharmony_ci in_len -= skip; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci if (fill) { 1638c2ecf20Sopenharmony_ci /* Move the unused data to the beginning of the buffer. */ 1648c2ecf20Sopenharmony_ci memcpy(in_buf_save, in_buf, in_len); 1658c2ecf20Sopenharmony_ci in_buf = in_buf_save; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (posp) 1698c2ecf20Sopenharmony_ci *posp = skip; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci for (;;) { 1728c2ecf20Sopenharmony_ci /* read uncompressed block size */ 1738c2ecf20Sopenharmony_ci if (fill && in_len < 4) { 1748c2ecf20Sopenharmony_ci skip = fill(in_buf + in_len, 4 - in_len); 1758c2ecf20Sopenharmony_ci if (skip > 0) 1768c2ecf20Sopenharmony_ci in_len += skip; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci if (in_len < 4) { 1798c2ecf20Sopenharmony_ci error("file corrupted"); 1808c2ecf20Sopenharmony_ci goto exit_2; 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci dst_len = get_unaligned_be32(in_buf); 1838c2ecf20Sopenharmony_ci in_buf += 4; 1848c2ecf20Sopenharmony_ci in_len -= 4; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci /* exit if last block */ 1878c2ecf20Sopenharmony_ci if (dst_len == 0) { 1888c2ecf20Sopenharmony_ci if (posp) 1898c2ecf20Sopenharmony_ci *posp += 4; 1908c2ecf20Sopenharmony_ci break; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (dst_len > LZO_BLOCK_SIZE) { 1948c2ecf20Sopenharmony_ci error("dest len longer than block size"); 1958c2ecf20Sopenharmony_ci goto exit_2; 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci /* read compressed block size, and skip block checksum info */ 1998c2ecf20Sopenharmony_ci if (fill && in_len < 8) { 2008c2ecf20Sopenharmony_ci skip = fill(in_buf + in_len, 8 - in_len); 2018c2ecf20Sopenharmony_ci if (skip > 0) 2028c2ecf20Sopenharmony_ci in_len += skip; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci if (in_len < 8) { 2058c2ecf20Sopenharmony_ci error("file corrupted"); 2068c2ecf20Sopenharmony_ci goto exit_2; 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci src_len = get_unaligned_be32(in_buf); 2098c2ecf20Sopenharmony_ci in_buf += 8; 2108c2ecf20Sopenharmony_ci in_len -= 8; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci if (src_len <= 0 || src_len > dst_len) { 2138c2ecf20Sopenharmony_ci error("file corrupted"); 2148c2ecf20Sopenharmony_ci goto exit_2; 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci /* decompress */ 2188c2ecf20Sopenharmony_ci if (fill && in_len < src_len) { 2198c2ecf20Sopenharmony_ci skip = fill(in_buf + in_len, src_len - in_len); 2208c2ecf20Sopenharmony_ci if (skip > 0) 2218c2ecf20Sopenharmony_ci in_len += skip; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci if (in_len < src_len) { 2248c2ecf20Sopenharmony_ci error("file corrupted"); 2258c2ecf20Sopenharmony_ci goto exit_2; 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci tmp = dst_len; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci /* When the input data is not compressed at all, 2308c2ecf20Sopenharmony_ci * lzo1x_decompress_safe will fail, so call memcpy() 2318c2ecf20Sopenharmony_ci * instead */ 2328c2ecf20Sopenharmony_ci if (unlikely(dst_len == src_len)) 2338c2ecf20Sopenharmony_ci memcpy(out_buf, in_buf, src_len); 2348c2ecf20Sopenharmony_ci else { 2358c2ecf20Sopenharmony_ci r = lzo1x_decompress_safe((u8 *) in_buf, src_len, 2368c2ecf20Sopenharmony_ci out_buf, &tmp); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci if (r != LZO_E_OK || dst_len != tmp) { 2398c2ecf20Sopenharmony_ci error("Compressed data violation"); 2408c2ecf20Sopenharmony_ci goto exit_2; 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci if (flush && flush(out_buf, dst_len) != dst_len) 2458c2ecf20Sopenharmony_ci goto exit_2; 2468c2ecf20Sopenharmony_ci if (output) 2478c2ecf20Sopenharmony_ci out_buf += dst_len; 2488c2ecf20Sopenharmony_ci if (posp) 2498c2ecf20Sopenharmony_ci *posp += src_len + 12; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci in_buf += src_len; 2528c2ecf20Sopenharmony_ci in_len -= src_len; 2538c2ecf20Sopenharmony_ci if (fill) { 2548c2ecf20Sopenharmony_ci /* 2558c2ecf20Sopenharmony_ci * If there happens to still be unused data left in 2568c2ecf20Sopenharmony_ci * in_buf, move it to the beginning of the buffer. 2578c2ecf20Sopenharmony_ci * Use a loop to avoid memmove() dependency. 2588c2ecf20Sopenharmony_ci */ 2598c2ecf20Sopenharmony_ci if (in_len > 0) 2608c2ecf20Sopenharmony_ci for (skip = 0; skip < in_len; ++skip) 2618c2ecf20Sopenharmony_ci in_buf_save[skip] = in_buf[skip]; 2628c2ecf20Sopenharmony_ci in_buf = in_buf_save; 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci ret = 0; 2678c2ecf20Sopenharmony_ciexit_2: 2688c2ecf20Sopenharmony_ci if (!input) 2698c2ecf20Sopenharmony_ci free(in_buf_save); 2708c2ecf20Sopenharmony_ciexit_1: 2718c2ecf20Sopenharmony_ci if (!output) 2728c2ecf20Sopenharmony_ci free(out_buf); 2738c2ecf20Sopenharmony_ciexit: 2748c2ecf20Sopenharmony_ci return ret; 2758c2ecf20Sopenharmony_ci} 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci#ifdef PREBOOT 2788c2ecf20Sopenharmony_ciSTATIC int INIT __decompress(unsigned char *buf, long len, 2798c2ecf20Sopenharmony_ci long (*fill)(void*, unsigned long), 2808c2ecf20Sopenharmony_ci long (*flush)(void*, unsigned long), 2818c2ecf20Sopenharmony_ci unsigned char *out_buf, long olen, 2828c2ecf20Sopenharmony_ci long *pos, 2838c2ecf20Sopenharmony_ci void (*error)(char *x)) 2848c2ecf20Sopenharmony_ci{ 2858c2ecf20Sopenharmony_ci return unlzo(buf, len, fill, flush, out_buf, pos, error); 2868c2ecf20Sopenharmony_ci} 2878c2ecf20Sopenharmony_ci#endif 288