src/cdrom/linux/SDL_syscdrom.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 29 May 2006 04:04:35 +0000
branchSDL-1.3
changeset 1668 4da1ee79c9af
parent 1662 782fd950bd46
permissions -rw-r--r--
more tweaking indent options
     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@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
   120 CheckDrive(char *drive, char *mnttype, struct stat *stbuf)
   121 {
   122     int is_cd, cdfd;
   123     struct cdrom_subchnl info;
   124 
   125     /* If it doesn't exist, return -1 */
   126     if (stat(drive, stbuf) < 0) {
   127         return (-1);
   128     }
   129 
   130     /* If it does exist, verify that it's an available CD-ROM */
   131     is_cd = 0;
   132     if (S_ISCHR(stbuf->st_mode) || S_ISBLK(stbuf->st_mode)) {
   133         cdfd = open(drive, (O_RDONLY | O_NONBLOCK), 0);
   134         if (cdfd >= 0) {
   135             info.cdsc_format = CDROM_MSF;
   136             /* Under Linux, EIO occurs when a disk is not present.
   137              */
   138             if ((ioctl(cdfd, CDROMSUBCHNL, &info) == 0) ||
   139                 ERRNO_TRAYEMPTY(errno)) {
   140                 is_cd = 1;
   141             }
   142             close(cdfd);
   143         }
   144 #ifdef USE_MNTENT
   145         /* Even if we can't read it, it might be mounted */
   146         else if (mnttype && (SDL_strcmp(mnttype, MNTTYPE_CDROM) == 0)) {
   147             is_cd = 1;
   148         }
   149 #endif
   150     }
   151     return (is_cd);
   152 }
   153 
   154 /* Add a CD-ROM drive to our list of valid drives */
   155 static void
   156 AddDrive(char *drive, struct stat *stbuf)
   157 {
   158     int i;
   159 
   160     if (SDL_numcds < MAX_DRIVES) {
   161         /* Check to make sure it's not already in our list.
   162            This can happen when we see a drive via symbolic link.
   163          */
   164         for (i = 0; i < SDL_numcds; ++i) {
   165             if (stbuf->st_rdev == SDL_cdmode[i]) {
   166 #ifdef DEBUG_CDROM
   167                 fprintf(stderr, "Duplicate drive detected: %s == %s\n",
   168                         drive, SDL_cdlist[i]);
   169 #endif
   170                 return;
   171             }
   172         }
   173 
   174         /* Add this drive to our list */
   175         i = SDL_numcds;
   176         SDL_cdlist[i] = SDL_strdup(drive);
   177         if (SDL_cdlist[i] == NULL) {
   178             SDL_OutOfMemory();
   179             return;
   180         }
   181         SDL_cdmode[i] = stbuf->st_rdev;
   182         ++SDL_numcds;
   183 #ifdef DEBUG_CDROM
   184         fprintf(stderr, "Added CD-ROM drive: %s\n", drive);
   185 #endif
   186     }
   187 }
   188 
   189 #ifdef USE_MNTENT
   190 static void
   191 CheckMounts(const char *mtab)
   192 {
   193     FILE *mntfp;
   194     struct mntent *mntent;
   195     struct stat stbuf;
   196 
   197     mntfp = setmntent(mtab, "r");
   198     if (mntfp != NULL) {
   199         char *tmp;
   200         char *mnt_type;
   201         size_t mnt_type_len;
   202         char *mnt_dev;
   203         size_t mnt_dev_len;
   204 
   205         while ((mntent = getmntent(mntfp)) != NULL) {
   206             mnt_type_len = SDL_strlen(mntent->mnt_type) + 1;
   207             mnt_type = SDL_stack_alloc(char, mnt_type_len);
   208             if (mnt_type == NULL)
   209                 continue;       /* maybe you'll get lucky next time. */
   210 
   211             mnt_dev_len = SDL_strlen(mntent->mnt_fsname) + 1;
   212             mnt_dev = SDL_stack_alloc(char, mnt_dev_len);
   213             if (mnt_dev == NULL) {
   214                 SDL_stack_free(mnt_type);
   215                 continue;
   216             }
   217 
   218             SDL_strlcpy(mnt_type, mntent->mnt_type, mnt_type_len);
   219             SDL_strlcpy(mnt_dev, mntent->mnt_fsname, mnt_dev_len);
   220 
   221             /* Handle "supermount" filesystem mounts */
   222             if (SDL_strcmp(mnt_type, MNTTYPE_SUPER) == 0) {
   223                 tmp = SDL_strstr(mntent->mnt_opts, "fs=");
   224                 if (tmp) {
   225                     SDL_free(mnt_type);
   226                     mnt_type = SDL_strdup(tmp + SDL_strlen("fs="));
   227                     if (mnt_type) {
   228                         tmp = SDL_strchr(mnt_type, ',');
   229                         if (tmp) {
   230                             *tmp = '\0';
   231                         }
   232                     }
   233                 }
   234                 tmp = SDL_strstr(mntent->mnt_opts, "dev=");
   235                 if (tmp) {
   236                     SDL_free(mnt_dev);
   237                     mnt_dev = SDL_strdup(tmp + SDL_strlen("dev="));
   238                     if (mnt_dev) {
   239                         tmp = SDL_strchr(mnt_dev, ',');
   240                         if (tmp) {
   241                             *tmp = '\0';
   242                         }
   243                     }
   244                 }
   245             }
   246             if (SDL_strcmp(mnt_type, MNTTYPE_CDROM) == 0) {
   247 #ifdef DEBUG_CDROM
   248                 fprintf(stderr,
   249                         "Checking mount path from %s: %s mounted on %s of %s\n",
   250                         mtab, mnt_dev, mntent->mnt_dir, mnt_type);
   251 #endif
   252                 if (CheckDrive(mnt_dev, mnt_type, &stbuf) > 0) {
   253                     AddDrive(mnt_dev, &stbuf);
   254                 }
   255             }
   256             SDL_stack_free(mnt_dev);
   257             SDL_stack_free(mnt_type);
   258         }
   259         endmntent(mntfp);
   260     }
   261 }
   262 #endif /* USE_MNTENT */
   263 
   264 int
   265 SDL_SYS_CDInit(void)
   266 {
   267     /* checklist: /dev/cdrom, /dev/hd?, /dev/scd? /dev/sr? */
   268     static char *checklist[] = {
   269         "cdrom", "?a hd?", "?0 scd?", "?0 sr?", NULL
   270     };
   271     char *SDLcdrom;
   272     int i, j, exists;
   273     char drive[32];
   274     struct stat stbuf;
   275 
   276     /* Fill in our driver capabilities */
   277     SDL_CDcaps.Name = SDL_SYS_CDName;
   278     SDL_CDcaps.Open = SDL_SYS_CDOpen;
   279     SDL_CDcaps.GetTOC = SDL_SYS_CDGetTOC;
   280     SDL_CDcaps.Status = SDL_SYS_CDStatus;
   281     SDL_CDcaps.Play = SDL_SYS_CDPlay;
   282     SDL_CDcaps.Pause = SDL_SYS_CDPause;
   283     SDL_CDcaps.Resume = SDL_SYS_CDResume;
   284     SDL_CDcaps.Stop = SDL_SYS_CDStop;
   285     SDL_CDcaps.Eject = SDL_SYS_CDEject;
   286     SDL_CDcaps.Close = SDL_SYS_CDClose;
   287 
   288     /* Look in the environment for our CD-ROM drive list */
   289     SDLcdrom = SDL_getenv("SDL_CDROM"); /* ':' separated list of devices */
   290     if (SDLcdrom != NULL) {
   291         char *cdpath, *delim;
   292         size_t len = SDL_strlen(SDLcdrom) + 1;
   293         cdpath = SDL_stack_alloc(char, len);
   294         if (cdpath != NULL) {
   295             SDL_strlcpy(cdpath, SDLcdrom, len);
   296             SDLcdrom = cdpath;
   297             do {
   298                 delim = SDL_strchr(SDLcdrom, ':');
   299                 if (delim) {
   300                     *delim++ = '\0';
   301                 }
   302 #ifdef DEBUG_CDROM
   303                 fprintf(stderr,
   304                         "Checking CD-ROM drive from SDL_CDROM: %s\n",
   305                         SDLcdrom);
   306 #endif
   307                 if (CheckDrive(SDLcdrom, NULL, &stbuf) > 0) {
   308                     AddDrive(SDLcdrom, &stbuf);
   309                 }
   310                 if (delim) {
   311                     SDLcdrom = delim;
   312                 } else {
   313                     SDLcdrom = NULL;
   314                 }
   315             }
   316             while (SDLcdrom);
   317             SDL_stack_free(cdpath);
   318         }
   319 
   320         /* If we found our drives, there's nothing left to do */
   321         if (SDL_numcds > 0) {
   322             return (0);
   323         }
   324     }
   325 #ifdef USE_MNTENT
   326     /* Check /dev/cdrom first :-) */
   327     if (CheckDrive("/dev/cdrom", NULL, &stbuf) > 0) {
   328         AddDrive("/dev/cdrom", &stbuf);
   329     }
   330 
   331     /* Now check the currently mounted CD drives */
   332     CheckMounts(_PATH_MOUNTED);
   333 
   334     /* Finally check possible mountable drives in /etc/fstab */
   335     CheckMounts(_PATH_MNTTAB);
   336 
   337     /* If we found our drives, there's nothing left to do */
   338     if (SDL_numcds > 0) {
   339         return (0);
   340     }
   341 #endif /* USE_MNTENT */
   342 
   343     /* Scan the system for CD-ROM drives.
   344        Not always 100% reliable, so use the USE_MNTENT code above first.
   345      */
   346     for (i = 0; checklist[i]; ++i) {
   347         if (checklist[i][0] == '?') {
   348             char *insert;
   349             exists = 1;
   350             for (j = checklist[i][1]; exists; ++j) {
   351                 SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%s",
   352                              &checklist[i][3]);
   353                 insert = SDL_strchr(drive, '?');
   354                 if (insert != NULL) {
   355                     *insert = j;
   356                 }
   357 #ifdef DEBUG_CDROM
   358                 fprintf(stderr, "Checking possible CD-ROM drive: %s\n",
   359                         drive);
   360 #endif
   361                 switch (CheckDrive(drive, NULL, &stbuf)) {
   362                     /* Drive exists and is a CD-ROM */
   363                 case 1:
   364                     AddDrive(drive, &stbuf);
   365                     break;
   366                     /* Drive exists, but isn't a CD-ROM */
   367                 case 0:
   368                     break;
   369                     /* Drive doesn't exist */
   370                 case -1:
   371                     exists = 0;
   372                     break;
   373                 }
   374             }
   375         } else {
   376             SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%s",
   377                          checklist[i]);
   378 #ifdef DEBUG_CDROM
   379             fprintf(stderr, "Checking possible CD-ROM drive: %s\n", drive);
   380 #endif
   381             if (CheckDrive(drive, NULL, &stbuf) > 0) {
   382                 AddDrive(drive, &stbuf);
   383             }
   384         }
   385     }
   386     return (0);
   387 }
   388 
   389 /* General ioctl() CD-ROM command function */
   390 static int
   391 SDL_SYS_CDioctl(int id, int command, void *arg)
   392 {
   393     int retval;
   394 
   395     retval = ioctl(id, command, arg);
   396     if (retval < 0) {
   397         SDL_SetError("ioctl() error: %s", strerror(errno));
   398     }
   399     return (retval);
   400 }
   401 
   402 static const char *
   403 SDL_SYS_CDName(int drive)
   404 {
   405     return (SDL_cdlist[drive]);
   406 }
   407 
   408 static int
   409 SDL_SYS_CDOpen(int drive)
   410 {
   411     return (open(SDL_cdlist[drive], (O_RDONLY | O_NONBLOCK), 0));
   412 }
   413 
   414 static int
   415 SDL_SYS_CDGetTOC(SDL_CD * cdrom)
   416 {
   417     struct cdrom_tochdr toc;
   418     int i, okay;
   419     struct cdrom_tocentry entry;
   420 
   421     okay = 0;
   422     if (SDL_SYS_CDioctl(cdrom->id, CDROMREADTOCHDR, &toc) == 0) {
   423         cdrom->numtracks = toc.cdth_trk1 - toc.cdth_trk0 + 1;
   424         if (cdrom->numtracks > SDL_MAX_TRACKS) {
   425             cdrom->numtracks = SDL_MAX_TRACKS;
   426         }
   427         /* Read all the track TOC entries */
   428         for (i = 0; i <= cdrom->numtracks; ++i) {
   429             if (i == cdrom->numtracks) {
   430                 cdrom->track[i].id = CDROM_LEADOUT;
   431             } else {
   432                 cdrom->track[i].id = toc.cdth_trk0 + i;
   433             }
   434             entry.cdte_track = cdrom->track[i].id;
   435             entry.cdte_format = CDROM_MSF;
   436             if (SDL_SYS_CDioctl(cdrom->id, CDROMREADTOCENTRY, &entry) < 0) {
   437                 break;
   438             } else {
   439                 if (entry.cdte_ctrl & CDROM_DATA_TRACK) {
   440                     cdrom->track[i].type = SDL_DATA_TRACK;
   441                 } else {
   442                     cdrom->track[i].type = SDL_AUDIO_TRACK;
   443                 }
   444                 cdrom->track[i].offset =
   445                     MSF_TO_FRAMES(entry.cdte_addr.msf.minute,
   446                                   entry.cdte_addr.msf.second,
   447                                   entry.cdte_addr.msf.frame);
   448                 cdrom->track[i].length = 0;
   449                 if (i > 0) {
   450                     cdrom->track[i - 1].length =
   451                         cdrom->track[i].offset - cdrom->track[i - 1].offset;
   452                 }
   453             }
   454         }
   455         if (i == (cdrom->numtracks + 1)) {
   456             okay = 1;
   457         }
   458     }
   459     return (okay ? 0 : -1);
   460 }
   461 
   462 /* Get CD-ROM status */
   463 static CDstatus
   464 SDL_SYS_CDStatus(SDL_CD * cdrom, int *position)
   465 {
   466     CDstatus status;
   467     struct cdrom_tochdr toc;
   468     struct cdrom_subchnl info;
   469 
   470     info.cdsc_format = CDROM_MSF;
   471     if (ioctl(cdrom->id, CDROMSUBCHNL, &info) < 0) {
   472         if (ERRNO_TRAYEMPTY(errno)) {
   473             status = CD_TRAYEMPTY;
   474         } else {
   475             status = CD_ERROR;
   476         }
   477     } else {
   478         switch (info.cdsc_audiostatus) {
   479         case CDROM_AUDIO_INVALID:
   480         case CDROM_AUDIO_NO_STATUS:
   481             /* Try to determine if there's a CD available */
   482             if (ioctl(cdrom->id, CDROMREADTOCHDR, &toc) == 0)
   483                 status = CD_STOPPED;
   484             else
   485                 status = CD_TRAYEMPTY;
   486             break;
   487         case CDROM_AUDIO_COMPLETED:
   488             status = CD_STOPPED;
   489             break;
   490         case CDROM_AUDIO_PLAY:
   491             status = CD_PLAYING;
   492             break;
   493         case CDROM_AUDIO_PAUSED:
   494             /* Workaround buggy CD-ROM drive */
   495             if (info.cdsc_trk == CDROM_LEADOUT) {
   496                 status = CD_STOPPED;
   497             } else {
   498                 status = CD_PAUSED;
   499             }
   500             break;
   501         default:
   502             status = CD_ERROR;
   503             break;
   504         }
   505     }
   506     if (position) {
   507         if (status == CD_PLAYING || (status == CD_PAUSED)) {
   508             *position = MSF_TO_FRAMES(info.cdsc_absaddr.msf.minute,
   509                                       info.cdsc_absaddr.msf.second,
   510                                       info.cdsc_absaddr.msf.frame);
   511         } else {
   512             *position = 0;
   513         }
   514     }
   515     return (status);
   516 }
   517 
   518 /* Start play */
   519 static int
   520 SDL_SYS_CDPlay(SDL_CD * cdrom, int start, int length)
   521 {
   522     struct cdrom_msf playtime;
   523 
   524     FRAMES_TO_MSF(start,
   525                   &playtime.cdmsf_min0, &playtime.cdmsf_sec0,
   526                   &playtime.cdmsf_frame0);
   527     FRAMES_TO_MSF(start + length, &playtime.cdmsf_min1, &playtime.cdmsf_sec1,
   528                   &playtime.cdmsf_frame1);
   529 #ifdef DEBUG_CDROM
   530     fprintf(stderr, "Trying to play from %d:%d:%d to %d:%d:%d\n",
   531             playtime.cdmsf_min0, playtime.cdmsf_sec0, playtime.cdmsf_frame0,
   532             playtime.cdmsf_min1, playtime.cdmsf_sec1, playtime.cdmsf_frame1);
   533 #endif
   534     return (SDL_SYS_CDioctl(cdrom->id, CDROMPLAYMSF, &playtime));
   535 }
   536 
   537 /* Pause play */
   538 static int
   539 SDL_SYS_CDPause(SDL_CD * cdrom)
   540 {
   541     return (SDL_SYS_CDioctl(cdrom->id, CDROMPAUSE, 0));
   542 }
   543 
   544 /* Resume play */
   545 static int
   546 SDL_SYS_CDResume(SDL_CD * cdrom)
   547 {
   548     return (SDL_SYS_CDioctl(cdrom->id, CDROMRESUME, 0));
   549 }
   550 
   551 /* Stop play */
   552 static int
   553 SDL_SYS_CDStop(SDL_CD * cdrom)
   554 {
   555     return (SDL_SYS_CDioctl(cdrom->id, CDROMSTOP, 0));
   556 }
   557 
   558 /* Eject the CD-ROM */
   559 static int
   560 SDL_SYS_CDEject(SDL_CD * cdrom)
   561 {
   562     return (SDL_SYS_CDioctl(cdrom->id, CDROMEJECT, 0));
   563 }
   564 
   565 /* Close the CD-ROM handle */
   566 static void
   567 SDL_SYS_CDClose(SDL_CD * cdrom)
   568 {
   569     close(cdrom->id);
   570 }
   571 
   572 void
   573 SDL_SYS_CDQuit(void)
   574 {
   575     int i;
   576 
   577     if (SDL_numcds > 0) {
   578         for (i = 0; i < SDL_numcds; ++i) {
   579             SDL_free(SDL_cdlist[i]);
   580         }
   581         SDL_numcds = 0;
   582     }
   583 }
   584 
   585 #endif /* SDL_CDROM_LINUX */
   586 /* vi: set ts=4 sw=4 expandtab: */