Path: blob/main/sys/contrib/openzfs/cmd/zstream/zstream_recompress.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) 2022 by Delphix. All rights reserved.27* Copyright (c) 2024, Klara, Inc.28*/2930#include <err.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_recompress(int argc, char *argv[])67{68int bufsz = SPA_MAXBLOCKSIZE;69char *buf = safe_malloc(bufsz);70dmu_replay_record_t thedrr;71dmu_replay_record_t *drr = &thedrr;72zio_cksum_t stream_cksum;73int c;74int level = 0;7576while ((c = getopt(argc, argv, "l:")) != -1) {77switch (c) {78case 'l':79if (sscanf(optarg, "%d", &level) != 1) {80fprintf(stderr,81"failed to parse level '%s'\n",82optarg);83zstream_usage();84}85break;86case '?':87(void) fprintf(stderr, "invalid option '%c'\n",88optopt);89zstream_usage();90break;91}92}9394argc -= optind;95argv += optind;9697if (argc != 1)98zstream_usage();99100enum zio_compress ctype;101if (strcmp(argv[0], "off") == 0) {102ctype = ZIO_COMPRESS_OFF;103} else {104for (ctype = 0; ctype < ZIO_COMPRESS_FUNCTIONS; ctype++) {105if (strcmp(argv[0],106zio_compress_table[ctype].ci_name) == 0)107break;108}109if (ctype == ZIO_COMPRESS_FUNCTIONS ||110zio_compress_table[ctype].ci_compress == NULL) {111fprintf(stderr, "Invalid compression type %s.\n",112argv[0]);113exit(2);114}115}116117if (isatty(STDIN_FILENO)) {118(void) fprintf(stderr,119"Error: The send stream is a binary format "120"and can not be read from a\n"121"terminal. Standard input must be redirected.\n");122exit(1);123}124125abd_init();126fletcher_4_init();127zio_init();128zstd_init();129int begin = 0;130boolean_t seen = B_FALSE;131while (sfread(drr, sizeof (*drr), stdin) != 0) {132struct drr_write *drrw;133uint64_t payload_size = 0;134135/*136* We need to regenerate the checksum.137*/138if (drr->drr_type != DRR_BEGIN) {139memset(&drr->drr_u.drr_checksum.drr_checksum, 0,140sizeof (drr->drr_u.drr_checksum.drr_checksum));141}142143144switch (drr->drr_type) {145case DRR_BEGIN:146{147ZIO_SET_CHECKSUM(&stream_cksum, 0, 0, 0, 0);148VERIFY0(begin++);149seen = B_TRUE;150151uint32_t sz = drr->drr_payloadlen;152153VERIFY3U(sz, <=, 1U << 28);154155if (sz != 0) {156if (sz > bufsz) {157buf = realloc(buf, sz);158if (buf == NULL)159err(1, "realloc");160bufsz = sz;161}162(void) sfread(buf, sz, stdin);163}164payload_size = sz;165break;166}167case DRR_END:168{169struct drr_end *drre = &drr->drr_u.drr_end;170/*171* We would prefer to just check --begin == 0, but172* replication streams have an end of stream END173* record, so we must avoid tripping it.174*/175VERIFY3B(seen, ==, B_TRUE);176begin--;177/*178* Use the recalculated checksum, unless this is179* the END record of a stream package, which has180* no checksum.181*/182if (!ZIO_CHECKSUM_IS_ZERO(&drre->drr_checksum))183drre->drr_checksum = stream_cksum;184break;185}186187case DRR_OBJECT:188{189struct drr_object *drro = &drr->drr_u.drr_object;190VERIFY3S(begin, ==, 1);191192if (drro->drr_bonuslen > 0) {193payload_size = DRR_OBJECT_PAYLOAD_SIZE(drro);194(void) sfread(buf, payload_size, stdin);195}196break;197}198199case DRR_SPILL:200{201struct drr_spill *drrs = &drr->drr_u.drr_spill;202VERIFY3S(begin, ==, 1);203payload_size = DRR_SPILL_PAYLOAD_SIZE(drrs);204(void) sfread(buf, payload_size, stdin);205break;206}207208case DRR_WRITE_BYREF:209VERIFY3S(begin, ==, 1);210fprintf(stderr,211"Deduplicated streams are not supported\n");212exit(1);213break;214215case DRR_WRITE:216{217VERIFY3S(begin, ==, 1);218drrw = &thedrr.drr_u.drr_write;219payload_size = DRR_WRITE_PAYLOAD_SIZE(drrw);220/*221* In order to recompress an encrypted block, you have222* to decrypt, decompress, recompress, and223* re-encrypt. That can be a future enhancement (along224* with decryption or re-encryption), but for now we225* skip encrypted blocks.226*/227boolean_t encrypted = B_FALSE;228for (int i = 0; i < ZIO_DATA_SALT_LEN; i++) {229if (drrw->drr_salt[i] != 0) {230encrypted = B_TRUE;231break;232}233}234if (encrypted) {235(void) sfread(buf, payload_size, stdin);236break;237}238enum zio_compress dtype = drrw->drr_compressiontype;239if (dtype >= ZIO_COMPRESS_FUNCTIONS) {240fprintf(stderr, "Invalid compression type in "241"stream: %d\n", dtype);242exit(3);243}244if (zio_compress_table[dtype].ci_decompress == NULL)245dtype = ZIO_COMPRESS_OFF;246247/* Set up buffers to minimize memcpys */248char *cbuf, *dbuf;249if (ctype == ZIO_COMPRESS_OFF)250dbuf = buf;251else252dbuf = safe_calloc(bufsz);253254if (dtype == ZIO_COMPRESS_OFF)255cbuf = dbuf;256else257cbuf = safe_calloc(payload_size);258259/* Read and decompress the payload */260(void) sfread(cbuf, payload_size, stdin);261if (dtype != ZIO_COMPRESS_OFF) {262abd_t cabd, dabd;263abd_get_from_buf_struct(&cabd,264cbuf, payload_size);265abd_get_from_buf_struct(&dabd, dbuf,266MIN(bufsz, drrw->drr_logical_size));267if (zio_decompress_data(dtype, &cabd, &dabd,268payload_size, abd_get_size(&dabd),269NULL) != 0) {270warnx("decompression type %d failed "271"for ino %llu offset %llu",272dtype,273(u_longlong_t)drrw->drr_object,274(u_longlong_t)drrw->drr_offset);275exit(4);276}277payload_size = drrw->drr_logical_size;278abd_free(&dabd);279abd_free(&cabd);280free(cbuf);281}282283/* Recompress the payload */284if (ctype != ZIO_COMPRESS_OFF) {285abd_t dabd, abd;286abd_get_from_buf_struct(&dabd,287dbuf, drrw->drr_logical_size);288abd_t *pabd =289abd_get_from_buf_struct(&abd, buf, bufsz);290size_t csize = zio_compress_data(ctype, &dabd,291&pabd, drrw->drr_logical_size,292drrw->drr_logical_size, level);293size_t rounded =294P2ROUNDUP(csize, SPA_MINBLOCKSIZE);295if (rounded >= drrw->drr_logical_size) {296memcpy(buf, dbuf, payload_size);297drrw->drr_compressiontype = 0;298drrw->drr_compressed_size = 0;299} else {300abd_zero_off(pabd, csize,301rounded - csize);302drrw->drr_compressiontype = ctype;303drrw->drr_compressed_size =304payload_size = rounded;305}306abd_free(&abd);307abd_free(&dabd);308free(dbuf);309} else {310drrw->drr_compressiontype = 0;311drrw->drr_compressed_size = 0;312}313break;314}315316case DRR_WRITE_EMBEDDED:317{318struct drr_write_embedded *drrwe =319&drr->drr_u.drr_write_embedded;320VERIFY3S(begin, ==, 1);321payload_size =322P2ROUNDUP((uint64_t)drrwe->drr_psize, 8);323(void) sfread(buf, payload_size, stdin);324break;325}326327case DRR_FREEOBJECTS:328case DRR_FREE:329case DRR_OBJECT_RANGE:330VERIFY3S(begin, ==, 1);331break;332333default:334(void) fprintf(stderr, "INVALID record type 0x%x\n",335drr->drr_type);336/* should never happen, so assert */337assert(B_FALSE);338}339340if (feof(stdout)) {341fprintf(stderr, "Error: unexpected end-of-file\n");342exit(1);343}344if (ferror(stdout)) {345fprintf(stderr, "Error while reading file: %s\n",346strerror(errno));347exit(1);348}349350/*351* We need to recalculate the checksum, and it needs to be352* initially zero to do that. BEGIN records don't have353* a checksum.354*/355if (drr->drr_type != DRR_BEGIN) {356memset(&drr->drr_u.drr_checksum.drr_checksum, 0,357sizeof (drr->drr_u.drr_checksum.drr_checksum));358}359if (dump_record(drr, buf, payload_size,360&stream_cksum, STDOUT_FILENO) != 0)361break;362if (drr->drr_type == DRR_END) {363/*364* Typically the END record is either the last365* thing in the stream, or it is followed366* by a BEGIN record (which also zeros the checksum).367* However, a stream package ends with two END368* records. The last END record's checksum starts369* from zero.370*/371ZIO_SET_CHECKSUM(&stream_cksum, 0, 0, 0, 0);372}373}374free(buf);375fletcher_4_fini();376zio_fini();377zstd_fini();378abd_fini();379380return (0);381}382383384