xref: /freebsd-src/sys/contrib/openzfs/cmd/zstream/zstream_recompress.c (revision 15f0b8c309dea1dcb14d3e374686576ff68ac43f)
1dbd5678dSMartin Matuska /*
2dbd5678dSMartin Matuska  * CDDL HEADER START
3dbd5678dSMartin Matuska  *
4dbd5678dSMartin Matuska  * The contents of this file are subject to the terms of the
5dbd5678dSMartin Matuska  * Common Development and Distribution License (the "License").
6dbd5678dSMartin Matuska  * You may not use this file except in compliance with the License.
7dbd5678dSMartin Matuska  *
8dbd5678dSMartin Matuska  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9dbd5678dSMartin Matuska  * or https://opensource.org/licenses/CDDL-1.0.
10dbd5678dSMartin Matuska  * See the License for the specific language governing permissions
11dbd5678dSMartin Matuska  * and limitations under the License.
12dbd5678dSMartin Matuska  *
13dbd5678dSMartin Matuska  * When distributing Covered Code, include this CDDL HEADER in each
14dbd5678dSMartin Matuska  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15dbd5678dSMartin Matuska  * If applicable, add the following below this CDDL HEADER, with the
16dbd5678dSMartin Matuska  * fields enclosed by brackets "[]" replaced with your own identifying
17dbd5678dSMartin Matuska  * information: Portions Copyright [yyyy] [name of copyright owner]
18dbd5678dSMartin Matuska  *
19dbd5678dSMartin Matuska  * CDDL HEADER END
20dbd5678dSMartin Matuska  */
21dbd5678dSMartin Matuska 
22dbd5678dSMartin Matuska /*
23dbd5678dSMartin Matuska  * Copyright 2022 Axcient.  All rights reserved.
24dbd5678dSMartin Matuska  * Use is subject to license terms.
25dbd5678dSMartin Matuska  */
26dbd5678dSMartin Matuska 
27dbd5678dSMartin Matuska /*
28dbd5678dSMartin Matuska  * Copyright (c) 2022 by Delphix. All rights reserved.
29dbd5678dSMartin Matuska  */
30dbd5678dSMartin Matuska 
31dbd5678dSMartin Matuska #include <err.h>
32dbd5678dSMartin Matuska #include <stdio.h>
33dbd5678dSMartin Matuska #include <stdlib.h>
34dbd5678dSMartin Matuska #include <unistd.h>
35dbd5678dSMartin Matuska #include <sys/zfs_ioctl.h>
36dbd5678dSMartin Matuska #include <sys/zio_checksum.h>
37dbd5678dSMartin Matuska #include <sys/zstd/zstd.h>
38dbd5678dSMartin Matuska #include "zfs_fletcher.h"
39dbd5678dSMartin Matuska #include "zstream.h"
40dbd5678dSMartin Matuska 
41dbd5678dSMartin Matuska static int
42dbd5678dSMartin Matuska dump_record(dmu_replay_record_t *drr, void *payload, int payload_len,
43dbd5678dSMartin Matuska     zio_cksum_t *zc, int outfd)
44dbd5678dSMartin Matuska {
45dbd5678dSMartin Matuska 	assert(offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum)
46dbd5678dSMartin Matuska 	    == sizeof (dmu_replay_record_t) - sizeof (zio_cksum_t));
47dbd5678dSMartin Matuska 	fletcher_4_incremental_native(drr,
48dbd5678dSMartin Matuska 	    offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum), zc);
49dbd5678dSMartin Matuska 	if (drr->drr_type != DRR_BEGIN) {
50dbd5678dSMartin Matuska 		assert(ZIO_CHECKSUM_IS_ZERO(&drr->drr_u.
51dbd5678dSMartin Matuska 		    drr_checksum.drr_checksum));
52dbd5678dSMartin Matuska 		drr->drr_u.drr_checksum.drr_checksum = *zc;
53dbd5678dSMartin Matuska 	}
54dbd5678dSMartin Matuska 	fletcher_4_incremental_native(&drr->drr_u.drr_checksum.drr_checksum,
55dbd5678dSMartin Matuska 	    sizeof (zio_cksum_t), zc);
56dbd5678dSMartin Matuska 	if (write(outfd, drr, sizeof (*drr)) == -1)
57dbd5678dSMartin Matuska 		return (errno);
58dbd5678dSMartin Matuska 	if (payload_len != 0) {
59dbd5678dSMartin Matuska 		fletcher_4_incremental_native(payload, payload_len, zc);
60dbd5678dSMartin Matuska 		if (write(outfd, payload, payload_len) == -1)
61dbd5678dSMartin Matuska 			return (errno);
62dbd5678dSMartin Matuska 	}
63dbd5678dSMartin Matuska 	return (0);
64dbd5678dSMartin Matuska }
65dbd5678dSMartin Matuska 
66dbd5678dSMartin Matuska int
67dbd5678dSMartin Matuska zstream_do_recompress(int argc, char *argv[])
68dbd5678dSMartin Matuska {
69dbd5678dSMartin Matuska 	int bufsz = SPA_MAXBLOCKSIZE;
70dbd5678dSMartin Matuska 	char *buf = safe_malloc(bufsz);
71dbd5678dSMartin Matuska 	dmu_replay_record_t thedrr;
72dbd5678dSMartin Matuska 	dmu_replay_record_t *drr = &thedrr;
73dbd5678dSMartin Matuska 	zio_cksum_t stream_cksum;
74dbd5678dSMartin Matuska 	int c;
75dbd5678dSMartin Matuska 	int level = -1;
76dbd5678dSMartin Matuska 
77dbd5678dSMartin Matuska 	while ((c = getopt(argc, argv, "l:")) != -1) {
78dbd5678dSMartin Matuska 		switch (c) {
79dbd5678dSMartin Matuska 		case 'l':
80dbd5678dSMartin Matuska 			if (sscanf(optarg, "%d", &level) != 0) {
81dbd5678dSMartin Matuska 				fprintf(stderr,
82dbd5678dSMartin Matuska 				    "failed to parse level '%s'\n",
83dbd5678dSMartin Matuska 				    optarg);
84dbd5678dSMartin Matuska 				zstream_usage();
85dbd5678dSMartin Matuska 			}
86dbd5678dSMartin Matuska 			break;
87dbd5678dSMartin Matuska 		case '?':
88dbd5678dSMartin Matuska 			(void) fprintf(stderr, "invalid option '%c'\n",
89dbd5678dSMartin Matuska 			    optopt);
90dbd5678dSMartin Matuska 			zstream_usage();
91dbd5678dSMartin Matuska 			break;
92dbd5678dSMartin Matuska 		}
93dbd5678dSMartin Matuska 	}
94dbd5678dSMartin Matuska 
95dbd5678dSMartin Matuska 	argc -= optind;
96dbd5678dSMartin Matuska 	argv += optind;
97dbd5678dSMartin Matuska 
98dbd5678dSMartin Matuska 	if (argc != 1)
99dbd5678dSMartin Matuska 		zstream_usage();
100dbd5678dSMartin Matuska 	int type = 0;
101dbd5678dSMartin Matuska 	zio_compress_info_t *cinfo = NULL;
102dbd5678dSMartin Matuska 	if (0 == strcmp(argv[0], "off")) {
103dbd5678dSMartin Matuska 		type = ZIO_COMPRESS_OFF;
104dbd5678dSMartin Matuska 		cinfo = &zio_compress_table[type];
105dbd5678dSMartin Matuska 	} else if (0 == strcmp(argv[0], "inherit") ||
106dbd5678dSMartin Matuska 	    0 == strcmp(argv[0], "empty") ||
107dbd5678dSMartin Matuska 	    0 == strcmp(argv[0], "on")) {
108dbd5678dSMartin Matuska 		// Fall through to invalid compression type case
109dbd5678dSMartin Matuska 	} else {
110dbd5678dSMartin Matuska 		for (int i = 0; i < ZIO_COMPRESS_FUNCTIONS; i++) {
111dbd5678dSMartin Matuska 			if (0 == strcmp(zio_compress_table[i].ci_name,
112dbd5678dSMartin Matuska 			    argv[0])) {
113dbd5678dSMartin Matuska 				cinfo = &zio_compress_table[i];
114dbd5678dSMartin Matuska 				type = i;
115dbd5678dSMartin Matuska 				break;
116dbd5678dSMartin Matuska 			}
117dbd5678dSMartin Matuska 		}
118dbd5678dSMartin Matuska 	}
119dbd5678dSMartin Matuska 	if (cinfo == NULL) {
120dbd5678dSMartin Matuska 		fprintf(stderr, "Invalid compression type %s.\n",
121dbd5678dSMartin Matuska 		    argv[0]);
122dbd5678dSMartin Matuska 		exit(2);
123dbd5678dSMartin Matuska 	}
124dbd5678dSMartin Matuska 
125dbd5678dSMartin Matuska 	if (cinfo->ci_compress == NULL) {
126dbd5678dSMartin Matuska 		type = 0;
127dbd5678dSMartin Matuska 		cinfo = &zio_compress_table[0];
128dbd5678dSMartin Matuska 	}
129dbd5678dSMartin Matuska 
130dbd5678dSMartin Matuska 	if (isatty(STDIN_FILENO)) {
131dbd5678dSMartin Matuska 		(void) fprintf(stderr,
132dbd5678dSMartin Matuska 		    "Error: The send stream is a binary format "
133dbd5678dSMartin Matuska 		    "and can not be read from a\n"
134dbd5678dSMartin Matuska 		    "terminal.  Standard input must be redirected.\n");
135dbd5678dSMartin Matuska 		exit(1);
136dbd5678dSMartin Matuska 	}
137dbd5678dSMartin Matuska 
138dbd5678dSMartin Matuska 	fletcher_4_init();
139dbd5678dSMartin Matuska 	zio_init();
140dbd5678dSMartin Matuska 	zstd_init();
141*15f0b8c3SMartin Matuska 	int begin = 0;
142*15f0b8c3SMartin Matuska 	boolean_t seen = B_FALSE;
143dbd5678dSMartin Matuska 	while (sfread(drr, sizeof (*drr), stdin) != 0) {
144dbd5678dSMartin Matuska 		struct drr_write *drrw;
145dbd5678dSMartin Matuska 		uint64_t payload_size = 0;
146dbd5678dSMartin Matuska 
147dbd5678dSMartin Matuska 		/*
148dbd5678dSMartin Matuska 		 * We need to regenerate the checksum.
149dbd5678dSMartin Matuska 		 */
150dbd5678dSMartin Matuska 		if (drr->drr_type != DRR_BEGIN) {
151dbd5678dSMartin Matuska 			memset(&drr->drr_u.drr_checksum.drr_checksum, 0,
152dbd5678dSMartin Matuska 			    sizeof (drr->drr_u.drr_checksum.drr_checksum));
153dbd5678dSMartin Matuska 		}
154dbd5678dSMartin Matuska 
155dbd5678dSMartin Matuska 
156dbd5678dSMartin Matuska 		switch (drr->drr_type) {
157dbd5678dSMartin Matuska 		case DRR_BEGIN:
158dbd5678dSMartin Matuska 		{
159dbd5678dSMartin Matuska 			ZIO_SET_CHECKSUM(&stream_cksum, 0, 0, 0, 0);
160*15f0b8c3SMartin Matuska 			VERIFY0(begin++);
161*15f0b8c3SMartin Matuska 			seen = B_TRUE;
162dbd5678dSMartin Matuska 
163*15f0b8c3SMartin Matuska 			uint32_t sz = drr->drr_payloadlen;
164*15f0b8c3SMartin Matuska 
165*15f0b8c3SMartin Matuska 			VERIFY3U(sz, <=, 1U << 28);
166*15f0b8c3SMartin Matuska 
167dbd5678dSMartin Matuska 			if (sz != 0) {
168dbd5678dSMartin Matuska 				if (sz > bufsz) {
169dbd5678dSMartin Matuska 					buf = realloc(buf, sz);
170dbd5678dSMartin Matuska 					if (buf == NULL)
171dbd5678dSMartin Matuska 						err(1, "realloc");
172dbd5678dSMartin Matuska 					bufsz = sz;
173dbd5678dSMartin Matuska 				}
174dbd5678dSMartin Matuska 				(void) sfread(buf, sz, stdin);
175dbd5678dSMartin Matuska 			}
176dbd5678dSMartin Matuska 			payload_size = sz;
177dbd5678dSMartin Matuska 			break;
178dbd5678dSMartin Matuska 		}
179dbd5678dSMartin Matuska 		case DRR_END:
180dbd5678dSMartin Matuska 		{
181dbd5678dSMartin Matuska 			struct drr_end *drre = &drr->drr_u.drr_end;
182dbd5678dSMartin Matuska 			/*
183*15f0b8c3SMartin Matuska 			 * We would prefer to just check --begin == 0, but
184*15f0b8c3SMartin Matuska 			 * replication streams have an end of stream END
185*15f0b8c3SMartin Matuska 			 * record, so we must avoid tripping it.
186*15f0b8c3SMartin Matuska 			 */
187*15f0b8c3SMartin Matuska 			VERIFY3B(seen, ==, B_TRUE);
188*15f0b8c3SMartin Matuska 			begin--;
189*15f0b8c3SMartin Matuska 			/*
190dbd5678dSMartin Matuska 			 * Use the recalculated checksum, unless this is
191dbd5678dSMartin Matuska 			 * the END record of a stream package, which has
192dbd5678dSMartin Matuska 			 * no checksum.
193dbd5678dSMartin Matuska 			 */
194dbd5678dSMartin Matuska 			if (!ZIO_CHECKSUM_IS_ZERO(&drre->drr_checksum))
195dbd5678dSMartin Matuska 				drre->drr_checksum = stream_cksum;
196dbd5678dSMartin Matuska 			break;
197dbd5678dSMartin Matuska 		}
198dbd5678dSMartin Matuska 
199dbd5678dSMartin Matuska 		case DRR_OBJECT:
200dbd5678dSMartin Matuska 		{
201dbd5678dSMartin Matuska 			struct drr_object *drro = &drr->drr_u.drr_object;
202*15f0b8c3SMartin Matuska 			VERIFY3S(begin, ==, 1);
203dbd5678dSMartin Matuska 
204dbd5678dSMartin Matuska 			if (drro->drr_bonuslen > 0) {
205dbd5678dSMartin Matuska 				payload_size = DRR_OBJECT_PAYLOAD_SIZE(drro);
206dbd5678dSMartin Matuska 				(void) sfread(buf, payload_size, stdin);
207dbd5678dSMartin Matuska 			}
208dbd5678dSMartin Matuska 			break;
209dbd5678dSMartin Matuska 		}
210dbd5678dSMartin Matuska 
211dbd5678dSMartin Matuska 		case DRR_SPILL:
212dbd5678dSMartin Matuska 		{
213dbd5678dSMartin Matuska 			struct drr_spill *drrs = &drr->drr_u.drr_spill;
214*15f0b8c3SMartin Matuska 			VERIFY3S(begin, ==, 1);
215dbd5678dSMartin Matuska 			payload_size = DRR_SPILL_PAYLOAD_SIZE(drrs);
216dbd5678dSMartin Matuska 			(void) sfread(buf, payload_size, stdin);
217dbd5678dSMartin Matuska 			break;
218dbd5678dSMartin Matuska 		}
219dbd5678dSMartin Matuska 
220dbd5678dSMartin Matuska 		case DRR_WRITE_BYREF:
221*15f0b8c3SMartin Matuska 			VERIFY3S(begin, ==, 1);
222dbd5678dSMartin Matuska 			fprintf(stderr,
223dbd5678dSMartin Matuska 			    "Deduplicated streams are not supported\n");
224dbd5678dSMartin Matuska 			exit(1);
225dbd5678dSMartin Matuska 			break;
226dbd5678dSMartin Matuska 
227dbd5678dSMartin Matuska 		case DRR_WRITE:
228dbd5678dSMartin Matuska 		{
229*15f0b8c3SMartin Matuska 			VERIFY3S(begin, ==, 1);
230dbd5678dSMartin Matuska 			drrw = &thedrr.drr_u.drr_write;
231dbd5678dSMartin Matuska 			payload_size = DRR_WRITE_PAYLOAD_SIZE(drrw);
232dbd5678dSMartin Matuska 			/*
233dbd5678dSMartin Matuska 			 * In order to recompress an encrypted block, you have
234dbd5678dSMartin Matuska 			 * to decrypt, decompress, recompress, and
235dbd5678dSMartin Matuska 			 * re-encrypt. That can be a future enhancement (along
236dbd5678dSMartin Matuska 			 * with decryption or re-encryption), but for now we
237dbd5678dSMartin Matuska 			 * skip encrypted blocks.
238dbd5678dSMartin Matuska 			 */
239dbd5678dSMartin Matuska 			boolean_t encrypted = B_FALSE;
240dbd5678dSMartin Matuska 			for (int i = 0; i < ZIO_DATA_SALT_LEN; i++) {
241dbd5678dSMartin Matuska 				if (drrw->drr_salt[i] != 0) {
242dbd5678dSMartin Matuska 					encrypted = B_TRUE;
243dbd5678dSMartin Matuska 					break;
244dbd5678dSMartin Matuska 				}
245dbd5678dSMartin Matuska 			}
246dbd5678dSMartin Matuska 			if (encrypted) {
247dbd5678dSMartin Matuska 				(void) sfread(buf, payload_size, stdin);
248dbd5678dSMartin Matuska 				break;
249dbd5678dSMartin Matuska 			}
250dbd5678dSMartin Matuska 			if (drrw->drr_compressiontype >=
251dbd5678dSMartin Matuska 			    ZIO_COMPRESS_FUNCTIONS) {
252dbd5678dSMartin Matuska 				fprintf(stderr, "Invalid compression type in "
253dbd5678dSMartin Matuska 				    "stream: %d\n", drrw->drr_compressiontype);
254dbd5678dSMartin Matuska 				exit(3);
255dbd5678dSMartin Matuska 			}
256dbd5678dSMartin Matuska 			zio_compress_info_t *dinfo =
257dbd5678dSMartin Matuska 			    &zio_compress_table[drrw->drr_compressiontype];
258dbd5678dSMartin Matuska 
259dbd5678dSMartin Matuska 			/* Set up buffers to minimize memcpys */
260dbd5678dSMartin Matuska 			char *cbuf, *dbuf;
261dbd5678dSMartin Matuska 			if (cinfo->ci_compress == NULL)
262dbd5678dSMartin Matuska 				dbuf = buf;
263dbd5678dSMartin Matuska 			else
264dbd5678dSMartin Matuska 				dbuf = safe_calloc(bufsz);
265dbd5678dSMartin Matuska 
266dbd5678dSMartin Matuska 			if (dinfo->ci_decompress == NULL)
267dbd5678dSMartin Matuska 				cbuf = dbuf;
268dbd5678dSMartin Matuska 			else
269dbd5678dSMartin Matuska 				cbuf = safe_calloc(payload_size);
270dbd5678dSMartin Matuska 
271dbd5678dSMartin Matuska 			/* Read and decompress the payload */
272dbd5678dSMartin Matuska 			(void) sfread(cbuf, payload_size, stdin);
273dbd5678dSMartin Matuska 			if (dinfo->ci_decompress != NULL) {
274dbd5678dSMartin Matuska 				if (0 != dinfo->ci_decompress(cbuf, dbuf,
275dbd5678dSMartin Matuska 				    payload_size, MIN(bufsz,
276dbd5678dSMartin Matuska 				    drrw->drr_logical_size), dinfo->ci_level)) {
277dbd5678dSMartin Matuska 					warnx("decompression type %d failed "
278dbd5678dSMartin Matuska 					    "for ino %llu offset %llu",
279dbd5678dSMartin Matuska 					    type,
280dbd5678dSMartin Matuska 					    (u_longlong_t)drrw->drr_object,
281dbd5678dSMartin Matuska 					    (u_longlong_t)drrw->drr_offset);
282dbd5678dSMartin Matuska 					exit(4);
283dbd5678dSMartin Matuska 				}
284dbd5678dSMartin Matuska 				payload_size = drrw->drr_logical_size;
285dbd5678dSMartin Matuska 				free(cbuf);
286dbd5678dSMartin Matuska 			}
287dbd5678dSMartin Matuska 
288dbd5678dSMartin Matuska 			/* Recompress the payload */
289dbd5678dSMartin Matuska 			if (cinfo->ci_compress != NULL) {
290dbd5678dSMartin Matuska 				payload_size = P2ROUNDUP(cinfo->ci_compress(
291dbd5678dSMartin Matuska 				    dbuf, buf, drrw->drr_logical_size,
292dbd5678dSMartin Matuska 				    MIN(payload_size, bufsz), (level == -1 ?
293dbd5678dSMartin Matuska 				    cinfo->ci_level : level)),
294dbd5678dSMartin Matuska 				    SPA_MINBLOCKSIZE);
295dbd5678dSMartin Matuska 				if (payload_size != drrw->drr_logical_size) {
296dbd5678dSMartin Matuska 					drrw->drr_compressiontype = type;
297dbd5678dSMartin Matuska 					drrw->drr_compressed_size =
298dbd5678dSMartin Matuska 					    payload_size;
299dbd5678dSMartin Matuska 				} else {
300dbd5678dSMartin Matuska 					memcpy(buf, dbuf, payload_size);
301dbd5678dSMartin Matuska 					drrw->drr_compressiontype = 0;
302dbd5678dSMartin Matuska 					drrw->drr_compressed_size = 0;
303dbd5678dSMartin Matuska 				}
304dbd5678dSMartin Matuska 				free(dbuf);
305dbd5678dSMartin Matuska 			} else {
306dbd5678dSMartin Matuska 				drrw->drr_compressiontype = type;
307dbd5678dSMartin Matuska 				drrw->drr_compressed_size = 0;
308dbd5678dSMartin Matuska 			}
309dbd5678dSMartin Matuska 			break;
310dbd5678dSMartin Matuska 		}
311dbd5678dSMartin Matuska 
312dbd5678dSMartin Matuska 		case DRR_WRITE_EMBEDDED:
313dbd5678dSMartin Matuska 		{
314dbd5678dSMartin Matuska 			struct drr_write_embedded *drrwe =
315dbd5678dSMartin Matuska 			    &drr->drr_u.drr_write_embedded;
316*15f0b8c3SMartin Matuska 			VERIFY3S(begin, ==, 1);
317dbd5678dSMartin Matuska 			payload_size =
318dbd5678dSMartin Matuska 			    P2ROUNDUP((uint64_t)drrwe->drr_psize, 8);
319dbd5678dSMartin Matuska 			(void) sfread(buf, payload_size, stdin);
320dbd5678dSMartin Matuska 			break;
321dbd5678dSMartin Matuska 		}
322dbd5678dSMartin Matuska 
323dbd5678dSMartin Matuska 		case DRR_FREEOBJECTS:
324dbd5678dSMartin Matuska 		case DRR_FREE:
325dbd5678dSMartin Matuska 		case DRR_OBJECT_RANGE:
326*15f0b8c3SMartin Matuska 			VERIFY3S(begin, ==, 1);
327dbd5678dSMartin Matuska 			break;
328dbd5678dSMartin Matuska 
329dbd5678dSMartin Matuska 		default:
330dbd5678dSMartin Matuska 			(void) fprintf(stderr, "INVALID record type 0x%x\n",
331dbd5678dSMartin Matuska 			    drr->drr_type);
332dbd5678dSMartin Matuska 			/* should never happen, so assert */
333dbd5678dSMartin Matuska 			assert(B_FALSE);
334dbd5678dSMartin Matuska 		}
335dbd5678dSMartin Matuska 
336dbd5678dSMartin Matuska 		if (feof(stdout)) {
337dbd5678dSMartin Matuska 			fprintf(stderr, "Error: unexpected end-of-file\n");
338dbd5678dSMartin Matuska 			exit(1);
339dbd5678dSMartin Matuska 		}
340dbd5678dSMartin Matuska 		if (ferror(stdout)) {
341dbd5678dSMartin Matuska 			fprintf(stderr, "Error while reading file: %s\n",
342dbd5678dSMartin Matuska 			    strerror(errno));
343dbd5678dSMartin Matuska 			exit(1);
344dbd5678dSMartin Matuska 		}
345dbd5678dSMartin Matuska 
346dbd5678dSMartin Matuska 		/*
347dbd5678dSMartin Matuska 		 * We need to recalculate the checksum, and it needs to be
348dbd5678dSMartin Matuska 		 * initially zero to do that.  BEGIN records don't have
349dbd5678dSMartin Matuska 		 * a checksum.
350dbd5678dSMartin Matuska 		 */
351dbd5678dSMartin Matuska 		if (drr->drr_type != DRR_BEGIN) {
352dbd5678dSMartin Matuska 			memset(&drr->drr_u.drr_checksum.drr_checksum, 0,
353dbd5678dSMartin Matuska 			    sizeof (drr->drr_u.drr_checksum.drr_checksum));
354dbd5678dSMartin Matuska 		}
355dbd5678dSMartin Matuska 		if (dump_record(drr, buf, payload_size,
356dbd5678dSMartin Matuska 		    &stream_cksum, STDOUT_FILENO) != 0)
357dbd5678dSMartin Matuska 			break;
358dbd5678dSMartin Matuska 		if (drr->drr_type == DRR_END) {
359dbd5678dSMartin Matuska 			/*
360dbd5678dSMartin Matuska 			 * Typically the END record is either the last
361dbd5678dSMartin Matuska 			 * thing in the stream, or it is followed
362dbd5678dSMartin Matuska 			 * by a BEGIN record (which also zeros the checksum).
363dbd5678dSMartin Matuska 			 * However, a stream package ends with two END
364dbd5678dSMartin Matuska 			 * records.  The last END record's checksum starts
365dbd5678dSMartin Matuska 			 * from zero.
366dbd5678dSMartin Matuska 			 */
367dbd5678dSMartin Matuska 			ZIO_SET_CHECKSUM(&stream_cksum, 0, 0, 0, 0);
368dbd5678dSMartin Matuska 		}
369dbd5678dSMartin Matuska 	}
370dbd5678dSMartin Matuska 	free(buf);
371dbd5678dSMartin Matuska 	fletcher_4_fini();
372dbd5678dSMartin Matuska 	zio_fini();
373dbd5678dSMartin Matuska 	zstd_fini();
374dbd5678dSMartin Matuska 
375dbd5678dSMartin Matuska 	return (0);
376dbd5678dSMartin Matuska }
377