gunzip.c 5.94 KB
Newer Older
1 2
/* gunzip.c - Alpine Package Keeper (APK)
 *
3
 * Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
4 5
 * All rights reserved.
 *
6
 * This program is free software; you can redistribute it and/or modify it
7 8 9 10
 * 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.
 */

11
#include <errno.h>
12 13 14 15 16 17 18 19 20 21 22 23 24
#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_bstream *bs;
	z_stream zs;
25
	int err;
26 27 28

	apk_multipart_cb cb;
	void *cbctx;
29
	void *cbprev;
30
	apk_blob_t cbarg;
31 32
};

33 34 35 36
static void gzi_get_meta(void *stream, struct apk_file_meta *meta)
{
	struct apk_gzip_istream *gis =
		container_of(stream, struct apk_gzip_istream, is);
37
	apk_bstream_get_meta(gis->bs, meta);
38 39
}

40
static ssize_t gzi_read(void *stream, void *ptr, size_t size)
41 42 43
{
	struct apk_gzip_istream *gis =
		container_of(stream, struct apk_gzip_istream, is);
44
	int r;
45

46 47 48 49 50
	if (gis->err != 0) {
		if (gis->err > 0)
			return 0;
		return gis->err;
	}
51 52 53 54 55 56 57

	if (ptr == NULL)
		return apk_istream_skip(&gis->is, size);

	gis->zs.avail_out = size;
	gis->zs.next_out  = ptr;

58
	while (gis->zs.avail_out != 0 && gis->err == 0) {
59 60 61 62 63 64 65 66 67 68 69 70
		if (!APK_BLOB_IS_NULL(gis->cbarg)) {
			r = gis->cb(gis->cbctx,
				    gis->err ? APK_MPART_END : APK_MPART_BOUNDARY,
				    gis->cbarg);
			if (r > 0)
				r = -ECANCELED;
			if (r != 0) {
				gis->err = r;
				goto ret;
			}
			gis->cbarg = APK_BLOB_NULL;
		}
71
		if (gis->zs.avail_in == 0) {
72 73
			apk_blob_t blob;

Timo Teräs's avatar
Timo Teräs committed
74 75
			if (gis->cb != NULL && gis->cbprev != NULL &&
			    gis->cbprev != gis->zs.next_in) {
76 77 78
				gis->cb(gis->cbctx, APK_MPART_DATA,
					APK_BLOB_PTR_LEN(gis->cbprev,
					(void *)gis->zs.next_in - gis->cbprev));
79
			}
80
			blob = apk_bstream_read(gis->bs, APK_BLOB_NULL);
81
			gis->cbprev = blob.ptr;
82
			gis->zs.avail_in = blob.len;
83
			gis->zs.next_in = (void *) gis->cbprev;
84 85
			if (blob.len < 0) {
				gis->err = blob.len;
86
				goto ret;
87
			} else if (gis->zs.avail_in == 0) {
Timo Teräs's avatar
Timo Teräs committed
88
				gis->err = 1;
89 90 91
				if (gis->cb != NULL) {
					r = gis->cb(gis->cbctx, APK_MPART_END,
						    APK_BLOB_NULL);
Timo Teräs's avatar
Timo Teräs committed
92
					if (r > 0)
93
						r = -ECANCELED;
Timo Teräs's avatar
Timo Teräs committed
94
					if (r != 0)
95
						gis->err = r;
Timo Teräs's avatar
Timo Teräs committed
96
				}
97
				goto ret;
98 99 100
			}
		}

101 102 103
		r = inflate(&gis->zs, Z_NO_FLUSH);
		switch (r) {
		case Z_STREAM_END:
104
			/* Digest the inflated bytes */
Timo Teräs's avatar
Timo Teräs committed
105 106 107
			if ((gis->bs->flags & APK_BSTREAM_EOF) &&
			    gis->zs.avail_in == 0)
				gis->err = 1;
108
			if (gis->cb != NULL) {
109 110 111 112 113 114 115 116 117
				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->err) {
Timo Teräs's avatar
Timo Teräs committed
118 119
				r = gis->cb(gis->cbctx,
					    gis->err ? APK_MPART_END : APK_MPART_BOUNDARY,
120
					    gis->cbarg);
121 122
				if (r > 0)
					r = -ECANCELED;
Timo Teräs's avatar
Timo Teräs committed
123
				goto ret;
124
			}
125 126
			inflateEnd(&gis->zs);
			if (inflateInit2(&gis->zs, 15+32) != Z_OK)
127
				return -ENOMEM;
128 129 130 131
			break;
		case Z_OK:
			break;
		default:
132
			gis->err = -EIO;
133
			break;
134
		}
135 136
	}

137 138
ret:
	if (size - gis->zs.avail_out == 0)
Timo Teräs's avatar
Timo Teräs committed
139
		return gis->err < 0 ? gis->err : 0;
140 141 142 143

	return size - gis->zs.avail_out;
}

144
static void gzi_close(void *stream)
145 146 147 148 149
{
	struct apk_gzip_istream *gis =
		container_of(stream, struct apk_gzip_istream, is);

	inflateEnd(&gis->zs);
150
	apk_bstream_close(gis->bs, NULL);
151 152 153
	free(gis);
}

154 155 156 157 158 159
static const struct apk_istream_ops gunzip_istream_ops = {
	.get_meta = gzi_get_meta,
	.read = gzi_read,
	.close = gzi_close,
};

160
struct apk_istream *apk_bstream_gunzip_mpart(struct apk_bstream *bs,
161
					     apk_multipart_cb cb, void *ctx)
162 163 164
{
	struct apk_gzip_istream *gis;

165
	if (IS_ERR_OR_NULL(bs)) return ERR_CAST(bs);
166

167
	gis = malloc(sizeof(struct apk_gzip_istream));
Timo Teräs's avatar
Timo Teräs committed
168
	if (!gis) goto err;
169 170

	*gis = (struct apk_gzip_istream) {
171
		.is.ops = &gunzip_istream_ops,
172
		.bs = bs,
173 174
		.cb = cb,
		.cbctx = ctx,
175 176 177 178
	};

	if (inflateInit2(&gis->zs, 15+32) != Z_OK) {
		free(gis);
179
		goto err;
180 181 182
	}

	return &gis->is;
183
err:
184
	apk_bstream_close(bs, NULL);
185
	return ERR_PTR(-ENOMEM);
186 187 188 189 190 191 192 193
}

struct apk_gzip_ostream {
	struct apk_ostream os;
	struct apk_ostream *output;
	z_stream zs;
};

194
static ssize_t gzo_write(void *stream, const void *ptr, size_t size)
195 196
{
	struct apk_gzip_ostream *gos = (struct apk_gzip_ostream *) stream;
Timo Teräs's avatar
Timo Teräs committed
197
	unsigned char buffer[1024];
198
	ssize_t have, r;
199 200 201 202

	gos->zs.avail_in = size;
	gos->zs.next_in = (void *) ptr;
	while (gos->zs.avail_in) {
Timo Teräs's avatar
Timo Teräs committed
203 204
		gos->zs.avail_out = sizeof(buffer);
		gos->zs.next_out = buffer;
205 206
		r = deflate(&gos->zs, Z_NO_FLUSH);
		if (r == Z_STREAM_ERROR)
207
			return -EIO;
Timo Teräs's avatar
Timo Teräs committed
208
		have = sizeof(buffer) - gos->zs.avail_out;
209
		if (have != 0) {
210
			r = apk_ostream_write(gos->output, buffer, have);
211
			if (r != have)
212
				return -EIO;
213 214 215 216 217 218
		}
	}

	return size;
}

219
static int gzo_close(void *stream)
220 221
{
	struct apk_gzip_ostream *gos = (struct apk_gzip_ostream *) stream;
Timo Teräs's avatar
Timo Teräs committed
222
	unsigned char buffer[1024];
223
	size_t have;
224
	int r, rc = 0;
225

226 227 228 229 230
	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;
231
		if (apk_ostream_write(gos->output, buffer, have) != have)
232
			rc = -EIO;
233
	} while (r == Z_OK);
234
	r = apk_ostream_close(gos->output);
235 236
	if (r != 0)
		rc = r;
237 238 239

	deflateEnd(&gos->zs);
	free(stream);
240 241

	return rc;
242 243
}

244 245 246 247 248
static const struct apk_ostream_ops gzip_ostream_ops = {
	.write = gzo_write,
	.close = gzo_close,
};

249 250 251 252
struct apk_ostream *apk_ostream_gzip(struct apk_ostream *output)
{
	struct apk_gzip_ostream *gos;

253
	if (IS_ERR_OR_NULL(output)) return ERR_CAST(output);
254 255

	gos = malloc(sizeof(struct apk_gzip_ostream));
256
	if (gos == NULL) goto err;
257 258

	*gos = (struct apk_gzip_ostream) {
259
		.os.ops = &gzip_ostream_ops,
260 261 262 263 264 265 266 267 268 269 270
		.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:
271
	apk_ostream_close(output);
272
	return ERR_PTR(-ENOMEM);
273 274
}