xref: /freebsd/usr.sbin/cdcontrol/cdcontrol.c (revision 05c7a37afb48ddd5ee1bd921a5d46fe59cc70b15)
1 /*
2  * Compact Disc Control Utility by Serge V. Vakulenko <vak@cronyx.ru>.
3  * Based on the non-X based CD player by Jean-Marc Zucconi and
4  * Andrey A. Chernov.
5  *
6  * Fixed and further modified on 5-Sep-1995 by Jukka Ukkonen <jau@funet.fi>.
7  *
8  * 11-Sep-1995: Jukka A. Ukkonen <jau@funet.fi>
9  *              A couple of further fixes to my own earlier "fixes".
10  *
11  * 18-Sep-1995: Jukka A. Ukkonen <jau@funet.fi>
12  *              Added an ability to specify addresses relative to the
13  *              beginning of a track. This is in fact a variation of
14  *              doing the simple play_msf() call.
15  *
16  * 11-Oct-1995: Serge V.Vakulenko <vak@cronyx.ru>
17  *              New eject algorithm.
18  *              Some code style reformatting.
19  *
20  * $Id: cdcontrol.c,v 1.11 1996/02/09 00:22:17 ache Exp $
21  */
22 
23 #include <ctype.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <sys/file.h>
30 #include <sys/cdio.h>
31 #include <sys/ioctl.h>
32 
33 #define VERSION "2.0"
34 
35 #define ASTS_INVALID    0x00  /* Audio status byte not valid */
36 #define ASTS_PLAYING    0x11  /* Audio play operation in progress */
37 #define ASTS_PAUSED     0x12  /* Audio play operation paused */
38 #define ASTS_COMPLETED  0x13  /* Audio play operation successfully completed */
39 #define ASTS_ERROR      0x14  /* Audio play operation stopped due to error */
40 #define ASTS_VOID       0x15  /* No current audio status to return */
41 
42 #ifndef DEFAULT_CD_DRIVE
43 #  define DEFAULT_CD_DRIVE  "/dev/cd0c"
44 #endif
45 
46 #ifndef DEFAULT_CD_PARTITION
47 #  define DEFAULT_CD_PARTITION  "c"
48 #endif
49 
50 #define CMD_DEBUG       1
51 #define CMD_EJECT       2
52 #define CMD_HELP        3
53 #define CMD_INFO        4
54 #define CMD_PAUSE       5
55 #define CMD_PLAY        6
56 #define CMD_QUIT        7
57 #define CMD_RESUME      8
58 #define CMD_STOP        9
59 #define CMD_VOLUME      10
60 #define CMD_CLOSE       11
61 #define CMD_RESET       12
62 #define CMD_SET         13
63 #define CMD_STATUS      14
64 
65 struct cmdtab {
66 	int command;
67 	char *name;
68 	unsigned  min;
69 	char *args;
70 } cmdtab[] = {
71 { CMD_CLOSE,    "close",        1, "" },
72 { CMD_DEBUG,    "debug",        1, "on | off" },
73 { CMD_EJECT,    "eject",        1, "" },
74 { CMD_HELP,     "?",            1, 0 },
75 { CMD_HELP,     "help",         1, "" },
76 { CMD_INFO,     "info",         1, "" },
77 { CMD_PAUSE,    "pause",        2, "" },
78 { CMD_PLAY,     "play",         1, "min1:sec1[.fram1] [min2:sec2[.fram2]]" },
79 { CMD_PLAY,     "play",         1, "track1[.index1] [track2[.index2]]" },
80 { CMD_PLAY,     "play",         1, "tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]" },
81 { CMD_PLAY,     "play",         1, "[#block [len]]" },
82 { CMD_QUIT,     "quit",         1, "" },
83 { CMD_RESET,    "reset",        4, "" },
84 { CMD_RESUME,   "resume",       1, "" },
85 { CMD_SET,      "set",          2, "msf | lba" },
86 { CMD_STATUS,   "status",       1, "" },
87 { CMD_STOP,     "stop",         3, "" },
88 { CMD_VOLUME,   "volume",       1, "<l> <r> | left | right | mute | mono | stereo" },
89 { 0, }
90 };
91 
92 struct cd_toc_entry     toc_buffer[100];
93 
94 const char      *cdname;
95 int             fd = -1;
96 int             verbose = 1;
97 int             msf = 1;
98 
99 extern char     *__progname;
100 
101 int             setvol __P((int, int));
102 int             read_toc_entrys __P((int));
103 int             play_msf __P((int, int, int, int, int, int));
104 int             play_track __P((int, int, int, int));
105 int             get_vol __P((int *, int *));
106 int             status __P((int *, int *, int *, int *));
107 int             open_cd __P((void));
108 int             play __P((char *arg));
109 int             info __P((char *arg));
110 int             pstatus __P((char *arg));
111 char            *input __P((int *));
112 void            prtrack __P((struct cd_toc_entry *e, int lastflag));
113 void            lba2msf __P((unsigned long lba,
114                             u_char *m, u_char *s, u_char *f));
115 unsigned int    msf2lba __P((u_char m, u_char s, u_char f));
116 int             play_blocks __P((int blk, int len));
117 int             run __P((int cmd, char *arg));
118 char            *parse __P((char *buf, int *cmd));
119 
120 void help ()
121 {
122 	struct cmdtab *c;
123 	char *s, n;
124 	int i;
125 
126 	for (c=cmdtab; c->name; ++c) {
127 		if (! c->args)
128 			continue;
129 		printf("\t");
130 		for (i = c->min, s = c->name; *s; s++, i--) {
131 			if (i > 0)
132 				n = toupper(*s);
133 			else
134 				n = *s;
135 			putchar(n);
136 		}
137 		if (*c->args)
138 			printf (" %s", c->args);
139 		printf ("\n");
140 	}
141 	printf ("\n\tThe word \"play\" is not required for the play commands.\n");
142 	printf ("\tThe plain target address is taken as a synonym for play.\n");
143 }
144 
145 void usage ()
146 {
147 	printf ("Usage:\n\t%s [ -vs ] [ -f disc ] [ command args... ]\n", __progname);
148 	printf ("Options:\n");
149 	printf ("\t-v       - verbose mode\n");
150 	printf ("\t-s       - silent mode\n");
151 	printf ("\t-f disc  - a block device name such as /dev/cd0c\n");
152 	printf ("\tMUSIC_CD - shell variable with device name\n");
153 	printf ("Commands:\n");
154 	help ();
155 	exit (1);
156 }
157 
158 int main (int argc, char **argv)
159 {
160 	int cmd;
161 	char *arg;
162 
163 	cdname = getenv ("MUSIC_CD");
164 	if (! cdname)
165 		cdname = getenv ("CD_DRIVE");
166 	if (! cdname)
167 		cdname = getenv ("DISC");
168 	if (! cdname)
169 		cdname = getenv ("CDPLAY");
170 
171 	for (;;) {
172 		switch (getopt (argc, argv, "svhf:")) {
173 		case EOF:
174 			break;
175 		case 's':
176 			verbose = 0;
177 			continue;
178 		case 'v':
179 			verbose = 2;
180 			continue;
181 		case 'f':
182 			cdname = optarg;
183 			continue;
184 		case 'h':
185 		default:
186 			usage ();
187 		}
188 		break;
189 	}
190 	argc -= optind;
191 	argv += optind;
192 
193 	if (argc > 0 && ! strcasecmp (*argv, "help"))
194 		usage ();
195 
196 	if (! cdname) {
197 		cdname = DEFAULT_CD_DRIVE;
198 		fprintf (stderr,
199 			 "No CD device name specified. Defaulting to %s.\n", cdname);
200 	}
201 
202 	if (argc > 0) {
203 		char buf[80], *p;
204 		int len;
205 
206 		for (p=buf; argc-->0; ++argv) {
207 			len = strlen (*argv);
208 
209 			if (p + len >= buf + sizeof (buf) - 1)
210 				usage ();
211 
212 			if (p > buf)
213 				*p++ = ' ';
214 
215 			strcpy (p, *argv);
216 			p += len;
217 		}
218 		*p = 0;
219 		arg = parse (buf, &cmd);
220 		return (run (cmd, arg));
221 	}
222 
223 	if (verbose == 1)
224 		verbose = isatty (0);
225 
226 	if (verbose) {
227 		printf ("Compact Disc Control utility, version %s\n", VERSION);
228 		printf ("Type `?' for command list\n\n");
229 	}
230 
231 	for (;;) {
232 		arg = input (&cmd);
233 		if (run (cmd, arg) < 0) {
234 			if (verbose)
235 				perror (__progname);
236 			close (fd);
237 			fd = -1;
238 		}
239 		fflush (stdout);
240 	}
241 }
242 
243 int run (int cmd, char *arg)
244 {
245 	int l, r, rc;
246 
247 	switch (cmd) {
248 
249 	case CMD_QUIT:
250 		exit (0);
251 
252 	case CMD_INFO:
253 		if (fd < 0 && ! open_cd ())
254 			return (0);
255 
256 		return info (arg);
257 
258 	case CMD_STATUS:
259 		if (fd < 0 && ! open_cd ())
260 			return (0);
261 
262 		return pstatus (arg);
263 
264 	case CMD_PAUSE:
265 		if (fd < 0 && ! open_cd ())
266 			return (0);
267 
268 		return ioctl (fd, CDIOCPAUSE);
269 
270 	case CMD_RESUME:
271 		if (fd < 0 && ! open_cd ())
272 			return (0);
273 
274 		return ioctl (fd, CDIOCRESUME);
275 
276 	case CMD_STOP:
277 		if (fd < 0 && ! open_cd ())
278 			return (0);
279 
280 		rc = ioctl (fd, CDIOCSTOP);
281 
282 		(void) ioctl (fd, CDIOCALLOW);
283 
284 		return (rc);
285 
286 	case CMD_RESET:
287 		if (fd < 0 && ! open_cd ())
288 			return (0);
289 
290 		rc = ioctl (fd, CDIOCRESET);
291 		if (rc < 0)
292 			return rc;
293 		close(fd);
294 		fd = -1;
295 		return (0);
296 
297 	case CMD_DEBUG:
298 		if (fd < 0 && ! open_cd ())
299 			return (0);
300 
301 		if (! strcasecmp (arg, "on"))
302 			return ioctl (fd, CDIOCSETDEBUG);
303 
304 		if (! strcasecmp (arg, "off"))
305 			return ioctl (fd, CDIOCCLRDEBUG);
306 
307 		printf ("%s: Invalid command arguments\n", __progname);
308 
309 		return (0);
310 
311 	case CMD_EJECT:
312 		if (fd < 0 && ! open_cd ())
313 			return (0);
314 
315 		(void) ioctl (fd, CDIOCALLOW);
316 		rc = ioctl (fd, CDIOCEJECT);
317 		if (rc < 0)
318 			return (rc);
319 		return (0);
320 
321 	case CMD_CLOSE:
322 		if (fd < 0 && ! open_cd ())
323 			return (0);
324 
325 		(void) ioctl (fd, CDIOCALLOW);
326 		rc = ioctl (fd, CDIOCCLOSE);
327 		if (rc < 0)
328 			return (rc);
329 		close(fd);
330 		fd = -1;
331 		return (0);
332 
333 	case CMD_PLAY:
334 		if (fd < 0 && ! open_cd ())
335 			return (0);
336 
337 		while (isspace (*arg))
338 			arg++;
339 
340 		return play (arg);
341 
342 	case CMD_SET:
343 		if (! strcasecmp (arg, "msf"))
344 			msf = 1;
345 		else if (! strcasecmp (arg, "lba"))
346 			msf = 0;
347 		else
348 			printf ("%s: Invalid command arguments\n", __progname);
349 		return (0);
350 
351 	case CMD_VOLUME:
352 		if (fd < 0 && !open_cd ())
353 			return (0);
354 
355 		if (! strncasecmp (arg, "left", strlen(arg)))
356 			return ioctl (fd, CDIOCSETLEFT);
357 
358 		if (! strncasecmp (arg, "right", strlen(arg)))
359 			return ioctl (fd, CDIOCSETRIGHT);
360 
361 		if (! strncasecmp (arg, "mono", strlen(arg)))
362 			return ioctl (fd, CDIOCSETMONO);
363 
364 		if (! strncasecmp (arg, "stereo", strlen(arg)))
365 			return ioctl (fd, CDIOCSETSTERIO);
366 
367 		if (! strncasecmp (arg, "mute", strlen(arg)))
368 			return ioctl (fd, CDIOCSETMUTE);
369 
370 		if (2 != sscanf (arg, "%d %d", &l, &r)) {
371 			printf ("%s: Invalid command arguments\n", __progname);
372 			return (0);
373 		}
374 
375 		return setvol (l, r);
376 
377 	default:
378 	case CMD_HELP:
379 		help ();
380 		return (0);
381 
382 	}
383 }
384 
385 int play (char *arg)
386 {
387 	struct ioc_toc_header h;
388 	int rc, n, start, end = 0, istart = 1, iend = 1;
389 
390 	rc = ioctl (fd, CDIOREADTOCHEADER, &h);
391 
392 	if (rc < 0)
393 		return (rc);
394 
395 	n = h.ending_track - h.starting_track + 1;
396 	rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
397 
398 	if (rc < 0)
399 		return (rc);
400 
401 	if (! arg || ! *arg) {
402 		/* Play the whole disc */
403 		if (msf)
404 			return play_blocks (0, msf2lba (toc_buffer[n].addr.msf.minute,
405 							toc_buffer[n].addr.msf.second,
406 							toc_buffer[n].addr.msf.frame));
407 		else
408 			return play_blocks (0, ntohl(toc_buffer[n].addr.lba));
409 	}
410 
411 	if (strchr (arg, '#')) {
412 		/* Play block #blk [ len ] */
413 		int blk, len = 0;
414 
415 		if (2 != sscanf (arg, "#%d%d", &blk, &len) &&
416 		    1 != sscanf (arg, "#%d", &blk))
417 			goto Clean_up;
418 
419 		if (len == 0) {
420 			if (msf)
421 				len = msf2lba (toc_buffer[n].addr.msf.minute,
422 					       toc_buffer[n].addr.msf.second,
423 					       toc_buffer[n].addr.msf.frame) - blk;
424 			else
425 				len = ntohl(toc_buffer[n].addr.lba) - blk;
426 		}
427 		return play_blocks (blk, len);
428 	}
429 
430 	if (strchr (arg, ':')) {
431 		/*
432 		 * Play MSF m1:s1 [ .f1 ] [ m2:s2 [ .f2 ] ]
433 		 *
434 		 * Will now also undestand timed addresses relative
435 		 * to the beginning of a track in the form...
436 		 *
437 		 *      tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]
438 		 */
439 		unsigned tr1, tr2;
440 		unsigned m1, m2, s1, s2, f1, f2;
441 		unsigned char tm, ts, tf;
442 
443 		tr2 = m2 = s2 = f2 = f1 = 0;
444 		if (8 == sscanf (arg, "%d %d:%d.%d %d %d:%d.%d",
445 		    &tr1, &m1, &s1, &f1, &tr2, &m2, &s2, &f2))
446 			goto Play_Relative_Addresses;
447 
448 		tr2 = m2 = s2 = f2 = f1 = 0;
449 		if (7 == sscanf (arg, "%d %d:%d %d %d:%d.%d",
450 		    &tr1, &m1, &s1, &tr2, &m2, &s2, &f2))
451 			goto Play_Relative_Addresses;
452 
453 		tr2 = m2 = s2 = f2 = f1 = 0;
454 		if (7 == sscanf (arg, "%d %d:%d.%d %d %d:%d",
455 		    &tr1, &m1, &s1, &f1, &tr2, &m2, &s2))
456 			goto Play_Relative_Addresses;
457 
458 		tr2 = m2 = s2 = f2 = f1 = 0;
459 		if (7 == sscanf (arg, "%d %d:%d.%d %d:%d.%d",
460 		    &tr1, &m1, &s1, &f1, &m2, &s2, &f2))
461 			goto Play_Relative_Addresses;
462 
463 		tr2 = m2 = s2 = f2 = f1 = 0;
464 		if (6 == sscanf (arg, "%d %d:%d.%d %d:%d",
465 		    &tr1, &m1, &s1, &f1, &m2, &s2))
466 			goto Play_Relative_Addresses;
467 
468 		tr2 = m2 = s2 = f2 = f1 = 0;
469 		if (6 == sscanf (arg, "%d %d:%d %d:%d.%d",
470 		    &tr1, &m1, &s1, &m2, &s2, &f2))
471 			goto Play_Relative_Addresses;
472 
473 		tr2 = m2 = s2 = f2 = f1 = 0;
474 		if (6 == sscanf (arg, "%d %d:%d.%d %d %d",
475 		    &tr1, &m1, &s1, &f1, &tr2, &m2))
476 			goto Play_Relative_Addresses;
477 
478 		tr2 = m2 = s2 = f2 = f1 = 0;
479 		if (5 == sscanf (arg, "%d %d:%d %d:%d", &tr1, &m1, &s1, &m2, &s2))
480 			goto Play_Relative_Addresses;
481 
482 		tr2 = m2 = s2 = f2 = f1 = 0;
483 		if (5 == sscanf (arg, "%d %d:%d %d %d",
484 		    &tr1, &m1, &s1, &tr2, &m2))
485 			goto Play_Relative_Addresses;
486 
487 		tr2 = m2 = s2 = f2 = f1 = 0;
488 		if (5 == sscanf (arg, "%d %d:%d.%d %d",
489 		    &tr1, &m1, &s1, &f1, &tr2))
490 			goto Play_Relative_Addresses;
491 
492 		tr2 = m2 = s2 = f2 = f1 = 0;
493 		if (4 == sscanf (arg, "%d %d:%d %d", &tr1, &m1, &s1, &tr2))
494 			goto Play_Relative_Addresses;
495 
496 		tr2 = m2 = s2 = f2 = f1 = 0;
497 		if (4 == sscanf (arg, "%d %d:%d.%d", &tr1, &m1, &s1, &f1))
498 			goto Play_Relative_Addresses;
499 
500 		tr2 = m2 = s2 = f2 = f1 = 0;
501 		if (3 == sscanf (arg, "%d %d:%d", &tr1, &m1, &s1))
502 			goto Play_Relative_Addresses;
503 
504 		tr2 = m2 = s2 = f2 = f1 = 0;
505 		goto Try_Absolute_Timed_Addresses;
506 
507 Play_Relative_Addresses:
508 		if (! tr1)
509 			tr1 = 1;
510 		else if (tr1 > n)
511 			tr1 = n;
512 
513 		if (msf) {
514 			tm = toc_buffer[tr1].addr.msf.minute;
515 			ts = toc_buffer[tr1].addr.msf.second;
516 			tf = toc_buffer[tr1].addr.msf.frame;
517 		} else
518 			lba2msf(ntohl(toc_buffer[tr1].addr.lba),
519 				&tm, &ts, &tf);
520 		if ((m1 > tm)
521 		    || ((m1 == tm)
522 		    && ((s1 > ts)
523 		    || ((s1 == ts)
524 		    && (f1 > tf))))) {
525 			printf ("Track %d is not that long.\n", tr1);
526 			return (0);
527 		}
528 
529 		tr1--;
530 
531 		f1 += tf;
532 		if (f1 >= 75) {
533 			s1 += f1 / 75;
534 			f1 %= 75;
535 		}
536 
537 		s1 += ts;
538 		if (s1 >= 60) {
539 			m1 += s1 / 60;
540 			s1 %= 60;
541 		}
542 
543 		m1 += tm;
544 
545 		if (! tr2) {
546 			if (m2 || s2 || f2) {
547 				tr2 = tr1;
548 				f2 += f1;
549 				if (f2 >= 75) {
550 					s2 += f2 / 75;
551 					f2 %= 75;
552 				}
553 
554 				s2 += s1;
555 				if (s2 > 60) {
556 					m2 += s2 / 60;
557 					s2 %= 60;
558 				}
559 
560 				m2 += m1;
561 			} else {
562 				tr2 = n;
563 				if (msf) {
564 					m2 = toc_buffer[n].addr.msf.minute;
565 					s2 = toc_buffer[n].addr.msf.second;
566 					f2 = toc_buffer[n].addr.msf.frame;
567 				} else {
568 					lba2msf(ntohl(toc_buffer[n].addr.lba),
569 						&tm, &ts, &tf);
570 					m2 = tm;
571 					s2 = ts;
572 					f2 = tf;
573 				}
574 			}
575 		} else if (tr2 > n) {
576 			tr2 = n;
577 			m2 = s2 = f2 = 0;
578 		} else {
579 			if (m2 || s2 || f2)
580 				tr2--;
581 			if (msf) {
582 				tm = toc_buffer[tr2].addr.msf.minute;
583 				ts = toc_buffer[tr2].addr.msf.second;
584 				tf = toc_buffer[tr2].addr.msf.frame;
585 			} else
586 				lba2msf(ntohl(toc_buffer[tr2].addr.lba),
587 					&tm, &ts, &tf);
588 			f2 += tf;
589 			if (f2 >= 75) {
590 				s2 += f2 / 75;
591 				f2 %= 75;
592 			}
593 
594 			s2 += ts;
595 			if (s2 > 60) {
596 				m2 += s2 / 60;
597 				s2 %= 60;
598 			}
599 
600 			m2 += tm;
601 		}
602 
603 		if (msf) {
604 			tm = toc_buffer[n].addr.msf.minute;
605 			ts = toc_buffer[n].addr.msf.second;
606 			tf = toc_buffer[n].addr.msf.frame;
607 		} else
608 			lba2msf(ntohl(toc_buffer[n].addr.lba),
609 				&tm, &ts, &tf);
610 		if ((tr2 < n)
611 		    && ((m2 > tm)
612 		    || ((m2 == tm)
613 		    && ((s2 > ts)
614 		    || ((s2 == ts)
615 		    && (f2 > tf)))))) {
616 			printf ("The playing time of the disc is not that long.\n");
617 			return (0);
618 		}
619 		return (play_msf (m1, s1, f1, m2, s2, f2));
620 
621 Try_Absolute_Timed_Addresses:
622 		if (6 != sscanf (arg, "%d:%d.%d%d:%d.%d",
623 			&m1, &s1, &f1, &m2, &s2, &f2) &&
624 		    5 != sscanf (arg, "%d:%d.%d%d:%d", &m1, &s1, &f1, &m2, &s2) &&
625 		    5 != sscanf (arg, "%d:%d%d:%d.%d", &m1, &s1, &m2, &s2, &f2) &&
626 		    3 != sscanf (arg, "%d:%d.%d", &m1, &s1, &f1) &&
627 		    4 != sscanf (arg, "%d:%d%d:%d", &m1, &s1, &m2, &s2) &&
628 		    2 != sscanf (arg, "%d:%d", &m1, &s1))
629 			goto Clean_up;
630 
631 		if (m2 == 0) {
632 			if (msf) {
633 				m2 = toc_buffer[n].addr.msf.minute;
634 				s2 = toc_buffer[n].addr.msf.second;
635 				f2 = toc_buffer[n].addr.msf.frame;
636 			} else {
637 				lba2msf(ntohl(toc_buffer[n].addr.lba),
638 					&tm, &ts, &tf);
639 				m2 = tm;
640 				s2 = ts;
641 				f2 = tf;
642 			}
643 		}
644 		return play_msf (m1, s1, f1, m2, s2, f2);
645 	}
646 
647 	/*
648 	 * Play track trk1 [ .idx1 ] [ trk2 [ .idx2 ] ]
649 	 */
650 	if (4 != sscanf (arg, "%d.%d%d.%d", &start, &istart, &end, &iend) &&
651 	    3 != sscanf (arg, "%d.%d%d", &start, &istart, &end) &&
652 	    3 != sscanf (arg, "%d%d.%d", &start, &end, &iend) &&
653 	    2 != sscanf (arg, "%d.%d", &start, &istart) &&
654 	    2 != sscanf (arg, "%d%d", &start, &end) &&
655 	    1 != sscanf (arg, "%d", &start))
656 		goto Clean_up;
657 
658 	if (end == 0)
659 		end = n;
660 	return (play_track (start, istart, end, iend));
661 
662 Clean_up:
663 	printf ("%s: Invalid command arguments\n", __progname);
664 	return (0);
665 }
666 
667 char *strstatus (int sts)
668 {
669 	switch (sts) {
670 	case ASTS_INVALID:   return ("invalid");
671 	case ASTS_PLAYING:   return ("playing");
672 	case ASTS_PAUSED:    return ("paused");
673 	case ASTS_COMPLETED: return ("completed");
674 	case ASTS_ERROR:     return ("error");
675 	case ASTS_VOID:      return ("void");
676 	default:             return ("??");
677 	}
678 }
679 
680 int pstatus (char *arg)
681 {
682 	struct ioc_vol v;
683 	struct ioc_read_subchannel ss;
684 	struct cd_sub_channel_info data;
685 	int rc, trk, m, s, f;
686 
687 	rc = status (&trk, &m, &s, &f);
688 	if (rc >= 0)
689 		if (verbose)
690 			printf ("Audio status = %d<%s>, current track = %d, current position = %d:%02d.%02d\n",
691 				rc, strstatus (rc), trk, m, s, f);
692 		else
693 			printf ("%d %d %d:%02d.%02d\n", rc, trk, m, s, f);
694 	else
695 		printf ("No current status info available\n");
696 
697 	bzero (&ss, sizeof (ss));
698 	ss.data = &data;
699 	ss.data_len = sizeof (data);
700 	ss.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
701 	ss.data_format = CD_MEDIA_CATALOG;
702 	rc = ioctl (fd, CDIOCREADSUBCHANNEL, (char *) &ss);
703 	if (rc >= 0) {
704 		printf("Media catalog is %sactive",
705 		ss.data->what.media_catalog.mc_valid ? "": "in");
706 		if (ss.data->what.media_catalog.mc_number[0])
707 			printf(", number \"%.15s\"",
708 			       ss.data->what.media_catalog.mc_number);
709 		putchar('\n');
710 	} else
711 		printf("No media catalog info available\n");
712 
713 	rc = ioctl (fd, CDIOCGETVOL, &v);
714 	if (rc >= 0)
715 		if (verbose)
716 			printf ("Left volume = %d, right volume = %d\n",
717 				v.vol[0], v.vol[1]);
718 		else
719 			printf ("%d %d\n", v.vol[0], v.vol[1]);
720 	else
721 		printf ("No volume level info available\n");
722 	return(0);
723 }
724 
725 int info (char *arg)
726 {
727 	struct ioc_toc_header h;
728 	int rc, i, n;
729 
730 	rc = ioctl (fd, CDIOREADTOCHEADER, &h);
731 	if (rc >= 0) {
732 		if (verbose)
733 			printf ("Starting track = %d, ending track = %d, TOC size = %d bytes\n",
734 				h.starting_track, h.ending_track, h.len);
735 		else
736 			printf ("%d %d %d\n", h.starting_track,
737 				h.ending_track, h.len);
738 	} else {
739 		perror ("getting toc header");
740 		return (rc);
741 	}
742 
743 	n = h.ending_track - h.starting_track + 1;
744 	rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
745 	if (rc < 0)
746 		return (rc);
747 
748 	if (verbose) {
749 		printf ("track     start  duration   block  length   type\n");
750 		printf ("-------------------------------------------------\n");
751 	}
752 
753 	for (i = 0; i < n; i++) {
754 		printf ("%5d  ", toc_buffer[i].track);
755 		prtrack (toc_buffer + i, 0);
756 	}
757 	printf ("%5d  ", toc_buffer[n].track);
758 	prtrack (toc_buffer + n, 1);
759 	return (0);
760 }
761 
762 void lba2msf (unsigned long lba, u_char *m, u_char *s, u_char *f)
763 {
764 	lba += 150;                     /* block start offset */
765 	lba &= 0xffffff;                /* negative lbas use only 24 bits */
766 	*m = lba / (60 * 75);
767 	lba %= (60 * 75);
768 	*s = lba / 75;
769 	*f = lba % 75;
770 }
771 
772 unsigned int msf2lba (u_char m, u_char s, u_char f)
773 {
774 	return (((m * 60) + s) * 75 + f) - 150;
775 }
776 
777 void prtrack (struct cd_toc_entry *e, int lastflag)
778 {
779 	int block, next, len;
780 	u_char m, s, f;
781 
782 	if (msf) {
783 		/* Print track start */
784 		printf ("%2d:%02d.%02d  ", e->addr.msf.minute,
785 			e->addr.msf.second, e->addr.msf.frame);
786 
787 		block = msf2lba (e->addr.msf.minute, e->addr.msf.second,
788 			e->addr.msf.frame);
789 	} else {
790 		block = ntohl(e->addr.lba);
791 		lba2msf(block, &m, &s, &f);
792 		/* Print track start */
793 		printf ("%2d:%02d.%02d  ", m, s, f);
794 	}
795 	if (lastflag) {
796 		/* Last track -- print block */
797 		printf ("       -  %6d       -      -\n", block);
798 		return;
799 	}
800 
801 	if (msf)
802 		next = msf2lba (e[1].addr.msf.minute, e[1].addr.msf.second,
803 			e[1].addr.msf.frame);
804 	else
805 		next = ntohl(e[1].addr.lba);
806 	len = next - block;
807 	lba2msf (len, &m, &s, &f);
808 
809 	/* Print duration, block, length, type */
810 	printf ("%2d:%02d.%02d  %6d  %6d  %5s\n", m, s, f, block, len,
811 		(e->control & 4) ? "data" : "audio");
812 }
813 
814 int play_track (int tstart, int istart, int tend, int iend)
815 {
816 	struct ioc_play_track t;
817 
818 	t.start_track = tstart;
819 	t.start_index = istart;
820 	t.end_track = tend;
821 	t.end_index = iend;
822 
823 	return ioctl (fd, CDIOCPLAYTRACKS, &t);
824 }
825 
826 int play_blocks (int blk, int len)
827 {
828 	struct ioc_play_blocks  t;
829 
830 	t.blk = blk;
831 	t.len = len;
832 
833 	return ioctl (fd, CDIOCPLAYBLOCKS, &t);
834 }
835 
836 int setvol (int left, int right)
837 {
838 	struct ioc_vol  v;
839 
840 	v.vol[0] = left;
841 	v.vol[1] = right;
842 	v.vol[2] = 0;
843 	v.vol[3] = 0;
844 
845 	return ioctl (fd, CDIOCSETVOL, &v);
846 }
847 
848 int read_toc_entrys (int len)
849 {
850 	struct ioc_read_toc_entry t;
851 
852 	t.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
853 	t.starting_track = 0;
854 	t.data_len = len;
855 	t.data = toc_buffer;
856 
857 	return (ioctl (fd, CDIOREADTOCENTRYS, (char *) &t));
858 }
859 
860 int play_msf (int start_m, int start_s, int start_f,
861 	int end_m, int end_s, int end_f)
862 {
863 	struct ioc_play_msf     a;
864 
865 	a.start_m = start_m;
866 	a.start_s = start_s;
867 	a.start_f = start_f;
868 	a.end_m = end_m;
869 	a.end_s = end_s;
870 	a.end_f = end_f;
871 
872 	return ioctl (fd, CDIOCPLAYMSF, (char *) &a);
873 }
874 
875 int status (int *trk, int *min, int *sec, int *frame)
876 {
877 	struct ioc_read_subchannel s;
878 	struct cd_sub_channel_info data;
879 	u_char mm, ss, ff;
880 
881 	bzero (&s, sizeof (s));
882 	s.data = &data;
883 	s.data_len = sizeof (data);
884 	s.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
885 	s.data_format = CD_CURRENT_POSITION;
886 
887 	if (ioctl (fd, CDIOCREADSUBCHANNEL, (char *) &s) < 0)
888 		return -1;
889 
890 	*trk = s.data->what.position.track_number;
891 	if (msf) {
892 		*min = s.data->what.position.reladdr.msf.minute;
893 		*sec = s.data->what.position.reladdr.msf.second;
894 		*frame = s.data->what.position.reladdr.msf.frame;
895 	} else {
896 		lba2msf(ntohl(s.data->what.position.reladdr.lba),
897 			&mm, &ss, &ff);
898 		*min = mm;
899 		*sec = ss;
900 		*frame = ff;
901 	}
902 
903 	return s.data->header.audio_status;
904 }
905 
906 char *input (int *cmd)
907 {
908 	static char buf[80];
909 	char *p;
910 
911 	do {
912 		if (verbose)
913 			fprintf (stderr, "%s> ", __progname);
914 		if (! fgets (buf, sizeof (buf), stdin)) {
915 			*cmd = CMD_QUIT;
916 			fprintf (stderr, "\r\n");
917 			return (0);
918 		}
919 		p = parse (buf, cmd);
920 	} while (! p);
921 	return (p);
922 }
923 
924 char *parse (char *buf, int *cmd)
925 {
926 	struct cmdtab *c;
927 	char *p;
928 	int len;
929 
930 	for (p=buf; isspace (*p); p++)
931 		continue;
932 
933 	if (isdigit (*p) || (p[0] == '#' && isdigit (p[1]))) {
934 		*cmd = CMD_PLAY;
935 		return (p);
936 	}
937 
938 	for (buf = p; *p && ! isspace (*p); p++)
939 		continue;
940 
941 	len = p - buf;
942 	if (! len)
943 		return (0);
944 
945 	if (*p) {                       /* It must be a spacing character! */
946 		char *q;
947 
948 		*p++ = 0;
949 		for (q=p; *q && *q != '\n' && *q != '\r'; q++)
950 			continue;
951 		*q = 0;
952 	}
953 
954 	*cmd = -1;
955 	for (c=cmdtab; c->name; ++c) {
956 		/* Is it an exact match? */
957 		if (! strcasecmp (buf, c->name)) {
958   			*cmd = c->command;
959   			break;
960   		}
961 
962 		/* Try short hand forms then... */
963 		if (len >= c->min && ! strncasecmp (buf, c->name, len)) {
964 			if (*cmd != -1 && *cmd != c->command) {
965 				fprintf (stderr, "Ambiguous command\n");
966 				return (0);
967 			}
968 			*cmd = c->command;
969   		}
970 	}
971 
972 	if (*cmd == -1) {
973 		fprintf (stderr, "%s: Invalid command, enter ``help'' for commands.\n",
974 			__progname);
975 		return (0);
976 	}
977 
978 	while (isspace (*p))
979 		p++;
980 	return p;
981 }
982 
983 int open_cd ()
984 {
985 	char devbuf[80];
986 
987 	if (fd > -1)
988 		return (1);
989 
990 	if (*cdname == '/')
991 		strcpy (devbuf, cdname);
992 	else if (*cdname == 'r')
993 		sprintf (devbuf, "/dev/%s", cdname);
994 	else
995 		sprintf (devbuf, "/dev/r%s", cdname);
996 
997 	fd = open (devbuf, O_RDONLY);
998 
999 	if (fd < 0 && errno == ENOENT) {
1000 		strcat (devbuf, DEFAULT_CD_PARTITION);
1001 		fd = open (devbuf, O_RDONLY);
1002 	}
1003 
1004 	if (fd < 0) {
1005 		if (errno == ENXIO) {
1006 			/*  ENXIO has an overloaded meaning here.
1007 			 *  The original "Device not configured" should
1008 			 *  be interpreted as "No disc in drive %s". */
1009 			fprintf (stderr, "%s: No disc in drive %s.\n", __progname, devbuf);
1010 			return (0);
1011 		}
1012 		perror (devbuf);
1013 		exit (1);
1014 	}
1015 	return (1);
1016 }
1017