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