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