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