1 /* 2 SDL - Simple DirectMedia Layer 3 Copyright (C) 1997-2006 Sam Lantinga 4 5 This library is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Lesser General Public 7 License as published by the Free Software Foundation; either 8 version 2.1 of the License, or (at your option) any later version. 9 10 This library is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General Public 16 License along with this library; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 19 Sam Lantinga 20 slouken (at) libsdl.org 21 */ 22 #include "SDL_config.h" 23 24 #ifdef SDL_CDROM_LINUX 25 26 /* Functions for system-level CD-ROM audio control */ 27 28 #include <string.h> /* For strerror() */ 29 #include <sys/types.h> 30 #include <sys/stat.h> 31 #include <sys/ioctl.h> 32 #include <fcntl.h> 33 #include <errno.h> 34 #include <unistd.h> 35 #ifdef __LINUX__ 36 #ifdef HAVE_LINUX_VERSION_H 37 /* linux 2.6.9 workaround */ 38 #include <linux/version.h> 39 #if LINUX_VERSION_CODE == KERNEL_VERSION(2,6,9) 40 #include <asm/types.h> 41 #define __le64 __u64 42 #define __le32 __u32 43 #define __le16 __u16 44 #define __be64 __u64 45 #define __be32 __u32 46 #define __be16 __u16 47 #endif /* linux 2.6.9 workaround */ 48 #endif /* HAVE_LINUX_VERSION_H */ 49 #include <linux/cdrom.h> 50 #endif 51 #ifdef __SVR4 52 #include <sys/cdio.h> 53 #endif 54 55 /* Define this to use the alternative getmntent() code */ 56 #ifndef __SVR4 57 #define USE_MNTENT 58 #endif 59 60 #ifdef USE_MNTENT 61 #if defined(__USLC__) 62 #include <sys/mntent.h> 63 #else 64 #include <mntent.h> 65 #endif 66 67 #ifndef _PATH_MNTTAB 68 #ifdef MNTTAB 69 #define _PATH_MNTTAB MNTTAB 70 #else 71 #define _PATH_MNTTAB "/etc/fstab" 72 #endif 73 #endif /* !_PATH_MNTTAB */ 74 75 #ifndef _PATH_MOUNTED 76 #define _PATH_MOUNTED "/etc/mtab" 77 #endif /* !_PATH_MOUNTED */ 78 79 #ifndef MNTTYPE_CDROM 80 #define MNTTYPE_CDROM "iso9660" 81 #endif 82 #ifndef MNTTYPE_SUPER 83 #define MNTTYPE_SUPER "supermount" 84 #endif 85 #endif /* USE_MNTENT */ 86 87 #include "SDL_cdrom.h" 88 #include "../SDL_syscdrom.h" 89 90 91 /* The maximum number of CD-ROM drives we'll detect */ 92 #define MAX_DRIVES 16 93 94 /* A list of available CD-ROM drives */ 95 static char *SDL_cdlist[MAX_DRIVES]; 96 static dev_t SDL_cdmode[MAX_DRIVES]; 97 98 /* The system-dependent CD control functions */ 99 static const char *SDL_SYS_CDName(int drive); 100 static int SDL_SYS_CDOpen(int drive); 101 static int SDL_SYS_CDGetTOC(SDL_CD *cdrom); 102 static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position); 103 static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length); 104 static int SDL_SYS_CDPause(SDL_CD *cdrom); 105 static int SDL_SYS_CDResume(SDL_CD *cdrom); 106 static int SDL_SYS_CDStop(SDL_CD *cdrom); 107 static int SDL_SYS_CDEject(SDL_CD *cdrom); 108 static void SDL_SYS_CDClose(SDL_CD *cdrom); 109 110 /* Some ioctl() errno values which occur when the tray is empty */ 111 #ifndef ENOMEDIUM 112 #define ENOMEDIUM ENOENT 113 #endif 114 #define ERRNO_TRAYEMPTY(errno) \ 115 ((errno == EIO) || (errno == ENOENT) || \ 116 (errno == EINVAL) || (errno == ENOMEDIUM)) 117 118 /* Check a drive to see if it is a CD-ROM */ 119 static int CheckDrive(char *drive, char *mnttype, struct stat *stbuf) 120 { 121 int is_cd, cdfd; 122 struct cdrom_subchnl info; 123 124 /* If it doesn't exist, return -1 */ 125 if ( stat(drive, stbuf) < 0 ) { 126 return(-1); 127 } 128 129 /* If it does exist, verify that it's an available CD-ROM */ 130 is_cd = 0; 131 if ( S_ISCHR(stbuf->st_mode) || S_ISBLK(stbuf->st_mode) ) { 132 cdfd = open(drive, (O_RDONLY|O_NONBLOCK), 0); 133 if ( cdfd >= 0 ) { 134 info.cdsc_format = CDROM_MSF; 135 /* Under Linux, EIO occurs when a disk is not present. 136 */ 137 if ( (ioctl(cdfd, CDROMSUBCHNL, &info) == 0) || 138 ERRNO_TRAYEMPTY(errno) ) { 139 is_cd = 1; 140 } 141 close(cdfd); 142 } 143 #ifdef USE_MNTENT 144 /* Even if we can't read it, it might be mounted */ 145 else if ( mnttype && (SDL_strcmp(mnttype, MNTTYPE_CDROM) == 0) ) { 146 is_cd = 1; 147 } 148 #endif 149 } 150 return(is_cd); 151 } 152 153 /* Add a CD-ROM drive to our list of valid drives */ 154 static void AddDrive(char *drive, struct stat *stbuf) 155 { 156 int i; 157 158 if ( SDL_numcds < MAX_DRIVES ) { 159 /* Check to make sure it's not already in our list. 160 This can happen when we see a drive via symbolic link. 161 */ 162 for ( i=0; i<SDL_numcds; ++i ) { 163 if ( stbuf->st_rdev == SDL_cdmode[i] ) { 164 #ifdef DEBUG_CDROM 165 fprintf(stderr, "Duplicate drive detected: %s == %s\n", drive, SDL_cdlist[i]); 166 #endif 167 return; 168 } 169 } 170 171 /* Add this drive to our list */ 172 i = SDL_numcds; 173 SDL_cdlist[i] = SDL_strdup(drive); 174 if ( SDL_cdlist[i] == NULL ) { 175 SDL_OutOfMemory(); 176 return; 177 } 178 SDL_cdmode[i] = stbuf->st_rdev; 179 ++SDL_numcds; 180 #ifdef DEBUG_CDROM 181 fprintf(stderr, "Added CD-ROM drive: %s\n", drive); 182 #endif 183 } 184 } 185 186 #ifdef USE_MNTENT 187 static void CheckMounts(const char *mtab) 188 { 189 FILE *mntfp; 190 struct mntent *mntent; 191 struct stat stbuf; 192 193 mntfp = setmntent(mtab, "r"); 194 if ( mntfp != NULL ) { 195 char *tmp; 196 char *mnt_type; 197 size_t mnt_type_len; 198 char *mnt_dev; 199 size_t mnt_dev_len; 200 201 while ( (mntent=getmntent(mntfp)) != NULL ) { 202 mnt_type_len = SDL_strlen(mntent->mnt_type) + 1; 203 mnt_type = SDL_stack_alloc(char, mnt_type_len); 204 if (mnt_type == NULL) 205 continue; /* maybe you'll get lucky next time. */ 206 207 mnt_dev_len = SDL_strlen(mntent->mnt_fsname) + 1; 208 mnt_dev = SDL_stack_alloc(char, mnt_dev_len); 209 if (mnt_dev == NULL) { 210 SDL_stack_free(mnt_type); 211 continue; 212 } 213 214 SDL_strlcpy(mnt_type, mntent->mnt_type, mnt_type_len); 215 SDL_strlcpy(mnt_dev, mntent->mnt_fsname, mnt_dev_len); 216 217 /* Handle "supermount" filesystem mounts */ 218 if ( SDL_strcmp(mnt_type, MNTTYPE_SUPER) == 0 ) { 219 tmp = SDL_strstr(mntent->mnt_opts, "fs="); 220 if ( tmp ) { 221 SDL_stack_free(mnt_type); 222 mnt_type = SDL_strdup(tmp + SDL_strlen("fs=")); 223 if ( mnt_type ) { 224 tmp = SDL_strchr(mnt_type, ','); 225 if ( tmp ) { 226 *tmp = '\0'; 227 } 228 } 229 } 230 tmp = SDL_strstr(mntent->mnt_opts, "dev="); 231 if ( tmp ) { 232 SDL_stack_free(mnt_dev); 233 mnt_dev = SDL_strdup(tmp + SDL_strlen("dev=")); 234 if ( mnt_dev ) { 235 tmp = SDL_strchr(mnt_dev, ','); 236 if ( tmp ) { 237 *tmp = '\0'; 238 } 239 } 240 } 241 } 242 if ( SDL_strcmp(mnt_type, MNTTYPE_CDROM) == 0 ) { 243 #ifdef DEBUG_CDROM 244 fprintf(stderr, "Checking mount path from %s: %s mounted on %s of %s\n", 245 mtab, mnt_dev, mntent->mnt_dir, mnt_type); 246 #endif 247 if (CheckDrive(mnt_dev, mnt_type, &stbuf) > 0) { 248 AddDrive(mnt_dev, &stbuf); 249 } 250 } 251 SDL_stack_free(mnt_dev); 252 SDL_stack_free(mnt_type); 253 } 254 endmntent(mntfp); 255 } 256 } 257 #endif /* USE_MNTENT */ 258 259 int SDL_SYS_CDInit(void) 260 { 261 /* checklist: /dev/cdrom, /dev/hd?, /dev/scd? /dev/sr? */ 262 static char *checklist[] = { 263 "cdrom", "?a hd?", "?0 scd?", "?0 sr?", NULL 264 }; 265 char *SDLcdrom; 266 int i, j, exists; 267 char drive[32]; 268 struct stat stbuf; 269 270 /* Fill in our driver capabilities */ 271 SDL_CDcaps.Name = SDL_SYS_CDName; 272 SDL_CDcaps.Open = SDL_SYS_CDOpen; 273 SDL_CDcaps.GetTOC = SDL_SYS_CDGetTOC; 274 SDL_CDcaps.Status = SDL_SYS_CDStatus; 275 SDL_CDcaps.Play = SDL_SYS_CDPlay; 276 SDL_CDcaps.Pause = SDL_SYS_CDPause; 277 SDL_CDcaps.Resume = SDL_SYS_CDResume; 278 SDL_CDcaps.Stop = SDL_SYS_CDStop; 279 SDL_CDcaps.Eject = SDL_SYS_CDEject; 280 SDL_CDcaps.Close = SDL_SYS_CDClose; 281 282 /* Look in the environment for our CD-ROM drive list */ 283 SDLcdrom = SDL_getenv("SDL_CDROM"); /* ':' separated list of devices */ 284 if ( SDLcdrom != NULL ) { 285 char *cdpath, *delim; 286 size_t len = SDL_strlen(SDLcdrom)+1; 287 cdpath = SDL_stack_alloc(char, len); 288 if ( cdpath != NULL ) { 289 SDL_strlcpy(cdpath, SDLcdrom, len); 290 SDLcdrom = cdpath; 291 do { 292 delim = SDL_strchr(SDLcdrom, ':'); 293 if ( delim ) { 294 *delim++ = '\0'; 295 } 296 #ifdef DEBUG_CDROM 297 fprintf(stderr, "Checking CD-ROM drive from SDL_CDROM: %s\n", SDLcdrom); 298 #endif 299 if ( CheckDrive(SDLcdrom, NULL, &stbuf) > 0 ) { 300 AddDrive(SDLcdrom, &stbuf); 301 } 302 if ( delim ) { 303 SDLcdrom = delim; 304 } else { 305 SDLcdrom = NULL; 306 } 307 } while ( SDLcdrom ); 308 SDL_stack_free(cdpath); 309 } 310 311 /* If we found our drives, there's nothing left to do */ 312 if ( SDL_numcds > 0 ) { 313 return(0); 314 } 315 } 316 317 #ifdef USE_MNTENT 318 /* Check /dev/cdrom first :-) */ 319 if (CheckDrive("/dev/cdrom", NULL, &stbuf) > 0) { 320 AddDrive("/dev/cdrom", &stbuf); 321 } 322 323 /* Now check the currently mounted CD drives */ 324 CheckMounts(_PATH_MOUNTED); 325 326 /* Finally check possible mountable drives in /etc/fstab */ 327 CheckMounts(_PATH_MNTTAB); 328 329 /* If we found our drives, there's nothing left to do */ 330 if ( SDL_numcds > 0 ) { 331 return(0); 332 } 333 #endif /* USE_MNTENT */ 334 335 /* Scan the system for CD-ROM drives. 336 Not always 100% reliable, so use the USE_MNTENT code above first. 337 */ 338 for ( i=0; checklist[i]; ++i ) { 339 if ( checklist[i][0] == '?' ) { 340 char *insert; 341 exists = 1; 342 for ( j=checklist[i][1]; exists; ++j ) { 343 SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%s", &checklist[i][3]); 344 insert = SDL_strchr(drive, '?'); 345 if ( insert != NULL ) { 346 *insert = j; 347 } 348 #ifdef DEBUG_CDROM 349 fprintf(stderr, "Checking possible CD-ROM drive: %s\n", drive); 350 #endif 351 switch (CheckDrive(drive, NULL, &stbuf)) { 352 /* Drive exists and is a CD-ROM */ 353 case 1: 354 AddDrive(drive, &stbuf); 355 break; 356 /* Drive exists, but isn't a CD-ROM */ 357 case 0: 358 break; 359 /* Drive doesn't exist */ 360 case -1: 361 exists = 0; 362 break; 363 } 364 } 365 } else { 366 SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%s", checklist[i]); 367 #ifdef DEBUG_CDROM 368 fprintf(stderr, "Checking possible CD-ROM drive: %s\n", drive); 369 #endif 370 if ( CheckDrive(drive, NULL, &stbuf) > 0 ) { 371 AddDrive(drive, &stbuf); 372 } 373 } 374 } 375 return(0); 376 } 377 378 /* General ioctl() CD-ROM command function */ 379 static int SDL_SYS_CDioctl(int id, int command, void *arg) 380 { 381 int retval; 382 383 retval = ioctl(id, command, arg); 384 if ( retval < 0 ) { 385 SDL_SetError("ioctl() error: %s", strerror(errno)); 386 } 387 return(retval); 388 } 389 390 static const char *SDL_SYS_CDName(int drive) 391 { 392 return(SDL_cdlist[drive]); 393 } 394 395 static int SDL_SYS_CDOpen(int drive) 396 { 397 return(open(SDL_cdlist[drive], (O_RDONLY|O_NONBLOCK), 0)); 398 } 399 400 static int SDL_SYS_CDGetTOC(SDL_CD *cdrom) 401 { 402 struct cdrom_tochdr toc; 403 int i, okay; 404 struct cdrom_tocentry entry; 405 406 okay = 0; 407 if ( SDL_SYS_CDioctl(cdrom->id, CDROMREADTOCHDR, &toc) == 0 ) { 408 cdrom->numtracks = toc.cdth_trk1-toc.cdth_trk0+1; 409 if ( cdrom->numtracks > SDL_MAX_TRACKS ) { 410 cdrom->numtracks = SDL_MAX_TRACKS; 411 } 412 /* Read all the track TOC entries */ 413 for ( i=0; i<=cdrom->numtracks; ++i ) { 414 if ( i == cdrom->numtracks ) { 415 cdrom->track[i].id = CDROM_LEADOUT; 416 } else { 417 cdrom->track[i].id = toc.cdth_trk0+i; 418 } 419 entry.cdte_track = cdrom->track[i].id; 420 entry.cdte_format = CDROM_MSF; 421 if ( SDL_SYS_CDioctl(cdrom->id, CDROMREADTOCENTRY, 422 &entry) < 0 ) { 423 break; 424 } else { 425 if ( entry.cdte_ctrl & CDROM_DATA_TRACK ) { 426 cdrom->track[i].type = SDL_DATA_TRACK; 427 } else { 428 cdrom->track[i].type = SDL_AUDIO_TRACK; 429 } 430 cdrom->track[i].offset = MSF_TO_FRAMES( 431 entry.cdte_addr.msf.minute, 432 entry.cdte_addr.msf.second, 433 entry.cdte_addr.msf.frame); 434 cdrom->track[i].length = 0; 435 if ( i > 0 ) { 436 cdrom->track[i-1].length = 437 cdrom->track[i].offset- 438 cdrom->track[i-1].offset; 439 } 440 } 441 } 442 if ( i == (cdrom->numtracks+1) ) { 443 okay = 1; 444 } 445 } 446 return(okay ? 0 : -1); 447 } 448 449 /* Get CD-ROM status */ 450 static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position) 451 { 452 CDstatus status; 453 struct cdrom_tochdr toc; 454 struct cdrom_subchnl info; 455 456 info.cdsc_format = CDROM_MSF; 457 if ( ioctl(cdrom->id, CDROMSUBCHNL, &info) < 0 ) { 458 if ( ERRNO_TRAYEMPTY(errno) ) { 459 status = CD_TRAYEMPTY; 460 } else { 461 status = CD_ERROR; 462 } 463 } else { 464 switch (info.cdsc_audiostatus) { 465 case CDROM_AUDIO_INVALID: 466 case CDROM_AUDIO_NO_STATUS: 467 /* Try to determine if there's a CD available */ 468 if (ioctl(cdrom->id, CDROMREADTOCHDR, &toc)==0) 469 status = CD_STOPPED; 470 else 471 status = CD_TRAYEMPTY; 472 break; 473 case CDROM_AUDIO_COMPLETED: 474 status = CD_STOPPED; 475 break; 476 case CDROM_AUDIO_PLAY: 477 status = CD_PLAYING; 478 break; 479 case CDROM_AUDIO_PAUSED: 480 /* Workaround buggy CD-ROM drive */ 481 if ( info.cdsc_trk == CDROM_LEADOUT ) { 482 status = CD_STOPPED; 483 } else { 484 status = CD_PAUSED; 485 } 486 break; 487 default: 488 status = CD_ERROR; 489 break; 490 } 491 } 492 if ( position ) { 493 if ( status == CD_PLAYING || (status == CD_PAUSED) ) { 494 *position = MSF_TO_FRAMES( 495 info.cdsc_absaddr.msf.minute, 496 info.cdsc_absaddr.msf.second, 497 info.cdsc_absaddr.msf.frame); 498 } else { 499 *position = 0; 500 } 501 } 502 return(status); 503 } 504 505 /* Start play */ 506 static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length) 507 { 508 struct cdrom_msf playtime; 509 510 FRAMES_TO_MSF(start, 511 &playtime.cdmsf_min0, &playtime.cdmsf_sec0, &playtime.cdmsf_frame0); 512 FRAMES_TO_MSF(start+length, 513 &playtime.cdmsf_min1, &playtime.cdmsf_sec1, &playtime.cdmsf_frame1); 514 #ifdef DEBUG_CDROM 515 fprintf(stderr, "Trying to play from %d:%d:%d to %d:%d:%d\n", 516 playtime.cdmsf_min0, playtime.cdmsf_sec0, playtime.cdmsf_frame0, 517 playtime.cdmsf_min1, playtime.cdmsf_sec1, playtime.cdmsf_frame1); 518 #endif 519 return(SDL_SYS_CDioctl(cdrom->id, CDROMPLAYMSF, &playtime)); 520 } 521 522 /* Pause play */ 523 static int SDL_SYS_CDPause(SDL_CD *cdrom) 524 { 525 return(SDL_SYS_CDioctl(cdrom->id, CDROMPAUSE, 0)); 526 } 527 528 /* Resume play */ 529 static int SDL_SYS_CDResume(SDL_CD *cdrom) 530 { 531 return(SDL_SYS_CDioctl(cdrom->id, CDROMRESUME, 0)); 532 } 533 534 /* Stop play */ 535 static int SDL_SYS_CDStop(SDL_CD *cdrom) 536 { 537 return(SDL_SYS_CDioctl(cdrom->id, CDROMSTOP, 0)); 538 } 539 540 /* Eject the CD-ROM */ 541 static int SDL_SYS_CDEject(SDL_CD *cdrom) 542 { 543 return(SDL_SYS_CDioctl(cdrom->id, CDROMEJECT, 0)); 544 } 545 546 /* Close the CD-ROM handle */ 547 static void SDL_SYS_CDClose(SDL_CD *cdrom) 548 { 549 close(cdrom->id); 550 } 551 552 void SDL_SYS_CDQuit(void) 553 { 554 int i; 555 556 if ( SDL_numcds > 0 ) { 557 for ( i=0; i<SDL_numcds; ++i ) { 558 SDL_free(SDL_cdlist[i]); 559 } 560 SDL_numcds = 0; 561 } 562 } 563 564 #endif /* SDL_CDROM_LINUX */ 565