aboutsummaryrefslogtreecommitdiff
path: root/lib/io
diff options
context:
space:
mode:
Diffstat (limited to 'lib/io')
-rw-r--r--lib/io/Makemodule.am45
-rw-r--r--lib/io/compress/bzip2.c96
-rw-r--r--lib/io/compress/gzip.c92
-rw-r--r--lib/io/compress/ostream_compressor.c108
-rw-r--r--lib/io/compress/xz.c80
-rw-r--r--lib/io/compress/zstd.c94
-rw-r--r--lib/io/get_line.c118
-rw-r--r--lib/io/internal.h81
-rw-r--r--lib/io/istream.c91
-rw-r--r--lib/io/ostream.c84
-rw-r--r--lib/io/printf.c30
-rw-r--r--lib/io/uncompress/autodetect.c55
-rw-r--r--lib/io/uncompress/bzip2.c118
-rw-r--r--lib/io/uncompress/gzip.c106
-rw-r--r--lib/io/uncompress/istream_compressor.c69
-rw-r--r--lib/io/uncompress/xz.c96
-rw-r--r--lib/io/uncompress/zstd.c81
-rw-r--r--lib/io/unix/istream.c124
-rw-r--r--lib/io/unix/ostream.c173
-rw-r--r--lib/io/win32/istream.c138
-rw-r--r--lib/io/win32/ostream.c197
-rw-r--r--lib/io/xfrm.c67
22 files changed, 2143 insertions, 0 deletions
diff --git a/lib/io/Makemodule.am b/lib/io/Makemodule.am
new file mode 100644
index 0000000..63ce958
--- /dev/null
+++ b/lib/io/Makemodule.am
@@ -0,0 +1,45 @@
+libio_a_SOURCES = lib/io/internal.h
+libio_a_SOURCES += include/io/istream.h lib/io/ostream.c lib/io/printf.c
+libio_a_SOURCES += include/io/ostream.h lib/io/istream.c lib/io/get_line.c
+libio_a_SOURCES += include/io/xfrm.h lib/io/xfrm.c
+libio_a_SOURCES += include/io/file.h include/io/std.h
+libio_a_SOURCES += lib/io/compress/ostream_compressor.c
+libio_a_SOURCES += lib/io/uncompress/istream_compressor.c
+libio_a_SOURCES += lib/io/uncompress/autodetect.c
+libio_a_CFLAGS = $(AM_CFLAGS) $(ZLIB_CFLAGS) $(XZ_CFLAGS)
+libio_a_CFLAGS += $(ZSTD_CFLAGS) $(BZIP2_CFLAGS)
+libio_a_CPPFLAGS = $(AM_CPPFLAGS)
+
+if WINDOWS
+libio_a_SOURCES += lib/io/win32/ostream.c
+libio_a_SOURCES += lib/io/win32/istream.c
+libio_a_CFLAGS += -DWINVER=0x0600 -D_WIN32_WINNT=0x0600
+else
+libio_a_SOURCES += lib/io/unix/ostream.c
+libio_a_SOURCES += lib/io/unix/istream.c
+endif
+
+if WITH_XZ
+libio_a_SOURCES += lib/io/compress/xz.c lib/io/uncompress/xz.c
+libio_a_CPPFLAGS += -DWITH_XZ
+endif
+
+if WITH_GZIP
+libio_a_SOURCES += lib/io/compress/gzip.c
+libio_a_SOURCES += lib/io/uncompress/gzip.c
+libio_a_CPPFLAGS += -DWITH_GZIP
+endif
+
+if WITH_ZSTD
+libio_a_SOURCES += lib/io/compress/zstd.c
+libio_a_SOURCES += lib/io/uncompress/zstd.c
+libio_a_CPPFLAGS += -DWITH_ZSTD
+endif
+
+if WITH_BZIP2
+libio_a_SOURCES += lib/io/compress/bzip2.c
+libio_a_SOURCES += lib/io/uncompress/bzip2.c
+libio_a_CPPFLAGS += -DWITH_BZIP2
+endif
+
+noinst_LIBRARIES += libio.a
diff --git a/lib/io/compress/bzip2.c b/lib/io/compress/bzip2.c
new file mode 100644
index 0000000..7f0c09a
--- /dev/null
+++ b/lib/io/compress/bzip2.c
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * bzip2.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#include <bzlib.h>
+
+typedef struct {
+ ostream_comp_t base;
+
+ bz_stream strm;
+} ostream_bzip2_t;
+
+static int flush_inbuf(ostream_comp_t *base, bool finish)
+{
+ ostream_bzip2_t *bzip2 = (ostream_bzip2_t *)base;
+ size_t have;
+ int ret;
+
+ bzip2->strm.next_in = (char *)base->inbuf;
+
+ if (base->inbuf_used > sizeof(base->inbuf))
+ base->inbuf_used = sizeof(base->inbuf);
+
+ if ((sizeof(size_t) > sizeof(unsigned int)) &&
+ (base->inbuf_used > (size_t)UINT_MAX)) {
+ bzip2->strm.avail_in = UINT_MAX;
+ } else {
+ bzip2->strm.avail_in = (unsigned int)base->inbuf_used;
+ }
+
+ for (;;) {
+ bzip2->strm.next_out = (char *)base->outbuf;
+ bzip2->strm.avail_out = sizeof(base->outbuf);
+
+ ret = BZ2_bzCompress(&bzip2->strm, finish ? BZ_FINISH : BZ_RUN);
+
+ if (ret < 0 && ret != BZ_OUTBUFF_FULL) {
+ fprintf(stderr, "%s: internal error in bzip2 "
+ "compressor.\n",
+ base->wrapped->get_filename(base->wrapped));
+ return -1;
+ }
+
+ have = sizeof(base->outbuf) - bzip2->strm.avail_out;
+
+ if (base->wrapped->append(base->wrapped, base->outbuf, have))
+ return -1;
+
+ if (ret == BZ_STREAM_END || ret == BZ_OUTBUFF_FULL ||
+ bzip2->strm.avail_in == 0) {
+ break;
+ }
+ }
+
+ if (bzip2->strm.avail_in > 0) {
+ memmove(base->inbuf, bzip2->strm.next_in,
+ bzip2->strm.avail_in);
+ }
+
+ base->inbuf_used = bzip2->strm.avail_in;
+ return 0;
+}
+
+static void cleanup(ostream_comp_t *base)
+{
+ ostream_bzip2_t *bzip2 = (ostream_bzip2_t *)base;
+
+ BZ2_bzCompressEnd(&bzip2->strm);
+}
+
+ostream_comp_t *ostream_bzip2_create(const char *filename)
+{
+ ostream_bzip2_t *bzip2 = calloc(1, sizeof(*bzip2));
+ ostream_comp_t *base = (ostream_comp_t *)bzip2;
+
+ if (bzip2 == NULL) {
+ fprintf(stderr, "%s: creating bzip2 compressor: %s.\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+
+ if (BZ2_bzCompressInit(&bzip2->strm, 9, 0, 30) != BZ_OK) {
+ fprintf(stderr, "%s: error initializing bzip2 compressor.\n",
+ filename);
+ free(bzip2);
+ return NULL;
+ }
+
+ base->flush_inbuf = flush_inbuf;
+ base->cleanup = cleanup;
+ return base;
+}
diff --git a/lib/io/compress/gzip.c b/lib/io/compress/gzip.c
new file mode 100644
index 0000000..b73a258
--- /dev/null
+++ b/lib/io/compress/gzip.c
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * gzip.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#include <zlib.h>
+
+typedef struct {
+ ostream_comp_t base;
+
+ z_stream strm;
+} ostream_gzip_t;
+
+static int flush_inbuf(ostream_comp_t *base, bool finish)
+{
+ ostream_gzip_t *gzip = (ostream_gzip_t *)base;
+ size_t have;
+ int ret;
+
+ if (base->inbuf_used > sizeof(base->inbuf))
+ base->inbuf_used = sizeof(base->inbuf);
+
+ if (sizeof(size_t) > sizeof(uInt)) {
+ gzip->strm.avail_in = ~((uInt)0);
+
+ if ((size_t)gzip->strm.avail_in > base->inbuf_used)
+ gzip->strm.avail_in = (uInt)base->inbuf_used;
+ } else {
+ gzip->strm.avail_in = (uInt)base->inbuf_used;
+ }
+
+ gzip->strm.next_in = base->inbuf;
+
+ do {
+ gzip->strm.avail_out = BUFSZ;
+ gzip->strm.next_out = base->outbuf;
+
+ ret = deflate(&gzip->strm, finish ? Z_FINISH : Z_NO_FLUSH);
+
+ if (ret == Z_STREAM_ERROR) {
+ fprintf(stderr,
+ "%s: internal error in gzip compressor.\n",
+ base->wrapped->get_filename(base->wrapped));
+ return -1;
+ }
+
+ have = BUFSZ - gzip->strm.avail_out;
+
+ if (base->wrapped->append(base->wrapped, base->outbuf, have))
+ return -1;
+ } while (gzip->strm.avail_out == 0);
+
+ base->inbuf_used = 0;
+ return 0;
+}
+
+static void cleanup(ostream_comp_t *base)
+{
+ ostream_gzip_t *gzip = (ostream_gzip_t *)base;
+
+ deflateEnd(&gzip->strm);
+}
+
+ostream_comp_t *ostream_gzip_create(const char *filename)
+{
+ ostream_gzip_t *gzip = calloc(1, sizeof(*gzip));
+ ostream_comp_t *base = (ostream_comp_t *)gzip;
+ int ret;
+
+ if (gzip == NULL) {
+ fprintf(stderr, "%s: creating gzip wrapper: %s.\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+
+ ret = deflateInit2(&gzip->strm, 9, Z_DEFLATED, 16 + 15, 8,
+ Z_DEFAULT_STRATEGY);
+ if (ret != Z_OK) {
+ fprintf(stderr,
+ "%s: internal error creating gzip compressor.\n",
+ filename);
+ free(gzip);
+ return NULL;
+ }
+
+ base->flush_inbuf = flush_inbuf;
+ base->cleanup = cleanup;
+ return base;
+}
diff --git a/lib/io/compress/ostream_compressor.c b/lib/io/compress/ostream_compressor.c
new file mode 100644
index 0000000..314ce6b
--- /dev/null
+++ b/lib/io/compress/ostream_compressor.c
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * ostream_compressor.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+static int comp_append(ostream_t *strm, const void *data, size_t size)
+{
+ ostream_comp_t *comp = (ostream_comp_t *)strm;
+ size_t diff;
+
+ while (size > 0) {
+ if (comp->inbuf_used >= BUFSZ) {
+ if (comp->flush_inbuf(comp, false))
+ return -1;
+ }
+
+ diff = BUFSZ - comp->inbuf_used;
+
+ if (diff > size)
+ diff = size;
+
+ memcpy(comp->inbuf + comp->inbuf_used, data, diff);
+
+ comp->inbuf_used += diff;
+ data = (const char *)data + diff;
+ size -= diff;
+ }
+
+ return 0;
+}
+
+static int comp_flush(ostream_t *strm)
+{
+ ostream_comp_t *comp = (ostream_comp_t *)strm;
+
+ if (comp->inbuf_used > 0) {
+ if (comp->flush_inbuf(comp, true))
+ return -1;
+ }
+
+ return comp->wrapped->flush(comp->wrapped);
+}
+
+static const char *comp_get_filename(ostream_t *strm)
+{
+ ostream_comp_t *comp = (ostream_comp_t *)strm;
+
+ return comp->wrapped->get_filename(comp->wrapped);
+}
+
+static void comp_destroy(sqfs_object_t *obj)
+{
+ ostream_comp_t *comp = (ostream_comp_t *)obj;
+
+ comp->cleanup(comp);
+ sqfs_destroy(comp->wrapped);
+ free(comp);
+}
+
+ostream_t *ostream_compressor_create(ostream_t *strm, int comp_id)
+{
+ ostream_comp_t *comp = NULL;
+ sqfs_object_t *obj;
+ ostream_t *base;
+
+ switch (comp_id) {
+ case IO_COMPRESSOR_GZIP:
+#ifdef WITH_GZIP
+ comp = ostream_gzip_create(strm->get_filename(strm));
+#endif
+ break;
+ case IO_COMPRESSOR_XZ:
+#ifdef WITH_XZ
+ comp = ostream_xz_create(strm->get_filename(strm));
+#endif
+ break;
+ case IO_COMPRESSOR_ZSTD:
+#if defined(WITH_ZSTD) && defined(HAVE_ZSTD_STREAM)
+ comp = ostream_zstd_create(strm->get_filename(strm));
+#endif
+ break;
+ case IO_COMPRESSOR_BZIP2:
+#ifdef WITH_BZIP2
+ comp = ostream_bzip2_create(strm->get_filename(strm));
+#endif
+ break;
+ default:
+ break;
+ }
+
+ if (comp == NULL)
+ return NULL;
+
+ comp->wrapped = strm;
+ comp->inbuf_used = 0;
+
+ base = (ostream_t *)comp;
+ base->append = comp_append;
+ base->flush = comp_flush;
+ base->get_filename = comp_get_filename;
+
+ obj = (sqfs_object_t *)comp;
+ obj->destroy = comp_destroy;
+ return base;
+}
diff --git a/lib/io/compress/xz.c b/lib/io/compress/xz.c
new file mode 100644
index 0000000..65bda0b
--- /dev/null
+++ b/lib/io/compress/xz.c
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * xz.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#include <lzma.h>
+
+typedef struct {
+ ostream_comp_t base;
+
+ lzma_stream strm;
+} ostream_xz_t;
+
+static int flush_inbuf(ostream_comp_t *base, bool finish)
+{
+ ostream_xz_t *xz = (ostream_xz_t *)base;
+ lzma_ret ret_xz;
+ size_t have;
+
+ xz->strm.next_in = base->inbuf;
+ xz->strm.avail_in = base->inbuf_used;
+
+ do {
+ xz->strm.next_out = base->outbuf;
+ xz->strm.avail_out = BUFSZ;
+
+ ret_xz = lzma_code(&xz->strm, finish ? LZMA_FINISH : LZMA_RUN);
+
+ if ((ret_xz != LZMA_OK) && (ret_xz != LZMA_STREAM_END)) {
+ fprintf(stderr,
+ "%s: internal error in XZ compressor.\n",
+ base->wrapped->get_filename(base->wrapped));
+ return -1;
+ }
+
+ have = BUFSZ - xz->strm.avail_out;
+
+ if (base->wrapped->append(base->wrapped, base->outbuf, have))
+ return -1;
+ } while (xz->strm.avail_out == 0);
+
+ base->inbuf_used = 0;
+ return 0;
+}
+
+static void cleanup(ostream_comp_t *base)
+{
+ ostream_xz_t *xz = (ostream_xz_t *)base;
+
+ lzma_end(&xz->strm);
+}
+
+ostream_comp_t *ostream_xz_create(const char *filename)
+{
+ ostream_xz_t *xz = calloc(1, sizeof(*xz));
+ ostream_comp_t *base = (ostream_comp_t *)xz;
+ lzma_ret ret_xz;
+
+ if (xz == NULL) {
+ fprintf(stderr, "%s: creating xz wrapper: %s.\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+
+ ret_xz = lzma_easy_encoder(&xz->strm, LZMA_PRESET_DEFAULT,
+ LZMA_CHECK_CRC64);
+ if (ret_xz != LZMA_OK) {
+ fprintf(stderr, "%s: error initializing XZ compressor\n",
+ filename);
+ free(xz);
+ return NULL;
+ }
+
+ base->flush_inbuf = flush_inbuf;
+ base->cleanup = cleanup;
+ return base;
+}
diff --git a/lib/io/compress/zstd.c b/lib/io/compress/zstd.c
new file mode 100644
index 0000000..c0b002e
--- /dev/null
+++ b/lib/io/compress/zstd.c
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * zstd.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#include <zstd.h>
+
+#ifdef HAVE_ZSTD_STREAM
+typedef struct {
+ ostream_comp_t base;
+
+ ZSTD_CStream *strm;
+} ostream_zstd_t;
+
+static int flush_inbuf(ostream_comp_t *base, bool finish)
+{
+ ostream_zstd_t *zstd = (ostream_zstd_t *)base;
+ ZSTD_EndDirective op;
+ ZSTD_outBuffer out;
+ ZSTD_inBuffer in;
+ size_t ret;
+
+ op = finish ? ZSTD_e_end : ZSTD_e_continue;
+
+ do {
+ memset(&in, 0, sizeof(in));
+ memset(&out, 0, sizeof(out));
+
+ in.src = base->inbuf;
+ in.size = base->inbuf_used;
+
+ out.dst = base->outbuf;
+ out.size = BUFSZ;
+
+ ret = ZSTD_compressStream2(zstd->strm, &out, &in, op);
+
+ if (ZSTD_isError(ret)) {
+ fprintf(stderr, "%s: error in zstd compressor.\n",
+ base->wrapped->get_filename(base->wrapped));
+ return -1;
+ }
+
+ if (base->wrapped->append(base->wrapped, base->outbuf,
+ out.pos)) {
+ return -1;
+ }
+
+ if (in.pos < in.size) {
+ base->inbuf_used = in.size - in.pos;
+
+ memmove(base->inbuf, base->inbuf + in.pos,
+ base->inbuf_used);
+ } else {
+ base->inbuf_used = 0;
+ }
+ } while (finish && ret != 0);
+
+ return 0;
+}
+
+static void cleanup(ostream_comp_t *base)
+{
+ ostream_zstd_t *zstd = (ostream_zstd_t *)base;
+
+ ZSTD_freeCStream(zstd->strm);
+}
+
+ostream_comp_t *ostream_zstd_create(const char *filename)
+{
+ ostream_zstd_t *zstd = calloc(1, sizeof(*zstd));
+ ostream_comp_t *base = (ostream_comp_t *)zstd;
+
+ if (zstd == NULL) {
+ fprintf(stderr, "%s: creating zstd wrapper: %s.\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+
+ zstd->strm = ZSTD_createCStream();
+ if (zstd->strm == NULL) {
+ fprintf(stderr, "%s: error creating zstd decoder.\n",
+ filename);
+ free(zstd);
+ return NULL;
+ }
+
+ base->flush_inbuf = flush_inbuf;
+ base->cleanup = cleanup;
+ return base;
+}
+#endif /* HAVE_ZSTD_STREAM */
diff --git a/lib/io/get_line.c b/lib/io/get_line.c
new file mode 100644
index 0000000..f7e0b59
--- /dev/null
+++ b/lib/io/get_line.c
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * get_line.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "internal.h"
+
+static void ltrim(char *buffer)
+{
+ size_t i = 0;
+
+ while (isspace(buffer[i]))
+ ++i;
+
+ if (i > 0)
+ memmove(buffer, buffer + i, strlen(buffer + i) + 1);
+}
+
+static void rtrim(char *buffer)
+{
+ size_t i = strlen(buffer);
+
+ while (i > 0 && isspace(buffer[i - 1]))
+ --i;
+
+ buffer[i] = '\0';
+}
+
+static size_t trim(char *buffer, int flags)
+{
+ if (flags & ISTREAM_LINE_LTRIM)
+ ltrim(buffer);
+
+ if (flags & ISTREAM_LINE_RTRIM)
+ rtrim(buffer);
+
+ return strlen(buffer);
+}
+
+int istream_get_line(istream_t *strm, char **out,
+ size_t *line_num, int flags)
+{
+ char *line = NULL, *new;
+ size_t i, line_len = 0;
+ bool have_line = false;
+
+ *out = NULL;
+
+ for (;;) {
+ if (istream_precache(strm))
+ return -1;
+
+ if (strm->buffer_used == 0) {
+ if (line_len == 0)
+ goto out_eof;
+
+ line_len = trim(line, flags);
+
+ if (line_len == 0 &&
+ (flags & ISTREAM_LINE_SKIP_EMPTY)) {
+ goto out_eof;
+ }
+ break;
+ }
+
+ for (i = 0; i < strm->buffer_used; ++i) {
+ if (strm->buffer[i] == '\n')
+ break;
+ }
+
+ if (i < strm->buffer_used) {
+ have_line = true;
+ strm->buffer_offset = i + 1;
+
+ if (i > 0 && strm->buffer[i - 1] == '\r')
+ --i;
+ } else {
+ strm->buffer_offset = i;
+ }
+
+ new = realloc(line, line_len + i + 1);
+ if (new == NULL)
+ goto fail_errno;
+
+ line = new;
+ memcpy(line + line_len, strm->buffer, i);
+ line_len += i;
+ line[line_len] = '\0';
+
+ if (have_line) {
+ line_len = trim(line, flags);
+
+ if (line_len == 0 &&
+ (flags & ISTREAM_LINE_SKIP_EMPTY)) {
+ free(line);
+ line = NULL;
+ have_line = false;
+ *line_num += 1;
+ continue;
+ }
+ break;
+ }
+ }
+
+ *out = line;
+ return 0;
+fail_errno:
+ fprintf(stderr, "%s: " PRI_SZ ": %s.\n", strm->get_filename(strm),
+ *line_num, strerror(errno));
+ free(line);
+ *out = NULL;
+ return -1;
+out_eof:
+ free(line);
+ *out = NULL;
+ return 1;
+}
diff --git a/lib/io/internal.h b/lib/io/internal.h
new file mode 100644
index 0000000..4ac38f5
--- /dev/null
+++ b/lib/io/internal.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * internal.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef INTERNAL_H
+#define INTERNAL_H
+
+#include "config.h"
+#include "compat.h"
+#include "io/istream.h"
+#include "io/ostream.h"
+#include "io/file.h"
+#include "io/xfrm.h"
+#include "io/std.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+
+#define BUFSZ (262144)
+
+typedef struct ostream_comp_t {
+ ostream_t base;
+
+ ostream_t *wrapped;
+
+ size_t inbuf_used;
+
+ sqfs_u8 inbuf[BUFSZ];
+ sqfs_u8 outbuf[BUFSZ];
+
+ int (*flush_inbuf)(struct ostream_comp_t *ostrm, bool finish);
+
+ void (*cleanup)(struct ostream_comp_t *ostrm);
+} ostream_comp_t;
+
+typedef struct istream_comp_t {
+ istream_t base;
+
+ istream_t *wrapped;
+
+ sqfs_u8 uncompressed[BUFSZ];
+
+ bool eof;
+
+ void (*cleanup)(struct istream_comp_t *strm);
+} istream_comp_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SQFS_INTERNAL ostream_comp_t *ostream_gzip_create(const char *filename);
+
+SQFS_INTERNAL ostream_comp_t *ostream_xz_create(const char *filename);
+
+SQFS_INTERNAL ostream_comp_t *ostream_zstd_create(const char *filename);
+
+SQFS_INTERNAL ostream_comp_t *ostream_bzip2_create(const char *filename);
+
+SQFS_INTERNAL istream_comp_t *istream_gzip_create(const char *filename);
+
+SQFS_INTERNAL istream_comp_t *istream_xz_create(const char *filename);
+
+SQFS_INTERNAL istream_comp_t *istream_zstd_create(const char *filename);
+
+SQFS_INTERNAL istream_comp_t *istream_bzip2_create(const char *filename);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* INTERNAL_H */
diff --git a/lib/io/istream.c b/lib/io/istream.c
new file mode 100644
index 0000000..6318a23
--- /dev/null
+++ b/lib/io/istream.c
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * istream.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "internal.h"
+
+
+sqfs_s32 istream_read(istream_t *strm, void *data, size_t size)
+{
+ sqfs_s32 total = 0;
+ size_t diff;
+
+ if (size > 0x7FFFFFFF)
+ size = 0x7FFFFFFF;
+
+ while (size > 0) {
+ if (strm->buffer_offset >= strm->buffer_used) {
+ if (istream_precache(strm))
+ return -1;
+
+ if (strm->buffer_used == 0)
+ break;
+ }
+
+ diff = strm->buffer_used - strm->buffer_offset;
+ if (diff > size)
+ diff = size;
+
+ memcpy(data, strm->buffer + strm->buffer_offset, diff);
+ data = (char *)data + diff;
+ strm->buffer_offset += diff;
+ size -= diff;
+ total += diff;
+ }
+
+ return total;
+}
+
+int istream_precache(istream_t *strm)
+{
+ if (strm->buffer_offset >= strm->buffer_used) {
+ strm->buffer_offset = 0;
+ strm->buffer_used = 0;
+ } else if (strm->buffer_offset > 0) {
+ memmove(strm->buffer,
+ strm->buffer + strm->buffer_offset,
+ strm->buffer_used - strm->buffer_offset);
+
+ strm->buffer_used -= strm->buffer_offset;
+ strm->buffer_offset = 0;
+ }
+
+ if (strm->eof)
+ return 0;
+
+ return strm->precache(strm);
+}
+
+const char *istream_get_filename(istream_t *strm)
+{
+ return strm->get_filename(strm);
+}
+
+int istream_skip(istream_t *strm, sqfs_u64 size)
+{
+ size_t diff;
+
+ while (size > 0) {
+ if (strm->buffer_offset >= strm->buffer_used) {
+ if (istream_precache(strm))
+ return -1;
+
+ if (strm->buffer_used == 0) {
+ fprintf(stderr, "%s: unexpected end-of-file\n",
+ strm->get_filename(strm));
+ return -1;
+ }
+ }
+
+ diff = strm->buffer_used - strm->buffer_offset;
+ if ((sqfs_u64)diff > size)
+ diff = size;
+
+ strm->buffer_offset += diff;
+ size -= diff;
+ }
+
+ return 0;
+}
diff --git a/lib/io/ostream.c b/lib/io/ostream.c
new file mode 100644
index 0000000..afe76e8
--- /dev/null
+++ b/lib/io/ostream.c
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * ostream.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "internal.h"
+
+
+static int append_sparse_fallback(ostream_t *strm, size_t size)
+{
+ char buffer[512];
+ size_t diff;
+
+ memset(buffer, 0, sizeof(buffer));
+
+ while (size > 0) {
+ diff = size < sizeof(buffer) ? size : sizeof(buffer);
+
+ if (strm->append(strm, buffer, diff))
+ return -1;
+
+ size -= diff;
+ }
+
+ return 0;
+}
+
+
+int ostream_append(ostream_t *strm, const void *data, size_t size)
+{
+ return strm->append(strm, data, size);
+}
+
+int ostream_append_sparse(ostream_t *strm, size_t size)
+{
+ if (strm->append_sparse == NULL)
+ return append_sparse_fallback(strm, size);
+
+ return strm->append_sparse(strm, size);
+}
+
+int ostream_flush(ostream_t *strm)
+{
+ return strm->flush(strm);
+}
+
+const char *ostream_get_filename(ostream_t *strm)
+{
+ return strm->get_filename(strm);
+}
+
+sqfs_s32 ostream_append_from_istream(ostream_t *out, istream_t *in,
+ sqfs_u32 size)
+{
+ sqfs_s32 total = 0;
+ size_t diff;
+
+ if (size > 0x7FFFFFFF)
+ size = 0x7FFFFFFF;
+
+ while (size > 0) {
+ if (in->buffer_offset >= in->buffer_used) {
+ if (istream_precache(in))
+ return -1;
+
+ if (in->buffer_used == 0)
+ break;
+ }
+
+ diff = in->buffer_used - in->buffer_offset;
+ if (diff > size)
+ diff = size;
+
+ if (out->append(out, in->buffer + in->buffer_offset, diff))
+ return -1;
+
+ in->buffer_offset += diff;
+ size -= diff;
+ total += diff;
+ }
+
+ return total;
+}
diff --git a/lib/io/printf.c b/lib/io/printf.c
new file mode 100644
index 0000000..3850487
--- /dev/null
+++ b/lib/io/printf.c
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * printf.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "internal.h"
+
+int ostream_printf(ostream_t *strm, const char *fmt, ...)
+{
+ char *temp = NULL;
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+
+ ret = vasprintf(&temp, fmt, ap);
+ if (ret < 0)
+ perror(strm->get_filename(strm));
+ va_end(ap);
+
+ if (ret < 0)
+ return -1;
+
+ if (strm->append(strm, temp, ret))
+ ret = -1;
+
+ free(temp);
+ return ret;
+}
diff --git a/lib/io/uncompress/autodetect.c b/lib/io/uncompress/autodetect.c
new file mode 100644
index 0000000..dde33c8
--- /dev/null
+++ b/lib/io/uncompress/autodetect.c
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * autodetect.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+static const struct {
+ int id;
+ const sqfs_u8 *value;
+ size_t len;
+} magic[] = {
+ { IO_COMPRESSOR_GZIP, (const sqfs_u8 *)"\x1F\x8B\x08", 3 },
+ { IO_COMPRESSOR_XZ, (const sqfs_u8 *)("\xFD" "7zXZ"), 6 },
+ { IO_COMPRESSOR_ZSTD, (const sqfs_u8 *)"\x28\xB5\x2F\xFD", 4 },
+ { IO_COMPRESSOR_BZIP2, (const sqfs_u8 *)"BZh", 3 },
+};
+
+int istream_detect_compressor(istream_t *strm,
+ int (*probe)(const sqfs_u8 *data, size_t size))
+{
+ size_t i;
+ int ret;
+
+ ret = istream_precache(strm);
+ if (ret != 0)
+ return ret;
+
+ if (probe != NULL) {
+ ret = probe(strm->buffer + strm->buffer_offset,
+ strm->buffer_used - strm->buffer_offset);
+ if (ret < 0)
+ return ret;
+
+ /* XXX: this means the data is uncompressed. We do this check
+ first since it might be perfectly OK for the uncompressed
+ data to contain a magic number from the table. */
+ if (ret > 0)
+ return 0;
+ }
+
+ for (i = 0; i < sizeof(magic) / sizeof(magic[0]); ++i) {
+ if ((strm->buffer_used - strm->buffer_offset) < magic[i].len)
+ continue;
+
+ ret = memcmp(strm->buffer + strm->buffer_offset,
+ magic[i].value, magic[i].len);
+
+ if (ret == 0)
+ return magic[i].id;
+ }
+
+ return 0;
+}
diff --git a/lib/io/uncompress/bzip2.c b/lib/io/uncompress/bzip2.c
new file mode 100644
index 0000000..3b44383
--- /dev/null
+++ b/lib/io/uncompress/bzip2.c
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * bzip2.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#include <bzlib.h>
+
+typedef struct {
+ istream_comp_t base;
+
+ bool initialized;
+ bz_stream strm;
+} istream_bzip2_t;
+
+static int precache(istream_t *base)
+{
+ istream_bzip2_t *bzip2 = (istream_bzip2_t *)base;
+ istream_t *wrapped = ((istream_comp_t *)base)->wrapped;
+ size_t avail;
+ int ret;
+
+ for (;;) {
+ if (!bzip2->initialized) {
+ if (BZ2_bzDecompressInit(&bzip2->strm, 0, 0) != BZ_OK) {
+ fprintf(stderr, "%s: error initializing "
+ "bzip2 decompressor.\n",
+ wrapped->get_filename(wrapped));
+ return -1;
+ }
+
+ bzip2->initialized = true;
+ }
+
+ ret = istream_precache(wrapped);
+ if (ret != 0)
+ return ret;
+
+ avail = wrapped->buffer_used;
+ if ((sizeof(size_t) > sizeof(unsigned int)) &&
+ (avail > (size_t)UINT_MAX)) {
+ avail = UINT_MAX;
+ }
+
+ bzip2->strm.next_in = (char *)wrapped->buffer;
+ bzip2->strm.avail_in = (unsigned int)avail;
+
+ if (base->buffer_used > BUFSZ)
+ base->buffer_used = BUFSZ;
+
+ avail = BUFSZ - base->buffer_used;
+
+ if ((sizeof(size_t) > sizeof(unsigned int)) &&
+ (avail > (size_t)UINT_MAX)) {
+ avail = UINT_MAX;
+ }
+
+ bzip2->strm.next_out = (char *)base->buffer + base->buffer_used;
+ bzip2->strm.avail_out = (unsigned int)avail;
+
+ if (bzip2->strm.avail_out < 1)
+ break;
+
+ ret = BZ2_bzDecompress(&bzip2->strm);
+
+ if (ret < 0) {
+ fprintf(stderr, "%s: internal error in bzip2 "
+ "decompressor.\n",
+ wrapped->get_filename(wrapped));
+ return -1;
+ }
+
+ base->buffer_used = BUFSZ - bzip2->strm.avail_out;
+ wrapped->buffer_offset = wrapped->buffer_used -
+ bzip2->strm.avail_in;
+
+ if (ret == BZ_STREAM_END) {
+ if (istream_precache(wrapped))
+ return -1;
+
+ BZ2_bzDecompressEnd(&bzip2->strm);
+ bzip2->initialized = false;
+
+ if (wrapped->buffer_used == 0) {
+ base->eof = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void cleanup(istream_comp_t *base)
+{
+ istream_bzip2_t *bzip2 = (istream_bzip2_t *)base;
+
+ if (bzip2->initialized)
+ BZ2_bzDecompressEnd(&bzip2->strm);
+}
+
+istream_comp_t *istream_bzip2_create(const char *filename)
+{
+ istream_bzip2_t *bzip2 = calloc(1, sizeof(*bzip2));
+ istream_comp_t *base = (istream_comp_t *)bzip2;
+
+ if (bzip2 == NULL) {
+ fprintf(stderr, "%s: creating bzip2 compressor: %s.\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+
+ ((istream_t *)base)->precache = precache;
+ base->cleanup = cleanup;
+ return base;
+}
diff --git a/lib/io/uncompress/gzip.c b/lib/io/uncompress/gzip.c
new file mode 100644
index 0000000..1d6274c
--- /dev/null
+++ b/lib/io/uncompress/gzip.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * gzip.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#include <zlib.h>
+
+typedef struct {
+ istream_comp_t base;
+
+ z_stream strm;
+} istream_gzip_t;
+
+static int precache(istream_t *base)
+{
+ istream_t *wrapped = ((istream_comp_t *)base)->wrapped;
+ istream_gzip_t *gzip = (istream_gzip_t *)base;
+ size_t avail_in, avail_out;
+ int ret;
+
+ for (;;) {
+ ret = istream_precache(wrapped);
+ if (ret != 0)
+ return ret;
+
+ avail_in = wrapped->buffer_used;
+ avail_out = BUFSZ - base->buffer_used;
+
+ if (sizeof(size_t) > sizeof(uInt)) {
+ gzip->strm.avail_in = ~((uInt)0U);
+ gzip->strm.avail_out = ~((uInt)0U);
+
+ if ((size_t)gzip->strm.avail_in > avail_in)
+ gzip->strm.avail_in = (uInt)avail_in;
+
+ if ((size_t)gzip->strm.avail_out > avail_out)
+ gzip->strm.avail_out = (uInt)avail_out;
+ } else {
+ gzip->strm.avail_in = (uInt)avail_in;
+ gzip->strm.avail_out = (uInt)avail_out;
+ }
+
+ gzip->strm.next_in = wrapped->buffer;
+ gzip->strm.next_out = base->buffer + base->buffer_used;
+
+ ret = inflate(&gzip->strm, Z_NO_FLUSH);
+
+ wrapped->buffer_offset = wrapped->buffer_used -
+ gzip->strm.avail_in;
+
+ base->buffer_used = BUFSZ - gzip->strm.avail_out;
+
+ if (ret == Z_BUF_ERROR)
+ break;
+
+ if (ret == Z_STREAM_END) {
+ base->eof = true;
+ break;
+ }
+
+ if (ret != Z_OK) {
+ fprintf(stderr,
+ "%s: internal error in gzip decoder.\n",
+ wrapped->get_filename(wrapped));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void cleanup(istream_comp_t *base)
+{
+ istream_gzip_t *gzip = (istream_gzip_t *)base;
+
+ inflateEnd(&gzip->strm);
+}
+
+istream_comp_t *istream_gzip_create(const char *filename)
+{
+ istream_gzip_t *gzip = calloc(1, sizeof(*gzip));
+ istream_comp_t *base = (istream_comp_t *)gzip;
+ int ret;
+
+ if (gzip == NULL) {
+ fprintf(stderr, "%s: creating gzip decoder: %s.\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+
+ ret = inflateInit2(&gzip->strm, 16 + 15);
+ if (ret != Z_OK) {
+ fprintf(stderr,
+ "%s: internal error creating gzip reader.\n",
+ filename);
+ free(gzip);
+ return NULL;
+ }
+
+ ((istream_t *)base)->precache = precache;
+ base->cleanup = cleanup;
+ return base;
+}
diff --git a/lib/io/uncompress/istream_compressor.c b/lib/io/uncompress/istream_compressor.c
new file mode 100644
index 0000000..ab9ad8b
--- /dev/null
+++ b/lib/io/uncompress/istream_compressor.c
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * istream_compressor.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+static const char *comp_get_filename(istream_t *strm)
+{
+ istream_comp_t *comp = (istream_comp_t *)strm;
+
+ return comp->wrapped->get_filename(comp->wrapped);
+}
+
+static void comp_destroy(sqfs_object_t *obj)
+{
+ istream_comp_t *comp = (istream_comp_t *)obj;
+
+ comp->cleanup(comp);
+ sqfs_destroy(comp->wrapped);
+ free(comp);
+}
+
+istream_t *istream_compressor_create(istream_t *strm, int comp_id)
+{
+ istream_comp_t *comp = NULL;
+ sqfs_object_t *obj;
+ istream_t *base;
+
+ switch (comp_id) {
+ case IO_COMPRESSOR_GZIP:
+#ifdef WITH_GZIP
+ comp = istream_gzip_create(strm->get_filename(strm));
+#endif
+ break;
+ case IO_COMPRESSOR_XZ:
+#ifdef WITH_XZ
+ comp = istream_xz_create(strm->get_filename(strm));
+#endif
+ break;
+ case IO_COMPRESSOR_ZSTD:
+#if defined(WITH_ZSTD) && defined(HAVE_ZSTD_STREAM)
+ comp = istream_zstd_create(strm->get_filename(strm));
+#endif
+ break;
+ case IO_COMPRESSOR_BZIP2:
+#ifdef WITH_BZIP2
+ comp = istream_bzip2_create(strm->get_filename(strm));
+#endif
+ break;
+ default:
+ break;
+ }
+
+ if (comp == NULL)
+ return NULL;
+
+ comp->wrapped = strm;
+
+ base = (istream_t *)comp;
+ base->get_filename = comp_get_filename;
+ base->buffer = comp->uncompressed;
+ base->eof = false;
+
+ obj = (sqfs_object_t *)comp;
+ obj->destroy = comp_destroy;
+ return base;
+}
diff --git a/lib/io/uncompress/xz.c b/lib/io/uncompress/xz.c
new file mode 100644
index 0000000..0fd9ce6
--- /dev/null
+++ b/lib/io/uncompress/xz.c
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * xz.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#include <lzma.h>
+
+typedef struct {
+ istream_comp_t base;
+
+ lzma_stream strm;
+} istream_xz_t;
+
+static int precache(istream_t *base)
+{
+ istream_xz_t *xz = (istream_xz_t *)base;
+ istream_t *wrapped = ((istream_comp_t *)base)->wrapped;
+ lzma_action action;
+ lzma_ret ret_xz;
+ int ret;
+
+ for (;;) {
+ ret = istream_precache(wrapped);
+ if (ret != 0)
+ return ret;
+
+ action = wrapped->eof ? LZMA_FINISH : LZMA_RUN;
+
+ xz->strm.avail_in = wrapped->buffer_used;
+ xz->strm.next_in = wrapped->buffer;
+
+ xz->strm.avail_out = BUFSZ - base->buffer_used;
+ xz->strm.next_out = base->buffer + base->buffer_used;
+
+ ret_xz = lzma_code(&xz->strm, action);
+
+ base->buffer_used = BUFSZ - xz->strm.avail_out;
+ wrapped->buffer_offset = wrapped->buffer_used -
+ xz->strm.avail_in;
+
+ if (ret_xz == LZMA_BUF_ERROR)
+ break;
+
+ if (ret_xz == LZMA_STREAM_END) {
+ base->eof = true;
+ break;
+ }
+
+ if (ret_xz != LZMA_OK) {
+ fprintf(stderr,
+ "%s: internal error in xz decoder.\n",
+ wrapped->get_filename(wrapped));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void cleanup(istream_comp_t *base)
+{
+ istream_xz_t *xz = (istream_xz_t *)base;
+
+ lzma_end(&xz->strm);
+}
+
+istream_comp_t *istream_xz_create(const char *filename)
+{
+ istream_xz_t *xz = calloc(1, sizeof(*xz));
+ istream_comp_t *base = (istream_comp_t *)xz;
+ sqfs_u64 memlimit = 65 * 1024 * 1024;
+ lzma_ret ret_xz;
+
+ if (xz == NULL) {
+ fprintf(stderr, "%s: creating xz decoder: %s.\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+
+ ret_xz = lzma_stream_decoder(&xz->strm, memlimit, LZMA_CONCATENATED);
+
+ if (ret_xz != LZMA_OK) {
+ fprintf(stderr,
+ "%s: error initializing xz decoder.\n",
+ filename);
+ free(xz);
+ return NULL;
+ }
+
+ ((istream_t *)base)->precache = precache;
+ base->cleanup = cleanup;
+ return base;
+}
diff --git a/lib/io/uncompress/zstd.c b/lib/io/uncompress/zstd.c
new file mode 100644
index 0000000..fd22cbf
--- /dev/null
+++ b/lib/io/uncompress/zstd.c
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * zstd.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#include <zstd.h>
+
+#ifdef HAVE_ZSTD_STREAM
+typedef struct {
+ istream_comp_t base;
+
+ ZSTD_DStream* strm;
+} istream_zstd_t;
+
+static int precache(istream_t *base)
+{
+ istream_zstd_t *zstd = (istream_zstd_t *)base;
+ istream_t *wrapped = ((istream_comp_t *)base)->wrapped;
+ ZSTD_outBuffer out;
+ ZSTD_inBuffer in;
+ size_t ret;
+
+ if (istream_precache(wrapped))
+ return -1;
+
+ memset(&in, 0, sizeof(in));
+ memset(&out, 0, sizeof(out));
+
+ in.src = wrapped->buffer;
+ in.size = wrapped->buffer_used;
+
+ out.dst = ((istream_comp_t *)base)->uncompressed + base->buffer_used;
+ out.size = BUFSZ - base->buffer_used;
+
+ ret = ZSTD_decompressStream(zstd->strm, &out, &in);
+
+ if (ZSTD_isError(ret)) {
+ fprintf(stderr, "%s: error in zstd decoder.\n",
+ wrapped->get_filename(wrapped));
+ return -1;
+ }
+
+ wrapped->buffer_offset = in.pos;
+ base->buffer_used += out.pos;
+ return 0;
+}
+
+static void cleanup(istream_comp_t *base)
+{
+ istream_zstd_t *zstd = (istream_zstd_t *)base;
+
+ ZSTD_freeDStream(zstd->strm);
+}
+
+istream_comp_t *istream_zstd_create(const char *filename)
+{
+ istream_zstd_t *zstd = calloc(1, sizeof(*zstd));
+ istream_comp_t *base = (istream_comp_t *)zstd;
+
+ if (zstd == NULL) {
+ fprintf(stderr, "%s: creating zstd decoder: %s.\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+
+ zstd->strm = ZSTD_createDStream();
+ if (zstd->strm == NULL) {
+ fprintf(stderr, "%s: error creating zstd decoder.\n",
+ filename);
+ free(zstd);
+ return NULL;
+ }
+
+ ((istream_t *)base)->precache = precache;
+ base->cleanup = cleanup;
+ return base;
+}
+#endif /* HAVE_ZSTD_STREAM */
diff --git a/lib/io/unix/istream.c b/lib/io/unix/istream.c
new file mode 100644
index 0000000..5898141
--- /dev/null
+++ b/lib/io/unix/istream.c
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * istream.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+typedef struct {
+ istream_t base;
+ char *path;
+ int fd;
+ bool eof;
+
+ sqfs_u8 buffer[BUFSZ];
+} file_istream_t;
+
+static int file_precache(istream_t *strm)
+{
+ file_istream_t *file = (file_istream_t *)strm;
+ ssize_t ret;
+ size_t diff;
+
+ while (strm->buffer_used < sizeof(file->buffer)) {
+ diff = sizeof(file->buffer) - strm->buffer_used;
+
+ ret = read(file->fd, strm->buffer + strm->buffer_used, diff);
+
+ if (ret == 0) {
+ file->eof = true;
+ break;
+ }
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+
+ perror(file->path);
+ return -1;
+ }
+
+ strm->buffer_used += ret;
+ }
+
+ return 0;
+}
+
+static const char *file_get_filename(istream_t *strm)
+{
+ file_istream_t *file = (file_istream_t *)strm;
+
+ return file->path;
+}
+
+static void file_destroy(sqfs_object_t *obj)
+{
+ file_istream_t *file = (file_istream_t *)obj;
+
+ if (file->fd != STDIN_FILENO)
+ close(file->fd);
+
+ free(file->path);
+ free(file);
+}
+
+istream_t *istream_open_file(const char *path)
+{
+ file_istream_t *file = calloc(1, sizeof(*file));
+ sqfs_object_t *obj = (sqfs_object_t *)file;
+ istream_t *strm = (istream_t *)file;
+
+ if (file == NULL) {
+ perror(path);
+ return NULL;
+ }
+
+ file->path = strdup(path);
+ if (file->path == NULL) {
+ perror(path);
+ goto fail_free;
+ }
+
+ file->fd = open(path, O_RDONLY);
+ if (file->fd < 0) {
+ perror(path);
+ goto fail_path;
+ }
+
+ strm->buffer = file->buffer;
+ strm->precache = file_precache;
+ strm->get_filename = file_get_filename;
+ obj->destroy = file_destroy;
+ return strm;
+fail_path:
+ free(file->path);
+fail_free:
+ free(file);
+ return NULL;
+}
+
+istream_t *istream_open_stdin(void)
+{
+ file_istream_t *file = calloc(1, sizeof(*file));
+ sqfs_object_t *obj = (sqfs_object_t *)file;
+ istream_t *strm = (istream_t *)file;
+
+ if (file == NULL)
+ goto fail;
+
+ file->path = strdup("stdin");
+ if (file->path == NULL)
+ goto fail;
+
+ file->fd = STDIN_FILENO;
+ strm->buffer = file->buffer;
+ strm->precache = file_precache;
+ strm->get_filename = file_get_filename;
+ obj->destroy = file_destroy;
+ return strm;
+fail:
+ perror("creating file wrapper for stdin");
+ free(file);
+ return NULL;
+}
diff --git a/lib/io/unix/ostream.c b/lib/io/unix/ostream.c
new file mode 100644
index 0000000..17f1998
--- /dev/null
+++ b/lib/io/unix/ostream.c
@@ -0,0 +1,173 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * ostream.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+typedef struct {
+ ostream_t base;
+ char *path;
+ int fd;
+
+ off_t sparse_count;
+ off_t size;
+} file_ostream_t;
+
+static int file_append(ostream_t *strm, const void *data, size_t size)
+{
+ file_ostream_t *file = (file_ostream_t *)strm;
+ ssize_t ret;
+
+ if (size == 0)
+ return 0;
+
+ if (file->sparse_count > 0) {
+ if (lseek(file->fd, file->sparse_count, SEEK_CUR) == (off_t)-1)
+ goto fail_errno;
+
+ file->sparse_count = 0;
+ }
+
+ while (size > 0) {
+ ret = write(file->fd, data, size);
+
+ if (ret == 0) {
+ fprintf(stderr, "%s: truncated data write.\n",
+ file->path);
+ return -1;
+ }
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ goto fail_errno;
+ }
+
+ file->size += ret;
+ size -= ret;
+ data = (const char *)data + ret;
+ }
+
+ return 0;
+fail_errno:
+ perror(file->path);
+ return -1;
+}
+
+static int file_append_sparse(ostream_t *strm, size_t size)
+{
+ file_ostream_t *file = (file_ostream_t *)strm;
+
+ file->sparse_count += size;
+ file->size += size;
+ return 0;
+}
+
+static int file_flush(ostream_t *strm)
+{
+ file_ostream_t *file = (file_ostream_t *)strm;
+
+ if (file->sparse_count > 0) {
+ if (ftruncate(file->fd, file->size) != 0)
+ goto fail;
+ }
+
+ if (fsync(file->fd) != 0) {
+ if (errno == EINVAL)
+ return 0;
+ goto fail;
+ }
+
+ return 0;
+fail:
+ perror(file->path);
+ return -1;
+}
+
+static void file_destroy(sqfs_object_t *obj)
+{
+ file_ostream_t *file = (file_ostream_t *)obj;
+
+ if (file->fd != STDOUT_FILENO)
+ close(file->fd);
+
+ free(file->path);
+ free(file);
+}
+
+static const char *file_get_filename(ostream_t *strm)
+{
+ file_ostream_t *file = (file_ostream_t *)strm;
+
+ return file->path;
+}
+
+ostream_t *ostream_open_file(const char *path, int flags)
+{
+ file_ostream_t *file = calloc(1, sizeof(*file));
+ sqfs_object_t *obj = (sqfs_object_t *)file;
+ ostream_t *strm = (ostream_t *)file;
+
+ if (file == NULL) {
+ perror(path);
+ return NULL;
+ }
+
+ file->path = strdup(path);
+ if (file->path == NULL) {
+ perror(path);
+ goto fail_free;
+ }
+
+ if (flags & OSTREAM_OPEN_OVERWRITE) {
+ file->fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ } else {
+ file->fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
+ }
+
+ if (file->fd < 0) {
+ perror(path);
+ goto fail_path;
+ }
+
+ if (flags & OSTREAM_OPEN_SPARSE)
+ strm->append_sparse = file_append_sparse;
+
+ strm->append = file_append;
+ strm->flush = file_flush;
+ strm->get_filename = file_get_filename;
+ obj->destroy = file_destroy;
+ return strm;
+fail_path:
+ free(file->path);
+fail_free:
+ free(file);
+ return NULL;
+}
+
+ostream_t *ostream_open_stdout(void)
+{
+ file_ostream_t *file = calloc(1, sizeof(*file));
+ sqfs_object_t *obj = (sqfs_object_t *)file;
+ ostream_t *strm = (ostream_t *)file;
+
+ if (file == NULL)
+ goto fail;
+
+ file->path = strdup("stdout");
+ if (file->path == NULL)
+ goto fail;
+
+ file->fd = STDOUT_FILENO;
+ strm->append = file_append;
+ strm->flush = file_flush;
+ strm->get_filename = file_get_filename;
+ obj->destroy = file_destroy;
+ return strm;
+fail:
+ perror("creating file wrapper for stdout");
+ free(file);
+ return NULL;
+}
diff --git a/lib/io/win32/istream.c b/lib/io/win32/istream.c
new file mode 100644
index 0000000..b591584
--- /dev/null
+++ b/lib/io/win32/istream.c
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * istream.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+typedef struct {
+ istream_t base;
+ char *path;
+ HANDLE hnd;
+
+ sqfs_u8 buffer[BUFSZ];
+} file_istream_t;
+
+static int file_precache(istream_t *strm)
+{
+ file_istream_t *file = (file_istream_t *)strm;
+ DWORD diff, actual;
+ HANDLE hnd;
+
+ hnd = file->path == NULL ? GetStdHandle(STD_INPUT_HANDLE) : file->hnd;
+
+ while (strm->buffer_used < sizeof(file->buffer)) {
+ diff = sizeof(file->buffer) - strm->buffer_used;
+
+ if (!ReadFile(hnd, strm->buffer + strm->buffer_used,
+ diff, &actual, NULL)) {
+ DWORD error = GetLastError();
+
+ if (error == ERROR_HANDLE_EOF ||
+ error == ERROR_BROKEN_PIPE) {
+ strm->eof = true;
+ break;
+ }
+
+ SetLastError(error);
+
+ w32_perror(file->path == NULL ? "stdin" : file->path);
+ return -1;
+ }
+
+ if (actual == 0) {
+ strm->eof = true;
+ break;
+ }
+
+ strm->buffer_used += actual;
+ }
+
+ return 0;
+}
+
+static const char *file_get_filename(istream_t *strm)
+{
+ file_istream_t *file = (file_istream_t *)strm;
+
+ return file->path == NULL ? "stdin" : file->path;
+}
+
+static void file_destroy(sqfs_object_t *obj)
+{
+ file_istream_t *file = (file_istream_t *)obj;
+
+ if (file->path != NULL) {
+ CloseHandle(file->hnd);
+ free(file->path);
+ }
+
+ free(file);
+}
+
+istream_t *istream_open_file(const char *path)
+{
+ file_istream_t *file = calloc(1, sizeof(*file));
+ sqfs_object_t *obj = (sqfs_object_t *)file;
+ istream_t *strm = (istream_t *)file;
+ WCHAR *wpath = NULL;
+
+ if (file == NULL) {
+ perror(path);
+ return NULL;
+ }
+
+ wpath = path_to_windows(path);
+ if (wpath == NULL)
+ goto fail_free;
+
+ file->path = strdup(path);
+ if (file->path == NULL) {
+ perror(path);
+ goto fail_free;
+ }
+
+ file->hnd = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (file->hnd == INVALID_HANDLE_VALUE) {
+ perror(path);
+ goto fail_path;
+ }
+
+ free(wpath);
+
+ strm->buffer = file->buffer;
+ strm->precache = file_precache;
+ strm->get_filename = file_get_filename;
+ obj->destroy = file_destroy;
+ return strm;
+fail_path:
+ free(file->path);
+fail_free:
+ free(wpath);
+ free(file);
+ return NULL;
+}
+
+istream_t *istream_open_stdin(void)
+{
+ file_istream_t *file = calloc(1, sizeof(*file));
+ sqfs_object_t *obj = (sqfs_object_t *)file;
+ istream_t *strm = (istream_t *)file;
+
+ if (file == NULL) {
+ perror("stdin");
+ return NULL;
+ }
+
+ strm->buffer = file->buffer;
+ strm->precache = file_precache;
+ strm->get_filename = file_get_filename;
+ obj->destroy = file_destroy;
+ return strm;
+}
diff --git a/lib/io/win32/ostream.c b/lib/io/win32/ostream.c
new file mode 100644
index 0000000..2bd78c8
--- /dev/null
+++ b/lib/io/win32/ostream.c
@@ -0,0 +1,197 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * ostream.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+typedef struct {
+ ostream_t base;
+ char *path;
+ HANDLE hnd;
+} file_ostream_t;
+
+static int w32_append(HANDLE hnd, const char *filename,
+ const void *data, size_t size)
+{
+ DWORD diff;
+
+ while (size > 0) {
+ if (!WriteFile(hnd, data, size, &diff, NULL)) {
+ w32_perror(filename);
+ return -1;
+ }
+
+ size -= diff;
+ data = (const char *)data + diff;
+ }
+
+ return 0;
+}
+
+static int w32_flush(HANDLE hnd, const char *filename)
+{
+ if (!FlushFileBuffers(hnd)) {
+ w32_perror(filename);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+static int file_append(ostream_t *strm, const void *data, size_t size)
+{
+ file_ostream_t *file = (file_ostream_t *)strm;
+
+ return w32_append(file->hnd, file->path, data, size);
+}
+
+static int file_append_sparse(ostream_t *strm, size_t size)
+{
+ file_ostream_t *file = (file_ostream_t *)strm;
+ LARGE_INTEGER pos;
+
+ pos.QuadPart = size;
+
+ if (!SetFilePointerEx(file->hnd, pos, NULL, FILE_CURRENT))
+ goto fail;
+
+ if (!SetEndOfFile(file->hnd))
+ goto fail;
+
+ return 0;
+fail:
+ w32_perror(file->path);
+ return -1;
+}
+
+static int file_flush(ostream_t *strm)
+{
+ file_ostream_t *file = (file_ostream_t *)strm;
+
+ return w32_flush(file->hnd, file->path);
+}
+
+static void file_destroy(sqfs_object_t *obj)
+{
+ file_ostream_t *file = (file_ostream_t *)obj;
+
+ CloseHandle(file->hnd);
+ free(file->path);
+ free(file);
+}
+
+static const char *file_get_filename(ostream_t *strm)
+{
+ file_ostream_t *file = (file_ostream_t *)strm;
+
+ return file->path;
+}
+
+/*****************************************************************************/
+
+static int stdout_append(ostream_t *strm, const void *data, size_t size)
+{
+ (void)strm;
+ return w32_append(GetStdHandle(STD_OUTPUT_HANDLE), "stdout",
+ data, size);
+}
+
+static int stdout_flush(ostream_t *strm)
+{
+ (void)strm;
+ return w32_flush(GetStdHandle(STD_OUTPUT_HANDLE), "stdout");
+}
+
+static void stdout_destroy(sqfs_object_t *obj)
+{
+ free(obj);
+}
+
+static const char *stdout_get_filename(ostream_t *strm)
+{
+ (void)strm;
+ return "stdout";
+}
+
+/*****************************************************************************/
+
+ostream_t *ostream_open_file(const char *path, int flags)
+{
+ file_ostream_t *file = calloc(1, sizeof(*file));
+ sqfs_object_t *obj = (sqfs_object_t *)file;
+ ostream_t *strm = (ostream_t *)file;
+ int access_flags, creation_mode;
+ WCHAR *wpath = NULL;
+
+ if (file == NULL) {
+ perror(path);
+ return NULL;
+ }
+
+ wpath = path_to_windows(path);
+ if (wpath == NULL)
+ goto fail_free;
+
+ file->path = strdup(path);
+ if (file->path == NULL) {
+ perror(path);
+ goto fail_free;
+ }
+
+ access_flags = GENERIC_WRITE;
+
+ if (flags & OSTREAM_OPEN_OVERWRITE) {
+ creation_mode = CREATE_ALWAYS;
+ } else {
+ creation_mode = CREATE_NEW;
+ }
+
+ file->hnd = CreateFileW(wpath, access_flags, 0, NULL, creation_mode,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (file->hnd == INVALID_HANDLE_VALUE) {
+ w32_perror(path);
+ goto fail_path;
+ }
+
+ free(wpath);
+
+ if (flags & OSTREAM_OPEN_SPARSE)
+ strm->append_sparse = file_append_sparse;
+
+ strm->append = file_append;
+ strm->flush = file_flush;
+ strm->get_filename = file_get_filename;
+ obj->destroy = file_destroy;
+ return strm;
+fail_path:
+ free(file->path);
+fail_free:
+ free(file);
+ free(wpath);
+ return NULL;
+}
+
+ostream_t *ostream_open_stdout(void)
+{
+ ostream_t *strm = calloc(1, sizeof(*strm));
+ sqfs_object_t *obj = (sqfs_object_t *)strm;
+
+ if (strm == NULL) {
+ perror("creating stdout file wrapper");
+ return NULL;
+ }
+
+ strm->append = stdout_append;
+ strm->flush = stdout_flush;
+ strm->get_filename = stdout_get_filename;
+ obj->destroy = stdout_destroy;
+ return strm;
+}
diff --git a/lib/io/xfrm.c b/lib/io/xfrm.c
new file mode 100644
index 0000000..22fd953
--- /dev/null
+++ b/lib/io/xfrm.c
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * compressor.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "internal.h"
+
+int io_compressor_id_from_name(const char *name)
+{
+ if (strcmp(name, "gzip") == 0)
+ return IO_COMPRESSOR_GZIP;
+
+ if (strcmp(name, "xz") == 0)
+ return IO_COMPRESSOR_XZ;
+
+ if (strcmp(name, "zstd") == 0)
+ return IO_COMPRESSOR_ZSTD;
+
+ if (strcmp(name, "bzip2") == 0)
+ return IO_COMPRESSOR_BZIP2;
+
+ return -1;
+}
+
+const char *io_compressor_name_from_id(int id)
+{
+ if (id == IO_COMPRESSOR_GZIP)
+ return "gzip";
+
+ if (id == IO_COMPRESSOR_XZ)
+ return "xz";
+
+ if (id == IO_COMPRESSOR_ZSTD)
+ return "zstd";
+
+ if (id == IO_COMPRESSOR_BZIP2)
+ return "bzip2";
+
+ return NULL;
+}
+
+bool io_compressor_exists(int id)
+{
+ switch (id) {
+#ifdef WITH_GZIP
+ case IO_COMPRESSOR_GZIP:
+ return true;
+#endif
+#ifdef WITH_XZ
+ case IO_COMPRESSOR_XZ:
+ return true;
+#endif
+#if defined(WITH_ZSTD) && defined(HAVE_ZSTD_STREAM)
+ case IO_COMPRESSOR_ZSTD:
+ return true;
+#endif
+#ifdef WITH_BZIP2
+ case IO_COMPRESSOR_BZIP2:
+ return true;
+#endif
+ default:
+ break;
+ }
+
+ return false;
+}