1 /* $OpenBSD: chio.c,v 1.26 2019/06/28 13:34:58 deraadt Exp $ */ 2 /* $NetBSD: chio.c,v 1.1.1.1 1996/04/03 00:34:38 thorpej Exp $ */ 3 4 /* 5 * Copyright (c) 1996 Jason R. Thorpe <thorpej@and.com> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 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 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgments: 18 * This product includes software developed by Jason R. Thorpe 19 * for And Communications, http://www.and.com/ 20 * 4. The name of the author may not be used to endorse or promote products 21 * derived from this software without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/types.h> 37 #include <sys/ioctl.h> 38 #include <sys/mtio.h> 39 #include <sys/chio.h> 40 #include <err.h> 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <limits.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <unistd.h> 48 #include <util.h> 49 50 #include "defs.h" 51 #include "pathnames.h" 52 53 #define _PATH_CH_CONF "/etc/chio.conf" 54 extern char *parse_tapedev(const char *, const char *, int); /* parse.y */ 55 extern char *__progname; /* from crt0.o */ 56 57 static void usage(void); 58 static int parse_element_type(char *); 59 static int parse_element_unit(char *); 60 static int parse_special(char *); 61 static int is_special(char *); 62 static char *bits_to_string(int, const char *); 63 static void find_voltag(char *, int *, int *); 64 static void check_source_drive(int); 65 66 static int do_move(char *, int, char **); 67 static int do_exchange(char *, int, char **); 68 static int do_position(char *, int, char **); 69 static int do_params(char *, int, char **); 70 static int do_getpicker(char *, int, char **); 71 static int do_setpicker(char *, int, char **); 72 static int do_status(char *, int, char **); 73 74 /* Valid changer element types. */ 75 const struct element_type elements[] = { 76 { "drive", CHET_DT }, 77 { "picker", CHET_MT }, 78 { "portal", CHET_IE }, 79 { "slot", CHET_ST }, 80 { NULL, 0 }, 81 }; 82 83 /* Valid commands. */ 84 const struct changer_command commands[] = { 85 { "exchange", do_exchange }, 86 { "getpicker", do_getpicker }, 87 { "move", do_move }, 88 { "params", do_params }, 89 { "position", do_position }, 90 { "setpicker", do_setpicker }, 91 { "status", do_status }, 92 { NULL, 0 }, 93 }; 94 95 /* Valid special words. */ 96 const struct special_word specials[] = { 97 { "inv", SW_INVERT }, 98 { "inv1", SW_INVERT1 }, 99 { "inv2", SW_INVERT2 }, 100 { NULL, 0 }, 101 }; 102 103 static int changer_fd; 104 static char *changer_name; 105 static int avoltag; 106 static int pvoltag; 107 108 int 109 main(int argc, char *argv[]) 110 { 111 int ch, i; 112 113 while ((ch = getopt(argc, argv, "f:")) != -1) { 114 switch (ch) { 115 case 'f': 116 changer_name = optarg; 117 break; 118 default: 119 usage(); 120 } 121 } 122 argc -= optind; 123 argv += optind; 124 125 if (argc == 0) 126 usage(); 127 128 /* Get the default changer if not already specified. */ 129 if (changer_name == NULL) 130 if ((changer_name = getenv(CHANGER_ENV_VAR)) == NULL) 131 changer_name = _PATH_CH; 132 133 /* Open the changer device. */ 134 if ((changer_fd = open(changer_name, O_RDWR, 0600)) == -1) 135 err(1, "%s: open", changer_name); 136 137 /* Find the specified command. */ 138 for (i = 0; commands[i].cc_name != NULL; ++i) 139 if (strcmp(*argv, commands[i].cc_name) == 0) 140 break; 141 if (commands[i].cc_name == NULL) { 142 /* look for abbreviation */ 143 for (i = 0; commands[i].cc_name != NULL; ++i) 144 if (strncmp(*argv, commands[i].cc_name, 145 strlen(*argv)) == 0) 146 break; 147 } 148 if (commands[i].cc_name == NULL) 149 errx(1, "unknown command: %s", *argv); 150 151 exit((*commands[i].cc_handler)(commands[i].cc_name, argc, argv)); 152 } 153 154 static int 155 do_move(char *cname, int argc, char *argv[]) 156 { 157 struct changer_move cmd; 158 int val; 159 160 /* 161 * On a move command, we expect the following: 162 * 163 * <from ET> <from EU> <to ET> <to EU> [inv] 164 * 165 * where ET == element type and EU == element unit. 166 */ 167 168 ++argv; --argc; 169 170 if (argc < 4) { 171 warnx("%s: too few arguments", cname); 172 goto usage; 173 } else if (argc > 5) { 174 warnx("%s: too many arguments", cname); 175 goto usage; 176 } 177 bzero(&cmd, sizeof(cmd)); 178 179 /* 180 * Get the from ET and EU - we search for it if the ET is 181 * "voltag", otherwise, we just use the ET and EU given to us. 182 */ 183 if (strcmp(*argv, "voltag") == 0) { 184 ++argv; --argc; 185 find_voltag(*argv, &cmd.cm_fromtype, &cmd.cm_fromunit); 186 ++argv; --argc; 187 } else { 188 cmd.cm_fromtype = parse_element_type(*argv); 189 ++argv; --argc; 190 cmd.cm_fromunit = parse_element_unit(*argv); 191 ++argv; --argc; 192 } 193 194 if (cmd.cm_fromtype == CHET_DT) 195 check_source_drive(cmd.cm_fromunit); 196 197 /* 198 * Don't allow voltag on the to ET, using a volume 199 * as a destination makes no sense on a move 200 */ 201 cmd.cm_totype = parse_element_type(*argv); 202 ++argv; --argc; 203 cmd.cm_tounit = parse_element_unit(*argv); 204 ++argv; --argc; 205 206 /* Deal with optional command modifier. */ 207 if (argc) { 208 val = parse_special(*argv); 209 switch (val) { 210 case SW_INVERT: 211 cmd.cm_flags |= CM_INVERT; 212 break; 213 214 default: 215 errx(1, "%s: inappropriate modifier `%s'", 216 cname, *argv); 217 /* NOTREACHED */ 218 } 219 } 220 221 /* Send command to changer. */ 222 if (ioctl(changer_fd, CHIOMOVE, &cmd) == -1) 223 err(1, "%s: CHIOMOVE", changer_name); 224 225 return (0); 226 227 usage: 228 fprintf(stderr, "usage: %s %s " 229 "<from ET> <from EU> <to ET> <to EU> [inv]\n", __progname, cname); 230 return (1); 231 } 232 233 static int 234 do_exchange(char *cname, int argc, char *argv[]) 235 { 236 struct changer_exchange cmd; 237 int val; 238 239 /* 240 * On an exchange command, we expect the following: 241 * 242 * <src ET> <src EU> <dst1 ET> <dst1 EU> [<dst2 ET> <dst2 EU>] [inv1] [inv2] 243 * 244 * where ET == element type and EU == element unit. 245 */ 246 247 ++argv; --argc; 248 249 if (argc < 4) { 250 warnx("%s: too few arguments", cname); 251 goto usage; 252 } else if (argc > 8) { 253 warnx("%s: too many arguments", cname); 254 goto usage; 255 } 256 bzero(&cmd, sizeof(cmd)); 257 258 /* <src ET> */ 259 cmd.ce_srctype = parse_element_type(*argv); 260 ++argv; --argc; 261 262 /* <src EU> */ 263 cmd.ce_srcunit = parse_element_unit(*argv); 264 ++argv; --argc; 265 266 /* <dst1 ET> */ 267 cmd.ce_fdsttype = parse_element_type(*argv); 268 ++argv; --argc; 269 270 /* <dst1 EU> */ 271 cmd.ce_fdstunit = parse_element_unit(*argv); 272 ++argv; --argc; 273 274 /* 275 * If the next token is a special word or there are no more 276 * arguments, then this is a case of simple exchange. 277 * dst2 == src. 278 */ 279 if ((argc == 0) || is_special(*argv)) { 280 cmd.ce_sdsttype = cmd.ce_srctype; 281 cmd.ce_sdstunit = cmd.ce_srcunit; 282 goto do_special; 283 } 284 285 /* <dst2 ET> */ 286 cmd.ce_sdsttype = parse_element_type(*argv); 287 ++argv; --argc; 288 289 /* <dst2 EU> */ 290 cmd.ce_sdstunit = parse_element_unit(*argv); 291 ++argv; --argc; 292 293 do_special: 294 /* Deal with optional command modifiers. */ 295 while (argc) { 296 val = parse_special(*argv); 297 ++argv; --argc; 298 switch (val) { 299 case SW_INVERT1: 300 cmd.ce_flags |= CE_INVERT1; 301 break; 302 303 case SW_INVERT2: 304 cmd.ce_flags |= CE_INVERT2; 305 break; 306 307 default: 308 errx(1, "%s: inappropriate modifier `%s'", 309 cname, *argv); 310 /* NOTREACHED */ 311 } 312 } 313 314 /* Send command to changer. */ 315 if (ioctl(changer_fd, CHIOEXCHANGE, &cmd) == -1) 316 err(1, "%s: CHIOEXCHANGE", changer_name); 317 318 return (0); 319 320 usage: 321 fprintf(stderr, "usage: %s %s <src ET> <src EU> <dst1 ET> <dst1 EU>\n" 322 " [<dst2 ET> <dst2 EU>] [inv1] [inv2]\n", 323 __progname, cname); 324 return (1); 325 } 326 327 static int 328 do_position(char *cname, int argc, char *argv[]) 329 { 330 struct changer_position cmd; 331 int val; 332 333 /* 334 * On a position command, we expect the following: 335 * 336 * <to ET> <to EU> [inv] 337 * 338 * where ET == element type and EU == element unit. 339 */ 340 341 ++argv; --argc; 342 343 if (argc < 2) { 344 warnx("%s: too few arguments", cname); 345 goto usage; 346 } else if (argc > 3) { 347 warnx("%s: too many arguments", cname); 348 goto usage; 349 } 350 bzero(&cmd, sizeof(cmd)); 351 352 /* <to ET> */ 353 cmd.cp_type = parse_element_type(*argv); 354 ++argv; --argc; 355 356 /* <to EU> */ 357 cmd.cp_unit = parse_element_unit(*argv); 358 ++argv; --argc; 359 360 /* Deal with optional command modifier. */ 361 if (argc) { 362 val = parse_special(*argv); 363 switch (val) { 364 case SW_INVERT: 365 cmd.cp_flags |= CP_INVERT; 366 break; 367 368 default: 369 errx(1, "%s: inappropriate modifier `%s'", 370 cname, *argv); 371 /* NOTREACHED */ 372 } 373 } 374 375 /* Send command to changer. */ 376 if (ioctl(changer_fd, CHIOPOSITION, &cmd) == -1) 377 err(1, "%s: CHIOPOSITION", changer_name); 378 379 return (0); 380 381 usage: 382 fprintf(stderr, "usage: %s %s <to ET> <to EU> [inv]\n", 383 __progname, cname); 384 return (1); 385 } 386 387 static int 388 do_params(char *cname, int argc, char *argv[]) 389 { 390 struct changer_params data; 391 392 /* No arguments to this command. */ 393 394 ++argv; --argc; 395 396 if (argc) { 397 warnx("%s: no arguments expected", cname); 398 goto usage; 399 } 400 401 /* Get params from changer and display them. */ 402 bzero(&data, sizeof(data)); 403 if (ioctl(changer_fd, CHIOGPARAMS, &data) == -1) 404 err(1, "%s: CHIOGPARAMS", changer_name); 405 406 printf("%s: %d slot%s, %d drive%s, %d picker%s", 407 changer_name, 408 data.cp_nslots, (data.cp_nslots > 1) ? "s" : "", 409 data.cp_ndrives, (data.cp_ndrives > 1) ? "s" : "", 410 data.cp_npickers, (data.cp_npickers > 1) ? "s" : ""); 411 if (data.cp_nportals) 412 printf(", %d portal%s", data.cp_nportals, 413 (data.cp_nportals > 1) ? "s" : ""); 414 printf("\n%s: current picker: %d\n", changer_name, data.cp_curpicker); 415 416 return (0); 417 418 usage: 419 fprintf(stderr, "usage: %s %s\n", __progname, cname); 420 return (1); 421 } 422 423 static int 424 do_getpicker(char *cname, int argc, char *argv[]) 425 { 426 int picker; 427 428 /* No arguments to this command. */ 429 430 ++argv; --argc; 431 432 if (argc) { 433 warnx("%s: no arguments expected", cname); 434 goto usage; 435 } 436 437 /* Get current picker from changer and display it. */ 438 if (ioctl(changer_fd, CHIOGPICKER, &picker) == -1) 439 err(1, "%s: CHIOGPICKER", changer_name); 440 441 printf("%s: current picker: %d\n", changer_name, picker); 442 443 return (0); 444 445 usage: 446 fprintf(stderr, "usage: %s %s\n", __progname, cname); 447 return (1); 448 } 449 450 static int 451 do_setpicker(char *cname, int argc, char *argv[]) 452 { 453 int picker; 454 455 ++argv; --argc; 456 457 if (argc < 1) { 458 warnx("%s: too few arguments", cname); 459 goto usage; 460 } else if (argc > 1) { 461 warnx("%s: too many arguments", cname); 462 goto usage; 463 } 464 465 picker = parse_element_unit(*argv); 466 467 /* Set the changer picker. */ 468 if (ioctl(changer_fd, CHIOSPICKER, &picker) == -1) 469 err(1, "%s: CHIOSPICKER", changer_name); 470 471 return (0); 472 473 usage: 474 fprintf(stderr, "usage: %s %s <picker>\n", __progname, cname); 475 return (1); 476 } 477 478 static int 479 do_status(char *cname, int argc, char *argv[]) 480 { 481 struct changer_element_status_request cmd; 482 struct changer_params data; 483 int i, chet, schet, echet, c; 484 char *description; 485 size_t count; 486 487 optreset = 1; 488 optind = 1; 489 while ((c = getopt(argc, argv, "vVa")) != -1) { 490 switch (c) { 491 case 'v': 492 pvoltag = 1; 493 break; 494 case 'V': 495 avoltag = 1; 496 break; 497 case 'a': 498 pvoltag = avoltag = 1; 499 break; 500 default: 501 goto usage; 502 } 503 } 504 505 argc -= optind; 506 argv += optind; 507 508 /* 509 * On a status command, we expect the following: 510 * 511 * [<ET>] 512 * 513 * where ET == element type. 514 * 515 * If we get no arguments, we get the status of all 516 * known element types. 517 */ 518 if (argc > 1) { 519 warnx("%s: too many arguments", cname); 520 goto usage; 521 } 522 523 /* 524 * Get params from changer. Specifically, we need the element 525 * counts. 526 */ 527 bzero(&data, sizeof(data)); 528 if (ioctl(changer_fd, CHIOGPARAMS, &data) == -1) 529 err(1, "%s: CHIOGPARAMS", changer_name); 530 531 if (argc) 532 schet = echet = parse_element_type(*argv); 533 else { 534 schet = CHET_MT; 535 echet = CHET_DT; 536 } 537 538 for (chet = schet; chet <= echet; ++chet) { 539 switch (chet) { 540 case CHET_MT: 541 count = data.cp_npickers; 542 description = "picker"; 543 break; 544 545 case CHET_ST: 546 count = data.cp_nslots; 547 description = "slot"; 548 break; 549 550 case CHET_IE: 551 count = data.cp_nportals; 552 description = "portal"; 553 break; 554 555 case CHET_DT: 556 count = data.cp_ndrives; 557 description = "drive"; 558 break; 559 } 560 561 if (count == 0) { 562 if (argc == 0) 563 continue; 564 else { 565 printf("%s: no %s elements\n", 566 changer_name, description); 567 return (0); 568 } 569 } 570 571 bzero(&cmd, sizeof(cmd)); 572 573 cmd.cesr_type = chet; 574 /* Allocate storage for the status info. */ 575 cmd.cesr_data = calloc(count, sizeof(*cmd.cesr_data)); 576 if ((cmd.cesr_data) == NULL) 577 errx(1, "can't allocate status storage"); 578 if (avoltag || pvoltag) 579 cmd.cesr_flags |= CESR_VOLTAGS; 580 581 if (ioctl(changer_fd, CHIOGSTATUS, &cmd) == -1) { 582 free(cmd.cesr_data); 583 err(1, "%s: CHIOGSTATUS", changer_name); 584 } 585 586 /* Dump the status for each element of this type. */ 587 for (i = 0; i < count; ++i) { 588 struct changer_element_status *ces = 589 &(cmd.cesr_data[i]); 590 printf("%s %d: %s", description, i, 591 bits_to_string(ces->ces_flags, CESTATUS_BITS)); 592 if (pvoltag) 593 printf(" voltag: <%s:%d>", 594 ces->ces_pvoltag.cv_volid, 595 ces->ces_pvoltag.cv_serial); 596 if (avoltag) 597 printf(" avoltag: <%s:%d>", 598 ces->ces_avoltag.cv_volid, 599 ces->ces_avoltag.cv_serial); 600 printf("\n"); 601 } 602 603 free(cmd.cesr_data); 604 } 605 606 return (0); 607 608 usage: 609 fprintf(stderr, "usage: %s %s [<element type>]\n", __progname, 610 cname); 611 return (1); 612 } 613 614 /* 615 * Check a drive unit as the source for a move or exchange 616 * operation. If the drive is not accessible, we attempt 617 * to unmount the tape in it before moving to avoid 618 * errors in "disconnected" type pickers where the drive 619 * is on a separate target from the changer. 620 */ 621 static void 622 check_source_drive(int unit) 623 { 624 struct mtop mtoffl = { MTOFFL, 1 }; 625 struct changer_element_status_request cmd; 626 struct changer_element_status *ces; 627 struct changer_params data; 628 size_t count = 0; 629 int mtfd; 630 char *tapedev; 631 632 /* 633 * Get params from changer. Specifically, we need the element 634 * counts. 635 */ 636 bzero(&data, sizeof(data)); 637 if (ioctl(changer_fd, CHIOGPARAMS, &data) == -1) 638 err(1, "%s: CHIOGPARAMS", changer_name); 639 640 count = data.cp_ndrives; 641 if (unit < 0 || unit >= count) 642 err(1, "%s: invalid drive: drive %d", changer_name, unit); 643 644 bzero(&cmd, sizeof(cmd)); 645 cmd.cesr_type = CHET_DT; 646 /* Allocate storage for the status info. */ 647 cmd.cesr_data = calloc(count, sizeof(*cmd.cesr_data)); 648 if ((cmd.cesr_data) == NULL) 649 errx(1, "can't allocate status storage"); 650 651 if (ioctl(changer_fd, CHIOGSTATUS, &cmd) == -1) { 652 free(cmd.cesr_data); 653 err(1, "%s: CHIOGSTATUS", changer_name); 654 } 655 ces = &(cmd.cesr_data[unit]); 656 657 if ((ces->ces_flags & CESTATUS_FULL) != CESTATUS_FULL) 658 err(1, "%s: drive %d is empty!", changer_name, unit); 659 660 if ((ces->ces_flags & CESTATUS_ACCESS) == CESTATUS_ACCESS) 661 return; /* changer thinks all is well - trust it */ 662 663 /* 664 * Otherwise, drive is FULL, but not accessible. 665 * Try to make it accessible by doing an mt offline. 666 */ 667 tapedev = parse_tapedev(_PATH_CH_CONF, changer_name, unit); 668 mtfd = opendev(tapedev, O_RDONLY, 0, NULL); 669 if (mtfd == -1) 670 err(1, "%s drive %d (%s): open", changer_name, unit, tapedev); 671 if (ioctl(mtfd, MTIOCTOP, &mtoffl) == -1) 672 err(1, "%s drive %d (%s): rewoffl", changer_name, unit, 673 tapedev); 674 close(mtfd); 675 } 676 677 void 678 find_voltag(char *voltag, int *type, int *unit) 679 { 680 struct changer_element_status_request cmd; 681 struct changer_params data; 682 int i, chet, schet, echet, found; 683 size_t count = 0; 684 685 /* 686 * Get params from changer. Specifically, we need the element 687 * counts. 688 */ 689 bzero(&data, sizeof(data)); 690 if (ioctl(changer_fd, CHIOGPARAMS, &data) == -1) 691 err(1, "%s: CHIOGPARAMS", changer_name); 692 693 found = 0; 694 schet = CHET_MT; 695 echet = CHET_DT; 696 697 /* 698 * For each type of element, iterate through each one until 699 * we find the correct volume id. 700 */ 701 for (chet = schet; chet <= echet; ++chet) { 702 switch (chet) { 703 case CHET_MT: 704 count = data.cp_npickers; 705 break; 706 case CHET_ST: 707 count = data.cp_nslots; 708 break; 709 case CHET_IE: 710 count = data.cp_nportals; 711 break; 712 case CHET_DT: 713 count = data.cp_ndrives; 714 break; 715 } 716 if (count == 0 || found) 717 continue; 718 719 bzero(&cmd, sizeof(cmd)); 720 cmd.cesr_type = chet; 721 /* Allocate storage for the status info. */ 722 cmd.cesr_data = calloc(count, sizeof(*cmd.cesr_data)); 723 if ((cmd.cesr_data) == NULL) 724 errx(1, "can't allocate status storage"); 725 cmd.cesr_flags |= CESR_VOLTAGS; 726 727 if (ioctl(changer_fd, CHIOGSTATUS, &cmd) == -1) { 728 free(cmd.cesr_data); 729 err(1, "%s: CHIOGSTATUS", changer_name); 730 } 731 732 /* 733 * look through each element to see if it has our desired 734 * volume tag. 735 */ 736 for (i = 0; i < count; ++i) { 737 struct changer_element_status *ces = 738 &(cmd.cesr_data[i]); 739 if ((ces->ces_flags & CESTATUS_FULL) != CESTATUS_FULL) 740 continue; /* no tape in drive */ 741 if (strcasecmp(voltag, ces->ces_pvoltag.cv_volid) 742 == 0) { 743 *type = chet; 744 *unit = i; 745 found = 1; 746 free(cmd.cesr_data); 747 return; 748 } 749 } 750 free(cmd.cesr_data); 751 } 752 errx(1, "%s: unable to locate voltag: %s", changer_name, voltag); 753 } 754 755 756 static int 757 parse_element_type(char *cp) 758 { 759 int i; 760 761 for (i = 0; elements[i].et_name != NULL; ++i) 762 if (strcmp(elements[i].et_name, cp) == 0) 763 return (elements[i].et_type); 764 765 errx(1, "invalid element type `%s'", cp); 766 } 767 768 static int 769 parse_element_unit(char *cp) 770 { 771 int i; 772 char *p; 773 774 i = (int)strtol(cp, &p, 10); 775 if ((i < 0) || (*p != '\0')) 776 errx(1, "invalid unit number `%s'", cp); 777 778 return (i); 779 } 780 781 static int 782 parse_special(char *cp) 783 { 784 int val; 785 786 val = is_special(cp); 787 if (val) 788 return (val); 789 790 errx(1, "invalid modifier `%s'", cp); 791 } 792 793 static int 794 is_special(char *cp) 795 { 796 int i; 797 798 for (i = 0; specials[i].sw_name != NULL; ++i) 799 if (strcmp(specials[i].sw_name, cp) == 0) 800 return (specials[i].sw_value); 801 802 return (0); 803 } 804 805 static char * 806 bits_to_string(int v, const char *cp) 807 { 808 const char *np; 809 char f, sep, *bp; 810 static char buf[128]; 811 812 bp = buf; 813 bzero(buf, sizeof(buf)); 814 815 for (sep = '<'; (f = *cp++) != 0; cp = np) { 816 for (np = cp; *np >= ' ';) 817 np++; 818 if ((v & (1 << (f - 1))) == 0) 819 continue; 820 (void)snprintf(bp, sizeof(buf) - (bp - &buf[0]), 821 "%c%.*s", sep, (int)(np - cp), cp); 822 bp += strlen(bp); 823 sep = ','; 824 } 825 if (sep != '<') 826 *bp = '>'; 827 828 return (buf); 829 } 830 831 static void 832 usage(void) 833 { 834 int i; 835 836 fprintf(stderr, "usage: %s [-f changer] command [arg ...]\n", 837 __progname); 838 fprintf(stderr, "commands:"); 839 for (i = 0; commands[i].cc_name; i++) 840 fprintf(stderr, " %s", commands[i].cc_name); 841 fprintf(stderr, "\n"); 842 exit(1); 843 } 844