1/*
2 * Copyright (c) 2013
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 * lz4_wrapper.c
20 *
21 * Support for LZ4 compression http://fastcompression.blogspot.com/p/lz4.html
22 */
23
24#include <stdio.h>
25#include <string.h>
26#include <stdlib.h>
27#include <lz4.h>
28#include <lz4hc.h>
29
30#include "squashfs_fs.h"
31#include "lz4_wrapper.h"
32#include "compressor.h"
33
34static int hc = 0;
35
36/*
37 * This function is called by the options parsing code in mksquashfs.c
38 * to parse any -X compressor option.
39 *
40 * This function returns:
41 *	>=0 (number of additional args parsed) on success
42 *	-1 if the option was unrecognised, or
43 *	-2 if the option was recognised, but otherwise bad in
44 *	   some way (e.g. invalid parameter)
45 *
46 * Note: this function sets internal compressor state, but does not
47 * pass back the results of the parsing other than success/failure.
48 * The lz4_dump_options() function is called later to get the options in
49 * a format suitable for writing to the filesystem.
50 */
51static int lz4_options(char *argv[], int argc)
52{
53	if(strcmp(argv[0], "-Xhc") == 0) {
54		hc = 1;
55		return 0;
56	}
57
58	return -1;
59}
60
61
62/*
63 * This function is called by mksquashfs to dump the parsed
64 * compressor options in a format suitable for writing to the
65 * compressor options field in the filesystem (stored immediately
66 * after the superblock).
67 *
68 * This function returns a pointer to the compression options structure
69 * to be stored (and the size), or NULL if there are no compression
70 * options
71 *
72 * Currently LZ4 always returns a comp_opts structure, with
73 * the version indicating LZ4_LEGACY stream fomat.  This is to
74 * easily accomodate changes in the kernel code to different
75 * stream formats
76 */
77static void *lz4_dump_options(int block_size, int *size)
78{
79	static struct lz4_comp_opts comp_opts;
80
81	comp_opts.version = LZ4_LEGACY;
82	comp_opts.flags = hc ? LZ4_HC : 0;
83	SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
84
85	*size = sizeof(comp_opts);
86	return &comp_opts;
87}
88
89
90/*
91 * This function is a helper specifically for the append mode of
92 * mksquashfs.  Its purpose is to set the internal compressor state
93 * to the stored compressor options in the passed compressor options
94 * structure.
95 *
96 * In effect this function sets up the compressor options
97 * to the same state they were when the filesystem was originally
98 * generated, this is to ensure on appending, the compressor uses
99 * the same compression options that were used to generate the
100 * original filesystem.
101 *
102 * Note, even if there are no compressor options, this function is still
103 * called with an empty compressor structure (size == 0), to explicitly
104 * set the default options, this is to ensure any user supplied
105 * -X options on the appending mksquashfs command line are over-ridden
106 *
107 * This function returns 0 on sucessful extraction of options, and
108 *			-1 on error
109 */
110static int lz4_extract_options(int block_size, void *buffer, int size)
111{
112	struct lz4_comp_opts *comp_opts = buffer;
113
114	/* we expect a comp_opts structure to be present */
115	if(size < sizeof(*comp_opts))
116		goto failed;
117
118	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
119
120	/* we expect the stream format to be LZ4_LEGACY */
121	if(comp_opts->version != LZ4_LEGACY) {
122		fprintf(stderr, "lz4: unknown LZ4 version\n");
123		goto failed;
124	}
125
126	/*
127	 * Check compression flags, currently only LZ4_HC ("high compression")
128	 * can be set.
129	 */
130	if(comp_opts->flags == LZ4_HC)
131		hc = 1;
132	else if(comp_opts->flags != 0) {
133		fprintf(stderr, "lz4: unknown LZ4 flags\n");
134		goto failed;
135	}
136
137	return 0;
138
139failed:
140	fprintf(stderr, "lz4: error reading stored compressor options from "
141		"filesystem!\n");
142
143	return -1;
144}
145
146
147/*
148 * This function is a helper specifically for unsquashfs.
149 * Its purpose is to check that the compression options are
150 * understood by this version of LZ4.
151 *
152 * This is important for LZ4 because the format understood by the
153 * Linux kernel may change from the already obsolete legacy format
154 * currently supported.
155 *
156 * If this does happen, then this version of LZ4 will not be able to decode
157 * the newer format.  So we need to check for this.
158 *
159 * This function returns 0 on sucessful checking of options, and
160 *			-1 on error
161 */
162static int lz4_check_options(int block_size, void *buffer, int size)
163{
164	struct lz4_comp_opts *comp_opts = buffer;
165
166	/* we expect a comp_opts structure to be present */
167	if(size < sizeof(*comp_opts))
168		goto failed;
169
170	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
171
172	/* we expect the stream format to be LZ4_LEGACY */
173	if(comp_opts->version != LZ4_LEGACY) {
174		fprintf(stderr, "lz4: unknown LZ4 version\n");
175		goto failed;
176	}
177
178	return 0;
179
180failed:
181	fprintf(stderr, "lz4: error reading stored compressor options from "
182		"filesystem!\n");
183	return -1;
184}
185
186
187void lz4_display_options(void *buffer, int size)
188{
189	struct lz4_comp_opts *comp_opts = buffer;
190
191	/* check passed comp opts struct is of the correct length */
192	if(size < sizeof(*comp_opts))
193		goto failed;
194
195	SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
196
197	/* we expect the stream format to be LZ4_LEGACY */
198	if(comp_opts->version != LZ4_LEGACY) {
199		fprintf(stderr, "lz4: unknown LZ4 version\n");
200		goto failed;
201	}
202
203	/*
204	 * Check compression flags, currently only LZ4_HC ("high compression")
205	 * can be set.
206	 */
207	if(comp_opts->flags & ~LZ4_FLAGS_MASK) {
208		fprintf(stderr, "lz4: unknown LZ4 flags\n");
209		goto failed;
210	}
211
212	if(comp_opts->flags & LZ4_HC)
213		printf("\tHigh Compression option specified (-Xhc)\n");
214
215	return;
216
217failed:
218	fprintf(stderr, "lz4: error reading stored compressor options from "
219		"filesystem!\n");
220}
221
222
223static int lz4_compress(void *strm, void *dest, void *src,  int size,
224	int block_size, int *error)
225{
226	int res;
227
228	if(hc)
229		res = LZ4_compressHC_limitedOutput(src, dest, size, block_size);
230	else
231		res = LZ4_compress_limitedOutput(src, dest, size, block_size);
232
233	if(res == 0) {
234		/*
235	 	 * Output buffer overflow.  Return out of buffer space
236	 	 */
237		return 0;
238	} else if(res < 0) {
239		/*
240	 	 * All other errors return failure, with the compressor
241	 	 * specific error code in *error
242	 	 */
243		*error = res;
244		return -1;
245	}
246
247	return res;
248}
249
250
251static int lz4_uncompress(void *dest, void *src, int size, int outsize,
252	int *error)
253{
254	int res = LZ4_decompress_safe(src, dest, size, outsize);
255	if(res < 0) {
256		*error = res;
257		return -1;
258	}
259
260	return res;
261}
262
263
264void lz4_usage()
265{
266	fprintf(stderr, "\t  -Xhc\n");
267	fprintf(stderr, "\t\tCompress using LZ4 High Compression\n");
268}
269
270
271struct compressor lz4_comp_ops = {
272	.compress = lz4_compress,
273	.uncompress = lz4_uncompress,
274	.options = lz4_options,
275	.dump_options = lz4_dump_options,
276	.extract_options = lz4_extract_options,
277	.check_options = lz4_check_options,
278	.display_options = lz4_display_options,
279	.usage = lz4_usage,
280	.id = LZ4_COMPRESSION,
281	.name = "lz4",
282	.supported = 1
283};
284