aboutsummaryrefslogtreecommitdiff
path: root/lib/io/src
diff options
context:
space:
mode:
authorDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2023-01-31 11:21:30 +0100
committerDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2023-01-31 13:51:49 +0100
commitcdccc69c62579b0c13b35fad0728079652b8f3c9 (patch)
tree9fa54c710f73c5e08a9c8466e7a712eb63ee07ac /lib/io/src
parent2182129c8f359c4fa1390eaba7a65b595ccd4182 (diff)
Move library source into src sub-directory
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'lib/io/src')
-rw-r--r--lib/io/src/get_line.c118
-rw-r--r--lib/io/src/internal.h31
-rw-r--r--lib/io/src/istream.c86
-rw-r--r--lib/io/src/ostream.c68
-rw-r--r--lib/io/src/printf.c30
-rw-r--r--lib/io/src/unix/istream.c123
-rw-r--r--lib/io/src/unix/ostream.c173
-rw-r--r--lib/io/src/win32/istream.c138
-rw-r--r--lib/io/src/win32/ostream.c199
-rw-r--r--lib/io/src/xfrm/istream.c106
-rw-r--r--lib/io/src/xfrm/ostream.c144
11 files changed, 1216 insertions, 0 deletions
diff --git a/lib/io/src/get_line.c b/lib/io/src/get_line.c
new file mode 100644
index 0000000..f7e0b59
--- /dev/null
+++ b/lib/io/src/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/src/internal.h b/lib/io/src/internal.h
new file mode 100644
index 0000000..25a0196
--- /dev/null
+++ b/lib/io/src/internal.h
@@ -0,0 +1,31 @@
+/* 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 "xfrm/compress.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)
+
+#endif /* INTERNAL_H */
diff --git a/lib/io/src/istream.c b/lib/io/src/istream.c
new file mode 100644
index 0000000..64fa478
--- /dev/null
+++ b/lib/io/src/istream.c
@@ -0,0 +1,86 @@
+/* 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);
+}
+
+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/src/ostream.c b/lib/io/src/ostream.c
new file mode 100644
index 0000000..da0b7b3
--- /dev/null
+++ b/lib/io/src/ostream.c
@@ -0,0 +1,68 @@
+/* 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_sparse(ostream_t *strm, size_t size)
+{
+ if (strm->append_sparse == NULL)
+ return append_sparse_fallback(strm, size);
+
+ return strm->append_sparse(strm, size);
+}
+
+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/src/printf.c b/lib/io/src/printf.c
new file mode 100644
index 0000000..3850487
--- /dev/null
+++ b/lib/io/src/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/src/unix/istream.c b/lib/io/src/unix/istream.c
new file mode 100644
index 0000000..f8cffad
--- /dev/null
+++ b/lib/io/src/unix/istream.c
@@ -0,0 +1,123 @@
+/* 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;
+
+ 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) {
+ strm->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));
+ istream_t *strm = (istream_t *)file;
+
+ if (file == NULL) {
+ perror(path);
+ return NULL;
+ }
+
+ sqfs_object_init(file, file_destroy, 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;
+ 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));
+ istream_t *strm = (istream_t *)file;
+
+ if (file == NULL)
+ goto fail;
+
+ sqfs_object_init(file, file_destroy, NULL);
+
+ 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;
+ return strm;
+fail:
+ perror("creating file wrapper for stdin");
+ free(file);
+ return NULL;
+}
diff --git a/lib/io/src/unix/ostream.c b/lib/io/src/unix/ostream.c
new file mode 100644
index 0000000..5ef2af2
--- /dev/null
+++ b/lib/io/src/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));
+ ostream_t *strm = (ostream_t *)file;
+
+ if (file == NULL) {
+ perror(path);
+ return NULL;
+ }
+
+ sqfs_object_init(file, file_destroy, 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;
+ 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));
+ 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;
+
+ sqfs_object_init(file, file_destroy, NULL);
+ return strm;
+fail:
+ perror("creating file wrapper for stdout");
+ free(file);
+ return NULL;
+}
diff --git a/lib/io/src/win32/istream.c b/lib/io/src/win32/istream.c
new file mode 100644
index 0000000..be3d829
--- /dev/null
+++ b/lib/io/src/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));
+ istream_t *strm = (istream_t *)file;
+ WCHAR *wpath = NULL;
+
+ if (file == NULL) {
+ perror(path);
+ return NULL;
+ }
+
+ sqfs_object_init(file, file_destroy, 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;
+ 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));
+ istream_t *strm = (istream_t *)file;
+
+ if (file == NULL) {
+ perror("stdin");
+ return NULL;
+ }
+
+ sqfs_object_init(file, file_destroy, NULL);
+
+ strm->buffer = file->buffer;
+ strm->precache = file_precache;
+ strm->get_filename = file_get_filename;
+ return strm;
+}
diff --git a/lib/io/src/win32/ostream.c b/lib/io/src/win32/ostream.c
new file mode 100644
index 0000000..0fe04f3
--- /dev/null
+++ b/lib/io/src/win32/ostream.c
@@ -0,0 +1,199 @@
+/* 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;
+ }
+
+ sqfs_object_init(file, file_destroy, 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;
+ 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;
+ }
+
+ sqfs_object_init(strm, stdout_destroy, NULL);
+
+ strm->append = stdout_append;
+ strm->flush = stdout_flush;
+ strm->get_filename = stdout_get_filename;
+ return strm;
+}
diff --git a/lib/io/src/xfrm/istream.c b/lib/io/src/xfrm/istream.c
new file mode 100644
index 0000000..4a1ad77
--- /dev/null
+++ b/lib/io/src/xfrm/istream.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * istream.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+typedef struct istream_xfrm_t {
+ istream_t base;
+
+ istream_t *wrapped;
+ xfrm_stream_t *xfrm;
+
+ sqfs_u8 uncompressed[BUFSZ];
+} istream_xfrm_t;
+
+static int xfrm_precache(istream_t *base)
+{
+ istream_xfrm_t *xfrm = (istream_xfrm_t *)base;
+ int ret;
+
+ ret = istream_precache(xfrm->wrapped);
+ if (ret != 0)
+ return ret;
+
+ for (;;) {
+ const sqfs_u32 in_sz = xfrm->wrapped->buffer_used;
+ const sqfs_u32 out_sz = sizeof(xfrm->uncompressed);
+ sqfs_u32 in_off = 0, out_off = base->buffer_used;
+ int mode = XFRM_STREAM_FLUSH_NONE;
+
+ if (xfrm->wrapped->eof)
+ mode = XFRM_STREAM_FLUSH_FULL;
+
+ ret = xfrm->xfrm->process_data(xfrm->xfrm,
+ xfrm->wrapped->buffer, in_sz,
+ base->buffer + out_off,
+ out_sz - out_off,
+ &in_off, &out_off, mode);
+
+ if (ret == XFRM_STREAM_ERROR) {
+ fprintf(stderr, "%s: internal error in decompressor.\n",
+ base->get_filename(base));
+ return -1;
+ }
+
+ base->buffer_used = out_off;
+ xfrm->wrapped->buffer_offset = in_off;
+
+ if (ret == XFRM_STREAM_BUFFER_FULL || out_off >= out_sz)
+ break;
+
+ ret = istream_precache(xfrm->wrapped);
+ if (ret != 0)
+ return ret;
+
+ if (xfrm->wrapped->eof && xfrm->wrapped->buffer_used == 0) {
+ if (base->buffer_used == 0)
+ base->eof = true;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static const char *xfrm_get_filename(istream_t *strm)
+{
+ istream_xfrm_t *xfrm = (istream_xfrm_t *)strm;
+
+ return xfrm->wrapped->get_filename(xfrm->wrapped);
+}
+
+static void xfrm_destroy(sqfs_object_t *obj)
+{
+ istream_xfrm_t *xfrm = (istream_xfrm_t *)obj;
+
+ sqfs_drop(xfrm->xfrm);
+ sqfs_drop(xfrm->wrapped);
+ free(xfrm);
+}
+
+istream_t *istream_xfrm_create(istream_t *strm, xfrm_stream_t *xfrm)
+{
+ istream_xfrm_t *stream = calloc(1, sizeof(*stream));
+ istream_t *base = (istream_t *)stream;
+
+ if (stream == NULL)
+ goto fail;
+
+ sqfs_object_init(stream, xfrm_destroy, NULL);
+
+ stream->wrapped = sqfs_grab(strm);
+ stream->xfrm = sqfs_grab(xfrm);
+
+ base->precache = xfrm_precache;
+ base->get_filename = xfrm_get_filename;
+ base->buffer = stream->uncompressed;
+ base->eof = false;
+ return base;
+fail:
+ fprintf(stderr, "%s: error initializing decompressor stream.\n",
+ strm->get_filename(strm));
+ return NULL;
+}
diff --git a/lib/io/src/xfrm/ostream.c b/lib/io/src/xfrm/ostream.c
new file mode 100644
index 0000000..bd94515
--- /dev/null
+++ b/lib/io/src/xfrm/ostream.c
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * ostream.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "../internal.h"
+
+typedef struct ostream_xfrm_t {
+ ostream_t base;
+
+ ostream_t *wrapped;
+ xfrm_stream_t *xfrm;
+
+ size_t inbuf_used;
+
+ sqfs_u8 inbuf[BUFSZ];
+ sqfs_u8 outbuf[BUFSZ];
+} ostream_xfrm_t;
+
+static int flush_inbuf(ostream_xfrm_t *xfrm, bool finish)
+{
+ const sqfs_u32 avail_out = sizeof(xfrm->outbuf);
+ const sqfs_u32 avail_in = xfrm->inbuf_used;
+ const int mode = finish ? XFRM_STREAM_FLUSH_FULL :
+ XFRM_STREAM_FLUSH_NONE;
+ sqfs_u32 off_in = 0, off_out = 0;
+ int ret;
+
+ while (finish || off_in < avail_in) {
+ ret = xfrm->xfrm->process_data(xfrm->xfrm,
+ xfrm->inbuf + off_in,
+ avail_in - off_in,
+ xfrm->outbuf + off_out,
+ avail_out - off_out,
+ &off_in, &off_out, mode);
+
+ if (ret == XFRM_STREAM_ERROR) {
+ fprintf(stderr,
+ "%s: internal error in compressor.\n",
+ xfrm->wrapped->get_filename(xfrm->wrapped));
+ return -1;
+ }
+
+ if (ostream_append(xfrm->wrapped, xfrm->outbuf, off_out))
+ return -1;
+
+ off_out = 0;
+
+ if (ret == XFRM_STREAM_END)
+ break;
+ }
+
+ if (off_out > 0) {
+ if (ostream_append(xfrm->wrapped, xfrm->outbuf, off_out))
+ return -1;
+ }
+
+ if (off_in < avail_in) {
+ memmove(xfrm->inbuf, xfrm->inbuf + off_in, avail_in - off_in);
+ xfrm->inbuf_used -= off_in;
+ } else {
+ xfrm->inbuf_used = 0;
+ }
+
+ return 0;
+}
+
+static int xfrm_append(ostream_t *strm, const void *data, size_t size)
+{
+ ostream_xfrm_t *xfrm = (ostream_xfrm_t *)strm;
+ size_t diff;
+
+ while (size > 0) {
+ if (xfrm->inbuf_used >= BUFSZ) {
+ if (flush_inbuf(xfrm, false))
+ return -1;
+ }
+
+ diff = BUFSZ - xfrm->inbuf_used;
+
+ if (diff > size)
+ diff = size;
+
+ memcpy(xfrm->inbuf + xfrm->inbuf_used, data, diff);
+
+ xfrm->inbuf_used += diff;
+ data = (const char *)data + diff;
+ size -= diff;
+ }
+
+ return 0;
+}
+
+static int xfrm_flush(ostream_t *strm)
+{
+ ostream_xfrm_t *xfrm = (ostream_xfrm_t *)strm;
+
+ if (xfrm->inbuf_used > 0) {
+ if (flush_inbuf(xfrm, true))
+ return -1;
+ }
+
+ return xfrm->wrapped->flush(xfrm->wrapped);
+}
+
+static const char *xfrm_get_filename(ostream_t *strm)
+{
+ ostream_xfrm_t *xfrm = (ostream_xfrm_t *)strm;
+
+ return xfrm->wrapped->get_filename(xfrm->wrapped);
+}
+
+static void xfrm_destroy(sqfs_object_t *obj)
+{
+ ostream_xfrm_t *xfrm = (ostream_xfrm_t *)obj;
+
+ sqfs_drop(xfrm->wrapped);
+ sqfs_drop(xfrm->xfrm);
+ free(xfrm);
+}
+
+ostream_t *ostream_xfrm_create(ostream_t *strm, xfrm_stream_t *xfrm)
+{
+ ostream_xfrm_t *stream = calloc(1, sizeof(*stream));
+ ostream_t *base = (ostream_t *)stream;
+
+ if (stream == NULL)
+ goto fail;
+
+ sqfs_object_init(stream, xfrm_destroy, NULL);
+
+ stream->wrapped = sqfs_grab(strm);
+ stream->xfrm = sqfs_grab(xfrm);
+ stream->inbuf_used = 0;
+ base->append = xfrm_append;
+ base->flush = xfrm_flush;
+ base->get_filename = xfrm_get_filename;
+ return base;
+fail:
+ fprintf(stderr, "%s: error initializing compressor.\n",
+ strm->get_filename(strm));
+ return NULL;
+}