xref: /dragonfly/usr.sbin/cdcontrol/cdcontrol.c (revision 2c3b1d1b)
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  * $FreeBSD: src/usr.sbin/cdcontrol/cdcontrol.c,v 1.24.2.11 2002/11/20 00:26:19 njl Exp $
21  */
22 
23 #include <sys/cdio.h>
24 #include <sys/cdrio.h>
25 #include <sys/ioctl.h>
26 #include <sys/param.h>
27 #include <ctype.h>
28 #include <err.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <histedit.h>
32 #include <limits.h>
33 #include <paths.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <vis.h>
39 
40 #define VERSION "2.0"
41 
42 #define ASTS_INVALID	0x00  /* Audio status byte not valid */
43 #define ASTS_PLAYING	0x11  /* Audio play operation in progress */
44 #define ASTS_PAUSED	0x12  /* Audio play operation paused */
45 #define ASTS_COMPLETED	0x13  /* Audio play operation successfully completed */
46 #define ASTS_ERROR	0x14  /* Audio play operation stopped due to error */
47 #define ASTS_VOID	0x15  /* No current audio status to return */
48 
49 #ifndef DEFAULT_CD_DRIVE
50 #  define DEFAULT_CD_DRIVE  "/dev/cd0c"
51 #endif
52 
53 #ifndef DEFAULT_CD_PARTITION
54 #  define DEFAULT_CD_PARTITION  "c"
55 #endif
56 
57 #define CMD_DEBUG	1
58 #define CMD_EJECT	2
59 #define CMD_HELP	3
60 #define CMD_INFO	4
61 #define CMD_PAUSE	5
62 #define CMD_PLAY	6
63 #define CMD_QUIT	7
64 #define CMD_RESUME	8
65 #define CMD_STOP	9
66 #define CMD_VOLUME	10
67 #define CMD_CLOSE	11
68 #define CMD_RESET	12
69 #define CMD_SET		13
70 #define CMD_STATUS	14
71 #define CMD_CDID	15
72 #define CMD_NEXT	16
73 #define CMD_PREVIOUS	17
74 #define CMD_SPEED	18
75 #define STATUS_AUDIO	0x1
76 #define STATUS_MEDIA	0x2
77 #define STATUS_VOLUME	0x4
78 
79 static struct cmdtab {
80 	int command;
81 	const char *name;
82 	unsigned min;
83 	const char *args;
84 } cmdtab[] = {
85 { CMD_CLOSE,	"close",	1, "" },
86 { CMD_DEBUG,	"debug",	1, "on | off" },
87 { CMD_EJECT,	"eject",	1, "" },
88 { CMD_HELP,	"?",		1, 0 },
89 { CMD_HELP,	"help",		1, "" },
90 { CMD_INFO,	"info",		1, "" },
91 { CMD_NEXT,	"next",		1, "" },
92 { CMD_PAUSE,	"pause",	2, "" },
93 { CMD_PLAY,	"play",		1, "min1:sec1[.fram1] [min2:sec2[.fram2]]" },
94 { CMD_PLAY,	"play",		1, "track1[.index1] [track2[.index2]]" },
95 { CMD_PLAY,	"play",		1, "tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]" },
96 { CMD_PLAY,	"play",		1, "[#block [len]]" },
97 { CMD_PREVIOUS,	"previous",	2, "" },
98 { CMD_QUIT,	"quit",		1, "" },
99 { CMD_QUIT,	"exit",		1, "" },
100 { CMD_RESET,	"reset",	4, "" },
101 { CMD_RESUME,	"resume",	1, "" },
102 { CMD_SET,	"set",		2, "msf | lba" },
103 { CMD_STATUS,	"status",	1, "[audio | media | volume]" },
104 { CMD_STOP,	"stop",		3, "" },
105 { CMD_VOLUME,	"volume",	1,
106       "<l> <r> | left | right | mute | mono | stereo" },
107 { CMD_CDID,	"cdid",		2, "" },
108 { CMD_SPEED,	"speed",	2, "speed" },
109 { 0,		NULL,		0, NULL }
110 };
111 
112 static struct cd_toc_entry	toc_buffer[100];
113 
114 static const char	*cdname;
115 static int		fd = -1;
116 static int		verbose = 1;
117 static int		msf = 1;
118 
119 static int		 setvol(int, int);
120 static int		 read_toc_entrys(int);
121 static int		 play_msf(int, int, int, int, int, int);
122 static int		 play_track(int, int, int, int);
123 static int		 status(int *, int *, int *, int *);
124 static int		 open_cd(void);
125 static int		 next_prev(char *arg, int);
126 static int		 play(char *arg);
127 static int		 info(char *arg);
128 static int		 cdid(void);
129 static int		 pstatus(char *arg);
130 static char		*input(int *);
131 static void		 prtrack(struct cd_toc_entry *e, int lastflag);
132 static void		 lba2msf(unsigned long lba,
133 			     u_char *m, u_char *s, u_char *f);
134 static unsigned int	 msf2lba(u_char m, u_char s, u_char f);
135 static int		 play_blocks(int blk, int len);
136 static int		 run(int cmd, char *arg);
137 static char		*parse(char *buf, int *cmd);
138 static void		 help(void);
139 static void		 usage(void) __dead2;
140 static char		*use_cdrom_instead(const char *);
141 static const char	*strstatus(int);
142 static u_int		 dbprog_discid(void);
143 static const char	*cdcontrol_prompt(void);
144 
145 static void
help(void)146 help(void)
147 {
148 	struct cmdtab *c;
149 	const char *s;
150 	char n;
151 	int i;
152 
153 	for (c=cmdtab; c->name; ++c) {
154 		if (! c->args)
155 			continue;
156 		printf("\t");
157 		for (i = c->min, s = c->name; *s; s++, i--) {
158 			if (i > 0)
159 				n = toupper(*s);
160 			else
161 				n = *s;
162 			putchar(n);
163 		}
164 		if (*c->args)
165 			printf (" %s", c->args);
166 		printf ("\n");
167 	}
168 	printf ("\n\tThe word \"play\" is not required for the play commands.\n");
169 	printf ("\tThe plain target address is taken as a synonym for play.\n");
170 }
171 
172 static void
usage(void)173 usage(void)
174 {
175 	fprintf (stderr, "usage: cdcontrol [-sv] [-f device] [command ...]\n");
176 	exit (1);
177 }
178 
179 static char *
use_cdrom_instead(const char * old_envvar)180 use_cdrom_instead(const char *old_envvar)
181 {
182 	char *device;
183 
184 	device = getenv(old_envvar);
185 	if (device)
186 		warnx("%s environment variable deprecated, "
187 		    "please use CDROM in the future.", old_envvar);
188 	return device;
189 }
190 
191 
192 int
main(int argc,char ** argv)193 main(int argc, char **argv)
194 {
195 	int cmd;
196 	char *arg;
197 
198 	for (;;) {
199 		switch (getopt (argc, argv, "svhf:")) {
200 		case EOF:
201 			break;
202 		case 's':
203 			verbose = 0;
204 			continue;
205 		case 'v':
206 			verbose = 2;
207 			continue;
208 		case 'f':
209 			cdname = optarg;
210 			continue;
211 		case 'h':
212 		default:
213 			usage ();
214 		}
215 		break;
216 	}
217 	argc -= optind;
218 	argv += optind;
219 
220 	if (argc > 0 && ! strcasecmp (*argv, "help"))
221 		usage ();
222 
223 	if (! cdname) {
224 		cdname = getenv("CDROM");
225 	}
226 
227 	if (! cdname)
228 		cdname = use_cdrom_instead("MUSIC_CD");
229 	if (! cdname)
230 		cdname = use_cdrom_instead("CD_DRIVE");
231 	if (! cdname)
232 		cdname = use_cdrom_instead("DISC");
233 	if (! cdname)
234 		cdname = use_cdrom_instead("CDPLAY");
235 
236 	if (! cdname) {
237 		cdname = DEFAULT_CD_DRIVE;
238 		warnx("no CD device name specified, defaulting to %s", cdname);
239 	}
240 
241 	if (argc > 0) {
242 		char buf[80], *p;
243 		int len;
244 
245 		for (p=buf; argc-->0; ++argv) {
246 			len = strlen (*argv);
247 
248 			if (p + len >= buf + sizeof (buf) - 1)
249 				usage ();
250 
251 			if (p > buf)
252 				*p++ = ' ';
253 
254 			strcpy (p, *argv);
255 			p += len;
256 		}
257 		*p = 0;
258 		arg = parse (buf, &cmd);
259 		return (run (cmd, arg));
260 	}
261 
262 	if (verbose == 1)
263 		verbose = isatty (0);
264 
265 	if (verbose) {
266 		printf ("Compact Disc Control utility, version %s\n", VERSION);
267 		printf ("Type `?' for command list\n\n");
268 	}
269 
270 	for (;;) {
271 		arg = input (&cmd);
272 		if (run (cmd, arg) < 0) {
273 			if (verbose)
274 				warn(NULL);
275 			close (fd);
276 			fd = -1;
277 		}
278 		fflush (stdout);
279 	}
280 }
281 
282 static int
run(int cmd,char * arg)283 run(int cmd, char *arg)
284 {
285 	long speed;
286 	int l, r, rc;
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 		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 		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 		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 (! strncasecmp (arg, "left", strlen(arg)))
413 			return ioctl (fd, CDIOCSETLEFT);
414 
415 		if (! strncasecmp (arg, "right", strlen(arg)))
416 			return ioctl (fd, CDIOCSETRIGHT);
417 
418 		if (! strncasecmp (arg, "mono", strlen(arg)))
419 			return ioctl (fd, CDIOCSETMONO);
420 
421 		if (! strncasecmp (arg, "stereo", strlen(arg)))
422 			return ioctl (fd, CDIOCSETSTEREO);
423 
424 		if (! strncasecmp (arg, "mute", strlen(arg)))
425 			return ioctl (fd, CDIOCSETMUTE);
426 
427 		if (2 != sscanf (arg, "%d %d", &l, &r)) {
428 			warnx("invalid command arguments");
429 			return (0);
430 		}
431 
432 		return setvol (l, r);
433 
434 	case CMD_SPEED:
435 		if (fd < 0 && ! open_cd ())
436 			return (0);
437 
438 		errno = 0;
439 		if (strcasecmp("max", arg) == 0)
440 			speed = CDR_MAX_SPEED;
441 		else
442 			speed = strtol(arg, NULL, 10) * 177;
443 		if (speed <= 0 || speed > INT_MAX) {
444 			warnx("invalid command arguments %s", arg);
445 			return (0);
446 		}
447 		return ioctl(fd, CDRIOCREADSPEED, &speed);
448 
449 	default:
450 	case CMD_HELP:
451 		help ();
452 		return (0);
453 
454 	}
455 }
456 
457 static int
play(char * arg)458 play(char *arg)
459 {
460 	struct ioc_toc_header h;
461 	unsigned int n;
462 	int rc, start, end = 0, istart = 1, iend = 1;
463 
464 	rc = ioctl (fd, CDIOREADTOCHEADER, &h);
465 
466 	if (rc < 0)
467 		return (rc);
468 
469 	n = h.ending_track - h.starting_track + 1;
470 	rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
471 
472 	if (rc < 0)
473 		return (rc);
474 
475 	if (! arg || ! *arg) {
476 		/* Play the whole disc */
477 		if (msf)
478 			return play_blocks (0, msf2lba (toc_buffer[n].addr.msf.minute,
479 							toc_buffer[n].addr.msf.second,
480 							toc_buffer[n].addr.msf.frame));
481 		else
482 			return play_blocks (0, ntohl(toc_buffer[n].addr.lba));
483 	}
484 
485 	if (strchr (arg, '#')) {
486 		/* Play block #blk [ len ] */
487 		int blk, len = 0;
488 
489 		if (2 != sscanf (arg, "#%d%d", &blk, &len) &&
490 		    1 != sscanf (arg, "#%d", &blk))
491 			goto Clean_up;
492 
493 		if (len == 0) {
494 			if (msf)
495 				len = msf2lba (toc_buffer[n].addr.msf.minute,
496 					       toc_buffer[n].addr.msf.second,
497 					       toc_buffer[n].addr.msf.frame) - blk;
498 			else
499 				len = ntohl(toc_buffer[n].addr.lba) - blk;
500 		}
501 		return play_blocks (blk, len);
502 	}
503 
504 	if (strchr (arg, ':')) {
505 		/*
506 		 * Play MSF m1:s1 [ .f1 ] [ m2:s2 [ .f2 ] ]
507 		 *
508 		 * Will now also undestand timed addresses relative
509 		 * to the beginning of a track in the form...
510 		 *
511 		 *      tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]
512 		 */
513 		unsigned tr1, tr2;
514 		unsigned m1, m2, s1, s2, f1, f2;
515 		unsigned char tm, ts, tf;
516 
517 		tr2 = m2 = s2 = f2 = f1 = 0;
518 		if (8 == sscanf (arg, "%d %d:%d.%d %d %d:%d.%d",
519 		    &tr1, &m1, &s1, &f1, &tr2, &m2, &s2, &f2))
520 			goto Play_Relative_Addresses;
521 
522 		tr2 = m2 = s2 = f2 = f1 = 0;
523 		if (7 == sscanf (arg, "%d %d:%d %d %d:%d.%d",
524 		    &tr1, &m1, &s1, &tr2, &m2, &s2, &f2))
525 			goto Play_Relative_Addresses;
526 
527 		tr2 = m2 = s2 = f2 = f1 = 0;
528 		if (7 == sscanf (arg, "%d %d:%d.%d %d %d:%d",
529 		    &tr1, &m1, &s1, &f1, &tr2, &m2, &s2))
530 			goto Play_Relative_Addresses;
531 
532 		tr2 = m2 = s2 = f2 = f1 = 0;
533 		if (7 == sscanf (arg, "%d %d:%d.%d %d:%d.%d",
534 		    &tr1, &m1, &s1, &f1, &m2, &s2, &f2))
535 			goto Play_Relative_Addresses;
536 
537 		tr2 = m2 = s2 = f2 = f1 = 0;
538 		if (6 == sscanf (arg, "%d %d:%d.%d %d:%d",
539 		    &tr1, &m1, &s1, &f1, &m2, &s2))
540 			goto Play_Relative_Addresses;
541 
542 		tr2 = m2 = s2 = f2 = f1 = 0;
543 		if (6 == sscanf (arg, "%d %d:%d %d:%d.%d",
544 		    &tr1, &m1, &s1, &m2, &s2, &f2))
545 			goto Play_Relative_Addresses;
546 
547 		tr2 = m2 = s2 = f2 = f1 = 0;
548 		if (6 == sscanf (arg, "%d %d:%d.%d %d %d",
549 		    &tr1, &m1, &s1, &f1, &tr2, &m2))
550 			goto Play_Relative_Addresses;
551 
552 		tr2 = m2 = s2 = f2 = f1 = 0;
553 		if (5 == sscanf (arg, "%d %d:%d %d:%d", &tr1, &m1, &s1, &m2, &s2))
554 			goto Play_Relative_Addresses;
555 
556 		tr2 = m2 = s2 = f2 = f1 = 0;
557 		if (5 == sscanf (arg, "%d %d:%d %d %d",
558 		    &tr1, &m1, &s1, &tr2, &m2))
559 			goto Play_Relative_Addresses;
560 
561 		tr2 = m2 = s2 = f2 = f1 = 0;
562 		if (5 == sscanf (arg, "%d %d:%d.%d %d",
563 		    &tr1, &m1, &s1, &f1, &tr2))
564 			goto Play_Relative_Addresses;
565 
566 		tr2 = m2 = s2 = f2 = f1 = 0;
567 		if (4 == sscanf (arg, "%d %d:%d %d", &tr1, &m1, &s1, &tr2))
568 			goto Play_Relative_Addresses;
569 
570 		tr2 = m2 = s2 = f2 = f1 = 0;
571 		if (4 == sscanf (arg, "%d %d:%d.%d", &tr1, &m1, &s1, &f1))
572 			goto Play_Relative_Addresses;
573 
574 		tr2 = m2 = s2 = f2 = f1 = 0;
575 		if (3 == sscanf (arg, "%d %d:%d", &tr1, &m1, &s1))
576 			goto Play_Relative_Addresses;
577 
578 		tr2 = m2 = s2 = f2 = f1 = 0;
579 		goto Try_Absolute_Timed_Addresses;
580 
581 Play_Relative_Addresses:
582 		if (tr1 == 0)
583 			tr1 = 1;
584 		else if (tr1 > n)
585 			tr1 = n;
586 
587 		tr1--;
588 
589 		if (msf) {
590 			tm = toc_buffer[tr1].addr.msf.minute;
591 			ts = toc_buffer[tr1].addr.msf.second;
592 			tf = toc_buffer[tr1].addr.msf.frame;
593 		} else
594 			lba2msf(ntohl(toc_buffer[tr1].addr.lba),
595 				&tm, &ts, &tf);
596 		if ((m1 > tm)
597 		    || ((m1 == tm)
598 		    && ((s1 > ts)
599 		    || ((s1 == ts)
600 		    && (f1 > tf))))) {
601 			printf ("Track %d is not that long.\n", tr1 + 1);
602 			return (0);
603 		}
604 
605 		f1 += tf;
606 		if (f1 >= 75) {
607 			s1 += f1 / 75;
608 			f1 %= 75;
609 		}
610 
611 		s1 += ts;
612 		if (s1 >= 60) {
613 			m1 += s1 / 60;
614 			s1 %= 60;
615 		}
616 
617 		m1 += tm;
618 
619 		if (! tr2) {
620 			if (m2 || s2 || f2) {
621 				tr2 = tr1;
622 				f2 += f1;
623 				if (f2 >= 75) {
624 					s2 += f2 / 75;
625 					f2 %= 75;
626 				}
627 
628 				s2 += s1;
629 				if (s2 > 60) {
630 					m2 += s2 / 60;
631 					s2 %= 60;
632 				}
633 
634 				m2 += m1;
635 			} else {
636 				tr2 = n;
637 				if (msf) {
638 					m2 = toc_buffer[n].addr.msf.minute;
639 					s2 = toc_buffer[n].addr.msf.second;
640 					f2 = toc_buffer[n].addr.msf.frame;
641 				} else {
642 					lba2msf(ntohl(toc_buffer[n].addr.lba),
643 						&tm, &ts, &tf);
644 					m2 = tm;
645 					s2 = ts;
646 					f2 = tf;
647 				}
648 			}
649 		} else if (tr2 > n) {
650 			tr2 = n;
651 			m2 = s2 = f2 = 0;
652 		} else {
653 			if (m2 || s2 || f2)
654 				tr2--;
655 			if (msf) {
656 				tm = toc_buffer[tr2].addr.msf.minute;
657 				ts = toc_buffer[tr2].addr.msf.second;
658 				tf = toc_buffer[tr2].addr.msf.frame;
659 			} else
660 				lba2msf(ntohl(toc_buffer[tr2].addr.lba),
661 					&tm, &ts, &tf);
662 			f2 += tf;
663 			if (f2 >= 75) {
664 				s2 += f2 / 75;
665 				f2 %= 75;
666 			}
667 
668 			s2 += ts;
669 			if (s2 > 60) {
670 				m2 += s2 / 60;
671 				s2 %= 60;
672 			}
673 
674 			m2 += tm;
675 		}
676 
677 		if (msf) {
678 			tm = toc_buffer[n].addr.msf.minute;
679 			ts = toc_buffer[n].addr.msf.second;
680 			tf = toc_buffer[n].addr.msf.frame;
681 		} else
682 			lba2msf(ntohl(toc_buffer[n].addr.lba),
683 				&tm, &ts, &tf);
684 		if ((tr2 < n)
685 		    && ((m2 > tm)
686 		    || ((m2 == tm)
687 		    && ((s2 > ts)
688 		    || ((s2 == ts)
689 		    && (f2 > tf)))))) {
690 			printf ("The playing time of the disc is not that long.\n");
691 			return (0);
692 		}
693 		return (play_msf (m1, s1, f1, m2, s2, f2));
694 
695 Try_Absolute_Timed_Addresses:
696 		if (6 != sscanf (arg, "%d:%d.%d%d:%d.%d",
697 			&m1, &s1, &f1, &m2, &s2, &f2) &&
698 		    5 != sscanf (arg, "%d:%d.%d%d:%d", &m1, &s1, &f1, &m2, &s2) &&
699 		    5 != sscanf (arg, "%d:%d%d:%d.%d", &m1, &s1, &m2, &s2, &f2) &&
700 		    3 != sscanf (arg, "%d:%d.%d", &m1, &s1, &f1) &&
701 		    4 != sscanf (arg, "%d:%d%d:%d", &m1, &s1, &m2, &s2) &&
702 		    2 != sscanf (arg, "%d:%d", &m1, &s1))
703 			goto Clean_up;
704 
705 		if (m2 == 0) {
706 			if (msf) {
707 				m2 = toc_buffer[n].addr.msf.minute;
708 				s2 = toc_buffer[n].addr.msf.second;
709 				f2 = toc_buffer[n].addr.msf.frame;
710 			} else {
711 				lba2msf(ntohl(toc_buffer[n].addr.lba),
712 					&tm, &ts, &tf);
713 				m2 = tm;
714 				s2 = ts;
715 				f2 = tf;
716 			}
717 		}
718 		return play_msf (m1, s1, f1, m2, s2, f2);
719 	}
720 
721 	/*
722 	 * Play track trk1 [ .idx1 ] [ trk2 [ .idx2 ] ]
723 	 */
724 	if (4 != sscanf (arg, "%d.%d%d.%d", &start, &istart, &end, &iend) &&
725 	    3 != sscanf (arg, "%d.%d%d", &start, &istart, &end) &&
726 	    3 != sscanf (arg, "%d%d.%d", &start, &end, &iend) &&
727 	    2 != sscanf (arg, "%d.%d", &start, &istart) &&
728 	    2 != sscanf (arg, "%d%d", &start, &end) &&
729 	    1 != sscanf (arg, "%d", &start))
730 		goto Clean_up;
731 
732 	if (end == 0)
733 		end = n;
734 	return (play_track (start, istart, end, iend));
735 
736 Clean_up:
737 	warnx("invalid command arguments");
738 	return (0);
739 }
740 
741 static int
next_prev(char * arg,int cmd)742 next_prev(char *arg, int cmd)
743 {
744 	struct ioc_toc_header h;
745 	int dir, junk, n, off, rc, trk;
746 
747 	dir = (cmd == CMD_NEXT) ? 1 : -1;
748 	rc = ioctl (fd, CDIOREADTOCHEADER, &h);
749 	if (rc < 0)
750 		return (rc);
751 
752 	n = h.ending_track - h.starting_track + 1;
753 	rc = status (&trk, &junk, &junk, &junk);
754 	if (rc < 0)
755 		return (-1);
756 
757 	if (arg && *arg) {
758 		if (sscanf (arg, "%u", &off) != 1) {
759 		    warnx("invalid command argument");
760 		    return (0);
761 		} else
762 		    trk += off * dir;
763 	} else
764 		trk += dir;
765 
766 	if (trk > h.ending_track)
767 		trk = 1;
768 
769 	return (play_track (trk, 1, n, 1));
770 }
771 
772 static const char *
strstatus(int sts)773 strstatus(int sts)
774 {
775 	switch (sts) {
776 	case ASTS_INVALID:	return ("invalid");
777 	case ASTS_PLAYING:	return ("playing");
778 	case ASTS_PAUSED:	return ("paused");
779 	case ASTS_COMPLETED:	return ("completed");
780 	case ASTS_ERROR:	return ("error");
781 	case ASTS_VOID:		return ("void");
782 	default:		return ("??");
783 	}
784 }
785 
786 static int
pstatus(char * arg)787 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 = NULL;
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  */
869 static int
dbprog_sum(int n)870 dbprog_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  */
897 static u_int
dbprog_discid(void)898 dbprog_discid(void)
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 
929 static int
cdid(void)930 cdid(void)
931 {
932 	u_int	id;
933 
934 	id = dbprog_discid();
935 	if (id)
936 	{
937 		if (verbose)
938 			printf ("CDID=");
939 		printf ("%08x\n",id);
940 	}
941 	return id ? 0 : 1;
942 }
943 
944 static int
info(char * arg __unused)945 info(char *arg __unused)
946 {
947 	struct ioc_toc_header h;
948 	int rc, i, n;
949 
950 	rc = ioctl (fd, CDIOREADTOCHEADER, &h);
951 	if (rc >= 0) {
952 		if (verbose)
953 			printf ("Starting track = %d, ending track = %d, TOC size = %d bytes\n",
954 				h.starting_track, h.ending_track, h.len);
955 		else
956 			printf ("%d %d %d\n", h.starting_track,
957 				h.ending_track, h.len);
958 	} else {
959 		warn("getting toc header");
960 		return (rc);
961 	}
962 
963 	n = h.ending_track - h.starting_track + 1;
964 	rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
965 	if (rc < 0)
966 		return (rc);
967 
968 	if (verbose) {
969 		printf ("track     start  duration   block  length   type\n");
970 		printf ("-------------------------------------------------\n");
971 	}
972 
973 	for (i = 0; i < n; i++) {
974 		printf ("%5d  ", toc_buffer[i].track);
975 		prtrack (toc_buffer + i, 0);
976 	}
977 	printf ("%5d  ", toc_buffer[n].track);
978 	prtrack (toc_buffer + n, 1);
979 	return (0);
980 }
981 
982 static void
lba2msf(unsigned long lba,u_char * m,u_char * s,u_char * f)983 lba2msf(unsigned long lba, u_char *m, u_char *s, u_char *f)
984 {
985 	lba += 150;			/* block start offset */
986 	lba &= 0xffffff;		/* negative lbas use only 24 bits */
987 	*m = lba / (60 * 75);
988 	lba %= (60 * 75);
989 	*s = lba / 75;
990 	*f = lba % 75;
991 }
992 
993 static unsigned int
msf2lba(u_char m,u_char s,u_char f)994 msf2lba(u_char m, u_char s, u_char f)
995 {
996 	return (((m * 60) + s) * 75 + f) - 150;
997 }
998 
999 static void
prtrack(struct cd_toc_entry * e,int lastflag)1000 prtrack(struct cd_toc_entry *e, int lastflag)
1001 {
1002 	int block, next, len;
1003 	u_char m, s, f;
1004 
1005 	if (msf) {
1006 		/* Print track start */
1007 		printf ("%2d:%02d.%02d  ", e->addr.msf.minute,
1008 			e->addr.msf.second, e->addr.msf.frame);
1009 
1010 		block = msf2lba (e->addr.msf.minute, e->addr.msf.second,
1011 			e->addr.msf.frame);
1012 	} else {
1013 		block = ntohl(e->addr.lba);
1014 		lba2msf(block, &m, &s, &f);
1015 		/* Print track start */
1016 		printf ("%2d:%02d.%02d  ", m, s, f);
1017 	}
1018 	if (lastflag) {
1019 		/* Last track -- print block */
1020 		printf ("       -  %6d       -      -\n", block);
1021 		return;
1022 	}
1023 
1024 	if (msf)
1025 		next = msf2lba (e[1].addr.msf.minute, e[1].addr.msf.second,
1026 			e[1].addr.msf.frame);
1027 	else
1028 		next = ntohl(e[1].addr.lba);
1029 	len = next - block;
1030 	/* Take into account a start offset time. */
1031 	lba2msf (len - 150, &m, &s, &f);
1032 
1033 	/* Print duration, block, length, type */
1034 	printf ("%2d:%02d.%02d  %6d  %6d  %5s\n", m, s, f, block, len,
1035 		(e->control & 4) ? "data" : "audio");
1036 }
1037 
1038 static int
play_track(int tstart,int istart,int tend,int iend)1039 play_track(int tstart, int istart, int tend, int iend)
1040 {
1041 	struct ioc_play_track t;
1042 
1043 	t.start_track = tstart;
1044 	t.start_index = istart;
1045 	t.end_track = tend;
1046 	t.end_index = iend;
1047 
1048 	return ioctl (fd, CDIOCPLAYTRACKS, &t);
1049 }
1050 
1051 static int
play_blocks(int blk,int len)1052 play_blocks(int blk, int len)
1053 {
1054 	struct ioc_play_blocks  t;
1055 
1056 	t.blk = blk;
1057 	t.len = len;
1058 
1059 	return ioctl (fd, CDIOCPLAYBLOCKS, &t);
1060 }
1061 
1062 static int
setvol(int left,int right)1063 setvol(int left, int right)
1064 {
1065 	struct ioc_vol  v;
1066 
1067 	v.vol[0] = left;
1068 	v.vol[1] = right;
1069 	v.vol[2] = 0;
1070 	v.vol[3] = 0;
1071 
1072 	return ioctl (fd, CDIOCSETVOL, &v);
1073 }
1074 
1075 static int
read_toc_entrys(int len)1076 read_toc_entrys(int len)
1077 {
1078 	struct ioc_read_toc_entry t;
1079 
1080 	t.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
1081 	t.starting_track = 0;
1082 	t.data_len = len;
1083 	t.data = toc_buffer;
1084 
1085 	return (ioctl (fd, CDIOREADTOCENTRYS, (char *) &t));
1086 }
1087 
1088 static int
play_msf(int start_m,int start_s,int start_f,int end_m,int end_s,int end_f)1089 play_msf(int start_m, int start_s, int start_f,
1090 	int end_m, int end_s, int end_f)
1091 {
1092 	struct ioc_play_msf a;
1093 
1094 	a.start_m = start_m;
1095 	a.start_s = start_s;
1096 	a.start_f = start_f;
1097 	a.end_m = end_m;
1098 	a.end_s = end_s;
1099 	a.end_f = end_f;
1100 
1101 	return ioctl (fd, CDIOCPLAYMSF, (char *) &a);
1102 }
1103 
1104 static int
status(int * trk,int * min,int * sec,int * frame)1105 status(int *trk, int *min, int *sec, int *frame)
1106 {
1107 	struct ioc_read_subchannel s;
1108 	struct cd_sub_channel_info data;
1109 	u_char mm, ss, ff;
1110 
1111 	bzero (&s, sizeof (s));
1112 	s.data = &data;
1113 	s.data_len = sizeof (data);
1114 	s.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
1115 	s.data_format = CD_CURRENT_POSITION;
1116 
1117 	if (ioctl (fd, CDIOCREADSUBCHANNEL, (char *) &s) < 0)
1118 		return -1;
1119 
1120 	*trk = s.data->what.position.track_number;
1121 	if (msf) {
1122 		*min = s.data->what.position.reladdr.msf.minute;
1123 		*sec = s.data->what.position.reladdr.msf.second;
1124 		*frame = s.data->what.position.reladdr.msf.frame;
1125 	} else {
1126 		lba2msf(ntohl(s.data->what.position.reladdr.lba),
1127 			&mm, &ss, &ff);
1128 		*min = mm;
1129 		*sec = ss;
1130 		*frame = ff;
1131 	}
1132 
1133 	return s.data->header.audio_status;
1134 }
1135 
1136 static const char *
cdcontrol_prompt(void)1137 cdcontrol_prompt(void)
1138 {
1139 	return ("cdcontrol> ");
1140 }
1141 
1142 static char *
input(int * cmd)1143 input(int *cmd)
1144 {
1145 #define MAXLINE 80
1146 	static EditLine *el = NULL;
1147 	static History *hist = NULL;
1148 	static HistEvent he;
1149 	static char buf[MAXLINE];
1150 	int num = 0;
1151 	int len;
1152 	const char *bp = NULL;
1153 	char *p;
1154 
1155 	do {
1156 		if (verbose) {
1157 			if (!el) {
1158 				el = el_init("cdcontrol", stdin, stdout, stderr);
1159 				hist = history_init();
1160 				history(hist, &he, H_SETSIZE, 100);
1161 				el_set(el, EL_HIST, history, hist);
1162 				el_set(el, EL_EDITOR, "emacs");
1163 				el_set(el, EL_PROMPT, cdcontrol_prompt);
1164 				el_set(el, EL_SIGNAL, 1);
1165 				el_source(el, NULL);
1166 			}
1167 			if ((bp = el_gets(el, &num)) == NULL || num == 0) {
1168 				*cmd = CMD_QUIT;
1169 				fprintf (stderr, "\r\n");
1170 				return (0);
1171 			}
1172 
1173 			len = (num >= MAXLINE) ? MAXLINE - 1 : num;
1174 			memcpy(buf, bp, len);
1175 			buf[len] = 0;
1176 			history(hist, &he, H_ENTER, bp);
1177 #undef MAXLINE
1178 
1179 		} else {
1180 			if (! fgets (buf, sizeof (buf), stdin)) {
1181 				*cmd = CMD_QUIT;
1182 				fprintf (stderr, "\r\n");
1183 				return (0);
1184 			}
1185 		}
1186 		p = parse (buf, cmd);
1187 	} while (! p);
1188 	return (p);
1189 }
1190 
1191 static char *
parse(char * buf,int * cmd)1192 parse(char *buf, int *cmd)
1193 {
1194 	struct cmdtab *c;
1195 	char *p;
1196 	unsigned int len;
1197 
1198 	for (p=buf; isspace (*p); p++)
1199 		continue;
1200 
1201 	if (isdigit (*p) || (p[0] == '#' && isdigit (p[1]))) {
1202 		*cmd = CMD_PLAY;
1203 		return (p);
1204 	} else if (*p == '+') {
1205 		*cmd = CMD_NEXT;
1206 		return (p + 1);
1207 	} else if (*p == '-') {
1208 		*cmd = CMD_PREVIOUS;
1209 		return (p + 1);
1210 	}
1211 
1212 	for (buf = p; *p && ! isspace (*p); p++)
1213 		continue;
1214 
1215 	len = p - buf;
1216 	if (! len)
1217 		return (0);
1218 
1219 	if (*p) {		/* It must be a spacing character! */
1220 		char *q;
1221 
1222 		*p++ = 0;
1223 		for (q=p; *q && *q != '\n' && *q != '\r'; q++)
1224 			continue;
1225 		*q = 0;
1226 	}
1227 
1228 	*cmd = -1;
1229 	for (c=cmdtab; c->name; ++c) {
1230 		/* Is it an exact match? */
1231 		if (! strcasecmp (buf, c->name)) {
1232   			*cmd = c->command;
1233   			break;
1234   		}
1235 
1236 		/* Try short hand forms then... */
1237 		if (len >= c->min && ! strncasecmp (buf, c->name, len)) {
1238 			if (*cmd != -1 && *cmd != c->command) {
1239 				warnx("ambiguous command");
1240 				return (0);
1241 			}
1242 			*cmd = c->command;
1243   		}
1244 	}
1245 
1246 	if (*cmd == -1) {
1247 		warnx("invalid command, enter ``help'' for commands");
1248 		return (0);
1249 	}
1250 
1251 	while (isspace (*p))
1252 		p++;
1253 	return p;
1254 }
1255 
1256 static int
open_cd(void)1257 open_cd(void)
1258 {
1259 	char devbuf[MAXPATHLEN];
1260 
1261 	if (fd > -1)
1262 		return (1);
1263 
1264 	if (*cdname == '/') {
1265 		snprintf (devbuf, MAXPATHLEN, "%s", cdname);
1266 	} else {
1267 		snprintf (devbuf, MAXPATHLEN, "%s%s", _PATH_DEV, cdname);
1268 	}
1269 
1270 	fd = open (devbuf, O_RDONLY);
1271 
1272 	if (fd < 0 && errno == ENOENT) {
1273 		strcat (devbuf, DEFAULT_CD_PARTITION);
1274 		fd = open (devbuf, O_RDONLY);
1275 	}
1276 
1277 	if (fd < 0) {
1278 		if (errno == ENXIO) {
1279 			/*  ENXIO has an overloaded meaning here.
1280 			 *  The original "Device not configured" should
1281 			 *  be interpreted as "No disc in drive %s". */
1282 			warnx("no disc in drive %s", devbuf);
1283 			return (0);
1284 		}
1285 		err(1, "%s", devbuf);
1286 	}
1287 	return (1);
1288 }
1289