xref: /freebsd/sys/contrib/openzfs/cmd/zstream/zstream_dump.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * This file and its contents are supplied under the terms of the
4  * Common Development and Distribution License ("CDDL"), version 1.0.
5  * You may only use this file in accordance with the terms of version
6  * 1.0 of the CDDL.
7  *
8  * A full copy of the text of the CDDL should have accompanied this
9  * source.  A copy of the CDDL is also available via the Internet at
10  * https://opensource.org/license/CDDL-1.0.
11  */
12 
13 /*
14  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
15  * Use is subject to license terms.
16  *
17  * Portions Copyright 2012 Martin Matuska <martin@matuska.org>
18  * Copyright (c) 2013, 2015 by Delphix. All rights reserved.
19  * Portions copyright 2026 by Garth Snyder <garth@garthsnyder.com>
20  */
21 
22 #include <ctype.h>
23 #include <err.h>
24 #include <libnvpair.h>
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <sys/nvpair.h>
29 #include <sys/param.h>
30 #include <sys/spa_checksum.h>
31 #include <sys/stdtypes.h>
32 #include <sys/zfs_ioctl.h>
33 #include <sys/zio.h>
34 #include <unistd.h>
35 
36 #include "zstream.h"
37 #include "zstream_modules.h"
38 
39 /*
40  * If dump mode is enabled, the number of bytes to print per line
41  */
42 #define	BYTES_PER_LINE	16
43 /*
44  * If dump mode is enabled, the number of bytes to group together, separated
45  * by newlines or spaces
46  */
47 #define	DUMP_GROUPING	4
48 
49 typedef struct {
50 	uint8_t drr_salt[ZIO_DATA_SALT_LEN];
51 	uint8_t drr_iv[ZIO_DATA_IV_LEN];
52 	uint8_t drr_mac[ZIO_DATA_MAC_LEN];
53 } crypto_fields_t;
54 
55 typedef void dumper_f(drr_packet_t *item);
56 
57 typedef struct {
58 	const char	*rt_typename;
59 	dumper_f	*rt_dumper;
60 } record_dumper_t;
61 
62 static int stream_error;
63 
64 /*
65  * Print part of a block in ASCII characters
66  */
67 static void
print_ascii_block(uint8_t * subbuf,int length)68 print_ascii_block(uint8_t *subbuf, int length)
69 {
70 	int i;
71 
72 	for (i = 0; i < length; i++) {
73 		char char_print = isprint(subbuf[i]) ? subbuf[i] : '.';
74 		if (i != 0 && i % DUMP_GROUPING == 0) {
75 			printf(" ");
76 		}
77 		printf("%c", char_print);
78 	}
79 	printf("\n");
80 }
81 
82 /*
83  * print_block - Dump the contents of a modified block to STDOUT
84  *
85  * Assumes that buf has capacity evenly divisible by BYTES_PER_LINE
86  */
87 static void
print_block(uint8_t * buf,uint32_t length)88 print_block(uint8_t *buf, uint32_t length)
89 {
90 	int i;
91 	/*
92 	 * Start printing ASCII characters at a constant offset, after
93 	 * the hex prints. Leave 3 characters per byte on a line (2 digit
94 	 * hex number plus 1 space) plus spaces between characters and
95 	 * groupings.
96 	 */
97 	int ascii_start = BYTES_PER_LINE * 3 +
98 	    BYTES_PER_LINE / DUMP_GROUPING + 2;
99 
100 	for (i = 0; i < length; i += BYTES_PER_LINE) {
101 		int j;
102 		int this_line_length = MIN(BYTES_PER_LINE, length - i);
103 		int print_offset = 0;
104 
105 		for (j = 0; j < this_line_length; j++) {
106 			int buf_offset = i + j;
107 
108 			/*
109 			 * Separate every DUMP_GROUPING bytes by a space.
110 			 */
111 			if (buf_offset % DUMP_GROUPING == 0) {
112 				print_offset += printf(" ");
113 			}
114 
115 			/*
116 			 * Print the two-digit hex value for this byte.
117 			 */
118 			unsigned char hex_print = buf[buf_offset];
119 			print_offset += printf("%02x ", hex_print);
120 		}
121 
122 		(void) printf("%*s", ascii_start - print_offset, " ");
123 
124 		print_ascii_block(buf + i, this_line_length);
125 	}
126 }
127 
128 /*
129  * Print an array of bytes to stdout as hexadecimal characters. str must
130  * have buf_len * 2 + 1 bytes of space.
131  */
132 static void
sprintf_bytes(char * str,uint8_t * buf,uint_t buf_len)133 sprintf_bytes(char *str, uint8_t *buf, uint_t buf_len)
134 {
135 	int i, n;
136 
137 	for (i = 0; i < buf_len; i++) {
138 		n = sprintf(str, "%02x", buf[i] & 0xff);
139 		str += n;
140 	}
141 
142 	str[0] = '\0';
143 }
144 
145 static void
maybe_dump_payload(drr_packet_t * item)146 maybe_dump_payload(drr_packet_t *item)
147 {
148 	if (OPTION_ENABLED(CA_DUMP_DATA)) {
149 		print_block(item->dp_payload, item->dp_payload_size);
150 	}
151 }
152 
153 static char *
stringify_encryption_fields(void * crypto_in)154 stringify_encryption_fields(void *crypto_in)
155 {
156 	crypto_fields_t *crypto = crypto_in;
157 	char salt[sizeof (crypto->drr_salt) * 2 + 1];
158 	char iv[sizeof (crypto->drr_iv) * 2 + 1];
159 	char mac[sizeof (crypto->drr_mac) * 2 + 1];
160 	static char buff[sizeof (salt) + sizeof (iv) + sizeof (mac) + 32];
161 
162 	sprintf_bytes(salt, crypto->drr_salt, sizeof (crypto->drr_salt));
163 	sprintf_bytes(iv, crypto->drr_iv, sizeof (crypto->drr_iv));
164 	sprintf_bytes(mac, crypto->drr_mac, sizeof (crypto->drr_mac));
165 	snprintf(buff, sizeof (buff), "salt = %s iv = %s mac = %s",
166 	    salt, iv, mac);
167 	return (buff);
168 }
169 
170 static void
dump_begin_record(drr_packet_t * item)171 dump_begin_record(drr_packet_t *item)
172 {
173 	dmu_replay_record_t *drr = &item->dp_drr;
174 	struct drr_begin *drrb = &item->dp_drr.drr_u.drr_begin;
175 
176 	if (!OPTION_ENABLED(CA_DUMP_BEGIN_AND_END) &&
177 	    !OPTION_ENABLED(CA_DUMP_ALL_RECORDS))
178 		return;
179 
180 	printf("BEGIN record\n");
181 	printf("\thdrtype = %llu\n",
182 	    DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo));
183 	printf("\tfeatures = %llx\n",
184 	    DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo));
185 	printf("\tmagic = %llx\n", (u_longlong_t)drrb->drr_magic);
186 	printf("\tcreation_time = %llx\n",
187 	    (u_longlong_t)drrb->drr_creation_time);
188 	printf("\ttype = %u\n", drrb->drr_type);
189 	printf("\tflags = 0x%x\n", drrb->drr_flags);
190 	printf("\ttoguid = %llx\n", (u_longlong_t)drrb->drr_toguid);
191 	printf("\tfromguid = %llx\n", (u_longlong_t)drrb->drr_fromguid);
192 	printf("\ttoname = %s\n", drrb->drr_toname);
193 	printf("\tpayloadlen = %u\n", drr->drr_payloadlen);
194 
195 	if (OPTION_ENABLED(CA_VERBOSE))
196 		printf("\n");
197 
198 	if (drr->drr_payloadlen >= 2) {
199 		nvlist_t *nv;
200 		/*
201 		 * It looks like zfs send or the ioctls it's using are
202 		 * generating packed nvlists with NV_ENCODE_NATIVE encoding
203 		 * in some circumstances. I don't think these can be decoded
204 		 * on an opposite-endian system, even by the core ZFS code.
205 		 */
206 		uint8_t *nvlist_header = item->dp_payload;
207 		uint8_t nvlist_encoding = nvlist_header[0];
208 		boolean_t big_endian = nvlist_header[1] == 0;
209 		if (nvlist_encoding == NV_ENCODE_XDR) {
210 			printf("nvlist encoding = NV_ENCODE_XDR\n");
211 		} else {
212 			printf("nvlist encoding = NV_ENCODE_NATIVE (%s)\n",
213 			    big_endian ? "big-endian" : "little-endian");
214 		}
215 		int err = nvlist_unpack((char *)item->dp_payload,
216 		    drr->drr_payloadlen, &nv, 0);
217 		if (err) {
218 			printf("failed to unpack DRR_BEGIN nvlist: %s\n",
219 			    strerror(err));
220 			if (!stream_error)
221 				stream_error = err;
222 		} else {
223 			nvlist_print(stdout, nv);
224 			nvlist_free(nv);
225 		}
226 	} else if (drr->drr_payloadlen != 0) {
227 		printf("unexpected packed nvlist length %d\n",
228 		    drr->drr_payloadlen);
229 	}
230 }
231 
232 static void
dump_end_record(drr_packet_t * item)233 dump_end_record(drr_packet_t *item)
234 {
235 	struct drr_end *drre = &item->dp_drr.drr_u.drr_end;
236 
237 	if (!OPTION_ENABLED(CA_DUMP_BEGIN_AND_END) &&
238 	    !OPTION_ENABLED(CA_DUMP_ALL_RECORDS))
239 		return;
240 
241 	printf("END checksum = %llx/%llx/%llx/%llx\n",
242 	    (u_longlong_t)drre->drr_checksum.zc_word[0],
243 	    (u_longlong_t)drre->drr_checksum.zc_word[1],
244 	    (u_longlong_t)drre->drr_checksum.zc_word[2],
245 	    (u_longlong_t)drre->drr_checksum.zc_word[3]);
246 }
247 
248 static void
dump_object_record(drr_packet_t * item)249 dump_object_record(drr_packet_t *item)
250 {
251 	struct drr_object *drro = &item->dp_drr.drr_u.drr_object;
252 
253 	if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
254 		printf("OBJECT object = %llu type = %u "
255 		    "bonustype = %u blksz = %u bonuslen = %u "
256 		    "dn_slots = %u raw_bonuslen = %u "
257 		    "flags = %u maxblkid = %llu "
258 		    "indblkshift = %u nlevels = %u "
259 		    "nblkptr = %u\n",
260 		    (u_longlong_t)drro->drr_object,
261 		    drro->drr_type,
262 		    drro->drr_bonustype,
263 		    drro->drr_blksz,
264 		    drro->drr_bonuslen,
265 		    drro->drr_dn_slots,
266 		    drro->drr_raw_bonuslen,
267 		    drro->drr_flags,
268 		    (u_longlong_t)drro->drr_maxblkid,
269 		    drro->drr_indblkshift,
270 		    drro->drr_nlevels,
271 		    drro->drr_nblkptr);
272 	}
273 	if (drro->drr_bonuslen > 0) {
274 		maybe_dump_payload(item);
275 	}
276 }
277 
278 static void
dump_freeobjects_record(drr_packet_t * item)279 dump_freeobjects_record(drr_packet_t *item)
280 {
281 	struct drr_freeobjects *drrfo = &item->dp_drr.drr_u.drr_freeobjects;
282 
283 	if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
284 		printf("FREEOBJECTS firstobj = %llu numobjs = %llu\n",
285 		    (u_longlong_t)drrfo->drr_firstobj,
286 		    (u_longlong_t)drrfo->drr_numobjs);
287 	}
288 }
289 
290 static void
dump_write_record(drr_packet_t * item)291 dump_write_record(drr_packet_t *item)
292 {
293 	struct drr_write *drrw = &item->dp_drr.drr_u.drr_write;
294 
295 	if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
296 		printf("WRITE object = %llu type = %u "
297 		    "checksum type = %u compression type = %u "
298 		    "flags = %u offset = %llu "
299 		    "logical_size = %llu "
300 		    "compressed_size = %llu "
301 		    "payload_size = %u props = %llx "
302 		    "%s\n",
303 		    (u_longlong_t)drrw->drr_object,
304 		    drrw->drr_type,
305 		    drrw->drr_checksumtype,
306 		    drrw->drr_compressiontype,
307 		    drrw->drr_flags,
308 		    (u_longlong_t)drrw->drr_offset,
309 		    (u_longlong_t)drrw->drr_logical_size,
310 		    (u_longlong_t)drrw->drr_compressed_size,
311 		    item->dp_payload_size,
312 		    (u_longlong_t)drrw->drr_key.ddk_prop,
313 		    stringify_encryption_fields(&drrw->drr_salt));
314 	}
315 	maybe_dump_payload(item);
316 }
317 
318 static void
dump_write_byref_record(drr_packet_t * item)319 dump_write_byref_record(drr_packet_t *item)
320 {
321 	struct drr_write_byref *drrwbr = &item->dp_drr.drr_u.drr_write_byref;
322 
323 	if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
324 		printf("WRITE_BYREF object = %llu "
325 		    "checksum type = %u props = %llx "
326 		    "offset = %llu length = %llu "
327 		    "toguid = %llx refguid = %llx "
328 		    "refobject = %llu refoffset = %llu\n",
329 		    (u_longlong_t)drrwbr->drr_object,
330 		    drrwbr->drr_checksumtype,
331 		    (u_longlong_t)drrwbr->drr_key.ddk_prop,
332 		    (u_longlong_t)drrwbr->drr_offset,
333 		    (u_longlong_t)drrwbr->drr_length,
334 		    (u_longlong_t)drrwbr->drr_toguid,
335 		    (u_longlong_t)drrwbr->drr_refguid,
336 		    (u_longlong_t)drrwbr->drr_refobject,
337 		    (u_longlong_t)drrwbr->drr_refoffset);
338 	}
339 }
340 
341 static void
dump_free_record(drr_packet_t * item)342 dump_free_record(drr_packet_t *item)
343 {
344 	struct drr_free *drrf = &item->dp_drr.drr_u.drr_free;
345 
346 	if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
347 		printf("FREE object = %llu "
348 		    "offset = %llu length = %lld\n",
349 		    (u_longlong_t)drrf->drr_object,
350 		    (u_longlong_t)drrf->drr_offset,
351 		    (longlong_t)drrf->drr_length);
352 	}
353 }
354 
355 static void
dump_spill_record(drr_packet_t * item)356 dump_spill_record(drr_packet_t *item)
357 {
358 	struct drr_spill *drrs = &item->dp_drr.drr_u.drr_spill;
359 
360 	if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
361 		printf("SPILL block for object = %llu "
362 		    "length = %llu flags = %u "
363 		    "compression type = %u "
364 		    "compressed_size = %llu "
365 		    "payload_size = %u "
366 		    "%s\n",
367 		    (u_longlong_t)drrs->drr_object,
368 		    (u_longlong_t)drrs->drr_length,
369 		    drrs->drr_flags,
370 		    drrs->drr_compressiontype,
371 		    (u_longlong_t)drrs->drr_compressed_size,
372 		    item->dp_payload_size,
373 		    stringify_encryption_fields(&drrs->drr_salt));
374 	}
375 	maybe_dump_payload(item);
376 }
377 
378 static void
dump_write_embedded_record(drr_packet_t * item)379 dump_write_embedded_record(drr_packet_t *item)
380 {
381 	struct drr_write_embedded *drrwe =
382 	    &item->dp_drr.drr_u.drr_write_embedded;
383 
384 	if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
385 		printf("WRITE_EMBEDDED object = %llu "
386 		    "offset = %llu length = %llu "
387 		    "toguid = %llx comp = %u etype = %u "
388 		    "lsize = %u psize = %u\n",
389 		    (u_longlong_t)drrwe->drr_object,
390 		    (u_longlong_t)drrwe->drr_offset,
391 		    (u_longlong_t)drrwe->drr_length,
392 		    (u_longlong_t)drrwe->drr_toguid,
393 		    drrwe->drr_compression,
394 		    drrwe->drr_etype,
395 		    drrwe->drr_lsize,
396 		    drrwe->drr_psize);
397 	}
398 	maybe_dump_payload(item);
399 }
400 
401 static void
dump_object_range_record(drr_packet_t * item)402 dump_object_range_record(drr_packet_t *item)
403 {
404 	struct drr_object_range *drror = &item->dp_drr.drr_u.drr_object_range;
405 
406 	if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
407 		printf("OBJECT_RANGE firstobj = %llu "
408 		    "numslots = %llu flags = %u "
409 		    "%s\n",
410 		    (u_longlong_t)drror->drr_firstobj,
411 		    (u_longlong_t)drror->drr_numslots,
412 		    drror->drr_flags,
413 		    stringify_encryption_fields(&drror->drr_salt));
414 	}
415 }
416 
417 static void
dump_redact_record(drr_packet_t * item)418 dump_redact_record(drr_packet_t *item)
419 {
420 	struct drr_redact *drrr = &item->dp_drr.drr_u.drr_redact;
421 
422 	if (OPTION_ENABLED(CA_DUMP_ALL_RECORDS)) {
423 		printf("REDACT object = %llu offset = "
424 		    "%llu length = %llu\n",
425 		    (u_longlong_t)drrr->drr_object,
426 		    (u_longlong_t)drrr->drr_offset,
427 		    (u_longlong_t)drrr->drr_length);
428 	}
429 }
430 
431 static const record_dumper_t record_dumpers[] = {
432 	{ "DRR_BEGIN", 		dump_begin_record },
433 	{ "DRR_OBJECT", 	dump_object_record },
434 	{ "DRR_FREEOBJECTS", 	dump_freeobjects_record },
435 	{ "DRR_WRITE", 		dump_write_record },
436 	{ "DRR_FREE", 		dump_free_record },
437 	{ "DRR_END", 		dump_end_record },
438 	{ "DRR_WRITE_BYREF", 	dump_write_byref_record },
439 	{ "DRR_SPILL", 		dump_spill_record },
440 	{ "DRR_WRITE_EMBEDDED",	dump_write_embedded_record },
441 	{ "DRR_OBJECT_RANGE",	dump_object_range_record },
442 	{ "DRR_REDACT",		dump_redact_record }
443 };
444 
445 static disposition_t
chain_dump_records(void * item_in,void * context)446 chain_dump_records(void *item_in, void *context)
447 {
448 	(void) context;
449 	drr_packet_t *item = (drr_packet_t *)item_in;
450 
451 	if (item == NULL) {
452 		return (D_OK);
453 	}
454 
455 	dmu_replay_record_t *drr = &item->dp_drr;
456 	zio_cksum_t *cksum = &drr->drr_u.drr_checksum.drr_checksum;
457 	int type = (int)drr->drr_type;
458 
459 	if (type < 0 || type >= DRR_NUMTYPES)
460 		errx(1, "unknown record type: %d", type);
461 
462 	record_dumpers[type].rt_dumper(item);
463 
464 	if (type != DRR_BEGIN && OPTION_ENABLED(CA_DUMP_CHECKSUMS)) {
465 		printf("    checksum = %llx/%llx/%llx/%llx\n",
466 		    (u_longlong_t)cksum->zc_word[0],
467 		    (u_longlong_t)cksum->zc_word[1],
468 		    (u_longlong_t)cksum->zc_word[2],
469 		    (u_longlong_t)cksum->zc_word[3]);
470 	}
471 
472 	return (D_OK);
473 }
474 
475 chain_step_t
serial_dump_records(void)476 serial_dump_records(void)
477 {
478 	size_t all_dumpers = sizeof (record_dumpers);
479 	size_t one_dumper = sizeof (record_dumpers[0]);
480 
481 	if (all_dumpers / one_dumper != DRR_NUMTYPES) {
482 		errx(1, "number of record_dumpers must match DRR_NUMTYPES");
483 	}
484 
485 	chain_step_t step = {
486 		.cs_type = CS_SERIAL,
487 		.cs_in_size = sizeof (drr_packet_t),
488 		.cs_out_size = sizeof (drr_packet_t),
489 		.cs_context = NULL,
490 		.cs_serial = {
491 			.process = chain_dump_records
492 		}
493 	};
494 	return (step);
495 }
496 
497 int
zstream_do_dump(int argc,char * argv[])498 zstream_do_dump(int argc, char *argv[])
499 {
500 	chain_attrs_t attrs = {0};
501 	const char *input_file = NULL;
502 	int c;
503 
504 	ENABLE_OPTION(&attrs, CA_DUMP_BEGIN_AND_END);
505 
506 	while ((c = getopt(argc, argv, ":vCd")) != -1) {
507 		switch (c) {
508 		case 'C':
509 			ENABLE_OPTION(&attrs, CA_IGNORE_CKSUMS);
510 			break;
511 		case 'v':
512 			if (attrs.ca_command_opts & CA_VERBOSE) {
513 				ENABLE_OPTION(&attrs, CA_DUMP_CHECKSUMS);
514 			} else {
515 				ENABLE_OPTION(&attrs, CA_VERBOSE);
516 				ENABLE_OPTION(&attrs, CA_DUMP_ALL_RECORDS);
517 			}
518 			break;
519 		case 'd':
520 			ENABLE_OPTION(&attrs, CA_VERBOSE);
521 			ENABLE_OPTION(&attrs, CA_DUMP_ALL_RECORDS);
522 			ENABLE_OPTION(&attrs, CA_DUMP_CHECKSUMS);
523 			ENABLE_OPTION(&attrs, CA_DUMP_DATA);
524 			break;
525 		case ':':
526 			warnx("missing argument for '%c' option\n", optopt);
527 			zstream_usage();
528 		case '?':
529 			warnx("invalid option '%c'\n", optopt);
530 			zstream_usage();
531 		}
532 	}
533 
534 	if (argc > optind) {
535 		input_file = argv[optind];
536 	}
537 
538 	zstream_chain_t dump_chain = {
539 		STANDARD_INPUT_STACK(input_file),
540 		serial_dump_records(),
541 		NULL_OUTPUT_STACK()
542 	};
543 
544 	stream_error = 0;
545 	zstream_chain_exec(dump_chain, &attrs);
546 
547 	/*
548 	 * Match previous zstream dump summary order
549 	 */
550 	int print_order[] = {
551 		DRR_BEGIN, DRR_END, DRR_OBJECT, DRR_FREEOBJECTS,
552 		DRR_WRITE, DRR_WRITE_BYREF, DRR_WRITE_EMBEDDED,
553 		DRR_FREE, DRR_SPILL, DRR_OBJECT_RANGE, DRR_REDACT
554 	};
555 
556 	printf("SUMMARY:\n");
557 	for (int i = 0; i < DRR_NUMTYPES; i++) {
558 		int type = print_order[i];
559 		const record_dumper_t *rec = &record_dumpers[type];
560 		record_stats_t *stats = &attrs.ca_stats_in[type];
561 		printf("\tTotal %s records = %llu (%llu bytes)\n",
562 		    rec->rt_typename,
563 		    (u_longlong_t)stats->rs_num_records,
564 		    (u_longlong_t)stats->rs_total_payload_bytes);
565 	}
566 
567 	uint64_t total_payload =
568 	    attrs.ca_totals_in.rs_total_payload_bytes;
569 	uint64_t total_header =
570 	    attrs.ca_totals_in.rs_total_header_bytes;
571 
572 	printf("\tTotal records = %llu\n",
573 	    (u_longlong_t)attrs.ca_totals_in.rs_num_records);
574 	printf("\tTotal payload size = %llu (0x%llx)\n",
575 	    (u_longlong_t)total_payload, (u_longlong_t)total_payload);
576 	printf("\tTotal header overhead = %llu (0x%llx)\n",
577 	    (u_longlong_t)total_header, (u_longlong_t)total_header);
578 	printf("\tTotal stream length = %llu (0x%llx)\n",
579 	    (u_longlong_t)(total_header + total_payload),
580 	    (u_longlong_t)(total_header + total_payload));
581 
582 	if (stream_error) {
583 		fflush(stdout);
584 		fprintf(stderr, "\nzstream dump completed with errors (first "
585 		    "error code %d)\n", stream_error);
586 		exit(stream_error);
587 	}
588 	return (0);
589 }
590