1324145Smm/*-
2324145Smm * Copyright (c) 2017 Sean Purcell
3324145Smm * All rights reserved.
4324145Smm *
5324145Smm * Redistribution and use in source and binary forms, with or without
6324145Smm * modification, are permitted provided that the following conditions
7324145Smm * are met:
8324145Smm * 1. Redistributions of source code must retain the above copyright
9324145Smm *    notice, this list of conditions and the following disclaimer.
10324145Smm * 2. Redistributions in binary form must reproduce the above copyright
11324145Smm *    notice, this list of conditions and the following disclaimer in the
12324145Smm *    documentation and/or other materials provided with the distribution.
13324145Smm *
14324145Smm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15324145Smm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16324145Smm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17324145Smm * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18324145Smm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19324145Smm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20324145Smm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21324145Smm * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22324145Smm * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23324145Smm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24324145Smm */
25324145Smm
26324145Smm#include "archive_platform.h"
27324145Smm
28324145Smm__FBSDID("$FreeBSD: stable/10/contrib/libarchive/libarchive/archive_write_add_filter_zstd.c 362134 2020-06-12 23:02:34Z mm $");
29324145Smm
30324145Smm
31324145Smm#ifdef HAVE_ERRNO_H
32324145Smm#include <errno.h>
33324145Smm#endif
34324145Smm#ifdef HAVE_STDLIB_H
35324145Smm#include <stdlib.h>
36324145Smm#endif
37324145Smm#ifdef HAVE_STRING_H
38324145Smm#include <string.h>
39324145Smm#endif
40324145Smm#ifdef HAVE_ZSTD_H
41324145Smm#include <zstd.h>
42324145Smm#endif
43324145Smm
44324145Smm#include "archive.h"
45324145Smm#include "archive_private.h"
46324145Smm#include "archive_string.h"
47324145Smm#include "archive_write_private.h"
48324145Smm
49324145Smm/* Don't compile this if we don't have zstd.h */
50324145Smm
51324145Smmstruct private_data {
52324145Smm	int		 compression_level;
53324145Smm#if HAVE_ZSTD_H && HAVE_LIBZSTD
54324145Smm	ZSTD_CStream	*cstream;
55324145Smm	int64_t		 total_in;
56324145Smm	ZSTD_outBuffer	 out;
57324145Smm#else
58324145Smm	struct archive_write_program_data *pdata;
59324145Smm#endif
60324145Smm};
61324145Smm
62362134Smm/* If we don't have the library use default range values (zstdcli.c v1.4.0) */
63362134Smm#define CLEVEL_MIN -99
64362134Smm#define CLEVEL_STD_MIN 0 /* prior to 1.3.4 and more recent without using --fast */
65362134Smm#define CLEVEL_DEFAULT 3
66362134Smm#define CLEVEL_STD_MAX 19 /* without using --ultra */
67362134Smm#define CLEVEL_MAX 22
68362134Smm
69362134Smm#define MINVER_NEGCLEVEL 10304
70362134Smm#define MINVER_MINCLEVEL 10306
71362134Smm
72324145Smmstatic int archive_compressor_zstd_options(struct archive_write_filter *,
73324145Smm		    const char *, const char *);
74324145Smmstatic int archive_compressor_zstd_open(struct archive_write_filter *);
75324145Smmstatic int archive_compressor_zstd_write(struct archive_write_filter *,
76324145Smm		    const void *, size_t);
77324145Smmstatic int archive_compressor_zstd_close(struct archive_write_filter *);
78324145Smmstatic int archive_compressor_zstd_free(struct archive_write_filter *);
79324145Smm#if HAVE_ZSTD_H && HAVE_LIBZSTD
80324145Smmstatic int drive_compressor(struct archive_write_filter *,
81324145Smm		    struct private_data *, int, const void *, size_t);
82324145Smm#endif
83324145Smm
84324145Smm
85324145Smm/*
86324145Smm * Add a zstd compression filter to this write handle.
87324145Smm */
88324145Smmint
89324145Smmarchive_write_add_filter_zstd(struct archive *_a)
90324145Smm{
91324145Smm	struct archive_write *a = (struct archive_write *)_a;
92324145Smm	struct archive_write_filter *f = __archive_write_allocate_filter(_a);
93324145Smm	struct private_data *data;
94324145Smm	archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC,
95324145Smm	    ARCHIVE_STATE_NEW, "archive_write_add_filter_zstd");
96324145Smm
97324145Smm	data = calloc(1, sizeof(*data));
98324145Smm	if (data == NULL) {
99324145Smm		archive_set_error(&a->archive, ENOMEM, "Out of memory");
100324145Smm		return (ARCHIVE_FATAL);
101324145Smm	}
102324145Smm	f->data = data;
103324145Smm	f->open = &archive_compressor_zstd_open;
104324145Smm	f->options = &archive_compressor_zstd_options;
105324145Smm	f->close = &archive_compressor_zstd_close;
106324145Smm	f->free = &archive_compressor_zstd_free;
107324145Smm	f->code = ARCHIVE_FILTER_ZSTD;
108324145Smm	f->name = "zstd";
109362134Smm	data->compression_level = CLEVEL_DEFAULT;
110324145Smm#if HAVE_ZSTD_H && HAVE_LIBZSTD
111324145Smm	data->cstream = ZSTD_createCStream();
112324145Smm	if (data->cstream == NULL) {
113324145Smm		free(data);
114324145Smm		archive_set_error(&a->archive, ENOMEM,
115324145Smm		    "Failed to allocate zstd compressor object");
116324145Smm		return (ARCHIVE_FATAL);
117324145Smm	}
118324145Smm
119324145Smm	return (ARCHIVE_OK);
120324145Smm#else
121324145Smm	data->pdata = __archive_write_program_allocate("zstd");
122324145Smm	if (data->pdata == NULL) {
123324145Smm		free(data);
124324145Smm		archive_set_error(&a->archive, ENOMEM, "Out of memory");
125324145Smm		return (ARCHIVE_FATAL);
126324145Smm	}
127324145Smm	archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
128324145Smm	    "Using external zstd program");
129324145Smm	return (ARCHIVE_WARN);
130324145Smm#endif
131324145Smm}
132324145Smm
133324145Smmstatic int
134324145Smmarchive_compressor_zstd_free(struct archive_write_filter *f)
135324145Smm{
136324145Smm	struct private_data *data = (struct private_data *)f->data;
137324145Smm#if HAVE_ZSTD_H && HAVE_LIBZSTD
138324145Smm	ZSTD_freeCStream(data->cstream);
139324145Smm	free(data->out.dst);
140324145Smm#else
141324145Smm	__archive_write_program_free(data->pdata);
142324145Smm#endif
143324145Smm	free(data);
144324145Smm	f->data = NULL;
145324145Smm	return (ARCHIVE_OK);
146324145Smm}
147324145Smm
148362134Smmstatic int string_is_numeric (const char* value)
149362134Smm{
150362134Smm       size_t len = strlen(value);
151362134Smm       size_t i;
152362134Smm
153362134Smm       if (len == 0) {
154362134Smm               return (ARCHIVE_WARN);
155362134Smm       }
156362134Smm       else if (len == 1 && !(value[0] >= '0' && value[0] <= '9')) {
157362134Smm               return (ARCHIVE_WARN);
158362134Smm       }
159362134Smm       else if (!(value[0] >= '0' && value[0] <= '9') &&
160362134Smm                value[0] != '-' && value[0] != '+') {
161362134Smm               return (ARCHIVE_WARN);
162362134Smm       }
163362134Smm
164362134Smm       for (i = 1; i < len; i++) {
165362134Smm               if (!(value[i] >= '0' && value[i] <= '9')) {
166362134Smm                       return (ARCHIVE_WARN);
167362134Smm               }
168362134Smm       }
169362134Smm
170362134Smm       return (ARCHIVE_OK);
171362134Smm}
172362134Smm
173324145Smm/*
174324145Smm * Set write options.
175324145Smm */
176324145Smmstatic int
177324145Smmarchive_compressor_zstd_options(struct archive_write_filter *f, const char *key,
178324145Smm    const char *value)
179324145Smm{
180324145Smm	struct private_data *data = (struct private_data *)f->data;
181324145Smm
182324145Smm	if (strcmp(key, "compression-level") == 0) {
183324145Smm		int level = atoi(value);
184362134Smm		/* If we don't have the library, hard-code the max level */
185362134Smm		int minimum = CLEVEL_MIN;
186362134Smm		int maximum = CLEVEL_MAX;
187362134Smm		if (string_is_numeric(value) != ARCHIVE_OK) {
188362134Smm			return (ARCHIVE_WARN);
189362134Smm		}
190324145Smm#if HAVE_ZSTD_H && HAVE_LIBZSTD
191362134Smm		maximum = ZSTD_maxCLevel();
192362134Smm#if ZSTD_VERSION_NUMBER >= MINVER_MINCLEVEL
193362134Smm		if (ZSTD_versionNumber() >= MINVER_MINCLEVEL) {
194362134Smm			minimum = ZSTD_minCLevel();
195362134Smm		}
196362134Smm		else
197324145Smm#endif
198362134Smm		if (ZSTD_versionNumber() < MINVER_NEGCLEVEL) {
199362134Smm			minimum = CLEVEL_STD_MIN;
200362134Smm		}
201362134Smm#endif
202362134Smm		if (level < minimum || level > maximum) {
203324145Smm			return (ARCHIVE_WARN);
204324145Smm		}
205324145Smm		data->compression_level = level;
206324145Smm		return (ARCHIVE_OK);
207324145Smm	}
208324145Smm
209324145Smm	/* Note: The "warn" return is just to inform the options
210324145Smm	 * supervisor that we didn't handle it.  It will generate
211324145Smm	 * a suitable error if no one used this option. */
212324145Smm	return (ARCHIVE_WARN);
213324145Smm}
214324145Smm
215324145Smm#if HAVE_ZSTD_H && HAVE_LIBZSTD
216324145Smm/*
217324145Smm * Setup callback.
218324145Smm */
219324145Smmstatic int
220324145Smmarchive_compressor_zstd_open(struct archive_write_filter *f)
221324145Smm{
222324145Smm	struct private_data *data = (struct private_data *)f->data;
223324145Smm
224324145Smm	if (data->out.dst == NULL) {
225324145Smm		size_t bs = ZSTD_CStreamOutSize(), bpb;
226324145Smm		if (f->archive->magic == ARCHIVE_WRITE_MAGIC) {
227324145Smm			/* Buffer size should be a multiple number of
228324145Smm			 * the of bytes per block for performance. */
229324145Smm			bpb = archive_write_get_bytes_per_block(f->archive);
230324145Smm			if (bpb > bs)
231324145Smm				bs = bpb;
232324145Smm			else if (bpb != 0)
233324145Smm				bs -= bs % bpb;
234324145Smm		}
235324145Smm		data->out.size = bs;
236324145Smm		data->out.pos = 0;
237324145Smm		data->out.dst
238324145Smm		    = (unsigned char *)malloc(data->out.size);
239324145Smm		if (data->out.dst == NULL) {
240324145Smm			archive_set_error(f->archive, ENOMEM,
241324145Smm			    "Can't allocate data for compression buffer");
242324145Smm			return (ARCHIVE_FATAL);
243324145Smm		}
244324145Smm	}
245324145Smm
246324145Smm	f->write = archive_compressor_zstd_write;
247324145Smm
248324145Smm	if (ZSTD_isError(ZSTD_initCStream(data->cstream,
249324145Smm	    data->compression_level))) {
250324145Smm		archive_set_error(f->archive, ARCHIVE_ERRNO_MISC,
251324145Smm		    "Internal error initializing zstd compressor object");
252324145Smm		return (ARCHIVE_FATAL);
253324145Smm	}
254324145Smm
255324145Smm	return (ARCHIVE_OK);
256324145Smm}
257324145Smm
258324145Smm/*
259324145Smm * Write data to the compressed stream.
260324145Smm */
261324145Smmstatic int
262324145Smmarchive_compressor_zstd_write(struct archive_write_filter *f, const void *buff,
263324145Smm    size_t length)
264324145Smm{
265324145Smm	struct private_data *data = (struct private_data *)f->data;
266324145Smm	int ret;
267324145Smm
268324145Smm	/* Update statistics */
269324145Smm	data->total_in += length;
270324145Smm
271324145Smm	if ((ret = drive_compressor(f, data, 0, buff, length)) != ARCHIVE_OK)
272324145Smm		return (ret);
273324145Smm
274324145Smm	return (ARCHIVE_OK);
275324145Smm}
276324145Smm
277324145Smm/*
278324145Smm * Finish the compression...
279324145Smm */
280324145Smmstatic int
281324145Smmarchive_compressor_zstd_close(struct archive_write_filter *f)
282324145Smm{
283324145Smm	struct private_data *data = (struct private_data *)f->data;
284324145Smm
285324145Smm	/* Finish zstd frame */
286358090Smm	return drive_compressor(f, data, 1, NULL, 0);
287324145Smm}
288324145Smm
289324145Smm/*
290324145Smm * Utility function to push input data through compressor,
291324145Smm * writing full output blocks as necessary.
292324145Smm *
293324145Smm * Note that this handles both the regular write case (finishing ==
294324145Smm * false) and the end-of-archive case (finishing == true).
295324145Smm */
296324145Smmstatic int
297324145Smmdrive_compressor(struct archive_write_filter *f,
298324145Smm    struct private_data *data, int finishing, const void *src, size_t length)
299324145Smm{
300324145Smm	ZSTD_inBuffer in = (ZSTD_inBuffer) { src, length, 0 };
301324145Smm
302324145Smm	for (;;) {
303324145Smm		if (data->out.pos == data->out.size) {
304324145Smm			const int ret = __archive_write_filter(f->next_filter,
305324145Smm			    data->out.dst, data->out.size);
306324145Smm			if (ret != ARCHIVE_OK)
307324145Smm				return (ARCHIVE_FATAL);
308324145Smm			data->out.pos = 0;
309324145Smm		}
310324145Smm
311324145Smm		/* If there's nothing to do, we're done. */
312324145Smm		if (!finishing && in.pos == in.size)
313324145Smm			return (ARCHIVE_OK);
314324145Smm
315324145Smm		{
316324145Smm			const size_t zstdret = !finishing ?
317324145Smm			    ZSTD_compressStream(data->cstream, &data->out, &in)
318324145Smm			    : ZSTD_endStream(data->cstream, &data->out);
319324145Smm
320324145Smm			if (ZSTD_isError(zstdret)) {
321324145Smm				archive_set_error(f->archive,
322324145Smm				    ARCHIVE_ERRNO_MISC,
323324145Smm				    "Zstd compression failed: %s",
324324145Smm				    ZSTD_getErrorName(zstdret));
325324145Smm				return (ARCHIVE_FATAL);
326324145Smm			}
327324145Smm
328324145Smm			/* If we're finishing, 0 means nothing left to flush */
329324145Smm			if (finishing && zstdret == 0) {
330324145Smm				const int ret = __archive_write_filter(f->next_filter,
331324145Smm				    data->out.dst, data->out.pos);
332324145Smm				return (ret);
333324145Smm			}
334324145Smm		}
335324145Smm	}
336324145Smm}
337324145Smm
338324145Smm#else /* HAVE_ZSTD_H && HAVE_LIBZSTD */
339324145Smm
340324145Smmstatic int
341324145Smmarchive_compressor_zstd_open(struct archive_write_filter *f)
342324145Smm{
343324145Smm	struct private_data *data = (struct private_data *)f->data;
344324145Smm	struct archive_string as;
345324145Smm	int r;
346324145Smm
347324145Smm	archive_string_init(&as);
348362134Smm	/* --no-check matches library default */
349362134Smm	archive_strcpy(&as, "zstd --no-check");
350324145Smm
351362134Smm	if (data->compression_level < CLEVEL_STD_MIN) {
352362134Smm		struct archive_string as2;
353362134Smm		archive_string_init(&as2);
354362134Smm		archive_string_sprintf(&as2, " --fast=%d", -data->compression_level);
355362134Smm		archive_string_concat(&as, &as2);
356362134Smm		archive_string_free(&as2);
357362134Smm	} else {
358362134Smm		struct archive_string as2;
359362134Smm		archive_string_init(&as2);
360362134Smm		archive_string_sprintf(&as2, " -%d", data->compression_level);
361362134Smm		archive_string_concat(&as, &as2);
362362134Smm		archive_string_free(&as2);
363362134Smm	}
364362134Smm
365362134Smm	if (data->compression_level > CLEVEL_STD_MAX) {
366362134Smm		archive_strcat(&as, " --ultra");
367362134Smm	}
368362134Smm
369324145Smm	f->write = archive_compressor_zstd_write;
370324145Smm	r = __archive_write_program_open(f, data->pdata, as.s);
371324145Smm	archive_string_free(&as);
372324145Smm	return (r);
373324145Smm}
374324145Smm
375324145Smmstatic int
376324145Smmarchive_compressor_zstd_write(struct archive_write_filter *f, const void *buff,
377324145Smm    size_t length)
378324145Smm{
379324145Smm	struct private_data *data = (struct private_data *)f->data;
380324145Smm
381324145Smm	return __archive_write_program_write(f, data->pdata, buff, length);
382324145Smm}
383324145Smm
384324145Smmstatic int
385324145Smmarchive_compressor_zstd_close(struct archive_write_filter *f)
386324145Smm{
387324145Smm	struct private_data *data = (struct private_data *)f->data;
388324145Smm
389324145Smm	return __archive_write_program_close(f, data->pdata);
390324145Smm}
391324145Smm
392324145Smm#endif /* HAVE_ZSTD_H && HAVE_LIBZSTD */
393