From 359d71e90050a8b83f7bc7d2ecd4ff29c477cc22 Mon Sep 17 00:00:00 2001
From: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Date: Sun, 26 Jun 2022 22:51:27 +0200
Subject: Cleanup: rename libfstream to libio, split headers

Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
---
 Makefile.am                                 |   2 +-
 bin/gensquashfs/Makemodule.am               |   2 +-
 bin/rdsquashfs/Makemodule.am                |   2 +-
 bin/sqfs2tar/Makemodule.am                  |   2 +-
 bin/sqfs2tar/options.c                      |  12 +-
 bin/sqfs2tar/sqfs2tar.h                     |   2 +
 bin/sqfsdiff/Makemodule.am                  |   2 +-
 bin/tar2sqfs/Makemodule.am                  |   2 +-
 bin/tar2sqfs/options.c                      |   8 +-
 bin/tar2sqfs/tar2sqfs.c                     |   4 +-
 bin/tar2sqfs/tar2sqfs.h                     |   2 +
 include/common.h                            |   4 +-
 include/compat.h                            |   2 +-
 include/fstream.h                           | 402 --------------------------
 include/fstree.h                            |   2 +-
 include/io/file.h                           |  52 ++++
 include/io/istream.h                        | 128 +++++++++
 include/io/ostream.h                        | 138 +++++++++
 include/io/std.h                            |  39 +++
 include/io/xfrm.h                           | 128 +++++++++
 include/tar.h                               |   3 +-
 lib/fstream/Makemodule.am                   |  45 ---
 lib/fstream/compress/bzip2.c                |  96 -------
 lib/fstream/compress/gzip.c                 |  92 ------
 lib/fstream/compress/ostream_compressor.c   | 108 -------
 lib/fstream/compress/xz.c                   |  80 ------
 lib/fstream/compress/zstd.c                 |  94 ------
 lib/fstream/compressor.c                    |  67 -----
 lib/fstream/get_line.c                      | 118 --------
 lib/fstream/internal.h                      |  77 -----
 lib/fstream/istream.c                       |  91 ------
 lib/fstream/ostream.c                       |  84 ------
 lib/fstream/printf.c                        |  30 --
 lib/fstream/uncompress/autodetect.c         |  55 ----
 lib/fstream/uncompress/bzip2.c              | 118 --------
 lib/fstream/uncompress/gzip.c               | 106 -------
 lib/fstream/uncompress/istream_compressor.c |  69 -----
 lib/fstream/uncompress/xz.c                 |  96 -------
 lib/fstream/uncompress/zstd.c               |  81 ------
 lib/fstream/unix/istream.c                  | 124 --------
 lib/fstream/unix/ostream.c                  | 173 -----------
 lib/fstream/win32/istream.c                 | 138 ---------
 lib/fstream/win32/ostream.c                 | 197 -------------
 lib/fstree/fstree_from_file.c               |   2 +-
 lib/io/Makemodule.am                        |  45 +++
 lib/io/compress/bzip2.c                     |  96 +++++++
 lib/io/compress/gzip.c                      |  92 ++++++
 lib/io/compress/ostream_compressor.c        | 108 +++++++
 lib/io/compress/xz.c                        |  80 ++++++
 lib/io/compress/zstd.c                      |  94 ++++++
 lib/io/get_line.c                           | 118 ++++++++
 lib/io/internal.h                           |  81 ++++++
 lib/io/istream.c                            |  91 ++++++
 lib/io/ostream.c                            |  84 ++++++
 lib/io/printf.c                             |  30 ++
 lib/io/uncompress/autodetect.c              |  55 ++++
 lib/io/uncompress/bzip2.c                   | 118 ++++++++
 lib/io/uncompress/gzip.c                    | 106 +++++++
 lib/io/uncompress/istream_compressor.c      |  69 +++++
 lib/io/uncompress/xz.c                      |  96 +++++++
 lib/io/uncompress/zstd.c                    |  81 ++++++
 lib/io/unix/istream.c                       | 124 ++++++++
 lib/io/unix/ostream.c                       | 173 +++++++++++
 lib/io/win32/istream.c                      | 138 +++++++++
 lib/io/win32/ostream.c                      | 197 +++++++++++++
 lib/io/xfrm.c                               |  67 +++++
 tests/Makemodule.am                         |   2 +-
 tests/libfstream/Makemodule.am              |  68 -----
 tests/libfstream/get_line.c                 | 166 -----------
 tests/libfstream/get_line.txt               |  11 -
 tests/libfstream/uncompress.c               | 431 ---------------------------
 tests/libfstree/Makemodule.am               |  12 +-
 tests/libio/Makemodule.am                   |  68 +++++
 tests/libio/get_line.c                      | 166 +++++++++++
 tests/libio/get_line.txt                    |  11 +
 tests/libio/uncompress.c                    | 432 ++++++++++++++++++++++++++++
 tests/libtar/Makemodule.am                  |  60 ++--
 tests/libtar/tar_big_file.c                 |   1 +
 tests/libtar/tar_fuzz.c                     |   1 +
 tests/libtar/tar_simple.c                   |   1 +
 tests/libtar/tar_sparse.c                   |   1 +
 tests/libtar/tar_sparse_gnu.c               |   1 +
 tests/libtar/tar_target_filled.c            |   1 +
 tests/libtar/tar_xattr.c                    |   1 +
 tests/libtar/tar_xattr_bin.c                |   1 +
 tests/libutil/Makemodule.am                 |   2 +-
 tests/libutil/str_table.c                   |   2 +-
 87 files changed, 3382 insertions(+), 3279 deletions(-)
 delete mode 100644 include/fstream.h
 create mode 100644 include/io/file.h
 create mode 100644 include/io/istream.h
 create mode 100644 include/io/ostream.h
 create mode 100644 include/io/std.h
 create mode 100644 include/io/xfrm.h
 delete mode 100644 lib/fstream/Makemodule.am
 delete mode 100644 lib/fstream/compress/bzip2.c
 delete mode 100644 lib/fstream/compress/gzip.c
 delete mode 100644 lib/fstream/compress/ostream_compressor.c
 delete mode 100644 lib/fstream/compress/xz.c
 delete mode 100644 lib/fstream/compress/zstd.c
 delete mode 100644 lib/fstream/compressor.c
 delete mode 100644 lib/fstream/get_line.c
 delete mode 100644 lib/fstream/internal.h
 delete mode 100644 lib/fstream/istream.c
 delete mode 100644 lib/fstream/ostream.c
 delete mode 100644 lib/fstream/printf.c
 delete mode 100644 lib/fstream/uncompress/autodetect.c
 delete mode 100644 lib/fstream/uncompress/bzip2.c
 delete mode 100644 lib/fstream/uncompress/gzip.c
 delete mode 100644 lib/fstream/uncompress/istream_compressor.c
 delete mode 100644 lib/fstream/uncompress/xz.c
 delete mode 100644 lib/fstream/uncompress/zstd.c
 delete mode 100644 lib/fstream/unix/istream.c
 delete mode 100644 lib/fstream/unix/ostream.c
 delete mode 100644 lib/fstream/win32/istream.c
 delete mode 100644 lib/fstream/win32/ostream.c
 create mode 100644 lib/io/Makemodule.am
 create mode 100644 lib/io/compress/bzip2.c
 create mode 100644 lib/io/compress/gzip.c
 create mode 100644 lib/io/compress/ostream_compressor.c
 create mode 100644 lib/io/compress/xz.c
 create mode 100644 lib/io/compress/zstd.c
 create mode 100644 lib/io/get_line.c
 create mode 100644 lib/io/internal.h
 create mode 100644 lib/io/istream.c
 create mode 100644 lib/io/ostream.c
 create mode 100644 lib/io/printf.c
 create mode 100644 lib/io/uncompress/autodetect.c
 create mode 100644 lib/io/uncompress/bzip2.c
 create mode 100644 lib/io/uncompress/gzip.c
 create mode 100644 lib/io/uncompress/istream_compressor.c
 create mode 100644 lib/io/uncompress/xz.c
 create mode 100644 lib/io/uncompress/zstd.c
 create mode 100644 lib/io/unix/istream.c
 create mode 100644 lib/io/unix/ostream.c
 create mode 100644 lib/io/win32/istream.c
 create mode 100644 lib/io/win32/ostream.c
 create mode 100644 lib/io/xfrm.c
 delete mode 100644 tests/libfstream/Makemodule.am
 delete mode 100644 tests/libfstream/get_line.c
 delete mode 100644 tests/libfstream/get_line.txt
 delete mode 100644 tests/libfstream/uncompress.c
 create mode 100644 tests/libio/Makemodule.am
 create mode 100644 tests/libio/get_line.c
 create mode 100644 tests/libio/get_line.txt
 create mode 100644 tests/libio/uncompress.c

diff --git a/Makefile.am b/Makefile.am
index 33efeb7..bbb48f0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -29,7 +29,7 @@ TESTS =
 include lib/lz4/Makemodule.am
 include lib/sqfs/Makemodule.am
 include lib/util/Makemodule.am
-include lib/fstream/Makemodule.am
+include lib/io/Makemodule.am
 
 if BUILD_TOOLS
 include lib/fstree/Makemodule.am
diff --git a/bin/gensquashfs/Makemodule.am b/bin/gensquashfs/Makemodule.am
index 11a3535..03f4f4c 100644
--- a/bin/gensquashfs/Makemodule.am
+++ b/bin/gensquashfs/Makemodule.am
@@ -1,7 +1,7 @@
 gensquashfs_SOURCES = bin/gensquashfs/mkfs.c bin/gensquashfs/mkfs.h
 gensquashfs_SOURCES += bin/gensquashfs/options.c bin/gensquashfs/selinux.c
 gensquashfs_SOURCES += bin/gensquashfs/dirscan_xattr.c
-gensquashfs_LDADD = libcommon.a libsquashfs.la libfstree.a libfstream.a
+gensquashfs_LDADD = libcommon.a libsquashfs.la libfstree.a libio.a
 gensquashfs_LDADD += libcompat.a $(LZO_LIBS) $(PTHREAD_LIBS)
 gensquashfs_CPPFLAGS = $(AM_CPPFLAGS)
 gensquashfs_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS)
diff --git a/bin/rdsquashfs/Makemodule.am b/bin/rdsquashfs/Makemodule.am
index 094771c..974e7bc 100644
--- a/bin/rdsquashfs/Makemodule.am
+++ b/bin/rdsquashfs/Makemodule.am
@@ -4,7 +4,7 @@ rdsquashfs_SOURCES += bin/rdsquashfs/restore_fstree.c bin/rdsquashfs/describe.c
 rdsquashfs_SOURCES += bin/rdsquashfs/fill_files.c bin/rdsquashfs/dump_xattrs.c
 rdsquashfs_SOURCES += bin/rdsquashfs/stat.c
 rdsquashfs_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS)
-rdsquashfs_LDADD = libcommon.a libfstream.a libcompat.a libsquashfs.la
+rdsquashfs_LDADD = libcommon.a libio.a libcompat.a libsquashfs.la
 rdsquashfs_LDADD += libfstree.a $(LZO_LIBS) $(PTHREAD_LIBS)
 
 dist_man1_MANS += bin/rdsquashfs/rdsquashfs.1
diff --git a/bin/sqfs2tar/Makemodule.am b/bin/sqfs2tar/Makemodule.am
index 5355157..22d523e 100644
--- a/bin/sqfs2tar/Makemodule.am
+++ b/bin/sqfs2tar/Makemodule.am
@@ -3,7 +3,7 @@ sqfs2tar_SOURCES += bin/sqfs2tar/options.c bin/sqfs2tar/write_tree.c
 sqfs2tar_SOURCES += bin/sqfs2tar/xattr.c
 sqfs2tar_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS)
 sqfs2tar_LDADD = libcommon.a libutil.a libsquashfs.la libtar.a
-sqfs2tar_LDADD += libfstream.a libcompat.a libfstree.a
+sqfs2tar_LDADD += libio.a libcompat.a libfstree.a
 sqfs2tar_LDADD += $(ZLIB_LIBS) $(XZ_LIBS) $(LZO_LIBS) $(ZSTD_LIBS) $(BZIP2_LIBS)
 sqfs2tar_LDADD += $(PTHREAD_LIBS)
 
diff --git a/bin/sqfs2tar/options.c b/bin/sqfs2tar/options.c
index 1b652b5..4f783e0 100644
--- a/bin/sqfs2tar/options.c
+++ b/bin/sqfs2tar/options.c
@@ -91,14 +91,14 @@ void process_args(int argc, char **argv)
 
 		switch (i) {
 		case 'c':
-			compressor = fstream_compressor_id_from_name(optarg);
+			compressor = io_compressor_id_from_name(optarg);
 			if (compressor <= 0) {
 				fprintf(stderr, "unknown compressor '%s'.\n",
 					optarg);
 				goto fail;
 			}
 
-			if (!fstream_compressor_exists(compressor)) {
+			if (!io_compressor_exists(compressor)) {
 				fprintf(stderr,
 					"%s compressor is not supported.\n",
 					optarg);
@@ -163,11 +163,11 @@ void process_args(int argc, char **argv)
 		case 'h':
 			fputs(usagestr, stdout);
 
-			i = FSTREAM_COMPRESSOR_MIN;
+			i = IO_COMPRESSOR_MIN;
 
-			while (i <= FSTREAM_COMPRESSOR_MAX) {
-				name = fstream_compressor_name_from_id(i);
-				if (fstream_compressor_exists(i))
+			while (i <= IO_COMPRESSOR_MAX) {
+				name = io_compressor_name_from_id(i);
+				if (io_compressor_exists(i))
 					printf("\t%s\n", name);
 				++i;
 			}
diff --git a/bin/sqfs2tar/sqfs2tar.h b/bin/sqfs2tar/sqfs2tar.h
index 1986c56..587e8ad 100644
--- a/bin/sqfs2tar/sqfs2tar.h
+++ b/bin/sqfs2tar/sqfs2tar.h
@@ -11,6 +11,8 @@
 #include "common.h"
 #include "tar.h"
 
+#include "io/xfrm.h"
+
 #include <getopt.h>
 #include <string.h>
 #include <stdlib.h>
diff --git a/bin/sqfsdiff/Makemodule.am b/bin/sqfsdiff/Makemodule.am
index 8331f25..bd93a74 100644
--- a/bin/sqfsdiff/Makemodule.am
+++ b/bin/sqfsdiff/Makemodule.am
@@ -4,7 +4,7 @@ sqfsdiff_SOURCES += bin/sqfsdiff/compare_dir.c bin/sqfsdiff/node_compare.c
 sqfsdiff_SOURCES += bin/sqfsdiff/compare_files.c bin/sqfsdiff/super.c
 sqfsdiff_SOURCES += bin/sqfsdiff/extract.c
 sqfsdiff_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS)
-sqfsdiff_LDADD = libcommon.a libsquashfs.la libfstream.a libcompat.a
+sqfsdiff_LDADD = libcommon.a libsquashfs.la libio.a libcompat.a
 sqfsdiff_LDADD += $(LZO_LIBS) libfstree.a $(PTHREAD_LIBS)
 
 dist_man1_MANS += bin/sqfsdiff/sqfsdiff.1
diff --git a/bin/tar2sqfs/Makemodule.am b/bin/tar2sqfs/Makemodule.am
index 6db3628..818f6e2 100644
--- a/bin/tar2sqfs/Makemodule.am
+++ b/bin/tar2sqfs/Makemodule.am
@@ -1,7 +1,7 @@
 tar2sqfs_SOURCES = bin/tar2sqfs/tar2sqfs.c bin/tar2sqfs/tar2sqfs.h
 tar2sqfs_SOURCES += bin/tar2sqfs/options.c bin/tar2sqfs/process_tarball.c
 tar2sqfs_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS)
-tar2sqfs_LDADD = libcommon.a libsquashfs.la libtar.a libfstream.a
+tar2sqfs_LDADD = libcommon.a libsquashfs.la libtar.a libio.a
 tar2sqfs_LDADD += libfstree.a libcompat.a libfstree.a $(LZO_LIBS)
 tar2sqfs_LDADD += $(ZLIB_LIBS) $(XZ_LIBS) $(ZSTD_LIBS) $(BZIP2_LIBS)
 tar2sqfs_LDADD += $(PTHREAD_LIBS)
diff --git a/bin/tar2sqfs/options.c b/bin/tar2sqfs/options.c
index 7727a47..94e7036 100644
--- a/bin/tar2sqfs/options.c
+++ b/bin/tar2sqfs/options.c
@@ -94,15 +94,15 @@ char *root_becomes = NULL;
 
 static void input_compressor_print_available(void)
 {
-	int i = FSTREAM_COMPRESSOR_MIN;
+	int i = IO_COMPRESSOR_MIN;
 	const char *name;
 
 	fputs("\nSupported tar compression formats:\n", stdout);
 
-	while (i <= FSTREAM_COMPRESSOR_MAX) {
-		name = fstream_compressor_name_from_id(i);
+	while (i <= IO_COMPRESSOR_MAX) {
+		name = io_compressor_name_from_id(i);
 
-		if (fstream_compressor_exists(i))
+		if (io_compressor_exists(i))
 			printf("\t%s\n", name);
 
 		++i;
diff --git a/bin/tar2sqfs/tar2sqfs.c b/bin/tar2sqfs/tar2sqfs.c
index 603ee25..4e9ade9 100644
--- a/bin/tar2sqfs/tar2sqfs.c
+++ b/bin/tar2sqfs/tar2sqfs.c
@@ -50,11 +50,11 @@ int main(int argc, char **argv)
 		goto out_if;
 
 	if (ret > 0) {
-		if (!fstream_compressor_exists(ret)) {
+		if (!io_compressor_exists(ret)) {
 			fprintf(stderr,
 				"%s: %s compression is not supported.\n",
 				istream_get_filename(input_file),
-				fstream_compressor_name_from_id(ret));
+				io_compressor_name_from_id(ret));
 			goto out_if;
 		}
 
diff --git a/bin/tar2sqfs/tar2sqfs.h b/bin/tar2sqfs/tar2sqfs.h
index 915d89c..d5a4ada 100644
--- a/bin/tar2sqfs/tar2sqfs.h
+++ b/bin/tar2sqfs/tar2sqfs.h
@@ -12,6 +12,8 @@
 #include "compat.h"
 #include "tar.h"
 
+#include "io/xfrm.h"
+
 #include <stdlib.h>
 #include <getopt.h>
 #include <string.h>
diff --git a/include/common.h b/include/common.h
index df3017a..ab07461 100644
--- a/include/common.h
+++ b/include/common.h
@@ -20,7 +20,9 @@
 
 #include "simple_writer.h"
 #include "compress_cli.h"
-#include "fstream.h"
+#include "io/ostream.h"
+#include "io/file.h"
+#include "io/std.h"
 #include "compat.h"
 #include "fstree.h"
 #include "tar.h"
diff --git a/include/compat.h b/include/compat.h
index 6b4d83d..8969d6c 100644
--- a/include/compat.h
+++ b/include/compat.h
@@ -8,7 +8,7 @@
 #define COMPAT_H
 
 #include "sqfs/predef.h"
-#include "fstream.h"
+#include "io/ostream.h"
 #include "config.h"
 
 #include <limits.h>
diff --git a/include/fstream.h b/include/fstream.h
deleted file mode 100644
index 0e8905e..0000000
--- a/include/fstream.h
+++ /dev/null
@@ -1,402 +0,0 @@
-/* SPDX-License-Identifier: GPL-3.0-or-later */
-/*
- * fstream.h
- *
- * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
- */
-#ifndef FSTREAM_H
-#define FSTREAM_H
-
-#include "sqfs/predef.h"
-
-#if defined(__GNUC__) || defined(__clang__)
-#	define PRINTF_ATTRIB(fmt, elipsis)			\
-		__attribute__ ((format (printf, fmt, elipsis)))
-#else
-#	define PRINTF_ATTRIB(fmt, elipsis)
-#endif
-
-/**
- * @struct ostream_t
- *
- * @extends sqfs_object_t
- *
- * @brief An append-only data stream.
- */
-typedef struct ostream_t {
-	sqfs_object_t base;
-
-	int (*append)(struct ostream_t *strm, const void *data, size_t size);
-
-	int (*append_sparse)(struct ostream_t *strm, size_t size);
-
-	int (*flush)(struct ostream_t *strm);
-
-	const char *(*get_filename)(struct ostream_t *strm);
-} ostream_t;
-
-/**
- * @struct istream_t
- *
- * @extends sqfs_object_t
- *
- * @brief A sequential, read-only data stream.
- */
-typedef struct istream_t {
-	sqfs_object_t base;
-
-	size_t buffer_used;
-	size_t buffer_offset;
-	bool eof;
-
-	sqfs_u8 *buffer;
-
-	int (*precache)(struct istream_t *strm);
-
-	const char *(*get_filename)(struct istream_t *strm);
-} istream_t;
-
-
-enum {
-	OSTREAM_OPEN_OVERWRITE = 0x01,
-	OSTREAM_OPEN_SPARSE = 0x02,
-};
-
-enum {
-	ISTREAM_LINE_LTRIM = 0x01,
-	ISTREAM_LINE_RTRIM = 0x02,
-	ISTREAM_LINE_SKIP_EMPTY = 0x04,
-};
-
-enum {
-	/**
-	 * @brief Deflate compressor with gzip headers.
-	 *
-	 * This actually creates a gzip compatible file, including a
-	 * gzip header and trailer.
-	 */
-	FSTREAM_COMPRESSOR_GZIP = 1,
-
-	FSTREAM_COMPRESSOR_XZ = 2,
-
-	FSTREAM_COMPRESSOR_ZSTD = 3,
-
-	FSTREAM_COMPRESSOR_BZIP2 = 4,
-
-	FSTREAM_COMPRESSOR_MIN = 1,
-	FSTREAM_COMPRESSOR_MAX = 4,
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * @brief Create an output stream that writes to a file.
- *
- * @memberof ostream_t
- *
- * If the file does not yet exist, it is created. If it does exist this
- * function fails, unless the flag OSTREAM_OPEN_OVERWRITE is set, in which
- * case the file is opened and its contents are discarded.
- *
- * If the flag OSTREAM_OPEN_SPARSE is set, the underlying implementation tries
- * to support sparse output files. If the flag is not set, holes will always
- * be filled with zero bytes.
- *
- * @param path A path to the file to open or create.
- * @param flags A combination of flags controling how to open/create the file.
- *
- * @return A pointer to an output stream on success, NULL on failure.
- */
-SQFS_INTERNAL ostream_t *ostream_open_file(const char *path, int flags);
-
-/**
- * @brief Create an output stream that writes to standard output.
- *
- * @memberof ostream_t
- *
- * @return A pointer to an output stream on success, NULL on failure.
- */
-SQFS_INTERNAL ostream_t *ostream_open_stdout(void);
-
-/**
- * @brief Create an input stream that reads from a file.
- *
- * @memberof istream_t
- *
- * @param path A path to the file to open or create.
- *
- * @return A pointer to an output stream on success, NULL on failure.
- */
-SQFS_INTERNAL istream_t *istream_open_file(const char *path);
-
-/**
- * @brief Create an input stream that reads from standard input.
- *
- * @memberof istream_t
- *
- * @return A pointer to an input stream on success, NULL on failure.
- */
-SQFS_INTERNAL istream_t *istream_open_stdin(void);
-
-/**
- * @brief Create an output stream that transparently compresses data.
- *
- * @memberof ostream_t
- *
- * This function creates an output stream that transparently compresses all
- * data appended to it and writes the compressed data to an underlying, wrapped
- * output stream.
- *
- * The new stream takes ownership of the wrapped stream and destroys it when
- * the compressor stream is destroyed. If this function fails, the wrapped
- * stream is also destroyed.
- *
- * @param strm A pointer to another stream that should be wrapped.
- * @param comp_id An identifier describing the compressor to use.
- *
- * @return A pointer to an output stream on success, NULL on failure.
- */
-SQFS_INTERNAL ostream_t *ostream_compressor_create(ostream_t *strm,
-						   int comp_id);
-
-/**
- * @brief Create an input stream that transparently uncompresses data.
- *
- * @memberof istream_t
- *
- * This function creates an input stream that wraps an underlying input stream
- * that is compressed and transparently uncompresses the data when reading
- * from it.
- *
- * The new stream takes ownership of the wrapped stream and destroys it when
- * the compressor stream is destroyed. If this function fails, the wrapped
- * stream is also destroyed.
- *
- * @param strm A pointer to another stream that should be wrapped.
- * @param comp_id An identifier describing the compressor to use.
- *
- * @return A pointer to an input stream on success, NULL on failure.
- */
-SQFS_INTERNAL istream_t *istream_compressor_create(istream_t *strm,
-						   int comp_id);
-
-/**
- * @brief Probe the buffered data in an istream to check if it is compressed.
- *
- * @memberof istream_t
- *
- * This function peeks into the internal buffer of an input stream to check
- * for magic signatures of various compressors.
- *
- * @param strm A pointer to an input stream to check
- * @param probe A callback used to check if raw/decoded data matches an
- *              expected format. Returns 0 if not, -1 on failure and +1
- *              on success.
- *
- * @return A compressor ID on success, 0 if no match was found, -1 on failure.
- */
-SQFS_INTERNAL int istream_detect_compressor(istream_t *strm,
-					    int (*probe)(const sqfs_u8 *data,
-							 size_t size));
-
-/**
- * @brief Append a block of data to an output stream.
- *
- * @memberof ostream_t
- *
- * @param strm A pointer to an output stream.
- * @param data A pointer to the data block to append.
- * @param size The number of bytes to append.
- *
- * @return Zero on success, -1 on failure.
- */
-SQFS_INTERNAL int ostream_append(ostream_t *strm, const void *data,
-				 size_t size);
-
-/**
- * @brief Append a number of zero bytes to an output stream.
- *
- * @memberof ostream_t
- *
- * If the unerlying implementation supports sparse files, this function can be
- * used to create a "hole". If the implementation does not support it, a
- * fallback is used that just appends a block of zeros manualy.
- *
- * @param strm A pointer to an output stream.
- * @param size The number of zero bytes to append.
- *
- * @return Zero on success, -1 on failure.
- */
-SQFS_INTERNAL int ostream_append_sparse(ostream_t *strm, size_t size);
-
-/**
- * @brief Process all pending, buffered data and flush it to disk.
- *
- * @memberof ostream_t
- *
- * If the stream performs some kind of transformation (e.g. transparent data
- * compression), flushing caues the wrapped format to insert a termination
- * token. Only call this function when you are absolutely DONE appending data,
- * shortly before destroying the stream.
- *
- * @param strm A pointer to an output stream.
- *
- * @return Zero on success, -1 on failure.
- */
-SQFS_INTERNAL int ostream_flush(ostream_t *strm);
-
-/**
- * @brief Get the underlying filename of a output stream.
- *
- * @memberof ostream_t
- *
- * @param strm The output stream to get the filename from.
- *
- * @return A string holding the underlying filename.
- */
-SQFS_INTERNAL const char *ostream_get_filename(ostream_t *strm);
-
-/**
- * @brief Printf like function that appends to an output stream
- *
- * @memberof ostream_t
- *
- * @param strm The output stream to append to.
- * @param fmt A printf style format string.
- *
- * @return The number of characters written on success, -1 on failure.
- */
-SQFS_INTERNAL int ostream_printf(ostream_t *strm, const char *fmt, ...)
-	PRINTF_ATTRIB(2, 3);
-
-/**
- * @brief Read a line of text from an input stream
- *
- * @memberof istream_t
- *
- * The line returned is allocated using malloc and must subsequently be
- * freed when it is no longer needed. The line itself is always null-terminated
- * and never includes the line break characters (LF or CR-LF).
- *
- * If the flag @ref ISTREAM_LINE_LTRIM is set, leading white space characters
- * are removed. If the flag @ref ISTREAM_LINE_RTRIM is set, trailing white space
- * characters are remvoed.
- *
- * If the flag @ref ISTREAM_LINE_SKIP_EMPTY is set and a line is discovered to
- * be empty (after the optional trimming), the function discards the empty line
- * and retries. The given line_num pointer is used to increment the line
- * number.
- *
- * @param strm A pointer to an input stream.
- * @param out Returns a pointer to a line on success.
- * @param line_num This is incremented if lines are skipped.
- * @param flags A combination of flags controling the functions behaviour.
- *
- * @return Zero on success, a negative value on error, a positive value if
- *         end-of-file was reached without reading any data.
- */
-SQFS_INTERNAL int istream_get_line(istream_t *strm, char **out,
-				   size_t *line_num, int flags);
-
-/**
- * @brief Read data from an input stream
- *
- * @memberof istream_t
- *
- * @param strm A pointer to an input stream.
- * @param data A buffer to read into.
- * @param size The number of bytes to read into the buffer.
- *
- * @return The number of bytes actually read on success, -1 on failure,
- *         0 on end-of-file.
- */
-SQFS_INTERNAL sqfs_s32 istream_read(istream_t *strm, void *data, size_t size);
-
-/**
- * @brief Adjust and refill the internal buffer of an input stream
- *
- * @memberof istream_t
- *
- * This function resets the buffer offset of an input stream (moving any unread
- * data up front if it has to) and calls an internal callback of the input
- * stream to fill the rest of the buffer to the extent possible.
- *
- * @param strm A pointer to an input stream.
- *
- * @return 0 on success, -1 on failure.
- */
-SQFS_INTERNAL int istream_precache(istream_t *strm);
-
-/**
- * @brief Get the underlying filename of an input stream.
- *
- * @memberof istream_t
- *
- * @param strm The input stream to get the filename from.
- *
- * @return A string holding the underlying filename.
- */
-SQFS_INTERNAL const char *istream_get_filename(istream_t *strm);
-
-/**
- * @brief Skip over a number of bytes in an input stream.
- *
- * @memberof istream_t
- *
- * @param strm A pointer to an input stream.
- * @param size The number of bytes to seek forward.
- *
- * @return Zero on success, -1 on failure.
- */
-SQFS_INTERNAL int istream_skip(istream_t *strm, sqfs_u64 size);
-
-/**
- * @brief Read data from an input stream and append it to an output stream
- *
- * @memberof ostream_t
- *
- * @param out A pointer to an output stream to append to.
- * @param in A pointer to an input stream to read from.
- * @param size The number of bytes to copy over.
- *
- * @return The number of bytes copied on success, -1 on failure,
- *         0 on end-of-file.
- */
-SQFS_INTERNAL sqfs_s32 ostream_append_from_istream(ostream_t *out,
-						   istream_t *in,
-						   sqfs_u32 size);
-
-/**
- * @brief Resolve a compressor name to an ID.
- *
- * @param name A compressor name.
- *
- * @return A compressor ID on success, -1 on failure.
- */
-SQFS_INTERNAL int fstream_compressor_id_from_name(const char *name);
-
-/**
- * @brief Resolve a id to a  compressor name.
- *
- * @param id A compressor ID.
- *
- * @return A compressor name on success, NULL on failure.
- */
-SQFS_INTERNAL const char *fstream_compressor_name_from_id(int id);
-
-/**
- * @brief Check if support for a given compressor has been built in.
- *
- * @param id A compressor ID.
- *
- * @return True if the compressor is supported, false if not.
- */
-SQFS_INTERNAL bool fstream_compressor_exists(int id);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* FSTREAM_H */
diff --git a/include/fstree.h b/include/fstree.h
index 58936d7..3fb4f47 100644
--- a/include/fstree.h
+++ b/include/fstree.h
@@ -15,7 +15,7 @@
 #include <stdio.h>
 
 #include "sqfs/predef.h"
-#include "fstream.h"
+#include "io/istream.h"
 #include "compat.h"
 
 enum {
diff --git a/include/io/file.h b/include/io/file.h
new file mode 100644
index 0000000..8c6e851
--- /dev/null
+++ b/include/io/file.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * file.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef IO_FILE_H
+#define IO_FILE_H
+
+#include "io/istream.h"
+#include "io/ostream.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Create an input stream that reads from a file.
+ *
+ * @memberof istream_t
+ *
+ * @param path A path to the file to open or create.
+ *
+ * @return A pointer to an output stream on success, NULL on failure.
+ */
+SQFS_INTERNAL istream_t *istream_open_file(const char *path);
+
+/**
+ * @brief Create an output stream that writes to a file.
+ *
+ * @memberof ostream_t
+ *
+ * If the file does not yet exist, it is created. If it does exist this
+ * function fails, unless the flag OSTREAM_OPEN_OVERWRITE is set, in which
+ * case the file is opened and its contents are discarded.
+ *
+ * If the flag OSTREAM_OPEN_SPARSE is set, the underlying implementation tries
+ * to support sparse output files. If the flag is not set, holes will always
+ * be filled with zero bytes.
+ *
+ * @param path A path to the file to open or create.
+ * @param flags A combination of flags controling how to open/create the file.
+ *
+ * @return A pointer to an output stream on success, NULL on failure.
+ */
+SQFS_INTERNAL ostream_t *ostream_open_file(const char *path, int flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IO_FILE_H */
diff --git a/include/io/istream.h b/include/io/istream.h
new file mode 100644
index 0000000..567d7e3
--- /dev/null
+++ b/include/io/istream.h
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * istream.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef IO_ISTREAM_H
+#define IO_ISTREAM_H
+
+#include "sqfs/predef.h"
+
+/**
+ * @struct istream_t
+ *
+ * @extends sqfs_object_t
+ *
+ * @brief A sequential, read-only data stream.
+ */
+typedef struct istream_t {
+	sqfs_object_t base;
+
+	size_t buffer_used;
+	size_t buffer_offset;
+	bool eof;
+
+	sqfs_u8 *buffer;
+
+	int (*precache)(struct istream_t *strm);
+
+	const char *(*get_filename)(struct istream_t *strm);
+} istream_t;
+
+enum {
+	ISTREAM_LINE_LTRIM = 0x01,
+	ISTREAM_LINE_RTRIM = 0x02,
+	ISTREAM_LINE_SKIP_EMPTY = 0x04,
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Read a line of text from an input stream
+ *
+ * @memberof istream_t
+ *
+ * The line returned is allocated using malloc and must subsequently be
+ * freed when it is no longer needed. The line itself is always null-terminated
+ * and never includes the line break characters (LF or CR-LF).
+ *
+ * If the flag @ref ISTREAM_LINE_LTRIM is set, leading white space characters
+ * are removed. If the flag @ref ISTREAM_LINE_RTRIM is set, trailing white space
+ * characters are remvoed.
+ *
+ * If the flag @ref ISTREAM_LINE_SKIP_EMPTY is set and a line is discovered to
+ * be empty (after the optional trimming), the function discards the empty line
+ * and retries. The given line_num pointer is used to increment the line
+ * number.
+ *
+ * @param strm A pointer to an input stream.
+ * @param out Returns a pointer to a line on success.
+ * @param line_num This is incremented if lines are skipped.
+ * @param flags A combination of flags controling the functions behaviour.
+ *
+ * @return Zero on success, a negative value on error, a positive value if
+ *         end-of-file was reached without reading any data.
+ */
+SQFS_INTERNAL int istream_get_line(istream_t *strm, char **out,
+				   size_t *line_num, int flags);
+
+/**
+ * @brief Read data from an input stream
+ *
+ * @memberof istream_t
+ *
+ * @param strm A pointer to an input stream.
+ * @param data A buffer to read into.
+ * @param size The number of bytes to read into the buffer.
+ *
+ * @return The number of bytes actually read on success, -1 on failure,
+ *         0 on end-of-file.
+ */
+SQFS_INTERNAL sqfs_s32 istream_read(istream_t *strm, void *data, size_t size);
+
+/**
+ * @brief Adjust and refill the internal buffer of an input stream
+ *
+ * @memberof istream_t
+ *
+ * This function resets the buffer offset of an input stream (moving any unread
+ * data up front if it has to) and calls an internal callback of the input
+ * stream to fill the rest of the buffer to the extent possible.
+ *
+ * @param strm A pointer to an input stream.
+ *
+ * @return 0 on success, -1 on failure.
+ */
+SQFS_INTERNAL int istream_precache(istream_t *strm);
+
+/**
+ * @brief Get the underlying filename of an input stream.
+ *
+ * @memberof istream_t
+ *
+ * @param strm The input stream to get the filename from.
+ *
+ * @return A string holding the underlying filename.
+ */
+SQFS_INTERNAL const char *istream_get_filename(istream_t *strm);
+
+/**
+ * @brief Skip over a number of bytes in an input stream.
+ *
+ * @memberof istream_t
+ *
+ * @param strm A pointer to an input stream.
+ * @param size The number of bytes to seek forward.
+ *
+ * @return Zero on success, -1 on failure.
+ */
+SQFS_INTERNAL int istream_skip(istream_t *strm, sqfs_u64 size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IO_ISTREAM_H */
diff --git a/include/io/ostream.h b/include/io/ostream.h
new file mode 100644
index 0000000..15585f9
--- /dev/null
+++ b/include/io/ostream.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * ostream.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef IO_OSTREAM_H
+#define IO_OSTREAM_H
+
+#include "sqfs/predef.h"
+#include "io/istream.h"
+
+#if defined(__GNUC__) || defined(__clang__)
+#	define PRINTF_ATTRIB(fmt, elipsis)			\
+		__attribute__ ((format (printf, fmt, elipsis)))
+#else
+#	define PRINTF_ATTRIB(fmt, elipsis)
+#endif
+
+/**
+ * @struct ostream_t
+ *
+ * @extends sqfs_object_t
+ *
+ * @brief An append-only data stream.
+ */
+typedef struct ostream_t {
+	sqfs_object_t base;
+
+	int (*append)(struct ostream_t *strm, const void *data, size_t size);
+
+	int (*append_sparse)(struct ostream_t *strm, size_t size);
+
+	int (*flush)(struct ostream_t *strm);
+
+	const char *(*get_filename)(struct ostream_t *strm);
+} ostream_t;
+
+enum {
+	OSTREAM_OPEN_OVERWRITE = 0x01,
+	OSTREAM_OPEN_SPARSE = 0x02,
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Append a block of data to an output stream.
+ *
+ * @memberof ostream_t
+ *
+ * @param strm A pointer to an output stream.
+ * @param data A pointer to the data block to append.
+ * @param size The number of bytes to append.
+ *
+ * @return Zero on success, -1 on failure.
+ */
+SQFS_INTERNAL int ostream_append(ostream_t *strm, const void *data,
+				 size_t size);
+
+/**
+ * @brief Append a number of zero bytes to an output stream.
+ *
+ * @memberof ostream_t
+ *
+ * If the unerlying implementation supports sparse files, this function can be
+ * used to create a "hole". If the implementation does not support it, a
+ * fallback is used that just appends a block of zeros manualy.
+ *
+ * @param strm A pointer to an output stream.
+ * @param size The number of zero bytes to append.
+ *
+ * @return Zero on success, -1 on failure.
+ */
+SQFS_INTERNAL int ostream_append_sparse(ostream_t *strm, size_t size);
+
+/**
+ * @brief Process all pending, buffered data and flush it to disk.
+ *
+ * @memberof ostream_t
+ *
+ * If the stream performs some kind of transformation (e.g. transparent data
+ * compression), flushing caues the wrapped format to insert a termination
+ * token. Only call this function when you are absolutely DONE appending data,
+ * shortly before destroying the stream.
+ *
+ * @param strm A pointer to an output stream.
+ *
+ * @return Zero on success, -1 on failure.
+ */
+SQFS_INTERNAL int ostream_flush(ostream_t *strm);
+
+/**
+ * @brief Get the underlying filename of a output stream.
+ *
+ * @memberof ostream_t
+ *
+ * @param strm The output stream to get the filename from.
+ *
+ * @return A string holding the underlying filename.
+ */
+SQFS_INTERNAL const char *ostream_get_filename(ostream_t *strm);
+
+/**
+ * @brief Printf like function that appends to an output stream
+ *
+ * @memberof ostream_t
+ *
+ * @param strm The output stream to append to.
+ * @param fmt A printf style format string.
+ *
+ * @return The number of characters written on success, -1 on failure.
+ */
+SQFS_INTERNAL int ostream_printf(ostream_t *strm, const char *fmt, ...)
+	PRINTF_ATTRIB(2, 3);
+
+/**
+ * @brief Read data from an input stream and append it to an output stream
+ *
+ * @memberof ostream_t
+ *
+ * @param out A pointer to an output stream to append to.
+ * @param in A pointer to an input stream to read from.
+ * @param size The number of bytes to copy over.
+ *
+ * @return The number of bytes copied on success, -1 on failure,
+ *         0 on end-of-file.
+ */
+SQFS_INTERNAL sqfs_s32 ostream_append_from_istream(ostream_t *out,
+						   istream_t *in,
+						   sqfs_u32 size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IO_OSTREAM_H */
diff --git a/include/io/std.h b/include/io/std.h
new file mode 100644
index 0000000..805bebd
--- /dev/null
+++ b/include/io/std.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * std.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef IO_STD_H
+#define IO_STD_H
+
+#include "io/istream.h"
+#include "io/ostream.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Create an input stream that reads from standard input.
+ *
+ * @memberof istream_t
+ *
+ * @return A pointer to an input stream on success, NULL on failure.
+ */
+SQFS_INTERNAL istream_t *istream_open_stdin(void);
+
+/**
+ * @brief Create an output stream that writes to standard output.
+ *
+ * @memberof ostream_t
+ *
+ * @return A pointer to an output stream on success, NULL on failure.
+ */
+SQFS_INTERNAL ostream_t *ostream_open_stdout(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IO_STD_H */
diff --git a/include/io/xfrm.h b/include/io/xfrm.h
new file mode 100644
index 0000000..22a42b6
--- /dev/null
+++ b/include/io/xfrm.h
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * xfrm.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef IO_XFRM_H
+#define IO_XFRM_H
+
+#include "io/istream.h"
+#include "io/ostream.h"
+
+enum {
+	/**
+	 * @brief Deflate compressor with gzip headers.
+	 *
+	 * This actually creates a gzip compatible file, including a
+	 * gzip header and trailer.
+	 */
+	IO_COMPRESSOR_GZIP = 1,
+
+	IO_COMPRESSOR_XZ = 2,
+
+	IO_COMPRESSOR_ZSTD = 3,
+
+	IO_COMPRESSOR_BZIP2 = 4,
+
+	IO_COMPRESSOR_MIN = 1,
+	IO_COMPRESSOR_MAX = 4,
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Create an input stream that transparently uncompresses data.
+ *
+ * @memberof istream_t
+ *
+ * This function creates an input stream that wraps an underlying input stream
+ * that is compressed and transparently uncompresses the data when reading
+ * from it.
+ *
+ * The new stream takes ownership of the wrapped stream and destroys it when
+ * the compressor stream is destroyed. If this function fails, the wrapped
+ * stream is also destroyed.
+ *
+ * @param strm A pointer to another stream that should be wrapped.
+ * @param comp_id An identifier describing the compressor to use.
+ *
+ * @return A pointer to an input stream on success, NULL on failure.
+ */
+SQFS_INTERNAL istream_t *istream_compressor_create(istream_t *strm,
+						   int comp_id);
+
+/**
+ * @brief Create an output stream that transparently compresses data.
+ *
+ * @memberof ostream_t
+ *
+ * This function creates an output stream that transparently compresses all
+ * data appended to it and writes the compressed data to an underlying, wrapped
+ * output stream.
+ *
+ * The new stream takes ownership of the wrapped stream and destroys it when
+ * the compressor stream is destroyed. If this function fails, the wrapped
+ * stream is also destroyed.
+ *
+ * @param strm A pointer to another stream that should be wrapped.
+ * @param comp_id An identifier describing the compressor to use.
+ *
+ * @return A pointer to an output stream on success, NULL on failure.
+ */
+SQFS_INTERNAL ostream_t *ostream_compressor_create(ostream_t *strm,
+						   int comp_id);
+
+/**
+ * @brief Probe the buffered data in an istream to check if it is compressed.
+ *
+ * @memberof istream_t
+ *
+ * This function peeks into the internal buffer of an input stream to check
+ * for magic signatures of various compressors.
+ *
+ * @param strm A pointer to an input stream to check
+ * @param probe A callback used to check if raw/decoded data matches an
+ *              expected format. Returns 0 if not, -1 on failure and +1
+ *              on success.
+ *
+ * @return A compressor ID on success, 0 if no match was found, -1 on failure.
+ */
+SQFS_INTERNAL int istream_detect_compressor(istream_t *strm,
+					    int (*probe)(const sqfs_u8 *data,
+							 size_t size));
+
+/**
+ * @brief Resolve a compressor name to an ID.
+ *
+ * @param name A compressor name.
+ *
+ * @return A compressor ID on success, -1 on failure.
+ */
+SQFS_INTERNAL int io_compressor_id_from_name(const char *name);
+
+/**
+ * @brief Resolve a id to a  compressor name.
+ *
+ * @param id A compressor ID.
+ *
+ * @return A compressor name on success, NULL on failure.
+ */
+SQFS_INTERNAL const char *io_compressor_name_from_id(int id);
+
+/**
+ * @brief Check if support for a given compressor has been built in.
+ *
+ * @param id A compressor ID.
+ *
+ * @return True if the compressor is supported, false if not.
+ */
+SQFS_INTERNAL bool io_compressor_exists(int id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IO_XFRM_H */
diff --git a/include/tar.h b/include/tar.h
index 41be57f..dadc16e 100644
--- a/include/tar.h
+++ b/include/tar.h
@@ -9,7 +9,8 @@
 
 #include "config.h"
 #include "compat.h"
-#include "fstream.h"
+#include "io/istream.h"
+#include "io/ostream.h"
 
 #include <stdbool.h>
 #include <stdint.h>
diff --git a/lib/fstream/Makemodule.am b/lib/fstream/Makemodule.am
deleted file mode 100644
index ad5f426..0000000
--- a/lib/fstream/Makemodule.am
+++ /dev/null
@@ -1,45 +0,0 @@
-libfstream_a_SOURCES = include/fstream.h
-libfstream_a_SOURCES += lib/fstream/internal.h
-libfstream_a_SOURCES += lib/fstream/ostream.c lib/fstream/printf.c
-libfstream_a_SOURCES += lib/fstream/istream.c lib/fstream/get_line.c
-libfstream_a_SOURCES += lib/fstream/compressor.c
-libfstream_a_SOURCES += lib/fstream/compress/ostream_compressor.c
-libfstream_a_SOURCES += lib/fstream/uncompress/istream_compressor.c
-libfstream_a_SOURCES += lib/fstream/uncompress/autodetect.c
-libfstream_a_CFLAGS = $(AM_CFLAGS) $(ZLIB_CFLAGS) $(XZ_CFLAGS)
-libfstream_a_CFLAGS += $(ZSTD_CFLAGS) $(BZIP2_CFLAGS)
-libfstream_a_CPPFLAGS = $(AM_CPPFLAGS)
-
-if WINDOWS
-libfstream_a_SOURCES += lib/fstream/win32/ostream.c
-libfstream_a_SOURCES += lib/fstream/win32/istream.c
-libfstream_a_CFLAGS += -DWINVER=0x0600 -D_WIN32_WINNT=0x0600
-else
-libfstream_a_SOURCES += lib/fstream/unix/ostream.c
-libfstream_a_SOURCES += lib/fstream/unix/istream.c
-endif
-
-if WITH_XZ
-libfstream_a_SOURCES += lib/fstream/compress/xz.c lib/fstream/uncompress/xz.c
-libfstream_a_CPPFLAGS += -DWITH_XZ
-endif
-
-if WITH_GZIP
-libfstream_a_SOURCES += lib/fstream/compress/gzip.c
-libfstream_a_SOURCES += lib/fstream/uncompress/gzip.c
-libfstream_a_CPPFLAGS += -DWITH_GZIP
-endif
-
-if WITH_ZSTD
-libfstream_a_SOURCES += lib/fstream/compress/zstd.c
-libfstream_a_SOURCES += lib/fstream/uncompress/zstd.c
-libfstream_a_CPPFLAGS += -DWITH_ZSTD
-endif
-
-if WITH_BZIP2
-libfstream_a_SOURCES += lib/fstream/compress/bzip2.c
-libfstream_a_SOURCES += lib/fstream/uncompress/bzip2.c
-libfstream_a_CPPFLAGS += -DWITH_BZIP2
-endif
-
-noinst_LIBRARIES += libfstream.a
diff --git a/lib/fstream/compress/bzip2.c b/lib/fstream/compress/bzip2.c
deleted file mode 100644
index 7f0c09a..0000000
--- a/lib/fstream/compress/bzip2.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/* 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/fstream/compress/gzip.c b/lib/fstream/compress/gzip.c
deleted file mode 100644
index b73a258..0000000
--- a/lib/fstream/compress/gzip.c
+++ /dev/null
@@ -1,92 +0,0 @@
-/* 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/fstream/compress/ostream_compressor.c b/lib/fstream/compress/ostream_compressor.c
deleted file mode 100644
index 30ff7eb..0000000
--- a/lib/fstream/compress/ostream_compressor.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/* 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 FSTREAM_COMPRESSOR_GZIP:
-#ifdef WITH_GZIP
-		comp = ostream_gzip_create(strm->get_filename(strm));
-#endif
-		break;
-	case FSTREAM_COMPRESSOR_XZ:
-#ifdef WITH_XZ
-		comp = ostream_xz_create(strm->get_filename(strm));
-#endif
-		break;
-	case FSTREAM_COMPRESSOR_ZSTD:
-#if defined(WITH_ZSTD) && defined(HAVE_ZSTD_STREAM)
-		comp = ostream_zstd_create(strm->get_filename(strm));
-#endif
-		break;
-	case FSTREAM_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/fstream/compress/xz.c b/lib/fstream/compress/xz.c
deleted file mode 100644
index 65bda0b..0000000
--- a/lib/fstream/compress/xz.c
+++ /dev/null
@@ -1,80 +0,0 @@
-/* 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/fstream/compress/zstd.c b/lib/fstream/compress/zstd.c
deleted file mode 100644
index c0b002e..0000000
--- a/lib/fstream/compress/zstd.c
+++ /dev/null
@@ -1,94 +0,0 @@
-/* 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/fstream/compressor.c b/lib/fstream/compressor.c
deleted file mode 100644
index 48f9567..0000000
--- a/lib/fstream/compressor.c
+++ /dev/null
@@ -1,67 +0,0 @@
-/* SPDX-License-Identifier: GPL-3.0-or-later */
-/*
- * compressor.c
- *
- * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
- */
-#include "internal.h"
-
-int fstream_compressor_id_from_name(const char *name)
-{
-	if (strcmp(name, "gzip") == 0)
-		return FSTREAM_COMPRESSOR_GZIP;
-
-	if (strcmp(name, "xz") == 0)
-		return FSTREAM_COMPRESSOR_XZ;
-
-	if (strcmp(name, "zstd") == 0)
-		return FSTREAM_COMPRESSOR_ZSTD;
-
-	if (strcmp(name, "bzip2") == 0)
-		return FSTREAM_COMPRESSOR_BZIP2;
-
-	return -1;
-}
-
-const char *fstream_compressor_name_from_id(int id)
-{
-	if (id == FSTREAM_COMPRESSOR_GZIP)
-		return "gzip";
-
-	if (id == FSTREAM_COMPRESSOR_XZ)
-		return "xz";
-
-	if (id == FSTREAM_COMPRESSOR_ZSTD)
-		return "zstd";
-
-	if (id == FSTREAM_COMPRESSOR_BZIP2)
-		return "bzip2";
-
-	return NULL;
-}
-
-bool fstream_compressor_exists(int id)
-{
-	switch (id) {
-#ifdef WITH_GZIP
-	case FSTREAM_COMPRESSOR_GZIP:
-		return true;
-#endif
-#ifdef WITH_XZ
-	case FSTREAM_COMPRESSOR_XZ:
-		return true;
-#endif
-#if defined(WITH_ZSTD) && defined(HAVE_ZSTD_STREAM)
-	case FSTREAM_COMPRESSOR_ZSTD:
-		return true;
-#endif
-#ifdef WITH_BZIP2
-	case FSTREAM_COMPRESSOR_BZIP2:
-		return true;
-#endif
-	default:
-		break;
-	}
-
-	return false;
-}
diff --git a/lib/fstream/get_line.c b/lib/fstream/get_line.c
deleted file mode 100644
index f7e0b59..0000000
--- a/lib/fstream/get_line.c
+++ /dev/null
@@ -1,118 +0,0 @@
-/* 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/fstream/internal.h b/lib/fstream/internal.h
deleted file mode 100644
index 4f02f8c..0000000
--- a/lib/fstream/internal.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/* 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 "fstream.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/fstream/istream.c b/lib/fstream/istream.c
deleted file mode 100644
index 6318a23..0000000
--- a/lib/fstream/istream.c
+++ /dev/null
@@ -1,91 +0,0 @@
-/* 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/fstream/ostream.c b/lib/fstream/ostream.c
deleted file mode 100644
index afe76e8..0000000
--- a/lib/fstream/ostream.c
+++ /dev/null
@@ -1,84 +0,0 @@
-/* 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/fstream/printf.c b/lib/fstream/printf.c
deleted file mode 100644
index 3850487..0000000
--- a/lib/fstream/printf.c
+++ /dev/null
@@ -1,30 +0,0 @@
-/* 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/fstream/uncompress/autodetect.c b/lib/fstream/uncompress/autodetect.c
deleted file mode 100644
index 61628f8..0000000
--- a/lib/fstream/uncompress/autodetect.c
+++ /dev/null
@@ -1,55 +0,0 @@
-/* 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[] = {
-	{ FSTREAM_COMPRESSOR_GZIP, (const sqfs_u8 *)"\x1F\x8B\x08", 3 },
-	{ FSTREAM_COMPRESSOR_XZ, (const sqfs_u8 *)("\xFD" "7zXZ"), 6 },
-	{ FSTREAM_COMPRESSOR_ZSTD, (const sqfs_u8 *)"\x28\xB5\x2F\xFD", 4 },
-	{ FSTREAM_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/fstream/uncompress/bzip2.c b/lib/fstream/uncompress/bzip2.c
deleted file mode 100644
index 3b44383..0000000
--- a/lib/fstream/uncompress/bzip2.c
+++ /dev/null
@@ -1,118 +0,0 @@
-/* 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/fstream/uncompress/gzip.c b/lib/fstream/uncompress/gzip.c
deleted file mode 100644
index 1d6274c..0000000
--- a/lib/fstream/uncompress/gzip.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/* 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/fstream/uncompress/istream_compressor.c b/lib/fstream/uncompress/istream_compressor.c
deleted file mode 100644
index 75edd1b..0000000
--- a/lib/fstream/uncompress/istream_compressor.c
+++ /dev/null
@@ -1,69 +0,0 @@
-/* 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 FSTREAM_COMPRESSOR_GZIP:
-#ifdef WITH_GZIP
-		comp = istream_gzip_create(strm->get_filename(strm));
-#endif
-		break;
-	case FSTREAM_COMPRESSOR_XZ:
-#ifdef WITH_XZ
-		comp = istream_xz_create(strm->get_filename(strm));
-#endif
-		break;
-	case FSTREAM_COMPRESSOR_ZSTD:
-#if defined(WITH_ZSTD) && defined(HAVE_ZSTD_STREAM)
-		comp = istream_zstd_create(strm->get_filename(strm));
-#endif
-		break;
-	case FSTREAM_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/fstream/uncompress/xz.c b/lib/fstream/uncompress/xz.c
deleted file mode 100644
index 0fd9ce6..0000000
--- a/lib/fstream/uncompress/xz.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/* 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/fstream/uncompress/zstd.c b/lib/fstream/uncompress/zstd.c
deleted file mode 100644
index fd22cbf..0000000
--- a/lib/fstream/uncompress/zstd.c
+++ /dev/null
@@ -1,81 +0,0 @@
-/* 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/fstream/unix/istream.c b/lib/fstream/unix/istream.c
deleted file mode 100644
index 5898141..0000000
--- a/lib/fstream/unix/istream.c
+++ /dev/null
@@ -1,124 +0,0 @@
-/* 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/fstream/unix/ostream.c b/lib/fstream/unix/ostream.c
deleted file mode 100644
index 17f1998..0000000
--- a/lib/fstream/unix/ostream.c
+++ /dev/null
@@ -1,173 +0,0 @@
-/* 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/fstream/win32/istream.c b/lib/fstream/win32/istream.c
deleted file mode 100644
index b591584..0000000
--- a/lib/fstream/win32/istream.c
+++ /dev/null
@@ -1,138 +0,0 @@
-/* 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/fstream/win32/ostream.c b/lib/fstream/win32/ostream.c
deleted file mode 100644
index 2bd78c8..0000000
--- a/lib/fstream/win32/ostream.c
+++ /dev/null
@@ -1,197 +0,0 @@
-/* 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/fstree/fstree_from_file.c b/lib/fstree/fstree_from_file.c
index e77f19a..dd289bc 100644
--- a/lib/fstree/fstree_from_file.c
+++ b/lib/fstree/fstree_from_file.c
@@ -6,8 +6,8 @@
  */
 #include "config.h"
 
+#include "io/file.h"
 #include "fstree.h"
-#include "fstream.h"
 #include "compat.h"
 
 #include <stdlib.h>
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;
+}
diff --git a/tests/Makemodule.am b/tests/Makemodule.am
index 3165889..0ae3f8b 100644
--- a/tests/Makemodule.am
+++ b/tests/Makemodule.am
@@ -1,5 +1,5 @@
 include tests/libutil/Makemodule.am
-include tests/libfstream/Makemodule.am
+include tests/libio/Makemodule.am
 include tests/libfstree/Makemodule.am
 include tests/libtar/Makemodule.am
 include tests/libsqfs/Makemodule.am
diff --git a/tests/libfstream/Makemodule.am b/tests/libfstream/Makemodule.am
deleted file mode 100644
index 57a98bc..0000000
--- a/tests/libfstream/Makemodule.am
+++ /dev/null
@@ -1,68 +0,0 @@
-test_get_line_SOURCES = tests/libfstream/get_line.c tests/test.h
-test_get_line_LDADD = libfstream.a libcompat.a
-test_get_line_CPPFLAGS = $(AM_CPPFLAGS)
-test_get_line_CPPFLAGS += -DTESTFILE=$(top_srcdir)/tests/libfstream/get_line.txt
-
-test_xfrm_bzip2_SOURCES = tests/libfstream/uncompress.c tests/test.h
-test_xfrm_bzip2_LDADD = libfstream.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
-test_xfrm_bzip2_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
-test_xfrm_bzip2_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_BZIP2=1
-
-test_xfrm_bzip22_SOURCES = tests/libfstream/uncompress.c tests/test.h
-test_xfrm_bzip22_LDADD = libfstream.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
-test_xfrm_bzip22_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
-test_xfrm_bzip22_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_BZIP22=1
-
-test_xfrm_xz_SOURCES = tests/libfstream/uncompress.c tests/test.h
-test_xfrm_xz_LDADD = libfstream.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
-test_xfrm_xz_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
-test_xfrm_xz_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_XZ=1
-
-test_xfrm_xz2_SOURCES = tests/libfstream/uncompress.c tests/test.h
-test_xfrm_xz2_LDADD = libfstream.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
-test_xfrm_xz2_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
-test_xfrm_xz2_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_XZ2=1
-
-test_xfrm_gzip_SOURCES = tests/libfstream/uncompress.c tests/test.h
-test_xfrm_gzip_LDADD = libfstream.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
-test_xfrm_gzip_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
-test_xfrm_gzip_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_GZIP=1
-
-test_xfrm_zstd_SOURCES = tests/libfstream/uncompress.c tests/test.h
-test_xfrm_zstd_LDADD = libfstream.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
-test_xfrm_zstd_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
-test_xfrm_zstd_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_ZSTD=1
-
-test_xfrm_zstd2_SOURCES = tests/libfstream/uncompress.c tests/test.h
-test_xfrm_zstd2_LDADD = libfstream.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
-test_xfrm_zstd2_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
-test_xfrm_zstd2_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_ZSTD2=1
-
-if BUILD_TOOLS
-check_PROGRAMS += test_get_line
-TESTS += test_get_line
-
-if WITH_BZIP2
-check_PROGRAMS += test_xfrm_bzip2 test_xfrm_bzip22
-TESTS += test_xfrm_bzip2 test_xfrm_bzip22
-endif
-
-if WITH_XZ
-check_PROGRAMS += test_xfrm_xz test_xfrm_xz2
-TESTS += test_xfrm_xz test_xfrm_xz2
-endif
-
-if WITH_GZIP
-check_PROGRAMS += test_xfrm_gzip
-TESTS += test_xfrm_gzip
-endif
-
-if WITH_ZSTD
-if HAVE_ZSTD_STREAM
-check_PROGRAMS += test_xfrm_zstd test_xfrm_zstd2
-TESTS += test_xfrm_zstd test_xfrm_zstd2
-endif
-endif
-endif
-
-EXTRA_DIST += $(top_srcdir)/tests/libfstream/get_line.txt
diff --git a/tests/libfstream/get_line.c b/tests/libfstream/get_line.c
deleted file mode 100644
index 7d9a26a..0000000
--- a/tests/libfstream/get_line.c
+++ /dev/null
@@ -1,166 +0,0 @@
-/* SPDX-License-Identifier: GPL-3.0-or-later */
-/*
- * get_line.c
- *
- * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
- */
-#include "config.h"
-
-#include "fstream.h"
-#include "../test.h"
-
-typedef struct {
-	size_t line_num;
-	const char *str;
-} line_t;
-
-static void run_test_case(const line_t *lines, size_t count,
-			  int flags)
-{
-	size_t i, line_num, old_line_num;
-	istream_t *fp;
-	char *line;
-	int ret;
-
-	fp = istream_open_file(STRVALUE(TESTFILE));
-	TEST_NOT_NULL(fp);
-
-	line_num = 1;
-	line = NULL;
-
-	for (i = 0; i < count; ++i) {
-		old_line_num = line_num;
-		ret = istream_get_line(fp, &line, &line_num, flags);
-
-		TEST_ASSERT(line_num >= old_line_num);
-		TEST_EQUAL_I(ret, 0);
-		TEST_NOT_NULL(line);
-
-		TEST_EQUAL_UI(line_num, lines[i].line_num);
-		TEST_STR_EQUAL(line, lines[i].str);
-
-		free(line);
-		line = NULL;
-		line_num += 1;
-	}
-
-	ret = istream_get_line(fp, &line, &line_num, flags);
-	TEST_ASSERT(ret > 0);
-
-	sqfs_destroy(fp);
-}
-
-static const line_t lines_raw[] = {
-	{ 1, "" },
-	{ 2, "The quick" },
-	{ 3, "  " },
-	{ 4, "  brown fox  " },
-	{ 5, "" },
-	{ 6, "jumps over" },
-	{ 7, "the" },
-	{ 8, "lazy" },
-	{ 9, "" },
-	{ 10, "dog" },
-	{ 11, "" },
-};
-
-static const line_t lines_ltrim[] = {
-	{ 1, "" },
-	{ 2, "The quick" },
-	{ 3, "" },
-	{ 4, "brown fox  " },
-	{ 5, "" },
-	{ 6, "jumps over" },
-	{ 7, "the" },
-	{ 8, "lazy" },
-	{ 9, "" },
-	{ 10, "dog" },
-	{ 11, "" },
-};
-
-static const line_t lines_rtrim[] = {
-	{ 1, "" },
-	{ 2, "The quick" },
-	{ 3, "" },
-	{ 4, "  brown fox" },
-	{ 5, "" },
-	{ 6, "jumps over" },
-	{ 7, "the" },
-	{ 8, "lazy" },
-	{ 9, "" },
-	{ 10, "dog" },
-	{ 11, "" },
-};
-
-static const line_t lines_trim[] = {
-	{ 1, "" },
-	{ 2, "The quick" },
-	{ 3, "" },
-	{ 4, "brown fox" },
-	{ 5, "" },
-	{ 6, "jumps over" },
-	{ 7, "the" },
-	{ 8, "lazy" },
-	{ 9, "" },
-	{ 10, "dog" },
-	{ 11, "" },
-};
-
-static const line_t lines_no_empty[] = {
-	{ 2, "The quick" },
-	{ 3, "  " },
-	{ 4, "  brown fox  " },
-	{ 6, "jumps over" },
-	{ 7, "the" },
-	{ 8, "lazy" },
-	{ 10, "dog" },
-};
-
-static const line_t lines_no_empty_ltrim[] = {
-	{ 2, "The quick" },
-	{ 4, "brown fox  " },
-	{ 6, "jumps over" },
-	{ 7, "the" },
-	{ 8, "lazy" },
-	{ 10, "dog" },
-};
-
-static const line_t lines_no_empty_rtrim[] = {
-	{ 2, "The quick" },
-	{ 4, "  brown fox" },
-	{ 6, "jumps over" },
-	{ 7, "the" },
-	{ 8, "lazy" },
-	{ 10, "dog" },
-};
-
-static const line_t lines_no_empty_trim[] = {
-	{ 2, "The quick" },
-	{ 4, "brown fox" },
-	{ 6, "jumps over" },
-	{ 7, "the" },
-	{ 8, "lazy" },
-	{ 10, "dog" },
-};
-
-int main(int argc, char **argv)
-{
-	(void)argc; (void)argv;
-
-	run_test_case(lines_raw, 11, 0);
-	run_test_case(lines_ltrim, 11, ISTREAM_LINE_LTRIM);
-	run_test_case(lines_rtrim, 11, ISTREAM_LINE_RTRIM);
-	run_test_case(lines_trim, 11,
-		      ISTREAM_LINE_LTRIM | ISTREAM_LINE_RTRIM);
-
-	run_test_case(lines_no_empty, 7, ISTREAM_LINE_SKIP_EMPTY);
-	run_test_case(lines_no_empty_ltrim, 6,
-		      ISTREAM_LINE_SKIP_EMPTY | ISTREAM_LINE_LTRIM);
-	run_test_case(lines_no_empty_rtrim, 6,
-		      ISTREAM_LINE_SKIP_EMPTY | ISTREAM_LINE_RTRIM);
-	run_test_case(lines_no_empty_trim, 6,
-		      ISTREAM_LINE_SKIP_EMPTY | ISTREAM_LINE_LTRIM |
-		      ISTREAM_LINE_RTRIM);
-
-	return EXIT_SUCCESS;
-}
diff --git a/tests/libfstream/get_line.txt b/tests/libfstream/get_line.txt
deleted file mode 100644
index a1994f0..0000000
--- a/tests/libfstream/get_line.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-
-The quick
-  
-  brown fox  
-
-jumps over
-the
-lazy
-
-dog
-
diff --git a/tests/libfstream/uncompress.c b/tests/libfstream/uncompress.c
deleted file mode 100644
index 5f0cbec..0000000
--- a/tests/libfstream/uncompress.c
+++ /dev/null
@@ -1,431 +0,0 @@
-/* SPDX-License-Identifier: GPL-3.0-or-later */
-/*
- * uncompress.c
- *
- * Copyright (C) 2021 David Oberhollenzer <goliath@infraroot.at>
- */
-#include "fstream.h"
-#include "../test.h"
-
-static sqfs_u8 data_in[] = {
-#if defined(TEST_BZIP2)
-	0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26,
-	0x53, 0x59, 0x05, 0x24, 0x28, 0x04, 0x00, 0x00,
-	0x27, 0xd7, 0x80, 0x00, 0x10, 0x40, 0x05, 0x06,
-	0x04, 0x02, 0x00, 0x3f, 0xe7, 0xff, 0x40, 0x30,
-	0x01, 0x2d, 0x23, 0x62, 0x26, 0x05, 0x3d, 0x03,
-	0x54, 0xfd, 0x53, 0x4c, 0x86, 0x9e, 0x90, 0x6a,
-	0x9e, 0x9e, 0x85, 0x3c, 0xa0, 0x00, 0x00, 0x1a,
-	0x9e, 0x41, 0x13, 0x13, 0x28, 0x69, 0x03, 0xd4,
-	0x0f, 0x1c, 0x70, 0xd0, 0xb4, 0xe3, 0xe4, 0x75,
-	0x4e, 0x8b, 0x67, 0x43, 0x7b, 0x38, 0x27, 0x77,
-	0xe4, 0xc1, 0x98, 0x3a, 0x2d, 0x3a, 0xe4, 0x44,
-	0x98, 0xdc, 0x49, 0x8b, 0x22, 0x48, 0xfc, 0xc8,
-	0xe7, 0x57, 0x05, 0x3c, 0x5a, 0xee, 0x5a, 0x84,
-	0xcd, 0x7c, 0x8f, 0x26, 0x6b, 0x6e, 0xf7, 0xb5,
-	0x49, 0x1f, 0x79, 0x42, 0x5d, 0x09, 0x8c, 0xc6,
-	0xde, 0x0c, 0x0d, 0xb1, 0x46, 0xb4, 0xee, 0xd9,
-	0x8f, 0x33, 0x37, 0x04, 0xa9, 0x05, 0x49, 0xe3,
-	0x04, 0x16, 0x62, 0x36, 0x3a, 0x01, 0xda, 0xd4,
-	0xc8, 0x8a, 0x32, 0x02, 0x1f, 0x62, 0x4b, 0xa4,
-	0x49, 0x59, 0xda, 0x50, 0x85, 0x69, 0x35, 0x21,
-	0x10, 0xc6, 0x8a, 0x3c, 0x44, 0x95, 0xb0, 0xbc,
-	0xc5, 0x6b, 0xea, 0xfb, 0x40, 0xbd, 0x14, 0x01,
-	0x6a, 0xfa, 0xcd, 0x67, 0xd8, 0x2d, 0x93, 0x8b,
-	0xda, 0x44, 0x1b, 0xe9, 0x5a, 0x87, 0x60, 0xb0,
-	0xe0, 0x73, 0xd1, 0x01, 0x3a, 0x66, 0x05, 0xcc,
-	0x34, 0xa0, 0x63, 0x8d, 0x35, 0x5e, 0xa0, 0x9f,
-	0x05, 0x89, 0x15, 0x51, 0x48, 0x16, 0x0c, 0x61,
-	0xf4, 0x30, 0xb8, 0x07, 0x29, 0xc0, 0xf5, 0x1a,
-	0xe1, 0x0d, 0x6c, 0xfe, 0x91, 0xda, 0x13, 0x2f,
-	0x8e, 0x5b, 0x1c, 0xfc, 0xb3, 0xb2, 0x30, 0x9d,
-	0xf6, 0x09, 0x30, 0x55, 0x30, 0x67, 0xc2, 0x87,
-	0xe9, 0x9a, 0xd4, 0x1d, 0x66, 0x11, 0x54, 0x89,
-	0x21, 0xe1, 0x55, 0x84, 0xbf, 0xa6, 0x11, 0xa4,
-	0xb8, 0x40, 0xed, 0x42, 0x20, 0xb9, 0xb7, 0x26,
-	0x31, 0x14, 0x4f, 0x86, 0xdc, 0x50, 0x34, 0x38,
-	0x8b, 0x57, 0x77, 0x21, 0xf6, 0x89, 0xbd, 0xc5,
-	0x65, 0xc3, 0x23, 0x45, 0xec, 0x7f, 0x8b, 0xb9,
-	0x22, 0x9c, 0x28, 0x48, 0x02, 0x92, 0x14, 0x02,
-	0x00,
-#elif defined(TEST_BZIP22)
-	0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26,
-	0x53, 0x59, 0x5d, 0x09, 0x24, 0x1d, 0x00, 0x00,
-	0x13, 0xd7, 0x80, 0x00, 0x10, 0x40, 0x05, 0x00,
-	0x04, 0x02, 0x00, 0x3e, 0xa7, 0xff, 0x40, 0x30,
-	0x00, 0xac, 0x43, 0x54, 0xf5, 0x36, 0x4c, 0xa7,
-	0xa8, 0xd3, 0x6a, 0x60, 0x81, 0x40, 0x00, 0xd0,
-	0x32, 0x64, 0x0d, 0x53, 0xda, 0x02, 0x09, 0xa2,
-	0x68, 0x34, 0xd1, 0x27, 0x4a, 0xdd, 0xf2, 0x0a,
-	0x73, 0x43, 0xf9, 0xa2, 0x51, 0x85, 0x76, 0x45,
-	0x9a, 0x68, 0x3a, 0xe7, 0x0d, 0xc0, 0x21, 0x4a,
-	0xc4, 0xf9, 0xf7, 0x40, 0xc3, 0x10, 0xb2, 0x9b,
-	0x58, 0x56, 0x71, 0x50, 0x2f, 0xa4, 0xc5, 0x61,
-	0x19, 0xf6, 0x59, 0x06, 0x82, 0x03, 0x7f, 0xeb,
-	0xd2, 0x61, 0x88, 0xcd, 0xe8, 0xf7, 0xe8, 0x87,
-	0x59, 0x9d, 0xe1, 0xf8, 0x19, 0x6e, 0xad, 0x77,
-	0xbf, 0x34, 0x17, 0x21, 0x6b, 0x91, 0xc9, 0x52,
-	0xd0, 0x81, 0x1e, 0xb5, 0x0b, 0xee, 0x42, 0x84,
-	0x80, 0xd5, 0xa1, 0x8a, 0x04, 0x18, 0x4d, 0xf3,
-	0xda, 0x7e, 0x3c, 0x40, 0xa4, 0xdb, 0xe5, 0xf0,
-	0x37, 0x40, 0x3a, 0x7d, 0xa7, 0x45, 0x21, 0xf2,
-	0x5a, 0x7b, 0x59, 0x56, 0x16, 0xd5, 0xac, 0x9f,
-	0x60, 0x85, 0x0e, 0xf5, 0x73, 0xd9, 0x47, 0xe2,
-	0xee, 0x48, 0xa7, 0x0a, 0x12, 0x0b, 0xa1, 0x24,
-	0x83, 0xa0,
-	0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26,
-	0x53, 0x59, 0x2c, 0x24, 0x39, 0xa0, 0x00, 0x00,
-	0x1f, 0x55, 0x80, 0x00, 0x10, 0x40, 0x05, 0x06,
-	0x00, 0x3f, 0xe7, 0xff, 0x40, 0x30, 0x00, 0xb5,
-	0x91, 0x13, 0x4f, 0x54, 0x7a, 0x6a, 0x6d, 0x4d,
-	0xa2, 0x68, 0x0c, 0x84, 0x53, 0xf5, 0x30, 0x89,
-	0xa3, 0xd4, 0x0d, 0x0f, 0x49, 0xa0, 0xd4, 0xf4,
-	0xd1, 0x53, 0xf4, 0x93, 0x69, 0x3c, 0x81, 0x1a,
-	0x65, 0x53, 0x90, 0x51, 0x07, 0x2a, 0xad, 0x8f,
-	0x63, 0xba, 0x25, 0xc2, 0x0c, 0x8b, 0xb9, 0x95,
-	0x15, 0xd8, 0xda, 0x61, 0x5c, 0xa9, 0xe4, 0x0b,
-	0x21, 0xc9, 0x97, 0x57, 0x01, 0x28, 0x9b, 0xfb,
-	0x94, 0xb9, 0x48, 0xa3, 0x0a, 0xc6, 0x1c, 0x54,
-	0x98, 0x9a, 0x39, 0xc3, 0x87, 0x90, 0x33, 0x58,
-	0x2d, 0x3e, 0x16, 0xb1, 0xae, 0x26, 0x89, 0x75,
-	0xf5, 0x77, 0xa5, 0x8e, 0x5b, 0x8c, 0x8a, 0x39,
-	0xbd, 0x75, 0x21, 0x9d, 0x99, 0x18, 0x4a, 0x91,
-	0xab, 0xbc, 0x08, 0x87, 0xa4, 0xf1, 0x81, 0xb5,
-	0xb4, 0xb0, 0xfe, 0x6b, 0x9f, 0xbe, 0x19, 0x82,
-	0xd1, 0x50, 0xe1, 0x5e, 0x13, 0xb5, 0xc6, 0x2c,
-	0xa4, 0x82, 0xf2, 0x5c, 0xc3, 0x20, 0x41, 0x13,
-	0x56, 0x63, 0x3d, 0xec, 0x71, 0x2a, 0xbf, 0x2c,
-	0x60, 0x2f, 0x7a, 0x4d, 0xcb, 0x3f, 0x8b, 0xb9,
-	0x22, 0x9c, 0x28, 0x48, 0x16, 0x12, 0x1c, 0xd0,
-	0x00,
-#elif defined(TEST_XZ)
-	0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00,
-	0xff, 0x12, 0xd9, 0x41, 0x02, 0x00, 0x21, 0x01,
-	0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
-	0xe0, 0x01, 0xbd, 0x01, 0x43, 0x5d, 0x00, 0x26,
-	0x1b, 0xca, 0x46, 0x67, 0x5a, 0xf2, 0x77, 0xb8,
-	0x7d, 0x86, 0xd8, 0x41, 0xdb, 0x05, 0x35, 0xcd,
-	0x83, 0xa5, 0x7c, 0x12, 0xa5, 0x05, 0xdb, 0x90,
-	0xbd, 0x2f, 0x14, 0xd3, 0x71, 0x72, 0x96, 0xa8,
-	0x8a, 0x7d, 0x84, 0x56, 0x71, 0x8d, 0x6a, 0x22,
-	0x98, 0xab, 0x9e, 0x3d, 0xc3, 0x55, 0xef, 0xcc,
-	0xa5, 0xc3, 0xdd, 0x5b, 0x8e, 0xbf, 0x03, 0x81,
-	0x21, 0x40, 0xd6, 0x26, 0x91, 0x02, 0x45, 0x4e,
-	0x20, 0x91, 0xcf, 0x8c, 0x51, 0x22, 0x02, 0x70,
-	0xba, 0x05, 0x6b, 0x83, 0xef, 0x3f, 0x8e, 0x09,
-	0xef, 0x88, 0xf5, 0x37, 0x1b, 0x89, 0x8d, 0xff,
-	0x1e, 0xee, 0xe8, 0xb0, 0xac, 0xf2, 0x6e, 0xd4,
-	0x3e, 0x25, 0xaf, 0xa0, 0x6d, 0x2e, 0xc0, 0x7f,
-	0xb5, 0xa0, 0xcb, 0x90, 0x1f, 0x08, 0x1a, 0xe2,
-	0x90, 0x20, 0x19, 0x71, 0x0c, 0xe8, 0x3f, 0xe5,
-	0x39, 0xeb, 0x9a, 0x62, 0x4f, 0x06, 0xda, 0x3c,
-	0x32, 0x59, 0xcc, 0x83, 0xe3, 0x83, 0x0f, 0x38,
-	0x7d, 0x43, 0x37, 0x6c, 0x0b, 0x05, 0x65, 0x98,
-	0x25, 0xdb, 0xf2, 0xc0, 0x2d, 0x39, 0x36, 0x5d,
-	0xd4, 0xb6, 0xc2, 0x79, 0x73, 0x3e, 0xc2, 0x6e,
-	0x54, 0xec, 0x78, 0x2b, 0x5d, 0xf1, 0xd1, 0xb4,
-	0xb3, 0xcd, 0xf3, 0x89, 0xf5, 0x81, 0x3e, 0x2c,
-	0x65, 0xd6, 0x73, 0xd3, 0x1b, 0x20, 0x68, 0x0c,
-	0x93, 0xd4, 0xfc, 0x9f, 0xf8, 0xa7, 0xd4, 0xfa,
-	0x3a, 0xb1, 0x13, 0x93, 0x4b, 0xec, 0x78, 0x7d,
-	0x5c, 0x81, 0x80, 0xe5, 0x14, 0x78, 0xfe, 0x7e,
-	0xde, 0xf7, 0xad, 0x9e, 0x84, 0xba, 0xf1, 0x00,
-	0xe9, 0xbd, 0x2c, 0xf4, 0x70, 0x7d, 0xbe, 0x29,
-	0xb9, 0xf0, 0x10, 0xb9, 0x01, 0xf1, 0x76, 0x8a,
-	0x5a, 0xad, 0x02, 0xa1, 0x32, 0xc8, 0x53, 0x59,
-	0x11, 0x4c, 0xe2, 0x98, 0x34, 0xd9, 0x23, 0x51,
-	0x4a, 0x40, 0x2b, 0x87, 0x41, 0xdd, 0x50, 0xcd,
-	0x98, 0x1e, 0x29, 0x86, 0x23, 0x93, 0x3e, 0x9b,
-	0x6b, 0x16, 0xa1, 0x40, 0xac, 0xe7, 0x40, 0xfe,
-	0xa9, 0x87, 0x48, 0x25, 0x52, 0x02, 0x8b, 0xc4,
-	0x68, 0x08, 0x5a, 0x62, 0xc1, 0xb2, 0x07, 0x3b,
-	0x26, 0x1e, 0x59, 0x5c, 0x47, 0x24, 0xae, 0x8e,
-	0xe5, 0xf7, 0xe6, 0x4b, 0x13, 0xb4, 0x6d, 0x46,
-	0x65, 0x4f, 0xd0, 0x48, 0xcc, 0x51, 0x4b, 0x80,
-	0xcb, 0xf1, 0xd4, 0x6c, 0x45, 0x98, 0x92, 0x47,
-	0xeb, 0x60, 0x00, 0x00, 0x00, 0x01, 0xd7, 0x02,
-	0xbe, 0x03, 0x00, 0x00, 0xda, 0x2c, 0x45, 0x49,
-	0xa8, 0x00, 0x0a, 0xfc, 0x02, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x59, 0x5a
-#elif defined(TEST_XZ2)
-	0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x04,
-	0xe6, 0xd6, 0xb4, 0x46, 0x02, 0x00, 0x21, 0x01,
-	0x16, 0x00, 0x00, 0x00, 0x74, 0x2f, 0xe5, 0xa3,
-	0xe0, 0x00, 0xdc, 0x00, 0xb3, 0x5d, 0x00, 0x26,
-	0x1b, 0xca, 0x46, 0x67, 0x5a, 0xf2, 0x77, 0xb8,
-	0x7d, 0x86, 0xd8, 0x41, 0xdb, 0x05, 0x35, 0xcd,
-	0x83, 0xa5, 0x7c, 0x12, 0xa5, 0x05, 0xdb, 0x90,
-	0xbd, 0x2f, 0x14, 0xd3, 0x71, 0x72, 0x96, 0xa8,
-	0x8a, 0x7d, 0x84, 0x56, 0x71, 0x8d, 0x6a, 0x22,
-	0x98, 0xab, 0x9e, 0x3d, 0xc3, 0x55, 0xef, 0xcc,
-	0xa5, 0xc3, 0xdd, 0x5b, 0x8e, 0xbf, 0x03, 0x81,
-	0x21, 0x40, 0xd6, 0x26, 0x91, 0x02, 0x45, 0x4e,
-	0x20, 0x91, 0xcf, 0x8c, 0x51, 0x22, 0x02, 0x70,
-	0xba, 0x05, 0x6b, 0x83, 0xef, 0x3f, 0x8e, 0x09,
-	0xef, 0x88, 0xf5, 0x37, 0x1b, 0x89, 0x8d, 0xff,
-	0x1e, 0xee, 0xe8, 0xb0, 0xac, 0xf2, 0x6e, 0xd4,
-	0x3e, 0x25, 0xaf, 0xa0, 0x6d, 0x2e, 0xc0, 0x7f,
-	0xb5, 0xa0, 0xcb, 0x90, 0x1f, 0x08, 0x1a, 0xe2,
-	0x90, 0x20, 0x19, 0x71, 0x0c, 0xe8, 0x3f, 0xe5,
-	0x39, 0xeb, 0x9a, 0x62, 0x4f, 0x06, 0xda, 0x3c,
-	0x32, 0x59, 0xcc, 0x83, 0xe3, 0x83, 0x0f, 0x38,
-	0x7d, 0x43, 0x37, 0x6c, 0x0b, 0x05, 0x65, 0x98,
-	0x25, 0xdb, 0xf2, 0xc0, 0x2d, 0x39, 0x36, 0x5d,
-	0xd4, 0xb6, 0xc2, 0x79, 0x73, 0x3e, 0xc2, 0x6e,
-	0x54, 0xec, 0x78, 0x2b, 0x5d, 0xf1, 0xd1, 0xb4,
-	0xb3, 0xcd, 0xf3, 0x89, 0xf5, 0x80, 0x79, 0x46,
-	0xc0, 0x00, 0x00, 0x00, 0xc4, 0xf5, 0x1d, 0x08,
-	0xf0, 0x34, 0x3a, 0x59, 0x00, 0x01, 0xcf, 0x01,
-	0xdd, 0x01, 0x00, 0x00, 0x7f, 0x5a, 0x77, 0xcb,
-	0xb1, 0xc4, 0x67, 0xfb, 0x02, 0x00, 0x00, 0x00,
-	0x00, 0x04, 0x59, 0x5a,
-	0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x04,
-	0xe6, 0xd6, 0xb4, 0x46, 0x02, 0x00, 0x21, 0x01,
-	0x16, 0x00, 0x00, 0x00, 0x74, 0x2f, 0xe5, 0xa3,
-	0xe0, 0x00, 0xe0, 0x00, 0xb7, 0x5d, 0x00, 0x31,
-	0x9b, 0xca, 0x19, 0xc5, 0x54, 0xec, 0xb6, 0x54,
-	0xe7, 0xb1, 0x7d, 0xc4, 0x57, 0x9e, 0x6c, 0x89,
-	0xad, 0x4a, 0x6d, 0x16, 0xd8, 0x3c, 0x05, 0x94,
-	0x10, 0x16, 0x99, 0x38, 0x21, 0xa3, 0xb9, 0xc5,
-	0x80, 0xff, 0xfc, 0xee, 0xd4, 0xd5, 0x3f, 0xdd,
-	0x8c, 0xd7, 0x3d, 0x8f, 0x76, 0xec, 0x96, 0x9d,
-	0x20, 0xac, 0xcb, 0x18, 0xf5, 0xb2, 0x9c, 0x12,
-	0xf6, 0x7c, 0x33, 0xdc, 0x4f, 0x9a, 0xe5, 0x2d,
-	0x63, 0x68, 0xa4, 0x2b, 0x1d, 0x0a, 0x1e, 0xf0,
-	0xfe, 0x73, 0xf2, 0x5f, 0x7b, 0xb4, 0xea, 0x54,
-	0xad, 0x27, 0xd1, 0xff, 0xb6, 0x50, 0x06, 0x7b,
-	0x51, 0x3f, 0x25, 0x8a, 0xcf, 0x4c, 0x03, 0x3e,
-	0xc3, 0xad, 0x47, 0x34, 0xcf, 0xba, 0x45, 0x79,
-	0xd0, 0x7b, 0xf6, 0x66, 0x63, 0xc0, 0xc6, 0x69,
-	0xa7, 0x51, 0x84, 0xa8, 0xa0, 0x0b, 0xbc, 0x6f,
-	0x13, 0x89, 0xd6, 0x5e, 0xac, 0xca, 0x2f, 0xd2,
-	0xe7, 0xe1, 0x1e, 0x78, 0x22, 0x3a, 0x59, 0x6c,
-	0x9c, 0x8c, 0x65, 0xf1, 0x5b, 0xf4, 0xbf, 0xd5,
-	0xdc, 0x05, 0xeb, 0x70, 0x10, 0xb8, 0x6c, 0xf2,
-	0x13, 0x20, 0xb0, 0xdd, 0x3e, 0xb2, 0x92, 0x5b,
-	0xa3, 0xf7, 0x94, 0xa1, 0xa1, 0x74, 0x36, 0x9a,
-	0xf1, 0xd8, 0xc2, 0xf0, 0xc6, 0x29, 0x7e, 0x85,
-	0x28, 0xf5, 0xf2, 0x21, 0x00, 0x00, 0x00, 0x00,
-	0xc8, 0x80, 0x67, 0x40, 0xc3, 0xaa, 0x17, 0x57,
-	0x00, 0x01, 0xd3, 0x01, 0xe1, 0x01, 0x00, 0x00,
-	0x86, 0xdf, 0x9e, 0x05, 0xb1, 0xc4, 0x67, 0xfb,
-	0x02, 0x00, 0x00, 0x00, 0x00, 0x04, 0x59, 0x5a
-#elif defined(TEST_GZIP)
-	0x1f, 0x8b, 0x08, 0x08, 0x82, 0xd4, 0x97, 0x60,
-	0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74,
-	0x78, 0x74, 0x00, 0x35, 0x90, 0xc1, 0x71, 0x43,
-	0x31, 0x08, 0x44, 0xef, 0xbf, 0x8a, 0x2d, 0x20,
-	0xf3, 0xab, 0x48, 0x6e, 0xb9, 0xa6, 0x00, 0x82,
-	0xb0, 0xc3, 0x8c, 0x24, 0x64, 0x09, 0x3c, 0x2e,
-	0x3f, 0xc8, 0x4e, 0x6e, 0x42, 0xc0, 0xb2, 0xfb,
-	0x3e, 0x6d, 0x4a, 0x83, 0x8e, 0x15, 0x0d, 0xc5,
-	0xaa, 0x4d, 0x2c, 0x75, 0x50, 0x13, 0x7f, 0x03,
-	0x5b, 0x5f, 0xc2, 0x2e, 0x1e, 0x13, 0x54, 0x74,
-	0xe8, 0x62, 0xed, 0x57, 0x48, 0xd5, 0x6c, 0x2e,
-	0x29, 0xb9, 0x00, 0xd1, 0x58, 0xcd, 0xca, 0xe1,
-	0xd2, 0x46, 0x2e, 0x6b, 0x67, 0x2d, 0x5a, 0xa2,
-	0x3b, 0xc2, 0x51, 0xe9, 0x3b, 0xe5, 0x21, 0xfe,
-	0x92, 0x16, 0x34, 0xba, 0x76, 0x02, 0x55, 0xbd,
-	0x05, 0x9d, 0xf8, 0x72, 0x48, 0xd7, 0x96, 0xda,
-	0x68, 0xba, 0x1f, 0xf7, 0x2c, 0xa9, 0xbd, 0x1d,
-	0xb7, 0xd0, 0x85, 0x6e, 0xcb, 0x67, 0x14, 0xc8,
-	0x43, 0x26, 0xab, 0x93, 0xab, 0x75, 0x44, 0xad,
-	0xd4, 0xd8, 0x5e, 0xca, 0x7b, 0x48, 0x97, 0xee,
-	0x4b, 0x4f, 0x49, 0x1d, 0x39, 0x0c, 0xa1, 0x34,
-	0xde, 0xd2, 0x93, 0x1d, 0xcf, 0x00, 0x79, 0xca,
-	0x4f, 0xbc, 0x6f, 0x49, 0x0a, 0x17, 0xe8, 0x8c,
-	0x74, 0xf2, 0xca, 0xaa, 0x1d, 0x53, 0xc6, 0x94,
-	0x1f, 0xe9, 0x45, 0x66, 0x06, 0xcf, 0x8f, 0xbb,
-	0xd5, 0x18, 0x79, 0x4e, 0xd2, 0x4e, 0x26, 0x85,
-	0xac, 0x25, 0x07, 0x6b, 0xad, 0xff, 0x84, 0x32,
-	0x50, 0xe0, 0x12, 0x57, 0x25, 0x47, 0xdf, 0x86,
-	0x30, 0x68, 0x66, 0x11, 0xf3, 0xc4, 0xc7, 0x83,
-	0x65, 0xb8, 0xc4, 0xc6, 0x98, 0x0c, 0x8c, 0x99,
-	0x84, 0x73, 0x8e, 0x63, 0x68, 0x21, 0xdf, 0x1b,
-	0xd6, 0x8f, 0x31, 0x4d, 0x8b, 0xf4, 0x4d, 0x71,
-	0x93, 0xca, 0xa3, 0x1c, 0x75, 0x10, 0x32, 0x02,
-	0xec, 0x72, 0x51, 0x56, 0x42, 0x91, 0x25, 0x73,
-	0x77, 0x9b, 0xd5, 0x6d, 0x83, 0x36, 0x20, 0x4d,
-	0x1c, 0xeb, 0x8f, 0x6b, 0xb4, 0xf3, 0xf8, 0x05,
-	0x6b, 0x8b, 0x8b, 0x20, 0xbe, 0x01, 0x00, 0x00
-#elif defined(TEST_ZSTD)
-	0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x88, 0xa5, 0x08,
-	0x00, 0x46, 0x97, 0x3a, 0x1a, 0x80, 0x37, 0xcd,
-	0x01, 0xc0, 0x8a, 0xec, 0xfe, 0x2d, 0xf2, 0xb9,
-	0x44, 0x6b, 0xb9, 0x24, 0x77, 0x56, 0x5a, 0x33,
-	0x17, 0x0b, 0x67, 0x83, 0x2e, 0x47, 0x07, 0x31,
-	0x00, 0x32, 0x00, 0x33, 0x00, 0xc5, 0x2c, 0x5a,
-	0x92, 0x93, 0x0f, 0x7b, 0xd1, 0x1d, 0x63, 0x2c,
-	0xc8, 0x99, 0x94, 0x77, 0x8f, 0x94, 0x38, 0x75,
-	0x80, 0x2f, 0xae, 0xc1, 0x3e, 0xd2, 0xcf, 0x49,
-	0x15, 0x25, 0x1a, 0x87, 0x93, 0xdd, 0xe8, 0x00,
-	0x6d, 0xaa, 0xf8, 0x54, 0x74, 0xe5, 0x48, 0x4d,
-	0xa6, 0xf3, 0x1a, 0xa3, 0x13, 0x08, 0xe5, 0x26,
-	0xdc, 0x73, 0xcc, 0x3e, 0xfd, 0x86, 0xa9, 0x52,
-	0xb2, 0x76, 0xc7, 0xc2, 0x0f, 0xe4, 0x84, 0x4b,
-	0x12, 0x61, 0x3a, 0x6b, 0x7a, 0x1e, 0x8a, 0x81,
-	0xa9, 0x9b, 0x11, 0x37, 0x25, 0x55, 0x73, 0x73,
-	0x71, 0xa0, 0x84, 0xca, 0xc3, 0x4b, 0xb5, 0xcc,
-	0x50, 0xa6, 0x46, 0xd7, 0xe8, 0x08, 0xaa, 0x04,
-	0x28, 0xb1, 0x8e, 0xea, 0xb4, 0x4a, 0x49, 0x2b,
-	0xd6, 0x0d, 0x59, 0x68, 0xda, 0x64, 0x29, 0x1f,
-	0x85, 0x53, 0x72, 0xf1, 0xc5, 0x88, 0x1a, 0x0b,
-	0x4f, 0x96, 0x43, 0xe0, 0x91, 0x89, 0xb9, 0xc0,
-	0xe8, 0x18, 0xd5, 0x6e, 0x94, 0xe8, 0x35, 0x66,
-	0x01, 0x94, 0x80, 0x95, 0x87, 0xe2, 0xc8, 0x19,
-	0x73, 0xa3, 0x01, 0x05, 0xc1, 0x64, 0x72, 0xc9,
-	0x6b, 0x6e, 0x55, 0x7c, 0x29, 0x67, 0x90, 0x93,
-	0x49, 0xeb, 0xe3, 0x85, 0xc2, 0xf5, 0x79, 0x68,
-	0x9d, 0x92, 0xc3, 0x32, 0x75, 0x80, 0x66, 0xf2,
-	0x43, 0xa7, 0xb0, 0xc3, 0x22, 0x3f, 0x39, 0x8a,
-	0x35, 0x5c, 0x63, 0x5c, 0xd1, 0x9e, 0x8a, 0xd2,
-	0x78, 0x3c, 0x12, 0x01, 0x25, 0x04, 0x0e, 0x08,
-	0x10, 0x88, 0xb6, 0x1b, 0xb7, 0x96, 0x35, 0xa8,
-	0x0d, 0x1e, 0xae, 0xac, 0x4a, 0x70, 0xa5, 0x31,
-	0xd0, 0x0c, 0x78, 0xbf, 0xdd, 0xc5, 0x24, 0x3e,
-	0xcb, 0x0a, 0x0a, 0x69, 0x40, 0xba, 0xb0, 0xc4,
-	0x2a, 0x9b, 0x1e, 0x0a, 0x51, 0xa6, 0x16, 0x98,
-	0x76
-#elif defined(TEST_ZSTD2)
-	0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x58, 0x75, 0x04,
-	0x00, 0xb2, 0x4c, 0x20, 0x17, 0xa0, 0x25, 0x69,
-	0x03, 0xf0, 0xb2, 0x37, 0xb1, 0x5e, 0xb9, 0x24,
-	0x56, 0x5b, 0x52, 0x22, 0x39, 0x01, 0x44, 0x2b,
-	0x03, 0x55, 0xe3, 0x47,	0x03, 0x12, 0x9a, 0xe1,
-	0xf0, 0x94, 0x0b, 0xe5, 0xe2, 0xba, 0x7e, 0xfe,
-	0x9c, 0xc7, 0x61, 0x43, 0xc8, 0xfa, 0xf0, 0x3a,
-	0xfa, 0x51, 0xaa, 0x50,	0xa6, 0x2d, 0x9a, 0x78,
-	0xce, 0x2f, 0x61, 0x20, 0x6c, 0x7e, 0x35, 0x60,
-	0xfb, 0xdd, 0x4c, 0x63, 0xfb, 0x95, 0x35, 0xc0,
-	0x82, 0x59, 0xc2, 0xc9,	0x78, 0x6e, 0x30, 0xe6,
-	0xd2, 0x72, 0x15, 0x14, 0x18, 0x62, 0x5d, 0xeb,
-	0x2d, 0x9d, 0x3e, 0xee, 0x2e, 0x58, 0x58, 0xe9,
-	0x40, 0x68, 0xb9, 0x2f,	0x23, 0x99, 0x2a, 0x4d,
-	0xe8, 0x49, 0x79, 0x70, 0x1f, 0xf9, 0xe2, 0x34,
-	0x2e, 0xab, 0xa5, 0xa3, 0xf2, 0x70, 0x98, 0xd0,
-	0xb2, 0xb1, 0x3e, 0x5d,	0x90, 0x20, 0xd9, 0x36,
-	0x8b, 0xdb, 0xaa, 0x20, 0x40, 0x03, 0x14, 0x06,
-	0x03, 0x16, 0x2a, 0x9d, 0x31, 0xbd, 0x28, 0x3b,
-	0x0c, 0xac, 0x41,
-	0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x58, 0xbd, 0x04,
-	0x00, 0x62, 0xcd, 0x22, 0x19, 0xa0, 0x25, 0x69,
-	0x03, 0x60, 0x72, 0xc9, 0x36, 0xda, 0xd2, 0x8b,
-	0xfc, 0xbf, 0x25, 0x42, 0xa9, 0x82, 0x38, 0x70,
-	0x1a, 0x2e, 0x54, 0x95,	0x33, 0x02, 0x03, 0x51,
-	0x36, 0x51, 0x80, 0xcc, 0x7a, 0x6e, 0x52, 0x2e,
-	0x75, 0x64, 0x2d, 0x33, 0x2c, 0xd6, 0xdb, 0xfc,
-	0x39, 0x31, 0xd5, 0xa8,	0xa2, 0x40, 0xd7, 0x12,
-	0x4c, 0xc6, 0x76, 0xdc, 0x1e, 0x0f, 0xf4, 0x4e,
-	0x0a, 0xd3, 0x0c, 0x87, 0x67, 0x25, 0x25, 0x52,
-	0x66, 0x87, 0x95, 0xc6,	0x69, 0x0c, 0xb4, 0x5e,
-	0x1d, 0xe7, 0x5e, 0xcd, 0x47, 0x41, 0x80, 0x89,
-	0x5c, 0xa5, 0x4a, 0x32, 0x26, 0xb3, 0x3d, 0x2b,
-	0xd5, 0xc0, 0x16, 0xde,	0xfb, 0x65, 0xcd, 0x6a,
-	0x0c, 0x3f, 0xe7, 0xd6, 0xb2, 0x17, 0x7c, 0x25,
-	0x35, 0x6b, 0x58, 0xf0, 0x95, 0xb5, 0xf2, 0xe4,
-	0x4e, 0xf0, 0x34, 0x4f,	0x5f, 0x39, 0xd1, 0x90,
-	0xf8, 0xb9, 0x59, 0xbe, 0x2e, 0xf9, 0xd4, 0x02,
-	0x98, 0x50, 0x5a, 0xc2, 0xcf, 0xe1, 0x08, 0x02,
-	0x00, 0x0f, 0x1e, 0x44,	0x40, 0x79, 0x50, 0x67,
-	0x3d, 0xd3, 0x35, 0x8f
-#endif
-};
-
-static const char orig[] =
-"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\n"
-"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\n"
-"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n"
-"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\n"
-"cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\n"
-"proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n";
-
-#if defined(TEST_BZIP2) || defined(TEST_BZIP22)
-#define COMP_NAME "bzip2"
-#define COMP_ID FSTREAM_COMPRESSOR_BZIP2
-#elif defined(TEST_XZ) || defined(TEST_XZ2)
-#define COMP_NAME "xz"
-#define COMP_ID FSTREAM_COMPRESSOR_XZ
-#elif defined(TEST_GZIP)
-#define COMP_NAME "gzip"
-#define COMP_ID FSTREAM_COMPRESSOR_GZIP
-#elif defined(TEST_ZSTD) || defined(TEST_ZSTD2)
-#define COMP_NAME "zstd"
-#define COMP_ID FSTREAM_COMPRESSOR_ZSTD
-#endif
-
-static void destroy_noop(sqfs_object_t *obj)
-{
-	(void)obj;
-}
-
-static int precache_noop(istream_t *strm)
-{
-	(void)strm;
-	return 0;
-}
-
-static const char *get_filename(istream_t *strm)
-{
-	(void)strm;
-	return "memstream";
-}
-
-static istream_t memstream = {
-	.base = {
-		.destroy = destroy_noop,
-	},
-
-	.buffer_used = sizeof(data_in) / sizeof(data_in[0]),
-	.buffer_offset = 0,
-	.eof = true,
-	.buffer = data_in,
-
-	.precache = precache_noop,
-	.get_filename = get_filename,
-};
-
-int main(int argc, char **argv)
-{
-	char buffer[2 * (sizeof(orig) / sizeof(orig[0]))];
-	const char *name;
-	istream_t *xfrm;
-	size_t orig_sz;
-	int ret;
-	(void)argc; (void)argv;
-
-	/* XXX: null terminator not included in the compressed blob */
-	orig_sz = (sizeof(orig) / sizeof(orig[0])) - 1;
-
-	/* generic API test */
-	TEST_ASSERT(fstream_compressor_exists(COMP_ID));
-
-	name = fstream_compressor_name_from_id(COMP_ID);
-	TEST_STR_EQUAL(name, COMP_NAME);
-
-	ret = fstream_compressor_id_from_name(name);
-	TEST_EQUAL_I(ret, COMP_ID);
-
-	ret = istream_detect_compressor(&memstream, NULL);
-	TEST_EQUAL_I(ret, COMP_ID);
-
-	/* decoder test */
-	xfrm = istream_compressor_create(&memstream, COMP_ID);
-	TEST_NOT_NULL(xfrm);
-
-	name = istream_get_filename(xfrm);
-	TEST_STR_EQUAL(name, "memstream");
-
-	ret = istream_read(xfrm, buffer, sizeof(buffer));
-	TEST_ASSERT(ret > 0);
-	TEST_EQUAL_UI((size_t)ret, orig_sz);
-
-	ret = memcmp(buffer, orig, ret);
-	TEST_EQUAL_I(ret, 0);
-
-	ret = istream_read(xfrm, buffer, sizeof(buffer));
-	TEST_EQUAL_I(ret, 0);
-
-	/* cleanup */
-	sqfs_destroy(xfrm);
-	return EXIT_SUCCESS;
-}
diff --git a/tests/libfstree/Makemodule.am b/tests/libfstree/Makemodule.am
index 594ef7e..6b45ece 100644
--- a/tests/libfstree/Makemodule.am
+++ b/tests/libfstree/Makemodule.am
@@ -28,16 +28,16 @@ test_get_path_LDADD = libfstree.a libcompat.a
 
 test_fstree_sort_SOURCES = tests/libfstree/fstree_sort.c tests/test.h
 test_fstree_sort_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/lib/fstree
-test_fstree_sort_LDADD = libfstree.a libfstream.a libcompat.a
+test_fstree_sort_LDADD = libfstree.a libio.a libcompat.a
 
 test_fstree_from_file_SOURCES = tests/libfstree/fstree_from_file.c tests/test.h
 test_fstree_from_file_CPPFLAGS = $(AM_CPPFLAGS)
 test_fstree_from_file_CPPFLAGS += -DTESTPATH=$(FSTDATADIR)/fstree1.txt
-test_fstree_from_file_LDADD = libfstree.a libfstream.a libcompat.a
+test_fstree_from_file_LDADD = libfstree.a libio.a libcompat.a
 
 test_fstree_glob1_SOURCES = tests/libfstree/fstree_glob1.c tests/test.h
 test_fstree_glob1_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(FSTDATADIR)
-test_fstree_glob1_LDADD = libfstree.a libfstream.a libcompat.a
+test_fstree_glob1_LDADD = libfstree.a libio.a libcompat.a
 
 test_fstree_from_dir_SOURCES = tests/libfstree/fstree_from_dir.c tests/test.h
 test_fstree_from_dir_CPPFLAGS = $(AM_CPPFLAGS)
@@ -46,7 +46,7 @@ test_fstree_from_dir_LDADD = libfstree.a libcompat.a
 
 test_fstree_init_SOURCES = tests/libfstree/fstree_init.c tests/test.h
 test_fstree_init_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/lib/fstree
-test_fstree_init_LDADD = libfstree.a libfstream.a libcompat.a
+test_fstree_init_LDADD = libfstree.a libio.a libcompat.a
 
 test_filename_sane_SOURCES = tests/libfstree/filename_sane.c
 test_filename_sane_SOURCES += lib/fstree/filename_sane.c
@@ -63,10 +63,10 @@ test_fstree_epoch_SOURCES += lib/fstree/source_date_epoch.c
 test_fstree_epoch_LDADD = libcompat.a
 
 test_sort_file_SOURCES = tests/libfstree/sort_file.c
-test_sort_file_LDADD = libfstree.a libfstream.a libcompat.a
+test_sort_file_LDADD = libfstree.a libio.a libcompat.a
 
 fstree_fuzz_SOURCES = tests/libfstree/fstree_fuzz.c
-fstree_fuzz_LDADD = libfstree.a libfstream.a libcompat.a
+fstree_fuzz_LDADD = libfstree.a libio.a libcompat.a
 
 FSTREE_TESTS = \
 	test_canonicalize_name test_mknode_simple test_mknode_slink \
diff --git a/tests/libio/Makemodule.am b/tests/libio/Makemodule.am
new file mode 100644
index 0000000..86f2e3a
--- /dev/null
+++ b/tests/libio/Makemodule.am
@@ -0,0 +1,68 @@
+test_get_line_SOURCES = tests/libio/get_line.c tests/test.h
+test_get_line_LDADD = libio.a libcompat.a
+test_get_line_CPPFLAGS = $(AM_CPPFLAGS)
+test_get_line_CPPFLAGS += -DTESTFILE=$(top_srcdir)/tests/libio/get_line.txt
+
+test_xfrm_bzip2_SOURCES = tests/libio/uncompress.c tests/test.h
+test_xfrm_bzip2_LDADD = libio.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
+test_xfrm_bzip2_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
+test_xfrm_bzip2_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_BZIP2=1
+
+test_xfrm_bzip22_SOURCES = tests/libio/uncompress.c tests/test.h
+test_xfrm_bzip22_LDADD = libio.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
+test_xfrm_bzip22_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
+test_xfrm_bzip22_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_BZIP22=1
+
+test_xfrm_xz_SOURCES = tests/libio/uncompress.c tests/test.h
+test_xfrm_xz_LDADD = libio.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
+test_xfrm_xz_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
+test_xfrm_xz_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_XZ=1
+
+test_xfrm_xz2_SOURCES = tests/libio/uncompress.c tests/test.h
+test_xfrm_xz2_LDADD = libio.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
+test_xfrm_xz2_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
+test_xfrm_xz2_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_XZ2=1
+
+test_xfrm_gzip_SOURCES = tests/libio/uncompress.c tests/test.h
+test_xfrm_gzip_LDADD = libio.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
+test_xfrm_gzip_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
+test_xfrm_gzip_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_GZIP=1
+
+test_xfrm_zstd_SOURCES = tests/libio/uncompress.c tests/test.h
+test_xfrm_zstd_LDADD = libio.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
+test_xfrm_zstd_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
+test_xfrm_zstd_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_ZSTD=1
+
+test_xfrm_zstd2_SOURCES = tests/libio/uncompress.c tests/test.h
+test_xfrm_zstd2_LDADD = libio.a libcompat.a $(BZIP2_LIBS) $(ZLIB_LIBS)
+test_xfrm_zstd2_LDADD += $(XZ_LIBS) $(ZSTD_LIBS)
+test_xfrm_zstd2_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_ZSTD2=1
+
+if BUILD_TOOLS
+check_PROGRAMS += test_get_line
+TESTS += test_get_line
+
+if WITH_BZIP2
+check_PROGRAMS += test_xfrm_bzip2 test_xfrm_bzip22
+TESTS += test_xfrm_bzip2 test_xfrm_bzip22
+endif
+
+if WITH_XZ
+check_PROGRAMS += test_xfrm_xz test_xfrm_xz2
+TESTS += test_xfrm_xz test_xfrm_xz2
+endif
+
+if WITH_GZIP
+check_PROGRAMS += test_xfrm_gzip
+TESTS += test_xfrm_gzip
+endif
+
+if WITH_ZSTD
+if HAVE_ZSTD_STREAM
+check_PROGRAMS += test_xfrm_zstd test_xfrm_zstd2
+TESTS += test_xfrm_zstd test_xfrm_zstd2
+endif
+endif
+endif
+
+EXTRA_DIST += $(top_srcdir)/tests/libio/get_line.txt
diff --git a/tests/libio/get_line.c b/tests/libio/get_line.c
new file mode 100644
index 0000000..66fced5
--- /dev/null
+++ b/tests/libio/get_line.c
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * get_line.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "io/file.h"
+#include "../test.h"
+
+typedef struct {
+	size_t line_num;
+	const char *str;
+} line_t;
+
+static void run_test_case(const line_t *lines, size_t count,
+			  int flags)
+{
+	size_t i, line_num, old_line_num;
+	istream_t *fp;
+	char *line;
+	int ret;
+
+	fp = istream_open_file(STRVALUE(TESTFILE));
+	TEST_NOT_NULL(fp);
+
+	line_num = 1;
+	line = NULL;
+
+	for (i = 0; i < count; ++i) {
+		old_line_num = line_num;
+		ret = istream_get_line(fp, &line, &line_num, flags);
+
+		TEST_ASSERT(line_num >= old_line_num);
+		TEST_EQUAL_I(ret, 0);
+		TEST_NOT_NULL(line);
+
+		TEST_EQUAL_UI(line_num, lines[i].line_num);
+		TEST_STR_EQUAL(line, lines[i].str);
+
+		free(line);
+		line = NULL;
+		line_num += 1;
+	}
+
+	ret = istream_get_line(fp, &line, &line_num, flags);
+	TEST_ASSERT(ret > 0);
+
+	sqfs_destroy(fp);
+}
+
+static const line_t lines_raw[] = {
+	{ 1, "" },
+	{ 2, "The quick" },
+	{ 3, "  " },
+	{ 4, "  brown fox  " },
+	{ 5, "" },
+	{ 6, "jumps over" },
+	{ 7, "the" },
+	{ 8, "lazy" },
+	{ 9, "" },
+	{ 10, "dog" },
+	{ 11, "" },
+};
+
+static const line_t lines_ltrim[] = {
+	{ 1, "" },
+	{ 2, "The quick" },
+	{ 3, "" },
+	{ 4, "brown fox  " },
+	{ 5, "" },
+	{ 6, "jumps over" },
+	{ 7, "the" },
+	{ 8, "lazy" },
+	{ 9, "" },
+	{ 10, "dog" },
+	{ 11, "" },
+};
+
+static const line_t lines_rtrim[] = {
+	{ 1, "" },
+	{ 2, "The quick" },
+	{ 3, "" },
+	{ 4, "  brown fox" },
+	{ 5, "" },
+	{ 6, "jumps over" },
+	{ 7, "the" },
+	{ 8, "lazy" },
+	{ 9, "" },
+	{ 10, "dog" },
+	{ 11, "" },
+};
+
+static const line_t lines_trim[] = {
+	{ 1, "" },
+	{ 2, "The quick" },
+	{ 3, "" },
+	{ 4, "brown fox" },
+	{ 5, "" },
+	{ 6, "jumps over" },
+	{ 7, "the" },
+	{ 8, "lazy" },
+	{ 9, "" },
+	{ 10, "dog" },
+	{ 11, "" },
+};
+
+static const line_t lines_no_empty[] = {
+	{ 2, "The quick" },
+	{ 3, "  " },
+	{ 4, "  brown fox  " },
+	{ 6, "jumps over" },
+	{ 7, "the" },
+	{ 8, "lazy" },
+	{ 10, "dog" },
+};
+
+static const line_t lines_no_empty_ltrim[] = {
+	{ 2, "The quick" },
+	{ 4, "brown fox  " },
+	{ 6, "jumps over" },
+	{ 7, "the" },
+	{ 8, "lazy" },
+	{ 10, "dog" },
+};
+
+static const line_t lines_no_empty_rtrim[] = {
+	{ 2, "The quick" },
+	{ 4, "  brown fox" },
+	{ 6, "jumps over" },
+	{ 7, "the" },
+	{ 8, "lazy" },
+	{ 10, "dog" },
+};
+
+static const line_t lines_no_empty_trim[] = {
+	{ 2, "The quick" },
+	{ 4, "brown fox" },
+	{ 6, "jumps over" },
+	{ 7, "the" },
+	{ 8, "lazy" },
+	{ 10, "dog" },
+};
+
+int main(int argc, char **argv)
+{
+	(void)argc; (void)argv;
+
+	run_test_case(lines_raw, 11, 0);
+	run_test_case(lines_ltrim, 11, ISTREAM_LINE_LTRIM);
+	run_test_case(lines_rtrim, 11, ISTREAM_LINE_RTRIM);
+	run_test_case(lines_trim, 11,
+		      ISTREAM_LINE_LTRIM | ISTREAM_LINE_RTRIM);
+
+	run_test_case(lines_no_empty, 7, ISTREAM_LINE_SKIP_EMPTY);
+	run_test_case(lines_no_empty_ltrim, 6,
+		      ISTREAM_LINE_SKIP_EMPTY | ISTREAM_LINE_LTRIM);
+	run_test_case(lines_no_empty_rtrim, 6,
+		      ISTREAM_LINE_SKIP_EMPTY | ISTREAM_LINE_RTRIM);
+	run_test_case(lines_no_empty_trim, 6,
+		      ISTREAM_LINE_SKIP_EMPTY | ISTREAM_LINE_LTRIM |
+		      ISTREAM_LINE_RTRIM);
+
+	return EXIT_SUCCESS;
+}
diff --git a/tests/libio/get_line.txt b/tests/libio/get_line.txt
new file mode 100644
index 0000000..a1994f0
--- /dev/null
+++ b/tests/libio/get_line.txt
@@ -0,0 +1,11 @@
+
+The quick
+  
+  brown fox  
+
+jumps over
+the
+lazy
+
+dog
+
diff --git a/tests/libio/uncompress.c b/tests/libio/uncompress.c
new file mode 100644
index 0000000..9476877
--- /dev/null
+++ b/tests/libio/uncompress.c
@@ -0,0 +1,432 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * uncompress.c
+ *
+ * Copyright (C) 2021 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "io/istream.h"
+#include "io/xfrm.h"
+#include "../test.h"
+
+static sqfs_u8 data_in[] = {
+#if defined(TEST_BZIP2)
+	0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26,
+	0x53, 0x59, 0x05, 0x24, 0x28, 0x04, 0x00, 0x00,
+	0x27, 0xd7, 0x80, 0x00, 0x10, 0x40, 0x05, 0x06,
+	0x04, 0x02, 0x00, 0x3f, 0xe7, 0xff, 0x40, 0x30,
+	0x01, 0x2d, 0x23, 0x62, 0x26, 0x05, 0x3d, 0x03,
+	0x54, 0xfd, 0x53, 0x4c, 0x86, 0x9e, 0x90, 0x6a,
+	0x9e, 0x9e, 0x85, 0x3c, 0xa0, 0x00, 0x00, 0x1a,
+	0x9e, 0x41, 0x13, 0x13, 0x28, 0x69, 0x03, 0xd4,
+	0x0f, 0x1c, 0x70, 0xd0, 0xb4, 0xe3, 0xe4, 0x75,
+	0x4e, 0x8b, 0x67, 0x43, 0x7b, 0x38, 0x27, 0x77,
+	0xe4, 0xc1, 0x98, 0x3a, 0x2d, 0x3a, 0xe4, 0x44,
+	0x98, 0xdc, 0x49, 0x8b, 0x22, 0x48, 0xfc, 0xc8,
+	0xe7, 0x57, 0x05, 0x3c, 0x5a, 0xee, 0x5a, 0x84,
+	0xcd, 0x7c, 0x8f, 0x26, 0x6b, 0x6e, 0xf7, 0xb5,
+	0x49, 0x1f, 0x79, 0x42, 0x5d, 0x09, 0x8c, 0xc6,
+	0xde, 0x0c, 0x0d, 0xb1, 0x46, 0xb4, 0xee, 0xd9,
+	0x8f, 0x33, 0x37, 0x04, 0xa9, 0x05, 0x49, 0xe3,
+	0x04, 0x16, 0x62, 0x36, 0x3a, 0x01, 0xda, 0xd4,
+	0xc8, 0x8a, 0x32, 0x02, 0x1f, 0x62, 0x4b, 0xa4,
+	0x49, 0x59, 0xda, 0x50, 0x85, 0x69, 0x35, 0x21,
+	0x10, 0xc6, 0x8a, 0x3c, 0x44, 0x95, 0xb0, 0xbc,
+	0xc5, 0x6b, 0xea, 0xfb, 0x40, 0xbd, 0x14, 0x01,
+	0x6a, 0xfa, 0xcd, 0x67, 0xd8, 0x2d, 0x93, 0x8b,
+	0xda, 0x44, 0x1b, 0xe9, 0x5a, 0x87, 0x60, 0xb0,
+	0xe0, 0x73, 0xd1, 0x01, 0x3a, 0x66, 0x05, 0xcc,
+	0x34, 0xa0, 0x63, 0x8d, 0x35, 0x5e, 0xa0, 0x9f,
+	0x05, 0x89, 0x15, 0x51, 0x48, 0x16, 0x0c, 0x61,
+	0xf4, 0x30, 0xb8, 0x07, 0x29, 0xc0, 0xf5, 0x1a,
+	0xe1, 0x0d, 0x6c, 0xfe, 0x91, 0xda, 0x13, 0x2f,
+	0x8e, 0x5b, 0x1c, 0xfc, 0xb3, 0xb2, 0x30, 0x9d,
+	0xf6, 0x09, 0x30, 0x55, 0x30, 0x67, 0xc2, 0x87,
+	0xe9, 0x9a, 0xd4, 0x1d, 0x66, 0x11, 0x54, 0x89,
+	0x21, 0xe1, 0x55, 0x84, 0xbf, 0xa6, 0x11, 0xa4,
+	0xb8, 0x40, 0xed, 0x42, 0x20, 0xb9, 0xb7, 0x26,
+	0x31, 0x14, 0x4f, 0x86, 0xdc, 0x50, 0x34, 0x38,
+	0x8b, 0x57, 0x77, 0x21, 0xf6, 0x89, 0xbd, 0xc5,
+	0x65, 0xc3, 0x23, 0x45, 0xec, 0x7f, 0x8b, 0xb9,
+	0x22, 0x9c, 0x28, 0x48, 0x02, 0x92, 0x14, 0x02,
+	0x00,
+#elif defined(TEST_BZIP22)
+	0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26,
+	0x53, 0x59, 0x5d, 0x09, 0x24, 0x1d, 0x00, 0x00,
+	0x13, 0xd7, 0x80, 0x00, 0x10, 0x40, 0x05, 0x00,
+	0x04, 0x02, 0x00, 0x3e, 0xa7, 0xff, 0x40, 0x30,
+	0x00, 0xac, 0x43, 0x54, 0xf5, 0x36, 0x4c, 0xa7,
+	0xa8, 0xd3, 0x6a, 0x60, 0x81, 0x40, 0x00, 0xd0,
+	0x32, 0x64, 0x0d, 0x53, 0xda, 0x02, 0x09, 0xa2,
+	0x68, 0x34, 0xd1, 0x27, 0x4a, 0xdd, 0xf2, 0x0a,
+	0x73, 0x43, 0xf9, 0xa2, 0x51, 0x85, 0x76, 0x45,
+	0x9a, 0x68, 0x3a, 0xe7, 0x0d, 0xc0, 0x21, 0x4a,
+	0xc4, 0xf9, 0xf7, 0x40, 0xc3, 0x10, 0xb2, 0x9b,
+	0x58, 0x56, 0x71, 0x50, 0x2f, 0xa4, 0xc5, 0x61,
+	0x19, 0xf6, 0x59, 0x06, 0x82, 0x03, 0x7f, 0xeb,
+	0xd2, 0x61, 0x88, 0xcd, 0xe8, 0xf7, 0xe8, 0x87,
+	0x59, 0x9d, 0xe1, 0xf8, 0x19, 0x6e, 0xad, 0x77,
+	0xbf, 0x34, 0x17, 0x21, 0x6b, 0x91, 0xc9, 0x52,
+	0xd0, 0x81, 0x1e, 0xb5, 0x0b, 0xee, 0x42, 0x84,
+	0x80, 0xd5, 0xa1, 0x8a, 0x04, 0x18, 0x4d, 0xf3,
+	0xda, 0x7e, 0x3c, 0x40, 0xa4, 0xdb, 0xe5, 0xf0,
+	0x37, 0x40, 0x3a, 0x7d, 0xa7, 0x45, 0x21, 0xf2,
+	0x5a, 0x7b, 0x59, 0x56, 0x16, 0xd5, 0xac, 0x9f,
+	0x60, 0x85, 0x0e, 0xf5, 0x73, 0xd9, 0x47, 0xe2,
+	0xee, 0x48, 0xa7, 0x0a, 0x12, 0x0b, 0xa1, 0x24,
+	0x83, 0xa0,
+	0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26,
+	0x53, 0x59, 0x2c, 0x24, 0x39, 0xa0, 0x00, 0x00,
+	0x1f, 0x55, 0x80, 0x00, 0x10, 0x40, 0x05, 0x06,
+	0x00, 0x3f, 0xe7, 0xff, 0x40, 0x30, 0x00, 0xb5,
+	0x91, 0x13, 0x4f, 0x54, 0x7a, 0x6a, 0x6d, 0x4d,
+	0xa2, 0x68, 0x0c, 0x84, 0x53, 0xf5, 0x30, 0x89,
+	0xa3, 0xd4, 0x0d, 0x0f, 0x49, 0xa0, 0xd4, 0xf4,
+	0xd1, 0x53, 0xf4, 0x93, 0x69, 0x3c, 0x81, 0x1a,
+	0x65, 0x53, 0x90, 0x51, 0x07, 0x2a, 0xad, 0x8f,
+	0x63, 0xba, 0x25, 0xc2, 0x0c, 0x8b, 0xb9, 0x95,
+	0x15, 0xd8, 0xda, 0x61, 0x5c, 0xa9, 0xe4, 0x0b,
+	0x21, 0xc9, 0x97, 0x57, 0x01, 0x28, 0x9b, 0xfb,
+	0x94, 0xb9, 0x48, 0xa3, 0x0a, 0xc6, 0x1c, 0x54,
+	0x98, 0x9a, 0x39, 0xc3, 0x87, 0x90, 0x33, 0x58,
+	0x2d, 0x3e, 0x16, 0xb1, 0xae, 0x26, 0x89, 0x75,
+	0xf5, 0x77, 0xa5, 0x8e, 0x5b, 0x8c, 0x8a, 0x39,
+	0xbd, 0x75, 0x21, 0x9d, 0x99, 0x18, 0x4a, 0x91,
+	0xab, 0xbc, 0x08, 0x87, 0xa4, 0xf1, 0x81, 0xb5,
+	0xb4, 0xb0, 0xfe, 0x6b, 0x9f, 0xbe, 0x19, 0x82,
+	0xd1, 0x50, 0xe1, 0x5e, 0x13, 0xb5, 0xc6, 0x2c,
+	0xa4, 0x82, 0xf2, 0x5c, 0xc3, 0x20, 0x41, 0x13,
+	0x56, 0x63, 0x3d, 0xec, 0x71, 0x2a, 0xbf, 0x2c,
+	0x60, 0x2f, 0x7a, 0x4d, 0xcb, 0x3f, 0x8b, 0xb9,
+	0x22, 0x9c, 0x28, 0x48, 0x16, 0x12, 0x1c, 0xd0,
+	0x00,
+#elif defined(TEST_XZ)
+	0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00,
+	0xff, 0x12, 0xd9, 0x41, 0x02, 0x00, 0x21, 0x01,
+	0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+	0xe0, 0x01, 0xbd, 0x01, 0x43, 0x5d, 0x00, 0x26,
+	0x1b, 0xca, 0x46, 0x67, 0x5a, 0xf2, 0x77, 0xb8,
+	0x7d, 0x86, 0xd8, 0x41, 0xdb, 0x05, 0x35, 0xcd,
+	0x83, 0xa5, 0x7c, 0x12, 0xa5, 0x05, 0xdb, 0x90,
+	0xbd, 0x2f, 0x14, 0xd3, 0x71, 0x72, 0x96, 0xa8,
+	0x8a, 0x7d, 0x84, 0x56, 0x71, 0x8d, 0x6a, 0x22,
+	0x98, 0xab, 0x9e, 0x3d, 0xc3, 0x55, 0xef, 0xcc,
+	0xa5, 0xc3, 0xdd, 0x5b, 0x8e, 0xbf, 0x03, 0x81,
+	0x21, 0x40, 0xd6, 0x26, 0x91, 0x02, 0x45, 0x4e,
+	0x20, 0x91, 0xcf, 0x8c, 0x51, 0x22, 0x02, 0x70,
+	0xba, 0x05, 0x6b, 0x83, 0xef, 0x3f, 0x8e, 0x09,
+	0xef, 0x88, 0xf5, 0x37, 0x1b, 0x89, 0x8d, 0xff,
+	0x1e, 0xee, 0xe8, 0xb0, 0xac, 0xf2, 0x6e, 0xd4,
+	0x3e, 0x25, 0xaf, 0xa0, 0x6d, 0x2e, 0xc0, 0x7f,
+	0xb5, 0xa0, 0xcb, 0x90, 0x1f, 0x08, 0x1a, 0xe2,
+	0x90, 0x20, 0x19, 0x71, 0x0c, 0xe8, 0x3f, 0xe5,
+	0x39, 0xeb, 0x9a, 0x62, 0x4f, 0x06, 0xda, 0x3c,
+	0x32, 0x59, 0xcc, 0x83, 0xe3, 0x83, 0x0f, 0x38,
+	0x7d, 0x43, 0x37, 0x6c, 0x0b, 0x05, 0x65, 0x98,
+	0x25, 0xdb, 0xf2, 0xc0, 0x2d, 0x39, 0x36, 0x5d,
+	0xd4, 0xb6, 0xc2, 0x79, 0x73, 0x3e, 0xc2, 0x6e,
+	0x54, 0xec, 0x78, 0x2b, 0x5d, 0xf1, 0xd1, 0xb4,
+	0xb3, 0xcd, 0xf3, 0x89, 0xf5, 0x81, 0x3e, 0x2c,
+	0x65, 0xd6, 0x73, 0xd3, 0x1b, 0x20, 0x68, 0x0c,
+	0x93, 0xd4, 0xfc, 0x9f, 0xf8, 0xa7, 0xd4, 0xfa,
+	0x3a, 0xb1, 0x13, 0x93, 0x4b, 0xec, 0x78, 0x7d,
+	0x5c, 0x81, 0x80, 0xe5, 0x14, 0x78, 0xfe, 0x7e,
+	0xde, 0xf7, 0xad, 0x9e, 0x84, 0xba, 0xf1, 0x00,
+	0xe9, 0xbd, 0x2c, 0xf4, 0x70, 0x7d, 0xbe, 0x29,
+	0xb9, 0xf0, 0x10, 0xb9, 0x01, 0xf1, 0x76, 0x8a,
+	0x5a, 0xad, 0x02, 0xa1, 0x32, 0xc8, 0x53, 0x59,
+	0x11, 0x4c, 0xe2, 0x98, 0x34, 0xd9, 0x23, 0x51,
+	0x4a, 0x40, 0x2b, 0x87, 0x41, 0xdd, 0x50, 0xcd,
+	0x98, 0x1e, 0x29, 0x86, 0x23, 0x93, 0x3e, 0x9b,
+	0x6b, 0x16, 0xa1, 0x40, 0xac, 0xe7, 0x40, 0xfe,
+	0xa9, 0x87, 0x48, 0x25, 0x52, 0x02, 0x8b, 0xc4,
+	0x68, 0x08, 0x5a, 0x62, 0xc1, 0xb2, 0x07, 0x3b,
+	0x26, 0x1e, 0x59, 0x5c, 0x47, 0x24, 0xae, 0x8e,
+	0xe5, 0xf7, 0xe6, 0x4b, 0x13, 0xb4, 0x6d, 0x46,
+	0x65, 0x4f, 0xd0, 0x48, 0xcc, 0x51, 0x4b, 0x80,
+	0xcb, 0xf1, 0xd4, 0x6c, 0x45, 0x98, 0x92, 0x47,
+	0xeb, 0x60, 0x00, 0x00, 0x00, 0x01, 0xd7, 0x02,
+	0xbe, 0x03, 0x00, 0x00, 0xda, 0x2c, 0x45, 0x49,
+	0xa8, 0x00, 0x0a, 0xfc, 0x02, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x59, 0x5a
+#elif defined(TEST_XZ2)
+	0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x04,
+	0xe6, 0xd6, 0xb4, 0x46, 0x02, 0x00, 0x21, 0x01,
+	0x16, 0x00, 0x00, 0x00, 0x74, 0x2f, 0xe5, 0xa3,
+	0xe0, 0x00, 0xdc, 0x00, 0xb3, 0x5d, 0x00, 0x26,
+	0x1b, 0xca, 0x46, 0x67, 0x5a, 0xf2, 0x77, 0xb8,
+	0x7d, 0x86, 0xd8, 0x41, 0xdb, 0x05, 0x35, 0xcd,
+	0x83, 0xa5, 0x7c, 0x12, 0xa5, 0x05, 0xdb, 0x90,
+	0xbd, 0x2f, 0x14, 0xd3, 0x71, 0x72, 0x96, 0xa8,
+	0x8a, 0x7d, 0x84, 0x56, 0x71, 0x8d, 0x6a, 0x22,
+	0x98, 0xab, 0x9e, 0x3d, 0xc3, 0x55, 0xef, 0xcc,
+	0xa5, 0xc3, 0xdd, 0x5b, 0x8e, 0xbf, 0x03, 0x81,
+	0x21, 0x40, 0xd6, 0x26, 0x91, 0x02, 0x45, 0x4e,
+	0x20, 0x91, 0xcf, 0x8c, 0x51, 0x22, 0x02, 0x70,
+	0xba, 0x05, 0x6b, 0x83, 0xef, 0x3f, 0x8e, 0x09,
+	0xef, 0x88, 0xf5, 0x37, 0x1b, 0x89, 0x8d, 0xff,
+	0x1e, 0xee, 0xe8, 0xb0, 0xac, 0xf2, 0x6e, 0xd4,
+	0x3e, 0x25, 0xaf, 0xa0, 0x6d, 0x2e, 0xc0, 0x7f,
+	0xb5, 0xa0, 0xcb, 0x90, 0x1f, 0x08, 0x1a, 0xe2,
+	0x90, 0x20, 0x19, 0x71, 0x0c, 0xe8, 0x3f, 0xe5,
+	0x39, 0xeb, 0x9a, 0x62, 0x4f, 0x06, 0xda, 0x3c,
+	0x32, 0x59, 0xcc, 0x83, 0xe3, 0x83, 0x0f, 0x38,
+	0x7d, 0x43, 0x37, 0x6c, 0x0b, 0x05, 0x65, 0x98,
+	0x25, 0xdb, 0xf2, 0xc0, 0x2d, 0x39, 0x36, 0x5d,
+	0xd4, 0xb6, 0xc2, 0x79, 0x73, 0x3e, 0xc2, 0x6e,
+	0x54, 0xec, 0x78, 0x2b, 0x5d, 0xf1, 0xd1, 0xb4,
+	0xb3, 0xcd, 0xf3, 0x89, 0xf5, 0x80, 0x79, 0x46,
+	0xc0, 0x00, 0x00, 0x00, 0xc4, 0xf5, 0x1d, 0x08,
+	0xf0, 0x34, 0x3a, 0x59, 0x00, 0x01, 0xcf, 0x01,
+	0xdd, 0x01, 0x00, 0x00, 0x7f, 0x5a, 0x77, 0xcb,
+	0xb1, 0xc4, 0x67, 0xfb, 0x02, 0x00, 0x00, 0x00,
+	0x00, 0x04, 0x59, 0x5a,
+	0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x04,
+	0xe6, 0xd6, 0xb4, 0x46, 0x02, 0x00, 0x21, 0x01,
+	0x16, 0x00, 0x00, 0x00, 0x74, 0x2f, 0xe5, 0xa3,
+	0xe0, 0x00, 0xe0, 0x00, 0xb7, 0x5d, 0x00, 0x31,
+	0x9b, 0xca, 0x19, 0xc5, 0x54, 0xec, 0xb6, 0x54,
+	0xe7, 0xb1, 0x7d, 0xc4, 0x57, 0x9e, 0x6c, 0x89,
+	0xad, 0x4a, 0x6d, 0x16, 0xd8, 0x3c, 0x05, 0x94,
+	0x10, 0x16, 0x99, 0x38, 0x21, 0xa3, 0xb9, 0xc5,
+	0x80, 0xff, 0xfc, 0xee, 0xd4, 0xd5, 0x3f, 0xdd,
+	0x8c, 0xd7, 0x3d, 0x8f, 0x76, 0xec, 0x96, 0x9d,
+	0x20, 0xac, 0xcb, 0x18, 0xf5, 0xb2, 0x9c, 0x12,
+	0xf6, 0x7c, 0x33, 0xdc, 0x4f, 0x9a, 0xe5, 0x2d,
+	0x63, 0x68, 0xa4, 0x2b, 0x1d, 0x0a, 0x1e, 0xf0,
+	0xfe, 0x73, 0xf2, 0x5f, 0x7b, 0xb4, 0xea, 0x54,
+	0xad, 0x27, 0xd1, 0xff, 0xb6, 0x50, 0x06, 0x7b,
+	0x51, 0x3f, 0x25, 0x8a, 0xcf, 0x4c, 0x03, 0x3e,
+	0xc3, 0xad, 0x47, 0x34, 0xcf, 0xba, 0x45, 0x79,
+	0xd0, 0x7b, 0xf6, 0x66, 0x63, 0xc0, 0xc6, 0x69,
+	0xa7, 0x51, 0x84, 0xa8, 0xa0, 0x0b, 0xbc, 0x6f,
+	0x13, 0x89, 0xd6, 0x5e, 0xac, 0xca, 0x2f, 0xd2,
+	0xe7, 0xe1, 0x1e, 0x78, 0x22, 0x3a, 0x59, 0x6c,
+	0x9c, 0x8c, 0x65, 0xf1, 0x5b, 0xf4, 0xbf, 0xd5,
+	0xdc, 0x05, 0xeb, 0x70, 0x10, 0xb8, 0x6c, 0xf2,
+	0x13, 0x20, 0xb0, 0xdd, 0x3e, 0xb2, 0x92, 0x5b,
+	0xa3, 0xf7, 0x94, 0xa1, 0xa1, 0x74, 0x36, 0x9a,
+	0xf1, 0xd8, 0xc2, 0xf0, 0xc6, 0x29, 0x7e, 0x85,
+	0x28, 0xf5, 0xf2, 0x21, 0x00, 0x00, 0x00, 0x00,
+	0xc8, 0x80, 0x67, 0x40, 0xc3, 0xaa, 0x17, 0x57,
+	0x00, 0x01, 0xd3, 0x01, 0xe1, 0x01, 0x00, 0x00,
+	0x86, 0xdf, 0x9e, 0x05, 0xb1, 0xc4, 0x67, 0xfb,
+	0x02, 0x00, 0x00, 0x00, 0x00, 0x04, 0x59, 0x5a
+#elif defined(TEST_GZIP)
+	0x1f, 0x8b, 0x08, 0x08, 0x82, 0xd4, 0x97, 0x60,
+	0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74,
+	0x78, 0x74, 0x00, 0x35, 0x90, 0xc1, 0x71, 0x43,
+	0x31, 0x08, 0x44, 0xef, 0xbf, 0x8a, 0x2d, 0x20,
+	0xf3, 0xab, 0x48, 0x6e, 0xb9, 0xa6, 0x00, 0x82,
+	0xb0, 0xc3, 0x8c, 0x24, 0x64, 0x09, 0x3c, 0x2e,
+	0x3f, 0xc8, 0x4e, 0x6e, 0x42, 0xc0, 0xb2, 0xfb,
+	0x3e, 0x6d, 0x4a, 0x83, 0x8e, 0x15, 0x0d, 0xc5,
+	0xaa, 0x4d, 0x2c, 0x75, 0x50, 0x13, 0x7f, 0x03,
+	0x5b, 0x5f, 0xc2, 0x2e, 0x1e, 0x13, 0x54, 0x74,
+	0xe8, 0x62, 0xed, 0x57, 0x48, 0xd5, 0x6c, 0x2e,
+	0x29, 0xb9, 0x00, 0xd1, 0x58, 0xcd, 0xca, 0xe1,
+	0xd2, 0x46, 0x2e, 0x6b, 0x67, 0x2d, 0x5a, 0xa2,
+	0x3b, 0xc2, 0x51, 0xe9, 0x3b, 0xe5, 0x21, 0xfe,
+	0x92, 0x16, 0x34, 0xba, 0x76, 0x02, 0x55, 0xbd,
+	0x05, 0x9d, 0xf8, 0x72, 0x48, 0xd7, 0x96, 0xda,
+	0x68, 0xba, 0x1f, 0xf7, 0x2c, 0xa9, 0xbd, 0x1d,
+	0xb7, 0xd0, 0x85, 0x6e, 0xcb, 0x67, 0x14, 0xc8,
+	0x43, 0x26, 0xab, 0x93, 0xab, 0x75, 0x44, 0xad,
+	0xd4, 0xd8, 0x5e, 0xca, 0x7b, 0x48, 0x97, 0xee,
+	0x4b, 0x4f, 0x49, 0x1d, 0x39, 0x0c, 0xa1, 0x34,
+	0xde, 0xd2, 0x93, 0x1d, 0xcf, 0x00, 0x79, 0xca,
+	0x4f, 0xbc, 0x6f, 0x49, 0x0a, 0x17, 0xe8, 0x8c,
+	0x74, 0xf2, 0xca, 0xaa, 0x1d, 0x53, 0xc6, 0x94,
+	0x1f, 0xe9, 0x45, 0x66, 0x06, 0xcf, 0x8f, 0xbb,
+	0xd5, 0x18, 0x79, 0x4e, 0xd2, 0x4e, 0x26, 0x85,
+	0xac, 0x25, 0x07, 0x6b, 0xad, 0xff, 0x84, 0x32,
+	0x50, 0xe0, 0x12, 0x57, 0x25, 0x47, 0xdf, 0x86,
+	0x30, 0x68, 0x66, 0x11, 0xf3, 0xc4, 0xc7, 0x83,
+	0x65, 0xb8, 0xc4, 0xc6, 0x98, 0x0c, 0x8c, 0x99,
+	0x84, 0x73, 0x8e, 0x63, 0x68, 0x21, 0xdf, 0x1b,
+	0xd6, 0x8f, 0x31, 0x4d, 0x8b, 0xf4, 0x4d, 0x71,
+	0x93, 0xca, 0xa3, 0x1c, 0x75, 0x10, 0x32, 0x02,
+	0xec, 0x72, 0x51, 0x56, 0x42, 0x91, 0x25, 0x73,
+	0x77, 0x9b, 0xd5, 0x6d, 0x83, 0x36, 0x20, 0x4d,
+	0x1c, 0xeb, 0x8f, 0x6b, 0xb4, 0xf3, 0xf8, 0x05,
+	0x6b, 0x8b, 0x8b, 0x20, 0xbe, 0x01, 0x00, 0x00
+#elif defined(TEST_ZSTD)
+	0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x88, 0xa5, 0x08,
+	0x00, 0x46, 0x97, 0x3a, 0x1a, 0x80, 0x37, 0xcd,
+	0x01, 0xc0, 0x8a, 0xec, 0xfe, 0x2d, 0xf2, 0xb9,
+	0x44, 0x6b, 0xb9, 0x24, 0x77, 0x56, 0x5a, 0x33,
+	0x17, 0x0b, 0x67, 0x83, 0x2e, 0x47, 0x07, 0x31,
+	0x00, 0x32, 0x00, 0x33, 0x00, 0xc5, 0x2c, 0x5a,
+	0x92, 0x93, 0x0f, 0x7b, 0xd1, 0x1d, 0x63, 0x2c,
+	0xc8, 0x99, 0x94, 0x77, 0x8f, 0x94, 0x38, 0x75,
+	0x80, 0x2f, 0xae, 0xc1, 0x3e, 0xd2, 0xcf, 0x49,
+	0x15, 0x25, 0x1a, 0x87, 0x93, 0xdd, 0xe8, 0x00,
+	0x6d, 0xaa, 0xf8, 0x54, 0x74, 0xe5, 0x48, 0x4d,
+	0xa6, 0xf3, 0x1a, 0xa3, 0x13, 0x08, 0xe5, 0x26,
+	0xdc, 0x73, 0xcc, 0x3e, 0xfd, 0x86, 0xa9, 0x52,
+	0xb2, 0x76, 0xc7, 0xc2, 0x0f, 0xe4, 0x84, 0x4b,
+	0x12, 0x61, 0x3a, 0x6b, 0x7a, 0x1e, 0x8a, 0x81,
+	0xa9, 0x9b, 0x11, 0x37, 0x25, 0x55, 0x73, 0x73,
+	0x71, 0xa0, 0x84, 0xca, 0xc3, 0x4b, 0xb5, 0xcc,
+	0x50, 0xa6, 0x46, 0xd7, 0xe8, 0x08, 0xaa, 0x04,
+	0x28, 0xb1, 0x8e, 0xea, 0xb4, 0x4a, 0x49, 0x2b,
+	0xd6, 0x0d, 0x59, 0x68, 0xda, 0x64, 0x29, 0x1f,
+	0x85, 0x53, 0x72, 0xf1, 0xc5, 0x88, 0x1a, 0x0b,
+	0x4f, 0x96, 0x43, 0xe0, 0x91, 0x89, 0xb9, 0xc0,
+	0xe8, 0x18, 0xd5, 0x6e, 0x94, 0xe8, 0x35, 0x66,
+	0x01, 0x94, 0x80, 0x95, 0x87, 0xe2, 0xc8, 0x19,
+	0x73, 0xa3, 0x01, 0x05, 0xc1, 0x64, 0x72, 0xc9,
+	0x6b, 0x6e, 0x55, 0x7c, 0x29, 0x67, 0x90, 0x93,
+	0x49, 0xeb, 0xe3, 0x85, 0xc2, 0xf5, 0x79, 0x68,
+	0x9d, 0x92, 0xc3, 0x32, 0x75, 0x80, 0x66, 0xf2,
+	0x43, 0xa7, 0xb0, 0xc3, 0x22, 0x3f, 0x39, 0x8a,
+	0x35, 0x5c, 0x63, 0x5c, 0xd1, 0x9e, 0x8a, 0xd2,
+	0x78, 0x3c, 0x12, 0x01, 0x25, 0x04, 0x0e, 0x08,
+	0x10, 0x88, 0xb6, 0x1b, 0xb7, 0x96, 0x35, 0xa8,
+	0x0d, 0x1e, 0xae, 0xac, 0x4a, 0x70, 0xa5, 0x31,
+	0xd0, 0x0c, 0x78, 0xbf, 0xdd, 0xc5, 0x24, 0x3e,
+	0xcb, 0x0a, 0x0a, 0x69, 0x40, 0xba, 0xb0, 0xc4,
+	0x2a, 0x9b, 0x1e, 0x0a, 0x51, 0xa6, 0x16, 0x98,
+	0x76
+#elif defined(TEST_ZSTD2)
+	0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x58, 0x75, 0x04,
+	0x00, 0xb2, 0x4c, 0x20, 0x17, 0xa0, 0x25, 0x69,
+	0x03, 0xf0, 0xb2, 0x37, 0xb1, 0x5e, 0xb9, 0x24,
+	0x56, 0x5b, 0x52, 0x22, 0x39, 0x01, 0x44, 0x2b,
+	0x03, 0x55, 0xe3, 0x47,	0x03, 0x12, 0x9a, 0xe1,
+	0xf0, 0x94, 0x0b, 0xe5, 0xe2, 0xba, 0x7e, 0xfe,
+	0x9c, 0xc7, 0x61, 0x43, 0xc8, 0xfa, 0xf0, 0x3a,
+	0xfa, 0x51, 0xaa, 0x50,	0xa6, 0x2d, 0x9a, 0x78,
+	0xce, 0x2f, 0x61, 0x20, 0x6c, 0x7e, 0x35, 0x60,
+	0xfb, 0xdd, 0x4c, 0x63, 0xfb, 0x95, 0x35, 0xc0,
+	0x82, 0x59, 0xc2, 0xc9,	0x78, 0x6e, 0x30, 0xe6,
+	0xd2, 0x72, 0x15, 0x14, 0x18, 0x62, 0x5d, 0xeb,
+	0x2d, 0x9d, 0x3e, 0xee, 0x2e, 0x58, 0x58, 0xe9,
+	0x40, 0x68, 0xb9, 0x2f,	0x23, 0x99, 0x2a, 0x4d,
+	0xe8, 0x49, 0x79, 0x70, 0x1f, 0xf9, 0xe2, 0x34,
+	0x2e, 0xab, 0xa5, 0xa3, 0xf2, 0x70, 0x98, 0xd0,
+	0xb2, 0xb1, 0x3e, 0x5d,	0x90, 0x20, 0xd9, 0x36,
+	0x8b, 0xdb, 0xaa, 0x20, 0x40, 0x03, 0x14, 0x06,
+	0x03, 0x16, 0x2a, 0x9d, 0x31, 0xbd, 0x28, 0x3b,
+	0x0c, 0xac, 0x41,
+	0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x58, 0xbd, 0x04,
+	0x00, 0x62, 0xcd, 0x22, 0x19, 0xa0, 0x25, 0x69,
+	0x03, 0x60, 0x72, 0xc9, 0x36, 0xda, 0xd2, 0x8b,
+	0xfc, 0xbf, 0x25, 0x42, 0xa9, 0x82, 0x38, 0x70,
+	0x1a, 0x2e, 0x54, 0x95,	0x33, 0x02, 0x03, 0x51,
+	0x36, 0x51, 0x80, 0xcc, 0x7a, 0x6e, 0x52, 0x2e,
+	0x75, 0x64, 0x2d, 0x33, 0x2c, 0xd6, 0xdb, 0xfc,
+	0x39, 0x31, 0xd5, 0xa8,	0xa2, 0x40, 0xd7, 0x12,
+	0x4c, 0xc6, 0x76, 0xdc, 0x1e, 0x0f, 0xf4, 0x4e,
+	0x0a, 0xd3, 0x0c, 0x87, 0x67, 0x25, 0x25, 0x52,
+	0x66, 0x87, 0x95, 0xc6,	0x69, 0x0c, 0xb4, 0x5e,
+	0x1d, 0xe7, 0x5e, 0xcd, 0x47, 0x41, 0x80, 0x89,
+	0x5c, 0xa5, 0x4a, 0x32, 0x26, 0xb3, 0x3d, 0x2b,
+	0xd5, 0xc0, 0x16, 0xde,	0xfb, 0x65, 0xcd, 0x6a,
+	0x0c, 0x3f, 0xe7, 0xd6, 0xb2, 0x17, 0x7c, 0x25,
+	0x35, 0x6b, 0x58, 0xf0, 0x95, 0xb5, 0xf2, 0xe4,
+	0x4e, 0xf0, 0x34, 0x4f,	0x5f, 0x39, 0xd1, 0x90,
+	0xf8, 0xb9, 0x59, 0xbe, 0x2e, 0xf9, 0xd4, 0x02,
+	0x98, 0x50, 0x5a, 0xc2, 0xcf, 0xe1, 0x08, 0x02,
+	0x00, 0x0f, 0x1e, 0x44,	0x40, 0x79, 0x50, 0x67,
+	0x3d, 0xd3, 0x35, 0x8f
+#endif
+};
+
+static const char orig[] =
+"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\n"
+"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\n"
+"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n"
+"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\n"
+"cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\n"
+"proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n";
+
+#if defined(TEST_BZIP2) || defined(TEST_BZIP22)
+#define COMP_NAME "bzip2"
+#define COMP_ID IO_COMPRESSOR_BZIP2
+#elif defined(TEST_XZ) || defined(TEST_XZ2)
+#define COMP_NAME "xz"
+#define COMP_ID IO_COMPRESSOR_XZ
+#elif defined(TEST_GZIP)
+#define COMP_NAME "gzip"
+#define COMP_ID IO_COMPRESSOR_GZIP
+#elif defined(TEST_ZSTD) || defined(TEST_ZSTD2)
+#define COMP_NAME "zstd"
+#define COMP_ID IO_COMPRESSOR_ZSTD
+#endif
+
+static void destroy_noop(sqfs_object_t *obj)
+{
+	(void)obj;
+}
+
+static int precache_noop(istream_t *strm)
+{
+	(void)strm;
+	return 0;
+}
+
+static const char *get_filename(istream_t *strm)
+{
+	(void)strm;
+	return "memstream";
+}
+
+static istream_t memstream = {
+	.base = {
+		.destroy = destroy_noop,
+	},
+
+	.buffer_used = sizeof(data_in) / sizeof(data_in[0]),
+	.buffer_offset = 0,
+	.eof = true,
+	.buffer = data_in,
+
+	.precache = precache_noop,
+	.get_filename = get_filename,
+};
+
+int main(int argc, char **argv)
+{
+	char buffer[2 * (sizeof(orig) / sizeof(orig[0]))];
+	const char *name;
+	istream_t *xfrm;
+	size_t orig_sz;
+	int ret;
+	(void)argc; (void)argv;
+
+	/* XXX: null terminator not included in the compressed blob */
+	orig_sz = (sizeof(orig) / sizeof(orig[0])) - 1;
+
+	/* generic API test */
+	TEST_ASSERT(io_compressor_exists(COMP_ID));
+
+	name = io_compressor_name_from_id(COMP_ID);
+	TEST_STR_EQUAL(name, COMP_NAME);
+
+	ret = io_compressor_id_from_name(name);
+	TEST_EQUAL_I(ret, COMP_ID);
+
+	ret = istream_detect_compressor(&memstream, NULL);
+	TEST_EQUAL_I(ret, COMP_ID);
+
+	/* decoder test */
+	xfrm = istream_compressor_create(&memstream, COMP_ID);
+	TEST_NOT_NULL(xfrm);
+
+	name = istream_get_filename(xfrm);
+	TEST_STR_EQUAL(name, "memstream");
+
+	ret = istream_read(xfrm, buffer, sizeof(buffer));
+	TEST_ASSERT(ret > 0);
+	TEST_EQUAL_UI((size_t)ret, orig_sz);
+
+	ret = memcmp(buffer, orig, ret);
+	TEST_EQUAL_I(ret, 0);
+
+	ret = istream_read(xfrm, buffer, sizeof(buffer));
+	TEST_EQUAL_I(ret, 0);
+
+	/* cleanup */
+	sqfs_destroy(xfrm);
+	return EXIT_SUCCESS;
+}
diff --git a/tests/libtar/Makemodule.am b/tests/libtar/Makemodule.am
index bc829fb..b042f74 100644
--- a/tests/libtar/Makemodule.am
+++ b/tests/libtar/Makemodule.am
@@ -1,160 +1,160 @@
 TARDATADIR=$(top_srcdir)/tests/libtar/data
 
 test_tar_gnu0_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_gnu0_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_gnu0_LDADD = libtar.a libio.a libcompat.a
 test_tar_gnu0_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_gnu0_CPPFLAGS += -DTESTFILE=format-acceptance/gnu.tar
 
 test_tar_gnu1_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_gnu1_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_gnu1_LDADD = libtar.a libio.a libcompat.a
 test_tar_gnu1_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_gnu1_CPPFLAGS += -DTESTFILE=format-acceptance/gnu-g.tar
 
 test_tar_gnu2_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_gnu2_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_gnu2_LDADD = libtar.a libio.a libcompat.a
 test_tar_gnu2_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_gnu2_CPPFLAGS += -DTESTFILE=user-group-largenum/gnu.tar
 test_tar_gnu2_CPPFLAGS += -DTESTUID=0x80000000  -DTESTGID=0x80000000
 test_tar_gnu2_CPPFLAGS += -DTESTTS=1542995392
 
 test_tar_gnu3_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_gnu3_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_gnu3_LDADD = libtar.a libio.a libcompat.a
 test_tar_gnu3_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_gnu3_CPPFLAGS += -DTESTFILE=negative-mtime/gnu.tar -DTESTTS=-315622800
 
 test_tar_gnu4_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_gnu4_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_gnu4_LDADD = libtar.a libio.a libcompat.a
 test_tar_gnu4_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_gnu4_CPPFLAGS += -DTESTFILE=long-paths/gnu.tar -DLONG_NAME_TEST
 test_tar_gnu4_CPPFLAGS += -DTESTTS=1542909670
 
 test_tar_gnu5_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_gnu5_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_gnu5_LDADD = libtar.a libio.a libcompat.a
 test_tar_gnu5_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_gnu5_CPPFLAGS += -DTESTFILE=large-mtime/gnu.tar -DTESTTS=8589934592L
 
 test_tar_gnu6_SOURCES = tests/libtar/tar_big_file.c tests/test.h
-test_tar_gnu6_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_gnu6_LDADD = libtar.a libio.a libcompat.a
 test_tar_gnu6_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_gnu6_CPPFLAGS += -DTESTFILE=file-size/gnu.tar
 
 test_tar_pax0_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_pax0_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_pax0_LDADD = libtar.a libio.a libcompat.a
 test_tar_pax0_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_pax0_CPPFLAGS += -DTESTFILE=format-acceptance/pax.tar
 
 test_tar_pax1_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_pax1_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_pax1_LDADD = libtar.a libio.a libcompat.a
 test_tar_pax1_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_pax1_CPPFLAGS += -DTESTFILE=user-group-largenum/pax.tar
 test_tar_pax1_CPPFLAGS += -DTESTUID=2147483648UL -DTESTGID=2147483648UL
 test_tar_pax1_CPPFLAGS += -DTESTTS=1542995392
 
 test_tar_pax2_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_pax2_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_pax2_LDADD = libtar.a libio.a libcompat.a
 test_tar_pax2_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_pax2_CPPFLAGS += -DTESTFILE=large-mtime/pax.tar -DTESTTS=8589934592L
 
 test_tar_pax3_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_pax3_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_pax3_LDADD = libtar.a libio.a libcompat.a
 test_tar_pax3_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_pax3_CPPFLAGS += -DTESTFILE=negative-mtime/pax.tar -DTESTTS=-315622800
 
 test_tar_pax4_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_pax4_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_pax4_LDADD = libtar.a libio.a libcompat.a
 test_tar_pax4_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_pax4_CPPFLAGS += -DTESTFILE=long-paths/pax.tar
 test_tar_pax4_CPPFLAGS += -DLONG_NAME_TEST -DTESTTS=1542909670
 
 test_tar_pax5_SOURCES = tests/libtar/tar_big_file.c tests/test.h
-test_tar_pax5_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_pax5_LDADD = libtar.a libio.a libcompat.a
 test_tar_pax5_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_pax5_CPPFLAGS += -DTESTFILE=file-size/pax.tar
 
 test_tar_ustar0_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_ustar0_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_ustar0_LDADD = libtar.a libio.a libcompat.a
 test_tar_ustar0_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_ustar0_CPPFLAGS += -DTESTFILE=format-acceptance/ustar.tar
 
 test_tar_ustar1_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_ustar1_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_ustar1_LDADD = libtar.a libio.a libcompat.a
 test_tar_ustar1_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_ustar1_CPPFLAGS += -DTESTFILE=format-acceptance/ustar-pre-posix.tar
 
 test_tar_ustar2_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_ustar2_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_ustar2_LDADD = libtar.a libio.a libcompat.a
 test_tar_ustar2_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_ustar2_CPPFLAGS += -DTESTFILE=format-acceptance/v7.tar
 
 test_tar_ustar3_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_ustar3_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_ustar3_LDADD = libtar.a libio.a libcompat.a
 test_tar_ustar3_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_ustar3_CPPFLAGS += -DTESTFILE=user-group-largenum/8-digit.tar
 test_tar_ustar3_CPPFLAGS += -DTESTUID=8388608 -DTESTGID=8388608
 test_tar_ustar3_CPPFLAGS += -DTESTTS=1542995392
 
 test_tar_ustar4_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_ustar4_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_ustar4_LDADD = libtar.a libio.a libcompat.a
 test_tar_ustar4_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_ustar4_CPPFLAGS += -DTESTFILE=large-mtime/12-digit.tar
 test_tar_ustar4_CPPFLAGS += -DTESTTS=8589934592L
 
 test_tar_ustar5_SOURCES = tests/libtar/tar_simple.c tests/test.h
-test_tar_ustar5_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_ustar5_LDADD = libtar.a libio.a libcompat.a
 test_tar_ustar5_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_ustar5_CPPFLAGS += -DTESTFILE=long-paths/ustar.tar
 test_tar_ustar5_CPPFLAGS += -DLONG_NAME_TEST -DTESTTS=1542909670
 
 test_tar_ustar6_SOURCES = tests/libtar/tar_big_file.c tests/test.h
-test_tar_ustar6_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_ustar6_LDADD = libtar.a libio.a libcompat.a
 test_tar_ustar6_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_ustar6_CPPFLAGS += -DTESTFILE=file-size/12-digit.tar
 
 test_tar_target_filled_SOURCES = tests/libtar/tar_target_filled.c tests/test.h
-test_tar_target_filled_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_target_filled_LDADD = libtar.a libio.a libcompat.a
 test_tar_target_filled_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 
 test_tar_sparse_gnu_SOURCES = tests/libtar/tar_sparse_gnu.c tests/test.h
-test_tar_sparse_gnu_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_sparse_gnu_LDADD = libtar.a libio.a libcompat.a
 test_tar_sparse_gnu_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 
 test_tar_sparse_gnu0_SOURCES = tests/libtar/tar_sparse.c tests/test.h
-test_tar_sparse_gnu0_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_sparse_gnu0_LDADD = libtar.a libio.a libcompat.a
 test_tar_sparse_gnu0_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_sparse_gnu0_CPPFLAGS += -DTESTFILE=sparse-files/pax-gnu0-0.tar
 
 test_tar_sparse_gnu1_SOURCES = tests/libtar/tar_sparse.c tests/test.h
-test_tar_sparse_gnu1_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_sparse_gnu1_LDADD = libtar.a libio.a libcompat.a
 test_tar_sparse_gnu1_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_sparse_gnu1_CPPFLAGS += -DTESTFILE=sparse-files/pax-gnu0-1.tar
 
 test_tar_sparse_gnu2_SOURCES = tests/libtar/tar_sparse.c tests/test.h
-test_tar_sparse_gnu2_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_sparse_gnu2_LDADD = libtar.a libio.a libcompat.a
 test_tar_sparse_gnu2_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_sparse_gnu2_CPPFLAGS += -DTESTFILE=sparse-files/pax-gnu1-0.tar
 
 test_tar_sparse_gnu3_SOURCES = tests/libtar/tar_sparse.c tests/test.h
-test_tar_sparse_gnu3_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_sparse_gnu3_LDADD = libtar.a libio.a libcompat.a
 test_tar_sparse_gnu3_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_sparse_gnu3_CPPFLAGS += -DTESTFILE=sparse-files/gnu.tar
 
 test_tar_xattr_bsd_SOURCES = tests/libtar/tar_xattr.c tests/test.h
-test_tar_xattr_bsd_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_xattr_bsd_LDADD = libtar.a libio.a libcompat.a
 test_tar_xattr_bsd_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_xattr_bsd_CPPFLAGS += -DTESTFILE=xattr/xattr-libarchive.tar
 
 test_tar_xattr_schily_SOURCES = tests/libtar/tar_xattr.c tests/test.h
-test_tar_xattr_schily_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_xattr_schily_LDADD = libtar.a libio.a libcompat.a
 test_tar_xattr_schily_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_xattr_schily_CPPFLAGS += -DTESTFILE=xattr/xattr-schily.tar
 
 test_tar_xattr_schily_bin_SOURCES = tests/libtar/tar_xattr_bin.c tests/test.h
-test_tar_xattr_schily_bin_LDADD = libtar.a libfstream.a libcompat.a
+test_tar_xattr_schily_bin_LDADD = libtar.a libio.a libcompat.a
 test_tar_xattr_schily_bin_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(TARDATADIR)
 test_tar_xattr_schily_bin_CPPFLAGS += -DTESTFILE=xattr/xattr-schily-binary.tar
 
 tar_fuzz_SOURCES = tests/libtar/tar_fuzz.c
-tar_fuzz_LDADD = libtar.a libfstream.a libcompat.a
+tar_fuzz_LDADD = libtar.a libio.a libcompat.a
 
 LIBTAR_TESTS = \
 	test_tar_ustar0 test_tar_ustar1 test_tar_ustar2 test_tar_ustar3 \
diff --git a/tests/libtar/tar_big_file.c b/tests/libtar/tar_big_file.c
index 499805f..6e5af50 100644
--- a/tests/libtar/tar_big_file.c
+++ b/tests/libtar/tar_big_file.c
@@ -6,6 +6,7 @@
  */
 #include "config.h"
 #include "tar.h"
+#include "io/file.h"
 #include "../test.h"
 
 int main(int argc, char **argv)
diff --git a/tests/libtar/tar_fuzz.c b/tests/libtar/tar_fuzz.c
index d5728b5..e7ab2b4 100644
--- a/tests/libtar/tar_fuzz.c
+++ b/tests/libtar/tar_fuzz.c
@@ -6,6 +6,7 @@
  */
 #include "config.h"
 
+#include "io/file.h"
 #include "tar.h"
 
 #include <stdlib.h>
diff --git a/tests/libtar/tar_simple.c b/tests/libtar/tar_simple.c
index e5f0137..4fc1b8b 100644
--- a/tests/libtar/tar_simple.c
+++ b/tests/libtar/tar_simple.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
  */
 #include "config.h"
+#include "io/file.h"
 #include "tar.h"
 #include "../test.h"
 
diff --git a/tests/libtar/tar_sparse.c b/tests/libtar/tar_sparse.c
index 24f7a57..d868c80 100644
--- a/tests/libtar/tar_sparse.c
+++ b/tests/libtar/tar_sparse.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
  */
 #include "config.h"
+#include "io/file.h"
 #include "tar.h"
 #include "../test.h"
 
diff --git a/tests/libtar/tar_sparse_gnu.c b/tests/libtar/tar_sparse_gnu.c
index 5d12478..c55f175 100644
--- a/tests/libtar/tar_sparse_gnu.c
+++ b/tests/libtar/tar_sparse_gnu.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
  */
 #include "config.h"
+#include "io/file.h"
 #include "tar.h"
 #include "../test.h"
 
diff --git a/tests/libtar/tar_target_filled.c b/tests/libtar/tar_target_filled.c
index 57c6af9..34bf20f 100644
--- a/tests/libtar/tar_target_filled.c
+++ b/tests/libtar/tar_target_filled.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
  */
 #include "config.h"
+#include "io/file.h"
 #include "tar.h"
 #include "../test.h"
 
diff --git a/tests/libtar/tar_xattr.c b/tests/libtar/tar_xattr.c
index 877bfba..7dde243 100644
--- a/tests/libtar/tar_xattr.c
+++ b/tests/libtar/tar_xattr.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
  */
 #include "config.h"
+#include "io/file.h"
 #include "tar.h"
 #include "../test.h"
 
diff --git a/tests/libtar/tar_xattr_bin.c b/tests/libtar/tar_xattr_bin.c
index 51ca0b0..ea2bc28 100644
--- a/tests/libtar/tar_xattr_bin.c
+++ b/tests/libtar/tar_xattr_bin.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
  */
 #include "config.h"
+#include "io/file.h"
 #include "tar.h"
 #include "../test.h"
 
diff --git a/tests/libutil/Makemodule.am b/tests/libutil/Makemodule.am
index 27d6341..557a65c 100644
--- a/tests/libutil/Makemodule.am
+++ b/tests/libutil/Makemodule.am
@@ -1,5 +1,5 @@
 test_str_table_SOURCES = tests/libutil/str_table.c tests/test.h
-test_str_table_LDADD = libutil.a libfstream.a libcompat.a
+test_str_table_LDADD = libutil.a libio.a libcompat.a
 test_str_table_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(top_srcdir)/tests/libutil
 
 test_rbtree_SOURCES = tests/libutil/rbtree.c tests/test.h
diff --git a/tests/libutil/str_table.c b/tests/libutil/str_table.c
index a69a9c4..509594c 100644
--- a/tests/libutil/str_table.c
+++ b/tests/libutil/str_table.c
@@ -7,7 +7,7 @@
 #include "config.h"
 
 #include "util/str_table.h"
-#include "fstream.h"
+#include "io/file.h"
 #include "compat.h"
 #include "../test.h"
 
-- 
cgit v1.2.3