xref: /openbsd/bin/chio/chio.c (revision 09467b48)
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