// SPDX-License-Identifier: GPL-2.0-only
/*
* LZO1X Compressor from LZO
*
* Copyright (C) 1996-2012 Markus F.X.J. Oberhumer <markus@oberhumer.com>
*
* The full LZO package can be found at:
* http://www.oberhumer.com/opensource/lzo/
*
* Changed for Linux kernel use by:
* Nitin Gupta <nitingupta910@gmail.com>
* Richard Purdie <rpurdie@openedhand.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/unaligned.h>
#include <linux/lzo.h>
#include "lzodefs.h"
#undef LZO_UNSAFE
#ifndef LZO_SAFE
#define LZO_UNSAFE
1
#define LZO_SAFE(name) name
#define HAVE_OP(x)
1
#endif
#define NEED_OP(x)
if (!HAVE_OP(x))
goto output_overrun
static noinline
int
LZO_SAFE(lzo1x_1_do_compress)(
const unsigned char *in, size_t in_len,
unsigned char **out,
unsigned char *op_end,
size_t *tp,
void *wrkmem,
signed char *state_offset,
const unsigned char bitstream_version)
{
const unsigned char *ip;
unsigned char *op;
const unsigned char *
const in_end = in + in_len;
const unsigned char *
const ip_end = in + in_len -
20;
const unsigned char *ii;
lzo_dict_t *
const dict = (lzo_dict_t *) wrkmem;
size_t ti = *tp;
op = *out;
ip = in;
ii = ip;
ip += ti <
4 ?
4 - ti :
0;
for (;;) {
const unsigned char *m_pos = NULL;
size_t t, m_len, m_off;
u32 dv;
u32 run_length =
0;
literal:
ip +=
1 + ((ip - ii) >>
5);
next:
if (unlikely(ip >= ip_end))
break;
dv = get_unaligned_le32(ip);
if (dv ==
0 && bitstream_version) {
const unsigned char *ir = ip +
4;
const unsigned char *limit = min(ip_end, ip + MAX_ZERO_RUN_LENGTH +
1);
#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) && \
defined(LZO_FAST_64BIT_MEMORY_ACCESS)
u64 dv64;
for (; (ir +
32) <= limit; ir +=
32) {
dv64 = get_unaligned((u64 *)ir);
dv64 |= get_unaligned((u64 *)ir +
1);
dv64 |= get_unaligned((u64 *)ir +
2);
dv64 |= get_unaligned((u64 *)ir +
3);
if (dv64)
break;
}
for (; (ir +
8) <= limit; ir +=
8) {
dv64 = get_unaligned((u64 *)ir);
if (dv64) {
# if defined(__LITTLE_ENDIAN)
ir += __builtin_ctzll(dv64) >>
3;
# elif
defined(__BIG_ENDIAN)
ir += __builtin_clzll(dv64) >>
3;
# else
# error
"missing endian definition"
# endif
break;
}
}
#else
while ((ir < (
const unsigned char *)
ALIGN((uintptr_t)ir,
4)) &&
(ir < limit) && (*ir ==
0))
ir++;
if (IS_ALIGNED((uintptr_t)ir,
4)) {
for (; (ir +
4) <= limit; ir +=
4) {
dv = *((u32 *)ir);
if (dv) {
# if defined(__LITTLE_ENDIAN)
ir += __builtin_ctz(dv) >>
3;
# elif
defined(__BIG_ENDIAN)
ir += __builtin_clz(dv) >>
3;
# else
# error
"missing endian definition"
# endif
break;
}
}
}
#endif
while (likely(ir < limit) && unlikely(*ir ==
0))
ir++;
run_length = ir - ip;
if (run_length > MAX_ZERO_RUN_LENGTH)
run_length = MAX_ZERO_RUN_LENGTH;
}
else {
t = ((dv *
0x1824429d) >> (
32 - D_BITS)) & D_MASK;
m_pos = in + dict[t];
dict[t] = (lzo_dict_t) (ip - in);
if (unlikely(dv != get_unaligned_le32(m_pos)))
goto literal;
}
ii -= ti;
ti =
0;
t = ip - ii;
if (t !=
0) {
if (t <=
3) {
op[*state_offset] |= t;
NEED_OP(
4);
COPY4(op, ii);
op += t;
}
else if (t <=
16) {
NEED_OP(
17);
*op++ = (t -
3);
COPY8(op, ii);
COPY8(op +
8, ii +
8);
op += t;
}
else {
if (t <=
18) {
NEED_OP(
1);
*op++ = (t -
3);
}
else {
size_t tt = t -
18;
NEED_OP(
1);
*op++ =
0;
while (unlikely(tt >
255)) {
tt -=
255;
NEED_OP(
1);
*op++ =
0;
}
NEED_OP(
1);
*op++ = tt;
}
NEED_OP(t);
do {
COPY8(op, ii);
COPY8(op +
8, ii +
8);
op +=
16;
ii +=
16;
t -=
16;
}
while (t >=
16);
if (t >
0)
do {
*op++ = *ii++;
}
while (--t >
0);
}
}
if (unlikely(run_length)) {
ip += run_length;
run_length -= MIN_ZERO_RUN_LENGTH;
NEED_OP(
4);
put_unaligned_le32((run_length <<
21) |
0xfffc18
| (run_length &
0x7), op);
op +=
4;
run_length =
0;
*state_offset = -
3;
goto finished_writing_instruction;
}
m_len =
4;
{
#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) &&
defined(LZO_USE_CTZ64)
u64 v;
v = get_unaligned((
const u64 *) (ip + m_len)) ^
get_unaligned((
const u64 *) (m_pos + m_len));
if (unlikely(v ==
0)) {
do {
m_len +=
8;
v = get_unaligned((
const u64 *) (ip + m_len)) ^
get_unaligned((
const u64 *) (m_pos + m_len));
if (unlikely(ip + m_len >= ip_end))
goto m_len_done;
}
while (v ==
0);
}
# if defined(__LITTLE_ENDIAN)
m_len += (
unsigned) __builtin_ctzll(v) /
8;
# elif
defined(__BIG_ENDIAN)
m_len += (
unsigned) __builtin_clzll(v) /
8;
# else
# error
"missing endian definition"
# endif
#elif defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) &&
defined(LZO_USE_CTZ32)
u32 v;
v = get_unaligned((
const u32 *) (ip + m_len)) ^
get_unaligned((
const u32 *) (m_pos + m_len));
if (unlikely(v ==
0)) {
do {
m_len +=
4;
v = get_unaligned((
const u32 *) (ip + m_len)) ^
get_unaligned((
const u32 *) (m_pos + m_len));
if (v !=
0)
break;
m_len +=
4;
v = get_unaligned((
const u32 *) (ip + m_len)) ^
get_unaligned((
const u32 *) (m_pos + m_len));
if (unlikely(ip + m_len >= ip_end))
goto m_len_done;
}
while (v ==
0);
}
# if defined(__LITTLE_ENDIAN)
m_len += (
unsigned) __builtin_ctz(v) /
8;
# elif
defined(__BIG_ENDIAN)
m_len += (
unsigned) __builtin_clz(v) /
8;
# else
# error
"missing endian definition"
# endif
#else
if (unlikely(ip[m_len] == m_pos[m_len])) {
do {
m_len +=
1;
if (ip[m_len] != m_pos[m_len])
break;
m_len +=
1;
if (ip[m_len] != m_pos[m_len])
break;
m_len +=
1;
if (ip[m_len] != m_pos[m_len])
break;
m_len +=
1;
if (ip[m_len] != m_pos[m_len])
break;
m_len +=
1;
if (ip[m_len] != m_pos[m_len])
break;
m_len +=
1;
if (ip[m_len] != m_pos[m_len])
break;
m_len +=
1;
if (ip[m_len] != m_pos[m_len])
break;
m_len +=
1;
if (unlikely(ip + m_len >= ip_end))
goto m_len_done;
}
while (ip[m_len] == m_pos[m_len]);
}
#endif
}
m_len_done:
m_off = ip - m_pos;
ip += m_len;
if (m_len <= M2_MAX_LEN && m_off <= M2_MAX_OFFSET) {
m_off -=
1;
NEED_OP(
2);
*op++ = (((m_len -
1) <<
5) | ((m_off &
7) <<
2));
*op++ = (m_off >>
3);
}
else if (m_off <= M3_MAX_OFFSET) {
m_off -=
1;
NEED_OP(
1);
if (m_len <= M3_MAX_LEN)
*op++ = (M3_MARKER | (m_len -
2));
else {
m_len -= M3_MAX_LEN;
*op++ = M3_MARKER |
0;
while (unlikely(m_len >
255)) {
m_len -=
255;
NEED_OP(
1);
*op++ =
0;
}
NEED_OP(
1);
*op++ = (m_len);
}
NEED_OP(
2);
*op++ = (m_off <<
2);
*op++ = (m_off >>
6);
}
else {
m_off -=
0x4000;
NEED_OP(
1);
if (m_len <= M4_MAX_LEN)
*op++ = (M4_MARKER | ((m_off >>
11) &
8)
| (m_len -
2));
else {
if (unlikely(((m_off &
0x403f) ==
0x403f)
&& (m_len >=
261)
&& (m_len <=
264))
&& likely(bitstream_version)) {
// Under lzo-rle, block copies
// for 261 <= length <= 264 and
// (distance & 0x80f3) == 0x80f3
// can result in ambiguous
// output. Adjust length
// to 260 to prevent ambiguity.
ip -= m_len -
260;
m_len =
260;
}
m_len -= M4_MAX_LEN;
*op++ = (M4_MARKER | ((m_off >>
11) &
8));
while (unlikely(m_len >
255)) {
NEED_OP(
1);
m_len -=
255;
*op++ =
0;
}
NEED_OP(
1);
*op++ = (m_len);
}
NEED_OP(
2);
*op++ = (m_off <<
2);
*op++ = (m_off >>
6);
}
*state_offset = -
2;
finished_writing_instruction:
ii = ip;
goto next;
}
*out = op;
*tp = in_end - (ii - ti);
return LZO_E_OK;
output_overrun:
return LZO_E_OUTPUT_OVERRUN;
}
static int LZO_SAFE(lzogeneric1x_1_compress)(
const unsigned char *in, size_t in_len,
unsigned char *out, size_t *out_len,
void *wrkmem,
const unsigned char bitstream_version)
{
unsigned char *
const op_end = out + *out_len;
const unsigned char *ip = in;
unsigned char *op = out;
unsigned char *data_start;
size_t l = in_len;
size_t t =
0;
signed char state_offset = -
2;
unsigned int m4_max_offset;
// LZO v0 will never write 17 as first byte (except for zero-length
// input), so this is used to version the bitstream
if (bitstream_version >
0) {
*op++ =
17;
*op++ = bitstream_version;
m4_max_offset = M4_MAX_OFFSET_V1;
}
else {
m4_max_offset = M4_MAX_OFFSET_V0;
}
data_start = op;
while (l >
20) {
size_t ll = min_t(size_t, l, m4_max_offset +
1);
uintptr_t ll_end = (uintptr_t) ip + ll;
int err;
if ((ll_end + ((t + ll) >>
5)) <= ll_end)
break;
BUILD_BUG_ON(D_SIZE *
sizeof(lzo_dict_t) > LZO1X_1_MEM_COMPRESS);
memset(wrkmem,
0, D_SIZE *
sizeof(lzo_dict_t));
err = LZO_SAFE(lzo1x_1_do_compress)(
ip, ll, &op, op_end, &t, wrkmem,
&state_offset, bitstream_version);
if (err != LZO_E_OK)
return err;
ip += ll;
l -= ll;
}
t += l;
if (t >
0) {
const unsigned char *ii = in + in_len - t;
if (op == data_start && t <=
238) {
NEED_OP(
1);
*op++ = (
17 + t);
}
else if (t <=
3) {
op[state_offset] |= t;
}
else if (t <=
18) {
NEED_OP(
1);
*op++ = (t -
3);
}
else {
size_t tt = t -
18;
NEED_OP(
1);
*op++ =
0;
while (tt >
255) {
tt -=
255;
NEED_OP(
1);
*op++ =
0;
}
NEED_OP(
1);
*op++ = tt;
}
NEED_OP(t);
if (t >=
16)
do {
COPY8(op, ii);
COPY8(op +
8, ii +
8);
op +=
16;
ii +=
16;
t -=
16;
}
while (t >=
16);
if (t >
0)
do {
*op++ = *ii++;
}
while (--t >
0);
}
NEED_OP(
3);
*op++ = M4_MARKER |
1;
*op++ =
0;
*op++ =
0;
*out_len = op - out;
return LZO_E_OK;
output_overrun:
return LZO_E_OUTPUT_OVERRUN;
}
int LZO_SAFE(lzo1x_1_compress)(
const unsigned char *in, size_t in_len,
unsigned char *out, size_t *out_len,
void *wrkmem)
{
return LZO_SAFE(lzogeneric1x_1_compress)(
in, in_len, out, out_len, wrkmem,
0);
}
int LZO_SAFE(lzorle1x_1_compress)(
const unsigned char *in, size_t in_len,
unsigned char *out, size_t *out_len,
void *wrkmem)
{
return LZO_SAFE(lzogeneric1x_1_compress)(
in, in_len, out, out_len, wrkmem, LZO_VERSION);
}
EXPORT_SYMBOL_GPL(LZO_SAFE(lzo1x_1_compress));
EXPORT_SYMBOL_GPL(LZO_SAFE(lzorle1x_1_compress));
#ifndef LZO_UNSAFE
MODULE_LICENSE(
"GPL");
MODULE_DESCRIPTION(
"LZO1X-1 Compressor");
#endif