1 /* $NetBSD: cdplay.c,v 1.21 2002/09/28 21:48:35 is Exp $ */ 2 3 /* 4 * Copyright (c) 1999, 2000, 2001 Andrew Doran. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 */ 29 30 /* 31 * Compact Disc Control Utility, originally by Serge V. Vakulenko 32 * <vak@cronyx.ru>. First appeared in FreeBSD under the guise of 33 * cdcontrol(1). Based on the non-X based CD player by Jean-Marc 34 * Zucconi and Andrey A. Chernov. Fixed and further modified on 35 * by Jukka Ukkonen <jau@funet.fi>. Lots of fixes and improvements 36 * made subsequently by The NetBSD Project. 37 * 38 * from FreeBSD: cdcontrol.c,v 1.17.2.1 1999/01/31 15:36:01 billf Exp 39 */ 40 41 #include <sys/cdefs.h> 42 #ifndef lint 43 __RCSID("$NetBSD: cdplay.c,v 1.21 2002/09/28 21:48:35 is Exp $"); 44 #endif /* not lint */ 45 46 #include <sys/types.h> 47 48 #include <sys/endian.h> 49 #include <sys/ioctl.h> 50 #include <sys/file.h> 51 #include <sys/cdio.h> 52 53 #include <ctype.h> 54 #include <err.h> 55 #include <errno.h> 56 #include <histedit.h> 57 #include <limits.h> 58 #include <signal.h> 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <unistd.h> 63 #include <util.h> 64 65 enum cmd { 66 CMD_CLOSE, 67 CMD_EJECT, 68 CMD_HELP, 69 CMD_INFO, 70 CMD_NEXT, 71 CMD_PAUSE, 72 CMD_PLAY, 73 CMD_PREV, 74 CMD_QUIT, 75 CMD_RESET, 76 CMD_RESUME, 77 CMD_SET, 78 CMD_SHUFFLE, 79 CMD_SKIP, 80 CMD_STATUS, 81 CMD_STOP, 82 CMD_VOLUME, 83 }; 84 85 struct cmdtab { 86 enum cmd command; 87 const char *name; 88 unsigned int min; 89 const char *args; 90 } const cmdtab[] = { 91 { CMD_HELP, "?", 1, 0 }, 92 { CMD_CLOSE, "close", 1, NULL }, 93 { CMD_EJECT, "eject", 1, NULL }, 94 { CMD_HELP, "help", 1, NULL }, 95 { CMD_INFO, "info", 1, NULL }, 96 { CMD_NEXT, "next", 1, NULL }, 97 { CMD_PAUSE, "pause", 2, NULL }, 98 { CMD_PLAY, "play", 1, "min1:sec1[.fram1] [min2:sec2[.fram2]]" }, 99 { CMD_PLAY, "play", 1, "track1[.index1] [track2[.index2]]" }, 100 { CMD_PLAY, "play", 1, "tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]" }, 101 { CMD_PLAY, "play", 1, "[#block [len]]" }, 102 { CMD_PREV, "prev", 2, NULL }, 103 { CMD_QUIT, "quit", 1, NULL }, 104 { CMD_RESET, "reset", 4, NULL }, 105 { CMD_RESUME, "resume", 4, NULL }, 106 { CMD_SET, "set", 2, "msf | lba" }, 107 { CMD_SHUFFLE, "shuffle", 2, NULL }, 108 { CMD_SKIP, "skip", 2, NULL }, 109 { CMD_STATUS, "status", 3, NULL }, 110 { CMD_STOP, "stop", 3, NULL }, 111 { CMD_VOLUME, "volume", 1, "<l> <r>|left|right|mute|mono|stereo" }, 112 }; 113 114 struct cd_toc_entry toc_buffer[100]; 115 116 const char *cdname; 117 int fd = -1; 118 int msf = 1; 119 int shuffle; 120 int interactive = 1; 121 struct itimerval itv_timer; 122 123 History *hist; 124 HistEvent he; 125 EditLine *elptr; 126 127 int get_vol(int *, int *); 128 int get_status(int *, int *, int *, int *, int *); 129 void help(void); 130 int info(const char *); 131 void lba2msf(u_long, u_int *, u_int *, u_int *); 132 int main(int, char **); 133 u_int msf2lba(u_int, u_int, u_int); 134 int opencd(void); 135 const char *parse(char *, int *); 136 int play(const char *, int); 137 int play_blocks(int, int); 138 int play_msf(int, int, int, int, int, int); 139 int play_track(int, int, int, int); 140 int print_status(const char *); 141 void print_track(struct cd_toc_entry *, int); 142 const char *prompt(void); 143 int read_toc_entrys(int); 144 int run(int, const char *); 145 int setvol(int, int); 146 void sig_timer(int, int, struct sigcontext *); 147 int skip(int, int); 148 const char *strstatus(int); 149 void usage(void); 150 151 int 152 main(int argc, char **argv) 153 { 154 const char *arg; 155 char buf[80], *p; 156 static char defdev[16]; 157 int cmd, len, c; 158 char *line; 159 const char *elline; 160 int scratch, rv; 161 struct sigaction sa_timer; 162 163 cdname = getenv("MUSIC_CD"); 164 if (cdname == NULL) 165 cdname = getenv("CD_DRIVE"); 166 if (cdname == NULL) 167 cdname = getenv("DISC"); 168 if (cdname == NULL) 169 cdname = getenv("CDPLAY"); 170 171 while ((c = getopt(argc, argv, "f:h")) != -1) 172 switch (c) { 173 case 'f': 174 cdname = optarg; 175 continue; 176 case 'h': 177 default: 178 usage(); 179 /* NOTREACHED */ 180 } 181 argc -= optind; 182 argv += optind; 183 184 if (argc > 0 && strcasecmp(*argv, "help") == 0) 185 usage(); 186 187 if (cdname == NULL) { 188 sprintf(defdev, "cd0%c", 'a' + getrawpartition()); 189 cdname = defdev; 190 } 191 192 opencd(); 193 srandom(time(NULL)); 194 195 if (argc > 0) { 196 interactive = 0; 197 for (p = buf; argc-- > 0; argv++) { 198 len = strlen(*argv); 199 200 if (p + len >= buf + sizeof(buf) - 1) 201 usage(); 202 if (p > buf) 203 *p++ = ' '; 204 205 strcpy(p, *argv); 206 p += len; 207 } 208 *p = '\0'; 209 arg = parse(buf, &cmd); 210 return (run(cmd, arg)); 211 } 212 213 printf("Type `?' for command list\n\n"); 214 215 hist = history_init(); 216 history(hist, &he, H_SETSIZE, 100); /* 100 elt history buffer */ 217 elptr = el_init(getprogname(), stdin, stdout, stderr); 218 el_set(elptr, EL_EDITOR, "emacs"); 219 el_set(elptr, EL_PROMPT, prompt); 220 el_set(elptr, EL_HIST, history, hist); 221 el_source(elptr, NULL); 222 223 sigemptyset(&sa_timer.sa_mask); 224 sa_timer.sa_handler = (void (*)(int))sig_timer; 225 sa_timer.sa_flags = SA_RESTART; 226 if ((rv = sigaction(SIGALRM, &sa_timer, NULL)) < 0) 227 err(EXIT_FAILURE, "sigaction()"); 228 229 for (;;) { 230 line = NULL; 231 do { 232 if (((elline = el_gets(elptr, &scratch)) != NULL) 233 && (scratch != 0)){ 234 history(hist, &he, H_ENTER, elline); 235 line = strdup(elline); 236 arg = parse(line, &cmd); 237 } else { 238 cmd = CMD_QUIT; 239 fprintf(stderr, "\r\n"); 240 arg = 0; 241 break; 242 } 243 } while (arg == NULL); 244 245 if (run(cmd, arg) < 0) { 246 if (fd != -1) 247 close(fd); 248 fd = -1; 249 } 250 fflush(stdout); 251 if (line != NULL) 252 free(line); 253 } 254 255 el_end(elptr); 256 history_end(hist); 257 exit(EXIT_SUCCESS); 258 /* NOTREACHED */ 259 } 260 261 void 262 usage(void) 263 { 264 265 fprintf(stderr, "usage: cdplay [-f device] [command ...]\n"); 266 exit(EXIT_FAILURE); 267 /* NOTREACHED */ 268 } 269 270 void 271 help(void) 272 { 273 const struct cmdtab *c, *mc; 274 const char *s; 275 int i, n; 276 277 mc = cmdtab + sizeof(cmdtab) / sizeof(cmdtab[0]); 278 for (c = cmdtab; c < mc; c++) { 279 for (i = c->min, s = c->name; *s != '\0'; s++, i--) { 280 n = (i > 0 ? toupper(*s) : *s); 281 putchar(n); 282 } 283 if (c->args != NULL) 284 printf(" %s", c->args); 285 putchar('\n'); 286 } 287 printf( 288 "\nThe word \"play\" is not required for the play commands.\n" 289 "The plain target address is taken as a synonym for play.\n"); 290 } 291 292 int 293 run(int cmd, const char *arg) 294 { 295 int l, r, rv; 296 297 if (cmd == CMD_QUIT) { 298 close(fd); 299 exit(EXIT_SUCCESS); 300 /* NOTREACHED */ 301 } 302 303 if (fd < 0 && !opencd()) 304 return (0); 305 306 switch (cmd) { 307 case CMD_INFO: 308 rv = info(arg); 309 break; 310 311 case CMD_STATUS: 312 rv = print_status(arg); 313 break; 314 315 case CMD_PAUSE: 316 if ((rv = ioctl(fd, CDIOCPAUSE)) < 0) 317 warn("ioctl(CDIOCPAUSE)"); 318 break; 319 320 case CMD_RESUME: 321 if ((rv = ioctl(fd, CDIOCRESUME)) < 0) 322 warn("ioctl(CDIOCRESUME)"); 323 break; 324 325 case CMD_STOP: 326 if ((rv = ioctl(fd, CDIOCSTOP)) < 0) 327 warn("ioctl(CDIOCSTOP)"); 328 if (ioctl(fd, CDIOCALLOW) < 0) 329 warn("ioctl(CDIOCALLOW)"); 330 break; 331 332 case CMD_RESET: 333 if ((rv = ioctl(fd, CDIOCRESET)) >= 0) { 334 close(fd); 335 fd = -1; 336 } else 337 warn("ioctl(CDIOCRESET)"); 338 return (0); 339 340 case CMD_EJECT: 341 if (shuffle) 342 run(CMD_SHUFFLE, NULL); 343 if (ioctl(fd, CDIOCALLOW) < 0) 344 warn("ioctl(CDIOCALLOW)"); 345 if ((rv = ioctl(fd, CDIOCEJECT)) < 0) 346 warn("ioctl(CDIOCEJECT)"); 347 break; 348 349 case CMD_CLOSE: 350 ioctl(fd, CDIOCALLOW); 351 if ((rv = ioctl(fd, CDIOCCLOSE)) >= 0) { 352 close(fd); 353 fd = -1; 354 } else 355 warn("ioctl(CDIOCCLOSE)"); 356 break; 357 358 case CMD_PLAY: 359 while (isspace(*arg)) 360 arg++; 361 rv = play(arg, 1); 362 break; 363 364 case CMD_PREV: 365 rv = skip(-1, 1); 366 break; 367 368 case CMD_NEXT: 369 rv = skip(1, 1); 370 break; 371 372 case CMD_SHUFFLE: 373 if (interactive == 0) 374 errx(EXIT_FAILURE, 375 "`shuffle' valid only in interactive mode"); 376 if (shuffle == 0) { 377 itv_timer.it_interval.tv_sec = 1; 378 itv_timer.it_interval.tv_usec = 0; 379 itv_timer.it_value.tv_sec = 1; 380 itv_timer.it_value.tv_usec = 0; 381 if (setitimer(ITIMER_REAL, &itv_timer, NULL) == 0) { 382 shuffle = 1; 383 skip(0, 1); 384 } 385 } else { 386 itv_timer.it_interval.tv_sec = 0; 387 itv_timer.it_interval.tv_usec = 0; 388 itv_timer.it_value.tv_sec = 0; 389 itv_timer.it_value.tv_usec = 0; 390 if (setitimer(ITIMER_REAL, &itv_timer, NULL) == 0) 391 shuffle = 0; 392 } 393 printf("shuffle play:\t%s\n", shuffle ? "on" : "off"); 394 rv = 0; 395 break; 396 397 case CMD_SKIP: 398 if (!interactive) 399 errx(EXIT_FAILURE, 400 "`skip' valid only in interactive mode"); 401 if (!shuffle) 402 warnx("`skip' valid only in shuffle mode"); 403 else 404 skip(0, 1); 405 break; 406 407 case CMD_SET: 408 if (strcasecmp(arg, "msf") == 0) 409 msf = 1; 410 else if (strcasecmp(arg, "lba") == 0) 411 msf = 0; 412 else 413 warnx("invalid command arguments"); 414 break; 415 416 case CMD_VOLUME: 417 if (strncasecmp(arg, "left", strlen(arg)) == 0) 418 rv = ioctl(fd, CDIOCSETLEFT); 419 else if (strncasecmp(arg, "right", strlen(arg)) == 0) 420 rv = ioctl(fd, CDIOCSETRIGHT); 421 else if (strncasecmp(arg, "mono", strlen(arg)) == 0) 422 rv = ioctl(fd, CDIOCSETMONO); 423 else if (strncasecmp(arg, "stereo", strlen(arg)) == 0) 424 rv = ioctl(fd, CDIOCSETSTEREO); 425 else if (strncasecmp(arg, "mute", strlen(arg)) == 0) 426 rv = ioctl(fd, CDIOCSETMUTE); 427 else { 428 rv = 0; 429 if (sscanf(arg, "%d %d", &l, &r) != 2) { 430 if (sscanf(arg, "%d", &l) == 1) 431 r = l; 432 else { 433 warnx("invalid command arguments"); 434 break; 435 } 436 } 437 rv = setvol(l, r); 438 } 439 break; 440 441 case CMD_HELP: 442 default: 443 help(); 444 rv = 0; 445 break; 446 } 447 448 return (rv); 449 } 450 451 int 452 play(const char *arg, int fromuser) 453 { 454 int rv, n, start, end, istart, iend, blk, len; 455 u_int tr1, tr2, m1, m2, s1, s2, f1, f2, tm, ts, tf; 456 struct ioc_toc_header h; 457 458 if (shuffle && fromuser) { 459 warnx("`play' not valid in shuffle mode"); 460 return (0); 461 } 462 463 if ((rv = ioctl(fd, CDIOREADTOCHEADER, &h)) < 0) { 464 warn("ioctl(CDIOREADTOCHEADER)"); 465 return (rv); 466 } 467 468 end = 0; 469 istart = iend = 1; 470 n = h.ending_track - h.starting_track + 1; 471 rv = read_toc_entrys((n + 1) * sizeof(struct cd_toc_entry)); 472 if (rv < 0) 473 return (rv); 474 475 if (arg == NULL || *arg == '\0') { 476 /* Play the whole disc */ 477 return (play_track(h.starting_track, 1, h.ending_track, 99)); 478 } 479 480 if (strchr(arg, '#') != NULL) { 481 /* Play block #blk [ len ] */ 482 len = 0; 483 484 if (2 != sscanf(arg, "#%d%d", &blk, &len) && 485 1 != sscanf(arg, "#%d", &blk)) 486 goto Clean_up; 487 488 if (len == 0) { 489 if (msf) 490 len = msf2lba(toc_buffer[n].addr.msf.minute, 491 toc_buffer[n].addr.msf.second, 492 toc_buffer[n].addr.msf.frame) - blk; 493 else 494 len = be32toh(toc_buffer[n].addr.lba) - blk; 495 } 496 return (play_blocks(blk, len)); 497 } 498 499 if (strchr(arg, ':') != NULL) { 500 /* 501 * Play MSF m1:s1 [ .f1 ] [ m2:s2 [ .f2 ] ] 502 * 503 * Will now also undestand timed addresses relative 504 * to the beginning of a track in the form... 505 * 506 * tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]] 507 */ 508 tr2 = m2 = s2 = f2 = f1 = 0; 509 if (8 == sscanf(arg, "%d %d:%d.%d %d %d:%d.%d", &tr1, &m1, 510 &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", &tr1, &m1, &s1, 515 &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", &tr1, &m1, &s1, 520 &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", &tr1, &m1, &s1, 525 &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", &tr1, &m1, &s1, &f1, 530 &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", &tr1, &m1, &s1, &m2, 535 &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", &tr1, &m1, &s1, &f1, 540 &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, 545 &s2)) 546 goto Play_Relative_Addresses; 547 548 tr2 = m2 = s2 = f2 = f1 = 0; 549 if (5 == sscanf(arg, "%d %d:%d %d %d", &tr1, &m1, &s1, &tr2, 550 &m2)) 551 goto Play_Relative_Addresses; 552 553 tr2 = m2 = s2 = f2 = f1 = 0; 554 if (5 == sscanf(arg, "%d %d:%d.%d %d", &tr1, &m1, &s1, &f1, 555 &tr2)) 556 goto Play_Relative_Addresses; 557 558 tr2 = m2 = s2 = f2 = f1 = 0; 559 if (4 == sscanf(arg, "%d %d:%d %d", &tr1, &m1, &s1, &tr2)) 560 goto Play_Relative_Addresses; 561 562 tr2 = m2 = s2 = f2 = f1 = 0; 563 if (4 == sscanf(arg, "%d %d:%d.%d", &tr1, &m1, &s1, &f1)) 564 goto Play_Relative_Addresses; 565 566 tr2 = m2 = s2 = f2 = f1 = 0; 567 if (3 == sscanf(arg, "%d %d:%d", &tr1, &m1, &s1)) 568 goto Play_Relative_Addresses; 569 570 tr2 = m2 = s2 = f2 = f1 = 0; 571 goto Try_Absolute_Timed_Addresses; 572 573 Play_Relative_Addresses: 574 if (!tr1) 575 tr1 = 1; 576 else if (tr1 > n) 577 tr1 = n; 578 579 if (msf) { 580 tm = toc_buffer[tr1].addr.msf.minute; 581 ts = toc_buffer[tr1].addr.msf.second; 582 tf = toc_buffer[tr1].addr.msf.frame; 583 } else 584 lba2msf(be32toh(toc_buffer[tr1].addr.lba), &tm, &ts, &tf); 585 if ((m1 > tm) || ((m1 == tm) && ((s1 > ts) || ((s1 == ts) && 586 (f1 > tf))))) { 587 warnx("Track %d is not that long.", tr1); 588 return (0); 589 } 590 tr1--; 591 592 f1 += tf; 593 if (f1 >= 75) { 594 s1 += f1 / 75; 595 f1 %= 75; 596 } 597 s1 += ts; 598 if (s1 >= 60) { 599 m1 += s1 / 60; 600 s1 %= 60; 601 } 602 m1 += tm; 603 604 if (!tr2) { 605 if (m2 || s2 || f2) { 606 tr2 = tr1; 607 f2 += f1; 608 if (f2 >= 75) { 609 s2 += f2 / 75; 610 f2 %= 75; 611 } 612 s2 += s1; 613 if (s2 > 60) { 614 m2 += s2 / 60; 615 s2 %= 60; 616 } 617 m2 += m1; 618 } else { 619 tr2 = n; 620 if (msf) { 621 m2 = toc_buffer[n].addr.msf.minute; 622 s2 = toc_buffer[n].addr.msf.second; 623 f2 = toc_buffer[n].addr.msf.frame; 624 } else { 625 lba2msf(be32toh(toc_buffer[n].addr.lba), 626 &tm, &ts, &tf); 627 m2 = tm; 628 s2 = ts; 629 f2 = tf; 630 } 631 } 632 } else { 633 if (tr2 > n) { 634 tr2 = n; 635 m2 = s2 = f2 = 0; 636 } else { 637 if (m2 || s2 || f2) 638 tr2--; 639 if (msf) { 640 tm = toc_buffer[tr2].addr.msf.minute; 641 ts = toc_buffer[tr2].addr.msf.second; 642 tf = toc_buffer[tr2].addr.msf.frame; 643 } else 644 lba2msf(be32toh(toc_buffer[tr2].addr.lba), 645 &tm, &ts, &tf); 646 f2 += tf; 647 if (f2 >= 75) { 648 s2 += f2 / 75; 649 f2 %= 75; 650 } 651 s2 += ts; 652 if (s2 > 60) { 653 m2 += s2 / 60; 654 s2 %= 60; 655 } 656 m2 += tm; 657 } 658 } 659 660 if (msf) { 661 tm = toc_buffer[n].addr.msf.minute; 662 ts = toc_buffer[n].addr.msf.second; 663 tf = toc_buffer[n].addr.msf.frame; 664 } else 665 lba2msf(be32toh(toc_buffer[n].addr.lba), &tm, &ts, &tf); 666 667 if ((tr2 < n) && ((m2 > tm) || ((m2 == tm) && ((s2 > ts) || 668 ((s2 == ts) && (f2 > tf)))))) { 669 warnx("The playing time of the disc is not that long."); 670 return (0); 671 } 672 673 return (play_msf(m1, s1, f1, m2, s2, f2)); 674 675 Try_Absolute_Timed_Addresses: 676 m2 = UINT_MAX; 677 678 if (6 != sscanf(arg, "%d:%d.%d%d:%d.%d", 679 &m1, &s1, &f1, &m2, &s2, &f2) && 680 5 != sscanf(arg, "%d:%d.%d%d:%d", &m1, &s1, &f1, &m2, &s2) && 681 5 != sscanf(arg, "%d:%d%d:%d.%d", &m1, &s1, &m2, &s2, &f2) && 682 3 != sscanf(arg, "%d:%d.%d", &m1, &s1, &f1) && 683 4 != sscanf(arg, "%d:%d%d:%d", &m1, &s1, &m2, &s2) && 684 2 != sscanf(arg, "%d:%d", &m1, &s1)) 685 goto Clean_up; 686 687 if (m2 == UINT_MAX) { 688 if (msf) { 689 m2 = toc_buffer[n].addr.msf.minute; 690 s2 = toc_buffer[n].addr.msf.second; 691 f2 = toc_buffer[n].addr.msf.frame; 692 } else { 693 lba2msf(be32toh(toc_buffer[n].addr.lba), 694 &tm, &ts, &tf); 695 m2 = tm; 696 s2 = ts; 697 f2 = tf; 698 } 699 } 700 return (play_msf(m1, s1, f1, m2, s2, f2)); 701 } 702 703 /* 704 * Play track trk1 [ .idx1 ] [ trk2 [ .idx2 ] ] 705 */ 706 if (4 != sscanf(arg, "%d.%d%d.%d", &start, &istart, &end, &iend) && 707 3 != sscanf(arg, "%d.%d%d", &start, &istart, &end) && 708 3 != sscanf(arg, "%d%d.%d", &start, &end, &iend) && 709 2 != sscanf(arg, "%d.%d", &start, &istart) && 710 2 != sscanf(arg, "%d%d", &start, &end) && 711 1 != sscanf(arg, "%d", &start)) 712 goto Clean_up; 713 714 if (end == 0) 715 end = n; 716 return (play_track(start, istart, end, iend)); 717 718 Clean_up: 719 warnx("invalid command arguments"); 720 return (0); 721 } 722 723 void 724 sig_timer(int sig, int code, struct sigcontext *scp) 725 { 726 sigset_t anymore; 727 728 sigpending(&anymore); 729 if (sigismember(&anymore, SIGALRM)) 730 return; 731 setitimer(ITIMER_REAL, &itv_timer, NULL); 732 if (fd != -1) 733 skip(0, 0); 734 } 735 736 int 737 skip(int dir, int fromuser) 738 { 739 char str[16]; 740 int rv, trk, idx, m, s, f; 741 struct ioc_toc_header h; 742 743 if ((rv = ioctl(fd, CDIOREADTOCHEADER, &h)) < 0) { 744 warn("ioctl(CDIOREADTOCHEADER)"); 745 return (rv); 746 } 747 if ((rv = get_status(&trk, &idx, &m, &s, &f)) < 0) 748 return (rv); 749 750 if (dir == 0) { 751 if (fromuser || (rv != CD_AS_PLAY_IN_PROGRESS && 752 rv != CD_AS_PLAY_PAUSED)) 753 trk = h.starting_track + 754 random() % (h.ending_track - h.starting_track + 1); 755 else 756 return (0); 757 } else { 758 trk += dir; 759 if (trk > h.ending_track) 760 trk = h.starting_track; 761 else if(trk < h.starting_track) 762 trk = h.ending_track; 763 } 764 765 if (shuffle) 766 sprintf(str, "%d %d", trk, trk); 767 else 768 sprintf(str, "%d", trk); 769 770 return (play(str, 0)); 771 } 772 773 const char * 774 strstatus(int sts) 775 { 776 const char *str; 777 778 switch (sts) { 779 case CD_AS_AUDIO_INVALID: 780 str = "invalid"; 781 break; 782 case CD_AS_PLAY_IN_PROGRESS: 783 str = "playing"; 784 break; 785 case CD_AS_PLAY_PAUSED: 786 str = "paused"; 787 break; 788 case CD_AS_PLAY_COMPLETED: 789 str = "completed"; 790 break; 791 case CD_AS_PLAY_ERROR: 792 str = "error"; 793 break; 794 case CD_AS_NO_STATUS: 795 str = "not playing"; 796 break; 797 default: 798 str = "<unknown>"; 799 break; 800 } 801 802 return (str); 803 } 804 805 int 806 print_status(const char *arg) 807 { 808 struct cd_sub_channel_info data; 809 struct ioc_read_subchannel ss; 810 int rv, trk, idx, m, s, f; 811 struct ioc_vol v; 812 813 if ((rv = get_status(&trk, &idx, &m, &s, &f)) >= 0) { 814 printf("audio status:\t%s\n", strstatus(rv)); 815 printf("current track:\t%d\n", trk); 816 printf("current index:\t%d\n", idx); 817 printf("position:\t%d:%02d.%02d\n", m, s, f); 818 } else 819 printf("audio status:\tno info available\n"); 820 821 printf("shuffle play:\t%s\n", shuffle ? "on" : "off"); 822 823 bzero(&ss, sizeof(ss)); 824 ss.data = &data; 825 ss.data_len = sizeof(data); 826 ss.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT; 827 ss.data_format = CD_MEDIA_CATALOG; 828 829 if (ioctl(fd, CDIOCREADSUBCHANNEL, (char *)&ss) >= 0) { 830 printf("media catalog:\t%sactive", 831 ss.data->what.media_catalog.mc_valid ? "" : "in"); 832 if (ss.data->what.media_catalog.mc_valid && 833 ss.data->what.media_catalog.mc_number[0]) 834 printf(" (%.15s)", 835 ss.data->what.media_catalog.mc_number); 836 putchar('\n'); 837 } else 838 printf("media catalog:\tnone\n"); 839 840 if (ioctl(fd, CDIOCGETVOL, &v) >= 0) { 841 printf("left volume:\t%d\n", v.vol[0]); 842 printf("right volume:\t%d\n", v.vol[1]); 843 } else { 844 printf("left volume:\tnot available\n"); 845 printf("right volume:\tnot available\n"); 846 } 847 848 ; return (0); 849 } 850 851 int 852 info(const char *arg) 853 { 854 struct ioc_toc_header h; 855 int rc, i, n; 856 857 if ((rc = ioctl(fd, CDIOREADTOCHEADER, &h)) < 0) { 858 warn("ioctl(CDIOREADTOCHEADER)"); 859 return (rc); 860 } 861 862 n = h.ending_track - h.starting_track + 1; 863 rc = read_toc_entrys((n + 1) * sizeof(struct cd_toc_entry)); 864 if (rc < 0) 865 return (rc); 866 867 printf("track start duration block length type\n"); 868 printf("-------------------------------------------------\n"); 869 870 for (i = 0; i < n; i++) { 871 printf("%5d ", toc_buffer[i].track); 872 print_track(toc_buffer + i, 0); 873 } 874 printf("%5d ", toc_buffer[n].track); 875 print_track(toc_buffer + n, 1); 876 return (0); 877 } 878 879 void 880 lba2msf(u_long lba, u_int *m, u_int *s, u_int *f) 881 { 882 883 lba += 150; /* block start offset */ 884 lba &= 0xffffff; /* negative lbas use only 24 bits */ 885 *m = lba / (60 * 75); 886 lba %= (60 * 75); 887 *s = lba / 75; 888 *f = lba % 75; 889 } 890 891 u_int 892 msf2lba(u_int m, u_int s, u_int f) 893 { 894 895 return (((m * 60) + s) * 75 + f) - 150; 896 } 897 898 void 899 print_track(struct cd_toc_entry *e, int lastflag) 900 { 901 int block, next, len; 902 u_int m, s, f; 903 904 if (msf) { 905 /* Print track start */ 906 printf("%2d:%02d.%02d ", e->addr.msf.minute, 907 e->addr.msf.second, e->addr.msf.frame); 908 909 block = msf2lba(e->addr.msf.minute, e->addr.msf.second, 910 e->addr.msf.frame); 911 } else { 912 block = e->addr.lba; 913 lba2msf(block, &m, &s, &f); 914 /* Print track start */ 915 printf("%2d:%02d.%02d ", m, s, f); 916 } 917 if (lastflag) { 918 /* Last track -- print block */ 919 printf(" - %6d - -\n", block); 920 return; 921 } 922 if (msf) 923 next = msf2lba(e[1].addr.msf.minute, e[1].addr.msf.second, 924 e[1].addr.msf.frame); 925 else 926 next = e[1].addr.lba; 927 len = next - block; 928 lba2msf(len, &m, &s, &f); 929 930 /* Print duration, block, length, type */ 931 printf("%2d:%02d.%02d %6d %6d %5s\n", m, s, f, block, len, 932 (e->control & 4) ? "data" : "audio"); 933 } 934 935 int 936 play_track(int tstart, int istart, int tend, int iend) 937 { 938 struct ioc_play_track t; 939 int rv; 940 941 t.start_track = tstart; 942 t.start_index = istart; 943 t.end_track = tend; 944 t.end_index = iend; 945 946 if ((rv = ioctl(fd, CDIOCPLAYTRACKS, &t)) < 0) 947 warn("ioctl(CDIOCPLAYTRACKS)"); 948 return (rv); 949 } 950 951 int 952 play_blocks(int blk, int len) 953 { 954 struct ioc_play_blocks t; 955 int rv; 956 957 t.blk = blk; 958 t.len = len; 959 960 if ((rv = ioctl(fd, CDIOCPLAYBLOCKS, &t)) < 0) 961 warn("ioctl(CDIOCPLAYBLOCKS"); 962 return (rv); 963 } 964 965 int 966 setvol(int left, int right) 967 { 968 struct ioc_vol v; 969 int rv; 970 971 v.vol[0] = left; 972 v.vol[1] = right; 973 v.vol[2] = 0; 974 v.vol[3] = 0; 975 976 if ((rv = ioctl(fd, CDIOCSETVOL, &v)) < 0) 977 warn("ioctl(CDIOCSETVOL)"); 978 return (rv); 979 } 980 981 int 982 read_toc_entrys(int len) 983 { 984 struct ioc_read_toc_entry t; 985 int rv; 986 987 t.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT; 988 t.starting_track = 0; 989 t.data_len = len; 990 t.data = toc_buffer; 991 992 if ((rv = ioctl(fd, CDIOREADTOCENTRYS, &t)) < 0) 993 warn("ioctl(CDIOREADTOCENTRYS)"); 994 return (rv); 995 } 996 997 int 998 play_msf(int start_m, int start_s, int start_f, int end_m, int end_s, 999 int end_f) 1000 { 1001 struct ioc_play_msf a; 1002 int rv; 1003 1004 a.start_m = start_m; 1005 a.start_s = start_s; 1006 a.start_f = start_f; 1007 a.end_m = end_m; 1008 a.end_s = end_s; 1009 a.end_f = end_f; 1010 1011 if ((rv = ioctl(fd, CDIOCPLAYMSF, &a)) < 0) 1012 warn("ioctl(CDIOREADTOCENTRYS)"); 1013 return (rv); 1014 } 1015 1016 int 1017 get_status(int *trk, int *idx, int *min, int *sec, int *frame) 1018 { 1019 struct ioc_read_subchannel s; 1020 struct cd_sub_channel_info data; 1021 u_int mm, ss, ff; 1022 int rv; 1023 1024 bzero(&s, sizeof(s)); 1025 s.data = &data; 1026 s.data_len = sizeof(data); 1027 s.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT; 1028 s.data_format = CD_CURRENT_POSITION; 1029 1030 if ((rv = ioctl(fd, CDIOCREADSUBCHANNEL, &s)) < 0) { 1031 warn("ioctl(CDIOCREADSUBCHANNEL)"); 1032 return (rv); 1033 } 1034 1035 *trk = s.data->what.position.track_number; 1036 *idx = s.data->what.position.index_number; 1037 if (msf) { 1038 *min = s.data->what.position.reladdr.msf.minute; 1039 *sec = s.data->what.position.reladdr.msf.second; 1040 *frame = s.data->what.position.reladdr.msf.frame; 1041 } else { 1042 lba2msf(be32toh(s.data->what.position.reladdr.lba), &mm, 1043 &ss, &ff); 1044 *min = mm; 1045 *sec = ss; 1046 *frame = ff; 1047 } 1048 1049 return (s.data->header.audio_status); 1050 } 1051 1052 const char * 1053 prompt(void) 1054 { 1055 1056 return ("cdplay> "); 1057 } 1058 1059 const char * 1060 parse(char *buf, int *cmd) 1061 { 1062 const struct cmdtab *c, *mc; 1063 char *p, *q; 1064 int len; 1065 1066 for (p = buf; isspace(*p); p++) 1067 continue; 1068 1069 if (isdigit(*p) || (p[0] == '#' && isdigit(p[1]))) { 1070 *cmd = CMD_PLAY; 1071 return (p); 1072 } 1073 1074 for (buf = p; *p != '\0' && !isspace(*p); p++) 1075 continue; 1076 1077 if ((len = p - buf) == 0) 1078 return (0); 1079 1080 if (*p != '\0') { /* It must be a spacing character! */ 1081 *p++ = 0; 1082 for (q = p; *q != '\0' && *q != '\n' && *q != '\r'; q++) 1083 continue; 1084 *q = 0; 1085 } 1086 1087 *cmd = -1; 1088 1089 mc = cmdtab + sizeof(cmdtab) / sizeof(cmdtab[0]); 1090 for (c = cmdtab; c < mc; c++) { 1091 /* Is it an exact match? */ 1092 if (strcasecmp(buf, c->name) == 0) { 1093 *cmd = c->command; 1094 break; 1095 } 1096 /* Try short hand forms then... */ 1097 if (len >= c->min && strncasecmp(buf, c->name, len) == 0) { 1098 if (*cmd != -1 && *cmd != c->command) { 1099 warnx("ambiguous command"); 1100 return (0); 1101 } 1102 *cmd = c->command; 1103 } 1104 } 1105 1106 if (*cmd == -1) { 1107 warnx("invalid command, enter ``help'' for commands"); 1108 return (0); 1109 } 1110 1111 while (isspace(*p)) 1112 p++; 1113 return (p); 1114 } 1115 1116 int 1117 opencd(void) 1118 { 1119 char devbuf[80]; 1120 1121 if (fd > -1) 1122 return (1); 1123 1124 fd = opendisk(cdname, O_RDONLY, devbuf, sizeof(devbuf), 0); 1125 if (fd < 0) { 1126 if (errno == ENXIO) { 1127 /* 1128 * ENXIO has an overloaded meaning here. The 1129 * original "Device not configured" should be 1130 * interpreted as "No disc in drive %s". 1131 */ 1132 warnx("no disc in drive %s", devbuf); 1133 return (0); 1134 } 1135 err(EXIT_FAILURE, "%s", devbuf); 1136 } 1137 1138 return (1); 1139 } 1140