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