Path: blob/main/sys/contrib/openzfs/cmd/zstream/zstream_decompress.c
48380 views
// SPDX-License-Identifier: CDDL-1.01/*2* CDDL HEADER START3*4* The contents of this file are subject to the terms of the5* Common Development and Distribution License (the "License").6* You may not use this file except in compliance with the License.7*8* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE9* or https://opensource.org/licenses/CDDL-1.0.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*/2122/*23* Copyright 2022 Axcient. All rights reserved.24* Use is subject to license terms.25*26* Copyright (c) 2024, Klara, Inc.27*/2829#include <err.h>30#include <search.h>31#include <stdio.h>32#include <stdlib.h>33#include <unistd.h>34#include <sys/zfs_ioctl.h>35#include <sys/zio_checksum.h>36#include <sys/zstd/zstd.h>37#include "zfs_fletcher.h"38#include "zstream.h"3940static int41dump_record(dmu_replay_record_t *drr, void *payload, int payload_len,42zio_cksum_t *zc, int outfd)43{44assert(offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum)45== sizeof (dmu_replay_record_t) - sizeof (zio_cksum_t));46fletcher_4_incremental_native(drr,47offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum), zc);48if (drr->drr_type != DRR_BEGIN) {49assert(ZIO_CHECKSUM_IS_ZERO(&drr->drr_u.50drr_checksum.drr_checksum));51drr->drr_u.drr_checksum.drr_checksum = *zc;52}53fletcher_4_incremental_native(&drr->drr_u.drr_checksum.drr_checksum,54sizeof (zio_cksum_t), zc);55if (write(outfd, drr, sizeof (*drr)) == -1)56return (errno);57if (payload_len != 0) {58fletcher_4_incremental_native(payload, payload_len, zc);59if (write(outfd, payload, payload_len) == -1)60return (errno);61}62return (0);63}6465int66zstream_do_decompress(int argc, char *argv[])67{68const int KEYSIZE = 64;69int bufsz = SPA_MAXBLOCKSIZE;70char *buf = safe_malloc(bufsz);71dmu_replay_record_t thedrr;72dmu_replay_record_t *drr = &thedrr;73zio_cksum_t stream_cksum;74int c;75boolean_t verbose = B_FALSE;7677while ((c = getopt(argc, argv, "v")) != -1) {78switch (c) {79case 'v':80verbose = B_TRUE;81break;82case '?':83(void) fprintf(stderr, "invalid option '%c'\n",84optopt);85zstream_usage();86break;87}88}8990argc -= optind;91argv += optind;9293if (argc < 0)94zstream_usage();9596if (hcreate(argc) == 0)97errx(1, "hcreate");98for (int i = 0; i < argc; i++) {99uint64_t object, offset;100char *obj_str;101char *offset_str;102char *key;103char *end;104enum zio_compress type = ZIO_COMPRESS_LZ4;105106obj_str = strsep(&argv[i], ",");107if (argv[i] == NULL) {108zstream_usage();109exit(2);110}111errno = 0;112object = strtoull(obj_str, &end, 0);113if (errno || *end != '\0')114errx(1, "invalid value for object");115offset_str = strsep(&argv[i], ",");116offset = strtoull(offset_str, &end, 0);117if (errno || *end != '\0')118errx(1, "invalid value for offset");119if (argv[i]) {120if (0 == strcmp("off", argv[i]))121type = ZIO_COMPRESS_OFF;122else if (0 == strcmp("lz4", argv[i]))123type = ZIO_COMPRESS_LZ4;124else if (0 == strcmp("lzjb", argv[i]))125type = ZIO_COMPRESS_LZJB;126else if (0 == strcmp("gzip", argv[i]))127type = ZIO_COMPRESS_GZIP_1;128else if (0 == strcmp("zle", argv[i]))129type = ZIO_COMPRESS_ZLE;130else if (0 == strcmp("zstd", argv[i]))131type = ZIO_COMPRESS_ZSTD;132else {133fprintf(stderr, "Invalid compression type %s.\n"134"Supported types are off, lz4, lzjb, gzip, "135"zle, and zstd\n",136argv[i]);137exit(2);138}139}140141if (asprintf(&key, "%llu,%llu", (u_longlong_t)object,142(u_longlong_t)offset) < 0) {143err(1, "asprintf");144}145ENTRY e = {.key = key};146ENTRY *p;147148p = hsearch(e, ENTER);149if (p == NULL)150errx(1, "hsearch");151p->data = (void*)(intptr_t)type;152}153154if (isatty(STDIN_FILENO)) {155(void) fprintf(stderr,156"Error: The send stream is a binary format "157"and can not be read from a\n"158"terminal. Standard input must be redirected.\n");159exit(1);160}161162fletcher_4_init();163int begin = 0;164boolean_t seen = B_FALSE;165while (sfread(drr, sizeof (*drr), stdin) != 0) {166struct drr_write *drrw;167uint64_t payload_size = 0;168169/*170* We need to regenerate the checksum.171*/172if (drr->drr_type != DRR_BEGIN) {173memset(&drr->drr_u.drr_checksum.drr_checksum, 0,174sizeof (drr->drr_u.drr_checksum.drr_checksum));175}176177switch (drr->drr_type) {178case DRR_BEGIN:179{180ZIO_SET_CHECKSUM(&stream_cksum, 0, 0, 0, 0);181VERIFY0(begin++);182seen = B_TRUE;183184uint32_t sz = drr->drr_payloadlen;185186VERIFY3U(sz, <=, 1U << 28);187188if (sz != 0) {189if (sz > bufsz) {190buf = realloc(buf, sz);191if (buf == NULL)192err(1, "realloc");193bufsz = sz;194}195(void) sfread(buf, sz, stdin);196}197payload_size = sz;198break;199}200case DRR_END:201{202struct drr_end *drre = &drr->drr_u.drr_end;203/*204* We would prefer to just check --begin == 0, but205* replication streams have an end of stream END206* record, so we must avoid tripping it.207*/208VERIFY3B(seen, ==, B_TRUE);209begin--;210/*211* Use the recalculated checksum, unless this is212* the END record of a stream package, which has213* no checksum.214*/215if (!ZIO_CHECKSUM_IS_ZERO(&drre->drr_checksum))216drre->drr_checksum = stream_cksum;217break;218}219220case DRR_OBJECT:221{222struct drr_object *drro = &drr->drr_u.drr_object;223VERIFY3S(begin, ==, 1);224225if (drro->drr_bonuslen > 0) {226payload_size = DRR_OBJECT_PAYLOAD_SIZE(drro);227(void) sfread(buf, payload_size, stdin);228}229break;230}231232case DRR_SPILL:233{234struct drr_spill *drrs = &drr->drr_u.drr_spill;235VERIFY3S(begin, ==, 1);236payload_size = DRR_SPILL_PAYLOAD_SIZE(drrs);237(void) sfread(buf, payload_size, stdin);238break;239}240241case DRR_WRITE_BYREF:242VERIFY3S(begin, ==, 1);243fprintf(stderr,244"Deduplicated streams are not supported\n");245exit(1);246break;247248case DRR_WRITE:249{250VERIFY3S(begin, ==, 1);251drrw = &thedrr.drr_u.drr_write;252payload_size = DRR_WRITE_PAYLOAD_SIZE(drrw);253ENTRY *p;254char key[KEYSIZE];255256snprintf(key, KEYSIZE, "%llu,%llu",257(u_longlong_t)drrw->drr_object,258(u_longlong_t)drrw->drr_offset);259ENTRY e = {.key = key};260261p = hsearch(e, FIND);262if (p == NULL) {263/*264* Read the contents of the block unaltered265*/266(void) sfread(buf, payload_size, stdin);267break;268}269270/*271* Read and decompress the block272*/273enum zio_compress c =274(enum zio_compress)(intptr_t)p->data;275276if (c == ZIO_COMPRESS_OFF) {277(void) sfread(buf, payload_size, stdin);278drrw->drr_compressiontype = 0;279drrw->drr_compressed_size = 0;280if (verbose)281fprintf(stderr,282"Resetting compression type to "283"off for ino %llu offset %llu\n",284(u_longlong_t)drrw->drr_object,285(u_longlong_t)drrw->drr_offset);286break;287}288289uint64_t lsize = drrw->drr_logical_size;290ASSERT3U(payload_size, <=, lsize);291292char *lzbuf = safe_calloc(payload_size);293(void) sfread(lzbuf, payload_size, stdin);294295abd_t sabd, dabd;296abd_get_from_buf_struct(&sabd, lzbuf, payload_size);297abd_get_from_buf_struct(&dabd, buf, lsize);298int err = zio_decompress_data(c, &sabd, &dabd,299payload_size, lsize, NULL);300abd_free(&dabd);301abd_free(&sabd);302303if (err == 0) {304drrw->drr_compressiontype = 0;305drrw->drr_compressed_size = 0;306payload_size = lsize;307if (verbose) {308fprintf(stderr,309"successfully decompressed "310"ino %llu offset %llu\n",311(u_longlong_t)drrw->drr_object,312(u_longlong_t)drrw->drr_offset);313}314} else {315/*316* The block must not be compressed, at least317* not with this compression type, possibly318* because it gets written multiple times in319* this stream.320*/321warnx("decompression failed for "322"ino %llu offset %llu",323(u_longlong_t)drrw->drr_object,324(u_longlong_t)drrw->drr_offset);325memcpy(buf, lzbuf, payload_size);326}327328free(lzbuf);329break;330}331332case DRR_WRITE_EMBEDDED:333{334VERIFY3S(begin, ==, 1);335struct drr_write_embedded *drrwe =336&drr->drr_u.drr_write_embedded;337payload_size =338P2ROUNDUP((uint64_t)drrwe->drr_psize, 8);339(void) sfread(buf, payload_size, stdin);340break;341}342343case DRR_FREEOBJECTS:344case DRR_FREE:345case DRR_OBJECT_RANGE:346VERIFY3S(begin, ==, 1);347break;348349default:350(void) fprintf(stderr, "INVALID record type 0x%x\n",351drr->drr_type);352/* should never happen, so assert */353assert(B_FALSE);354}355356if (feof(stdout)) {357fprintf(stderr, "Error: unexpected end-of-file\n");358exit(1);359}360if (ferror(stdout)) {361fprintf(stderr, "Error while reading file: %s\n",362strerror(errno));363exit(1);364}365366/*367* We need to recalculate the checksum, and it needs to be368* initially zero to do that. BEGIN records don't have369* a checksum.370*/371if (drr->drr_type != DRR_BEGIN) {372memset(&drr->drr_u.drr_checksum.drr_checksum, 0,373sizeof (drr->drr_u.drr_checksum.drr_checksum));374}375if (dump_record(drr, buf, payload_size,376&stream_cksum, STDOUT_FILENO) != 0)377break;378if (drr->drr_type == DRR_END) {379/*380* Typically the END record is either the last381* thing in the stream, or it is followed382* by a BEGIN record (which also zeros the checksum).383* However, a stream package ends with two END384* records. The last END record's checksum starts385* from zero.386*/387ZIO_SET_CHECKSUM(&stream_cksum, 0, 0, 0, 0);388}389}390free(buf);391fletcher_4_fini();392hdestroy();393394return (0);395}396397398