/*
* Copyright (C) 2023 Patrick McDermott
*
* This file is part of opkg-opkg.
*
* opkg-opkg is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opkg-opkg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opkg-opkg. If not, see .
*/
#include
#include
#include "defs.h"
#include "gzip.h"
#include "ustar.h"
#define OPKG_OPK_GZIP_WINDOW_BITS_ (15 + 16)
enum _opkg_opk_gzip_dir {
_OPKG_OPK_GZIP_DIR_READ,
_OPKG_OPK_GZIP_DIR_WRITE,
};
struct opkg_opk_gzip {
enum _opkg_opk_gzip_dir dir;
char *buffer;
size_t buffer_size;
opkg_opk_gzip_read_func *read_func;
opkg_opk_gzip_write_func *write_func;
void *user_data;
z_stream stream;
gz_header gz_header;
};
static struct opkg_opk_gzip *
_opkg_opk_gzip_init(enum _opkg_opk_gzip_dir dir,
opkg_opk_gzip_read_func *read_func,
opkg_opk_gzip_write_func *write_func, void *user_data)
{
struct opkg_opk_gzip *gzip;
gzip = malloc(sizeof(*gzip));
if (gzip == NULL) {
return NULL;
}
gzip->dir = dir;
gzip->read_func = read_func;
gzip->write_func = write_func;
gzip->user_data = user_data;
gzip->stream.zalloc = Z_NULL;
gzip->stream.zfree = Z_NULL;
gzip->stream.opaque = Z_NULL;
if (dir == _OPKG_OPK_GZIP_DIR_READ) {
gzip->stream.next_in = Z_NULL;
gzip->stream.avail_in = 0;
if (inflateInit2(&gzip->stream, OPKG_OPK_GZIP_WINDOW_BITS_) !=
Z_OK) {
free(gzip);
return NULL;
}
} else {
gzip->stream.next_out = Z_NULL;
gzip->stream.avail_out = 0;
if (deflateInit2(&gzip->stream, 9, Z_DEFLATED,
OPKG_OPK_GZIP_WINDOW_BITS_, 9,
Z_DEFAULT_STRATEGY) != Z_OK) {
free(gzip);
return NULL;
}
gzip->gz_header.text = 0;
gzip->gz_header.time = 0; /* Stored as 32-bit uint */
gzip->gz_header.os = 3; /* Unix, per RFC 1952 */
gzip->gz_header.extra = Z_NULL;
gzip->gz_header.name = Z_NULL;
gzip->gz_header.comment = Z_NULL;
gzip->gz_header.hcrc = 0;
if (deflateSetHeader(&gzip->stream, &gzip->gz_header) != Z_OK) {
deflateEnd(&gzip->stream);
free(gzip);
return NULL;
}
}
return gzip;
}
struct opkg_opk_gzip *
opkg_opk_gzip_init_read(opkg_opk_gzip_read_func *read, void *user_data)
{
return _opkg_opk_gzip_init(_OPKG_OPK_GZIP_DIR_READ,
read, NULL, user_data);
}
struct opkg_opk_gzip *
opkg_opk_gzip_init_write(opkg_opk_gzip_write_func *write, void *user_data)
{
return _opkg_opk_gzip_init(_OPKG_OPK_GZIP_DIR_WRITE,
NULL, write, user_data);
}
int
opkg_opk_gzip_set_write_buffer(struct opkg_opk_gzip *gzip, char *buffer,
size_t size)
{
gzip->buffer = buffer;
gzip->buffer_size = size;
return OPKG_OPK_OK;
}
int
opkg_opk_gzip_read(struct opkg_opk_gzip *gzip, void *record)
{
int end;
/* Sanity check */
if (gzip->dir != _OPKG_OPK_GZIP_DIR_READ) {
return OPKG_OPK_ERROR;
}
gzip->stream.next_out = record;
gzip->stream.avail_out = OPKG_OPK_USTAR_RECORD_SIZE;
for (;;) {
end = 0;
if (gzip->stream.avail_in == 0) {
/* Input buffer is empty and needs refilled. */
switch (gzip->read_func(gzip->user_data,
(char **) &gzip->stream.next_in,
(size_t *) &gzip->stream.
avail_in)) {
case OPKG_OPK_OK:
break;
case OPKG_OPK_END:
end = 1;
break;
case OPKG_OPK_ERROR:
default:
return OPKG_OPK_ERROR;
}
}
switch (inflate(&gzip->stream, Z_SYNC_FLUSH)) {
case Z_OK:
break;
case Z_BUF_ERROR:
if (end == 1) {
return OPKG_OPK_ERROR;
}
break;
case Z_STREAM_END:
if (gzip->stream.avail_out != 0) {
/* Premature end */
return OPKG_OPK_ERROR;
}
return OPKG_OPK_END;
default:
return OPKG_OPK_ERROR;
}
if (gzip->stream.avail_out == 0) {
/* Output buffer is filled and ready for use. */
return OPKG_OPK_OK;
}
}
}
static int
_opkg_opk_gzip_write(struct opkg_opk_gzip *gzip, void *record, size_t size,
int last)
{
/* Sanity check */
if (gzip->dir != _OPKG_OPK_GZIP_DIR_WRITE) {
return OPKG_OPK_ERROR;
}
gzip->stream.next_in = record;
gzip->stream.avail_in = size;
do {
gzip->stream.next_out = gzip->buffer;
gzip->stream.avail_out = gzip->buffer_size;
switch (deflate(&gzip->stream,
(last > 0 ? Z_FINISH : Z_NO_FLUSH))) {
case Z_OK:
case Z_BUF_ERROR:
case Z_STREAM_END:
break;
default:
return OPKG_OPK_ERROR;
}
/* Process output buffer. */
switch (gzip->write_func(gzip->user_data,
gzip->buffer_size
- gzip->stream.avail_out)) {
case OPKG_OPK_OK:
break;
case OPKG_OPK_END:
case OPKG_OPK_ERROR:
default:
return OPKG_OPK_ERROR;
}
} while (gzip->stream.avail_out == 0);
if (gzip->stream.avail_in != 0) {
return OPKG_OPK_ERROR;
}
if (last == 0) {
/* Input buffer is empty and needs refilled. */
return OPKG_OPK_OK;
} else {
return OPKG_OPK_END;
}
}
int
opkg_opk_gzip_write(struct opkg_opk_gzip *gzip, void *record, size_t size)
{
if (_opkg_opk_gzip_write(gzip, record, size, 0) == OPKG_OPK_OK) {
return OPKG_OPK_OK;
} else {
return OPKG_OPK_ERROR;
}
}
int
opkg_opk_gzip_free(struct opkg_opk_gzip *gzip)
{
int ret;
ret = OPKG_OPK_OK;
if (gzip->dir == _OPKG_OPK_GZIP_DIR_READ) {
if (inflateEnd(&gzip->stream) != Z_OK) {
ret = OPKG_OPK_ERROR;
}
} else {
if (_opkg_opk_gzip_write(gzip, NULL, 0, 1) != OPKG_OPK_END) {
ret = OPKG_OPK_ERROR;
}
if (deflateEnd(&gzip->stream) != Z_OK) {
ret = OPKG_OPK_ERROR;
}
}
free(gzip);
return ret;
}