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