xref: /openbsd-src/bin/chio/chio.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: chio.c,v 1.23 2011/04/24 01:13:55 krw 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/param.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))
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))
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))
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))
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))
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))
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 #ifdef lint
488 	count = 0;
489 	description = NULL;
490 #endif
491 
492 	optreset = 1;
493 	optind = 1;
494 	while ((c = getopt(argc, argv, "vVa")) != -1) {
495 		switch (c) {
496 		case 'v':
497 			pvoltag = 1;
498 			break;
499 		case 'V':
500 			avoltag = 1;
501 			break;
502 		case 'a':
503 			pvoltag = avoltag = 1;
504 			break;
505 		default:
506 			goto usage;
507 		}
508 	}
509 
510 	argc -= optind;
511 	argv += optind;
512 
513 	/*
514 	 * On a status command, we expect the following:
515 	 *
516 	 * [<ET>]
517 	 *
518 	 * where ET == element type.
519 	 *
520 	 * If we get no arguments, we get the status of all
521 	 * known element types.
522 	 */
523 	if (argc > 1) {
524 		warnx("%s: too many arguments", cname);
525 		goto usage;
526 	}
527 
528 	/*
529 	 * Get params from changer.  Specifically, we need the element
530 	 * counts.
531 	 */
532 	bzero(&data, sizeof(data));
533 	if (ioctl(changer_fd, CHIOGPARAMS, &data))
534 		err(1, "%s: CHIOGPARAMS", changer_name);
535 
536 	if (argc)
537 		schet = echet = parse_element_type(*argv);
538 	else {
539 		schet = CHET_MT;
540 		echet = CHET_DT;
541 	}
542 
543 	for (chet = schet; chet <= echet; ++chet) {
544 		switch (chet) {
545 		case CHET_MT:
546 			count = data.cp_npickers;
547 			description = "picker";
548 			break;
549 
550 		case CHET_ST:
551 			count = data.cp_nslots;
552 			description = "slot";
553 			break;
554 
555 		case CHET_IE:
556 			count = data.cp_nportals;
557 			description = "portal";
558 			break;
559 
560 		case CHET_DT:
561 			count = data.cp_ndrives;
562 			description = "drive";
563 			break;
564 		}
565 
566 		if (count == 0) {
567 			if (argc == 0)
568 				continue;
569 			else {
570 				printf("%s: no %s elements\n",
571 				    changer_name, description);
572 				return (0);
573 			}
574 		}
575 
576 		bzero(&cmd, sizeof(cmd));
577 
578 		cmd.cesr_type = chet;
579 		/* Allocate storage for the status info. */
580 		cmd.cesr_data = calloc(count, sizeof(*cmd.cesr_data));
581 		if ((cmd.cesr_data) == NULL)
582 			errx(1, "can't allocate status storage");
583 		if (avoltag || pvoltag)
584 			cmd.cesr_flags |= CESR_VOLTAGS;
585 
586 		if (ioctl(changer_fd, CHIOGSTATUS, &cmd)) {
587 			free(cmd.cesr_data);
588 			err(1, "%s: CHIOGSTATUS", changer_name);
589 		}
590 
591 		/* Dump the status for each element of this type. */
592 		for (i = 0; i < count; ++i) {
593 			struct changer_element_status *ces =
594 			         &(cmd.cesr_data[i]);
595 			printf("%s %d: %s", description, i,
596 			    bits_to_string(ces->ces_flags, CESTATUS_BITS));
597 			if (pvoltag)
598 				printf(" voltag: <%s:%d>",
599 				       ces->ces_pvoltag.cv_volid,
600 				       ces->ces_pvoltag.cv_serial);
601 			if (avoltag)
602 				printf(" avoltag: <%s:%d>",
603 				       ces->ces_avoltag.cv_volid,
604 				       ces->ces_avoltag.cv_serial);
605 			printf("\n");
606 		}
607 
608 		free(cmd.cesr_data);
609 	}
610 
611 	return (0);
612 
613  usage:
614 	fprintf(stderr, "usage: %s %s [<element type>]\n", __progname,
615 	    cname);
616 	return (1);
617 }
618 
619 /*
620  * Check a drive unit as the source for a move or exchange
621  * operation. If the drive is not accessible, we attempt
622  * to unmount the tape in it before moving to avoid
623  * errors in "disconnected" type pickers where the drive
624  * is on a separate target from the changer.
625  */
626 static void
627 check_source_drive(int unit)
628 {
629 	struct mtop mtoffl =  { MTOFFL, 1 };
630 	struct changer_element_status_request cmd;
631 	struct changer_element_status *ces;
632 	struct changer_params data;
633 	size_t count = 0;
634 	int mtfd;
635 	char *tapedev;
636 
637 	/*
638 	 * Get params from changer.  Specifically, we need the element
639 	 * counts.
640 	 */
641 	bzero(&data, sizeof(data));
642 	if (ioctl(changer_fd, CHIOGPARAMS, &data))
643 		err(1, "%s: CHIOGPARAMS", changer_name);
644 
645 	count = data.cp_ndrives;
646 	if (unit < 0 || unit >= count)
647 		err(1, "%s: invalid drive: drive %d", changer_name, unit);
648 
649 	bzero(&cmd, sizeof(cmd));
650 	cmd.cesr_type = CHET_DT;
651 	/* Allocate storage for the status info. */
652 	cmd.cesr_data = calloc(count, sizeof(*cmd.cesr_data));
653 	if ((cmd.cesr_data) == NULL)
654 		errx(1, "can't allocate status storage");
655 
656 	if (ioctl(changer_fd, CHIOGSTATUS, &cmd)) {
657 		free(cmd.cesr_data);
658 		err(1, "%s: CHIOGSTATUS", changer_name);
659 	}
660 	ces = &(cmd.cesr_data[unit]);
661 
662 	if ((ces->ces_flags & CESTATUS_FULL) != CESTATUS_FULL)
663 		err(1, "%s: drive %d is empty!", changer_name, unit);
664 
665 	if ((ces->ces_flags & CESTATUS_ACCESS) == CESTATUS_ACCESS)
666 		return; /* changer thinks all is well - trust it */
667 
668 	/*
669 	 * Otherwise, drive is FULL, but not accessible.
670 	 * Try to make it accessible by doing an mt offline.
671 	 */
672 	tapedev = parse_tapedev(_PATH_CH_CONF, changer_name, unit);
673 	mtfd = opendev(tapedev, O_RDONLY, 0, NULL);
674 	if (mtfd == -1)
675 		err(1, "%s drive %d (%s): open", changer_name, unit, tapedev);
676 	if (ioctl(mtfd, MTIOCTOP, &mtoffl) == -1)
677 		err(1, "%s drive %d (%s): rewoffl", changer_name, unit,
678 		    tapedev);
679 	close(mtfd);
680 }
681 
682 void
683 find_voltag(char *voltag, int *type, int *unit)
684 {
685 	struct changer_element_status_request cmd;
686 	struct changer_params data;
687 	int i, chet, schet, echet, found;
688 	size_t count = 0;
689 
690 	/*
691 	 * Get params from changer.  Specifically, we need the element
692 	 * counts.
693 	 */
694 	bzero(&data, sizeof(data));
695 	if (ioctl(changer_fd, CHIOGPARAMS, &data))
696 		err(1, "%s: CHIOGPARAMS", changer_name);
697 
698 	found = 0;
699 	schet = CHET_MT;
700 	echet = CHET_DT;
701 
702 	/*
703 	 * For each type of element, iterate through each one until
704 	 * we find the correct volume id.
705 	 */
706 	for (chet = schet; chet <= echet; ++chet) {
707 		switch (chet) {
708 		case CHET_MT:
709 			count = data.cp_npickers;
710 			break;
711 		case CHET_ST:
712 			count = data.cp_nslots;
713 			break;
714 		case CHET_IE:
715 			count = data.cp_nportals;
716 			break;
717 		case CHET_DT:
718 			count = data.cp_ndrives;
719 			break;
720 		}
721 		if (count == 0 || found)
722 			continue;
723 
724 		bzero(&cmd, sizeof(cmd));
725 		cmd.cesr_type = chet;
726 		/* Allocate storage for the status info. */
727 		cmd.cesr_data = calloc(count, sizeof(*cmd.cesr_data));
728 		if ((cmd.cesr_data) == NULL)
729 			errx(1, "can't allocate status storage");
730 		cmd.cesr_flags |= CESR_VOLTAGS;
731 
732 		if (ioctl(changer_fd, CHIOGSTATUS, &cmd)) {
733 			free(cmd.cesr_data);
734 			err(1, "%s: CHIOGSTATUS", changer_name);
735 		}
736 
737 		/*
738 		 * look through each element to see if it has our desired
739 		 * volume tag.
740 		 */
741 		for (i = 0; i < count; ++i) {
742 			struct changer_element_status *ces =
743 			    &(cmd.cesr_data[i]);
744 			if ((ces->ces_flags & CESTATUS_FULL) != CESTATUS_FULL)
745 				continue; /* no tape in drive */
746 			if (strcasecmp(voltag, ces->ces_pvoltag.cv_volid)
747 			    == 0) {
748 				*type = chet;
749 				*unit = i;
750 				found = 1;
751 				free(cmd.cesr_data);
752 				return;
753 			}
754 		}
755 		free(cmd.cesr_data);
756 	}
757 	errx(1, "%s: unable to locate voltag: %s", changer_name, voltag);
758 }
759 
760 
761 static int
762 parse_element_type(char *cp)
763 {
764 	int i;
765 
766 	for (i = 0; elements[i].et_name != NULL; ++i)
767 		if (strcmp(elements[i].et_name, cp) == 0)
768 			return (elements[i].et_type);
769 
770 	errx(1, "invalid element type `%s'", cp);
771 }
772 
773 static int
774 parse_element_unit(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(char *cp)
788 {
789 	int val;
790 
791 	val = is_special(cp);
792 	if (val)
793 		return (val);
794 
795 	errx(1, "invalid modifier `%s'", cp);
796 }
797 
798 static int
799 is_special(char *cp)
800 {
801 	int i;
802 
803 	for (i = 0; specials[i].sw_name != NULL; ++i)
804 		if (strcmp(specials[i].sw_name, cp) == 0)
805 			return (specials[i].sw_value);
806 
807 	return (0);
808 }
809 
810 static char *
811 bits_to_string(int v, const char *cp)
812 {
813 	const char *np;
814 	char f, sep, *bp;
815 	static char buf[128];
816 
817 	bp = buf;
818 	bzero(buf, sizeof(buf));
819 
820 	for (sep = '<'; (f = *cp++) != 0; cp = np) {
821 		for (np = cp; *np >= ' ';)
822 			np++;
823 		if ((v & (1 << (f - 1))) == 0)
824 			continue;
825 		(void)snprintf(bp, sizeof(buf) - (bp - &buf[0]),
826 		    "%c%.*s", sep, (int)(np - cp), cp);
827 		bp += strlen(bp);
828 		sep = ',';
829 	}
830 	if (sep != '<')
831 		*bp = '>';
832 
833 	return (buf);
834 }
835 
836 static void
837 usage(void)
838 {
839 	int i;
840 
841 	fprintf(stderr, "usage: %s [-f changer] command [arg ...]\n",
842 	    __progname);
843 	fprintf(stderr, "commands:");
844 	for (i = 0; commands[i].cc_name; i++)
845 		fprintf(stderr, " %s", commands[i].cc_name);
846 	fprintf(stderr, "\n");
847 	exit(1);
848 }
849