cdcontrol.c revision 180507
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 * 13-Dec-1999: Knut A. Syed <kas@kas.no>
21 * 		Volume-command modified.  If used with only one
22 * 		parameter it now sets both channels.  If used without
23 * 		parameters it will print volume-info.
24 * 		Version 2.0.1
25 *
26 * 27-Jun-2008  Pietro Cerutti <gahr@FreeBSD.org>
27 * 		Further enhancement to volume. Values not in range 0-255
28 * 		are now reduced to be in range. This prevents overflow in
29 * 		the uchar storing the volume (256 -> 0, -20 -> 236, ...).
30 * 		Version 2.0.2
31 *
32 */
33
34#include <sys/cdefs.h>
35__FBSDID("$FreeBSD: head/usr.sbin/cdcontrol/cdcontrol.c 180507 2008-07-14 13:22:09Z gahr $");
36
37#include <sys/cdio.h>
38#include <sys/cdrio.h>
39#include <sys/file.h>
40#include <sys/ioctl.h>
41#include <sys/param.h>
42#include <arpa/inet.h>
43#include <ctype.h>
44#include <err.h>
45#include <errno.h>
46#include <histedit.h>
47#include <limits.h>
48#include <paths.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <unistd.h>
53#include <vis.h>
54
55#define VERSION "2.0.2"
56
57#define ASTS_INVALID	0x00  /* Audio status byte not valid */
58#define ASTS_PLAYING	0x11  /* Audio play operation in progress */
59#define ASTS_PAUSED	0x12  /* Audio play operation paused */
60#define ASTS_COMPLETED	0x13  /* Audio play operation successfully completed */
61#define ASTS_ERROR	0x14  /* Audio play operation stopped due to error */
62#define ASTS_VOID	0x15  /* No current audio status to return */
63
64#ifdef DEFAULT_CD_DRIVE
65#  error "Setting DEFAULT_CD_DRIVE is no longer supported"
66#endif
67
68#define CMD_DEBUG	1
69#define CMD_EJECT	2
70#define CMD_HELP	3
71#define CMD_INFO	4
72#define CMD_PAUSE	5
73#define CMD_PLAY	6
74#define CMD_QUIT	7
75#define CMD_RESUME	8
76#define CMD_STOP	9
77#define CMD_VOLUME	10
78#define CMD_CLOSE	11
79#define CMD_RESET	12
80#define CMD_SET		13
81#define CMD_STATUS	14
82#define CMD_CDID	15
83#define CMD_NEXT	16
84#define CMD_PREVIOUS	17
85#define CMD_SPEED	18
86#define STATUS_AUDIO	0x1
87#define STATUS_MEDIA	0x2
88#define STATUS_VOLUME	0x4
89
90struct cmdtab {
91	int command;
92	const char *name;
93	unsigned min;
94	const char *args;
95} cmdtab[] = {
96{ CMD_CLOSE,	"close",	1, "" },
97{ CMD_DEBUG,	"debug",	1, "on | off" },
98{ CMD_EJECT,	"eject",	1, "" },
99{ CMD_HELP,	"?",		1, 0 },
100{ CMD_HELP,	"help",		1, "" },
101{ CMD_INFO,	"info",		1, "" },
102{ CMD_NEXT,	"next",		1, "" },
103{ CMD_PAUSE,	"pause",	2, "" },
104{ CMD_PLAY,	"play",		1, "min1:sec1[.fram1] [min2:sec2[.fram2]]" },
105{ CMD_PLAY,	"play",		1, "track1[.index1] [track2[.index2]]" },
106{ CMD_PLAY,	"play",		1, "tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]" },
107{ CMD_PLAY,	"play",		1, "[#block [len]]" },
108{ CMD_PREVIOUS,	"previous",	2, "" },
109{ CMD_QUIT,	"quit",		1, "" },
110{ CMD_RESET,	"reset",	4, "" },
111{ CMD_RESUME,	"resume",	1, "" },
112{ CMD_SET,	"set",		2, "msf | lba" },
113{ CMD_STATUS,	"status",	1, "[audio | media | volume]" },
114{ CMD_STOP,	"stop",		3, "" },
115{ CMD_VOLUME,	"volume",	1,
116      "<l&r> <l> <r> | left | right | mute | mono | stereo" },
117{ CMD_CDID,	"cdid",		2, "" },
118{ CMD_SPEED,	"speed",	2, "speed" },
119{ 0,		NULL,		0, NULL }
120};
121
122struct cd_toc_entry	toc_buffer[100];
123
124const char	*cdname;
125int		fd = -1;
126int		verbose = 1;
127int		msf = 1;
128
129int		 setvol(int, int);
130int		 read_toc_entrys(int);
131int		 play_msf(int, int, int, int, int, int);
132int		 play_track(int, int, int, int);
133int		 get_vol(int *, int *);
134int		 status(int *, int *, int *, int *);
135int		 open_cd(void);
136int		 next_prev(char *arg, int);
137int		 play(char *arg);
138int		 info(char *arg);
139int		 cdid(void);
140int		 pstatus(char *arg);
141char		*input(int *);
142void		 prtrack(struct cd_toc_entry *e, int lastflag);
143void		 lba2msf(unsigned long lba, u_char *m, u_char *s, u_char *f);
144unsigned int	 msf2lba(u_char m, u_char s, u_char f);
145int		 play_blocks(int blk, int len);
146int		 run(int cmd, char *arg);
147char		*parse(char *buf, int *cmd);
148void		 help(void);
149void		 usage(void);
150char		*use_cdrom_instead(const char *);
151__const char	*strstatus(int);
152static u_int	 dbprog_discid(void);
153__const char	*cdcontrol_prompt(void);
154
155void help ()
156{
157	struct cmdtab *c;
158	const char *s;
159	char n;
160	int i;
161
162	for (c=cmdtab; c->name; ++c) {
163		if (! c->args)
164			continue;
165		printf("\t");
166		for (i = c->min, s = c->name; *s; s++, i--) {
167			if (i > 0)
168				n = toupper(*s);
169			else
170				n = *s;
171			putchar(n);
172		}
173		if (*c->args)
174			printf (" %s", c->args);
175		printf ("\n");
176	}
177	printf ("\n\tThe word \"play\" is not required for the play commands.\n");
178	printf ("\tThe plain target address is taken as a synonym for play.\n");
179}
180
181void usage ()
182{
183	fprintf (stderr, "usage: cdcontrol [-sv] [-f device] [command ...]\n");
184	exit (1);
185}
186
187char *use_cdrom_instead(const char *old_envvar)
188{
189	char *device;
190
191	device = getenv(old_envvar);
192	if (device)
193		warnx("%s environment variable deprecated, "
194		    "please use CDROM in the future.", old_envvar);
195	return device;
196}
197
198
199int main (int argc, char **argv)
200{
201	int cmd;
202	char *arg;
203
204	for (;;) {
205		switch (getopt (argc, argv, "svhf:")) {
206		case -1:
207			break;
208		case 's':
209			verbose = 0;
210			continue;
211		case 'v':
212			verbose = 2;
213			continue;
214		case 'f':
215			cdname = optarg;
216			continue;
217		case 'h':
218		default:
219			usage ();
220		}
221		break;
222	}
223	argc -= optind;
224	argv += optind;
225
226	if (argc > 0 && ! strcasecmp (*argv, "help"))
227		usage ();
228
229	if (! cdname) {
230		cdname = getenv("CDROM");
231	}
232
233	if (! cdname)
234		cdname = use_cdrom_instead("MUSIC_CD");
235	if (! cdname)
236		cdname = use_cdrom_instead("CD_DRIVE");
237	if (! cdname)
238		cdname = use_cdrom_instead("DISC");
239	if (! cdname)
240		cdname = use_cdrom_instead("CDPLAY");
241
242	if (argc > 0) {
243		char buf[80], *p;
244		int len;
245
246		for (p=buf; argc-->0; ++argv) {
247			len = strlen (*argv);
248
249			if (p + len >= buf + sizeof (buf) - 1)
250				usage ();
251
252			if (p > buf)
253				*p++ = ' ';
254
255			strcpy (p, *argv);
256			p += len;
257		}
258		*p = 0;
259		arg = parse (buf, &cmd);
260		return (run (cmd, arg));
261	}
262
263	if (verbose == 1)
264		verbose = isatty (0);
265
266	if (verbose) {
267		printf ("Compact Disc Control utility, version %s\n", VERSION);
268		printf ("Type `?' for command list\n\n");
269	}
270
271	for (;;) {
272		arg = input (&cmd);
273		if (run (cmd, arg) < 0) {
274			if (verbose)
275				warn(NULL);
276			close (fd);
277			fd = -1;
278		}
279		fflush (stdout);
280	}
281}
282
283int run (int cmd, char *arg)
284{
285	long speed;
286	int l, r, rc, count;
287
288	switch (cmd) {
289
290	case CMD_QUIT:
291		exit (0);
292
293	case CMD_INFO:
294		if (fd < 0 && ! open_cd ())
295			return (0);
296
297		return info (arg);
298
299	case CMD_CDID:
300		if (fd < 0 && ! open_cd ())
301			return (0);
302
303		return cdid ();
304
305	case CMD_STATUS:
306		if (fd < 0 && ! open_cd ())
307			return (0);
308
309		return pstatus (arg);
310
311	case CMD_NEXT:
312	case CMD_PREVIOUS:
313		if (fd < 0 && ! open_cd ())
314			return (0);
315
316		while (isspace (*arg))
317			arg++;
318
319		return next_prev (arg, cmd);
320
321	case CMD_PAUSE:
322		if (fd < 0 && ! open_cd ())
323			return (0);
324
325		return ioctl (fd, CDIOCPAUSE);
326
327	case CMD_RESUME:
328		if (fd < 0 && ! open_cd ())
329			return (0);
330
331		return ioctl (fd, CDIOCRESUME);
332
333	case CMD_STOP:
334		if (fd < 0 && ! open_cd ())
335			return (0);
336
337		rc = ioctl (fd, CDIOCSTOP);
338
339		(void) ioctl (fd, CDIOCALLOW);
340
341		return (rc);
342
343	case CMD_RESET:
344		if (fd < 0 && ! open_cd ())
345			return (0);
346
347		rc = ioctl (fd, CDIOCRESET);
348		if (rc < 0)
349			return rc;
350		close(fd);
351		fd = -1;
352		return (0);
353
354	case CMD_DEBUG:
355		if (fd < 0 && ! open_cd ())
356			return (0);
357
358		if (! strcasecmp (arg, "on"))
359			return ioctl (fd, CDIOCSETDEBUG);
360
361		if (! strcasecmp (arg, "off"))
362			return ioctl (fd, CDIOCCLRDEBUG);
363
364		warnx("invalid command arguments");
365
366		return (0);
367
368	case CMD_EJECT:
369		if (fd < 0 && ! open_cd ())
370			return (0);
371
372		(void) ioctl (fd, CDIOCALLOW);
373		rc = ioctl (fd, CDIOCEJECT);
374		if (rc < 0)
375			return (rc);
376		return (0);
377
378	case CMD_CLOSE:
379		if (fd < 0 && ! open_cd ())
380			return (0);
381
382		(void) ioctl (fd, CDIOCALLOW);
383		rc = ioctl (fd, CDIOCCLOSE);
384		if (rc < 0)
385			return (rc);
386		close(fd);
387		fd = -1;
388		return (0);
389
390	case CMD_PLAY:
391		if (fd < 0 && ! open_cd ())
392			return (0);
393
394		while (isspace (*arg))
395			arg++;
396
397		return play (arg);
398
399	case CMD_SET:
400		if (! strcasecmp (arg, "msf"))
401			msf = 1;
402		else if (! strcasecmp (arg, "lba"))
403			msf = 0;
404		else
405			warnx("invalid command arguments");
406		return (0);
407
408	case CMD_VOLUME:
409		if (fd < 0 && !open_cd ())
410			return (0);
411
412		if (! strlen (arg))
413		    	return pstatus ("volume");
414
415		if (! strncasecmp (arg, "left", strlen(arg)))
416			return ioctl (fd, CDIOCSETLEFT);
417
418		if (! strncasecmp (arg, "right", strlen(arg)))
419			return ioctl (fd, CDIOCSETRIGHT);
420
421		if (! strncasecmp (arg, "mono", strlen(arg)))
422			return ioctl (fd, CDIOCSETMONO);
423
424		if (! strncasecmp (arg, "stereo", strlen(arg)))
425			return ioctl (fd, CDIOCSETSTERIO);
426
427		if (! strncasecmp (arg, "mute", strlen(arg)))
428			return ioctl (fd, CDIOCSETMUTE);
429
430		count = sscanf (arg, "%d %d", &l, &r);
431		if (count == 1)
432		    return setvol (l, l);
433		if (count == 2)
434		    return setvol (l, r);
435		warnx("invalid command arguments");
436		return (0);
437
438	case CMD_SPEED:
439		if (fd < 0 && ! open_cd ())
440			return (0);
441
442		errno = 0;
443		if (strcasecmp("max", arg) == 0)
444			speed = CDR_MAX_SPEED;
445		else
446			speed = strtol(arg, NULL, 10) * 177;
447		if (speed <= 0 || speed > INT_MAX) {
448			warnx("invalid command arguments %s", arg);
449			return (0);
450		}
451		return ioctl(fd, CDRIOCREADSPEED, &speed);
452
453	default:
454	case CMD_HELP:
455		help ();
456		return (0);
457
458	}
459}
460
461int play (char *arg)
462{
463	struct ioc_toc_header h;
464	unsigned int n;
465	int rc, start, end = 0, istart = 1, iend = 1;
466
467	rc = ioctl (fd, CDIOREADTOCHEADER, &h);
468
469	if (rc < 0)
470		return (rc);
471
472	n = h.ending_track - h.starting_track + 1;
473	rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
474
475	if (rc < 0)
476		return (rc);
477
478	if (! arg || ! *arg) {
479		/* Play the whole disc */
480		if (msf)
481			return play_blocks (0, msf2lba (toc_buffer[n].addr.msf.minute,
482							toc_buffer[n].addr.msf.second,
483							toc_buffer[n].addr.msf.frame));
484		else
485			return play_blocks (0, ntohl(toc_buffer[n].addr.lba));
486	}
487
488	if (strchr (arg, '#')) {
489		/* Play block #blk [ len ] */
490		int blk, len = 0;
491
492		if (2 != sscanf (arg, "#%d%d", &blk, &len) &&
493		    1 != sscanf (arg, "#%d", &blk))
494			goto Clean_up;
495
496		if (len == 0) {
497			if (msf)
498				len = msf2lba (toc_buffer[n].addr.msf.minute,
499					       toc_buffer[n].addr.msf.second,
500					       toc_buffer[n].addr.msf.frame) - blk;
501			else
502				len = ntohl(toc_buffer[n].addr.lba) - blk;
503		}
504		return play_blocks (blk, len);
505	}
506
507	if (strchr (arg, ':')) {
508		/*
509		 * Play MSF m1:s1 [ .f1 ] [ m2:s2 [ .f2 ] ]
510		 *
511		 * Will now also undestand timed addresses relative
512		 * to the beginning of a track in the form...
513		 *
514		 *      tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]
515		 */
516		unsigned tr1, tr2;
517		unsigned m1, m2, s1, s2, f1, f2;
518		unsigned char tm, ts, tf;
519
520		tr2 = m2 = s2 = f2 = f1 = 0;
521		if (8 == sscanf (arg, "%d %d:%d.%d %d %d:%d.%d",
522		    &tr1, &m1, &s1, &f1, &tr2, &m2, &s2, &f2))
523			goto Play_Relative_Addresses;
524
525		tr2 = m2 = s2 = f2 = f1 = 0;
526		if (7 == sscanf (arg, "%d %d:%d %d %d:%d.%d",
527		    &tr1, &m1, &s1, &tr2, &m2, &s2, &f2))
528			goto Play_Relative_Addresses;
529
530		tr2 = m2 = s2 = f2 = f1 = 0;
531		if (7 == sscanf (arg, "%d %d:%d.%d %d %d:%d",
532		    &tr1, &m1, &s1, &f1, &tr2, &m2, &s2))
533			goto Play_Relative_Addresses;
534
535		tr2 = m2 = s2 = f2 = f1 = 0;
536		if (7 == sscanf (arg, "%d %d:%d.%d %d:%d.%d",
537		    &tr1, &m1, &s1, &f1, &m2, &s2, &f2))
538			goto Play_Relative_Addresses;
539
540		tr2 = m2 = s2 = f2 = f1 = 0;
541		if (6 == sscanf (arg, "%d %d:%d.%d %d:%d",
542		    &tr1, &m1, &s1, &f1, &m2, &s2))
543			goto Play_Relative_Addresses;
544
545		tr2 = m2 = s2 = f2 = f1 = 0;
546		if (6 == sscanf (arg, "%d %d:%d %d:%d.%d",
547		    &tr1, &m1, &s1, &m2, &s2, &f2))
548			goto Play_Relative_Addresses;
549
550		tr2 = m2 = s2 = f2 = f1 = 0;
551		if (6 == sscanf (arg, "%d %d:%d.%d %d %d",
552		    &tr1, &m1, &s1, &f1, &tr2, &m2))
553			goto Play_Relative_Addresses;
554
555		tr2 = m2 = s2 = f2 = f1 = 0;
556		if (5 == sscanf (arg, "%d %d:%d %d:%d", &tr1, &m1, &s1, &m2, &s2))
557			goto Play_Relative_Addresses;
558
559		tr2 = m2 = s2 = f2 = f1 = 0;
560		if (5 == sscanf (arg, "%d %d:%d %d %d",
561		    &tr1, &m1, &s1, &tr2, &m2))
562			goto Play_Relative_Addresses;
563
564		tr2 = m2 = s2 = f2 = f1 = 0;
565		if (5 == sscanf (arg, "%d %d:%d.%d %d",
566		    &tr1, &m1, &s1, &f1, &tr2))
567			goto Play_Relative_Addresses;
568
569		tr2 = m2 = s2 = f2 = f1 = 0;
570		if (4 == sscanf (arg, "%d %d:%d %d", &tr1, &m1, &s1, &tr2))
571			goto Play_Relative_Addresses;
572
573		tr2 = m2 = s2 = f2 = f1 = 0;
574		if (4 == sscanf (arg, "%d %d:%d.%d", &tr1, &m1, &s1, &f1))
575			goto Play_Relative_Addresses;
576
577		tr2 = m2 = s2 = f2 = f1 = 0;
578		if (3 == sscanf (arg, "%d %d:%d", &tr1, &m1, &s1))
579			goto Play_Relative_Addresses;
580
581		tr2 = m2 = s2 = f2 = f1 = 0;
582		goto Try_Absolute_Timed_Addresses;
583
584Play_Relative_Addresses:
585		if (! tr1)
586			tr1 = 1;
587		else if (tr1 > n)
588			tr1 = n;
589
590		tr1--;
591
592		if (msf) {
593			tm = toc_buffer[tr1].addr.msf.minute;
594			ts = toc_buffer[tr1].addr.msf.second;
595			tf = toc_buffer[tr1].addr.msf.frame;
596		} else
597			lba2msf(ntohl(toc_buffer[tr1].addr.lba),
598				&tm, &ts, &tf);
599		if ((m1 > tm)
600		    || ((m1 == tm)
601		    && ((s1 > ts)
602		    || ((s1 == ts)
603		    && (f1 > tf))))) {
604			printf ("Track %d is not that long.\n", tr1);
605			return (0);
606		}
607
608		f1 += tf;
609		if (f1 >= 75) {
610			s1 += f1 / 75;
611			f1 %= 75;
612		}
613
614		s1 += ts;
615		if (s1 >= 60) {
616			m1 += s1 / 60;
617			s1 %= 60;
618		}
619
620		m1 += tm;
621
622		if (! tr2) {
623			if (m2 || s2 || f2) {
624				tr2 = tr1;
625				f2 += f1;
626				if (f2 >= 75) {
627					s2 += f2 / 75;
628					f2 %= 75;
629				}
630
631				s2 += s1;
632				if (s2 > 60) {
633					m2 += s2 / 60;
634					s2 %= 60;
635				}
636
637				m2 += m1;
638			} else {
639				tr2 = n;
640				if (msf) {
641					m2 = toc_buffer[n].addr.msf.minute;
642					s2 = toc_buffer[n].addr.msf.second;
643					f2 = toc_buffer[n].addr.msf.frame;
644				} else {
645					lba2msf(ntohl(toc_buffer[n].addr.lba),
646						&tm, &ts, &tf);
647					m2 = tm;
648					s2 = ts;
649					f2 = tf;
650				}
651			}
652		} else if (tr2 > n) {
653			tr2 = n;
654			m2 = s2 = f2 = 0;
655		} else {
656			if (m2 || s2 || f2)
657				tr2--;
658			if (msf) {
659				tm = toc_buffer[tr2].addr.msf.minute;
660				ts = toc_buffer[tr2].addr.msf.second;
661				tf = toc_buffer[tr2].addr.msf.frame;
662			} else
663				lba2msf(ntohl(toc_buffer[tr2].addr.lba),
664					&tm, &ts, &tf);
665			f2 += tf;
666			if (f2 >= 75) {
667				s2 += f2 / 75;
668				f2 %= 75;
669			}
670
671			s2 += ts;
672			if (s2 > 60) {
673				m2 += s2 / 60;
674				s2 %= 60;
675			}
676
677			m2 += tm;
678		}
679
680		if (msf) {
681			tm = toc_buffer[n].addr.msf.minute;
682			ts = toc_buffer[n].addr.msf.second;
683			tf = toc_buffer[n].addr.msf.frame;
684		} else
685			lba2msf(ntohl(toc_buffer[n].addr.lba),
686				&tm, &ts, &tf);
687		if ((tr2 < n)
688		    && ((m2 > tm)
689		    || ((m2 == tm)
690		    && ((s2 > ts)
691		    || ((s2 == ts)
692		    && (f2 > tf)))))) {
693			printf ("The playing time of the disc is not that long.\n");
694			return (0);
695		}
696		return (play_msf (m1, s1, f1, m2, s2, f2));
697
698Try_Absolute_Timed_Addresses:
699		if (6 != sscanf (arg, "%d:%d.%d%d:%d.%d",
700			&m1, &s1, &f1, &m2, &s2, &f2) &&
701		    5 != sscanf (arg, "%d:%d.%d%d:%d", &m1, &s1, &f1, &m2, &s2) &&
702		    5 != sscanf (arg, "%d:%d%d:%d.%d", &m1, &s1, &m2, &s2, &f2) &&
703		    3 != sscanf (arg, "%d:%d.%d", &m1, &s1, &f1) &&
704		    4 != sscanf (arg, "%d:%d%d:%d", &m1, &s1, &m2, &s2) &&
705		    2 != sscanf (arg, "%d:%d", &m1, &s1))
706			goto Clean_up;
707
708		if (m2 == 0) {
709			if (msf) {
710				m2 = toc_buffer[n].addr.msf.minute;
711				s2 = toc_buffer[n].addr.msf.second;
712				f2 = toc_buffer[n].addr.msf.frame;
713			} else {
714				lba2msf(ntohl(toc_buffer[n].addr.lba),
715					&tm, &ts, &tf);
716				m2 = tm;
717				s2 = ts;
718				f2 = tf;
719			}
720		}
721		return play_msf (m1, s1, f1, m2, s2, f2);
722	}
723
724	/*
725	 * Play track trk1 [ .idx1 ] [ trk2 [ .idx2 ] ]
726	 */
727	if (4 != sscanf (arg, "%d.%d%d.%d", &start, &istart, &end, &iend) &&
728	    3 != sscanf (arg, "%d.%d%d", &start, &istart, &end) &&
729	    3 != sscanf (arg, "%d%d.%d", &start, &end, &iend) &&
730	    2 != sscanf (arg, "%d.%d", &start, &istart) &&
731	    2 != sscanf (arg, "%d%d", &start, &end) &&
732	    1 != sscanf (arg, "%d", &start))
733		goto Clean_up;
734
735	if (end == 0)
736		end = n;
737	return (play_track (start, istart, end, iend));
738
739Clean_up:
740	warnx("invalid command arguments");
741	return (0);
742}
743
744int next_prev (char *arg, int cmd)
745{
746	struct ioc_toc_header h;
747	int dir, junk, n, off, rc, trk;
748
749	dir = (cmd == CMD_NEXT) ? 1 : -1;
750	rc = ioctl (fd, CDIOREADTOCHEADER, &h);
751	if (rc < 0)
752		return (rc);
753
754	n = h.ending_track - h.starting_track + 1;
755	rc = status (&trk, &junk, &junk, &junk);
756	if (rc < 0)
757		return (-1);
758
759	if (arg && *arg) {
760		if (sscanf (arg, "%u", &off) != 1) {
761		    warnx("invalid command argument");
762		    return (0);
763		} else
764		    trk += off * dir;
765	} else
766		trk += dir;
767
768	if (trk > h.ending_track)
769		trk = 1;
770
771	return (play_track (trk, 1, n, 1));
772}
773
774const char *strstatus (int sts)
775{
776	switch (sts) {
777	case ASTS_INVALID:	return ("invalid");
778	case ASTS_PLAYING:	return ("playing");
779	case ASTS_PAUSED:	return ("paused");
780	case ASTS_COMPLETED:	return ("completed");
781	case ASTS_ERROR:	return ("error");
782	case ASTS_VOID:		return ("void");
783	default:		return ("??");
784	}
785}
786
787int pstatus (char *arg)
788{
789	struct ioc_vol v;
790	struct ioc_read_subchannel ss;
791	struct cd_sub_channel_info data;
792	int rc, trk, m, s, f;
793	int what = 0;
794	char *p, vmcn[(4 * 15) + 1];
795
796	while ((p = strtok(arg, " \t"))) {
797	    arg = 0;
798	    if (!strncasecmp(p, "audio", strlen(p)))
799		what |= STATUS_AUDIO;
800	    else if (!strncasecmp(p, "media", strlen(p)))
801		what |= STATUS_MEDIA;
802	    else if (!strncasecmp(p, "volume", strlen(p)))
803		what |= STATUS_VOLUME;
804	    else {
805		warnx("invalid command arguments");
806		return 0;
807	    }
808	}
809	if (!what)
810	    what = STATUS_AUDIO|STATUS_MEDIA|STATUS_VOLUME;
811	if (what & STATUS_AUDIO) {
812	    rc = status (&trk, &m, &s, &f);
813	    if (rc >= 0)
814		if (verbose)
815		    printf ("Audio status = %d<%s>, current track = %d, current position = %d:%02d.%02d\n",
816			    rc, strstatus (rc), trk, m, s, f);
817		else
818		    printf ("%d %d %d:%02d.%02d\n", rc, trk, m, s, f);
819	    else
820		printf ("No current status info available\n");
821	}
822	if (what & STATUS_MEDIA) {
823	    bzero (&ss, sizeof (ss));
824	    ss.data = &data;
825	    ss.data_len = sizeof (data);
826	    ss.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
827	    ss.data_format = CD_MEDIA_CATALOG;
828	    rc = ioctl (fd, CDIOCREADSUBCHANNEL, (char *) &ss);
829	    if (rc >= 0) {
830		printf("Media catalog is %sactive",
831		    ss.data->what.media_catalog.mc_valid ? "": "in");
832		if (ss.data->what.media_catalog.mc_valid &&
833		    ss.data->what.media_catalog.mc_number[0])
834		{
835		    strvisx (vmcn, ss.data->what.media_catalog.mc_number,
836			    (sizeof (vmcn) - 1) / 4, VIS_OCTAL | VIS_NL);
837		    printf(", number \"%.*s\"", (int)sizeof (vmcn), vmcn);
838		}
839		putchar('\n');
840	    } else
841		printf("No media catalog info available\n");
842	}
843	if (what & STATUS_VOLUME) {
844	    rc = ioctl (fd, CDIOCGETVOL, &v);
845	    if (rc >= 0)
846		if (verbose)
847		    printf ("Left volume = %d, right volume = %d\n",
848			    v.vol[0], v.vol[1]);
849		else
850		    printf ("%d %d\n", v.vol[0], v.vol[1]);
851	    else
852		printf ("No volume level info available\n");
853	}
854	return(0);
855}
856
857/*
858 * dbprog_sum
859 *	Convert an integer to its text string representation, and
860 *	compute its checksum.  Used by dbprog_discid to derive the
861 *	disc ID.
862 *
863 * Args:
864 *	n - The integer value.
865 *
866 * Return:
867 *	The integer checksum.
868 */
869static int
870dbprog_sum(int n)
871{
872	char	buf[12],
873		*p;
874	int	ret = 0;
875
876	/* For backward compatibility this algorithm must not change */
877	sprintf(buf, "%u", n);
878	for (p = buf; *p != '\0'; p++)
879		ret += (*p - '0');
880
881	return(ret);
882}
883
884
885/*
886 * dbprog_discid
887 *	Compute a magic disc ID based on the number of tracks,
888 *	the length of each track, and a checksum of the string
889 *	that represents the offset of each track.
890 *
891 * Args:
892 *	s - Pointer to the curstat_t structure.
893 *
894 * Return:
895 *	The integer disc ID.
896 */
897static u_int
898dbprog_discid()
899{
900	struct	ioc_toc_header h;
901	int	rc;
902	int	i, ntr,
903		t = 0,
904		n = 0;
905
906	rc = ioctl (fd, CDIOREADTOCHEADER, &h);
907	if (rc < 0)
908		return 0;
909	ntr = h.ending_track - h.starting_track + 1;
910	i = msf;
911	msf = 1;
912	rc = read_toc_entrys ((ntr + 1) * sizeof (struct cd_toc_entry));
913	msf = i;
914	if (rc < 0)
915		return 0;
916	/* For backward compatibility this algorithm must not change */
917	for (i = 0; i < ntr; i++) {
918#define TC_MM(a) toc_buffer[a].addr.msf.minute
919#define TC_SS(a) toc_buffer[a].addr.msf.second
920		n += dbprog_sum((TC_MM(i) * 60) + TC_SS(i));
921
922		t += ((TC_MM(i+1) * 60) + TC_SS(i+1)) -
923		    ((TC_MM(i) * 60) + TC_SS(i));
924	}
925
926	return((n % 0xff) << 24 | t << 8 | ntr);
927}
928
929int cdid ()
930{
931	u_int	id;
932
933	id = dbprog_discid();
934	if (id)
935	{
936		if (verbose)
937			printf ("CDID=");
938		printf ("%08x\n",id);
939	}
940	return id ? 0 : 1;
941}
942
943int info (char *arg __unused)
944{
945	struct ioc_toc_header h;
946	int rc, i, n;
947
948	rc = ioctl (fd, CDIOREADTOCHEADER, &h);
949	if (rc >= 0) {
950		if (verbose)
951			printf ("Starting track = %d, ending track = %d, TOC size = %d bytes\n",
952				h.starting_track, h.ending_track, h.len);
953		else
954			printf ("%d %d %d\n", h.starting_track,
955				h.ending_track, h.len);
956	} else {
957		warn("getting toc header");
958		return (rc);
959	}
960
961	n = h.ending_track - h.starting_track + 1;
962	rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
963	if (rc < 0)
964		return (rc);
965
966	if (verbose) {
967		printf ("track     start  duration   block  length   type\n");
968		printf ("-------------------------------------------------\n");
969	}
970
971	for (i = 0; i < n; i++) {
972		printf ("%5d  ", toc_buffer[i].track);
973		prtrack (toc_buffer + i, 0);
974	}
975	printf ("%5d  ", toc_buffer[n].track);
976	prtrack (toc_buffer + n, 1);
977	return (0);
978}
979
980void lba2msf (unsigned long lba, u_char *m, u_char *s, u_char *f)
981{
982	lba += 150;			/* block start offset */
983	lba &= 0xffffff;		/* negative lbas use only 24 bits */
984	*m = lba / (60 * 75);
985	lba %= (60 * 75);
986	*s = lba / 75;
987	*f = lba % 75;
988}
989
990unsigned int msf2lba (u_char m, u_char s, u_char f)
991{
992	return (((m * 60) + s) * 75 + f) - 150;
993}
994
995void prtrack (struct cd_toc_entry *e, int lastflag)
996{
997	int block, next, len;
998	u_char m, s, f;
999
1000	if (msf) {
1001		/* Print track start */
1002		printf ("%2d:%02d.%02d  ", e->addr.msf.minute,
1003			e->addr.msf.second, e->addr.msf.frame);
1004
1005		block = msf2lba (e->addr.msf.minute, e->addr.msf.second,
1006			e->addr.msf.frame);
1007	} else {
1008		block = ntohl(e->addr.lba);
1009		lba2msf(block, &m, &s, &f);
1010		/* Print track start */
1011		printf ("%2d:%02d.%02d  ", m, s, f);
1012	}
1013	if (lastflag) {
1014		/* Last track -- print block */
1015		printf ("       -  %6d       -      -\n", block);
1016		return;
1017	}
1018
1019	if (msf)
1020		next = msf2lba (e[1].addr.msf.minute, e[1].addr.msf.second,
1021			e[1].addr.msf.frame);
1022	else
1023		next = ntohl(e[1].addr.lba);
1024	len = next - block;
1025	/* Take into account a start offset time. */
1026	lba2msf (len - 150, &m, &s, &f);
1027
1028	/* Print duration, block, length, type */
1029	printf ("%2d:%02d.%02d  %6d  %6d  %5s\n", m, s, f, block, len,
1030		(e->control & 4) ? "data" : "audio");
1031}
1032
1033int play_track (int tstart, int istart, int tend, int iend)
1034{
1035	struct ioc_play_track t;
1036
1037	t.start_track = tstart;
1038	t.start_index = istart;
1039	t.end_track = tend;
1040	t.end_index = iend;
1041
1042	return ioctl (fd, CDIOCPLAYTRACKS, &t);
1043}
1044
1045int play_blocks (int blk, int len)
1046{
1047	struct ioc_play_blocks  t;
1048
1049	t.blk = blk;
1050	t.len = len;
1051
1052	return ioctl (fd, CDIOCPLAYBLOCKS, &t);
1053}
1054
1055int setvol (int left, int right)
1056{
1057	struct ioc_vol  v;
1058
1059	left  = left  < 0 ? 0 : left  > 255 ? 255 : left;
1060	right = right < 0 ? 0 : right > 255 ? 255 : right;
1061
1062	v.vol[0] = left;
1063	v.vol[1] = right;
1064	v.vol[2] = 0;
1065	v.vol[3] = 0;
1066
1067	return ioctl (fd, CDIOCSETVOL, &v);
1068}
1069
1070int read_toc_entrys (int len)
1071{
1072	struct ioc_read_toc_entry t;
1073
1074	t.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
1075	t.starting_track = 0;
1076	t.data_len = len;
1077	t.data = toc_buffer;
1078
1079	return (ioctl (fd, CDIOREADTOCENTRYS, (char *) &t));
1080}
1081
1082int play_msf (int start_m, int start_s, int start_f,
1083	int end_m, int end_s, int end_f)
1084{
1085	struct ioc_play_msf a;
1086
1087	a.start_m = start_m;
1088	a.start_s = start_s;
1089	a.start_f = start_f;
1090	a.end_m = end_m;
1091	a.end_s = end_s;
1092	a.end_f = end_f;
1093
1094	return ioctl (fd, CDIOCPLAYMSF, (char *) &a);
1095}
1096
1097int status (int *trk, int *min, int *sec, int *frame)
1098{
1099	struct ioc_read_subchannel s;
1100	struct cd_sub_channel_info data;
1101	u_char mm, ss, ff;
1102
1103	bzero (&s, sizeof (s));
1104	s.data = &data;
1105	s.data_len = sizeof (data);
1106	s.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
1107	s.data_format = CD_CURRENT_POSITION;
1108
1109	if (ioctl (fd, CDIOCREADSUBCHANNEL, (char *) &s) < 0)
1110		return -1;
1111
1112	*trk = s.data->what.position.track_number;
1113	if (msf) {
1114		*min = s.data->what.position.reladdr.msf.minute;
1115		*sec = s.data->what.position.reladdr.msf.second;
1116		*frame = s.data->what.position.reladdr.msf.frame;
1117	} else {
1118		lba2msf(ntohl(s.data->what.position.reladdr.lba),
1119			&mm, &ss, &ff);
1120		*min = mm;
1121		*sec = ss;
1122		*frame = ff;
1123	}
1124
1125	return s.data->header.audio_status;
1126}
1127
1128const char *
1129cdcontrol_prompt()
1130{
1131	return ("cdcontrol> ");
1132}
1133
1134char *
1135input (int *cmd)
1136{
1137#define MAXLINE 80
1138	static EditLine *el = NULL;
1139	static History *hist = NULL;
1140	HistEvent he;
1141	static char buf[MAXLINE];
1142	int num = 0;
1143	int len;
1144	const char *bp = NULL;
1145	char *p;
1146
1147	do {
1148		if (verbose) {
1149			if (!el) {
1150				el = el_init("cdcontrol", stdin, stdout,
1151				    stderr);
1152				hist = history_init();
1153				history(hist, &he, H_SETSIZE, 100);
1154				el_set(el, EL_HIST, history, hist);
1155				el_set(el, EL_EDITOR, "emacs");
1156				el_set(el, EL_PROMPT, cdcontrol_prompt);
1157				el_set(el, EL_SIGNAL, 1);
1158				el_source(el, NULL);
1159			}
1160			if ((bp = el_gets(el, &num)) == NULL || num == 0) {
1161				*cmd = CMD_QUIT;
1162				fprintf (stderr, "\r\n");
1163				return (0);
1164			}
1165
1166			len = (num > MAXLINE) ? MAXLINE : num;
1167			memcpy(buf, bp, len);
1168			buf[len] = 0;
1169			history(hist, &he, H_ENTER, bp);
1170#undef MAXLINE
1171
1172		} else {
1173			if (! fgets (buf, sizeof (buf), stdin)) {
1174				*cmd = CMD_QUIT;
1175				fprintf (stderr, "\r\n");
1176				return (0);
1177			}
1178		}
1179		p = parse (buf, cmd);
1180	} while (! p);
1181	return (p);
1182}
1183
1184char *parse (char *buf, int *cmd)
1185{
1186	struct cmdtab *c;
1187	char *p;
1188	unsigned int len;
1189
1190	for (p=buf; isspace (*p); p++)
1191		continue;
1192
1193	if (isdigit (*p) || (p[0] == '#' && isdigit (p[1]))) {
1194		*cmd = CMD_PLAY;
1195		return (p);
1196	} else if (*p == '+') {
1197		*cmd = CMD_NEXT;
1198		return (p + 1);
1199	} else if (*p == '-') {
1200		*cmd = CMD_PREVIOUS;
1201		return (p + 1);
1202	}
1203
1204	for (buf = p; *p && ! isspace (*p); p++)
1205		continue;
1206
1207	len = p - buf;
1208	if (! len)
1209		return (0);
1210
1211	if (*p) {		/* It must be a spacing character! */
1212		char *q;
1213
1214		*p++ = 0;
1215		for (q=p; *q && *q != '\n' && *q != '\r'; q++)
1216			continue;
1217		*q = 0;
1218	}
1219
1220	*cmd = -1;
1221	for (c=cmdtab; c->name; ++c) {
1222		/* Is it an exact match? */
1223		if (! strcasecmp (buf, c->name)) {
1224  			*cmd = c->command;
1225  			break;
1226  		}
1227
1228		/* Try short hand forms then... */
1229		if (len >= c->min && ! strncasecmp (buf, c->name, len)) {
1230			if (*cmd != -1 && *cmd != c->command) {
1231				warnx("ambiguous command");
1232				return (0);
1233			}
1234			*cmd = c->command;
1235  		}
1236	}
1237
1238	if (*cmd == -1) {
1239		warnx("invalid command, enter ``help'' for commands");
1240		return (0);
1241	}
1242
1243	while (isspace (*p))
1244		p++;
1245	return p;
1246}
1247
1248int open_cd ()
1249{
1250	char devbuf[MAXPATHLEN];
1251	const char *dev;
1252
1253	if (fd > -1)
1254		return (1);
1255
1256	if (cdname) {
1257	    if (*cdname == '/') {
1258		    snprintf (devbuf, MAXPATHLEN, "%s", cdname);
1259	    } else {
1260		    snprintf (devbuf, MAXPATHLEN, "%s%s", _PATH_DEV, cdname);
1261	    }
1262	    fd = open (dev = devbuf, O_RDONLY);
1263	} else {
1264	    fd = open(dev = "/dev/cdrom", O_RDONLY);
1265	    if (fd < 0 && errno == ENOENT)
1266		fd = open(dev = "/dev/cd0", O_RDONLY);
1267	    if (fd < 0 && errno == ENOENT)
1268		fd = open(dev = "/dev/acd0", O_RDONLY);
1269	}
1270
1271	if (fd < 0) {
1272		if (errno == ENXIO) {
1273			/*  ENXIO has an overloaded meaning here.
1274			 *  The original "Device not configured" should
1275			 *  be interpreted as "No disc in drive %s". */
1276			warnx("no disc in drive %s", dev);
1277			return (0);
1278		}
1279		err(1, "%s", dev);
1280	}
1281	return (1);
1282}
1283