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