1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2014, 2017 Mark Johnston <markj@FreeBSD.org> 5 * Copyright (c) 2017 Conrad Meyer <cem@FreeBSD.org> 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions are 9 * 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 14 * the documentation and/or other materials provided with the 15 * distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 /* 31 * Subroutines used for writing compressed user process and kernel core dumps. 32 */ 33 34 #include <sys/cdefs.h> 35 __FBSDID("$FreeBSD$"); 36 37 #include "opt_gzio.h" 38 #include "opt_zstdio.h" 39 40 #include <sys/param.h> 41 #include <sys/systm.h> 42 43 #include <sys/compressor.h> 44 #include <sys/kernel.h> 45 #include <sys/linker_set.h> 46 #include <sys/malloc.h> 47 48 MALLOC_DEFINE(M_COMPRESS, "compressor", "kernel compression subroutines"); 49 50 struct compressor_methods { 51 int format; 52 void *(* const init)(size_t, int); 53 void (* const reset)(void *); 54 int (* const write)(void *, void *, size_t, compressor_cb_t, void *); 55 void (* const fini)(void *); 56 }; 57 58 struct compressor { 59 const struct compressor_methods *methods; 60 compressor_cb_t cb; 61 void *priv; 62 void *arg; 63 }; 64 65 SET_DECLARE(compressors, struct compressor_methods); 66 67 #ifdef GZIO 68 69 #include <contrib/zlib/zutil.h> 70 71 struct gz_stream { 72 uint8_t *gz_buffer; /* output buffer */ 73 size_t gz_bufsz; /* output buffer size */ 74 off_t gz_off; /* offset into the output stream */ 75 uint32_t gz_crc; /* stream CRC32 */ 76 z_stream gz_stream; /* zlib state */ 77 }; 78 79 static void *gz_init(size_t maxiosize, int level); 80 static void gz_reset(void *stream); 81 static int gz_write(void *stream, void *data, size_t len, compressor_cb_t, 82 void *); 83 static void gz_fini(void *stream); 84 85 static void * 86 gz_alloc(void *arg __unused, u_int n, u_int sz) 87 { 88 89 /* 90 * Memory for zlib state is allocated using M_NODUMP since it may be 91 * used to compress a kernel dump, and we don't want zlib to attempt to 92 * compress its own state. 93 */ 94 return (malloc(n * sz, M_COMPRESS, M_WAITOK | M_ZERO | M_NODUMP)); 95 } 96 97 static void 98 gz_free(void *arg __unused, void *ptr) 99 { 100 101 free(ptr, M_COMPRESS); 102 } 103 104 static void * 105 gz_init(size_t maxiosize, int level) 106 { 107 struct gz_stream *s; 108 int error; 109 110 s = gz_alloc(NULL, 1, roundup2(sizeof(*s), PAGE_SIZE)); 111 s->gz_buffer = gz_alloc(NULL, 1, maxiosize); 112 s->gz_bufsz = maxiosize; 113 114 s->gz_stream.zalloc = gz_alloc; 115 s->gz_stream.zfree = gz_free; 116 s->gz_stream.opaque = NULL; 117 s->gz_stream.next_in = Z_NULL; 118 s->gz_stream.avail_in = 0; 119 120 error = deflateInit2(&s->gz_stream, level, Z_DEFLATED, -MAX_WBITS, 121 DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); 122 if (error != 0) 123 goto fail; 124 125 gz_reset(s); 126 127 return (s); 128 129 fail: 130 gz_free(NULL, s); 131 return (NULL); 132 } 133 134 static void 135 gz_reset(void *stream) 136 { 137 struct gz_stream *s; 138 uint8_t *hdr; 139 const size_t hdrlen = 10; 140 141 s = stream; 142 s->gz_off = 0; 143 s->gz_crc = crc32(0L, Z_NULL, 0); 144 145 (void)deflateReset(&s->gz_stream); 146 s->gz_stream.avail_out = s->gz_bufsz; 147 s->gz_stream.next_out = s->gz_buffer; 148 149 /* Write the gzip header to the output buffer. */ 150 hdr = s->gz_buffer; 151 memset(hdr, 0, hdrlen); 152 hdr[0] = 0x1f; 153 hdr[1] = 0x8b; 154 hdr[2] = Z_DEFLATED; 155 hdr[9] = OS_CODE; 156 s->gz_stream.next_out += hdrlen; 157 s->gz_stream.avail_out -= hdrlen; 158 } 159 160 static int 161 gz_write(void *stream, void *data, size_t len, compressor_cb_t cb, 162 void *arg) 163 { 164 struct gz_stream *s; 165 uint8_t trailer[8]; 166 size_t room; 167 int error, zerror, zflag; 168 169 s = stream; 170 zflag = data == NULL ? Z_FINISH : Z_NO_FLUSH; 171 172 if (len > 0) { 173 s->gz_stream.avail_in = len; 174 s->gz_stream.next_in = data; 175 s->gz_crc = crc32(s->gz_crc, data, len); 176 } 177 178 error = 0; 179 do { 180 zerror = deflate(&s->gz_stream, zflag); 181 if (zerror != Z_OK && zerror != Z_STREAM_END) { 182 error = EIO; 183 break; 184 } 185 186 if (s->gz_stream.avail_out == 0 || zerror == Z_STREAM_END) { 187 /* 188 * Our output buffer is full or there's nothing left 189 * to produce, so we're flushing the buffer. 190 */ 191 len = s->gz_bufsz - s->gz_stream.avail_out; 192 if (zerror == Z_STREAM_END) { 193 /* 194 * Try to pack as much of the trailer into the 195 * output buffer as we can. 196 */ 197 ((uint32_t *)trailer)[0] = s->gz_crc; 198 ((uint32_t *)trailer)[1] = 199 s->gz_stream.total_in; 200 room = MIN(sizeof(trailer), 201 s->gz_bufsz - len); 202 memcpy(s->gz_buffer + len, trailer, room); 203 len += room; 204 } 205 206 error = cb(s->gz_buffer, len, s->gz_off, arg); 207 if (error != 0) 208 break; 209 210 s->gz_off += len; 211 s->gz_stream.next_out = s->gz_buffer; 212 s->gz_stream.avail_out = s->gz_bufsz; 213 214 /* 215 * If we couldn't pack the trailer into the output 216 * buffer, write it out now. 217 */ 218 if (zerror == Z_STREAM_END && room < sizeof(trailer)) 219 error = cb(trailer + room, 220 sizeof(trailer) - room, s->gz_off, arg); 221 } 222 } while (zerror != Z_STREAM_END && 223 (zflag == Z_FINISH || s->gz_stream.avail_in > 0)); 224 225 return (error); 226 } 227 228 static void 229 gz_fini(void *stream) 230 { 231 struct gz_stream *s; 232 233 s = stream; 234 (void)deflateEnd(&s->gz_stream); 235 gz_free(NULL, s->gz_buffer); 236 gz_free(NULL, s); 237 } 238 239 struct compressor_methods gzip_methods = { 240 .format = COMPRESS_GZIP, 241 .init = gz_init, 242 .reset = gz_reset, 243 .write = gz_write, 244 .fini = gz_fini, 245 }; 246 DATA_SET(compressors, gzip_methods); 247 248 #endif /* GZIO */ 249 250 #ifdef ZSTDIO 251 252 #define ZSTD_STATIC_LINKING_ONLY 253 #include <contrib/zstd/lib/zstd.h> 254 255 struct zstdio_stream { 256 ZSTD_CCtx *zst_stream; 257 ZSTD_inBuffer zst_inbuffer; 258 ZSTD_outBuffer zst_outbuffer; 259 uint8_t * zst_buffer; /* output buffer */ 260 size_t zst_maxiosz; /* Max output IO size */ 261 off_t zst_off; /* offset into the output stream */ 262 void * zst_static_wkspc; 263 }; 264 265 static void *zstdio_init(size_t maxiosize, int level); 266 static void zstdio_reset(void *stream); 267 static int zstdio_write(void *stream, void *data, size_t len, 268 compressor_cb_t, void *); 269 static void zstdio_fini(void *stream); 270 271 static void * 272 zstdio_init(size_t maxiosize, int level) 273 { 274 ZSTD_CCtx *dump_compressor; 275 struct zstdio_stream *s; 276 void *wkspc, *owkspc, *buffer; 277 size_t wkspc_size, buf_size, rc; 278 279 s = NULL; 280 wkspc_size = ZSTD_estimateCStreamSize(level); 281 owkspc = wkspc = malloc(wkspc_size + 8, M_COMPRESS, 282 M_WAITOK | M_NODUMP); 283 /* Zstd API requires 8-byte alignment. */ 284 if ((uintptr_t)wkspc % 8 != 0) 285 wkspc = (void *)roundup2((uintptr_t)wkspc, 8); 286 287 dump_compressor = ZSTD_initStaticCCtx(wkspc, wkspc_size); 288 if (dump_compressor == NULL) { 289 printf("%s: workspace too small.\n", __func__); 290 goto out; 291 } 292 293 rc = ZSTD_CCtx_setParameter(dump_compressor, ZSTD_c_checksumFlag, 1); 294 if (ZSTD_isError(rc)) { 295 printf("%s: error setting checksumFlag: %s\n", __func__, 296 ZSTD_getErrorName(rc)); 297 goto out; 298 } 299 rc = ZSTD_CCtx_setParameter(dump_compressor, ZSTD_c_compressionLevel, 300 level); 301 if (ZSTD_isError(rc)) { 302 printf("%s: error setting compressLevel: %s\n", __func__, 303 ZSTD_getErrorName(rc)); 304 goto out; 305 } 306 307 buf_size = ZSTD_CStreamOutSize() * 2; 308 buffer = malloc(buf_size, M_COMPRESS, M_WAITOK | M_NODUMP); 309 310 s = malloc(sizeof(*s), M_COMPRESS, M_NODUMP | M_WAITOK); 311 s->zst_buffer = buffer; 312 s->zst_outbuffer.dst = buffer; 313 s->zst_outbuffer.size = buf_size; 314 s->zst_maxiosz = maxiosize; 315 s->zst_stream = dump_compressor; 316 s->zst_static_wkspc = owkspc; 317 318 zstdio_reset(s); 319 320 out: 321 if (s == NULL) 322 free(owkspc, M_COMPRESS); 323 return (s); 324 } 325 326 static void 327 zstdio_reset(void *stream) 328 { 329 struct zstdio_stream *s; 330 size_t res; 331 332 s = stream; 333 res = ZSTD_resetCStream(s->zst_stream, 0); 334 if (ZSTD_isError(res)) 335 panic("%s: could not reset stream %p: %s\n", __func__, s, 336 ZSTD_getErrorName(res)); 337 338 s->zst_off = 0; 339 s->zst_inbuffer.src = NULL; 340 s->zst_inbuffer.size = 0; 341 s->zst_inbuffer.pos = 0; 342 s->zst_outbuffer.pos = 0; 343 } 344 345 static int 346 zst_flush_intermediate(struct zstdio_stream *s, compressor_cb_t cb, void *arg) 347 { 348 size_t bytes_to_dump; 349 int error; 350 351 /* Flush as many full output blocks as possible. */ 352 /* XXX: 4096 is arbitrary safe HDD block size for kernel dumps */ 353 while (s->zst_outbuffer.pos >= 4096) { 354 bytes_to_dump = rounddown(s->zst_outbuffer.pos, 4096); 355 356 if (bytes_to_dump > s->zst_maxiosz) 357 bytes_to_dump = s->zst_maxiosz; 358 359 error = cb(s->zst_buffer, bytes_to_dump, s->zst_off, arg); 360 if (error != 0) 361 return (error); 362 363 /* 364 * Shift any non-full blocks up to the front of the output 365 * buffer. 366 */ 367 s->zst_outbuffer.pos -= bytes_to_dump; 368 memmove(s->zst_outbuffer.dst, 369 (char *)s->zst_outbuffer.dst + bytes_to_dump, 370 s->zst_outbuffer.pos); 371 s->zst_off += bytes_to_dump; 372 } 373 return (0); 374 } 375 376 static int 377 zstdio_flush(struct zstdio_stream *s, compressor_cb_t cb, void *arg) 378 { 379 size_t rc, lastpos; 380 int error; 381 382 /* 383 * Positive return indicates unflushed data remaining; need to call 384 * endStream again after clearing out room in output buffer. 385 */ 386 rc = 1; 387 lastpos = s->zst_outbuffer.pos; 388 while (rc > 0) { 389 rc = ZSTD_endStream(s->zst_stream, &s->zst_outbuffer); 390 if (ZSTD_isError(rc)) { 391 printf("%s: ZSTD_endStream failed (%s)\n", __func__, 392 ZSTD_getErrorName(rc)); 393 return (EIO); 394 } 395 if (lastpos == s->zst_outbuffer.pos) { 396 printf("%s: did not make forward progress endStream %zu\n", 397 __func__, lastpos); 398 return (EIO); 399 } 400 401 error = zst_flush_intermediate(s, cb, arg); 402 if (error != 0) 403 return (error); 404 405 lastpos = s->zst_outbuffer.pos; 406 } 407 408 /* 409 * We've already done an intermediate flush, so all full blocks have 410 * been written. Only a partial block remains. Padding happens in a 411 * higher layer. 412 */ 413 if (s->zst_outbuffer.pos != 0) { 414 error = cb(s->zst_buffer, s->zst_outbuffer.pos, s->zst_off, 415 arg); 416 if (error != 0) 417 return (error); 418 } 419 420 return (0); 421 } 422 423 static int 424 zstdio_write(void *stream, void *data, size_t len, compressor_cb_t cb, 425 void *arg) 426 { 427 struct zstdio_stream *s; 428 size_t lastpos, rc; 429 int error; 430 431 s = stream; 432 if (data == NULL) 433 return (zstdio_flush(s, cb, arg)); 434 435 s->zst_inbuffer.src = data; 436 s->zst_inbuffer.size = len; 437 s->zst_inbuffer.pos = 0; 438 lastpos = 0; 439 440 while (s->zst_inbuffer.pos < s->zst_inbuffer.size) { 441 rc = ZSTD_compressStream(s->zst_stream, &s->zst_outbuffer, 442 &s->zst_inbuffer); 443 if (ZSTD_isError(rc)) { 444 printf("%s: Compress failed on %p! (%s)\n", 445 __func__, data, ZSTD_getErrorName(rc)); 446 return (EIO); 447 } 448 449 if (lastpos == s->zst_inbuffer.pos) { 450 /* 451 * XXX: May need flushStream to make forward progress 452 */ 453 printf("ZSTD: did not make forward progress @pos %zu\n", 454 lastpos); 455 return (EIO); 456 } 457 lastpos = s->zst_inbuffer.pos; 458 459 error = zst_flush_intermediate(s, cb, arg); 460 if (error != 0) 461 return (error); 462 } 463 return (0); 464 } 465 466 static void 467 zstdio_fini(void *stream) 468 { 469 struct zstdio_stream *s; 470 471 s = stream; 472 if (s->zst_static_wkspc != NULL) 473 free(s->zst_static_wkspc, M_COMPRESS); 474 else 475 ZSTD_freeCCtx(s->zst_stream); 476 free(s->zst_buffer, M_COMPRESS); 477 free(s, M_COMPRESS); 478 } 479 480 static struct compressor_methods zstd_methods = { 481 .format = COMPRESS_ZSTD, 482 .init = zstdio_init, 483 .reset = zstdio_reset, 484 .write = zstdio_write, 485 .fini = zstdio_fini, 486 }; 487 DATA_SET(compressors, zstd_methods); 488 489 #endif /* ZSTDIO */ 490 491 bool 492 compressor_avail(int format) 493 { 494 struct compressor_methods **iter; 495 496 SET_FOREACH(iter, compressors) { 497 if ((*iter)->format == format) 498 return (true); 499 } 500 return (false); 501 } 502 503 struct compressor * 504 compressor_init(compressor_cb_t cb, int format, size_t maxiosize, int level, 505 void *arg) 506 { 507 struct compressor_methods **iter; 508 struct compressor *s; 509 void *priv; 510 511 SET_FOREACH(iter, compressors) { 512 if ((*iter)->format == format) 513 break; 514 } 515 if (iter == SET_LIMIT(compressors)) 516 return (NULL); 517 518 priv = (*iter)->init(maxiosize, level); 519 if (priv == NULL) 520 return (NULL); 521 522 s = malloc(sizeof(*s), M_COMPRESS, M_WAITOK | M_ZERO); 523 s->methods = (*iter); 524 s->priv = priv; 525 s->cb = cb; 526 s->arg = arg; 527 return (s); 528 } 529 530 void 531 compressor_reset(struct compressor *stream) 532 { 533 534 stream->methods->reset(stream->priv); 535 } 536 537 int 538 compressor_write(struct compressor *stream, void *data, size_t len) 539 { 540 541 return (stream->methods->write(stream->priv, data, len, stream->cb, 542 stream->arg)); 543 } 544 545 int 546 compressor_flush(struct compressor *stream) 547 { 548 549 return (stream->methods->write(stream->priv, NULL, 0, stream->cb, 550 stream->arg)); 551 } 552 553 void 554 compressor_fini(struct compressor *stream) 555 { 556 557 stream->methods->fini(stream->priv); 558 } 559