xref: /illumos-gate/usr/src/cmd/diskinfo/diskinfo.c (revision c75092296cd7a466f779e1785caee762bf71f3bc)
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 (c) 2018 Joyent Inc., All rights reserved.
14  * Copyright 2021 RackTop Systems, Inc.
15  * Copyright 2021 Tintri by DDN, Inc. All rights reserved.
16  * Copyright 2023 Oxide Computer Company
17  * Copyright 2024 Sebastian Wiedenroth
18  * Copyright 2026 Jason King
19  */
20 
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <limits.h>
29 #include <assert.h>
30 #include <ctype.h>
31 #include <stdarg.h>
32 #include <strings.h>
33 #include <err.h>
34 
35 #include <libdiskmgt.h>
36 #include <sys/nvpair.h>
37 #include <sys/param.h>
38 #include <sys/ccompile.h>
39 
40 #include <fm/libtopo.h>
41 #include <fm/topo_hc.h>
42 #include <fm/topo_list.h>
43 #include <sys/fm/protocol.h>
44 #include <modules/common/disk/disk.h>
45 
46 typedef struct di_opts {
47 	boolean_t di_scripted;
48 	boolean_t di_parseable;
49 	boolean_t di_physical;
50 	boolean_t di_condensed;
51 } di_opts_t;
52 
53 typedef struct di_phys {
54 	const char *dp_dev;
55 	const char *dp_serial;
56 	const char *dp_slotname;
57 	int dp_chassis;
58 	int dp_slot;
59 	int dp_faulty;
60 	int dp_locate;
61 } di_phys_t;
62 
63 static void
64 usage(const char *execname)
65 {
66 	(void) fprintf(stderr, "Usage: %s [-Hp] [{-c|-P}]\n", execname);
67 }
68 
69 static void
70 nvlist_query_string(nvlist_t *nvl, const char *label, char **val)
71 {
72 	if (nvlist_lookup_string(nvl, label, val) != 0)
73 		*val = "-";
74 }
75 
76 static const char *
77 display_string(const char *label)
78 {
79 	return ((label) ? label : "-");
80 }
81 
82 static const char *
83 display_tristate(int val)
84 {
85 	if (val == 0)
86 		return ("no");
87 	if (val == 1)
88 		return ("yes");
89 
90 	return ("-");
91 }
92 
93 static char
94 condensed_tristate(int val, char c)
95 {
96 	if (val == 0)
97 		return ('-');
98 	if (val == 1)
99 		return (c);
100 
101 	return ('?');
102 }
103 static int
104 disk_walker(topo_hdl_t *hp, tnode_t *np, void *arg)
105 {
106 	di_phys_t *pp = arg;
107 	topo_faclist_t fl;
108 	topo_faclist_t *lp;
109 	int e;
110 	topo_led_state_t mode;
111 	topo_led_type_t type;
112 	char *name, *slotname, *serial;
113 	boolean_t consider_label = B_TRUE;
114 
115 	if (strcmp(topo_node_name(np), DISK) != 0)
116 		return (TOPO_WALK_NEXT);
117 
118 	if (topo_prop_get_string(np, TOPO_PGROUP_STORAGE,
119 	    TOPO_STORAGE_LOGICAL_DISK_NAME, &name, &e) != 0) {
120 		return (TOPO_WALK_NEXT);
121 	}
122 
123 	if (strcmp(name, pp->dp_dev) != 0)
124 		return (TOPO_WALK_NEXT);
125 
126 	if (topo_prop_get_string(np, TOPO_PGROUP_STORAGE,
127 	    TOPO_STORAGE_SERIAL_NUM, &serial, &e) == 0) {
128 		pp->dp_serial = serial;
129 	}
130 
131 	/*
132 	 * There are several hierarchies of nodes that we may be dealing with.
133 	 * Here are a few examples:
134 	 *
135 	 * chassis -> bay -> disk
136 	 * chassis -> bay -> nvme -> disk
137 	 * motherboard -> pcie device -> nvme -> disk
138 	 * motherboard -> slot -> nvme -> disk
139 	 * chassis -> port -> usb device -> disk
140 	 * motherboard -> pcie device -> aic -> usb device -> disk
141 	 *
142 	 * The list of possibilties can go on. We want to try and see if we can
143 	 * identify what tree this is so we can figure out what to do. To
144 	 * accomplish this we basically walk our parent nodes looking for
145 	 * information until we find everything that we expect.
146 	 */
147 	for (tnode_t *pnp = topo_node_parent(np); pnp != NULL;
148 	    pnp = topo_node_parent(pnp)) {
149 		const char *pname = topo_node_name(pnp);
150 
151 		/*
152 		 * First see if this is the name of something where we can
153 		 * derive the location information from and set it. We will only
154 		 * consider such information from the very first bay, slot, or
155 		 * usb-device that we encounter. If it is missing a label, a
156 		 * label higher up in the tree will not be appropriate.
157 		 */
158 		if ((strcmp(pname, BAY) == 0 || strcmp(pname, SLOT) == 0 ||
159 		    strcmp(pname, USB_DEVICE) == 0 ||
160 		    strcmp(pname, NVME) == 0) && consider_label) {
161 			consider_label = B_FALSE;
162 
163 			if (topo_prop_get_string(pnp, TOPO_PGROUP_PROTOCOL,
164 			    TOPO_PROP_LABEL, &slotname, &e) == 0) {
165 				pp->dp_slotname = slotname;
166 			}
167 		}
168 
169 		/*
170 		 * Next, see if these are nodes where we normally have
171 		 * facilities.
172 		 */
173 		if (strcmp(pname, BAY) == 0) {
174 			if (topo_node_facility(hp, pnp, TOPO_FAC_TYPE_INDICATOR,
175 			    TOPO_FAC_TYPE_ANY, &fl, &e) == 0) {
176 				for (lp = topo_list_next(&fl.tf_list);
177 				    lp != NULL; lp = topo_list_next(lp)) {
178 					uint32_t prop;
179 
180 					if (topo_prop_get_uint32(lp->tf_node,
181 					    TOPO_PGROUP_FACILITY,
182 					    TOPO_FACILITY_TYPE, &prop, &e) !=
183 					    0) {
184 						continue;
185 					}
186 					type = (topo_led_type_t)prop;
187 
188 					if (topo_prop_get_uint32(lp->tf_node,
189 					    TOPO_PGROUP_FACILITY, TOPO_LED_MODE,
190 					    &prop, &e) != 0) {
191 						continue;
192 					}
193 					mode = (topo_led_state_t)prop;
194 
195 					switch (type) {
196 					case TOPO_LED_TYPE_SERVICE:
197 						pp->dp_faulty = mode ? 1 : 0;
198 						break;
199 					case TOPO_LED_TYPE_LOCATE:
200 						pp->dp_locate = mode ? 1 : 0;
201 						break;
202 					default:
203 						break;
204 					}
205 				}
206 			}
207 		}
208 
209 		/*
210 		 * Finally if this is the chassis node, we want to record its
211 		 * instance number.
212 		 */
213 		if (strcmp(pname, CHASSIS) == 0) {
214 			pp->dp_chassis = topo_node_instance(pnp);
215 		}
216 	}
217 
218 	return (TOPO_WALK_TERMINATE);
219 }
220 
221 static void
222 populate_physical(topo_hdl_t *hp, di_phys_t *pp)
223 {
224 	int e;
225 	topo_walk_t *wp;
226 
227 	pp->dp_faulty = pp->dp_locate = -1;
228 	pp->dp_chassis = pp->dp_slot = -1;
229 
230 	e = 0;
231 	wp = topo_walk_init(hp, FM_FMRI_SCHEME_HC, disk_walker, pp, &e);
232 	if (wp == NULL) {
233 		errx(-1, "unable to initialise topo walker: %s",
234 		    topo_strerror(e));
235 	}
236 
237 	while ((e = topo_walk_step(wp, TOPO_WALK_CHILD)) == TOPO_WALK_NEXT)
238 		;
239 
240 	if (e == TOPO_WALK_ERR)
241 		errx(-1, "topo walk failed");
242 
243 	topo_walk_fini(wp);
244 }
245 
246 static void
247 enumerate_disks(di_opts_t *opts)
248 {
249 	topo_hdl_t *hp = NULL;
250 	int filter[] = { DM_DT_FIXED, -1 };
251 	dm_descriptor_t *media;
252 	uint_t i;
253 	int e;
254 
255 	e = 0;
256 	if ((media = dm_get_descriptors(DM_MEDIA, filter, &e)) == NULL) {
257 		errno = e;
258 		err(-1, "failed to obtain media descriptors");
259 	}
260 
261 	/*
262 	 * We only need to walk topo if we're intending to display
263 	 * condensed or physical information. If we don't need it, we leave
264 	 * hp = NULL.
265 	 */
266 	if (opts->di_condensed || opts->di_physical) {
267 		e = 0;
268 		hp = topo_open(TOPO_VERSION, NULL, &e);
269 		if (hp == NULL) {
270 			errx(-1, "unable to obtain topo handle: %s",
271 			    topo_strerror(e));
272 		}
273 
274 		e = 0;
275 		(void) topo_snap_hold(hp, NULL, &e);
276 		if (e != 0) {
277 			errx(-1, "unable to hold topo snapshot: %s",
278 			    topo_strerror(e));
279 		}
280 	}
281 
282 	for (i = 0; media != NULL && media[i] != 0; i++) {
283 		dm_descriptor_t *disk, *controller;
284 		nvlist_t *mattrs, *dattrs;
285 		char *vid, *pid, *serial, *opath, *ctype, *pctype, *c;
286 		boolean_t removable, ssd;
287 		char device[MAXPATHLEN];
288 		di_phys_t phys;
289 		size_t len;
290 		uint64_t size, total;
291 		uint32_t blocksize;
292 		double total_in_GiB;
293 		char sizestr[32];
294 		char slotname[32];
295 		char statestr[8];
296 
297 		if ((disk = dm_get_associated_descriptors(media[i],
298 		    DM_DRIVE, &e)) == NULL) {
299 			continue;
300 		}
301 
302 		/*
303 		 * The attributes depend on us being able to get the media
304 		 * info with DKIOCGMEDIAINFO which may not be the case for
305 		 * disks which are failing.
306 		 */
307 		if ((mattrs = dm_get_attributes(media[i], &e)) == NULL)
308 			continue;
309 
310 		e = nvlist_lookup_uint64(mattrs, DM_SIZE, &size);
311 		assert(e == 0);
312 		e = nvlist_lookup_uint32(mattrs, DM_BLOCKSIZE, &blocksize);
313 		assert(e == 0);
314 
315 		vid = pid = opath = "-";
316 		removable = B_FALSE;
317 		ssd = B_FALSE;
318 
319 		dattrs = dm_get_attributes(disk[0], &e);
320 		if (dattrs != NULL) {
321 			nvlist_query_string(dattrs, DM_VENDOR_ID, &vid);
322 			nvlist_query_string(dattrs, DM_PRODUCT_ID, &pid);
323 			nvlist_query_string(dattrs, DM_OPATH, &opath);
324 			nvlist_query_string(dattrs, DM_SERIAL, &serial);
325 
326 			if (nvlist_lookup_boolean(dattrs, DM_REMOVABLE) == 0)
327 				removable = B_TRUE;
328 
329 			if (nvlist_lookup_boolean(dattrs, DM_SOLIDSTATE) == 0)
330 				ssd = B_TRUE;
331 		}
332 
333 		pctype = "-";
334 		ctype = NULL;
335 		if ((controller = dm_get_associated_descriptors(disk[0],
336 		    DM_CONTROLLER, &e)) != NULL) {
337 			nvlist_t *cattrs;
338 
339 			cattrs = dm_get_attributes(controller[0], &e);
340 			if (cattrs != NULL) {
341 				nvlist_query_string(cattrs, DM_CTYPE, &ctype);
342 				ctype = strdup(ctype);
343 				nvlist_free(cattrs);
344 
345 				if (ctype != NULL) {
346 					for (c = ctype; *c != '\0'; c++)
347 						*c = toupper(*c);
348 					pctype = ctype;
349 				}
350 			}
351 			dm_free_descriptors(controller);
352 		}
353 
354 		/*
355 		 * Parse full device path to only show the device name,
356 		 * i.e. c0t1d0.  Many paths will reference a particular
357 		 * slice (c0t1d0s0), so remove the slice if present.
358 		 */
359 		if ((c = strrchr(opath, '/')) != NULL)
360 			(void) strlcpy(device, c + 1, sizeof (device));
361 		else
362 			(void) strlcpy(device, opath, sizeof (device));
363 		len = strlen(device);
364 		if (device[len - 2] == 's' &&
365 		    (device[len - 1] >= '0' && device[len - 1] <= '9')) {
366 			device[len - 2] = '\0';
367 		}
368 
369 		if (hp != NULL) {
370 			bzero(&phys, sizeof (phys));
371 			phys.dp_dev = device;
372 			populate_physical(hp, &phys);
373 
374 			if (opts->di_parseable) {
375 				(void) snprintf(slotname, sizeof (slotname),
376 				    "%d,%d", phys.dp_chassis, phys.dp_slot);
377 			} else if (phys.dp_slotname != NULL &&
378 			    phys.dp_chassis != -1) {
379 				(void) snprintf(slotname, sizeof (slotname),
380 				    "[%d] %s", phys.dp_chassis,
381 				    phys.dp_slotname);
382 			} else if (phys.dp_slotname != NULL) {
383 				(void) snprintf(slotname, sizeof (slotname),
384 				    "%s", phys.dp_slotname);
385 			} else {
386 				slotname[0] = '-';
387 				slotname[1] = '\0';
388 			}
389 		}
390 		if (phys.dp_serial == NULL) {
391 			phys.dp_serial = serial;
392 		}
393 
394 		/*
395 		 * The size is given in blocks, so multiply the number
396 		 * of blocks by the block size to get the total size,
397 		 * then convert to GiB.
398 		 */
399 		total = size * blocksize;
400 
401 		if (opts->di_parseable) {
402 			(void) snprintf(sizestr, sizeof (sizestr),
403 			    "%llu", total);
404 		} else {
405 			total_in_GiB = (double)total /
406 			    1024.0 / 1024.0 / 1024.0;
407 			(void) snprintf(sizestr, sizeof (sizestr),
408 			    "%7.2f GiB", total_in_GiB);
409 		}
410 
411 		if (opts->di_condensed) {
412 			(void) snprintf(statestr, sizeof (statestr), "%c%c%c%c",
413 			    condensed_tristate(phys.dp_faulty, 'F'),
414 			    condensed_tristate(phys.dp_locate, 'L'),
415 			    condensed_tristate(removable, 'R'),
416 			    condensed_tristate(ssd, 'S'));
417 		}
418 
419 		if (opts->di_physical) {
420 			if (opts->di_scripted) {
421 				printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
422 				    device, vid, pid,
423 				    display_string(phys.dp_serial),
424 				    display_tristate(phys.dp_faulty),
425 				    display_tristate(phys.dp_locate), slotname);
426 			} else {
427 				printf("%-22s  %-8s %-16s "
428 				    "%-20s %-3s %-3s %s\n",
429 				    device, vid, pid,
430 				    display_string(phys.dp_serial),
431 				    display_tristate(phys.dp_faulty),
432 				    display_tristate(phys.dp_locate), slotname);
433 			}
434 		} else if (opts->di_condensed) {
435 			if (opts->di_scripted) {
436 				printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
437 				    pctype, device, vid, pid,
438 				    display_string(phys.dp_serial),
439 				    sizestr, statestr, slotname);
440 			} else {
441 				printf("%-7s %-22s  %-8s %-16s "
442 				    "%-20s\n\t%-13s %-4s %s\n",
443 				    pctype, device, vid, pid,
444 				    display_string(phys.dp_serial),
445 				    sizestr, statestr, slotname);
446 			}
447 		} else {
448 			if (opts->di_scripted) {
449 				printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
450 				    pctype, device, vid, pid, sizestr,
451 				    display_tristate(removable),
452 				    display_tristate(ssd));
453 			} else {
454 				printf("%-7s %-22s  %-8s %-16s "
455 				    "%-13s %-3s %-3s\n", pctype, device,
456 				    vid, pid, sizestr,
457 				    display_tristate(removable),
458 				    display_tristate(ssd));
459 			}
460 		}
461 
462 		free(ctype);
463 		nvlist_free(dattrs);
464 		nvlist_free(mattrs);
465 		dm_free_descriptors(disk);
466 	}
467 
468 	dm_free_descriptors(media);
469 	if (hp != NULL) {
470 		topo_snap_release(hp);
471 		topo_close(hp);
472 	}
473 }
474 
475 int
476 main(int argc, char *argv[])
477 {
478 	int c;
479 
480 	di_opts_t opts = {
481 		.di_condensed = B_FALSE,
482 		.di_scripted = B_FALSE,
483 		.di_physical = B_FALSE,
484 		.di_parseable = B_FALSE
485 	};
486 
487 	while ((c = getopt(argc, argv, ":cHPp")) != EOF) {
488 		switch (c) {
489 		case 'c':
490 			if (opts.di_physical) {
491 				usage(argv[0]);
492 				errx(1, "-c and -P are mutually exclusive");
493 			}
494 			opts.di_condensed = B_TRUE;
495 			break;
496 		case 'H':
497 			opts.di_scripted = B_TRUE;
498 			break;
499 		case 'P':
500 			if (opts.di_condensed) {
501 				usage(argv[0]);
502 				errx(1, "-c and -P are mutually exclusive");
503 			}
504 			opts.di_physical = B_TRUE;
505 			break;
506 		case 'p':
507 			opts.di_parseable = B_TRUE;
508 			break;
509 		case '?':
510 			usage(argv[0]);
511 			errx(1, "unknown option -%c", optopt);
512 		default:
513 			errx(-1, "unexpected error on option -%c", optopt);
514 		}
515 	}
516 
517 	if (!opts.di_scripted) {
518 		if (opts.di_physical) {
519 			printf("DISK                    VID      PID"
520 			    "              SERIAL               FLT LOC"
521 			    " LOCATION\n");
522 		} else if (opts.di_condensed) {
523 			printf("TYPE    DISK                    VID      PID"
524 			    "              SERIAL\n");
525 			printf("\tSIZE          FLRS LOCATION\n");
526 		} else {
527 			printf("TYPE    DISK                    VID      PID"
528 			    "              SIZE          RMV SSD\n");
529 		}
530 	}
531 
532 	enumerate_disks(&opts);
533 
534 	return (0);
535 }
536