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
usage(const char * execname)62 usage(const char *execname)
63 {
64 (void) fprintf(stderr, "Usage: %s [-Hp] [{-c|-P}]\n", execname);
65 }
66
67 static void
nvlist_query_string(nvlist_t * nvl,const char * label,char ** val)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 *
display_string(const char * label)75 display_string(const char *label)
76 {
77 return ((label) ? label : "-");
78 }
79
80 static const char *
display_tristate(int val)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
condensed_tristate(int val,char c)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
disk_walker(topo_hdl_t * hp,tnode_t * np,void * arg)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
populate_physical(topo_hdl_t * hp,di_phys_t * pp)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
enumerate_disks(di_opts_t * opts)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
main(int argc,char * argv[])469 main(int argc, char *argv[])
470 {
471 int 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