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