1 /*
2  * Wrapper for decompressing LZ4-compressed kernel, initramfs, and initrd
3  *
4  * Copyright (C) 2013, LG Electronics, Kyungsik Lee <kyungsik.lee@lge.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  */
10 
11 #include "decompress.h"
12 #include <xen/lz4.h>
13 #include "lz4/decompress.c"
14 
15 /*
16  * Note: Uncompressed chunk size is used in the compressor side
17  * (userspace side for compression).
18  * It is hardcoded because there is not proper way to extract it
19  * from the binary stream which is generated by the preliminary
20  * version of LZ4 tool so far.
21  */
22 #define LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE (8 << 20)
23 #define ARCHIVE_MAGICNUMBER 0x184C2102
24 
unlz4(unsigned char * input,unsigned int in_len,int (* fill)(void *,unsigned int),int (* flush)(void *,unsigned int),unsigned char * output,unsigned int * posp,void (* error)(const char * x))25 int __init unlz4(unsigned char *input, unsigned int in_len,
26 		 int (*fill)(void *, unsigned int),
27 		 int (*flush)(void *, unsigned int),
28 		 unsigned char *output, unsigned int *posp,
29 		 void (*error)(const char *x))
30 {
31 	int ret = -1;
32 	size_t chunksize = 0;
33 	size_t uncomp_chunksize = LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE;
34 	u8 *inp;
35 	u8 *inp_start;
36 	u8 *outp;
37 	int size = in_len -= 4;
38 #if defined(__XEN__) || defined(__MINIOS__)
39 	size_t out_len = get_unaligned_le32(input + in_len);
40 #endif
41 	size_t dest_len;
42 
43 
44 	if (output) {
45 		outp = output;
46 	} else if (!flush) {
47 		error("NULL output pointer and no flush function provided");
48 		goto exit_0;
49 	} else {
50 		outp = large_malloc(uncomp_chunksize);
51 		if (!outp) {
52 			error("Could not allocate output buffer");
53 			goto exit_0;
54 		}
55 	}
56 
57 	if (input && fill) {
58 		error("Both input pointer and fill function provided,");
59 		goto exit_1;
60 	} else if (input) {
61 		inp = input;
62 	} else if (!fill) {
63 		error("NULL input pointer and missing fill function");
64 		goto exit_1;
65 	} else {
66 		inp = large_malloc(lz4_compressbound(uncomp_chunksize));
67 		if (!inp) {
68 			error("Could not allocate input buffer");
69 			goto exit_1;
70 		}
71 	}
72 	inp_start = inp;
73 
74 	if (posp)
75 		*posp = 0;
76 
77 	if (fill)
78 		fill(inp, 4);
79 
80 	chunksize = get_unaligned_le32(inp);
81 	if (chunksize == ARCHIVE_MAGICNUMBER) {
82 		inp += 4;
83 		size -= 4;
84 	} else {
85 		error("invalid header");
86 		goto exit_2;
87 	}
88 
89 	if (posp)
90 		*posp += 4;
91 
92 	for (;;) {
93 
94 		if (fill)
95 			fill(inp, 4);
96 
97 		chunksize = get_unaligned_le32(inp);
98 		if (chunksize == ARCHIVE_MAGICNUMBER) {
99 			inp += 4;
100 			size -= 4;
101 			if (posp)
102 				*posp += 4;
103 			continue;
104 		}
105 		inp += 4;
106 		size -= 4;
107 
108 		if (posp)
109 			*posp += 4;
110 
111 		if (fill) {
112 			if (chunksize > lz4_compressbound(uncomp_chunksize)) {
113 				error("chunk length is longer than allocated");
114 				goto exit_2;
115 			}
116 			fill(inp, chunksize);
117 		}
118 #if defined(__XEN__) || defined(__MINIOS__)
119 		if (out_len >= uncomp_chunksize) {
120 			dest_len = uncomp_chunksize;
121 			out_len -= dest_len;
122 		} else
123 			dest_len = out_len;
124 		ret = lz4_decompress(inp, &chunksize, outp, dest_len);
125 #else
126 		dest_len = uncomp_chunksize;
127 		ret = lz4_decompress_unknownoutputsize(inp, chunksize, outp,
128 				&dest_len);
129 #endif
130 		if (ret < 0) {
131 			error("Decoding failed");
132 			goto exit_2;
133 		}
134 
135 		ret = -1;
136 		if (flush && flush(outp, dest_len) != dest_len)
137 			goto exit_2;
138 		if (output)
139 			outp += dest_len;
140 		if (posp)
141 			*posp += chunksize;
142 
143 		size -= chunksize;
144 
145 		if (size == 0)
146 			break;
147 		else if (size < 0) {
148 			error("data corrupted");
149 			goto exit_2;
150 		}
151 
152 		inp += chunksize;
153 		if (fill)
154 			inp = inp_start;
155 	}
156 
157 	ret = 0;
158 exit_2:
159 	if (!input)
160 		large_free(inp_start);
161 exit_1:
162 	if (!output)
163 		large_free(outp);
164 exit_0:
165 	return ret;
166 }
167