]> CyberLeo.Net >> Repos - FreeBSD/releng/7.2.git/blob - usr.sbin/cdcontrol/cdcontrol.c
Create releng/7.2 from stable/7 in preparation for 7.2-RELEASE.
[FreeBSD/releng/7.2.git] / usr.sbin / cdcontrol / cdcontrol.c
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$");
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
90 struct 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
122 struct cd_toc_entry     toc_buffer[100];
123
124 const char      *cdname;
125 int             fd = -1;
126 int             verbose = 1;
127 int             msf = 1;
128
129 int              setvol(int, int);
130 int              read_toc_entrys(int);
131 int              play_msf(int, int, int, int, int, int);
132 int              play_track(int, int, int, int);
133 int              get_vol(int *, int *);
134 int              status(int *, int *, int *, int *);
135 int              open_cd(void);
136 int              next_prev(char *arg, int);
137 int              play(char *arg);
138 int              info(char *arg);
139 int              cdid(void);
140 int              pstatus(char *arg);
141 char            *input(int *);
142 void             prtrack(struct cd_toc_entry *e, int lastflag);
143 void             lba2msf(unsigned long lba, u_char *m, u_char *s, u_char *f);
144 unsigned int     msf2lba(u_char m, u_char s, u_char f);
145 int              play_blocks(int blk, int len);
146 int              run(int cmd, char *arg);
147 char            *parse(char *buf, int *cmd);
148 void             help(void);
149 void             usage(void);
150 char            *use_cdrom_instead(const char *);
151 __const char    *strstatus(int);
152 static u_int     dbprog_discid(void);
153 __const char    *cdcontrol_prompt(void);
154
155 void 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
181 void usage ()
182 {
183         fprintf (stderr, "usage: cdcontrol [-sv] [-f device] [command ...]\n");
184         exit (1);
185 }
186
187 char *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
199 int main (int argc, char **argv)
200 {
201         int cmd;
202         char *arg;
203
204         for (;;) {
205                 switch (getopt (argc, argv, "svhf:")) {
206                 case EOF:
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
283 int 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
461 int 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
584 Play_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
698 Try_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
739 Clean_up:
740         warnx("invalid command arguments");
741         return (0);
742 }
743
744 int 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
774 const 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
787 int 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  */
869 static int
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
898 dbprog_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
929 int 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
943 int 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
980 void 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
990 unsigned int msf2lba (u_char m, u_char s, u_char f)
991 {
992         return (((m * 60) + s) * 75 + f) - 150;
993 }
994
995 void 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
1033 int 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
1045 int 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
1055 int 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
1070 int 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
1082 int 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
1097 int 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
1128 const char *
1129 cdcontrol_prompt()
1130 {
1131         return ("cdcontrol> ");
1132 }
1133
1134 char *
1135 input (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
1184 char *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
1248 int 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 }