/*
 * unxz implementation for Bled/busybox
 *
 * Copyright © 2014-2020 Pete Batard <pete@akeo.ie>
 * Based on xz-embedded © Lasse Collin <lasse.collin@tukaani.org> - Public Domain
 *
 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
 */

#include "libbb.h"
#include "bb_archive.h"

#define XZ_EXTERN static
#define XZ_BUFSIZE BB_BUFSIZE

#include "xz_dec_bcj.c"
#include "xz_dec_lzma2.c"
#include "xz_dec_stream.c"

static void XZ_FUNC xz_crc32_init(void)
{
	if (!global_crc32_table)
		global_crc32_table = crc32_filltable(NULL, 0);
}

static uint32_t XZ_FUNC xz_crc32(const uint8_t *buf, size_t size, uint32_t crc)
{
	// The XZ CRC32 is INVERTED!
	return ~crc32_block_endian0(~crc, buf, size, global_crc32_table);
}

IF_DESKTOP(long long) int FAST_FUNC unpack_xz_stream(transformer_state_t *xstate)
{
	IF_DESKTOP(long long) int n = 0;
	struct xz_buf b;
	struct xz_dec *s;
	enum xz_ret ret = XZ_STREAM_END;
	uint8_t *in = NULL, *out = NULL;
	ssize_t nwrote;

	xz_crc32_init();

	/*
	 * Support up to 64 MiB dictionary. The actually needed memory
	 * is allocated once the headers have been parsed.
	 */
	s = xz_dec_init(XZ_DYNALLOC, 1 << 26);
	if (!s)
		bb_error_msg_and_err("memory allocation error");

	in = xmalloc(XZ_BUFSIZE);
	out = xmalloc(XZ_BUFSIZE);

	b.in = in;
	b.in_pos = 0;
	b.in_size = 0;
	b.out = out;
	b.out_pos = 0;
	b.out_size = XZ_BUFSIZE;

	while (true) {
		if (b.in_pos == b.in_size) {
			b.in_size = safe_read(xstate->src_fd, in, XZ_BUFSIZE);
			if ((int)b.in_size < 0)
				bb_error_msg_and_err("read error (errno: %d)", errno);
			b.in_pos = 0;
		}
		ret = xz_dec_run(s, &b);

		if (b.out_pos == XZ_BUFSIZE) {
			nwrote = transformer_write(xstate, b.out, b.out_pos);
			if (nwrote == -ENOSPC) {
				ret = XZ_BUF_FULL;
				goto out;
			}
			if (nwrote < 0) {
				ret = XZ_DATA_ERROR;
				bb_error_msg_and_err("write error (errno: %d)", errno);
			}
			IF_DESKTOP(n += nwrote;)
			b.out_pos = 0;
		}

		if (ret == XZ_OK)
			continue;

#ifdef XZ_DEC_ANY_CHECK
		if (ret == XZ_UNSUPPORTED_CHECK) {
//			bb_error_msg("unsupported check; not verifying file integrity");
			continue;
		}
#endif

		nwrote = transformer_write(xstate, b.out, b.out_pos);
		if (nwrote == -ENOSPC) {
			ret = XZ_BUF_FULL;
			goto out;
		}
		if (nwrote < 0) {
			ret = XZ_DATA_ERROR;
			bb_error_msg_and_err("write error (errno: %d)", errno);
		}
		IF_DESKTOP(n += nwrote;)

		switch (ret) {
		case XZ_STREAM_END:
			ret = XZ_OK;
			goto out;
		case XZ_MEM_ERROR:
			bb_error_msg_and_err("memory allocation error");
		case XZ_MEMLIMIT_ERROR:
			bb_error_msg_and_err("memory usage limit error");
		case XZ_FORMAT_ERROR:
			bb_error_msg_and_err("not a .xz file");
		case XZ_OPTIONS_ERROR:
			bb_error_msg_and_err("unsupported XZ header option");
		case XZ_DATA_ERROR:
			bb_error_msg_and_err("corrupted archive");
		case XZ_BUF_ERROR:
			bb_error_msg_and_err("corrupted buffer");
		case XZ_BUF_FULL:
			break;
		default:
			bb_error_msg_and_err("XZ decompression bug!");
		}
	}

out:
err:
	xz_dec_end(s);
	free(in);
	free(out);
	if (ret == XZ_OK)
		return n;
	else if (ret == XZ_BUF_FULL)
		return xstate->mem_output_size_max;
	else
		return -ret;
}
