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