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