xref: /illumos-gate/usr/src/cmd/zstreamdump/zstreamdump.c (revision 30f1f754a88aa357235064d8c7ce78f2f546e0aa)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * 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.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Copyright (c) 2014 Integros [integros.com]
29  * Copyright (c) 2013, 2015 by Delphix. All rights reserved.
30  */
31 
32 #include <ctype.h>
33 #include <libnvpair.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <strings.h>
37 #include <unistd.h>
38 #include <stddef.h>
39 
40 #include <sys/dmu.h>
41 #include <sys/zfs_ioctl.h>
42 #include <sys/zio.h>
43 #include <zfs_fletcher.h>
44 
45 /*
46  * If dump mode is enabled, the number of bytes to print per line
47  */
48 #define	BYTES_PER_LINE	16
49 /*
50  * If dump mode is enabled, the number of bytes to group together, separated
51  * by newlines or spaces
52  */
53 #define	DUMP_GROUPING	4
54 
55 uint64_t total_write_size = 0;
56 uint64_t total_stream_len = 0;
57 FILE *send_stream = 0;
58 boolean_t do_byteswap = B_FALSE;
59 boolean_t do_cksum = B_TRUE;
60 
61 static void
62 usage(void)
63 {
64 	(void) fprintf(stderr, "usage: zstreamdump [-v] [-C] [-d] < file\n");
65 	(void) fprintf(stderr, "\t -v -- verbose\n");
66 	(void) fprintf(stderr, "\t -C -- suppress checksum verification\n");
67 	(void) fprintf(stderr, "\t -d -- dump contents of blocks modified, "
68 	    "implies verbose\n");
69 	exit(1);
70 }
71 
72 static void *
73 safe_malloc(size_t size)
74 {
75 	void *rv = malloc(size);
76 	if (rv == NULL) {
77 		(void) fprintf(stderr, "ERROR; failed to allocate %zu bytes\n",
78 		    size);
79 		abort();
80 	}
81 	return (rv);
82 }
83 
84 /*
85  * ssread - send stream read.
86  *
87  * Read while computing incremental checksum
88  */
89 static size_t
90 ssread(void *buf, size_t len, zio_cksum_t *cksum)
91 {
92 	size_t outlen;
93 
94 	if ((outlen = fread(buf, len, 1, send_stream)) == 0)
95 		return (0);
96 
97 	if (do_cksum) {
98 		if (do_byteswap)
99 			fletcher_4_incremental_byteswap(buf, len, cksum);
100 		else
101 			fletcher_4_incremental_native(buf, len, cksum);
102 	}
103 	total_stream_len += len;
104 	return (outlen);
105 }
106 
107 static size_t
108 read_hdr(dmu_replay_record_t *drr, zio_cksum_t *cksum)
109 {
110 	ASSERT3U(offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum),
111 	    ==, sizeof (dmu_replay_record_t) - sizeof (zio_cksum_t));
112 	size_t r = ssread(drr, sizeof (*drr) - sizeof (zio_cksum_t), cksum);
113 	if (r == 0)
114 		return (0);
115 	zio_cksum_t saved_cksum = *cksum;
116 	r = ssread(&drr->drr_u.drr_checksum.drr_checksum,
117 	    sizeof (zio_cksum_t), cksum);
118 	if (r == 0)
119 		return (0);
120 	if (!ZIO_CHECKSUM_IS_ZERO(&drr->drr_u.drr_checksum.drr_checksum) &&
121 	    !ZIO_CHECKSUM_EQUAL(saved_cksum,
122 	    drr->drr_u.drr_checksum.drr_checksum)) {
123 		fprintf(stderr, "invalid checksum\n");
124 		(void) printf("Incorrect checksum in record header.\n");
125 		(void) printf("Expected checksum = %llx/%llx/%llx/%llx\n",
126 		    saved_cksum.zc_word[0],
127 		    saved_cksum.zc_word[1],
128 		    saved_cksum.zc_word[2],
129 		    saved_cksum.zc_word[3]);
130 		return (0);
131 	}
132 	return (sizeof (*drr));
133 }
134 
135 /*
136  * Print part of a block in ASCII characters
137  */
138 static void
139 print_ascii_block(char *subbuf, int length)
140 {
141 	int i;
142 
143 	for (i = 0; i < length; i++) {
144 		char char_print = isprint(subbuf[i]) ? subbuf[i] : '.';
145 		if (i != 0 && i % DUMP_GROUPING == 0) {
146 			(void) printf(" ");
147 		}
148 		(void) printf("%c", char_print);
149 	}
150 	(void) printf("\n");
151 }
152 
153 /*
154  * print_block - Dump the contents of a modified block to STDOUT
155  *
156  * Assume that buf has capacity evenly divisible by BYTES_PER_LINE
157  */
158 static void
159 print_block(char *buf, int length)
160 {
161 	int i;
162 	/*
163 	 * Start printing ASCII characters at a constant offset, after
164 	 * the hex prints. Leave 3 characters per byte on a line (2 digit
165 	 * hex number plus 1 space) plus spaces between characters and
166 	 * groupings.
167 	 */
168 	int ascii_start = BYTES_PER_LINE * 3 +
169 	    BYTES_PER_LINE / DUMP_GROUPING + 2;
170 
171 	for (i = 0; i < length; i += BYTES_PER_LINE) {
172 		int j;
173 		int this_line_length = MIN(BYTES_PER_LINE, length - i);
174 		int print_offset = 0;
175 
176 		for (j = 0; j < this_line_length; j++) {
177 			int buf_offset = i + j;
178 
179 			/*
180 			 * Separate every DUMP_GROUPING bytes by a space.
181 			 */
182 			if (buf_offset % DUMP_GROUPING == 0) {
183 				print_offset += printf(" ");
184 			}
185 
186 			/*
187 			 * Print the two-digit hex value for this byte.
188 			 */
189 			unsigned char hex_print = buf[buf_offset];
190 			print_offset += printf("%02x ", hex_print);
191 		}
192 
193 		(void) printf("%*s", ascii_start - print_offset, " ");
194 
195 		print_ascii_block(buf + i, this_line_length);
196 	}
197 }
198 
199 int
200 main(int argc, char *argv[])
201 {
202 	char *buf = safe_malloc(SPA_MAXBLOCKSIZE);
203 	uint64_t drr_record_count[DRR_NUMTYPES] = { 0 };
204 	uint64_t total_records = 0;
205 	dmu_replay_record_t thedrr;
206 	dmu_replay_record_t *drr = &thedrr;
207 	struct drr_begin *drrb = &thedrr.drr_u.drr_begin;
208 	struct drr_end *drre = &thedrr.drr_u.drr_end;
209 	struct drr_object *drro = &thedrr.drr_u.drr_object;
210 	struct drr_freeobjects *drrfo = &thedrr.drr_u.drr_freeobjects;
211 	struct drr_write *drrw = &thedrr.drr_u.drr_write;
212 	struct drr_write_byref *drrwbr = &thedrr.drr_u.drr_write_byref;
213 	struct drr_free *drrf = &thedrr.drr_u.drr_free;
214 	struct drr_spill *drrs = &thedrr.drr_u.drr_spill;
215 	struct drr_write_embedded *drrwe = &thedrr.drr_u.drr_write_embedded;
216 	struct drr_checksum *drrc = &thedrr.drr_u.drr_checksum;
217 	char c;
218 	boolean_t verbose = B_FALSE;
219 	boolean_t very_verbose = B_FALSE;
220 	boolean_t first = B_TRUE;
221 	/*
222 	 * dump flag controls whether the contents of any modified data blocks
223 	 * are printed to the console during processing of the stream. Warning:
224 	 * for large streams, this can obviously lead to massive prints.
225 	 */
226 	boolean_t dump = B_FALSE;
227 	int err;
228 	zio_cksum_t zc = { 0 };
229 	zio_cksum_t pcksum = { 0 };
230 
231 	while ((c = getopt(argc, argv, ":vCd")) != -1) {
232 		switch (c) {
233 		case 'C':
234 			do_cksum = B_FALSE;
235 			break;
236 		case 'v':
237 			if (verbose)
238 				very_verbose = B_TRUE;
239 			verbose = B_TRUE;
240 			break;
241 		case 'd':
242 			dump = B_TRUE;
243 			verbose = B_TRUE;
244 			very_verbose = B_TRUE;
245 			break;
246 		case ':':
247 			(void) fprintf(stderr,
248 			    "missing argument for '%c' option\n", optopt);
249 			usage();
250 			break;
251 		case '?':
252 			(void) fprintf(stderr, "invalid option '%c'\n",
253 			    optopt);
254 			usage();
255 			break;
256 		}
257 	}
258 
259 	if (isatty(STDIN_FILENO)) {
260 		(void) fprintf(stderr,
261 		    "Error: Backup stream can not be read "
262 		    "from a terminal.\n"
263 		    "You must redirect standard input.\n");
264 		exit(1);
265 	}
266 
267 	send_stream = stdin;
268 	while (read_hdr(drr, &zc)) {
269 
270 		/*
271 		 * If this is the first DMU record being processed, check for
272 		 * the magic bytes and figure out the endian-ness based on them.
273 		 */
274 		if (first) {
275 			if (drrb->drr_magic == BSWAP_64(DMU_BACKUP_MAGIC)) {
276 				do_byteswap = B_TRUE;
277 				if (do_cksum) {
278 					ZIO_SET_CHECKSUM(&zc, 0, 0, 0, 0);
279 					/*
280 					 * recalculate header checksum now
281 					 * that we know it needs to be
282 					 * byteswapped.
283 					 */
284 					fletcher_4_incremental_byteswap(drr,
285 					    sizeof (dmu_replay_record_t), &zc);
286 				}
287 			} else if (drrb->drr_magic != DMU_BACKUP_MAGIC) {
288 				(void) fprintf(stderr, "Invalid stream "
289 				    "(bad magic number)\n");
290 				exit(1);
291 			}
292 			first = B_FALSE;
293 		}
294 		if (do_byteswap) {
295 			drr->drr_type = BSWAP_32(drr->drr_type);
296 			drr->drr_payloadlen =
297 			    BSWAP_32(drr->drr_payloadlen);
298 		}
299 
300 		/*
301 		 * At this point, the leading fields of the replay record
302 		 * (drr_type and drr_payloadlen) have been byte-swapped if
303 		 * necessary, but the rest of the data structure (the
304 		 * union of type-specific structures) is still in its
305 		 * original state.
306 		 */
307 		if (drr->drr_type >= DRR_NUMTYPES) {
308 			(void) printf("INVALID record found: type 0x%x\n",
309 			    drr->drr_type);
310 			(void) printf("Aborting.\n");
311 			exit(1);
312 		}
313 
314 		drr_record_count[drr->drr_type]++;
315 		total_records++;
316 
317 		switch (drr->drr_type) {
318 		case DRR_BEGIN:
319 			if (do_byteswap) {
320 				drrb->drr_magic = BSWAP_64(drrb->drr_magic);
321 				drrb->drr_versioninfo =
322 				    BSWAP_64(drrb->drr_versioninfo);
323 				drrb->drr_creation_time =
324 				    BSWAP_64(drrb->drr_creation_time);
325 				drrb->drr_type = BSWAP_32(drrb->drr_type);
326 				drrb->drr_flags = BSWAP_32(drrb->drr_flags);
327 				drrb->drr_toguid = BSWAP_64(drrb->drr_toguid);
328 				drrb->drr_fromguid =
329 				    BSWAP_64(drrb->drr_fromguid);
330 			}
331 
332 			(void) printf("BEGIN record\n");
333 			(void) printf("\thdrtype = %lld\n",
334 			    DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo));
335 			(void) printf("\tfeatures = %llx\n",
336 			    DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo));
337 			(void) printf("\tmagic = %llx\n",
338 			    (u_longlong_t)drrb->drr_magic);
339 			(void) printf("\tcreation_time = %llx\n",
340 			    (u_longlong_t)drrb->drr_creation_time);
341 			(void) printf("\ttype = %u\n", drrb->drr_type);
342 			(void) printf("\tflags = 0x%x\n", drrb->drr_flags);
343 			(void) printf("\ttoguid = %llx\n",
344 			    (u_longlong_t)drrb->drr_toguid);
345 			(void) printf("\tfromguid = %llx\n",
346 			    (u_longlong_t)drrb->drr_fromguid);
347 			(void) printf("\ttoname = %s\n", drrb->drr_toname);
348 			if (verbose)
349 				(void) printf("\n");
350 
351 			if (drr->drr_payloadlen != 0) {
352 				nvlist_t *nv;
353 				int sz = drr->drr_payloadlen;
354 
355 				if (sz > SPA_MAXBLOCKSIZE) {
356 					free(buf);
357 					buf = safe_malloc(sz);
358 				}
359 				(void) ssread(buf, sz, &zc);
360 				if (ferror(send_stream))
361 					perror("fread");
362 				err = nvlist_unpack(buf, sz, &nv, 0);
363 				if (err)
364 					perror(strerror(err));
365 				nvlist_print(stdout, nv);
366 				nvlist_free(nv);
367 			}
368 			break;
369 
370 		case DRR_END:
371 			if (do_byteswap) {
372 				drre->drr_checksum.zc_word[0] =
373 				    BSWAP_64(drre->drr_checksum.zc_word[0]);
374 				drre->drr_checksum.zc_word[1] =
375 				    BSWAP_64(drre->drr_checksum.zc_word[1]);
376 				drre->drr_checksum.zc_word[2] =
377 				    BSWAP_64(drre->drr_checksum.zc_word[2]);
378 				drre->drr_checksum.zc_word[3] =
379 				    BSWAP_64(drre->drr_checksum.zc_word[3]);
380 			}
381 			/*
382 			 * We compare against the *previous* checksum
383 			 * value, because the stored checksum is of
384 			 * everything before the DRR_END record.
385 			 */
386 			if (do_cksum && !ZIO_CHECKSUM_EQUAL(drre->drr_checksum,
387 			    pcksum)) {
388 				(void) printf("Expected checksum differs from "
389 				    "checksum in stream.\n");
390 				(void) printf("Expected checksum = "
391 				    "%llx/%llx/%llx/%llx\n",
392 				    pcksum.zc_word[0],
393 				    pcksum.zc_word[1],
394 				    pcksum.zc_word[2],
395 				    pcksum.zc_word[3]);
396 			}
397 			(void) printf("END checksum = %llx/%llx/%llx/%llx\n",
398 			    drre->drr_checksum.zc_word[0],
399 			    drre->drr_checksum.zc_word[1],
400 			    drre->drr_checksum.zc_word[2],
401 			    drre->drr_checksum.zc_word[3]);
402 
403 			ZIO_SET_CHECKSUM(&zc, 0, 0, 0, 0);
404 			break;
405 
406 		case DRR_OBJECT:
407 			if (do_byteswap) {
408 				drro->drr_object = BSWAP_64(drro->drr_object);
409 				drro->drr_type = BSWAP_32(drro->drr_type);
410 				drro->drr_bonustype =
411 				    BSWAP_32(drro->drr_bonustype);
412 				drro->drr_blksz = BSWAP_32(drro->drr_blksz);
413 				drro->drr_bonuslen =
414 				    BSWAP_32(drro->drr_bonuslen);
415 				drro->drr_toguid = BSWAP_64(drro->drr_toguid);
416 			}
417 			if (verbose) {
418 				(void) printf("OBJECT object = %llu type = %u "
419 				    "bonustype = %u blksz = %u bonuslen = %u\n",
420 				    (u_longlong_t)drro->drr_object,
421 				    drro->drr_type,
422 				    drro->drr_bonustype,
423 				    drro->drr_blksz,
424 				    drro->drr_bonuslen);
425 			}
426 			if (drro->drr_bonuslen > 0) {
427 				(void) ssread(buf,
428 				    P2ROUNDUP(drro->drr_bonuslen, 8), &zc);
429 				if (dump) {
430 					print_block(buf,
431 					    P2ROUNDUP(drro->drr_bonuslen, 8));
432 				}
433 			}
434 			break;
435 
436 		case DRR_FREEOBJECTS:
437 			if (do_byteswap) {
438 				drrfo->drr_firstobj =
439 				    BSWAP_64(drrfo->drr_firstobj);
440 				drrfo->drr_numobjs =
441 				    BSWAP_64(drrfo->drr_numobjs);
442 				drrfo->drr_toguid = BSWAP_64(drrfo->drr_toguid);
443 			}
444 			if (verbose) {
445 				(void) printf("FREEOBJECTS firstobj = %llu "
446 				    "numobjs = %llu\n",
447 				    (u_longlong_t)drrfo->drr_firstobj,
448 				    (u_longlong_t)drrfo->drr_numobjs);
449 			}
450 			break;
451 
452 		case DRR_WRITE:
453 			if (do_byteswap) {
454 				drrw->drr_object = BSWAP_64(drrw->drr_object);
455 				drrw->drr_type = BSWAP_32(drrw->drr_type);
456 				drrw->drr_offset = BSWAP_64(drrw->drr_offset);
457 				drrw->drr_logical_size =
458 				    BSWAP_64(drrw->drr_logical_size);
459 				drrw->drr_toguid = BSWAP_64(drrw->drr_toguid);
460 				drrw->drr_key.ddk_prop =
461 				    BSWAP_64(drrw->drr_key.ddk_prop);
462 				drrw->drr_compressed_size =
463 				    BSWAP_64(drrw->drr_compressed_size);
464 			}
465 
466 			uint64_t payload_size = DRR_WRITE_PAYLOAD_SIZE(drrw);
467 
468 			/*
469 			 * If this is verbose and/or dump output,
470 			 * print info on the modified block
471 			 */
472 			if (verbose) {
473 				(void) printf("WRITE object = %llu type = %u "
474 				    "checksum type = %u compression type = %u\n"
475 				    "    offset = %llu logical_size = %llu "
476 				    "compressed_size = %llu "
477 				    "payload_size = %llu "
478 				    "props = %llx\n",
479 				    (u_longlong_t)drrw->drr_object,
480 				    drrw->drr_type,
481 				    drrw->drr_checksumtype,
482 				    drrw->drr_compressiontype,
483 				    (u_longlong_t)drrw->drr_offset,
484 				    (u_longlong_t)drrw->drr_logical_size,
485 				    (u_longlong_t)drrw->drr_compressed_size,
486 				    (u_longlong_t)payload_size,
487 				    (u_longlong_t)drrw->drr_key.ddk_prop);
488 			}
489 
490 			/*
491 			 * Read the contents of the block in from STDIN to buf
492 			 */
493 			(void) ssread(buf, payload_size, &zc);
494 			/*
495 			 * If in dump mode
496 			 */
497 			if (dump) {
498 				print_block(buf, payload_size);
499 			}
500 			total_write_size += payload_size;
501 			break;
502 
503 		case DRR_WRITE_BYREF:
504 			if (do_byteswap) {
505 				drrwbr->drr_object =
506 				    BSWAP_64(drrwbr->drr_object);
507 				drrwbr->drr_offset =
508 				    BSWAP_64(drrwbr->drr_offset);
509 				drrwbr->drr_length =
510 				    BSWAP_64(drrwbr->drr_length);
511 				drrwbr->drr_toguid =
512 				    BSWAP_64(drrwbr->drr_toguid);
513 				drrwbr->drr_refguid =
514 				    BSWAP_64(drrwbr->drr_refguid);
515 				drrwbr->drr_refobject =
516 				    BSWAP_64(drrwbr->drr_refobject);
517 				drrwbr->drr_refoffset =
518 				    BSWAP_64(drrwbr->drr_refoffset);
519 				drrwbr->drr_key.ddk_prop =
520 				    BSWAP_64(drrwbr->drr_key.ddk_prop);
521 			}
522 			if (verbose) {
523 				(void) printf("WRITE_BYREF object = %llu "
524 				    "checksum type = %u props = %llx\n"
525 				    "    offset = %llu length = %llu\n"
526 				    "toguid = %llx refguid = %llx\n"
527 				    "    refobject = %llu refoffset = %llu\n",
528 				    (u_longlong_t)drrwbr->drr_object,
529 				    drrwbr->drr_checksumtype,
530 				    (u_longlong_t)drrwbr->drr_key.ddk_prop,
531 				    (u_longlong_t)drrwbr->drr_offset,
532 				    (u_longlong_t)drrwbr->drr_length,
533 				    (u_longlong_t)drrwbr->drr_toguid,
534 				    (u_longlong_t)drrwbr->drr_refguid,
535 				    (u_longlong_t)drrwbr->drr_refobject,
536 				    (u_longlong_t)drrwbr->drr_refoffset);
537 			}
538 			break;
539 
540 		case DRR_FREE:
541 			if (do_byteswap) {
542 				drrf->drr_object = BSWAP_64(drrf->drr_object);
543 				drrf->drr_offset = BSWAP_64(drrf->drr_offset);
544 				drrf->drr_length = BSWAP_64(drrf->drr_length);
545 			}
546 			if (verbose) {
547 				(void) printf("FREE object = %llu "
548 				    "offset = %llu length = %lld\n",
549 				    (u_longlong_t)drrf->drr_object,
550 				    (u_longlong_t)drrf->drr_offset,
551 				    (longlong_t)drrf->drr_length);
552 			}
553 			break;
554 		case DRR_SPILL:
555 			if (do_byteswap) {
556 				drrs->drr_object = BSWAP_64(drrs->drr_object);
557 				drrs->drr_length = BSWAP_64(drrs->drr_length);
558 			}
559 			if (verbose) {
560 				(void) printf("SPILL block for object = %llu "
561 				    "length = %llu\n", drrs->drr_object,
562 				    drrs->drr_length);
563 			}
564 			(void) ssread(buf, drrs->drr_length, &zc);
565 			if (dump) {
566 				print_block(buf, drrs->drr_length);
567 			}
568 			break;
569 		case DRR_WRITE_EMBEDDED:
570 			if (do_byteswap) {
571 				drrwe->drr_object =
572 				    BSWAP_64(drrwe->drr_object);
573 				drrwe->drr_offset =
574 				    BSWAP_64(drrwe->drr_offset);
575 				drrwe->drr_length =
576 				    BSWAP_64(drrwe->drr_length);
577 				drrwe->drr_toguid =
578 				    BSWAP_64(drrwe->drr_toguid);
579 				drrwe->drr_lsize =
580 				    BSWAP_32(drrwe->drr_lsize);
581 				drrwe->drr_psize =
582 				    BSWAP_32(drrwe->drr_psize);
583 			}
584 			if (verbose) {
585 				(void) printf("WRITE_EMBEDDED object = %llu "
586 				    "offset = %llu length = %llu\n"
587 				    "    toguid = %llx comp = %u etype = %u "
588 				    "lsize = %u psize = %u\n",
589 				    (u_longlong_t)drrwe->drr_object,
590 				    (u_longlong_t)drrwe->drr_offset,
591 				    (u_longlong_t)drrwe->drr_length,
592 				    (u_longlong_t)drrwe->drr_toguid,
593 				    drrwe->drr_compression,
594 				    drrwe->drr_etype,
595 				    drrwe->drr_lsize,
596 				    drrwe->drr_psize);
597 			}
598 			(void) ssread(buf,
599 			    P2ROUNDUP(drrwe->drr_psize, 8), &zc);
600 			break;
601 		}
602 		if (drr->drr_type != DRR_BEGIN && very_verbose) {
603 			(void) printf("    checksum = %llx/%llx/%llx/%llx\n",
604 			    (longlong_t)drrc->drr_checksum.zc_word[0],
605 			    (longlong_t)drrc->drr_checksum.zc_word[1],
606 			    (longlong_t)drrc->drr_checksum.zc_word[2],
607 			    (longlong_t)drrc->drr_checksum.zc_word[3]);
608 		}
609 		pcksum = zc;
610 	}
611 	free(buf);
612 
613 	/* Print final summary */
614 
615 	(void) printf("SUMMARY:\n");
616 	(void) printf("\tTotal DRR_BEGIN records = %lld\n",
617 	    (u_longlong_t)drr_record_count[DRR_BEGIN]);
618 	(void) printf("\tTotal DRR_END records = %lld\n",
619 	    (u_longlong_t)drr_record_count[DRR_END]);
620 	(void) printf("\tTotal DRR_OBJECT records = %lld\n",
621 	    (u_longlong_t)drr_record_count[DRR_OBJECT]);
622 	(void) printf("\tTotal DRR_FREEOBJECTS records = %lld\n",
623 	    (u_longlong_t)drr_record_count[DRR_FREEOBJECTS]);
624 	(void) printf("\tTotal DRR_WRITE records = %lld\n",
625 	    (u_longlong_t)drr_record_count[DRR_WRITE]);
626 	(void) printf("\tTotal DRR_WRITE_BYREF records = %lld\n",
627 	    (u_longlong_t)drr_record_count[DRR_WRITE_BYREF]);
628 	(void) printf("\tTotal DRR_WRITE_EMBEDDED records = %lld\n",
629 	    (u_longlong_t)drr_record_count[DRR_WRITE_EMBEDDED]);
630 	(void) printf("\tTotal DRR_FREE records = %lld\n",
631 	    (u_longlong_t)drr_record_count[DRR_FREE]);
632 	(void) printf("\tTotal DRR_SPILL records = %lld\n",
633 	    (u_longlong_t)drr_record_count[DRR_SPILL]);
634 	(void) printf("\tTotal records = %lld\n",
635 	    (u_longlong_t)total_records);
636 	(void) printf("\tTotal write size = %lld (0x%llx)\n",
637 	    (u_longlong_t)total_write_size, (u_longlong_t)total_write_size);
638 	(void) printf("\tTotal stream length = %lld (0x%llx)\n",
639 	    (u_longlong_t)total_stream_len, (u_longlong_t)total_stream_len);
640 	return (0);
641 }
642