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