summaryrefslogtreecommitdiff
path: root/src/io_gunzip.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/io_gunzip.c')
-rw-r--r--src/io_gunzip.c252
1 files changed, 252 insertions, 0 deletions
diff --git a/src/io_gunzip.c b/src/io_gunzip.c
new file mode 100644
index 0000000..b7fa9f4
--- /dev/null
+++ b/src/io_gunzip.c
@@ -0,0 +1,252 @@
+/* io_gunzip.c - Alpine Package Keeper (APK)
+ *
+ * Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation. See http://www.gnu.org/ for details.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <zlib.h>
+
+#include "apk_defines.h"
+#include "apk_io.h"
+
+struct apk_gzip_istream {
+ struct apk_istream is;
+ struct apk_istream *zis;
+ z_stream zs;
+
+ apk_multipart_cb cb;
+ void *cbctx;
+ void *cbprev;
+ apk_blob_t cbarg;
+};
+
+static void gzi_get_meta(struct apk_istream *is, struct apk_file_meta *meta)
+{
+ struct apk_gzip_istream *gis = container_of(is, struct apk_gzip_istream, is);
+ apk_istream_get_meta(gis->zis, meta);
+}
+
+static int gzi_boundary_change(struct apk_gzip_istream *gis)
+{
+ int r;
+
+ r = gis->cb(gis->cbctx, gis->is.err ? APK_MPART_END : APK_MPART_BOUNDARY, gis->cbarg);
+ if (r > 0) r = -ECANCELED;
+ if (r != 0) gis->is.err = r;
+ return r;
+}
+
+static ssize_t gzi_read(struct apk_istream *is, void *ptr, size_t size)
+{
+ struct apk_gzip_istream *gis = container_of(is, struct apk_gzip_istream, is);
+ int r;
+
+ gis->zs.avail_out = size;
+ gis->zs.next_out = ptr;
+
+ while (gis->zs.avail_out != 0 && gis->is.err == 0) {
+ if (!APK_BLOB_IS_NULL(gis->cbarg)) {
+ if (gzi_boundary_change(gis))
+ goto ret;
+ gis->cbarg = APK_BLOB_NULL;
+ }
+ if (gis->zs.avail_in == 0) {
+ apk_blob_t blob;
+
+ if (gis->cb != NULL && gis->cbprev != NULL &&
+ gis->cbprev != gis->zs.next_in) {
+ gis->cb(gis->cbctx, APK_MPART_DATA,
+ APK_BLOB_PTR_LEN(gis->cbprev,
+ (void *)gis->zs.next_in - gis->cbprev));
+ }
+ blob = apk_istream_get_all(gis->zis);
+ gis->cbprev = blob.ptr;
+ gis->zs.avail_in = blob.len;
+ gis->zs.next_in = (void *) gis->cbprev;
+ if (blob.len < 0) {
+ gis->is.err = blob.len;
+ goto ret;
+ } else if (gis->zs.avail_in == 0) {
+ gis->is.err = 1;
+ gis->cbarg = APK_BLOB_NULL;
+ gzi_boundary_change(gis);
+ goto ret;
+ }
+ }
+
+ r = inflate(&gis->zs, Z_NO_FLUSH);
+ switch (r) {
+ case Z_STREAM_END:
+ /* Digest the inflated bytes */
+ if (gis->zis->err && gis->zs.avail_in == 0)
+ gis->is.err = gis->zis->err;
+ if (gis->cb != NULL) {
+ gis->cbarg = APK_BLOB_PTR_LEN(gis->cbprev, (void *) gis->zs.next_in - gis->cbprev);
+ gis->cbprev = gis->zs.next_in;
+ }
+ /* If we hit end of the bitstream (not end
+ * of just this gzip), we need to do the
+ * callback here, as we won't be called again.
+ * For boundaries it should be postponed to not
+ * be called until next gzip read is started. */
+ if (gis->is.err) {
+ gzi_boundary_change(gis);
+ goto ret;
+ }
+ inflateEnd(&gis->zs);
+ if (inflateInit2(&gis->zs, 15+32) != Z_OK)
+ return -ENOMEM;
+ if (gis->cb) goto ret;
+ break;
+ case Z_OK:
+ break;
+ default:
+ gis->is.err = -EIO;
+ break;
+ }
+ }
+
+ret:
+ return size - gis->zs.avail_out;
+}
+
+static void gzi_close(struct apk_istream *is)
+{
+ struct apk_gzip_istream *gis = container_of(is, struct apk_gzip_istream, is);
+
+ inflateEnd(&gis->zs);
+ apk_istream_close(gis->zis);
+ free(gis);
+}
+
+static const struct apk_istream_ops gunzip_istream_ops = {
+ .get_meta = gzi_get_meta,
+ .read = gzi_read,
+ .close = gzi_close,
+};
+
+struct apk_istream *apk_istream_gunzip_mpart(struct apk_istream *is, apk_multipart_cb cb, void *ctx)
+{
+ struct apk_gzip_istream *gis;
+
+ if (IS_ERR_OR_NULL(is)) return ERR_CAST(is);
+
+ gis = malloc(sizeof(*gis) + apk_io_bufsize);
+ if (!gis) goto err;
+
+ *gis = (struct apk_gzip_istream) {
+ .is.ops = &gunzip_istream_ops,
+ .is.buf = (uint8_t*)(gis + 1),
+ .is.buf_size = apk_io_bufsize,
+ .zis = is,
+ .cb = cb,
+ .cbctx = ctx,
+ };
+
+ if (inflateInit2(&gis->zs, 15+32) != Z_OK) {
+ free(gis);
+ goto err;
+ }
+
+ return &gis->is;
+err:
+ apk_istream_close(is);
+ return ERR_PTR(-ENOMEM);
+}
+
+struct apk_gzip_ostream {
+ struct apk_ostream os;
+ struct apk_ostream *output;
+ z_stream zs;
+};
+
+static ssize_t gzo_write(struct apk_ostream *os, const void *ptr, size_t size)
+{
+ struct apk_gzip_ostream *gos = container_of(os, struct apk_gzip_ostream, os);
+ unsigned char buffer[1024];
+ ssize_t have, r;
+
+ gos->zs.avail_in = size;
+ gos->zs.next_in = (void *) ptr;
+ while (gos->zs.avail_in) {
+ gos->zs.avail_out = sizeof(buffer);
+ gos->zs.next_out = buffer;
+ r = deflate(&gos->zs, Z_NO_FLUSH);
+ if (r == Z_STREAM_ERROR)
+ return -EIO;
+ have = sizeof(buffer) - gos->zs.avail_out;
+ if (have != 0) {
+ r = apk_ostream_write(gos->output, buffer, have);
+ if (r != have)
+ return -EIO;
+ }
+ }
+
+ return size;
+}
+
+static int gzo_close(struct apk_ostream *os)
+{
+ struct apk_gzip_ostream *gos = container_of(os, struct apk_gzip_ostream, os);
+ unsigned char buffer[1024];
+ size_t have;
+ int r, rc = 0;
+
+ do {
+ gos->zs.avail_out = sizeof(buffer);
+ gos->zs.next_out = buffer;
+ r = deflate(&gos->zs, Z_FINISH);
+ have = sizeof(buffer) - gos->zs.avail_out;
+ if (apk_ostream_write(gos->output, buffer, have) != have)
+ rc = -EIO;
+ } while (r == Z_OK);
+ r = apk_ostream_close(gos->output);
+ if (r != 0)
+ rc = r;
+
+ deflateEnd(&gos->zs);
+ free(gos);
+
+ return rc;
+}
+
+static const struct apk_ostream_ops gzip_ostream_ops = {
+ .write = gzo_write,
+ .close = gzo_close,
+};
+
+struct apk_ostream *apk_ostream_gzip(struct apk_ostream *output)
+{
+ struct apk_gzip_ostream *gos;
+
+ if (IS_ERR_OR_NULL(output)) return ERR_CAST(output);
+
+ gos = malloc(sizeof(struct apk_gzip_ostream));
+ if (gos == NULL) goto err;
+
+ *gos = (struct apk_gzip_ostream) {
+ .os.ops = &gzip_ostream_ops,
+ .output = output,
+ };
+
+ if (deflateInit2(&gos->zs, 9, Z_DEFLATED, 15 | 16, 8,
+ Z_DEFAULT_STRATEGY) != Z_OK) {
+ free(gos);
+ goto err;
+ }
+
+ return &gos->os;
+err:
+ apk_ostream_close(output);
+ return ERR_PTR(-ENOMEM);
+}
+