1 /*- 2 * Copyright (c) 2015 Baptiste Daroussin <bapt@FreeBSD.org> 3 * Copyright (c) 2015 Allan Jude <allanjude@FreeBSD.org> 4 * Copyright (c) 2000 by Matthew Jacob 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer 12 * in this position and unchanged. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 #include <sys/endian.h> 33 #include <sys/param.h> 34 #include <sys/ioctl.h> 35 #include <sys/types.h> 36 37 #include <err.h> 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <getopt.h> 41 #include <glob.h> 42 #include <stdbool.h> 43 #include <stddef.h> 44 #include <stdint.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 #include <libxo/xo.h> 50 51 #include <cam/scsi/scsi_enc.h> 52 53 #include "eltsub.h" 54 55 #define SESUTIL_XO_VERSION "1" 56 57 static int encstatus(int argc, char **argv); 58 static int fault(int argc, char **argv); 59 static int locate(int argc, char **argv); 60 static int objmap(int argc, char **argv); 61 static int sesled(int argc, char **argv, bool fault); 62 static void sesutil_print(bool *title, const char *fmt, ...) __printflike(2,3); 63 64 static struct command { 65 const char *name; 66 const char *param; 67 const char *desc; 68 int (*exec)(int argc, char **argv); 69 } cmds[] = { 70 { "fault", 71 "(<disk>|<sesid>|all) (on|off)", 72 "Change the state of the fault LED associated with a disk", 73 fault }, 74 { "locate", 75 "(<disk>|<sesid>|all) (on|off)", 76 "Change the state of the locate LED associated with a disk", 77 locate }, 78 { "map", "", 79 "Print a map of the devices managed by the enclosure", objmap } , 80 { "status", "", "Print the status of the enclosure", 81 encstatus }, 82 }; 83 84 static const int nbcmds = nitems(cmds); 85 static const char *uflag; 86 87 static void 88 usage(FILE *out, const char *subcmd) 89 { 90 int i; 91 92 if (subcmd == NULL) { 93 fprintf(out, "Usage: %s [-u /dev/ses<N>] <command> [options]\n", 94 getprogname()); 95 fprintf(out, "Commands supported:\n"); 96 } 97 for (i = 0; i < nbcmds; i++) { 98 if (subcmd != NULL) { 99 if (strcmp(subcmd, cmds[i].name) == 0) { 100 fprintf(out, "Usage: %s %s [-u /dev/ses<N>] " 101 "%s\n\t%s\n", getprogname(), subcmd, 102 cmds[i].param, cmds[i].desc); 103 break; 104 } 105 continue; 106 } 107 fprintf(out, " %-12s%s\n\t\t%s\n\n", cmds[i].name, 108 cmds[i].param, cmds[i].desc); 109 } 110 111 exit(EXIT_FAILURE); 112 } 113 114 static void 115 do_led(int fd, unsigned int idx, bool onoff, bool setfault) 116 { 117 encioc_elm_status_t o; 118 119 o.elm_idx = idx; 120 if (ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t) &o) < 0) { 121 close(fd); 122 xo_err(EXIT_FAILURE, "ENCIOC_GETELMSTAT"); 123 } 124 o.cstat[0] |= 0x80; 125 if (setfault) { 126 if (onoff) 127 o.cstat[3] |= 0x20; 128 else 129 o.cstat[3] &= 0xdf; 130 } else { 131 if (onoff) 132 o.cstat[2] |= 0x02; 133 else 134 o.cstat[2] &= 0xfd; 135 } 136 137 if (ioctl(fd, ENCIOC_SETELMSTAT, (caddr_t) &o) < 0) { 138 close(fd); 139 xo_err(EXIT_FAILURE, "ENCIOC_SETELMSTAT"); 140 } 141 } 142 143 static bool 144 disk_match(const char *devnames, const char *disk, size_t len) 145 { 146 const char *dname; 147 148 dname = devnames; 149 while ((dname = strstr(dname, disk)) != NULL) { 150 if (dname[len] == '\0' || dname[len] == ',') { 151 return (true); 152 } 153 dname++; 154 } 155 156 return (false); 157 } 158 159 static int 160 sesled(int argc, char **argv, bool setfault) 161 { 162 encioc_elm_devnames_t objdn; 163 encioc_element_t *objp; 164 glob_t g; 165 char *disk, *endptr; 166 size_t len, i, ndisks; 167 int fd; 168 unsigned int nobj, j, sesid; 169 bool all, isses, onoff; 170 171 isses = false; 172 all = false; 173 onoff = false; 174 175 if (argc != 3) { 176 usage(stderr, (setfault ? "fault" : "locate")); 177 } 178 179 disk = argv[1]; 180 181 sesid = strtoul(disk, &endptr, 10); 182 if (*endptr == '\0') { 183 endptr = strrchr(uflag, '*'); 184 if (endptr != NULL && *endptr == '*') { 185 xo_warnx("Must specifying a SES device (-u) to use a SES " 186 "id# to identify a disk"); 187 usage(stderr, (setfault ? "fault" : "locate")); 188 } 189 isses = true; 190 } 191 192 if (strcmp(argv[2], "on") == 0) { 193 onoff = true; 194 } else if (strcmp(argv[2], "off") == 0) { 195 onoff = false; 196 } else { 197 usage(stderr, (setfault ? "fault" : "locate")); 198 } 199 200 if (strcmp(disk, "all") == 0) { 201 all = true; 202 } 203 len = strlen(disk); 204 205 /* Get the list of ses devices */ 206 if (glob((uflag != NULL ? uflag : "/dev/ses[0-9]*"), 0, NULL, &g) == 207 GLOB_NOMATCH) { 208 globfree(&g); 209 xo_errx(EXIT_FAILURE, "No SES devices found"); 210 } 211 212 ndisks = 0; 213 for (i = 0; i < g.gl_pathc; i++) { 214 /* ensure we only got numbers after ses */ 215 if (strspn(g.gl_pathv[i] + 8, "0123456789") != 216 strlen(g.gl_pathv[i] + 8)) { 217 continue; 218 } 219 if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) { 220 /* 221 * Don't treat non-access errors as critical if we are 222 * accessing all devices 223 */ 224 if (errno == EACCES && g.gl_pathc > 1) { 225 xo_err(EXIT_FAILURE, "unable to access SES device"); 226 } 227 xo_warn("unable to access SES device: %s", g.gl_pathv[i]); 228 continue; 229 } 230 231 if (ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj) < 0) { 232 close(fd); 233 xo_err(EXIT_FAILURE, "ENCIOC_GETNELM"); 234 } 235 236 objp = calloc(nobj, sizeof(encioc_element_t)); 237 if (objp == NULL) { 238 close(fd); 239 xo_err(EXIT_FAILURE, "calloc()"); 240 } 241 242 if (ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) objp) < 0) { 243 close(fd); 244 xo_err(EXIT_FAILURE, "ENCIOC_GETELMMAP"); 245 } 246 247 if (isses) { 248 if (sesid > nobj) { 249 close(fd); 250 xo_errx(EXIT_FAILURE, 251 "Requested SES ID does not exist"); 252 } 253 do_led(fd, sesid, onoff, setfault); 254 ndisks++; 255 close(fd); 256 break; 257 } 258 for (j = 0; j < nobj; j++) { 259 if (all) { 260 do_led(fd, objp[j].elm_idx, onoff, setfault); 261 continue; 262 } 263 memset(&objdn, 0, sizeof(objdn)); 264 objdn.elm_idx = objp[j].elm_idx; 265 objdn.elm_names_size = 128; 266 objdn.elm_devnames = calloc(128, sizeof(char)); 267 if (objdn.elm_devnames == NULL) { 268 close(fd); 269 xo_err(EXIT_FAILURE, "calloc()"); 270 } 271 if (ioctl(fd, ENCIOC_GETELMDEVNAMES, 272 (caddr_t) &objdn) <0) { 273 continue; 274 } 275 if (objdn.elm_names_len > 0) { 276 if (disk_match(objdn.elm_devnames, disk, len)) { 277 do_led(fd, objdn.elm_idx, 278 onoff, setfault); 279 ndisks++; 280 break; 281 } 282 } 283 } 284 free(objp); 285 close(fd); 286 } 287 globfree(&g); 288 if (ndisks == 0 && all == false) { 289 xo_errx(EXIT_FAILURE, "Count not find the SES id of device '%s'", 290 disk); 291 } 292 293 return (EXIT_SUCCESS); 294 } 295 296 static int 297 locate(int argc, char **argv) 298 { 299 300 return (sesled(argc, argv, false)); 301 } 302 303 static int 304 fault(int argc, char **argv) 305 { 306 307 return (sesled(argc, argv, true)); 308 } 309 310 #define TEMPERATURE_OFFSET 20 311 static void 312 sesutil_print(bool *title, const char *fmt, ...) 313 { 314 va_list args; 315 316 if (!*title) { 317 xo_open_container("extra_status"); 318 xo_emit("\t\tExtra status:\n"); 319 *title = true; 320 } 321 va_start(args, fmt); 322 xo_emit_hv(NULL, fmt, args); 323 va_end(args); 324 } 325 326 static void 327 print_extra_status(int eletype, u_char *cstat) 328 { 329 bool title = false; 330 331 if (cstat[0] & 0x40) { 332 sesutil_print(&title, "\t\t-{e:predicted_failure/true} Predicted Failure\n"); 333 } 334 if (cstat[0] & 0x20) { 335 sesutil_print(&title, "\t\t-{e:disabled/true} Disabled\n"); 336 } 337 if (cstat[0] & 0x10) { 338 sesutil_print(&title, "\t\t-{e:swapped/true} Swapped\n"); 339 } 340 switch (eletype) { 341 case ELMTYP_DEVICE: 342 case ELMTYP_ARRAY_DEV: 343 if (cstat[2] & 0x02) { 344 sesutil_print(&title, "\t\t- LED={q:led/locate}\n"); 345 } 346 if (cstat[2] & 0x20) { 347 sesutil_print(&title, "\t\t- LED={q:led/fault}\n"); 348 } 349 break; 350 case ELMTYP_FAN: 351 sesutil_print(&title, "\t\t- Speed: {:speed/%d}{Uw:rpm}\n", 352 (((0x7 & cstat[1]) << 8) + cstat[2]) * 10); 353 break; 354 case ELMTYP_THERM: 355 if (cstat[2]) { 356 sesutil_print(&title, "\t\t- Temperature: {:temperature/%d}{Uw:C}\n", 357 cstat[2] - TEMPERATURE_OFFSET); 358 } else { 359 sesutil_print(&title, "\t\t- Temperature: -{q:temperature/reserved}-\n"); 360 } 361 break; 362 case ELMTYP_VOM: 363 sesutil_print(&title, "\t\t- Voltage: {:voltage/%.2f}{Uw:V}\n", 364 be16dec(cstat + 2) / 100.0); 365 break; 366 } 367 if (title) { 368 xo_close_container("extra_status"); 369 } 370 } 371 372 static int 373 objmap(int argc, char **argv __unused) 374 { 375 encioc_string_t stri; 376 encioc_elm_devnames_t e_devname; 377 encioc_elm_status_t e_status; 378 encioc_elm_desc_t e_desc; 379 encioc_element_t *e_ptr; 380 glob_t g; 381 int fd; 382 unsigned int j, nobj; 383 size_t i; 384 char str[32]; 385 386 if (argc != 1) { 387 usage(stderr, "map"); 388 } 389 390 /* Get the list of ses devices */ 391 if (glob(uflag, 0, NULL, &g) == GLOB_NOMATCH) { 392 globfree(&g); 393 xo_errx(EXIT_FAILURE, "No SES devices found"); 394 } 395 xo_set_version(SESUTIL_XO_VERSION); 396 xo_open_container("sesutil"); 397 xo_open_list("enclosures"); 398 for (i = 0; i < g.gl_pathc; i++) { 399 /* ensure we only got numbers after ses */ 400 if (strspn(g.gl_pathv[i] + 8, "0123456789") != 401 strlen(g.gl_pathv[i] + 8)) { 402 continue; 403 } 404 if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) { 405 /* 406 * Don't treat non-access errors as critical if we are 407 * accessing all devices 408 */ 409 if (errno == EACCES && g.gl_pathc > 1) { 410 xo_err(EXIT_FAILURE, "unable to access SES device"); 411 } 412 xo_warn("unable to access SES device: %s", g.gl_pathv[i]); 413 continue; 414 } 415 416 if (ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj) < 0) { 417 close(fd); 418 xo_err(EXIT_FAILURE, "ENCIOC_GETNELM"); 419 } 420 421 e_ptr = calloc(nobj, sizeof(encioc_element_t)); 422 if (e_ptr == NULL) { 423 close(fd); 424 xo_err(EXIT_FAILURE, "calloc()"); 425 } 426 427 if (ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) e_ptr) < 0) { 428 close(fd); 429 xo_err(EXIT_FAILURE, "ENCIOC_GETELMMAP"); 430 } 431 432 xo_open_instance("enclosures"); 433 xo_emit("{t:enc/%s}:\n", g.gl_pathv[i] + 5); 434 stri.bufsiz = sizeof(str); 435 stri.buf = &str[0]; 436 if (ioctl(fd, ENCIOC_GETENCNAME, (caddr_t) &stri) == 0) 437 xo_emit("\tEnclosure Name: {t:name/%s}\n", stri.buf); 438 stri.bufsiz = sizeof(str); 439 stri.buf = &str[0]; 440 if (ioctl(fd, ENCIOC_GETENCID, (caddr_t) &stri) == 0) 441 xo_emit("\tEnclosure ID: {t:id/%s}\n", stri.buf); 442 443 xo_open_list("elements"); 444 for (j = 0; j < nobj; j++) { 445 /* Get the status of the element */ 446 memset(&e_status, 0, sizeof(e_status)); 447 e_status.elm_idx = e_ptr[j].elm_idx; 448 if (ioctl(fd, ENCIOC_GETELMSTAT, 449 (caddr_t) &e_status) < 0) { 450 close(fd); 451 xo_err(EXIT_FAILURE, "ENCIOC_GETELMSTAT"); 452 } 453 /* Get the description of the element */ 454 memset(&e_desc, 0, sizeof(e_desc)); 455 e_desc.elm_idx = e_ptr[j].elm_idx; 456 e_desc.elm_desc_len = UINT16_MAX; 457 e_desc.elm_desc_str = calloc(UINT16_MAX, sizeof(char)); 458 if (e_desc.elm_desc_str == NULL) { 459 close(fd); 460 xo_err(EXIT_FAILURE, "calloc()"); 461 } 462 if (ioctl(fd, ENCIOC_GETELMDESC, 463 (caddr_t) &e_desc) < 0) { 464 close(fd); 465 xo_err(EXIT_FAILURE, "ENCIOC_GETELMDESC"); 466 } 467 /* Get the device name(s) of the element */ 468 memset(&e_devname, 0, sizeof(e_devname)); 469 e_devname.elm_idx = e_ptr[j].elm_idx; 470 e_devname.elm_names_size = 128; 471 e_devname.elm_devnames = calloc(128, sizeof(char)); 472 if (e_devname.elm_devnames == NULL) { 473 close(fd); 474 xo_err(EXIT_FAILURE, "calloc()"); 475 } 476 if (ioctl(fd, ENCIOC_GETELMDEVNAMES, 477 (caddr_t) &e_devname) <0) { 478 /* We don't care if this fails */ 479 e_devname.elm_devnames[0] = '\0'; 480 } 481 xo_open_instance("elements"); 482 xo_emit("\tElement {:id/%u}, Type: {:type/%s}\n", e_ptr[j].elm_idx, 483 geteltnm(e_ptr[j].elm_type)); 484 xo_emit("\t\tStatus: {:status/%s} ({q:status_code/0x%02x 0x%02x 0x%02x 0x%02x})\n", 485 scode2ascii(e_status.cstat[0]), e_status.cstat[0], 486 e_status.cstat[1], e_status.cstat[2], 487 e_status.cstat[3]); 488 if (e_desc.elm_desc_len > 0) { 489 xo_emit("\t\tDescription: {:description/%s}\n", 490 e_desc.elm_desc_str); 491 } 492 if (e_devname.elm_names_len > 0) { 493 xo_emit("\t\tDevice Names: {:device_names/%s}\n", 494 e_devname.elm_devnames); 495 } 496 print_extra_status(e_ptr[j].elm_type, e_status.cstat); 497 xo_close_instance("elements"); 498 free(e_devname.elm_devnames); 499 } 500 xo_close_list("elements"); 501 free(e_ptr); 502 close(fd); 503 } 504 globfree(&g); 505 xo_close_list("enclosures"); 506 xo_close_container("sesutil"); 507 xo_finish(); 508 509 return (EXIT_SUCCESS); 510 } 511 512 static int 513 encstatus(int argc, char **argv __unused) 514 { 515 glob_t g; 516 int fd, status; 517 size_t i, e; 518 u_char estat; 519 520 status = 0; 521 if (argc != 1) { 522 usage(stderr, "status"); 523 } 524 525 /* Get the list of ses devices */ 526 if (glob(uflag, 0, NULL, &g) == GLOB_NOMATCH) { 527 globfree(&g); 528 xo_errx(EXIT_FAILURE, "No SES devices found"); 529 } 530 531 xo_set_version(SESUTIL_XO_VERSION); 532 xo_open_container("sesutil"); 533 xo_open_list("enclosures"); 534 for (i = 0; i < g.gl_pathc; i++) { 535 /* ensure we only got numbers after ses */ 536 if (strspn(g.gl_pathv[i] + 8, "0123456789") != 537 strlen(g.gl_pathv[i] + 8)) { 538 continue; 539 } 540 if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) { 541 /* 542 * Don't treat non-access errors as critical if we are 543 * accessing all devices 544 */ 545 if (errno == EACCES && g.gl_pathc > 1) { 546 xo_err(EXIT_FAILURE, "unable to access SES device"); 547 } 548 xo_warn("unable to access SES device: %s", g.gl_pathv[i]); 549 continue; 550 } 551 552 if (ioctl(fd, ENCIOC_GETENCSTAT, (caddr_t) &estat) < 0) { 553 xo_err(EXIT_FAILURE, "ENCIOC_GETENCSTAT"); 554 close(fd); 555 } 556 557 xo_open_instance("enclosures"); 558 xo_emit("{:enc/%s}: ", g.gl_pathv[i] + 5); 559 e = 0; 560 if (estat == 0) { 561 if (status == 0) { 562 status = 1; 563 } 564 xo_emit("{q:status/OK}"); 565 } else { 566 if (estat & SES_ENCSTAT_INFO) { 567 xo_emit("{lq:status/INFO}"); 568 e++; 569 } 570 if (estat & SES_ENCSTAT_NONCRITICAL) { 571 if (e) 572 xo_emit(","); 573 xo_emit("{lq:status/NONCRITICAL}"); 574 e++; 575 } 576 if (estat & SES_ENCSTAT_CRITICAL) { 577 if (e) 578 xo_emit(","); 579 xo_emit("{lq:status/CRITICAL}"); 580 e++; 581 status = -1; 582 } 583 if (estat & SES_ENCSTAT_UNRECOV) { 584 if (e) 585 xo_emit(","); 586 xo_emit("{lq:status/UNRECOV}"); 587 e++; 588 status = -1; 589 } 590 } 591 xo_close_instance("enclosures"); 592 xo_emit("\n"); 593 close(fd); 594 } 595 globfree(&g); 596 597 xo_close_list("enclosures"); 598 xo_close_container("sesutil"); 599 xo_finish(); 600 601 if (status == 1) { 602 return (EXIT_SUCCESS); 603 } else { 604 return (EXIT_FAILURE); 605 } 606 } 607 608 int 609 main(int argc, char **argv) 610 { 611 int i, ch; 612 struct command *cmd = NULL; 613 614 argc = xo_parse_args(argc, argv); 615 if (argc < 0) 616 exit(1); 617 618 uflag = "/dev/ses[0-9]*"; 619 while ((ch = getopt_long(argc, argv, "u:", NULL, NULL)) != -1) { 620 switch (ch) { 621 case 'u': 622 uflag = optarg; 623 break; 624 case '?': 625 default: 626 usage(stderr, NULL); 627 } 628 } 629 argc -= optind; 630 argv += optind; 631 632 if (argc < 1) { 633 warnx("Missing command"); 634 usage(stderr, NULL); 635 } 636 637 for (i = 0; i < nbcmds; i++) { 638 if (strcmp(argv[0], cmds[i].name) == 0) { 639 cmd = &cmds[i]; 640 break; 641 } 642 } 643 644 if (cmd == NULL) { 645 warnx("unknown command %s", argv[0]); 646 usage(stderr, NULL); 647 } 648 649 return (cmd->exec(argc, argv)); 650 } 651