xref: /NextBSD/sys/kern/kern_gzio.c (revision 287e3b14e9552995def1802ec9c5034f4adf28ec)
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$");
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 
42 MALLOC_DEFINE(M_GZIO, "gzio", "zlib state");
43 
44 struct 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 
55 static void *	gz_alloc(void *, u_int, u_int);
56 static void	gz_free(void *, void *);
57 static int	gz_write(struct gzio_stream *, void *, u_int, int);
58 
59 struct gzio_stream *
gzio_init(gzio_cb cb,enum gzio_mode mode,size_t bufsz,int level,void * arg)60 gzio_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 
105 fail:
106 	gz_free(NULL, s->gz_buffer);
107 	gz_free(NULL, s);
108 	return (NULL);
109 }
110 
111 int
gzio_write(struct gzio_stream * s,void * data,u_int len)112 gzio_write(struct gzio_stream *s, void *data, u_int len)
113 {
114 
115 	return (gz_write(s, data, len, Z_NO_FLUSH));
116 }
117 
118 int
gzio_flush(struct gzio_stream * s)119 gzio_flush(struct gzio_stream *s)
120 {
121 
122 	return (gz_write(s, NULL, 0, Z_FINISH));
123 }
124 
125 void
gzio_fini(struct gzio_stream * s)126 gzio_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 
134 static void *
gz_alloc(void * arg __unused,u_int n,u_int sz)135 gz_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 
146 static void
gz_free(void * arg __unused,void * ptr)147 gz_free(void *arg __unused, void *ptr)
148 {
149 
150 	free(ptr, M_GZIO);
151 }
152 
153 static int
gz_write(struct gzio_stream * s,void * buf,u_int len,int zflag)154 gz_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