decompress_unlz4.c 4.15 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
/*
 * Wrapper for decompressing LZ4-compressed kernel, initramfs, and initrd
 *
 * Copyright (C) 2013, LG Electronics, Kyungsik Lee <kyungsik.lee@lge.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#ifdef STATIC
#define PREBOOT
#include "lz4/lz4_decompress.c"
#else
#include <linux/decompress/unlz4.h>
#endif
#include <linux/types.h>
#include <linux/lz4.h>
#include <linux/decompress/mm.h>
#include <linux/compiler.h>

#include <asm/unaligned.h>

/*
 * Note: Uncompressed chunk size is used in the compressor side
 * (userspace side for compression).
 * It is hardcoded because there is not proper way to extract it
 * from the binary stream which is generated by the preliminary
 * version of LZ4 tool so far.
 */
#define LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE (8 << 20)
#define ARCHIVE_MAGICNUMBER 0x184C2102

34 35 36 37
STATIC inline int INIT unlz4(u8 *input, long in_len,
				long (*fill)(void *, unsigned long),
				long (*flush)(void *, unsigned long),
				u8 *output, long *posp,
38 39 40 41 42 43 44 45
				void (*error) (char *x))
{
	int ret = -1;
	size_t chunksize = 0;
	size_t uncomp_chunksize = LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE;
	u8 *inp;
	u8 *inp_start;
	u8 *outp;
46
	long size = in_len;
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
#ifdef PREBOOT
	size_t out_len = get_unaligned_le32(input + in_len);
#endif
	size_t dest_len;


	if (output) {
		outp = output;
	} else if (!flush) {
		error("NULL output pointer and no flush function provided");
		goto exit_0;
	} else {
		outp = large_malloc(uncomp_chunksize);
		if (!outp) {
			error("Could not allocate output buffer");
			goto exit_0;
		}
	}

	if (input && fill) {
		error("Both input pointer and fill function provided,");
		goto exit_1;
	} else if (input) {
		inp = input;
	} else if (!fill) {
		error("NULL input pointer and missing fill function");
		goto exit_1;
	} else {
		inp = large_malloc(lz4_compressbound(uncomp_chunksize));
		if (!inp) {
			error("Could not allocate input buffer");
			goto exit_1;
		}
	}
	inp_start = inp;

	if (posp)
		*posp = 0;

86 87 88 89 90 91 92
	if (fill) {
		size = fill(inp, 4);
		if (size < 4) {
			error("data corrupted");
			goto exit_2;
		}
	}
93 94 95

	chunksize = get_unaligned_le32(inp);
	if (chunksize == ARCHIVE_MAGICNUMBER) {
96 97 98 99
		if (!fill) {
			inp += 4;
			size -= 4;
		}
100 101 102 103 104 105 106 107 108 109
	} else {
		error("invalid header");
		goto exit_2;
	}

	if (posp)
		*posp += 4;

	for (;;) {

110 111 112 113 114 115 116 117 118
		if (fill) {
			size = fill(inp, 4);
			if (size == 0)
				break;
			if (size < 4) {
				error("data corrupted");
				goto exit_2;
			}
		}
119 120 121

		chunksize = get_unaligned_le32(inp);
		if (chunksize == ARCHIVE_MAGICNUMBER) {
122 123 124 125
			if (!fill) {
				inp += 4;
				size -= 4;
			}
126 127 128 129
			if (posp)
				*posp += 4;
			continue;
		}
130

131 132 133 134

		if (posp)
			*posp += 4;

135 136 137 138
		if (!fill) {
			inp += 4;
			size -= 4;
		} else {
139 140 141 142
			if (chunksize > lz4_compressbound(uncomp_chunksize)) {
				error("chunk length is longer than allocated");
				goto exit_2;
			}
143 144 145 146 147
			size = fill(inp, chunksize);
			if (size < chunksize) {
				error("data corrupted");
				goto exit_2;
			}
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
		}
#ifdef PREBOOT
		if (out_len >= uncomp_chunksize) {
			dest_len = uncomp_chunksize;
			out_len -= dest_len;
		} else
			dest_len = out_len;
		ret = lz4_decompress(inp, &chunksize, outp, dest_len);
#else
		dest_len = uncomp_chunksize;
		ret = lz4_decompress_unknownoutputsize(inp, chunksize, outp,
				&dest_len);
#endif
		if (ret < 0) {
			error("Decoding failed");
			goto exit_2;
		}

166
		ret = -1;
167 168 169 170 171 172 173
		if (flush && flush(outp, dest_len) != dest_len)
			goto exit_2;
		if (output)
			outp += dest_len;
		if (posp)
			*posp += chunksize;

174 175
		if (!fill) {
			size -= chunksize;
176

177 178 179 180 181 182 183
			if (size == 0)
				break;
			else if (size < 0) {
				error("data corrupted");
				goto exit_2;
			}
			inp += chunksize;
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
		}
	}

	ret = 0;
exit_2:
	if (!input)
		large_free(inp_start);
exit_1:
	if (!output)
		large_free(outp);
exit_0:
	return ret;
}

#ifdef PREBOOT
199
STATIC int INIT __decompress(unsigned char *buf, long in_len,
200 201
			      long (*fill)(void*, unsigned long),
			      long (*flush)(void*, unsigned long),
202
			      unsigned char *output, long out_len,
203
			      long *posp,
204
			      void (*error)(char *x)
205 206 207 208 209
	)
{
	return unlz4(buf, in_len - 4, fill, flush, output, posp, error);
}
#endif