1/*-
2 * Copyright (c) 2014 Mark Johnston <markj@FreeBSD.org>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in
11 *    the documentation and/or other materials provided with the
12 *    distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: releng/11.0/sys/kern/kern_gzio.c 281855 2015-04-22 14:38:58Z rodrigc $");
29
30#include <sys/param.h>
31
32#include <sys/gzio.h>
33#include <sys/kernel.h>
34#include <sys/malloc.h>
35#include <sys/zutil.h>
36
37#define	KERN_GZ_HDRLEN		10	/* gzip header length */
38#define	KERN_GZ_TRAILERLEN	8	/* gzip trailer length */
39#define	KERN_GZ_MAGIC1		0x1f	/* first magic byte */
40#define	KERN_GZ_MAGIC2		0x8b	/* second magic byte */
41
42MALLOC_DEFINE(M_GZIO, "gzio", "zlib state");
43
44struct gzio_stream {
45	uint8_t *	gz_buffer;	/* output buffer */
46	size_t		gz_bufsz;	/* total buffer size */
47	off_t		gz_off;		/* offset into the output stream */
48	enum gzio_mode	gz_mode;	/* stream mode */
49	uint32_t	gz_crc;		/* stream CRC32 */
50	gzio_cb		gz_cb;		/* output callback */
51	void *		gz_arg;		/* private callback arg */
52	z_stream	gz_stream;	/* zlib state */
53};
54
55static void *	gz_alloc(void *, u_int, u_int);
56static void	gz_free(void *, void *);
57static int	gz_write(struct gzio_stream *, void *, u_int, int);
58
59struct gzio_stream *
60gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg)
61{
62	struct gzio_stream *s;
63	uint8_t *hdr;
64	int error;
65
66	if (bufsz < KERN_GZ_HDRLEN)
67		return (NULL);
68	if (mode != GZIO_DEFLATE)
69		return (NULL);
70
71	s = gz_alloc(NULL, 1, sizeof(*s));
72	s->gz_bufsz = bufsz;
73	s->gz_buffer = gz_alloc(NULL, 1, s->gz_bufsz);
74	s->gz_mode = mode;
75	s->gz_crc = ~0U;
76	s->gz_cb = cb;
77	s->gz_arg = arg;
78
79	s->gz_stream.zalloc = gz_alloc;
80	s->gz_stream.zfree = gz_free;
81	s->gz_stream.opaque = NULL;
82	s->gz_stream.next_in = Z_NULL;
83	s->gz_stream.avail_in = 0;
84
85	error = deflateInit2(&s->gz_stream, level, Z_DEFLATED, -MAX_WBITS,
86	    DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
87	if (error != 0)
88		goto fail;
89
90	s->gz_stream.avail_out = s->gz_bufsz;
91	s->gz_stream.next_out = s->gz_buffer;
92
93	/* Write the gzip header to the output buffer. */
94	hdr = s->gz_buffer;
95	memset(hdr, 0, KERN_GZ_HDRLEN);
96	hdr[0] = KERN_GZ_MAGIC1;
97	hdr[1] = KERN_GZ_MAGIC2;
98	hdr[2] = Z_DEFLATED;
99	hdr[9] = OS_CODE;
100	s->gz_stream.next_out += KERN_GZ_HDRLEN;
101	s->gz_stream.avail_out -= KERN_GZ_HDRLEN;
102
103	return (s);
104
105fail:
106	gz_free(NULL, s->gz_buffer);
107	gz_free(NULL, s);
108	return (NULL);
109}
110
111int
112gzio_write(struct gzio_stream *s, void *data, u_int len)
113{
114
115	return (gz_write(s, data, len, Z_NO_FLUSH));
116}
117
118int
119gzio_flush(struct gzio_stream *s)
120{
121
122	return (gz_write(s, NULL, 0, Z_FINISH));
123}
124
125void
126gzio_fini(struct gzio_stream *s)
127{
128
129	(void)deflateEnd(&s->gz_stream);
130	gz_free(NULL, s->gz_buffer);
131	gz_free(NULL, s);
132}
133
134static void *
135gz_alloc(void *arg __unused, u_int n, u_int sz)
136{
137
138	/*
139	 * Memory for zlib state is allocated using M_NODUMP since it may be
140	 * used to compress a kernel dump, and we don't want zlib to attempt to
141	 * compress its own state.
142	 */
143	return (malloc(n * sz, M_GZIO, M_WAITOK | M_ZERO | M_NODUMP));
144}
145
146static void
147gz_free(void *arg __unused, void *ptr)
148{
149
150	free(ptr, M_GZIO);
151}
152
153static int
154gz_write(struct gzio_stream *s, void *buf, u_int len, int zflag)
155{
156	uint8_t trailer[KERN_GZ_TRAILERLEN];
157	size_t room;
158	int error, zerror;
159
160	KASSERT(zflag == Z_FINISH || zflag == Z_NO_FLUSH,
161	    ("unexpected flag %d", zflag));
162	KASSERT(s->gz_mode == GZIO_DEFLATE,
163	    ("invalid stream mode %d", s->gz_mode));
164
165	if (len > 0) {
166		s->gz_stream.avail_in = len;
167		s->gz_stream.next_in = buf;
168		s->gz_crc = crc32_raw(buf, len, s->gz_crc);
169	} else
170		s->gz_crc ^= ~0U;
171
172	error = 0;
173	do {
174		zerror = deflate(&s->gz_stream, zflag);
175		if (zerror != Z_OK && zerror != Z_STREAM_END) {
176			error = EIO;
177			break;
178		}
179
180		if (s->gz_stream.avail_out == 0 || zerror == Z_STREAM_END) {
181			/*
182			 * Our output buffer is full or there's nothing left
183			 * to produce, so we're flushing the buffer.
184			 */
185			len = s->gz_bufsz - s->gz_stream.avail_out;
186			if (zerror == Z_STREAM_END) {
187				/*
188				 * Try to pack as much of the trailer into the
189				 * output buffer as we can.
190				 */
191				((uint32_t *)trailer)[0] = s->gz_crc;
192				((uint32_t *)trailer)[1] =
193				    s->gz_stream.total_in;
194				room = MIN(KERN_GZ_TRAILERLEN,
195				    s->gz_bufsz - len);
196				memcpy(s->gz_buffer + len, trailer, room);
197				len += room;
198			}
199
200			error = s->gz_cb(s->gz_buffer, len, s->gz_off,
201			    s->gz_arg);
202			if (error != 0)
203				break;
204
205			s->gz_off += len;
206			s->gz_stream.next_out = s->gz_buffer;
207			s->gz_stream.avail_out = s->gz_bufsz;
208
209			/*
210			 * If we couldn't pack the trailer into the output
211			 * buffer, write it out now.
212			 */
213			if (zerror == Z_STREAM_END && room < KERN_GZ_TRAILERLEN)
214				error = s->gz_cb(trailer + room,
215				    KERN_GZ_TRAILERLEN - room, s->gz_off,
216				    s->gz_arg);
217		}
218	} while (zerror != Z_STREAM_END &&
219	    (zflag == Z_FINISH || s->gz_stream.avail_in > 0));
220
221	return (error);
222}
223