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