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