xref: /illumos-gate/usr/src/cmd/nvmeadm/nvmeadm_ofmt.c (revision 08855964b9970604433f7b19dcd71cf5af5e5f14)
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 2024 Oxide Computer Company
14  * Copyright 2022 Tintri by DDN, Inc. All rights reserved.
15  */
16 
17 /*
18  * nvmeadm output formatting for ofmt based rendering
19  */
20 
21 #include <strings.h>
22 #include <sys/sysmacros.h>
23 #include <err.h>
24 
25 #include "nvmeadm.h"
26 
27 typedef struct {
28 	uint32_t	nb_flag;
29 	const char	*nb_str;
30 } nvmeadm_bitstr_t;
31 
32 static boolean_t
33 nvmeadm_bits_to_str(uint32_t val, const nvmeadm_bitstr_t *strs, size_t nstrs,
34     char *buf, size_t buflen)
35 {
36 	boolean_t comma = B_FALSE;
37 
38 	buf[0] = '\0';
39 	for (size_t i = 0; i < nstrs; i++) {
40 		if ((val & strs[i].nb_flag) != strs[i].nb_flag)
41 			continue;
42 		if (comma && strlcat(buf, ",", buflen) >= buflen)
43 			return (B_FALSE);
44 		if (strlcat(buf, strs[i].nb_str, buflen) >= buflen)
45 			return (B_FALSE);
46 		comma = true;
47 	}
48 
49 	if (buf[0] == '\0') {
50 		if (strlcat(buf, "--", buflen) >= buflen)
51 			return (B_FALSE);
52 	}
53 
54 	return (B_TRUE);
55 }
56 
57 typedef enum nvme_list_ofmt_field {
58 	NVME_LIST_MODEL,
59 	NVME_LIST_SERIAL,
60 	NVME_LIST_FWREV,
61 	NVME_LIST_VERSION,
62 	NVME_LIST_SIZE,
63 	NVME_LIST_CAPACITY,
64 	NVME_LIST_USED,
65 	NVME_LIST_INSTANCE,
66 	NVME_LIST_NAMESPACE,
67 	NVME_LIST_DISK,
68 	NVME_LIST_UNALLOC,
69 } nvme_list_ofmt_field_t;
70 
71 static boolean_t
72 nvmeadm_list_common_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
73 {
74 	nvmeadm_list_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg;
75 	nvme_ctrl_info_t *ctrl = list->nloa_ctrl;
76 	const nvme_version_t *vers;
77 	size_t ret;
78 
79 	switch (ofmt_arg->ofmt_id) {
80 	case NVME_LIST_MODEL:
81 		ret = strlcpy(buf, nvme_ctrl_info_model(ctrl), buflen);
82 		break;
83 	case NVME_LIST_SERIAL:
84 		ret = strlcpy(buf, nvme_ctrl_info_serial(ctrl), buflen);
85 		break;
86 	case NVME_LIST_FWREV:
87 		ret = strlcpy(buf, nvme_ctrl_info_fwrev(ctrl), buflen);
88 		break;
89 	case NVME_LIST_VERSION:
90 		vers = nvme_ctrl_info_version(ctrl);
91 		ret = snprintf(buf, buflen, "%u.%u", vers->v_major,
92 		    vers->v_minor);
93 		break;
94 	case NVME_LIST_INSTANCE:
95 		ret = strlcpy(buf, list->nloa_name, buflen);
96 		break;
97 	default:
98 		warnx("internal programmer error: encountered unknown ofmt "
99 		    "argument id 0x%x", ofmt_arg->ofmt_id);
100 		abort();
101 	}
102 	if (ret >= buflen) {
103 		return (B_FALSE);
104 	}
105 	return (B_TRUE);
106 }
107 
108 static boolean_t
109 nvmeadm_list_ctrl_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
110 {
111 	nvmeadm_list_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg;
112 	nvme_ctrl_info_t *ctrl = list->nloa_ctrl;
113 	nvme_uint128_t u128;
114 	size_t ret;
115 
116 	switch (ofmt_arg->ofmt_id) {
117 	case NVME_LIST_CAPACITY:
118 		if (nvme_ctrl_info_cap(ctrl, &u128)) {
119 			ret = nvme_snprint_uint128(buf, buflen, u128, 0, 0);
120 		} else {
121 			return (B_FALSE);
122 		}
123 		break;
124 	case NVME_LIST_UNALLOC:
125 		if (nvme_ctrl_info_unalloc_cap(ctrl, &u128)) {
126 			ret = nvme_snprint_uint128(buf, buflen, u128, 0, 0);
127 		} else {
128 			return (B_FALSE);
129 		}
130 		break;
131 	default:
132 		warnx("internal programmer error: encountered unknown ofmt "
133 		    "argument id 0x%x", ofmt_arg->ofmt_id);
134 		abort();
135 	}
136 
137 	if (ret >= buflen) {
138 		return (B_FALSE);
139 	}
140 	return (B_TRUE);
141 }
142 
143 static boolean_t
144 nvmeadm_list_nsid_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
145 {
146 	nvmeadm_list_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg;
147 	nvme_ns_info_t *ns = list->nloa_ns;
148 	const nvme_nvm_lba_fmt_t *fmt = NULL;
149 	uint64_t val;
150 	size_t ret;
151 
152 
153 	(void) nvme_ns_info_curformat(ns, &fmt);
154 
155 	switch (ofmt_arg->ofmt_id) {
156 	case NVME_LIST_NAMESPACE:
157 		ret = snprintf(buf, buflen, "%u", nvme_ns_info_nsid(ns));
158 		break;
159 	case NVME_LIST_DISK:
160 		if (list->nloa_disk != NULL) {
161 			ret = strlcpy(buf, list->nloa_disk, buflen);
162 		} else {
163 			return (B_FALSE);
164 		}
165 		break;
166 	case NVME_LIST_SIZE:
167 		if (nvme_ns_info_size(ns, &val) && fmt != NULL) {
168 			val *= nvme_nvm_lba_fmt_data_size(fmt);
169 			ret = snprintf(buf, buflen, "%" PRIu64, val);
170 		} else {
171 			return (B_FALSE);
172 		}
173 		break;
174 	case NVME_LIST_CAPACITY:
175 		if (nvme_ns_info_size(ns, &val) && fmt != NULL) {
176 			val *= nvme_nvm_lba_fmt_data_size(fmt);
177 			ret = snprintf(buf, buflen, "%" PRIu64, val);
178 		} else {
179 			return (B_FALSE);
180 		}
181 		break;
182 	case NVME_LIST_USED:
183 		if (nvme_ns_info_size(ns, &val) && fmt != NULL) {
184 			val *= nvme_nvm_lba_fmt_data_size(fmt);
185 			ret = snprintf(buf, buflen, "%" PRIu64, val);
186 		} else {
187 			return (B_FALSE);
188 		}
189 		break;
190 	default:
191 		warnx("internal programmer error: encountered unknown ofmt "
192 		    "argument id 0x%x", ofmt_arg->ofmt_id);
193 		abort();
194 	}
195 
196 	if (ret >= buflen) {
197 		return (B_FALSE);
198 	}
199 	return (B_TRUE);
200 }
201 
202 const ofmt_field_t nvmeadm_list_ctrl_ofmt[] = {
203 	{ "MODEL", 30, NVME_LIST_MODEL, nvmeadm_list_common_ofmt_cb },
204 	{ "SERIAL", 30, NVME_LIST_SERIAL, nvmeadm_list_common_ofmt_cb },
205 	{ "FWREV", 10, NVME_LIST_FWREV, nvmeadm_list_common_ofmt_cb },
206 	{ "VERSION", 10, NVME_LIST_VERSION, nvmeadm_list_common_ofmt_cb },
207 	{ "CAPACITY", 15, NVME_LIST_CAPACITY, nvmeadm_list_ctrl_ofmt_cb },
208 	{ "INSTANCE", 10, NVME_LIST_INSTANCE, nvmeadm_list_common_ofmt_cb },
209 	{ "UNALLOCATED", 15, NVME_LIST_UNALLOC, nvmeadm_list_ctrl_ofmt_cb },
210 	{ NULL, 0, 0, NULL }
211 };
212 
213 const ofmt_field_t nvmeadm_list_nsid_ofmt[] = {
214 	{ "MODEL", 30, NVME_LIST_MODEL, nvmeadm_list_common_ofmt_cb },
215 	{ "SERIAL", 30, NVME_LIST_SERIAL, nvmeadm_list_common_ofmt_cb },
216 	{ "FWREV", 10, NVME_LIST_FWREV, nvmeadm_list_common_ofmt_cb },
217 	{ "VERSION", 10, NVME_LIST_VERSION, nvmeadm_list_common_ofmt_cb },
218 	{ "SIZE", 15, NVME_LIST_SIZE, nvmeadm_list_nsid_ofmt_cb },
219 	{ "CAPACITY", 15, NVME_LIST_CAPACITY, nvmeadm_list_nsid_ofmt_cb },
220 	{ "USED", 15, NVME_LIST_USED, nvmeadm_list_nsid_ofmt_cb },
221 	{ "INSTANCE", 10, NVME_LIST_INSTANCE, nvmeadm_list_common_ofmt_cb },
222 	{ "NAMESPACE", 10, NVME_LIST_NAMESPACE, nvmeadm_list_nsid_ofmt_cb },
223 	{ "DISK", 15, NVME_LIST_DISK, nvmeadm_list_nsid_ofmt_cb },
224 	{ NULL, 0, 0, NULL }
225 };
226 
227 typedef enum {
228 	NVME_LIST_LOGS_DEVICE,
229 	NVME_LIST_LOGS_NAME,
230 	NVME_LIST_LOGS_DESC,
231 	NVME_LIST_LOGS_SCOPE,
232 	NVME_LIST_LOGS_FIELDS,
233 	NVME_LIST_LOGS_CSI,
234 	NVME_LIST_LOGS_LID,
235 	NVME_LIST_LOGS_SIZE,
236 	NVME_LIST_LOGS_MINSIZE,
237 	NVME_LIST_LOGS_IMPL,
238 	NVME_LIST_LOGS_SOURCES,
239 	NVME_LIST_LOGS_KIND
240 } nvme_list_logs_ofmt_field_t;
241 
242 static const nvmeadm_bitstr_t nvmeadm_log_scopes[] = {
243 	{ NVME_LOG_SCOPE_CTRL, "controller" },
244 	{ NVME_LOG_SCOPE_NVM, "nvm" },
245 	{ NVME_LOG_SCOPE_NS, "namespace" }
246 };
247 
248 static const nvmeadm_bitstr_t nvmeadm_log_fields[] = {
249 	{ NVME_LOG_DISC_F_NEED_LSP, "lsp" },
250 	{ NVME_LOG_DISC_F_NEED_LSI, "lsi" },
251 	{ NVME_LOG_DISC_F_NEED_RAE, "rae" }
252 };
253 
254 static const nvmeadm_bitstr_t nvmeadm_log_sources[] = {
255 	{ NVME_LOG_DISC_S_SPEC, "spec" },
256 	{ NVME_LOG_DISC_S_ID_CTRL, "identify-controller" },
257 	{ NVME_LOG_DISC_S_DB, "internal-db" },
258 	{ NVME_LOG_DISC_S_CMD, "command" }
259 };
260 
261 static boolean_t
262 nvmeadm_list_logs_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
263 {
264 	const nvmeadm_list_logs_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg;
265 	const nvme_log_disc_t *disc = list->nlloa_disc;
266 	uint64_t alloc;
267 	size_t ret;
268 	nvme_log_size_kind_t kind;
269 
270 	switch (ofmt_arg->ofmt_id) {
271 	case NVME_LIST_LOGS_DEVICE:
272 		ret = strlcpy(buf, list->nlloa_name, buflen);
273 		break;
274 	case NVME_LIST_LOGS_NAME:
275 		ret = strlcpy(buf, nvme_log_disc_name(disc), buflen);
276 		break;
277 	case NVME_LIST_LOGS_DESC:
278 		ret = strlcpy(buf, nvme_log_disc_desc(disc), buflen);
279 		break;
280 	case NVME_LIST_LOGS_SCOPE:
281 		return (nvmeadm_bits_to_str(nvme_log_disc_scopes(disc),
282 		    nvmeadm_log_scopes, ARRAY_SIZE(nvmeadm_log_scopes), buf,
283 		    buflen));
284 	case NVME_LIST_LOGS_FIELDS:
285 		return (nvmeadm_bits_to_str(nvme_log_disc_fields(disc),
286 		    nvmeadm_log_fields, ARRAY_SIZE(nvmeadm_log_fields), buf,
287 		    buflen));
288 		break;
289 	case NVME_LIST_LOGS_CSI:
290 		switch (nvme_log_disc_csi(disc)) {
291 		case NVME_CSI_NVM:
292 			ret = strlcpy(buf, "nvm", buflen);
293 			break;
294 		case NVME_CSI_KV:
295 			ret = strlcpy(buf, "kv", buflen);
296 			break;
297 		case NVME_CSI_ZNS:
298 			ret = strlcpy(buf, "zns", buflen);
299 			break;
300 		default:
301 			ret = snprintf(buf, buflen, "unknown (0x%x)",
302 			    nvme_log_disc_csi(disc));
303 			break;
304 		}
305 		break;
306 	case NVME_LIST_LOGS_LID:
307 		ret = snprintf(buf, buflen, "0x%x", nvme_log_disc_lid(disc));
308 		break;
309 	case NVME_LIST_LOGS_SIZE:
310 	case NVME_LIST_LOGS_MINSIZE:
311 		kind = nvme_log_disc_size(disc, &alloc);
312 
313 		if (kind == NVME_LOG_SIZE_K_UNKNOWN) {
314 			return (B_FALSE);
315 		}
316 
317 		if (kind == NVME_LOG_SIZE_K_VAR &&
318 		    ofmt_arg->ofmt_id == NVME_LIST_LOGS_SIZE) {
319 			return (B_FALSE);
320 		}
321 
322 		ret = snprintf(buf, buflen, "%" PRIu64, alloc);
323 		break;
324 	case NVME_LIST_LOGS_IMPL:
325 		ret = strlcpy(buf, nvme_log_disc_impl(disc) ? "yes" : "no",
326 		    buflen);
327 		break;
328 	case NVME_LIST_LOGS_SOURCES:
329 		return (nvmeadm_bits_to_str(nvme_log_disc_sources(disc),
330 		    nvmeadm_log_sources, ARRAY_SIZE(nvmeadm_log_sources), buf,
331 		    buflen));
332 		break;
333 	case NVME_LIST_LOGS_KIND:
334 		switch (nvme_log_disc_kind(disc)) {
335 		case NVME_LOG_ID_MANDATORY:
336 			ret = strlcpy(buf, "mandatory", buflen);
337 			break;
338 		case NVME_LOG_ID_OPTIONAL:
339 			ret = strlcpy(buf, "optional", buflen);
340 			break;
341 		case NVME_LOG_ID_VENDOR_SPECIFIC:
342 			ret = strlcpy(buf, "vendor-specific", buflen);
343 			break;
344 		default:
345 			ret = snprintf(buf, buflen, "unknown (0x%x)",
346 			    nvme_log_disc_kind(disc));
347 			break;
348 		}
349 		break;
350 	default:
351 		warnx("internal programmer error: encountered unknown ofmt "
352 		    "argument id 0x%x", ofmt_arg->ofmt_id);
353 		abort();
354 	}
355 
356 	return (ret < buflen);
357 }
358 
359 const char *nvmeadm_list_logs_fields = "device,name,scope,fields,desc";
360 const char *nvmeadm_list_logs_fields_impl = "device,name,scope,impl,fields,"
361 	"desc";
362 const ofmt_field_t nvmeadm_list_logs_ofmt[] = {
363 	{ "DEVICE", 8, NVME_LIST_LOGS_DEVICE, nvmeadm_list_logs_ofmt_cb },
364 	{ "NAME", 18, NVME_LIST_LOGS_NAME, nvmeadm_list_logs_ofmt_cb },
365 	{ "DESC", 30, NVME_LIST_LOGS_DESC, nvmeadm_list_logs_ofmt_cb },
366 	{ "SCOPE", 14, NVME_LIST_LOGS_SCOPE, nvmeadm_list_logs_ofmt_cb },
367 	{ "FIELDS", 10, NVME_LIST_LOGS_FIELDS, nvmeadm_list_logs_ofmt_cb },
368 	{ "CSI", 6, NVME_LIST_LOGS_CSI, nvmeadm_list_logs_ofmt_cb },
369 	{ "LID", 6, NVME_LIST_LOGS_LID, nvmeadm_list_logs_ofmt_cb },
370 	{ "SIZE", 10, NVME_LIST_LOGS_SIZE, nvmeadm_list_logs_ofmt_cb },
371 	{ "MINSIZE", 10, NVME_LIST_LOGS_MINSIZE, nvmeadm_list_logs_ofmt_cb },
372 	{ "IMPL", 6, NVME_LIST_LOGS_IMPL, nvmeadm_list_logs_ofmt_cb },
373 	{ "SOURCES", 20, NVME_LIST_LOGS_SOURCES, nvmeadm_list_logs_ofmt_cb },
374 	{ "KIND", 16, NVME_LIST_LOGS_KIND, nvmeadm_list_logs_ofmt_cb },
375 	{ NULL, 0, 0, NULL }
376 };
377 
378 typedef enum {
379 	NVME_LIST_FEATS_DEVICE,
380 	NVME_LIST_FEATS_SHORT,
381 	NVME_LIST_FEATS_SPEC,
382 	NVME_LIST_FEATS_FID,
383 	NVME_LIST_FEATS_SCOPE,
384 	NVME_LIST_FEATS_KIND,
385 	NVME_LIST_FEATS_CSI,
386 	NVME_LIST_FEATS_FLAGS,
387 	NVME_LIST_FEATS_GET_IN,
388 	NVME_LIST_FEATS_SET_IN,
389 	NVME_LIST_FEATS_GET_OUT,
390 	NVME_LIST_FEATS_SET_OUT,
391 	NVME_LIST_FEATS_DATA_LEN,
392 	NVME_LIST_FEATS_IMPL
393 } nvme_list_features_ofmt_field_t;
394 
395 static const nvmeadm_bitstr_t nvmeadm_feat_scopes[] = {
396 	{ NVME_FEAT_SCOPE_CTRL, "controller" },
397 	{ NVME_FEAT_SCOPE_NS, "namespace" }
398 };
399 
400 static const nvmeadm_bitstr_t nvmeadm_feat_get_in[] = {
401 	{ NVME_GET_FEAT_F_CDW11, "cdw11" },
402 	{ NVME_GET_FEAT_F_DATA, "data" },
403 	{ NVME_GET_FEAT_F_NSID, "nsid" }
404 };
405 
406 static const nvmeadm_bitstr_t nvmeadm_feat_set_in[] = {
407 	{ NVME_SET_FEAT_F_CDW11, "cdw11" },
408 	{ NVME_SET_FEAT_F_CDW12, "cdw12" },
409 	{ NVME_SET_FEAT_F_CDW13, "cdw13" },
410 	{ NVME_SET_FEAT_F_CDW14, "cdw14" },
411 	{ NVME_SET_FEAT_F_CDW15, "cdw15" },
412 	{ NVME_SET_FEAT_F_DATA, "data" },
413 	{ NVME_SET_FEAT_F_NSID, "nsid" }
414 };
415 
416 static const nvmeadm_bitstr_t nvmeadm_feat_output[] = {
417 	{ NVME_FEAT_OUTPUT_CDW0, "cdw0" },
418 	{ NVME_FEAT_OUTPUT_DATA, "data" }
419 };
420 
421 static const nvmeadm_bitstr_t nvmeadm_feat_flags[] = {
422 	{ NVME_FEAT_F_GET_BCAST_NSID, "get-bcastns" },
423 	{ NVME_FEAT_F_SET_BCAST_NSID, "set-bcastns" }
424 };
425 
426 static const nvmeadm_bitstr_t nvmeadm_feat_csi[] = {
427 	{ NVME_FEAT_CSI_NVM, "nvm" }
428 };
429 
430 static boolean_t
431 nvmeadm_list_features_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
432 {
433 	const nvmeadm_list_features_ofmt_arg_t *nlfo = ofmt_arg->ofmt_cbarg;
434 	const nvme_feat_disc_t *feat = nlfo->nlfoa_feat;
435 	size_t ret;
436 
437 	switch (ofmt_arg->ofmt_id) {
438 	case NVME_LIST_FEATS_DEVICE:
439 		ret = strlcpy(buf, nlfo->nlfoa_name, buflen);
440 		break;
441 	case NVME_LIST_FEATS_SHORT:
442 		ret = strlcpy(buf, nvme_feat_disc_short(feat), buflen);
443 		break;
444 	case NVME_LIST_FEATS_SPEC:
445 		ret = strlcpy(buf, nvme_feat_disc_spec(feat), buflen);
446 		break;
447 	case NVME_LIST_FEATS_FID:
448 		ret = snprintf(buf, buflen, "0x%x", nvme_feat_disc_fid(feat));
449 		break;
450 	case NVME_LIST_FEATS_SCOPE:
451 		return (nvmeadm_bits_to_str(nvme_feat_disc_scope(feat),
452 		    nvmeadm_feat_scopes, ARRAY_SIZE(nvmeadm_feat_scopes), buf,
453 		    buflen));
454 	case NVME_LIST_FEATS_KIND:
455 		switch (nvme_feat_disc_kind(feat)) {
456 		case NVME_FEAT_MANDATORY:
457 			ret = strlcpy(buf, "mandatory", buflen);
458 			break;
459 		case NVME_FEAT_OPTIONAL:
460 			ret = strlcpy(buf, "optional", buflen);
461 			break;
462 		case NVME_FEAT_VENDOR_SPECIFIC:
463 			ret = strlcpy(buf, "vendor-specific", buflen);
464 			break;
465 		default:
466 			ret = snprintf(buf, buflen, "unknown (0x%x)",
467 			    nvme_feat_disc_kind(feat));
468 			break;
469 		}
470 		break;
471 	case NVME_LIST_FEATS_CSI:
472 		if (nvme_feat_disc_csi(feat) == NVME_FEAT_CSI_NONE) {
473 			ret = strlcpy(buf, "none", buflen);
474 			break;
475 		}
476 
477 		return (nvmeadm_bits_to_str(nvme_feat_disc_csi(feat),
478 		    nvmeadm_feat_csi, ARRAY_SIZE(nvmeadm_feat_csi), buf,
479 		    buflen));
480 	case NVME_LIST_FEATS_FLAGS:
481 		return (nvmeadm_bits_to_str(nvme_feat_disc_flags(feat),
482 		    nvmeadm_feat_flags, ARRAY_SIZE(nvmeadm_feat_flags), buf,
483 		    buflen));
484 	case NVME_LIST_FEATS_GET_IN:
485 		return (nvmeadm_bits_to_str(nvme_feat_disc_fields_get(feat),
486 		    nvmeadm_feat_get_in, ARRAY_SIZE(nvmeadm_feat_get_in), buf,
487 		    buflen));
488 	case NVME_LIST_FEATS_SET_IN:
489 		return (nvmeadm_bits_to_str(nvme_feat_disc_fields_set(feat),
490 		    nvmeadm_feat_set_in, ARRAY_SIZE(nvmeadm_feat_set_in), buf,
491 		    buflen));
492 	case NVME_LIST_FEATS_GET_OUT:
493 		return (nvmeadm_bits_to_str(nvme_feat_disc_output_get(feat),
494 		    nvmeadm_feat_output, ARRAY_SIZE(nvmeadm_feat_output), buf,
495 		    buflen));
496 	case NVME_LIST_FEATS_SET_OUT:
497 		return (nvmeadm_bits_to_str(nvme_feat_disc_output_set(feat),
498 		    nvmeadm_feat_output, ARRAY_SIZE(nvmeadm_feat_output), buf,
499 		    buflen));
500 	case NVME_LIST_FEATS_DATA_LEN:
501 		if (nvme_feat_disc_data_size(feat) == 0) {
502 			ret = strlcpy(buf, "-", buflen);
503 		} else {
504 			ret = snprintf(buf, buflen, "%" PRIu64,
505 			    nvme_feat_disc_data_size(feat));
506 		}
507 		break;
508 	case NVME_LIST_FEATS_IMPL:
509 		switch (nvme_feat_disc_impl(feat)) {
510 		case NVME_FEAT_IMPL_UNKNOWN:
511 			ret = strlcpy(buf, "unknown", buflen);
512 			break;
513 		case NVME_FEAT_IMPL_UNSUPPORTED:
514 			ret = strlcpy(buf, "no", buflen);
515 			break;
516 		case NVME_FEAT_IMPL_SUPPORTED:
517 			ret = strlcpy(buf, "yes", buflen);
518 			break;
519 		default:
520 			ret = snprintf(buf, buflen, "unknown (0x%x)",
521 			    nvme_feat_disc_impl(feat));
522 			break;
523 		}
524 		break;
525 	default:
526 		warnx("internal programmer error: encountered unknown ofmt "
527 		    "argument id 0x%x", ofmt_arg->ofmt_id);
528 		abort();
529 	}
530 
531 	return (ret < buflen);
532 }
533 
534 const char *nvmeadm_list_features_fields = "device,short,scope,impl,spec";
535 const ofmt_field_t nvmeadm_list_features_ofmt[] = {
536 	{ "DEVICE", 8, NVME_LIST_FEATS_DEVICE, nvmeadm_list_features_ofmt_cb },
537 	{ "SHORT", 14, NVME_LIST_FEATS_SHORT, nvmeadm_list_features_ofmt_cb },
538 	{ "SPEC", 30, NVME_LIST_FEATS_SPEC, nvmeadm_list_features_ofmt_cb },
539 	{ "FID", 6, NVME_LIST_FEATS_FID, nvmeadm_list_features_ofmt_cb },
540 	{ "SCOPE", 14, NVME_LIST_FEATS_SCOPE, nvmeadm_list_features_ofmt_cb },
541 	{ "KIND", 16, NVME_LIST_FEATS_KIND, nvmeadm_list_features_ofmt_cb },
542 	{ "CSI", 6, NVME_LIST_FEATS_CSI, nvmeadm_list_features_ofmt_cb },
543 	{ "FLAGS", 14, NVME_LIST_FEATS_FLAGS, nvmeadm_list_features_ofmt_cb },
544 	{ "GET-IN", 14, NVME_LIST_FEATS_GET_IN, nvmeadm_list_features_ofmt_cb },
545 	{ "SET-IN", 14, NVME_LIST_FEATS_SET_IN, nvmeadm_list_features_ofmt_cb },
546 	{ "GET-OUT", 14, NVME_LIST_FEATS_GET_OUT,
547 	    nvmeadm_list_features_ofmt_cb },
548 	{ "SET-OUT", 14, NVME_LIST_FEATS_SET_OUT,
549 	    nvmeadm_list_features_ofmt_cb },
550 	{ "DATALEN", 8, NVME_LIST_FEATS_DATA_LEN,
551 	    nvmeadm_list_features_ofmt_cb },
552 	{ "IMPL", 8, NVME_LIST_FEATS_IMPL, nvmeadm_list_features_ofmt_cb },
553 	{ NULL, 0, 0, NULL }
554 };
555