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