1/*-
2 * Copyright (c) 2012 Michihiro NAKAJIMA
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "archive_platform.h"
27
28//#undef HAVE_LZO_LZOCONF_H
29//#undef HAVE_LZO_LZO1X_H
30
31#ifdef HAVE_ERRNO_H
32#include <errno.h>
33#endif
34#ifdef HAVE_STDLIB_H
35#include <stdlib.h>
36#endif
37#ifdef HAVE_STRING_H
38#include <string.h>
39#endif
40#include <time.h>
41#ifdef HAVE_LZO_LZOCONF_H
42#include <lzo/lzoconf.h>
43#endif
44#ifdef HAVE_LZO_LZO1X_H
45#include <lzo/lzo1x.h>
46#endif
47
48#include "archive.h"
49#include "archive_string.h"
50#include "archive_endian.h"
51#include "archive_write_private.h"
52
53enum lzo_method {
54	METHOD_LZO1X_1 = 1,
55	METHOD_LZO1X_1_15 = 2,
56	METHOD_LZO1X_999 = 3
57};
58struct write_lzop {
59	int compression_level;
60#if defined(HAVE_LZO_LZOCONF_H) && defined(HAVE_LZO_LZO1X_H)
61	unsigned char	*uncompressed;
62	size_t		 uncompressed_buffer_size;
63	size_t		 uncompressed_avail_bytes;
64	unsigned char	*compressed;
65	size_t		 compressed_buffer_size;
66	enum lzo_method	 method;
67	unsigned char	 level;
68	lzo_voidp	 work_buffer;
69	lzo_uint32	 work_buffer_size;
70	char		 header_written;
71#else
72	struct archive_write_program_data *pdata;
73#endif
74};
75
76static int archive_write_lzop_open(struct archive_write_filter *);
77static int archive_write_lzop_options(struct archive_write_filter *,
78		    const char *, const char *);
79static int archive_write_lzop_write(struct archive_write_filter *,
80		    const void *, size_t);
81static int archive_write_lzop_close(struct archive_write_filter *);
82static int archive_write_lzop_free(struct archive_write_filter *);
83
84#if defined(HAVE_LZO_LZOCONF_H) && defined(HAVE_LZO_LZO1X_H)
85/* Maximum block size. */
86#define BLOCK_SIZE			(256 * 1024)
87/* Block information is composed of uncompressed size(4 bytes),
88 * compressed size(4 bytes) and the checksum of uncompressed data(4 bytes)
89 * in this lzop writer. */
90#define BLOCK_INfO_SIZE			12
91
92#define HEADER_VERSION			9
93#define HEADER_LIBVERSION		11
94#define HEADER_METHOD			15
95#define HEADER_LEVEL			16
96#define HEADER_MTIME_LOW		25
97#define HEADER_MTIME_HIGH		29
98#define HEADER_H_CHECKSUM		34
99
100/*
101 * Header template.
102 */
103static const unsigned char header[] = {
104	/* LZOP Magic code 9 bytes */
105	0x89, 0x4c, 0x5a, 0x4f, 0x00, 0x0d, 0x0a, 0x1a, 0x0a,
106	/* LZOP utility version(fake data) 2 bytes */
107	0x10, 0x30,
108	/* LZO library version 2 bytes */
109	0x09, 0x40,
110	/* Minimum required LZO library version 2 bytes */
111	0x09, 0x40,
112	/* Method */
113	1,
114	/* Level */
115	5,
116	/* Flags 4 bytes
117	 *  -OS Unix
118	 *  -Stdout
119	 *  -Stdin
120	 *  -Adler32 used for uncompressed data 4 bytes */
121	0x03, 0x00, 0x00, 0x0d,
122	/* Mode (AE_IFREG | 0644) 4 bytes */
123	0x00, 0x00, 0x81, 0xa4,
124	/* Mtime low 4 bytes */
125	0x00, 0x00, 0x00, 0x00,
126	/* Mtime high 4 bytes */
127	0x00, 0x00, 0x00, 0x00,
128	/* Filename length */
129	0x00,
130	/* Header checksum 4 bytes */
131	0x00, 0x00, 0x00, 0x00,
132};
133#endif
134
135int
136archive_write_add_filter_lzop(struct archive *_a)
137{
138	struct archive_write_filter *f = __archive_write_allocate_filter(_a);
139	struct write_lzop *data;
140
141	archive_check_magic(_a, ARCHIVE_WRITE_MAGIC,
142	    ARCHIVE_STATE_NEW, "archive_write_add_filter_lzop");
143
144	data = calloc(1, sizeof(*data));
145	if (data == NULL) {
146		archive_set_error(_a, ENOMEM, "Can't allocate memory");
147		return (ARCHIVE_FATAL);
148	}
149
150	f->name = "lzop";
151	f->code = ARCHIVE_FILTER_LZOP;
152	f->data = data;
153	f->open = archive_write_lzop_open;
154	f->options = archive_write_lzop_options;
155	f->write = archive_write_lzop_write;
156	f->close = archive_write_lzop_close;
157	f->free = archive_write_lzop_free;
158#if defined(HAVE_LZO_LZOCONF_H) && defined(HAVE_LZO_LZO1X_H)
159	if (lzo_init() != LZO_E_OK) {
160		free(data);
161		archive_set_error(_a, ARCHIVE_ERRNO_MISC,
162		    "lzo_init(type check) failed");
163		return (ARCHIVE_FATAL);
164	}
165	if (lzo_version() < 0x940) {
166		free(data);
167		archive_set_error(_a, ARCHIVE_ERRNO_MISC,
168		    "liblzo library is too old(%s < 0.940)",
169		    lzo_version_string());
170		return (ARCHIVE_FATAL);
171	}
172	data->compression_level = 5;
173	return (ARCHIVE_OK);
174#else
175	data->pdata = __archive_write_program_allocate("lzop");
176	if (data->pdata == NULL) {
177		free(data);
178		archive_set_error(_a, ENOMEM, "Can't allocate memory");
179		return (ARCHIVE_FATAL);
180	}
181	data->compression_level = 0;
182	/* Note: We return "warn" to inform of using an external lzop
183	 * program. */
184	archive_set_error(_a, ARCHIVE_ERRNO_MISC,
185	    "Using external lzop program for lzop compression");
186	return (ARCHIVE_WARN);
187#endif
188}
189
190static int
191archive_write_lzop_free(struct archive_write_filter *f)
192{
193	struct write_lzop *data = (struct write_lzop *)f->data;
194
195#if defined(HAVE_LZO_LZOCONF_H) && defined(HAVE_LZO_LZO1X_H)
196	free(data->uncompressed);
197	free(data->compressed);
198	free(data->work_buffer);
199#else
200	__archive_write_program_free(data->pdata);
201#endif
202	free(data);
203	return (ARCHIVE_OK);
204}
205
206static int
207archive_write_lzop_options(struct archive_write_filter *f, const char *key,
208    const char *value)
209{
210	struct write_lzop *data = (struct write_lzop *)f->data;
211
212	if (strcmp(key, "compression-level") == 0) {
213		if (value == NULL || !(value[0] >= '1' && value[0] <= '9') ||
214		    value[1] != '\0')
215			return (ARCHIVE_WARN);
216		data->compression_level = value[0] - '0';
217		return (ARCHIVE_OK);
218	}
219	/* Note: The "warn" return is just to inform the options
220	 * supervisor that we didn't handle it.  It will generate
221	 * a suitable error if no one used this option. */
222	return (ARCHIVE_WARN);
223}
224
225#if defined(HAVE_LZO_LZOCONF_H) && defined(HAVE_LZO_LZO1X_H)
226static int
227archive_write_lzop_open(struct archive_write_filter *f)
228{
229	struct write_lzop *data = (struct write_lzop *)f->data;
230
231	switch (data->compression_level) {
232	case 1:
233		data->method = METHOD_LZO1X_1_15; data->level = 1; break;
234	default:
235	case 2: case 3: case 4: case 5: case 6:
236		data->method = METHOD_LZO1X_1; data->level = 5; break;
237	case 7:
238		data->method = METHOD_LZO1X_999; data->level = 7; break;
239	case 8:
240		data->method = METHOD_LZO1X_999; data->level = 8; break;
241	case 9:
242		data->method = METHOD_LZO1X_999; data->level = 9; break;
243	}
244	switch (data->method) {
245	case METHOD_LZO1X_1:
246		data->work_buffer_size = LZO1X_1_MEM_COMPRESS; break;
247	case METHOD_LZO1X_1_15:
248		data->work_buffer_size = LZO1X_1_15_MEM_COMPRESS; break;
249	case METHOD_LZO1X_999:
250		data->work_buffer_size = LZO1X_999_MEM_COMPRESS; break;
251	}
252	if (data->work_buffer == NULL) {
253		data->work_buffer = (lzo_voidp)malloc(data->work_buffer_size);
254		if (data->work_buffer == NULL) {
255			archive_set_error(f->archive, ENOMEM,
256			    "Can't allocate data for compression buffer");
257			return (ARCHIVE_FATAL);
258		}
259	}
260	if (data->compressed == NULL) {
261		data->compressed_buffer_size = sizeof(header) +
262		    BLOCK_SIZE + (BLOCK_SIZE >> 4) + 64 + 3;
263		data->compressed = (unsigned char *)
264		    malloc(data->compressed_buffer_size);
265		if (data->compressed == NULL) {
266			archive_set_error(f->archive, ENOMEM,
267			    "Can't allocate data for compression buffer");
268			return (ARCHIVE_FATAL);
269		}
270	}
271	if (data->uncompressed == NULL) {
272		data->uncompressed_buffer_size = BLOCK_SIZE;
273		data->uncompressed = (unsigned char *)
274		    malloc(data->uncompressed_buffer_size);
275		if (data->uncompressed == NULL) {
276			archive_set_error(f->archive, ENOMEM,
277			    "Can't allocate data for compression buffer");
278			return (ARCHIVE_FATAL);
279		}
280		data->uncompressed_avail_bytes = BLOCK_SIZE;
281	}
282	return (ARCHIVE_OK);
283}
284
285static int
286make_header(struct archive_write_filter *f)
287{
288	struct write_lzop *data = (struct write_lzop *)f->data;
289	int64_t t;
290	uint32_t checksum;
291
292	memcpy(data->compressed, header, sizeof(header));
293	/* Overwrite library version. */
294	data->compressed[HEADER_LIBVERSION] = (unsigned char )
295	    (lzo_version() >> 8) & 0xff;
296	data->compressed[HEADER_LIBVERSION + 1] = (unsigned char )
297	    lzo_version() & 0xff;
298	/* Overwrite method and level. */
299	data->compressed[HEADER_METHOD] = (unsigned char)data->method;
300	data->compressed[HEADER_LEVEL] = data->level;
301	/* Overwrite mtime with current time. */
302	t = (int64_t)time(NULL);
303	archive_be32enc(&data->compressed[HEADER_MTIME_LOW],
304	    (uint32_t)(t & 0xffffffff));
305	archive_be32enc(&data->compressed[HEADER_MTIME_HIGH],
306	    (uint32_t)((t >> 32) & 0xffffffff));
307	/* Overwrite header checksum with calculated value. */
308	checksum = lzo_adler32(1, data->compressed + HEADER_VERSION,
309			(lzo_uint)(HEADER_H_CHECKSUM - HEADER_VERSION));
310	archive_be32enc(&data->compressed[HEADER_H_CHECKSUM], checksum);
311	return (sizeof(header));
312}
313
314static int
315drive_compressor(struct archive_write_filter *f)
316{
317	struct write_lzop *data = (struct write_lzop *)f->data;
318	unsigned char *p;
319	const int block_info_bytes = 12;
320	int header_bytes, r;
321	lzo_uint usize, csize;
322	uint32_t checksum;
323
324	if (!data->header_written) {
325		header_bytes = make_header(f);
326		data->header_written = 1;
327	} else
328		header_bytes = 0;
329	p = data->compressed;
330
331	usize = (lzo_uint)
332	    (data->uncompressed_buffer_size - data->uncompressed_avail_bytes);
333	csize = 0;
334	switch (data->method) {
335	default:
336	case METHOD_LZO1X_1:
337		r = lzo1x_1_compress(data->uncompressed, usize,
338			p + header_bytes + block_info_bytes, &csize,
339			data->work_buffer);
340		break;
341	case METHOD_LZO1X_1_15:
342		r = lzo1x_1_15_compress(data->uncompressed, usize,
343			p + header_bytes + block_info_bytes, &csize,
344			data->work_buffer);
345		break;
346	case METHOD_LZO1X_999:
347		r = lzo1x_999_compress_level(data->uncompressed, usize,
348			p + header_bytes + block_info_bytes, &csize,
349			data->work_buffer, NULL, 0, 0, data->level);
350		break;
351	}
352	if (r != LZO_E_OK) {
353		archive_set_error(f->archive, ARCHIVE_ERRNO_MISC,
354		    "Lzop compression failed: returned status %d", r);
355		return (ARCHIVE_FATAL);
356	}
357
358	/* Store uncompressed size. */
359	archive_be32enc(p + header_bytes, (uint32_t)usize);
360	/* Store the checksum of the uncompressed data. */
361	checksum = lzo_adler32(1, data->uncompressed, usize);
362	archive_be32enc(p + header_bytes + 8, checksum);
363
364	if (csize < usize) {
365		/* Store compressed size. */
366		archive_be32enc(p + header_bytes + 4, (uint32_t)csize);
367		r = __archive_write_filter(f->next_filter, data->compressed,
368			header_bytes + block_info_bytes + csize);
369	} else {
370		/*
371		 * This case, we output uncompressed data instead.
372		 */
373		/* Store uncompressed size as compressed size. */
374		archive_be32enc(p + header_bytes + 4, (uint32_t)usize);
375		r = __archive_write_filter(f->next_filter, data->compressed,
376			header_bytes + block_info_bytes);
377		if (r != ARCHIVE_OK)
378			return (ARCHIVE_FATAL);
379		r = __archive_write_filter(f->next_filter, data->uncompressed,
380			usize);
381	}
382
383	if (r != ARCHIVE_OK)
384		return (ARCHIVE_FATAL);
385	return (ARCHIVE_OK);
386}
387
388static int
389archive_write_lzop_write(struct archive_write_filter *f,
390    const void *buff, size_t length)
391{
392	struct write_lzop *data = (struct write_lzop *)f->data;
393	const char *p = buff;
394	int r;
395
396	do {
397		if (data->uncompressed_avail_bytes > length) {
398			memcpy(data->uncompressed
399				+ data->uncompressed_buffer_size
400				- data->uncompressed_avail_bytes,
401			    p, length);
402			data->uncompressed_avail_bytes -= length;
403			return (ARCHIVE_OK);
404		}
405
406		memcpy(data->uncompressed + data->uncompressed_buffer_size
407			- data->uncompressed_avail_bytes,
408		    p, data->uncompressed_avail_bytes);
409		length -= data->uncompressed_avail_bytes;
410		p += data->uncompressed_avail_bytes;
411		data->uncompressed_avail_bytes = 0;
412
413		r = drive_compressor(f);
414		if (r != ARCHIVE_OK) return (r);
415		data->uncompressed_avail_bytes = BLOCK_SIZE;
416	} while (length);
417
418	return (ARCHIVE_OK);
419}
420
421static int
422archive_write_lzop_close(struct archive_write_filter *f)
423{
424	struct write_lzop *data = (struct write_lzop *)f->data;
425	const uint32_t endmark = 0;
426	int r;
427
428	if (data->uncompressed_avail_bytes < BLOCK_SIZE) {
429		/* Compress and output remaining data. */
430		r = drive_compressor(f);
431		if (r != ARCHIVE_OK)
432			return (r);
433	}
434	/* Write a zero uncompressed size as the end mark of the series of
435	 * compressed block. */
436	return __archive_write_filter(f->next_filter, &endmark, sizeof(endmark));
437}
438
439#else
440static int
441archive_write_lzop_open(struct archive_write_filter *f)
442{
443	struct write_lzop *data = (struct write_lzop *)f->data;
444	struct archive_string as;
445	int r;
446
447	archive_string_init(&as);
448	archive_strcpy(&as, "lzop");
449	/* Specify compression level. */
450	if (data->compression_level > 0) {
451		archive_strappend_char(&as, ' ');
452		archive_strappend_char(&as, '-');
453		archive_strappend_char(&as, '0' + data->compression_level);
454	}
455
456	r = __archive_write_program_open(f, data->pdata, as.s);
457	archive_string_free(&as);
458	return (r);
459}
460
461static int
462archive_write_lzop_write(struct archive_write_filter *f,
463    const void *buff, size_t length)
464{
465	struct write_lzop *data = (struct write_lzop *)f->data;
466
467	return __archive_write_program_write(f, data->pdata, buff, length);
468}
469
470static int
471archive_write_lzop_close(struct archive_write_filter *f)
472{
473	struct write_lzop *data = (struct write_lzop *)f->data;
474
475	return __archive_write_program_close(f, data->pdata);
476}
477#endif
478