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