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