mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2026-06-02 09:46:37 +03:00
The current gzwrite() implementation is limited to 4 GiB compressed
input buffer size due to struct z_stream_s { uInt avail_in } member,
which is of type unsigned int. Current gzwrite() implementation sets
the entire input buffer size as avail_in and performs decompression
of the whole compressed input buffer in one round, which limits the
size of input buffer to 4 GiB.
Rework the decompression loop to use chunked approach, and decompress
the input buffer in up to 4 GiB - 1 kiB avail_in chunks, possibly in
multiple decompression rounds. This way, the compressed input buffer
size is limited by gzwrite() function 'len' parameter type, which is
unsigned long.
In case of sandbox build, include parsing of 'gzwrite_chunk'
environment variable, so the chunked approach can be thoroughly tested
with non default chunk size. For non-sandbox builds, the chunk size is
4 GiB - 1 kiB.
The gzwrite test case is extended to test various chunk sizes during
gzwrite decompression test.
Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
343 lines
7.6 KiB
C
343 lines
7.6 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2000-2006
|
|
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
|
*/
|
|
|
|
#include <blk.h>
|
|
#include <command.h>
|
|
#include <console.h>
|
|
#include <div64.h>
|
|
#include <env.h>
|
|
#include <gzip.h>
|
|
#include <image.h>
|
|
#include <linux/sizes.h>
|
|
#include <malloc.h>
|
|
#include <memalign.h>
|
|
#include <u-boot/crc.h>
|
|
#include <watchdog.h>
|
|
#include <u-boot/zlib.h>
|
|
#include <asm/sections.h>
|
|
|
|
#define HEADER0 '\x1f'
|
|
#define HEADER1 '\x8b'
|
|
#define ZALLOC_ALIGNMENT 16
|
|
#define HEAD_CRC 2
|
|
#define EXTRA_FIELD 4
|
|
#define ORIG_NAME 8
|
|
#define COMMENT 0x10
|
|
#define RESERVED 0xe0
|
|
#define DEFLATED 8
|
|
|
|
void *gzalloc(void *x, unsigned items, unsigned size)
|
|
{
|
|
void *p;
|
|
|
|
size *= items;
|
|
size = (size + ZALLOC_ALIGNMENT - 1) & ~(ZALLOC_ALIGNMENT - 1);
|
|
|
|
p = malloc (size);
|
|
|
|
return (p);
|
|
}
|
|
|
|
void gzfree(void *x, void *addr, unsigned nb)
|
|
{
|
|
free (addr);
|
|
}
|
|
|
|
__rcode int gzip_parse_header(const unsigned char *src, unsigned long len)
|
|
{
|
|
int i, flags;
|
|
|
|
/* skip header */
|
|
i = 10;
|
|
flags = src[3];
|
|
if (src[2] != DEFLATED || (flags & RESERVED) != 0) {
|
|
puts ("Error: Bad gzipped data\n");
|
|
return (-1);
|
|
}
|
|
if ((flags & EXTRA_FIELD) != 0)
|
|
i = 12 + src[10] + (src[11] << 8);
|
|
if ((flags & ORIG_NAME) != 0)
|
|
while (src[i++] != 0)
|
|
;
|
|
if ((flags & COMMENT) != 0)
|
|
while (src[i++] != 0)
|
|
;
|
|
if ((flags & HEAD_CRC) != 0)
|
|
i += 2;
|
|
if (i >= len) {
|
|
puts ("Error: gunzip out of data in header\n");
|
|
return (-1);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
__rcode int gunzip(void *dst, int dstlen, unsigned char *src, unsigned long *lenp)
|
|
{
|
|
int offset = gzip_parse_header(src, *lenp);
|
|
|
|
if (offset < 0)
|
|
return offset;
|
|
|
|
return zunzip(dst, dstlen, src, lenp, 1, offset);
|
|
}
|
|
|
|
#ifdef CONFIG_CMD_UNZIP
|
|
__weak
|
|
void gzwrite_progress_init(size_t expectedsize)
|
|
{
|
|
putc('\n');
|
|
}
|
|
|
|
__weak
|
|
void gzwrite_progress(int iteration,
|
|
ulong bytes_written,
|
|
size_t total_bytes)
|
|
{
|
|
if (0 == (iteration & 3))
|
|
printf("%lu/%zu\r", bytes_written, total_bytes);
|
|
}
|
|
|
|
__weak
|
|
void gzwrite_progress_finish(int returnval,
|
|
ulong bytes_written,
|
|
size_t total_bytes,
|
|
u32 expected_crc,
|
|
u32 calculated_crc)
|
|
{
|
|
if (0 == returnval) {
|
|
printf("\n\t%zu bytes, crc 0x%08x\n",
|
|
total_bytes, calculated_crc);
|
|
} else {
|
|
printf("\n\tuncompressed %lu of %zu\n"
|
|
"\tcrcs == 0x%08x/0x%08x\n",
|
|
bytes_written, total_bytes,
|
|
expected_crc, calculated_crc);
|
|
}
|
|
}
|
|
|
|
int gzwrite(unsigned char *src, size_t len, struct blk_desc *dev,
|
|
size_t szwritebuf, off_t startoffs, size_t szexpected)
|
|
{
|
|
int flags;
|
|
z_stream s;
|
|
int r = 0;
|
|
unsigned char *writebuf;
|
|
unsigned crc = 0;
|
|
ulong totalfilled = 0;
|
|
lbaint_t blksperbuf, outblock;
|
|
u32 expected_crc;
|
|
size_t i, payload_size;
|
|
unsigned long blocks_written;
|
|
lbaint_t writeblocks;
|
|
int numfilled = 0;
|
|
int iteration = 0;
|
|
/*
|
|
* Allow runtime configuration of decompression chunk on
|
|
* sandbox to better cover the chunked decompression
|
|
* functionality without having to use > 4 GiB files.
|
|
*/
|
|
const ulong minchunk = 0x400;
|
|
const ulong maxchunk = SZ_4G - minchunk;
|
|
const ulong chunk =
|
|
CONFIG_IS_ENABLED(SANDBOX,
|
|
(clamp(env_get_ulong("gzwrite_chunk", 10, maxchunk),
|
|
minchunk, maxchunk)),
|
|
(maxchunk));
|
|
|
|
if (!szwritebuf ||
|
|
(szwritebuf % dev->blksz) ||
|
|
(szwritebuf < dev->blksz)) {
|
|
printf("%s: size %zu not a multiple of %lu\n",
|
|
__func__, szwritebuf, dev->blksz);
|
|
return -1;
|
|
}
|
|
|
|
if (startoffs & (dev->blksz-1)) {
|
|
printf("%s: start offset %lu not a multiple of %lu\n",
|
|
__func__, startoffs, dev->blksz);
|
|
return -1;
|
|
}
|
|
|
|
blksperbuf = szwritebuf / dev->blksz;
|
|
outblock = lldiv(startoffs, dev->blksz);
|
|
|
|
/* skip header */
|
|
i = 10;
|
|
flags = src[3];
|
|
if (src[2] != DEFLATED || (flags & RESERVED) != 0) {
|
|
puts("Error: Bad gzipped data\n");
|
|
return -1;
|
|
}
|
|
if ((flags & EXTRA_FIELD) != 0)
|
|
i = 12 + src[10] + (src[11] << 8);
|
|
if ((flags & ORIG_NAME) != 0)
|
|
while (src[i++] != 0)
|
|
;
|
|
if ((flags & COMMENT) != 0)
|
|
while (src[i++] != 0)
|
|
;
|
|
if ((flags & HEAD_CRC) != 0)
|
|
i += 2;
|
|
|
|
if (i >= len-8) {
|
|
puts("Error: gunzip out of data in header");
|
|
return -1;
|
|
}
|
|
|
|
payload_size = len - i;
|
|
|
|
memcpy(&expected_crc, src + len - 8, sizeof(expected_crc));
|
|
expected_crc = le32_to_cpu(expected_crc);
|
|
u32 szuncompressed;
|
|
memcpy(&szuncompressed, src + len - 4, sizeof(szuncompressed));
|
|
if (szexpected == 0) {
|
|
szexpected = le32_to_cpu(szuncompressed);
|
|
} else if (szuncompressed != (u32)szexpected) {
|
|
printf("size of %zx doesn't match trailer low bits %x\n",
|
|
szexpected, szuncompressed);
|
|
return -1;
|
|
}
|
|
if (lldiv(szexpected, dev->blksz) > (dev->lba - outblock)) {
|
|
printf("%s: uncompressed size %zu exceeds device size\n",
|
|
__func__, szexpected);
|
|
return -1;
|
|
}
|
|
|
|
gzwrite_progress_init(szexpected);
|
|
|
|
s.zalloc = gzalloc;
|
|
s.zfree = gzfree;
|
|
|
|
r = inflateInit2(&s, -MAX_WBITS);
|
|
if (r != Z_OK) {
|
|
printf("Error: inflateInit2() returned %d\n", r);
|
|
return -1;
|
|
}
|
|
|
|
src += i;
|
|
s.avail_in = 0;
|
|
writebuf = (unsigned char *)malloc_cache_aligned(szwritebuf);
|
|
|
|
/* decompress until deflate stream ends or end of file */
|
|
do {
|
|
if (s.avail_in == 0) {
|
|
if (payload_size == 0) {
|
|
printf("%s: weird termination with result %d\n",
|
|
__func__, r);
|
|
break;
|
|
}
|
|
|
|
s.next_in = src;
|
|
s.avail_in = (payload_size > chunk) ? chunk : payload_size;
|
|
src += s.avail_in;
|
|
payload_size -= s.avail_in;
|
|
}
|
|
|
|
/* run inflate() on input until output buffer not full */
|
|
do {
|
|
if (numfilled) {
|
|
s.avail_out = szwritebuf - numfilled;
|
|
s.next_out = writebuf + numfilled;
|
|
} else {
|
|
s.avail_out = szwritebuf;
|
|
s.next_out = writebuf;
|
|
}
|
|
r = inflate(&s, Z_SYNC_FLUSH);
|
|
if ((r != Z_OK) &&
|
|
(r != Z_STREAM_END)) {
|
|
printf("Error: inflate() returned %d\n", r);
|
|
goto out;
|
|
}
|
|
crc = crc32(crc, writebuf + numfilled,
|
|
szwritebuf - s.avail_out - numfilled);
|
|
totalfilled += szwritebuf - s.avail_out - numfilled;
|
|
numfilled = szwritebuf - s.avail_out;
|
|
if (numfilled < szwritebuf) {
|
|
writeblocks = (numfilled+dev->blksz-1)
|
|
/ dev->blksz;
|
|
memset(writebuf+numfilled, 0,
|
|
dev->blksz-(numfilled%dev->blksz));
|
|
} else {
|
|
writeblocks = blksperbuf;
|
|
numfilled = 0;
|
|
}
|
|
|
|
gzwrite_progress(iteration++,
|
|
totalfilled,
|
|
szexpected);
|
|
if (!numfilled) {
|
|
blocks_written = blk_dwrite(dev, outblock,
|
|
writeblocks, writebuf);
|
|
outblock += blocks_written;
|
|
}
|
|
if (ctrlc()) {
|
|
puts("abort\n");
|
|
goto out;
|
|
}
|
|
schedule();
|
|
} while (s.avail_out == 0);
|
|
/* done when inflate() says it's done */
|
|
} while (r != Z_STREAM_END);
|
|
|
|
if (numfilled) {
|
|
blocks_written = blk_dwrite(dev, outblock,
|
|
writeblocks, writebuf);
|
|
outblock += blocks_written;
|
|
}
|
|
|
|
if ((szexpected != totalfilled) ||
|
|
(crc != expected_crc))
|
|
r = -1;
|
|
else
|
|
r = 0;
|
|
|
|
out:
|
|
gzwrite_progress_finish(r, totalfilled, szexpected,
|
|
expected_crc, crc);
|
|
free(writebuf);
|
|
inflateEnd(&s);
|
|
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Uncompress blocks compressed with zlib without headers
|
|
*/
|
|
__rcode int zunzip(void *dst, int dstlen, unsigned char *src,
|
|
unsigned long *lenp, int stoponerr, int offset)
|
|
{
|
|
z_stream s;
|
|
int err = 0;
|
|
int r;
|
|
|
|
s.zalloc = gzalloc;
|
|
s.zfree = gzfree;
|
|
|
|
r = inflateInit2(&s, -MAX_WBITS);
|
|
if (r != Z_OK) {
|
|
printf("Error: inflateInit2() returned %d\n", r);
|
|
return -1;
|
|
}
|
|
s.next_in = src + offset;
|
|
s.avail_in = *lenp - offset;
|
|
s.next_out = dst;
|
|
s.avail_out = dstlen;
|
|
do {
|
|
r = inflate(&s, Z_FINISH);
|
|
if (stoponerr == 1 && r != Z_STREAM_END &&
|
|
(s.avail_in == 0 || s.avail_out == 0 || r != Z_BUF_ERROR)) {
|
|
printf("Error: inflate() returned %d\n", r);
|
|
err = r;
|
|
break;
|
|
}
|
|
} while (r == Z_BUF_ERROR);
|
|
*lenp = s.next_out - (unsigned char *) dst;
|
|
inflateEnd(&s);
|
|
|
|
return err;
|
|
}
|