xref: /illumos-gate/usr/src/cmd/diskinfo/diskinfo.c (revision 8b4261085e0d677be9a3253ff6b4c290e402576d)
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 2022 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 	tnode_t *pnp;
106 	tnode_t *ppnp;
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 
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 	pnp = topo_node_parent(np);
131 	ppnp = topo_node_parent(pnp);
132 	pp->dp_chassis = topo_node_instance(ppnp);
133 	if (strcmp(topo_node_name(pnp), BAY) == 0) {
134 		if (topo_node_facility(hp, pnp, TOPO_FAC_TYPE_INDICATOR,
135 		    TOPO_FAC_TYPE_ANY, &fl, &e) == 0) {
136 			for (lp = topo_list_next(&fl.tf_list); lp != NULL;
137 			    lp = topo_list_next(lp)) {
138 				uint32_t prop;
139 
140 				if (topo_prop_get_uint32(lp->tf_node,
141 				    TOPO_PGROUP_FACILITY, TOPO_FACILITY_TYPE,
142 				    &prop, &e) != 0) {
143 					continue;
144 				}
145 				type = (topo_led_type_t)prop;
146 
147 				if (topo_prop_get_uint32(lp->tf_node,
148 				    TOPO_PGROUP_FACILITY, TOPO_LED_MODE,
149 				    &prop, &e) != 0) {
150 					continue;
151 				}
152 				mode = (topo_led_state_t)prop;
153 
154 				switch (type) {
155 				case TOPO_LED_TYPE_SERVICE:
156 					pp->dp_faulty = mode ? 1 : 0;
157 					break;
158 				case TOPO_LED_TYPE_LOCATE:
159 					pp->dp_locate = mode ? 1 : 0;
160 					break;
161 				default:
162 					break;
163 				}
164 			}
165 		}
166 
167 		if (topo_prop_get_string(pnp, TOPO_PGROUP_PROTOCOL,
168 		    TOPO_PROP_LABEL, &slotname, &e) == 0) {
169 			pp->dp_slotname = slotname;
170 		}
171 
172 		pp->dp_slot = topo_node_instance(pnp);
173 	} else if (strcmp(topo_node_name(pnp), USB_DEVICE) == 0) {
174 		if (topo_prop_get_string(pnp, TOPO_PGROUP_PROTOCOL,
175 		    TOPO_PROP_LABEL, &slotname, &e) == 0) {
176 			pp->dp_slotname = slotname;
177 		}
178 
179 		/*
180 		 * Override dp_chassis for USB devices since they show up
181 		 * everywhere in the name space and may not be under a logical
182 		 * bay.
183 		 */
184 		pp->dp_chassis = -1;
185 	}
186 
187 	return (TOPO_WALK_TERMINATE);
188 }
189 
190 static void
191 populate_physical(topo_hdl_t *hp, di_phys_t *pp)
192 {
193 	int e;
194 	topo_walk_t *wp;
195 
196 	pp->dp_faulty = pp->dp_locate = -1;
197 	pp->dp_chassis = pp->dp_slot = -1;
198 
199 	e = 0;
200 	wp = topo_walk_init(hp, FM_FMRI_SCHEME_HC, disk_walker, pp, &e);
201 	if (wp == NULL) {
202 		errx(-1, "unable to initialise topo walker: %s",
203 		    topo_strerror(e));
204 	}
205 
206 	while ((e = topo_walk_step(wp, TOPO_WALK_CHILD)) == TOPO_WALK_NEXT)
207 		;
208 
209 	if (e == TOPO_WALK_ERR)
210 		errx(-1, "topo walk failed");
211 
212 	topo_walk_fini(wp);
213 }
214 
215 static void
216 enumerate_disks(di_opts_t *opts)
217 {
218 	topo_hdl_t *hp = NULL;
219 	int filter[] = { DM_DT_FIXED, -1 };
220 	dm_descriptor_t *media;
221 	uint_t i;
222 	int e;
223 
224 	e = 0;
225 	if ((media = dm_get_descriptors(DM_MEDIA, filter, &e)) == NULL) {
226 		errno = e;
227 		err(-1, "failed to obtain media descriptors");
228 	}
229 
230 	/*
231 	 * We only need to walk topo if we're intending to display
232 	 * condensed or physical information. If we don't need it, we leave
233 	 * hp = NULL.
234 	 */
235 	if (opts->di_condensed || opts->di_physical) {
236 		e = 0;
237 		hp = topo_open(TOPO_VERSION, NULL, &e);
238 		if (hp == NULL) {
239 			errx(-1, "unable to obtain topo handle: %s",
240 			    topo_strerror(e));
241 		}
242 
243 		e = 0;
244 		(void) topo_snap_hold(hp, NULL, &e);
245 		if (e != 0) {
246 			errx(-1, "unable to hold topo snapshot: %s",
247 			    topo_strerror(e));
248 		}
249 	}
250 
251 	for (i = 0; media != NULL && media[i] != 0; i++) {
252 		dm_descriptor_t *disk, *controller;
253 		nvlist_t *mattrs, *dattrs;
254 		char *vid, *pid, *opath, *ctype, *pctype, *c;
255 		boolean_t removable, ssd;
256 		char device[MAXPATHLEN];
257 		di_phys_t phys;
258 		size_t len;
259 		uint64_t size, total;
260 		uint32_t blocksize;
261 		double total_in_GiB;
262 		char sizestr[32];
263 		char slotname[32];
264 		char statestr[8];
265 
266 		if ((disk = dm_get_associated_descriptors(media[i],
267 		    DM_DRIVE, &e)) == NULL) {
268 			continue;
269 		}
270 
271 		/*
272 		 * The attributes depend on us being able to get the media
273 		 * info with DKIOCGMEDIAINFO which may not be the case for
274 		 * disks which are failing.
275 		 */
276 		if ((mattrs = dm_get_attributes(media[i], &e)) == NULL)
277 			continue;
278 
279 		e = nvlist_lookup_uint64(mattrs, DM_SIZE, &size);
280 		assert(e == 0);
281 		e = nvlist_lookup_uint32(mattrs, DM_BLOCKSIZE, &blocksize);
282 		assert(e == 0);
283 
284 		vid = pid = opath = "-";
285 		removable = B_FALSE;
286 		ssd = B_FALSE;
287 
288 		dattrs = dm_get_attributes(disk[0], &e);
289 		if (dattrs != NULL) {
290 			nvlist_query_string(dattrs, DM_VENDOR_ID, &vid);
291 			nvlist_query_string(dattrs, DM_PRODUCT_ID, &pid);
292 			nvlist_query_string(dattrs, DM_OPATH, &opath);
293 
294 			if (nvlist_lookup_boolean(dattrs, DM_REMOVABLE) == 0)
295 				removable = B_TRUE;
296 
297 			if (nvlist_lookup_boolean(dattrs, DM_SOLIDSTATE) == 0)
298 				ssd = B_TRUE;
299 		}
300 
301 		pctype = "-";
302 		ctype = NULL;
303 		if ((controller = dm_get_associated_descriptors(disk[0],
304 		    DM_CONTROLLER, &e)) != NULL) {
305 			nvlist_t *cattrs;
306 
307 			cattrs = dm_get_attributes(controller[0], &e);
308 			if (cattrs != NULL) {
309 				nvlist_query_string(cattrs, DM_CTYPE, &ctype);
310 				ctype = strdup(ctype);
311 				nvlist_free(cattrs);
312 
313 				if (ctype != NULL) {
314 					for (c = ctype; *c != '\0'; c++)
315 						*c = toupper(*c);
316 					pctype = ctype;
317 				}
318 			}
319 			dm_free_descriptors(controller);
320 		}
321 
322 		/*
323 		 * Parse full device path to only show the device name,
324 		 * i.e. c0t1d0.  Many paths will reference a particular
325 		 * slice (c0t1d0s0), so remove the slice if present.
326 		 */
327 		if ((c = strrchr(opath, '/')) != NULL)
328 			(void) strlcpy(device, c + 1, sizeof (device));
329 		else
330 			(void) strlcpy(device, opath, sizeof (device));
331 		len = strlen(device);
332 		if (device[len - 2] == 's' &&
333 		    (device[len - 1] >= '0' && device[len - 1] <= '9')) {
334 			device[len - 2] = '\0';
335 		}
336 
337 		if (hp != NULL) {
338 			bzero(&phys, sizeof (phys));
339 			phys.dp_dev = device;
340 			populate_physical(hp, &phys);
341 
342 			if (opts->di_parseable) {
343 				(void) snprintf(slotname, sizeof (slotname),
344 				    "%d,%d", phys.dp_chassis, phys.dp_slot);
345 			} else if (phys.dp_slotname != NULL &&
346 			    phys.dp_chassis != -1) {
347 				(void) snprintf(slotname, sizeof (slotname),
348 				    "[%d] %s", phys.dp_chassis,
349 				    phys.dp_slotname);
350 			} else if (phys.dp_slotname != NULL) {
351 				(void) snprintf(slotname, sizeof (slotname),
352 				    "%s", phys.dp_slotname);
353 			} else {
354 				slotname[0] = '-';
355 				slotname[1] = '\0';
356 			}
357 		}
358 
359 		/*
360 		 * The size is given in blocks, so multiply the number
361 		 * of blocks by the block size to get the total size,
362 		 * then convert to GiB.
363 		 */
364 		total = size * blocksize;
365 
366 		if (opts->di_parseable) {
367 			(void) snprintf(sizestr, sizeof (sizestr),
368 			    "%llu", total);
369 		} else {
370 			total_in_GiB = (double)total /
371 			    1024.0 / 1024.0 / 1024.0;
372 			(void) snprintf(sizestr, sizeof (sizestr),
373 			    "%7.2f GiB", total_in_GiB);
374 		}
375 
376 		if (opts->di_condensed) {
377 			(void) snprintf(statestr, sizeof (statestr), "%c%c%c%c",
378 			    condensed_tristate(phys.dp_faulty, 'F'),
379 			    condensed_tristate(phys.dp_locate, 'L'),
380 			    condensed_tristate(removable, 'R'),
381 			    condensed_tristate(ssd, 'S'));
382 		}
383 
384 		if (opts->di_physical) {
385 			if (opts->di_scripted) {
386 				printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
387 				    device, vid, pid,
388 				    display_string(phys.dp_serial),
389 				    display_tristate(phys.dp_faulty),
390 				    display_tristate(phys.dp_locate), slotname);
391 			} else {
392 				printf("%-22s  %-8s %-16s "
393 				    "%-20s %-3s %-3s %s\n",
394 				    device, vid, pid,
395 				    display_string(phys.dp_serial),
396 				    display_tristate(phys.dp_faulty),
397 				    display_tristate(phys.dp_locate), slotname);
398 			}
399 		} else if (opts->di_condensed) {
400 			if (opts->di_scripted) {
401 				printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
402 				    pctype, device, vid, pid,
403 				    display_string(phys.dp_serial),
404 				    sizestr, statestr, slotname);
405 			} else {
406 				printf("%-7s %-22s  %-8s %-16s "
407 				    "%-20s\n\t%-13s %-4s %s\n",
408 				    pctype, device, vid, pid,
409 				    display_string(phys.dp_serial),
410 				    sizestr, statestr, slotname);
411 			}
412 		} else {
413 			if (opts->di_scripted) {
414 				printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
415 				    pctype, device, vid, pid, sizestr,
416 				    display_tristate(removable),
417 				    display_tristate(ssd));
418 			} else {
419 				printf("%-7s %-22s  %-8s %-16s "
420 				    "%-13s %-3s %-3s\n", pctype, device,
421 				    vid, pid, sizestr,
422 				    display_tristate(removable),
423 				    display_tristate(ssd));
424 			}
425 		}
426 
427 		free(ctype);
428 		nvlist_free(dattrs);
429 		nvlist_free(mattrs);
430 		dm_free_descriptors(disk);
431 	}
432 
433 	dm_free_descriptors(media);
434 	if (hp != NULL) {
435 		topo_snap_release(hp);
436 		topo_close(hp);
437 	}
438 }
439 
440 int
441 main(int argc, char *argv[])
442 {
443 	char c;
444 
445 	di_opts_t opts = {
446 		.di_condensed = B_FALSE,
447 		.di_scripted = B_FALSE,
448 		.di_physical = B_FALSE,
449 		.di_parseable = B_FALSE
450 	};
451 
452 	while ((c = getopt(argc, argv, ":cHPp")) != EOF) {
453 		switch (c) {
454 		case 'c':
455 			if (opts.di_physical) {
456 				usage(argv[0]);
457 				errx(1, "-c and -P are mutually exclusive");
458 			}
459 			opts.di_condensed = B_TRUE;
460 			break;
461 		case 'H':
462 			opts.di_scripted = B_TRUE;
463 			break;
464 		case 'P':
465 			if (opts.di_condensed) {
466 				usage(argv[0]);
467 				errx(1, "-c and -P are mutually exclusive");
468 			}
469 			opts.di_physical = B_TRUE;
470 			break;
471 		case 'p':
472 			opts.di_parseable = B_TRUE;
473 			break;
474 		case '?':
475 			usage(argv[0]);
476 			errx(1, "unknown option -%c", optopt);
477 		default:
478 			errx(-1, "unexpected error on option -%c", optopt);
479 		}
480 	}
481 
482 	if (!opts.di_scripted) {
483 		if (opts.di_physical) {
484 			printf("DISK                    VID      PID"
485 			    "              SERIAL               FLT LOC"
486 			    " LOCATION\n");
487 		} else if (opts.di_condensed) {
488 			printf("TYPE    DISK                    VID      PID"
489 			    "              SERIAL\n");
490 			printf("\tSIZE          FLRS LOCATION\n");
491 		} else {
492 			printf("TYPE    DISK                    VID      PID"
493 			    "              SIZE          RMV SSD\n");
494 		}
495 	}
496 
497 	enumerate_disks(&opts);
498 
499 	return (0);
500 }
501