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