1/*	$NetBSD: cread.c,v 1.9 2009/03/18 17:06:43 cegger Exp $	*/
2
3/*
4 * Copyright (c) 1996
5 *	Matthias Drochner.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 */
28
29/*
30 * Support for compressed bootfiles  (only read)
31 *
32 * - provides copen(), cclose(), cread(), clseek().
33 * - compression parts stripped from zlib:gzio.c
34 * - copied from libsa with small modifications for my MiNT environment.
35 *   Note that everything in the 'tostools' hierarchy is made to function
36 *   in my local MiNT environment.
37 */
38
39/* gzio.c -- IO on .gz files
40 * Copyright (C) 1995-1996 Jean-loup Gailly.
41 * For conditions of distribution and use, see copyright notice in zlib.h
42 */
43
44#define _CREAD_C	/* Turn of open/close/read redefines */
45
46#include <unistd.h>
47#include <string.h>
48#include <memory.h>
49#include <fcntl.h>
50#include <errno.h>
51#include <zlib.h>
52#include <cread.h>
53
54#define __P(proto)		proto
55#define	SOPEN_MAX		1
56
57
58#define EOF (-1) /* needed by compression code */
59
60#ifdef SAVE_MEMORY
61#define Z_BUFSIZE 1024
62#else
63#define Z_BUFSIZE 32*1024
64#endif
65
66static int gz_magic[2] = {0x1f, 0x8b};	/* gzip magic header */
67
68/* gzip flag byte */
69#define ASCII_FLAG	0x01	/* bit 0 set: file probably ascii text */
70#define HEAD_CRC	0x02	/* bit 1 set: header CRC present */
71#define EXTRA_FIELD	0x04	/* bit 2 set: extra field present */
72#define ORIG_NAME	0x08	/* bit 3 set: original file name present */
73#define COMMENT		0x10	/* bit 4 set: file comment present */
74#define RESERVED	0xE0	/* bits 5..7: reserved */
75
76static struct sd {
77	z_stream	stream;
78	int		z_err;	/* error code for last stream operation */
79	int		z_eof;	/* set if end of input file */
80	int		fd;
81	unsigned char	*inbuf;	/* input buffer */
82	unsigned long	crc;	/* crc32 of uncompressed data */
83	int		compressed;	/* 1 if input file is a .gz file */
84} *ss[SOPEN_MAX];
85
86static int		get_byte(struct sd *);
87static unsigned long	getLong(struct sd *);
88static void		check_header(struct sd *);
89
90/* XXX - find suitable headerf ile for these: */
91void	*zcalloc(void *, unsigned int, unsigned int);
92void	zcfree(void *, void *);
93void	zmemcpy(unsigned char *, unsigned char *, unsigned int);
94
95
96/*
97 * compression utilities
98 */
99
100void *
101zcalloc (void *opaque, unsigned items, unsigned size)
102{
103	return(malloc(items * size));
104}
105
106void
107zcfree (void *opaque, void *ptr)
108{
109	free(ptr);
110}
111
112void
113zmemcpy(unsigned char *dest, unsigned char *source, unsigned int len)
114{
115	memcpy(dest, source, len);
116}
117
118static int
119get_byte(struct sd *s)
120{
121	if (s->z_eof)
122		return (EOF);
123
124	if (s->stream.avail_in == 0) {
125		int got;
126
127		errno = 0;
128		got = cread(s->fd, s->inbuf, Z_BUFSIZE);
129		if (got <= 0) {
130			s->z_eof = 1;
131			if (errno) s->z_err = Z_ERRNO;
132			return EOF;
133		}
134		s->stream.avail_in = got;
135		s->stream.next_in = s->inbuf;
136	}
137	s->stream.avail_in--;
138	return *(s->stream.next_in)++;
139}
140
141static unsigned long
142getLong (struct sd *s)
143{
144	unsigned long x = (unsigned long)get_byte(s);
145	int c;
146
147	x += ((unsigned long)get_byte(s)) << 8;
148	x += ((unsigned long)get_byte(s)) << 16;
149	c = get_byte(s);
150	if (c == EOF)
151		s->z_err = Z_DATA_ERROR;
152	x += ((unsigned long)c)<<24;
153	return x;
154}
155
156static void
157check_header(struct sd *s)
158{
159	int method; /* method byte */
160	int flags;  /* flags byte */
161	unsigned int len;
162	int c;
163
164	/* Check the gzip magic header */
165	for (len = 0; len < 2; len++) {
166		c = get_byte(s);
167		if (c == gz_magic[len])
168			continue;
169		if ((c == EOF) && (len == 0))  {
170			/*
171			 * We must not change s->compressed if we are at EOF;
172			 * we may have come to the end of a gzipped file and be
173			 * check to see if another gzipped file is concatenated
174			 * to this one. If one isn't, we still need to be able
175			 * to lseek on this file as a compressed file.
176			 */
177			return;
178		}
179		s->compressed = 0;
180		if (c != EOF) {
181			s->stream.avail_in++;
182			s->stream.next_in--;
183		}
184		s->z_err = s->stream.avail_in != 0 ? Z_OK : Z_STREAM_END;
185		return;
186	}
187	s->compressed = 1;
188	method = get_byte(s);
189	flags = get_byte(s);
190	if (method != Z_DEFLATED || (flags & RESERVED) != 0) {
191		s->z_err = Z_DATA_ERROR;
192		return;
193	}
194
195	/* Discard time, xflags and OS code: */
196	for (len = 0; len < 6; len++)
197		(void)get_byte(s);
198
199	if ((flags & EXTRA_FIELD) != 0) {
200		/* skip the extra field */
201		len  =  (unsigned int)get_byte(s);
202		len += ((unsigned int)get_byte(s)) << 8;
203		/* len is garbage if EOF but the loop below will quit anyway */
204		while (len-- != 0 && get_byte(s) != EOF) /*void*/;
205	}
206	if ((flags & ORIG_NAME) != 0) {
207		/* skip the original file name */
208		while ((c = get_byte(s)) != 0 && c != EOF) /*void*/;
209	}
210	if ((flags & COMMENT) != 0) {
211		/* skip the .gz file comment */
212		while ((c = get_byte(s)) != 0 && c != EOF) /*void*/;
213	}
214	if ((flags & HEAD_CRC) != 0) {  /* skip the header crc */
215		for (len = 0; len < 2; len++)
216			(void)get_byte(s);
217	}
218	s->z_err = s->z_eof ? Z_DATA_ERROR : Z_OK;
219}
220
221/*
222 * new open(), close(), read(), lseek()
223 */
224
225int
226copen(const char *fname, int mode)
227{
228	int fd;
229	struct sd *s = 0;
230
231	if ( ((fd = open(fname, mode)) == -1) || (mode != O_RDONLY) )
232		/* compression only for read */
233		return(fd);
234
235	ss[fd] = s = malloc(sizeof(struct sd));
236	if (s == 0)
237		goto errout;
238	memset(s, 0, sizeof(struct sd));
239
240	if (inflateInit2(&(s->stream), -15) != Z_OK)
241		goto errout;
242
243	s->stream.next_in  = s->inbuf = (unsigned char*)malloc(Z_BUFSIZE);
244	if (s->inbuf == 0) {
245		inflateEnd(&(s->stream));
246		goto errout;
247	}
248
249	s->fd = fd;
250	check_header(s); /* skip the .gz header */
251	return(fd);
252
253errout:
254	if (s != 0)
255		free(s);
256	close(fd);
257	return (-1);
258}
259
260int
261cclose(int fd)
262{
263	struct sd *s;
264
265	s = ss[fd];
266
267	inflateEnd(&(s->stream));
268
269	free(s->inbuf);
270	free(s);
271
272	return (close(fd));
273}
274
275size_t
276cread(int fd, void *buf, size_t len)
277{
278	struct sd *s;
279	unsigned char *start = buf; /* starting point for crc computation */
280
281	s = ss[fd];
282
283	if (s->z_err == Z_DATA_ERROR || s->z_err == Z_ERRNO)
284		return (-1);
285	if (s->z_err == Z_STREAM_END)
286		return (0);  /* EOF */
287
288	s->stream.next_out = buf;
289	s->stream.avail_out = len;
290
291	while (s->stream.avail_out != 0) {
292
293		if (s->compressed == 0) {
294			/* Copy first the lookahead bytes: */
295			unsigned int n = s->stream.avail_in;
296			if (n > s->stream.avail_out)
297				n = s->stream.avail_out;
298			if (n > 0) {
299				zmemcpy(s->stream.next_out,
300					s->stream.next_in, n);
301				s->stream.next_out  += n;
302				s->stream.next_in   += n;
303				s->stream.avail_out -= n;
304				s->stream.avail_in  -= n;
305			}
306			if (s->stream.avail_out > 0) {
307				int got;
308				got = read(s->fd, s->stream.next_out,
309					    s->stream.avail_out);
310				if (got == -1)
311					return (got);
312				s->stream.avail_out -= got;
313			}
314			return (int)(len - s->stream.avail_out);
315		}
316
317		if (s->stream.avail_in == 0 && !s->z_eof) {
318			int got;
319			errno = 0;
320			got = read(fd, s->inbuf, Z_BUFSIZE);
321			if (got <= 0) {
322				s->z_eof = 1;
323				if (errno) {
324					s->z_err = Z_ERRNO;
325					break;
326				}
327			}
328			s->stream.avail_in = got;
329			s->stream.next_in = s->inbuf;
330		}
331
332		s->z_err = inflate(&(s->stream), Z_NO_FLUSH);
333
334		if (s->z_err == Z_STREAM_END) {
335			/* Check CRC and original size */
336			s->crc = crc32(s->crc, start, (unsigned int)
337					(s->stream.next_out - start));
338			start = s->stream.next_out;
339
340			if (getLong(s) != s->crc ||
341			    getLong(s) != s->stream.total_out) {
342
343				s->z_err = Z_DATA_ERROR;
344			} else {
345				/* Check for concatenated .gz files: */
346				check_header(s);
347				if (s->z_err == Z_OK) {
348					inflateReset(&(s->stream));
349					s->crc = crc32(0L, Z_NULL, 0);
350				}
351			}
352		}
353		if (s->z_err != Z_OK || s->z_eof)
354			break;
355	}
356
357	s->crc = crc32(s->crc, start,
358		       (unsigned int)(s->stream.next_out - start));
359
360	return (int)(len - s->stream.avail_out);
361}
362
363off_t
364clseek(int fd, off_t offset, int where)
365{
366	struct sd *s;
367
368	s = ss[fd];
369
370	if(s->compressed == 0) {
371		off_t res = lseek(fd, offset, where);
372		if (res != (off_t)-1) {
373			/* make sure the lookahead buffer is invalid */
374			s->stream.avail_in = 0;
375		}
376		return (res);
377	}
378
379	switch(where) {
380	case SEEK_CUR:
381		    offset += s->stream.total_out;
382	case SEEK_SET:
383		/* if seek backwards, simply start from the beginning */
384		if (offset < s->stream.total_out) {
385			off_t res;
386			void *sav_inbuf;
387
388			res = lseek(fd, 0, SEEK_SET);
389			if(res == (off_t)-1)
390			    return(res);
391			/* ??? perhaps fallback to close / open */
392
393			inflateEnd(&(s->stream));
394
395			sav_inbuf = s->inbuf; /* don't allocate again */
396			memset(s, 0, sizeof(struct sd)); /* this resets total_out to 0! */
397
398			inflateInit2(&(s->stream), -15);
399			s->stream.next_in = s->inbuf = sav_inbuf;
400
401			s->fd = fd;
402			check_header(s); /* skip the .gz header */
403		}
404
405		    /* to seek forwards, throw away data */
406		if (offset > s->stream.total_out) {
407			off_t toskip = offset - s->stream.total_out;
408
409			while (toskip > 0) {
410#define DUMMYBUFSIZE 256
411				char dummybuf[DUMMYBUFSIZE];
412				off_t len = toskip;
413				if (len > DUMMYBUFSIZE) len = DUMMYBUFSIZE;
414				if (cread(fd, dummybuf, len) != len) {
415					errno = EINVAL;
416					return ((off_t)-1);
417				}
418				toskip -= len;
419			}
420		}
421#ifdef DEBUG
422		if (offset != s->stream.total_out)
423			panic("lseek compressed");
424#endif
425		return (offset);
426	case SEEK_END:
427		errno = EINVAL;
428		break;
429	default:
430		errno = EINVAL;
431	}
432
433	return((off_t)-1);
434}
435