xref: /netbsd-src/bin/chio/chio.c (revision dc306354b0b29af51801a7632f1e95265a68cd81)
1 /*	$NetBSD: chio.c,v 1.10 1998/07/28 05:31:22 mycroft Exp $	*/
2 
3 /*
4  * Copyright (c) 1996, 1998 Jason R. Thorpe <thorpej@and.com>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgements:
17  *	This product includes software developed by Jason R. Thorpe
18  *	for And Communications, http://www.and.com/
19  * 4. The name of the author may not be used to endorse or promote products
20  *    derived from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 /*
35  * Additional Copyright (c) 1997, by Matthew Jacob, for NASA/Ames Research Ctr.
36  */
37 
38 #include <sys/cdefs.h>
39 #ifndef lint
40 __COPYRIGHT(
41     "@(#) Copyright (c) 1996, 1998 Jason R. Thorpe.  All rights reserved.");
42 __RCSID("$NetBSD: chio.c,v 1.10 1998/07/28 05:31:22 mycroft Exp $");
43 #endif
44 
45 #include <sys/param.h>
46 #include <sys/ioctl.h>
47 #include <sys/chio.h>
48 #include <sys/cdio.h>	/* for ATAPI CD changer; too bad it uses a lame API */
49 #include <err.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <limits.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 
58 #include "defs.h"
59 #include "pathnames.h"
60 
61 extern	char *__progname;	/* from crt0.o */
62 
63 int	main __P((int, char *[]));
64 static	void usage __P((void));
65 static	void cleanup __P((void));
66 static	int parse_element_type __P((char *));
67 static	int parse_element_unit __P((char *));
68 static	int parse_special __P((char *));
69 static	int is_special __P((char *));
70 static	const char *bits_to_string __P((int, const char *));
71 
72 static	int do_move __P((char *, int, char **));
73 static	int do_exchange __P((char *, int, char **));
74 static	int do_position __P((char *, int, char **));
75 static	int do_params __P((char *, int, char **));
76 static	int do_getpicker __P((char *, int, char **));
77 static	int do_setpicker __P((char *, int, char **));
78 static	int do_status __P((char *, int, char **));
79 static	int do_ielem __P((char *, int, char **));
80 static	int do_cdlu __P((char *, int, char **));
81 
82 /* Valid changer element types. */
83 const struct element_type elements[] = {
84 	{ "picker",		CHET_MT },
85 	{ "slot",		CHET_ST },
86 	{ "portal",		CHET_IE },
87 	{ "drive",		CHET_DT },
88 	{ NULL,			0 },
89 };
90 
91 /* Valid commands. */
92 const struct changer_command commands[] = {
93 	{ "move",		do_move },
94 	{ "exchange",		do_exchange },
95 	{ "position",		do_position },
96 	{ "params",		do_params },
97 	{ "getpicker",		do_getpicker },
98 	{ "setpicker",		do_setpicker },
99 	{ "status",		do_status },
100 	{ "ielem", 		do_ielem },
101 	{ "cdlu",		do_cdlu },
102 	{ NULL,			0 },
103 };
104 
105 /* Valid special words. */
106 const struct special_word specials[] = {
107 	{ "inv",		SW_INVERT },
108 	{ "inv1",		SW_INVERT1 },
109 	{ "inv2",		SW_INVERT2 },
110 	{ NULL,			0 },
111 };
112 
113 static	int changer_fd;
114 static	const char *changer_name;
115 
116 int
117 main(argc, argv)
118 	int argc;
119 	char *argv[];
120 {
121 	int ch, i;
122 
123 	while ((ch = getopt(argc, argv, "f:")) != -1) {
124 		switch (ch) {
125 		case 'f':
126 			changer_name = optarg;
127 			break;
128 
129 		default:
130 			usage();
131 		}
132 	}
133 	argc -= optind;
134 	argv += optind;
135 
136 	if (argc == 0)
137 		usage();
138 
139 	/* Get the default changer if not already specified. */
140 	if (changer_name == NULL)
141 		if ((changer_name = getenv(CHANGER_ENV_VAR)) == NULL)
142 			changer_name = _PATH_CH;
143 
144 	/* Open the changer device. */
145 	if ((changer_fd = open(changer_name, O_RDWR, 0600)) == -1)
146 		err(1, "%s: open", changer_name);
147 
148 	/* Register cleanup function. */
149 	if (atexit(cleanup))
150 		err(1, "can't register cleanup function");
151 
152 	/* Find the specified command. */
153 	for (i = 0; commands[i].cc_name != NULL; ++i)
154 		if (strcmp(*argv, commands[i].cc_name) == 0)
155 			break;
156 	if (commands[i].cc_name == NULL)
157 		errx(1, "unknown command: %s", *argv);
158 
159 	/* Skip over the command name and call handler. */
160 	++argv; --argc;
161 	exit ((*commands[i].cc_handler)(commands[i].cc_name, argc, argv));
162 	/* NOTREACHED */
163 }
164 
165 static int
166 do_move(cname, argc, argv)
167 	char *cname;
168 	int argc;
169 	char **argv;
170 {
171 	struct changer_move cmd;
172 	int val;
173 
174 	/*
175 	 * On a move command, we expect the following:
176 	 *
177 	 * <from ET> <from EU> <to ET> <to EU> [inv]
178 	 *
179 	 * where ET == element type and EU == element unit.
180 	 */
181 	if (argc < 4) {
182 		warnx("%s: too few arguments", cname);
183 		goto usage;
184 	} else if (argc > 5) {
185 		warnx("%s: too many arguments", cname);
186 		goto usage;
187 	}
188 	(void) memset(&cmd, 0, sizeof(cmd));
189 
190 	/* <from ET>  */
191 	cmd.cm_fromtype = parse_element_type(*argv);
192 	++argv; --argc;
193 
194 	/* <from EU> */
195 	cmd.cm_fromunit = parse_element_unit(*argv);
196 	++argv; --argc;
197 
198 	/* <to ET> */
199 	cmd.cm_totype = parse_element_type(*argv);
200 	++argv; --argc;
201 
202 	/* <to EU> */
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 	(void) 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(cname, argc, argv)
235 	char *cname;
236 	int argc;
237 	char **argv;
238 {
239 	struct changer_exchange cmd;
240 	int val;
241 
242 	/*
243 	 * On an exchange command, we expect the following:
244 	 *
245   * <src ET> <src EU> <dst1 ET> <dst1 EU> [<dst2 ET> <dst2 EU>] [inv1] [inv2]
246 	 *
247 	 * where ET == element type and EU == element unit.
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 	(void) memset(&cmd, 0, 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 	(void) fprintf(stderr,
322 	    "usage: %s %s <src ET> <src EU> <dst1 ET> <dst1 EU>\n"
323 	    "       [<dst2 ET> <dst2 EU>] [inv1] [inv2]\n",
324 	    __progname, cname);
325 	return (1);
326 }
327 
328 static int
329 do_position(cname, argc, argv)
330 	char *cname;
331 	int argc;
332 	char **argv;
333 {
334 	struct changer_position cmd;
335 	int val;
336 
337 	/*
338 	 * On a position command, we expect the following:
339 	 *
340 	 * <to ET> <to EU> [inv]
341 	 *
342 	 * where ET == element type and EU == element unit.
343 	 */
344 	if (argc < 2) {
345 		warnx("%s: too few arguments", cname);
346 		goto usage;
347 	} else if (argc > 3) {
348 		warnx("%s: too many arguments", cname);
349 		goto usage;
350 	}
351 	(void) memset(&cmd, 0, sizeof(cmd));
352 
353 	/* <to ET>  */
354 	cmd.cp_type = parse_element_type(*argv);
355 	++argv; --argc;
356 
357 	/* <to EU> */
358 	cmd.cp_unit = parse_element_unit(*argv);
359 	++argv; --argc;
360 
361 	/* Deal with optional command modifier. */
362 	if (argc) {
363 		val = parse_special(*argv);
364 		switch (val) {
365 		case SW_INVERT:
366 			cmd.cp_flags |= CP_INVERT;
367 			break;
368 
369 		default:
370 			errx(1, "%s: inappropriate modifier `%s'",
371 			    cname, *argv);
372 			/* NOTREACHED */
373 		}
374 	}
375 
376 	/* Send command to changer. */
377 	if (ioctl(changer_fd, CHIOPOSITION, &cmd))
378 		err(1, "%s: CHIOPOSITION", changer_name);
379 
380 	return (0);
381 
382  usage:
383 	(void) fprintf(stderr, "usage: %s %s <to ET> <to EU> [inv]\n",
384 	    __progname, cname);
385 	return (1);
386 }
387 
388 /* ARGSUSED */
389 static int
390 do_params(cname, argc, argv)
391 	char *cname;
392 	int argc;
393 	char **argv;
394 {
395 	struct changer_params data;
396 
397 	/* No arguments to this command. */
398 	if (argc) {
399 		warnx("%s: no arguements expected", cname);
400 		goto usage;
401 	}
402 
403 	/* Get params from changer and display them. */
404 	(void) memset(&data, 0, sizeof(data));
405 	if (ioctl(changer_fd, CHIOGPARAMS, &data))
406 		err(1, "%s: CHIOGPARAMS", changer_name);
407 
408 	(void) printf("%s: %d slot%s, %d drive%s, %d picker%s",
409 	    changer_name,
410 	    data.cp_nslots, (data.cp_nslots > 1) ? "s" : "",
411 	    data.cp_ndrives, (data.cp_ndrives > 1) ? "s" : "",
412 	    data.cp_npickers, (data.cp_npickers > 1) ? "s" : "");
413 	if (data.cp_nportals)
414 		(void) printf(", %d portal%s", data.cp_nportals,
415 		    (data.cp_nportals > 1) ? "s" : "");
416 	(void) printf("\n%s: current picker: %d\n", changer_name,
417 	    data.cp_curpicker);
418 
419 	return (0);
420 
421  usage:
422 	(void) fprintf(stderr, "usage: %s %s\n", __progname, cname);
423 	return (1);
424 }
425 
426 /* ARGSUSED */
427 static int
428 do_getpicker(cname, argc, argv)
429 	char *cname;
430 	int argc;
431 	char **argv;
432 {
433 	int picker;
434 
435 	/* No arguments to this command. */
436 	if (argc) {
437 		warnx("%s: no arguments expected", cname);
438 		goto usage;
439 	}
440 
441 	/* Get current picker from changer and display it. */
442 	if (ioctl(changer_fd, CHIOGPICKER, &picker))
443 		err(1, "%s: CHIOGPICKER", changer_name);
444 
445 	(void) printf("%s: current picker: %d\n", changer_name, picker);
446 
447 	return (0);
448 
449  usage:
450 	(void) fprintf(stderr, "usage: %s %s\n", __progname, cname);
451 	return (1);
452 }
453 
454 static int
455 do_setpicker(cname, argc, argv)
456 	char *cname;
457 	int argc;
458 	char **argv;
459 {
460 	int picker;
461 
462 	if (argc < 1) {
463 		warnx("%s: too few arguments", cname);
464 		goto usage;
465 	} else if (argc > 1) {
466 		warnx("%s: too many arguments", cname);
467 		goto usage;
468 	}
469 
470 	picker = parse_element_unit(*argv);
471 
472 	/* Set the changer picker. */
473 	if (ioctl(changer_fd, CHIOSPICKER, &picker))
474 		err(1, "%s: CHIOSPICKER", changer_name);
475 
476 	return (0);
477 
478  usage:
479 	(void) fprintf(stderr, "usage: %s %s <picker>\n", __progname, cname);
480 	return (1);
481 }
482 
483 static int
484 do_status(cname, argc, argv)
485 	char *cname;
486 	int argc;
487 	char **argv;
488 {
489 	struct changer_element_status cmd;
490 	struct changer_params data;
491 	u_int8_t *statusp;
492 	int i, chet, schet, echet;
493 	size_t count;
494 	char *description;
495 
496 	/*
497 	 * On a status command, we expect the following:
498 	 *
499 	 * [<ET>]
500 	 *
501 	 * where ET == element type.
502 	 *
503 	 * If we get no arguments, we get the status of all
504 	 * known element types.
505 	 */
506 	if (argc > 1) {
507 		warnx("%s: too many arguments", cname);
508 		goto usage;
509 	}
510 
511 	/*
512 	 * Get params from changer.  Specifically, we need the element
513 	 * counts.
514 	 */
515 	(void) memset(&data, 0, sizeof(data));
516 	if (ioctl(changer_fd, CHIOGPARAMS, &data))
517 		err(1, "%s: CHIOGPARAMS", changer_name);
518 
519 	if (argc)
520 		schet = echet = parse_element_type(*argv);
521 	else {
522 		schet = CHET_MT;
523 		echet = CHET_DT;
524 	}
525 
526 	for (chet = schet; chet <= echet; ++chet) {
527 		switch (chet) {
528 		case CHET_MT:
529 			count = data.cp_npickers;
530 			description = "picker";
531 			break;
532 
533 		case CHET_ST:
534 			count = data.cp_nslots;
535 			description = "slot";
536 			break;
537 
538 		case CHET_IE:
539 			count = data.cp_nportals;
540 			description = "portal";
541 			break;
542 
543 		case CHET_DT:
544 			count = data.cp_ndrives;
545 			description = "drive";
546 			break;
547 
548 		default:
549 			/* To appease gcc -Wuninitialized. */
550 			count = 0;
551 			description = NULL;
552 		}
553 
554 		if (count == 0) {
555 			if (argc == 0)
556 				continue;
557 			else {
558 				(void) printf("%s: no %s elements\n",
559 				    changer_name, description);
560 				return (0);
561 			}
562 		}
563 
564 		/* Allocate storage for the status bytes. */
565 		if ((statusp = (u_int8_t *)malloc(count)) == NULL)
566 			errx(1, "can't allocate status storage");
567 
568 		(void) memset(statusp, 0, count);
569 		(void) memset(&cmd, 0, sizeof(cmd));
570 
571 		cmd.ces_type = chet;
572 		cmd.ces_data = statusp;
573 
574 		if (ioctl(changer_fd, CHIOGSTATUS, &cmd)) {
575 			free(statusp);
576 			err(1, "%s: CHIOGSTATUS", changer_name);
577 		}
578 
579 		/* Dump the status for each element of this type. */
580 		for (i = 0; i < count; ++i) {
581 			(void) printf("%s %d: %s\n", description, i,
582 			    bits_to_string(statusp[i], CESTATUS_BITS));
583 		}
584 
585 		free(statusp);
586 	}
587 
588 	return (0);
589 
590  usage:
591 	(void) fprintf(stderr, "usage: %s %s [<element type>]\n", __progname,
592 	    cname);
593 	return (1);
594 }
595 
596 /* ARGSUSED */
597 static int
598 do_ielem(cname, argc, argv)
599 	char *cname;
600 	int argc;
601 	char **argv;
602 {
603 	if (ioctl(changer_fd, CHIOIELEM, NULL))
604 		err(1, "%s: CHIOIELEM", changer_name);
605 
606 	return (0);
607 }
608 
609 static int
610 do_cdlu(cname, argc, argv)
611 	char *cname;
612 	int argc;
613 	char **argv;
614 {
615 	struct ioc_load_unload cmd;
616 	int i;
617 	static const struct special_word cdlu_subcmds[] = {
618 		{ "load",	CD_LU_LOAD },
619 		{ "unload",	CD_LU_UNLOAD },
620 		{ "abort",	CD_LU_ABORT },
621 		{ NULL,		0 },
622 	};
623 
624 	/*
625 	 * This command is a little different, since we are mostly dealing
626 	 * with ATAPI CD changers, which have a lame API (since ATAPI doesn't
627 	 * have LUNs).
628 	 *
629 	 * We have 3 sub-commands: "load", "unload", and "abort".  The
630 	 * first two take a slot number.  The latter does not.
631 	 */
632 
633 	if (argc < 1 || argc > 2)
634 		goto usage;
635 
636 	for (i = 0; cdlu_subcmds[i].sw_name != NULL; i++) {
637 		if (strcmp(argv[0], cdlu_subcmds[i].sw_name) == 0) {
638 			cmd.options = cdlu_subcmds[i].sw_value;
639 			break;
640 		}
641 	}
642 	if (cdlu_subcmds[i].sw_name == NULL)
643 		goto usage;
644 
645 	if (strcmp(argv[0], "abort") == 0)
646 		cmd.slot = 0;
647 	else
648 		cmd.slot = parse_element_unit(argv[1]);
649 
650 	/*
651 	 * XXX Should maybe do something different with the device
652 	 * XXX handling for cdlu; think about this some more.
653 	 */
654 	if (ioctl(changer_fd, CDIOCLOADUNLOAD, &cmd))
655 		err(1, "%s: CDIOCLOADUNLOAD", changer_name);
656 
657 	return (0);
658 
659  usage:
660 	(void) fprintf(stderr, "usage: %s %s load|unload <slot>\n",
661 	    __progname, cname);
662 	(void) fprintf(stderr, "       %s %s abort\n", __progname, cname);
663 	return (1);
664 }
665 
666 static int
667 parse_element_type(cp)
668 	char *cp;
669 {
670 	int i;
671 
672 	for (i = 0; elements[i].et_name != NULL; ++i)
673 		if (strcmp(elements[i].et_name, cp) == 0)
674 			return (elements[i].et_type);
675 
676 	errx(1, "invalid element type `%s'", cp);
677 	/* NOTREACHED */
678 }
679 
680 static int
681 parse_element_unit(cp)
682 	char *cp;
683 {
684 	int i;
685 	char *p;
686 
687 	i = (int)strtol(cp, &p, 10);
688 	if ((i < 0) || (*p != '\0'))
689 		errx(1, "invalid unit number `%s'", cp);
690 
691 	return (i);
692 }
693 
694 static int
695 parse_special(cp)
696 	char *cp;
697 {
698 	int val;
699 
700 	val = is_special(cp);
701 	if (val)
702 		return (val);
703 
704 	errx(1, "invalid modifier `%s'", cp);
705 	/* NOTREACHED */
706 }
707 
708 static int
709 is_special(cp)
710 	char *cp;
711 {
712 	int i;
713 
714 	for (i = 0; specials[i].sw_name != NULL; ++i)
715 		if (strcmp(specials[i].sw_name, cp) == 0)
716 			return (specials[i].sw_value);
717 
718 	return (0);
719 }
720 
721 static const char *
722 bits_to_string(v, cp)
723 	int v;
724 	const char *cp;
725 {
726 	const char *np;
727 	char f, *bp;
728 	int first;
729 	static char buf[128];
730 
731 	bp = buf;
732 	*bp++ = '<';
733 	for (first = 1; (f = *cp++) != 0; cp = np) {
734 		for (np = cp; *np >= ' ';)
735 			np++;
736 		if ((v & (1 << (f - 1))) == 0)
737 			continue;
738 		if (first)
739 			first = 0;
740 		else
741 			*bp++ = ',';
742 		(void) memcpy(bp, cp, np - cp);
743 		bp += np - cp;
744 	}
745 	*bp++ = '>';
746 	*bp = '\0';
747 
748 	return (buf);
749 }
750 
751 static void
752 cleanup()
753 {
754 	/* Simple enough... */
755 	(void)close(changer_fd);
756 }
757 
758 static void
759 usage()
760 {
761 
762 	(void) fprintf(stderr, "usage: %s command arg1 arg2 ...\n", __progname);
763 	(void) fprintf(stderr, "Examples:\n");
764 	(void) fprintf(stderr, "\tchio -f /dev/ch0 move slot 1 drive 0\n");
765 	(void) fprintf(stderr, "\tchio ielem\n");
766 	(void) fprintf(stderr, "\tchio -f /dev/ch1 status\n");
767 	(void) fprintf(stderr, "\tchio -f /dev/cd0a cdlu load 1\n");
768 	exit(1);
769 	/* NOTREACHED */
770 }
771