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