xref: /illumos-gate/usr/src/cmd/nvmeadm/nvmeadm_field.c (revision 0439b35b4c5b977fedef1ab5cbeff2c08150aba5)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2026 Oxide Computer Company
14  */
15 
16 /*
17  * Logic to slice, dice, and print structured data from log pages and related
18  * NVMe structures.
19  */
20 
21 #include <err.h>
22 #include <string.h>
23 #include <sys/sysmacros.h>
24 #include <sys/bitext.h>
25 #include <libcmdutils.h>
26 #include <sys/ilstr.h>
27 #include <ctype.h>
28 
29 #include "nvmeadm.h"
30 
31 static const nvmeadm_log_field_info_t *field_log_map[] = {
32 	&suplog_field_info,
33 	&supcmd_field_info,
34 	&supmicmd_field_info,
35 	&supfeat_field_info,
36 	&phyeye_field_info,
37 	&kioxia_vul_extsmart_field_info,
38 	&micron_vul_extsmart_field_info,
39 	&ocp_vul_smart_field_info,
40 	&ocp_vul_errrec_field_info,
41 	&ocp_vul_devcap_field_info,
42 	&ocp_vul_unsup_field_info,
43 	&ocp_vul_telstr_field_info,
44 	&solidigm_vul_power_field_info,
45 	&solidigm_vul_temp_field_info,
46 	&wdc_vul_cusmart_field_info,
47 	&wdc_vul_eol_field_info,
48 	&wdc_vul_power_field_info
49 };
50 
51 typedef struct {
52 	const char *fo_base;
53 	const char *fo_short;
54 	const char *fo_desc;
55 	char fo_val[256];
56 	char fo_hval[256];
57 	uint32_t fo_off;
58 	uint32_t fo_bitoff;
59 	uint32_t fo_len;
60 	uint32_t fo_bitlen;
61 } field_ofmt_t;
62 
63 typedef enum {
64 	NVMEADM_FIELD_OT_SHORT,
65 	NVMEADM_FIELD_OT_DESC,
66 	NVMEADM_FIELD_OT_VALUE,
67 	NVMEADM_FIELD_OT_HUMAN,
68 	NVMEADM_FIELD_OT_BYTEOFF,
69 	NVMEADM_FIELD_OT_BITOFF,
70 	NVMEADM_FIELD_OT_BYTELEN,
71 	NVMEADM_FIELD_OT_BITLEN
72 } phyeye_otype_t;
73 
74 static boolean_t
nvmeadm_field_ofmt_cb(ofmt_arg_t * ofarg,char * buf,uint_t buflen)75 nvmeadm_field_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
76 {
77 	size_t ret;
78 	field_ofmt_t *fo = ofarg->ofmt_cbarg;
79 
80 	switch (ofarg->ofmt_id) {
81 	case NVMEADM_FIELD_OT_SHORT:
82 		if (fo->fo_base == NULL) {
83 			ret = strlcat(buf, fo->fo_short, buflen);
84 		} else {
85 			ret = snprintf(buf, buflen, "%s.%s", fo->fo_base,
86 			    fo->fo_short);
87 		}
88 		break;
89 	case NVMEADM_FIELD_OT_DESC:
90 		ret = strlcat(buf, fo->fo_desc, buflen);
91 		break;
92 	case NVMEADM_FIELD_OT_VALUE:
93 		if (fo->fo_val[0] == '\0')
94 			return (B_FALSE);
95 
96 		ret = strlcat(buf, fo->fo_val, buflen);
97 		break;
98 	case NVMEADM_FIELD_OT_HUMAN:
99 		if (fo->fo_hval[0] != '\0') {
100 			ret = strlcat(buf, fo->fo_hval, buflen);
101 		} else {
102 			ret = strlcat(buf, fo->fo_val, buflen);
103 		}
104 		break;
105 	case NVMEADM_FIELD_OT_BYTEOFF:
106 		ret = snprintf(buf, buflen, "%u", fo->fo_off);
107 		break;
108 	case NVMEADM_FIELD_OT_BITOFF:
109 		ret = snprintf(buf, buflen, "%u", fo->fo_bitoff);
110 		break;
111 	case NVMEADM_FIELD_OT_BYTELEN:
112 		ret = snprintf(buf, buflen, "%u", fo->fo_len);
113 		break;
114 	case NVMEADM_FIELD_OT_BITLEN:
115 		ret = snprintf(buf, buflen, "%u", fo->fo_bitlen);
116 		break;
117 	default:
118 		abort();
119 	}
120 
121 	return (ret < buflen);
122 }
123 
124 const ofmt_field_t nvmeadm_field_ofmt[] = {
125 	{ "SHORT", 30, NVMEADM_FIELD_OT_SHORT, nvmeadm_field_ofmt_cb },
126 	{ "DESC", 30, NVMEADM_FIELD_OT_DESC, nvmeadm_field_ofmt_cb },
127 	{ "VALUE", 20, NVMEADM_FIELD_OT_VALUE, nvmeadm_field_ofmt_cb },
128 	{ "HUMAN", 20, NVMEADM_FIELD_OT_HUMAN, nvmeadm_field_ofmt_cb },
129 	{ "OFFSET", 8, NVMEADM_FIELD_OT_BYTEOFF, nvmeadm_field_ofmt_cb },
130 	{ "BITOFF", 8, NVMEADM_FIELD_OT_BITOFF, nvmeadm_field_ofmt_cb },
131 	{ "LENGTH", 8, NVMEADM_FIELD_OT_BYTELEN, nvmeadm_field_ofmt_cb },
132 	{ "BITLEN", 8, NVMEADM_FIELD_OT_BITLEN, nvmeadm_field_ofmt_cb },
133 	{ NULL, 0, 0, NULL }
134 };
135 
136 /*
137  * We've been asked to apply a filter that matches on a field which may be a
138  * top-level field or a nested one. For example, consider eom.odp.pefp. When
139  * we're in parsable mode we only ever allow for absolute matches. This ensures
140  * that if we add more fields to something that it doesn't end up changing the
141  * output the user gets. However, if we're in a non-parsable mode then we'll
142  * allow partial matches with a section. That is, 'eom' will match anything
143  * starting with 'eom'. 'eom.odp' will match all fields with 'eom.odp'. Partial
144  * matches within a field will not work, e.g. 'eom.o' would not match 'eom.odp'.
145  *
146  * However, a more specific match should match its parent. So, we want
147  * 'eom.odp.pefp' to match 'eom' and 'eom.odp'. Even if we match those, we don't
148  * count them as a use of the filter. Only exact matches count. This ensures
149  * that if someone makes a typo or uses a non-existent field, say 'eom.foobar',
150  * which does match 'eom', it still will generate an error.
151  */
152 bool
nvmeadm_field_filter(nvmeadm_field_print_t * print,const char * base,const char * shrt)153 nvmeadm_field_filter(nvmeadm_field_print_t *print, const char *base,
154     const char *shrt)
155 {
156 	char buf[PATH_MAX];
157 	const char *check;
158 	bool match = false;
159 
160 	if (print->fp_nfilts == 0) {
161 		return (true);
162 	}
163 
164 	if (base != NULL && shrt != NULL) {
165 		(void) snprintf(buf, sizeof (buf), "%s.%s", base, shrt);
166 		check = buf;
167 	} else if (base == NULL) {
168 		VERIFY3P(shrt, !=, NULL);
169 		check = shrt;
170 	} else if (shrt == NULL) {
171 		VERIFY3P(base, !=, NULL);
172 		check = base;
173 	} else {
174 		abort();
175 	}
176 
177 	/*
178 	 * Always check all filters so that way a user specifying the same thing
179 	 * multiple times doesn't end up in trouble.
180 	 */
181 	for (int i = 0; i < print->fp_nfilts; i++) {
182 		nvmeadm_field_filt_t *f = &print->fp_filts[i];
183 
184 		if (strcmp(check, f->nff_str) == 0) {
185 			f->nff_used = true;
186 			match = true;
187 			continue;
188 		}
189 
190 		if (print->fp_ofmt != NULL) {
191 			continue;
192 		}
193 
194 		size_t len = strlen(check);
195 		if (len >= f->nff_len) {
196 			if (strncmp(check, f->nff_str, f->nff_len) == 0 &&
197 			    check[f->nff_len] == '.') {
198 				match = true;
199 				continue;
200 			}
201 		} else {
202 			if (strncmp(check, f->nff_str, len) == 0 &&
203 			    f->nff_str[len] == '.') {
204 				match = true;
205 				continue;
206 			}
207 		}
208 	}
209 
210 	return (match);
211 }
212 
213 static void
field_print_one_bit(nvmeadm_field_print_t * print,field_ofmt_t * ofarg,nvmeadm_field_type_t type,uint32_t level)214 field_print_one_bit(nvmeadm_field_print_t *print, field_ofmt_t *ofarg,
215     nvmeadm_field_type_t type, uint32_t level)
216 {
217 	uint32_t indent;
218 
219 	if (!nvmeadm_field_filter(print, ofarg->fo_base, ofarg->fo_short)) {
220 		return;
221 	}
222 
223 	if (print->fp_ofmt != NULL) {
224 		ofmt_print(print->fp_ofmt, ofarg);
225 		return;
226 	}
227 
228 	indent = 4 + print->fp_indent * 2;
229 	if (level > 1) {
230 		indent += (level - 1) * 7;
231 	}
232 
233 	(void) printf("%*s|--> %s: ", indent, "", ofarg->fo_desc);
234 	switch (type) {
235 	case NVMEADM_FT_STRMAP:
236 		(void) printf("%s (%s)\n", ofarg->fo_hval,
237 		    ofarg->fo_val);
238 		break;
239 	case NVMEADM_FT_BITS:
240 		(void) printf("%s\n", ofarg->fo_val);
241 		break;
242 	case NVMEADM_FT_HEX:
243 	case NVMEADM_FT_PERCENT:
244 		(void) printf("%s\n", ofarg->fo_hval);
245 		break;
246 	default:
247 		abort();
248 	}
249 }
250 
251 /*
252  * Extract what should be a series of printable ASCII bytes, but don't assume
253  * that they are. Similarly, assume we need to trim any trailing spaces in the
254  * field. If anything in here is not ASCII, we'll escape it.
255  */
256 static void
field_extract_ascii(const void * data,nvmeadm_field_type_t type,size_t len,size_t off,field_ofmt_t * ofarg)257 field_extract_ascii(const void *data, nvmeadm_field_type_t type, size_t len,
258     size_t off, field_ofmt_t *ofarg)
259 {
260 	bool zpad = type == NVMEADM_FT_ASCIIZ;
261 	const uint8_t *u8p = data + off;
262 
263 	while (len > 0) {
264 		if ((zpad && u8p[len - 1] == '\0') ||
265 		    (!zpad && u8p[len - 1] == ' ')) {
266 			len--;
267 		} else {
268 			break;
269 		}
270 	}
271 
272 	if (len == 0)
273 		return;
274 
275 	ilstr_t ilstr;
276 	ilstr_init_prealloc(&ilstr, ofarg->fo_val, sizeof (ofarg->fo_val));
277 
278 	for (size_t i = 0; i < len; i++) {
279 		if (isascii(u8p[i]) && isprint(u8p[i])) {
280 			ilstr_append_char(&ilstr, u8p[i]);
281 		} else {
282 			ilstr_aprintf(&ilstr, "\\x%02x", u8p[i]);
283 		}
284 	}
285 
286 	if (ilstr_errno(&ilstr) != ILSTR_ERROR_OK) {
287 		errx(-1, "failed to construct internal string for field %s: "
288 		    "0x%x", ofarg->fo_desc, ilstr_errno(&ilstr));
289 	}
290 
291 	(void) memcpy(ofarg->fo_hval, ofarg->fo_val, ilstr_len(&ilstr) + 1);
292 	ilstr_fini(&ilstr);
293 }
294 
295 static uint64_t
nvmeadm_apply_addend(uint64_t val,const nvmeadm_field_addend_t * add)296 nvmeadm_apply_addend(uint64_t val, const nvmeadm_field_addend_t *add)
297 {
298 	if (add->nfa_shift > 0) {
299 		val <<= add->nfa_shift;
300 	}
301 
302 	val += add->nfa_addend;
303 	return (val);
304 }
305 
306 static void
nvmeadm_field_bit_extract(const nvmeadm_field_bit_t * bit,uint64_t fval,field_ofmt_t * ofarg,uint64_t * bp)307 nvmeadm_field_bit_extract(const nvmeadm_field_bit_t *bit, uint64_t fval,
308     field_ofmt_t *ofarg, uint64_t *bp)
309 {
310 	VERIFY3U(bit->nfb_hibit, <, 64);
311 	uint64_t bval = bitx64(fval, bit->nfb_hibit, bit->nfb_lowbit);
312 	if (bp != NULL)
313 		*bp = bval;
314 
315 	(void) snprintf(ofarg->fo_val, sizeof (ofarg->fo_val), "0x%" PRIx64,
316 	    bval);
317 	switch (bit->nfb_type) {
318 	case NVMEADM_FT_HEX:
319 		/*
320 		 * The "human" string is the version with the addend applied.
321 		 */
322 		bval = nvmeadm_apply_addend(bval, &bit->nfb_addend);
323 		(void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval),
324 		    "0x%" PRIx64, bval);
325 		break;
326 	case NVMEADM_FT_UNIT:
327 		bval = nvmeadm_apply_addend(bval, &bit->nfb_addend);
328 		(void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval),
329 		    "%" PRIu64 " %s", bval, bit->nfb_addend.nfa_unit);
330 		break;
331 	case NVMEADM_FT_BITS:
332 		/* No human string for this. */
333 		break;
334 	case NVMEADM_FT_STRMAP:
335 		if (bval < ARRAY_SIZE(bit->nfb_strs) &&
336 		    bit->nfb_strs[bval] != NULL) {
337 			(void) strlcpy(ofarg->fo_hval, bit->nfb_strs[bval],
338 			    sizeof (ofarg->fo_hval));
339 		} else {
340 			(void) strlcpy(ofarg->fo_hval, "reserved",
341 			    sizeof (ofarg->fo_hval));
342 		}
343 		break;
344 	case NVMEADM_FT_PERCENT:
345 		(void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval), "%u%%",
346 		    bval);
347 		break;
348 	case NVMEADM_FT_BYTES:
349 		bval = nvmeadm_apply_addend(bval, &bit->nfb_addend);
350 		nicenum(bval, ofarg->fo_hval, sizeof (ofarg->fo_hval));
351 		break;
352 	case NVMEADM_FT_GUID:
353 		/* GUIDs don't fit inside the 8 byte limit we have */
354 		abort();
355 	case NVMEADM_FT_ASCII:
356 	case NVMEADM_FT_ASCIIZ:
357 		/* We should handle this once it shows up here */
358 		abort();
359 	case NVMEADM_FT_CONTAINER:
360 		/* Containers are only used at the field level right now. */
361 		abort();
362 	}
363 }
364 
365 static void
field_print_bits(nvmeadm_field_print_t * print,const nvmeadm_field_bit_t * bits,size_t nbits,uint64_t val,const char * base,size_t off,size_t bitoff,uint32_t level)366 field_print_bits(nvmeadm_field_print_t *print, const nvmeadm_field_bit_t *bits,
367     size_t nbits, uint64_t val, const char *base, size_t off, size_t bitoff,
368     uint32_t level)
369 {
370 	for (size_t i = 0; i < nbits; i++) {
371 		uint8_t blen = bits[i].nfb_hibit - bits[i].nfb_lowbit + 1;
372 		field_ofmt_t ofarg = { 0 };
373 
374 		/*
375 		 * See if this field is one that is meaningful to this revision
376 		 * of the log page or controller. If the version is NULL or the
377 		 * revision is 0 in the field, then there is nothing to check.
378 		 * While most fields add something in a new revision, a few also
379 		 * change things, so we also check for a max revision as well.
380 		 */
381 		if (bits[i].nfb_rev != 0 && bits[i].nfb_rev > print->fp_rev) {
382 			continue;
383 		}
384 
385 		if (bits[i].nfb_maxrev != 0 && print->fp_rev >
386 		    bits[i].nfb_maxrev) {
387 			continue;
388 		}
389 
390 		if (bits[i].nfb_vers != NULL && print->fp_vers != NULL &&
391 		    !nvme_vers_atleast(print->fp_vers, bits[i].nfb_vers)) {
392 			continue;
393 		}
394 
395 		ofarg.fo_base = base;
396 		ofarg.fo_short = bits[i].nfb_short;
397 		ofarg.fo_desc = bits[i].nfb_desc;
398 		ofarg.fo_off = off + (bitoff + bits[i].nfb_lowbit) / NBBY;
399 		ofarg.fo_bitoff = (bitoff + bits[i].nfb_lowbit) % NBBY;
400 		ofarg.fo_len = blen / NBBY;
401 		ofarg.fo_bitlen = blen % NBBY;
402 
403 		uint64_t bit_val;
404 		nvmeadm_field_bit_extract(&bits[i], val, &ofarg, &bit_val);
405 
406 		field_print_one_bit(print, &ofarg, bits[i].nfb_type, level);
407 
408 		if (bits[i].nfb_type == NVMEADM_FT_BITS) {
409 			char buf[256];
410 
411 			(void) snprintf(buf, sizeof (buf), "%s.%s", base,
412 			    bits[i].nfb_short);
413 			field_print_bits(print, bits[i].nfb_bits,
414 			    bits[i].nfb_nbits, bit_val, buf, ofarg.fo_off,
415 			    ofarg.fo_bitoff, level + 1);
416 		}
417 	}
418 }
419 
420 static void
field_print_one(nvmeadm_field_print_t * print,field_ofmt_t * ofarg,nvmeadm_field_type_t type)421 field_print_one(nvmeadm_field_print_t *print, field_ofmt_t *ofarg,
422     nvmeadm_field_type_t type)
423 {
424 	if (!nvmeadm_field_filter(print, ofarg->fo_base, ofarg->fo_short)) {
425 		return;
426 	}
427 
428 	if (print->fp_ofmt != NULL) {
429 		if (type == NVMEADM_FT_CONTAINER)
430 			return;
431 		ofmt_print(print->fp_ofmt, ofarg);
432 		return;
433 	}
434 
435 	uint_t indent = 2 + print->fp_indent * 2;
436 	(void) printf("%*s%s:", indent, "", ofarg->fo_desc);
437 	switch (type) {
438 	case NVMEADM_FT_BITS:
439 		(void) printf(" %s\n", ofarg->fo_val);
440 		break;
441 	case NVMEADM_FT_STRMAP:
442 		(void) printf(" %s (%s)\n", ofarg->fo_hval, ofarg->fo_val);
443 		break;
444 	case NVMEADM_FT_HEX:
445 	case NVMEADM_FT_UNIT:
446 	case NVMEADM_FT_BYTES:
447 	case NVMEADM_FT_PERCENT:
448 	case NVMEADM_FT_GUID:
449 	case NVMEADM_FT_ASCII:
450 	case NVMEADM_FT_ASCIIZ:
451 		(void) printf(" %s\n", ofarg->fo_hval);
452 		break;
453 	case NVMEADM_FT_CONTAINER:
454 		(void) printf("\n");
455 		break;
456 	}
457 }
458 
459 /*
460  * Extract the u128 from where we are right now.
461  */
462 static void
nvmeadm_field_extract_u128(const nvmeadm_field_t * field,const void * data,field_ofmt_t * ofarg)463 nvmeadm_field_extract_u128(const nvmeadm_field_t *field, const void *data,
464     field_ofmt_t *ofarg)
465 {
466 	nvme_uint128_t u128;
467 	const uint8_t *u8p;
468 
469 	(void) memcpy(&u128, data + field->nf_off, sizeof (u128));
470 
471 	if (u128.hi == 0) {
472 		(void) snprintf(ofarg->fo_val, sizeof (ofarg->fo_val), "0x%x",
473 		    u128.lo);
474 	} else {
475 		(void) snprintf(ofarg->fo_val, sizeof (ofarg->fo_val),
476 		    "0x%x%016x", u128.hi, u128.lo);
477 	}
478 
479 	switch (field->nf_type) {
480 	case NVMEADM_FT_BYTES:
481 		/*
482 		 * Right now we a 64-bit byte value is 16 EiB. If we have more
483 		 * than that, error so we do something more clever, but
484 		 * otherwise punt for the time being.
485 		 */
486 		if (u128.hi != 0) {
487 			warnx("encountered 128-bit size with upper bits set "
488 			    "for field %s, cannot accurately convert",
489 			    field->nf_desc);
490 			u128.lo = UINT64_MAX;
491 		}
492 
493 		if (field->nf_addend.nfa_shift != 0 ||
494 		    field->nf_addend.nfa_addend != 0) {
495 			warnx("encountered 128-bit size with addend request "
496 			    "for field %s, but conversion not implemented",
497 			    field->nf_desc);
498 		}
499 
500 		nicenum(u128.lo, ofarg->fo_hval, sizeof (ofarg->fo_hval));
501 		break;
502 	case NVMEADM_FT_GUID:
503 		u8p = data + field->nf_off;
504 		(void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval),
505 		    "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
506 		    "%02x%02x%02x%02x%02x%02x",
507 		    u8p[15], u8p[14], u8p[13], u8p[12],
508 		    u8p[11], u8p[10], u8p[9], u8p[8],
509 		    u8p[7], u8p[6], u8p[5], u8p[4],
510 		    u8p[3], u8p[2], u8p[1], u8p[0]);
511 		break;
512 	case NVMEADM_FT_HEX:
513 		if (field->nf_addend.nfa_shift != 0 ||
514 		    field->nf_addend.nfa_addend != 0) {
515 			warnx("encountered 128-bit field with addend, but "
516 			    "cannot apply it");
517 		}
518 		(void) memcpy(ofarg->fo_hval, ofarg->fo_val,
519 		    sizeof (ofarg->fo_hval));
520 		break;
521 	default:
522 		break;
523 	}
524 }
525 
526 static void
nvmeadm_field_extract(const nvmeadm_field_t * field,const void * data,field_ofmt_t * ofarg,uint64_t * bp)527 nvmeadm_field_extract(const nvmeadm_field_t *field, const void *data,
528     field_ofmt_t *ofarg, uint64_t *bp)
529 {
530 	uint64_t val;
531 
532 	/*
533 	 * Don't touch containers. There is nothing to extract.
534 	 */
535 	if (field->nf_type == NVMEADM_FT_CONTAINER)
536 		return;
537 
538 	/*
539 	 * If this is an ASCII field, then we just handle this immediately.
540 	 */
541 	if (field->nf_type == NVMEADM_FT_ASCII ||
542 	    field->nf_type == NVMEADM_FT_ASCIIZ) {
543 		field_extract_ascii(data, field->nf_type, field->nf_len,
544 		    field->nf_off, ofarg);
545 		return;
546 	}
547 
548 	/*
549 	 * Next look at the type and size and see if this is something that we
550 	 * can deal with simply. Items that are less than a u64 are easy. If we
551 	 * had better C23 support and therefore could deal with a u128, then
552 	 * this would be simpler too. Until then, we split this based on things
553 	 * larger than a u64 and those not.
554 	 */
555 	if (field->nf_len > sizeof (uint64_t)) {
556 		switch (field->nf_type) {
557 		case NVMEADM_FT_HEX:
558 		case NVMEADM_FT_BYTES:
559 		case NVMEADM_FT_GUID:
560 			VERIFY3U(field->nf_len, ==, 16);
561 			nvmeadm_field_extract_u128(field, data, ofarg);
562 			break;
563 		default:
564 			abort();
565 		}
566 		return;
567 	}
568 
569 	/*
570 	 * NVMe integers are defined as being encoded in little endian.
571 	 */
572 	val = 0;
573 	const uint8_t *u8p = data + field->nf_off;
574 	for (size_t i = 0; i < field->nf_len; i++) {
575 		uint8_t shift = i * NBBY;
576 		val |= (uint64_t)u8p[i] << shift;
577 	}
578 
579 	if (bp != NULL)
580 		*bp = val;
581 	(void) snprintf(ofarg->fo_val, sizeof (ofarg->fo_val), "0x%" PRIx64,
582 	    val);
583 
584 	switch (field->nf_type) {
585 	case NVMEADM_FT_HEX:
586 		/*
587 		 * The "human" string is the version with the addend applied.
588 		 */
589 		val = nvmeadm_apply_addend(val, &field->nf_addend);
590 		(void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval),
591 		    "0x%" PRIx64, val);
592 		break;
593 	case NVMEADM_FT_UNIT:
594 		val = nvmeadm_apply_addend(val, &field->nf_addend);
595 		(void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval),
596 		    "%" PRIu64 " %s", val, field->nf_addend.nfa_unit);
597 		break;
598 	case NVMEADM_FT_BITS:
599 		/* No human string for these */
600 		break;
601 	case NVMEADM_FT_STRMAP:
602 		if (val < ARRAY_SIZE(field->nf_strs) &&
603 		    field->nf_strs[val] != NULL) {
604 			(void) strlcpy(ofarg->fo_hval, field->nf_strs[val],
605 			    sizeof (ofarg->fo_hval));
606 		} else {
607 			(void) strlcpy(ofarg->fo_hval, "reserved",
608 			    sizeof (ofarg->fo_hval));
609 		}
610 		break;
611 	case NVMEADM_FT_BYTES:
612 		val = nvmeadm_apply_addend(val, &field->nf_addend);
613 		nicenum(val, ofarg->fo_hval, sizeof (ofarg->fo_hval));
614 		break;
615 	case NVMEADM_FT_PERCENT:
616 		(void) snprintf(ofarg->fo_hval, sizeof (ofarg->fo_hval), "%u%%",
617 		    val);
618 		break;
619 	case NVMEADM_FT_GUID:
620 		/*
621 		 * GUIDs are larger than 8 bytes and so we should never hit
622 		 * this.
623 		 */
624 		abort();
625 	case NVMEADM_FT_ASCII:
626 	case NVMEADM_FT_ASCIIZ:
627 	case NVMEADM_FT_CONTAINER:
628 		/* Should already be handled above */
629 		abort();
630 	}
631 }
632 
633 void
nvmeadm_field_print(nvmeadm_field_print_t * print)634 nvmeadm_field_print(nvmeadm_field_print_t *print)
635 {
636 	if (print->fp_ofmt == NULL && print->fp_header != NULL &&
637 	    nvmeadm_field_filter(print, print->fp_base, NULL)) {
638 		(void) printf("%s\n", print->fp_header);
639 	}
640 
641 	for (size_t i = 0; i < print->fp_nfields; i++) {
642 		const nvmeadm_field_t *field = &print->fp_fields[i];
643 		field_ofmt_t ofarg = { 0 };
644 
645 		/*
646 		 * See if this field is one that is meaningful to this revision
647 		 * of the log page or controller. If the version is NULL or the
648 		 * revision is 0 in the field, then there is nothing to check.
649 		 */
650 		if (field->nf_rev != 0 && field->nf_rev > print->fp_rev) {
651 			continue;
652 		}
653 
654 		if (field->nf_maxrev != 0 && print->fp_rev > field->nf_maxrev) {
655 			continue;
656 		}
657 
658 		if (field->nf_vers != NULL && print->fp_vers != NULL &&
659 		    !nvme_vers_atleast(print->fp_vers, field->nf_vers)) {
660 			continue;
661 		}
662 
663 		ofarg.fo_base = print->fp_base;
664 		ofarg.fo_short = field->nf_short;
665 		ofarg.fo_desc = field->nf_desc;
666 		ofarg.fo_off = print->fp_off + field->nf_off;
667 		ofarg.fo_bitoff = 0;
668 		ofarg.fo_len = field->nf_len;
669 		ofarg.fo_bitlen = 0;
670 
671 		/*
672 		 * Extract the value from the field and perform any conversions
673 		 * to a human value where appropriate.
674 		 */
675 		uint64_t bit_val;
676 		nvmeadm_field_extract(field, print->fp_data, &ofarg, &bit_val);
677 
678 		field_print_one(print, &ofarg, field->nf_type);
679 
680 		/*
681 		 * Now that we've dealt with this, handle anything that's
682 		 * somewhat recursive in nature.
683 		 */
684 		if (field->nf_type == NVMEADM_FT_CONTAINER) {
685 			char buf[256];
686 			nvmeadm_field_print_t copy = *print;
687 
688 			if (print->fp_base == NULL) {
689 				(void) strlcpy(buf, field->nf_short,
690 				    sizeof (buf));
691 			} else {
692 				(void) snprintf(buf, sizeof (buf), "%s.%s",
693 				    print->fp_base, field->nf_short);
694 			}
695 
696 			copy.fp_header = NULL;
697 			copy.fp_base = buf;
698 			copy.fp_fields = field->nf_fields;
699 			copy.fp_nfields = field->nf_nfields;
700 			copy.fp_data += field->nf_off;
701 			copy.fp_dlen = field->nf_len;
702 			copy.fp_off += field->nf_off;
703 			copy.fp_indent++;
704 
705 			nvmeadm_field_print(&copy);
706 		} else if (field->nf_type == NVMEADM_FT_BITS) {
707 			char buf[256];
708 
709 			if (print->fp_base == NULL) {
710 				(void) strlcpy(buf, field->nf_short,
711 				    sizeof (buf));
712 			} else {
713 				(void) snprintf(buf, sizeof (buf), "%s.%s",
714 				    print->fp_base, field->nf_short);
715 			}
716 
717 			field_print_bits(print, field->nf_bits, field->nf_nbits,
718 			    bit_val, buf, ofarg.fo_off, 0, 1);
719 		}
720 	}
721 }
722 
723 bool
nvmeadm_log_page_fields(const nvme_process_arg_t * npa,const char * name,const void * data,size_t len,nvmeadm_field_filt_t * filts,size_t nfilts,nvmeadm_log_field_flag_t flags)724 nvmeadm_log_page_fields(const nvme_process_arg_t *npa, const char *name,
725     const void *data, size_t len, nvmeadm_field_filt_t *filts, size_t nfilts,
726     nvmeadm_log_field_flag_t flags)
727 {
728 	bool ret = true, found = false;
729 	VERIFY0(flags & ~NVMEADM_LFF_CHECK_NAME);
730 
731 	/*
732 	 * If we don't have a log page name, that's an indication that we're
733 	 * being asked to just do our hex print.
734 	 */
735 	if (name == NULL) {
736 		nvmeadm_dump_hex(data, len);
737 		return (ret);
738 	}
739 
740 	for (size_t i = 0; i < ARRAY_SIZE(field_log_map); i++) {
741 		if (strcmp(name, field_log_map[i]->nlfi_log) != 0) {
742 			continue;
743 		}
744 
745 		/*
746 		 * We've found a match for this log page. Ensure we don't hex
747 		 * dump it at the end.
748 		 */
749 		found = true;
750 
751 		if (len < field_log_map[i]->nlfi_min) {
752 			errx(-1, "cannot print log %s: log requires "
753 			    "0x%zx bytes of data but only have 0x%zx",
754 			    name, field_log_map[i]->nlfi_min, len);
755 		}
756 
757 		nvmeadm_field_print_t print = { 0 };
758 		if (field_log_map[i]->nlfi_getrev != NULL) {
759 			print.fp_rev = field_log_map[i]->nlfi_getrev(data,
760 			    len);
761 		}
762 
763 		/*
764 		 * This may be NULL if we're not getting this fresh from a
765 		 * controller.
766 		 */
767 		print.fp_vers = npa->npa_version;
768 
769 		print.fp_filts = filts;
770 		print.fp_nfilts = nfilts;
771 		print.fp_ofmt = npa->npa_ofmt;
772 
773 		/*
774 		 * A registered log page may instead ask to drive the process as
775 		 * there is variable data or similar present.
776 		 */
777 		if (field_log_map[i]->nlfi_drive != NULL) {
778 			if (!field_log_map[i]->nlfi_drive(&print, data, len)) {
779 				ret = false;
780 			}
781 		} else {
782 			print.fp_fields = field_log_map[i]->nlfi_fields;
783 			print.fp_nfields = field_log_map[i]->nlfi_nfields;
784 
785 			/*
786 			 * We set the base string to NULL for most log pages
787 			 * that aren't comprised of variable components by
788 			 * default. Ones that are variable instead will set this
789 			 * as part of their drive function callback.
790 			 */
791 			print.fp_base = NULL;
792 
793 			print.fp_data = data;
794 			print.fp_dlen = len;
795 
796 			nvmeadm_field_print(&print);
797 			break;
798 		}
799 	}
800 
801 	if (!found) {
802 		if ((flags & NVMEADM_LFF_CHECK_NAME) != 0) {
803 			warnx("unable to print log page %s: the log page is "
804 			    "either unknown or printing information is missing",
805 			    name);
806 			ret = false;
807 		}
808 
809 		if (npa->npa_ofmt != NULL) {
810 			errx(-1, "parsable mode requested, but unable to print "
811 			    "parsable output");
812 		}
813 
814 		nvmeadm_dump_hex(data, len);
815 	}
816 
817 	for (size_t i = 0; i < nfilts; i++) {
818 		if (!filts[i].nff_used) {
819 			warnx("filter '%s' did not match any fields",
820 			    filts[i].nff_str);
821 			ret = false;
822 		}
823 	}
824 
825 	return (ret);
826 }
827