1 /* $NetBSD: chio.c,v 1.18 2002/05/02 13:07:01 enami Exp $ */ 2 3 /*- 4 * Copyright (c) 1996, 1998, 1999 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, 9 * NASA Ames Research Center. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. All advertising materials mentioning features or use of this software 20 * must display the following acknowledgement: 21 * This product includes software developed by the NetBSD 22 * Foundation, Inc. and its contributors. 23 * 4. Neither the name of The NetBSD Foundation nor the names of its 24 * contributors may be used to endorse or promote products derived 25 * from this software without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 * POSSIBILITY OF SUCH DAMAGE. 38 */ 39 40 /* 41 * Additional Copyright (c) 1997, by Matthew Jacob, for NASA/Ames Research Ctr. 42 */ 43 44 #include <sys/cdefs.h> 45 #ifndef lint 46 __COPYRIGHT( 47 "@(#) Copyright (c) 1996, 1998, 1999\ 48 The NetBSD Foundation, Inc. All rights reserved."); 49 __RCSID("$NetBSD: chio.c,v 1.18 2002/05/02 13:07:01 enami Exp $"); 50 #endif 51 52 #include <sys/param.h> 53 #include <sys/ioctl.h> 54 #include <sys/chio.h> 55 #include <sys/cdio.h> /* for ATAPI CD changer; too bad it uses a lame API */ 56 57 #include <ctype.h> 58 #include <err.h> 59 #include <errno.h> 60 #include <fcntl.h> 61 #include <limits.h> 62 #include <stdio.h> 63 #include <stdlib.h> 64 #include <string.h> 65 #include <unistd.h> 66 67 #include "defs.h" 68 #include "pathnames.h" 69 70 int main(int, char *[]); 71 static void usage(void); 72 static void cleanup(void); 73 static int parse_element_type(const char *); 74 static int parse_element_unit(const char *); 75 static int parse_special(const char *); 76 static int is_special(const char *); 77 static const char *bits_to_string(int, const char *); 78 79 static int do_move(const char *, int, char **); 80 static int do_exchange(const char *, int, char **); 81 static int do_position(const char *, int, char **); 82 static int do_params(const char *, int, char **); 83 static int do_getpicker(const char *, int, char **); 84 static int do_setpicker(const char *, int, char **); 85 static int do_status(const char *, int, char **); 86 static int do_ielem(const char *, int, char **); 87 static int do_cdlu(const char *, int, char **); 88 89 /* Valid changer element types. */ 90 const struct element_type elements[] = { 91 { "picker", CHET_MT }, 92 { "slot", CHET_ST }, 93 { "portal", CHET_IE }, 94 { "drive", CHET_DT }, 95 { NULL, 0 }, 96 }; 97 98 /* Valid commands. */ 99 const struct changer_command commands[] = { 100 { "move", " <from ET> <from EU> <to ET> <to EU> [inv]", 101 do_move }, 102 103 { "exchange", " <src ET> <src EU> <dst1 ET> <dst1 EU>\n" 104 "\t\t [<dst2 ET> <dst2 EU>] [inv1] [inv2]", 105 do_exchange }, 106 107 { "position", " <to ET> <to EU> [inv]", do_position }, 108 109 { "params", "", 110 do_params }, 111 112 { "getpicker", "", 113 do_getpicker }, 114 115 { "setpicker", " <picker>", 116 do_setpicker }, 117 118 { "status", " [<ET> [unit [count]]] [voltags]", 119 do_status }, 120 121 { "ielem", "", 122 do_ielem }, 123 124 { "cdlu", " load|unload <slot>\n" 125 "\t abort", 126 do_cdlu }, 127 128 { NULL, NULL, 129 NULL }, 130 }; 131 132 /* Valid special words. */ 133 const struct special_word specials[] = { 134 { "inv", SW_INVERT }, 135 { "inv1", SW_INVERT1 }, 136 { "inv2", SW_INVERT2 }, 137 { "voltags", SW_VOLTAGS }, 138 { NULL, 0 }, 139 }; 140 141 static const char *changer_name; 142 static int changer_fd; 143 144 int 145 main(int argc, char *argv[]) 146 { 147 int ch, i; 148 149 setprogname(argv[0]); 150 while ((ch = getopt(argc, argv, "f:")) != -1) { 151 switch (ch) { 152 case 'f': 153 changer_name = optarg; 154 break; 155 default: 156 usage(); 157 } 158 } 159 argc -= optind; 160 argv += optind; 161 162 if (argc == 0) 163 usage(); 164 165 /* Get the default changer if not already specified. */ 166 if (changer_name == NULL) 167 if ((changer_name = getenv(CHANGER_ENV_VAR)) == NULL) 168 changer_name = _PATH_CH; 169 170 /* Open the changer device. */ 171 if ((changer_fd = open(changer_name, O_RDWR, 0600)) == -1) 172 err(1, "%s: open", changer_name); 173 174 /* Register cleanup function. */ 175 if (atexit(cleanup)) 176 err(1, "can't register cleanup function"); 177 178 /* Find the specified command. */ 179 for (i = 0; commands[i].cc_name != NULL; ++i) 180 if (strcmp(*argv, commands[i].cc_name) == 0) 181 break; 182 if (commands[i].cc_name == NULL) 183 errx(1, "unknown command: %s", *argv); 184 185 /* Skip over the command name and call handler. */ 186 ++argv; --argc; 187 exit((*commands[i].cc_handler)(commands[i].cc_name, argc, argv)); 188 /* NOTREACHED */ 189 } 190 191 static int 192 do_move(const char *cname, int argc, char **argv) 193 { 194 struct changer_move_request cmd; 195 int val; 196 197 /* 198 * On a move command, we expect the following: 199 * 200 * <from ET> <from EU> <to ET> <to EU> [inv] 201 * 202 * where ET == element type and EU == element unit. 203 */ 204 if (argc < 4) { 205 warnx("%s: too few arguments", cname); 206 usage(); 207 } else if (argc > 5) { 208 warnx("%s: too many arguments", cname); 209 usage(); 210 } 211 (void)memset(&cmd, 0, sizeof(cmd)); 212 213 /* <from ET> */ 214 cmd.cm_fromtype = parse_element_type(*argv); 215 ++argv; --argc; 216 217 /* <from EU> */ 218 cmd.cm_fromunit = parse_element_unit(*argv); 219 ++argv; --argc; 220 221 /* <to ET> */ 222 cmd.cm_totype = parse_element_type(*argv); 223 ++argv; --argc; 224 225 /* <to EU> */ 226 cmd.cm_tounit = parse_element_unit(*argv); 227 ++argv; --argc; 228 229 /* Deal with optional command modifier. */ 230 if (argc) { 231 val = parse_special(*argv); 232 switch (val) { 233 case SW_INVERT: 234 cmd.cm_flags |= CM_INVERT; 235 break; 236 default: 237 errx(1, "%s: inappropriate modifier `%s'", 238 cname, *argv); 239 /* NOTREACHED */ 240 } 241 } 242 243 /* Send command to changer. */ 244 if (ioctl(changer_fd, CHIOMOVE, &cmd)) 245 err(1, "%s: CHIOMOVE", changer_name); 246 247 return (0); 248 } 249 250 static int 251 do_exchange(const char *cname, int argc, char **argv) 252 { 253 struct changer_exchange_request cmd; 254 int val; 255 256 /* 257 * On an exchange command, we expect the following: 258 * 259 * <src ET> <src EU> <dst1 ET> <dst1 EU> [<dst2 ET> <dst2 EU>] [inv1] [inv2] 260 * 261 * where ET == element type and EU == element unit. 262 */ 263 if (argc < 4) { 264 warnx("%s: too few arguments", cname); 265 usage(); 266 } else if (argc > 8) { 267 warnx("%s: too many arguments", cname); 268 usage(); 269 } 270 (void)memset(&cmd, 0, sizeof(cmd)); 271 272 /* <src ET> */ 273 cmd.ce_srctype = parse_element_type(*argv); 274 ++argv; --argc; 275 276 /* <src EU> */ 277 cmd.ce_srcunit = parse_element_unit(*argv); 278 ++argv; --argc; 279 280 /* <dst1 ET> */ 281 cmd.ce_fdsttype = parse_element_type(*argv); 282 ++argv; --argc; 283 284 /* <dst1 EU> */ 285 cmd.ce_fdstunit = parse_element_unit(*argv); 286 ++argv; --argc; 287 288 /* 289 * If the next token is a special word or there are no more 290 * arguments, then this is a case of simple exchange. 291 * dst2 == src. 292 */ 293 if ((argc == 0) || is_special(*argv)) { 294 cmd.ce_sdsttype = cmd.ce_srctype; 295 cmd.ce_sdstunit = cmd.ce_srcunit; 296 goto do_special; 297 } 298 299 /* <dst2 ET> */ 300 cmd.ce_sdsttype = parse_element_type(*argv); 301 ++argv; --argc; 302 303 /* <dst2 EU> */ 304 cmd.ce_sdstunit = parse_element_unit(*argv); 305 ++argv; --argc; 306 307 do_special: 308 /* Deal with optional command modifiers. */ 309 while (argc) { 310 val = parse_special(*argv); 311 ++argv; --argc; 312 switch (val) { 313 case SW_INVERT1: 314 cmd.ce_flags |= CE_INVERT1; 315 break; 316 case SW_INVERT2: 317 cmd.ce_flags |= CE_INVERT2; 318 break; 319 default: 320 errx(1, "%s: inappropriate modifier `%s'", 321 cname, *argv); 322 /* NOTREACHED */ 323 } 324 } 325 326 /* Send command to changer. */ 327 if (ioctl(changer_fd, CHIOEXCHANGE, &cmd)) 328 err(1, "%s: CHIOEXCHANGE", changer_name); 329 330 return (0); 331 } 332 333 static int 334 do_position(const char *cname, int argc, char **argv) 335 { 336 struct changer_position_request cmd; 337 int val; 338 339 /* 340 * On a position command, we expect the following: 341 * 342 * <to ET> <to EU> [inv] 343 * 344 * where ET == element type and EU == element unit. 345 */ 346 if (argc < 2) { 347 warnx("%s: too few arguments", cname); 348 usage(); 349 } else if (argc > 3) { 350 warnx("%s: too many arguments", cname); 351 usage(); 352 } 353 (void)memset(&cmd, 0, sizeof(cmd)); 354 355 /* <to ET> */ 356 cmd.cp_type = parse_element_type(*argv); 357 ++argv; --argc; 358 359 /* <to EU> */ 360 cmd.cp_unit = parse_element_unit(*argv); 361 ++argv; --argc; 362 363 /* Deal with optional command modifier. */ 364 if (argc) { 365 val = parse_special(*argv); 366 switch (val) { 367 case SW_INVERT: 368 cmd.cp_flags |= CP_INVERT; 369 break; 370 default: 371 errx(1, "%s: inappropriate modifier `%s'", 372 cname, *argv); 373 /* NOTREACHED */ 374 } 375 } 376 377 /* Send command to changer. */ 378 if (ioctl(changer_fd, CHIOPOSITION, &cmd)) 379 err(1, "%s: CHIOPOSITION", changer_name); 380 381 return (0); 382 } 383 384 /* ARGSUSED */ 385 static int 386 do_params(const char *cname, int argc, char **argv) 387 { 388 struct changer_params data; 389 390 /* No arguments to this command. */ 391 if (argc) { 392 warnx("%s: no arguements expected", cname); 393 usage(); 394 } 395 396 /* Get params from changer and display them. */ 397 (void)memset(&data, 0, sizeof(data)); 398 if (ioctl(changer_fd, CHIOGPARAMS, &data)) 399 err(1, "%s: CHIOGPARAMS", changer_name); 400 401 #define PLURAL(n) (n) > 1 ? "s" : "" 402 403 (void)printf("%s: %d slot%s, %d drive%s, %d picker%s", 404 changer_name, 405 data.cp_nslots, PLURAL(data.cp_nslots), 406 data.cp_ndrives, PLURAL(data.cp_ndrives), 407 data.cp_npickers, PLURAL(data.cp_npickers)); 408 if (data.cp_nportals) 409 (void)printf(", %d portal%s", data.cp_nportals, 410 PLURAL(data.cp_nportals)); 411 412 #undef PLURAL 413 414 (void)printf("\n%s: current picker: %d\n", changer_name, 415 data.cp_curpicker); 416 417 return (0); 418 } 419 420 /* ARGSUSED */ 421 static int 422 do_getpicker(const char *cname, int argc, char **argv) 423 { 424 int picker; 425 426 /* No arguments to this command. */ 427 if (argc) { 428 warnx("%s: no arguments expected", cname); 429 usage(); 430 } 431 432 /* Get current picker from changer and display it. */ 433 if (ioctl(changer_fd, CHIOGPICKER, &picker)) 434 err(1, "%s: CHIOGPICKER", changer_name); 435 436 (void)printf("%s: current picker: %d\n", changer_name, picker); 437 438 return (0); 439 } 440 441 static int 442 do_setpicker(const char *cname, int argc, char **argv) 443 { 444 int picker; 445 446 if (argc < 1) { 447 warnx("%s: too few arguments", cname); 448 usage(); 449 } else if (argc > 1) { 450 warnx("%s: too many arguments", cname); 451 usage(); 452 } 453 454 picker = parse_element_unit(*argv); 455 456 /* Set the changer picker. */ 457 if (ioctl(changer_fd, CHIOSPICKER, &picker)) 458 err(1, "%s: CHIOSPICKER", changer_name); 459 460 return (0); 461 } 462 463 static int 464 do_status(const char *cname, int argc, char **argv) 465 { 466 struct changer_element_status_request cmd; 467 struct changer_params data; 468 struct changer_element_status *ces; 469 int i, chet, count, echet, flags, have_ucount, have_unit; 470 int schet, ucount, unit; 471 size_t size; 472 473 flags = 0; 474 have_ucount = 0; 475 have_unit = 0; 476 477 /* 478 * On a status command, we expect the following: 479 * 480 * [<ET> [unit [count]]] [voltags] 481 * 482 * where ET == element type. 483 * 484 * If we get no element-related arguments, we get the status of all 485 * known element types. 486 */ 487 if (argc > 4) { 488 warnx("%s: too many arguments", cname); 489 usage(); 490 } 491 492 /* 493 * Get params from changer. Specifically, we need the element 494 * counts. 495 */ 496 (void)memset(&data, 0, sizeof(data)); 497 if (ioctl(changer_fd, CHIOGPARAMS, &data)) 498 err(1, "%s: CHIOGPARAMS", changer_name); 499 500 schet = CHET_MT; 501 echet = CHET_DT; 502 503 for (; argc != 0; argc--, argv++) { 504 /* 505 * If we have the voltags modifier, it must be the 506 * last argument. 507 */ 508 if (is_special(argv[0])) { 509 if (argc != 1) { 510 warnx("%s: malformed command line", cname); 511 usage(); 512 } 513 if (parse_special(argv[0]) != SW_VOLTAGS) 514 errx(1, "%s: inappropriate special word: %s", 515 cname, argv[0]); 516 flags |= CESR_VOLTAGS; 517 continue; 518 } 519 520 /* 521 * If we get an element type, we can't have specified 522 * anything else. 523 */ 524 if (isdigit(*argv[0]) == 0) { 525 if (schet == echet || flags != 0 || have_unit || 526 have_ucount) { 527 warnx("%s: malformed command line", cname); 528 usage(); 529 } 530 schet = echet = parse_element_type(argv[0]); 531 continue; 532 } 533 534 /* 535 * We know we have a digit here. If we do, we must 536 * have specified an element type. 537 */ 538 if (schet != echet) { 539 warnx("%s: malformed command line", cname); 540 usage(); 541 } 542 543 i = parse_element_unit(argv[0]); 544 545 if (have_unit == 0) { 546 unit = i; 547 have_unit = 1; 548 } else if (have_ucount == 0) { 549 ucount = i; 550 have_ucount = 1; 551 } else { 552 warnx("%s: malformed command line", cname); 553 usage(); 554 } 555 } 556 557 for (chet = schet; chet <= echet; ++chet) { 558 switch (chet) { 559 case CHET_MT: 560 count = data.cp_npickers; 561 break; 562 case CHET_ST: 563 count = data.cp_nslots; 564 break; 565 case CHET_IE: 566 count = data.cp_nportals; 567 break; 568 case CHET_DT: 569 count = data.cp_ndrives; 570 break; 571 default: 572 /* To appease gcc -Wuninitialized. */ 573 count = 0; 574 } 575 576 if (count == 0) { 577 if (schet != echet) 578 continue; 579 else { 580 (void)printf("%s: no %s elements\n", 581 changer_name, 582 elements[chet].et_name); 583 return (0); 584 } 585 } 586 587 /* 588 * If we have a unit, we may or may not have a count. 589 * If we don't have a unit, we don't have a count, either. 590 * 591 * Make sure both are initialized. 592 */ 593 if (have_unit) { 594 if (have_ucount == 0) 595 ucount = 1; 596 } else { 597 unit = 0; 598 ucount = count; 599 } 600 601 if ((unit + ucount) > count) 602 errx(1, "%s: unvalid unit/count %d/%d\n", 603 cname, unit, ucount); 604 605 size = ucount * sizeof(struct changer_element_status); 606 607 /* Allocate storage for the status bytes. */ 608 if ((ces = malloc(size)) == NULL) 609 errx(1, "can't allocate status storage"); 610 611 (void)memset(ces, 0, size); 612 (void)memset(&cmd, 0, sizeof(cmd)); 613 614 cmd.cesr_type = chet; 615 cmd.cesr_unit = unit; 616 cmd.cesr_count = ucount; 617 cmd.cesr_flags = flags; 618 cmd.cesr_data = ces; 619 620 /* 621 * Should we deal with this eventually? 622 */ 623 cmd.cesr_vendor_data = NULL; 624 625 if (ioctl(changer_fd, CHIOGSTATUS, &cmd)) { 626 free(ces); 627 err(1, "%s: CHIOGSTATUS", changer_name); 628 } 629 630 /* Dump the status for each element of this type. */ 631 for (i = 0; i < ucount; i++) { 632 (void)printf("%s %d: ", elements[chet].et_name, 633 unit + i); 634 if ((ces[i].ces_flags & CESTATUS_STATUS_VALID) == 0) { 635 (void)printf("status not available\n"); 636 continue; 637 } 638 (void)printf("%s", bits_to_string(ces[i].ces_flags, 639 CESTATUS_BITS)); 640 if (ces[i].ces_flags & CESTATUS_XNAME_VALID) 641 (void)printf(" (%s)", ces[i].ces_xname); 642 (void)printf("\n"); 643 if (ces[i].ces_flags & CESTATUS_PVOL_VALID) 644 (void)printf("\tPrimary volume tag: %s " 645 "ver. %d\n", 646 ces[i].ces_pvoltag.cv_tag, 647 ces[i].ces_pvoltag.cv_serial); 648 if (ces[i].ces_flags & CESTATUS_AVOL_VALID) 649 (void)printf("\tAlternate volume tag: %s " 650 "ver. %d\n", 651 ces[i].ces_avoltag.cv_tag, 652 ces[i].ces_avoltag.cv_serial); 653 if (ces[i].ces_flags & CESTATUS_FROM_VALID) 654 (void)printf("\tFrom: %s %d\n", 655 elements[ces[i].ces_from_type].et_name, 656 ces[i].ces_from_unit); 657 if (ces[i].ces_vendor_len) 658 (void)printf("\tVendor-specific data size: " 659 "%lu\n", (u_long)ces[i].ces_vendor_len); 660 } 661 free(ces); 662 } 663 664 return (0); 665 } 666 667 /* ARGSUSED */ 668 static int 669 do_ielem(const char *cname, int argc, char **argv) 670 { 671 672 if (ioctl(changer_fd, CHIOIELEM, NULL)) 673 err(1, "%s: CHIOIELEM", changer_name); 674 675 return (0); 676 } 677 678 /* ARGSUSED */ 679 static int 680 do_cdlu(const char *cname, int argc, char **argv) 681 { 682 static const struct special_word cdlu_subcmds[] = { 683 { "load", CD_LU_LOAD }, 684 { "unload", CD_LU_UNLOAD }, 685 { "abort", CD_LU_ABORT }, 686 { NULL, 0 }, 687 }; 688 struct ioc_load_unload cmd; 689 int i; 690 691 /* 692 * This command is a little different, since we are mostly dealing 693 * with ATAPI CD changers, which have a lame API (since ATAPI doesn't 694 * have LUNs). 695 * 696 * We have 3 sub-commands: "load", "unload", and "abort". The 697 * first two take a slot number. The latter does not. 698 */ 699 700 if (argc < 1 || argc > 2) 701 usage(); 702 703 for (i = 0; cdlu_subcmds[i].sw_name != NULL; i++) { 704 if (strcmp(argv[0], cdlu_subcmds[i].sw_name) == 0) { 705 cmd.options = cdlu_subcmds[i].sw_value; 706 break; 707 } 708 } 709 if (cdlu_subcmds[i].sw_name == NULL) 710 usage(); 711 712 if (strcmp(argv[0], "abort") == 0) 713 cmd.slot = 0; 714 else 715 cmd.slot = parse_element_unit(argv[1]); 716 717 /* 718 * XXX Should maybe do something different with the device 719 * XXX handling for cdlu; think about this some more. 720 */ 721 if (ioctl(changer_fd, CDIOCLOADUNLOAD, &cmd)) 722 err(1, "%s: CDIOCLOADUNLOAD", changer_name); 723 724 return (0); 725 } 726 727 static int 728 parse_element_type(const char *cp) 729 { 730 int i; 731 732 for (i = 0; elements[i].et_name != NULL; ++i) 733 if (strcmp(elements[i].et_name, cp) == 0) 734 return (elements[i].et_type); 735 736 errx(1, "invalid element type `%s'", cp); 737 /* NOTREACHED */ 738 } 739 740 static int 741 parse_element_unit(const char *cp) 742 { 743 char *p; 744 int i; 745 746 i = (int)strtol(cp, &p, 10); 747 if ((i < 0) || (*p != '\0')) 748 errx(1, "invalid unit number `%s'", cp); 749 750 return (i); 751 } 752 753 static int 754 parse_special(const char *cp) 755 { 756 int val; 757 758 val = is_special(cp); 759 if (val) 760 return (val); 761 762 errx(1, "invalid modifier `%s'", cp); 763 /* NOTREACHED */ 764 } 765 766 static int 767 is_special(const char *cp) 768 { 769 int i; 770 771 for (i = 0; specials[i].sw_name != NULL; ++i) 772 if (strcmp(specials[i].sw_name, cp) == 0) 773 return (specials[i].sw_value); 774 775 return (0); 776 } 777 778 static const char * 779 bits_to_string(int v, const char *cp) 780 { 781 static char buf[128]; 782 const char *np; 783 char *bp, f; 784 int first; 785 786 bp = buf; 787 *bp++ = '<'; 788 for (first = 1; (f = *cp++) != 0; cp = np) { 789 for (np = cp; *np >= ' ';) 790 np++; 791 if ((v & (1 << (f - 1))) == 0) 792 continue; 793 if (first) 794 first = 0; 795 else 796 *bp++ = ','; 797 (void)memcpy(bp, cp, np - cp); 798 bp += np - cp; 799 } 800 *bp++ = '>'; 801 *bp = '\0'; 802 803 return (buf); 804 } 805 806 static void 807 cleanup(void) 808 { 809 810 /* Simple enough... */ 811 (void)close(changer_fd); 812 } 813 814 static void 815 usage(void) 816 { 817 int i; 818 819 (void)fprintf(stderr, "Usage: %s command arg1 arg2 ...\n", 820 getprogname()); 821 822 (void)fprintf(stderr, "Where command (and args) are:\n"); 823 for (i = 0; commands[i].cc_name != NULL; i++) 824 (void)fprintf(stderr, "\t%s%s\n", commands[i].cc_name, 825 commands[i].cc_args); 826 exit(1); 827 /* NOTREACHED */ 828 } 829