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