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