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
6 * Fixed and further modified on 5-Sep-1995 by Jukka Ukkonen <jau@funet.fi>.
8 * 11-Sep-1995: Jukka A. Ukkonen <jau@funet.fi>
9 * A couple of further fixes to my own earlier "fixes".
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.
16 * 11-Oct-1995: Serge V.Vakulenko <vak@cronyx.ru>
17 * New eject algorithm.
18 * Some code style reformatting.
22 static const char rcsid[] =
35 #include <sys/ioctl.h>
36 #include <sys/param.h>
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 */
48 #ifndef DEFAULT_CD_DRIVE
49 # define DEFAULT_CD_DRIVE "/dev/cd0c"
52 #ifndef DEFAULT_CD_PARTITION
53 # define DEFAULT_CD_PARTITION "c"
70 #define STATUS_AUDIO 0x1
71 #define STATUS_MEDIA 0x2
72 #define STATUS_VOLUME 0x4
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" },
101 struct cd_toc_entry toc_buffer[100];
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));
133 for (c=cmdtab; c->name; ++c) {
137 for (i = c->min, s = c->name; *s; s++, i--) {
145 printf (" %s", c->args);
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");
154 fprintf (stderr, "usage: cdcontrol [-sv] [-f device] [command ...]\n");
158 int main (int argc, char **argv)
163 cdname = getenv ("MUSIC_CD");
165 cdname = getenv ("CD_DRIVE");
167 cdname = getenv ("DISC");
169 cdname = getenv ("CDPLAY");
172 switch (getopt (argc, argv, "svhf:")) {
193 if (argc > 0 && ! strcasecmp (*argv, "help"))
197 cdname = DEFAULT_CD_DRIVE;
198 warnx("no CD device name specified, defaulting to %s", cdname);
205 for (p=buf; argc-->0; ++argv) {
206 len = strlen (*argv);
208 if (p + len >= buf + sizeof (buf) - 1)
218 arg = parse (buf, &cmd);
219 return (run (cmd, arg));
223 verbose = isatty (0);
226 printf ("Compact Disc Control utility, version %s\n", VERSION);
227 printf ("Type `?' for command list\n\n");
232 if (run (cmd, arg) < 0) {
242 int run (int cmd, char *arg)
254 if (fd < 0 && ! open_cd ())
260 if (fd < 0 && ! open_cd ())
263 return pstatus (arg);
266 if (fd < 0 && ! open_cd ())
269 return ioctl (fd, CDIOCPAUSE);
272 if (fd < 0 && ! open_cd ())
275 return ioctl (fd, CDIOCRESUME);
278 if (fd < 0 && ! open_cd ())
281 rc = ioctl (fd, CDIOCSTOP);
283 (void) ioctl (fd, CDIOCALLOW);
288 if (fd < 0 && ! open_cd ())
291 rc = ioctl (fd, CDIOCRESET);
299 if (fd < 0 && ! open_cd ())
302 if (! strcasecmp (arg, "on"))
303 return ioctl (fd, CDIOCSETDEBUG);
305 if (! strcasecmp (arg, "off"))
306 return ioctl (fd, CDIOCCLRDEBUG);
308 warnx("invalid command arguments");
313 if (fd < 0 && ! open_cd ())
316 (void) ioctl (fd, CDIOCALLOW);
317 rc = ioctl (fd, CDIOCEJECT);
323 if (fd < 0 && ! open_cd ())
326 (void) ioctl (fd, CDIOCALLOW);
327 rc = ioctl (fd, CDIOCCLOSE);
335 if (fd < 0 && ! open_cd ())
338 while (isspace (*arg))
344 if (! strcasecmp (arg, "msf"))
346 else if (! strcasecmp (arg, "lba"))
349 warnx("invalid command arguments");
353 if (fd < 0 && !open_cd ())
356 if (! strncasecmp (arg, "left", strlen(arg)))
357 return ioctl (fd, CDIOCSETLEFT);
359 if (! strncasecmp (arg, "right", strlen(arg)))
360 return ioctl (fd, CDIOCSETRIGHT);
362 if (! strncasecmp (arg, "mono", strlen(arg)))
363 return ioctl (fd, CDIOCSETMONO);
365 if (! strncasecmp (arg, "stereo", strlen(arg)))
366 return ioctl (fd, CDIOCSETSTERIO);
368 if (! strncasecmp (arg, "mute", strlen(arg)))
369 return ioctl (fd, CDIOCSETMUTE);
371 if (2 != sscanf (arg, "%d %d", &l, &r)) {
372 warnx("invalid command arguments");
376 return setvol (l, r);
388 struct ioc_toc_header h;
389 int rc, n, start, end = 0, istart = 1, iend = 1;
391 rc = ioctl (fd, CDIOREADTOCHEADER, &h);
396 n = h.ending_track - h.starting_track + 1;
397 rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
402 if (! arg || ! *arg) {
403 /* Play the whole disc */
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));
409 return play_blocks (0, ntohl(toc_buffer[n].addr.lba));
412 if (strchr (arg, '#')) {
413 /* Play block #blk [ len ] */
416 if (2 != sscanf (arg, "#%d%d", &blk, &len) &&
417 1 != sscanf (arg, "#%d", &blk))
422 len = msf2lba (toc_buffer[n].addr.msf.minute,
423 toc_buffer[n].addr.msf.second,
424 toc_buffer[n].addr.msf.frame) - blk;
426 len = ntohl(toc_buffer[n].addr.lba) - blk;
428 return play_blocks (blk, len);
431 if (strchr (arg, ':')) {
433 * Play MSF m1:s1 [ .f1 ] [ m2:s2 [ .f2 ] ]
435 * Will now also undestand timed addresses relative
436 * to the beginning of a track in the form...
438 * tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]
441 unsigned m1, m2, s1, s2, f1, f2;
442 unsigned char tm, ts, tf;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
501 tr2 = m2 = s2 = f2 = f1 = 0;
502 if (3 == sscanf (arg, "%d %d:%d", &tr1, &m1, &s1))
503 goto Play_Relative_Addresses;
505 tr2 = m2 = s2 = f2 = f1 = 0;
506 goto Try_Absolute_Timed_Addresses;
508 Play_Relative_Addresses:
515 tm = toc_buffer[tr1].addr.msf.minute;
516 ts = toc_buffer[tr1].addr.msf.second;
517 tf = toc_buffer[tr1].addr.msf.frame;
519 lba2msf(ntohl(toc_buffer[tr1].addr.lba),
526 printf ("Track %d is not that long.\n", tr1);
547 if (m2 || s2 || f2) {
565 m2 = toc_buffer[n].addr.msf.minute;
566 s2 = toc_buffer[n].addr.msf.second;
567 f2 = toc_buffer[n].addr.msf.frame;
569 lba2msf(ntohl(toc_buffer[n].addr.lba),
576 } else if (tr2 > n) {
583 tm = toc_buffer[tr2].addr.msf.minute;
584 ts = toc_buffer[tr2].addr.msf.second;
585 tf = toc_buffer[tr2].addr.msf.frame;
587 lba2msf(ntohl(toc_buffer[tr2].addr.lba),
605 tm = toc_buffer[n].addr.msf.minute;
606 ts = toc_buffer[n].addr.msf.second;
607 tf = toc_buffer[n].addr.msf.frame;
609 lba2msf(ntohl(toc_buffer[n].addr.lba),
617 printf ("The playing time of the disc is not that long.\n");
620 return (play_msf (m1, s1, f1, m2, s2, f2));
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))
634 m2 = toc_buffer[n].addr.msf.minute;
635 s2 = toc_buffer[n].addr.msf.second;
636 f2 = toc_buffer[n].addr.msf.frame;
638 lba2msf(ntohl(toc_buffer[n].addr.lba),
645 return play_msf (m1, s1, f1, m2, s2, f2);
649 * Play track trk1 [ .idx1 ] [ trk2 [ .idx2 ] ]
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))
661 return (play_track (start, istart, end, iend));
664 warnx("invalid command arguments");
668 char *strstatus (int 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 ("??");
681 int pstatus (char *arg)
684 struct ioc_read_subchannel ss;
685 struct cd_sub_channel_info data;
686 int rc, trk, m, s, f;
690 while ((p = strtok(arg, " \t"))) {
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;
699 warnx("invalid command arguments");
704 what = STATUS_AUDIO|STATUS_MEDIA|STATUS_VOLUME;
705 if (what & STATUS_AUDIO) {
706 rc = status (&trk, &m, &s, &f);
709 printf ("Audio status = %d<%s>, current track = %d, current position = %d:%02d.%02d\n",
710 rc, strstatus (rc), trk, m, s, f);
712 printf ("%d %d %d:%02d.%02d\n", rc, trk, m, s, f);
714 printf ("No current status info available\n");
716 if (what & STATUS_MEDIA) {
717 bzero (&ss, sizeof (ss));
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);
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);
732 printf("No media catalog info available\n");
734 if (what & STATUS_VOLUME) {
735 rc = ioctl (fd, CDIOCGETVOL, &v);
738 printf ("Left volume = %d, right volume = %d\n",
741 printf ("%d %d\n", v.vol[0], v.vol[1]);
743 printf ("No volume level info available\n");
750 struct ioc_toc_header h;
753 rc = ioctl (fd, CDIOREADTOCHEADER, &h);
756 printf ("Starting track = %d, ending track = %d, TOC size = %d bytes\n",
757 h.starting_track, h.ending_track, h.len);
759 printf ("%d %d %d\n", h.starting_track,
760 h.ending_track, h.len);
762 warn("getting toc header");
766 n = h.ending_track - h.starting_track + 1;
767 rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
772 printf ("track start duration block length type\n");
773 printf ("-------------------------------------------------\n");
776 for (i = 0; i < n; i++) {
777 printf ("%5d ", toc_buffer[i].track);
778 prtrack (toc_buffer + i, 0);
780 printf ("%5d ", toc_buffer[n].track);
781 prtrack (toc_buffer + n, 1);
785 void lba2msf (unsigned long lba, u_char *m, u_char *s, u_char *f)
787 lba += 150; /* block start offset */
788 lba &= 0xffffff; /* negative lbas use only 24 bits */
789 *m = lba / (60 * 75);
795 unsigned int msf2lba (u_char m, u_char s, u_char f)
797 return (((m * 60) + s) * 75 + f) - 150;
800 void prtrack (struct cd_toc_entry *e, int lastflag)
802 int block, next, len;
806 /* Print track start */
807 printf ("%2d:%02d.%02d ", e->addr.msf.minute,
808 e->addr.msf.second, e->addr.msf.frame);
810 block = msf2lba (e->addr.msf.minute, e->addr.msf.second,
813 block = ntohl(e->addr.lba);
814 lba2msf(block, &m, &s, &f);
815 /* Print track start */
816 printf ("%2d:%02d.%02d ", m, s, f);
819 /* Last track -- print block */
820 printf (" - %6d - -\n", block);
825 next = msf2lba (e[1].addr.msf.minute, e[1].addr.msf.second,
826 e[1].addr.msf.frame);
828 next = ntohl(e[1].addr.lba);
830 lba2msf (len, &m, &s, &f);
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");
837 int play_track (int tstart, int istart, int tend, int iend)
839 struct ioc_play_track t;
841 t.start_track = tstart;
842 t.start_index = istart;
846 return ioctl (fd, CDIOCPLAYTRACKS, &t);
849 int play_blocks (int blk, int len)
851 struct ioc_play_blocks t;
856 return ioctl (fd, CDIOCPLAYBLOCKS, &t);
859 int setvol (int left, int right)
868 return ioctl (fd, CDIOCSETVOL, &v);
871 int read_toc_entrys (int len)
873 struct ioc_read_toc_entry t;
875 t.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
876 t.starting_track = 0;
880 return (ioctl (fd, CDIOREADTOCENTRYS, (char *) &t));
883 int play_msf (int start_m, int start_s, int start_f,
884 int end_m, int end_s, int end_f)
886 struct ioc_play_msf a;
895 return ioctl (fd, CDIOCPLAYMSF, (char *) &a);
898 int status (int *trk, int *min, int *sec, int *frame)
900 struct ioc_read_subchannel s;
901 struct cd_sub_channel_info data;
904 bzero (&s, sizeof (s));
906 s.data_len = sizeof (data);
907 s.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
908 s.data_format = CD_CURRENT_POSITION;
910 if (ioctl (fd, CDIOCREADSUBCHANNEL, (char *) &s) < 0)
913 *trk = s.data->what.position.track_number;
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;
919 lba2msf(ntohl(s.data->what.position.reladdr.lba),
926 return s.data->header.audio_status;
932 return ("cdcontrol> ");
939 static EditLine *el = NULL;
940 static History *hist = NULL;
941 static char buf[MAXLINE];
944 const char *bp = NULL;
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);
959 if ((bp = el_gets(el, &num)) == NULL || num == 0)
962 len = (num > MAXLINE) ? MAXLINE : num;
963 memcpy(buf, bp, len);
965 history(hist, H_ENTER, bp);
969 if (! fgets (buf, sizeof (buf), stdin)) {
971 fprintf (stderr, "\r\n");
975 p = parse (buf, cmd);
980 char *parse (char *buf, int *cmd)
986 for (p=buf; isspace (*p); p++)
989 if (isdigit (*p) || (p[0] == '#' && isdigit (p[1]))) {
994 for (buf = p; *p && ! isspace (*p); p++)
1001 if (*p) { /* It must be a spacing character! */
1005 for (q=p; *q && *q != '\n' && *q != '\r'; q++)
1011 for (c=cmdtab; c->name; ++c) {
1012 /* Is it an exact match? */
1013 if (! strcasecmp (buf, c->name)) {
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");
1029 warnx("invalid command, enter ``help'' for commands");
1033 while (isspace (*p))
1040 char devbuf[MAXPATHLEN];
1045 if (*cdname == '/') {
1046 snprintf (devbuf, MAXPATHLEN, "%s", cdname);
1047 } else if (*cdname == 'r') {
1048 snprintf (devbuf, MAXPATHLEN, "/dev/%s", cdname);
1050 snprintf (devbuf, MAXPATHLEN, "/dev/r%s", cdname);
1053 fd = open (devbuf, O_RDONLY);
1055 if (fd < 0 && errno == ENOENT) {
1056 strcat (devbuf, DEFAULT_CD_PARTITION);
1057 fd = open (devbuf, O_RDONLY);
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);
1068 err(1, "%s", devbuf);