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