1/* 2 SDL - Simple DirectMedia Layer 3 Copyright (C) 1997-2012 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@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 */ 95static char *SDL_cdlist[MAX_DRIVES]; 96static dev_t SDL_cdmode[MAX_DRIVES]; 97 98/* The system-dependent CD control functions */ 99static const char *SDL_SYS_CDName(int drive); 100static int SDL_SYS_CDOpen(int drive); 101static int SDL_SYS_CDGetTOC(SDL_CD *cdrom); 102static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position); 103static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length); 104static int SDL_SYS_CDPause(SDL_CD *cdrom); 105static int SDL_SYS_CDResume(SDL_CD *cdrom); 106static int SDL_SYS_CDStop(SDL_CD *cdrom); 107static int SDL_SYS_CDEject(SDL_CD *cdrom); 108static 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 */ 119static 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 */ 154static 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 187static 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 259int 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 */ 379static 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 390static const char *SDL_SYS_CDName(int drive) 391{ 392 return(SDL_cdlist[drive]); 393} 394 395static int SDL_SYS_CDOpen(int drive) 396{ 397 return(open(SDL_cdlist[drive], (O_RDONLY|O_NONBLOCK), 0)); 398} 399 400static 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 */ 450static 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 */ 506static 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 */ 523static int SDL_SYS_CDPause(SDL_CD *cdrom) 524{ 525 return(SDL_SYS_CDioctl(cdrom->id, CDROMPAUSE, 0)); 526} 527 528/* Resume play */ 529static int SDL_SYS_CDResume(SDL_CD *cdrom) 530{ 531 return(SDL_SYS_CDioctl(cdrom->id, CDROMRESUME, 0)); 532} 533 534/* Stop play */ 535static int SDL_SYS_CDStop(SDL_CD *cdrom) 536{ 537 return(SDL_SYS_CDioctl(cdrom->id, CDROMSTOP, 0)); 538} 539 540/* Eject the CD-ROM */ 541static 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 */ 547static void SDL_SYS_CDClose(SDL_CD *cdrom) 548{ 549 close(cdrom->id); 550} 551 552void 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