96 lines
2.2 KiB
C
96 lines
2.2 KiB
C
/* SPDX-License-Identifier: BSD-2-Clause */
|
|
/* Minimal LZ4 decompressor for EROFS FreeBSD */
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include "erofs_defs.h"
|
|
#include "internal.h"
|
|
|
|
static int
|
|
lz4_finish(const uint8_t *ip, const uint8_t *iend, int partial)
|
|
{
|
|
if (partial)
|
|
return (0);
|
|
while (ip < iend) {
|
|
if (*ip++ != 0)
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
lz4_decompress(void *src, void *dst, size_t srclen, size_t dstlen, int partial)
|
|
{
|
|
const uint8_t *ip, *iend;
|
|
uint8_t *op, *oend;
|
|
unsigned int token;
|
|
size_t length, copylen;
|
|
size_t offset;
|
|
|
|
ip = src;
|
|
iend = ip + srclen;
|
|
op = dst;
|
|
oend = op + dstlen;
|
|
if (iend < ip || oend < op)
|
|
return (-1);
|
|
|
|
while (ip < iend) {
|
|
token = *ip++;
|
|
length = token >> EROFS_LZ4_TOKEN_LITERAL_SHIFT;
|
|
if (length == EROFS_LZ4_MAX_RUN) {
|
|
unsigned int value;
|
|
do {
|
|
if (ip >= iend)
|
|
return (-1);
|
|
value = *ip++;
|
|
if (length > SIZE_MAX - value)
|
|
return (-1);
|
|
length += value;
|
|
} while (value == EROFS_LZ4_EXT_SENTINEL);
|
|
}
|
|
if (length > (size_t)(iend - ip))
|
|
return (-1);
|
|
if (!partial && length > (size_t)(oend - op))
|
|
return (-1);
|
|
copylen = MIN(length, (size_t)(oend - op));
|
|
memcpy(op, ip, copylen);
|
|
ip += length;
|
|
op += copylen;
|
|
if (op == oend)
|
|
return (lz4_finish(ip, iend, partial));
|
|
if (ip >= iend)
|
|
break;
|
|
if (ip + EROFS_LZ4_OFFSET_BYTES > iend)
|
|
return (-1);
|
|
offset = ip[0] | (ip[1] << 8);
|
|
ip += EROFS_LZ4_OFFSET_BYTES;
|
|
if (offset == 0 || offset > (size_t)(op - (uint8_t *)dst))
|
|
return (-1);
|
|
length = token & EROFS_LZ4_TOKEN_MATCH_MASK;
|
|
if (length == EROFS_LZ4_MAX_RUN) {
|
|
unsigned int value;
|
|
do {
|
|
if (ip >= iend)
|
|
return (-1);
|
|
value = *ip++;
|
|
if (length > SIZE_MAX - value)
|
|
return (-1);
|
|
length += value;
|
|
} while (value == EROFS_LZ4_EXT_SENTINEL);
|
|
}
|
|
if (length > SIZE_MAX - EROFS_LZ4_MIN_MATCH)
|
|
return (-1);
|
|
length += EROFS_LZ4_MIN_MATCH;
|
|
if (!partial && length > (size_t)(oend - op))
|
|
return (-1);
|
|
copylen = MIN(length, (size_t)(oend - op));
|
|
while (copylen-- != 0) {
|
|
*op = *(op - offset);
|
|
++op;
|
|
}
|
|
if (op == oend)
|
|
return (lz4_finish(ip, iend, partial));
|
|
}
|
|
return (op == oend ? lz4_finish(ip, iend, partial) : -1);
|
|
}
|