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