Radix cross Linux

The main Radix cross Linux repository contains the build scripts of packages, which have the most complete and common functionality for desktop machines

452 Commits   2 Branches   1 Tag
Index: interface/cdda_interface.h
===================================================================
--- interface/cdda_interface.h	(nonexistent)
+++ interface/cdda_interface.h	(revision 5)
@@ -0,0 +1,207 @@
+/******************************************************************
+ * CopyPolicy: GNU Lesser General Public License 2.1 applies
+ * Copyright (C) 2001-2008 Xiph.org
+ * Original version by Heiko Eissfeldt heiko@escape.colossus.de
+ *
+ * Toplevel interface header; applications include this
+ *
+ ******************************************************************/
+
+#ifndef _cdda_interface_h_
+#define _cdda_interface_h_
+
+#ifndef CD_FRAMESIZE
+#define CD_FRAMESIZE 2048
+#endif
+#ifndef CD_FRAMESIZE_RAW
+#define CD_FRAMESIZE_RAW 2352
+#endif
+#define CD_FRAMESAMPLES (CD_FRAMESIZE_RAW / 4)
+
+#include <sys/types.h>
+#include <signal.h>
+
+#define MAXTRK 100
+
+typedef struct TOC {	/* structure of table of contents */
+  unsigned char bFlags;
+  unsigned char bTrack;
+  int32_t dwStartSector;
+} TOC;
+
+/* interface types */
+#define GENERIC_SCSI	 0
+#define COOKED_IOCTL	 1
+#define TEST_INTERFACE	 2
+#define SGIO_SCSI	 3
+#define SGIO_SCSI_BUGGY1 4
+
+#define CDDA_MESSAGE_FORGETIT 0
+#define CDDA_MESSAGE_PRINTIT 1
+#define CDDA_MESSAGE_LOGIT 2
+
+/* cdrom access function pointer */
+
+typedef struct cdda_private_data cdda_private_data_t;
+
+typedef struct cdrom_drive{
+
+  int opened; /* This struct may just represent a candidate for opening */
+
+  char *cdda_device_name;
+  char *ioctl_device_name;
+
+  int cdda_fd;
+  int ioctl_fd;
+
+  char *drive_model;
+  int drive_type;
+  int interface;
+  int bigendianp;
+  int nsectors;
+
+  int cd_extra;
+  int tracks;
+  TOC disc_toc[MAXTRK];
+  long audio_first_sector;
+  long audio_last_sector;
+
+  int errordest;
+  int messagedest;
+  char *errorbuf;
+  char *messagebuf;
+
+  /* functions specific to particular drives/interfaces */
+
+  int  (*enable_cdda)  (struct cdrom_drive *d, int onoff);
+  int  (*read_toc)     (struct cdrom_drive *d);
+  long (*read_audio)   (struct cdrom_drive *d, void *p, long begin, 
+		       long sectors);
+  int  (*set_speed)    (struct cdrom_drive *d, int speed);
+  int error_retry;
+  int report_all;
+
+  int is_atapi;
+  int is_mmc;
+
+  cdda_private_data_t *private_data;
+  void         *reserved;
+  unsigned char inqbytes[4];
+
+  /* Scsi parameters and state */
+  unsigned char density;
+  unsigned char orgdens;
+  unsigned int orgsize;
+  long bigbuff;
+  int adjust_ssize;
+
+  int fua;
+  int lun;
+
+  sigset_t sigset;
+
+} cdrom_drive;
+
+#define IS_AUDIO(d,i) (!(d->disc_toc[i].bFlags & 0x04))
+
+/******** Identification/autosense functions */
+
+extern cdrom_drive *cdda_find_a_cdrom(int messagedest, char **message);
+extern cdrom_drive *cdda_identify(const char *device, int messagedest,
+				  char **message);
+extern cdrom_drive *cdda_identify_cooked(const char *device,int messagedest,
+					 char **message);
+extern cdrom_drive *cdda_identify_scsi(const char *generic_device, 
+				       const char *ioctl_device,
+				       int messagedest, char **message);
+#ifdef CDDA_TEST
+extern cdrom_drive *cdda_identify_test(const char *filename,
+				       int messagedest, char **message);
+#endif
+
+/******** Drive oriented functions */
+
+extern char *cdda_version();
+extern int cdda_speed_set(cdrom_drive *d, int speed);
+extern void cdda_verbose_set(cdrom_drive *d,int err_action, int mes_action);
+extern char *cdda_messages(cdrom_drive *d);
+extern char *cdda_errors(cdrom_drive *d);
+
+extern int cdda_close(cdrom_drive *d);
+extern int cdda_open(cdrom_drive *d);
+extern long cdda_read(cdrom_drive *d, void *buffer,
+		       long beginsector, long sectors);
+extern long cdda_read_timed(cdrom_drive *d, void *buffer,
+			    long beginsector, long sectors, int *milliseconds);
+
+extern long cdda_track_firstsector(cdrom_drive *d,int track);
+extern long cdda_track_lastsector(cdrom_drive *d,int track);
+extern long cdda_tracks(cdrom_drive *d);
+extern int cdda_sector_gettrack(cdrom_drive *d,long sector);
+extern int cdda_track_channels(cdrom_drive *d,int track);
+extern int cdda_track_audiop(cdrom_drive *d,int track);
+extern int cdda_track_copyp(cdrom_drive *d,int track);
+extern int cdda_track_preemp(cdrom_drive *d,int track);
+extern long cdda_disc_firstsector(cdrom_drive *d);
+extern long cdda_disc_lastsector(cdrom_drive *d);
+
+/* transport errors: */
+
+#define TR_OK            0
+#define TR_EWRITE        1  /* Error writing packet command (transport) */
+#define TR_EREAD         2  /* Error reading packet data (transport) */
+#define TR_UNDERRUN      3  /* Read underrun */
+#define TR_OVERRUN       4  /* Read overrun */
+#define TR_ILLEGAL       5  /* Illegal/rejected request */
+#define TR_MEDIUM        6  /* Medium error */
+#define TR_BUSY          7  /* Device busy */
+#define TR_NOTREADY      8  /* Device not ready */
+#define TR_FAULT         9  /* Devive failure */
+#define TR_UNKNOWN      10  /* Unspecified error */
+#define TR_STREAMING    11  /* loss of streaming */
+
+static char *strerror_tr[]={
+  "Success",
+  "Error writing packet command to device",
+  "Error reading command from device",
+  "SCSI packet data underrun (too little data)",
+  "SCSI packet data overrun (too much data)",
+  "Illegal SCSI request (rejected by target)",
+  "Medium reading data from medium",
+  "Device busy",
+  "Device not ready",
+  "Target hardware fault",
+  "Unspecified error",
+  "Drive lost streaming"
+};
+
+/* Errors returned by lib: 
+
+001: Unable to set CDROM to read audio mode
+002: Unable to read table of contents lead-out
+003: CDROM reporting illegal number of tracks
+004: Unable to read table of contents header
+005: Unable to read table of contents entry
+006: Could not read any data from drive
+007: Unknown, unrecoverable error reading data
+008: Unable to identify CDROM model
+009: CDROM reporting illegal table of contents
+010: Unaddressable sector 
+
+100: Interface not supported
+101: Drive is neither a CDROM nor a WORM device
+102: Permision denied on cdrom (ioctl) device
+103: Permision denied on cdrom (data) device
+
+300: Kernel memory error
+
+400: Device not open
+401: Invalid track number
+402: Track not audio data
+403: No audio tracks on disc
+404: No medium present
+405: Option not supported by drive
+
+*/
+#endif
+
Index: interface/cooked_interface.c
===================================================================
--- interface/cooked_interface.c	(nonexistent)
+++ interface/cooked_interface.c	(revision 5)
@@ -0,0 +1,287 @@
+/******************************************************************
+ * CopyPolicy: GNU Lesser General Public License 2.1 applies
+ * Copyright (C) Monty xiphmont@mit.edu
+ *
+ * CDROM code specific to the cooked ioctl interface
+ *
+ ******************************************************************/
+
+#include "low_interface.h"
+#include "common_interface.h"
+#include "utils.h"
+#include <time.h> 
+static int timed_ioctl(cdrom_drive *d, int fd, int command, void *arg){
+  struct timespec tv1;
+  struct timespec tv2;
+  int ret1=clock_gettime(d->private_data->clock,&tv1);
+  int ret2=ioctl(fd, command,arg);
+  int ret3=clock_gettime(d->private_data->clock,&tv2);
+  if(ret1<0 || ret3<0){
+    d->private_data->last_milliseconds=-1;
+  }else{
+    d->private_data->last_milliseconds = (tv2.tv_sec-tv1.tv_sec)*1000. + (tv2.tv_nsec-tv1.tv_nsec)/1000000.;
+  }
+  return ret2;
+}
+
+static int cooked_readtoc (cdrom_drive *d){
+  int i;
+  int tracks;
+  struct cdrom_tochdr hdr;
+  struct cdrom_tocentry entry;
+
+  /* get TocHeader to find out how many entries there are */
+  if(ioctl(d->ioctl_fd, CDROMREADTOCHDR, &hdr ))
+    switch(errno){
+    case EPERM:
+      cderror(d,"102: Permision denied on cdrom (ioctl) device\n");
+      return(-102);
+    default:
+      cderror(d,"004: Unable to read table of contents header\n");
+      return(-4);
+    }
+
+  /* get all TocEntries */
+  for(i=0;i<hdr.cdth_trk1;i++){
+    entry.cdte_track= i+1;
+    entry.cdte_format = CDROM_LBA;
+    if(ioctl(d->ioctl_fd,CDROMREADTOCENTRY,&entry)){
+      cderror(d,"005: Unable to read table of contents entry\n");
+      return(-5);
+    }
+      
+    d->disc_toc[i].bFlags = (entry.cdte_adr << 4) | (entry.cdte_ctrl & 0x0f);
+    d->disc_toc[i].bTrack = i+1;
+    d->disc_toc[i].dwStartSector = entry.cdte_addr.lba;
+  }
+
+  entry.cdte_track = CDROM_LEADOUT;
+  entry.cdte_format = CDROM_LBA;
+  if(ioctl(d->ioctl_fd, CDROMREADTOCENTRY, &entry)){
+    cderror(d,"005: Unable to read table of contents entry\n");
+    return(-5);
+  }
+  d->disc_toc[i].bFlags = (entry.cdte_adr << 4) | (entry.cdte_ctrl & 0x0f);
+  d->disc_toc[i].bTrack = entry.cdte_track;
+  d->disc_toc[i].dwStartSector = entry.cdte_addr.lba;
+
+  tracks=hdr.cdth_trk1+1;
+  d->cd_extra=FixupTOC(d,tracks);
+  return(--tracks);  /* without lead-out */
+}
+
+/* Set operating speed */
+static int cooked_setspeed(cdrom_drive *d, int speed)
+{
+  if(d->ioctl_fd!=-1)
+    return ioctl(d->ioctl_fd, CDROM_SELECT_SPEED, speed);
+  else
+    return 0;
+}
+
+/* read 'SectorBurst' adjacent sectors of audio sectors 
+ * to Buffer '*p' beginning at sector 'lSector'
+ */
+
+static long cooked_read (cdrom_drive *d, void *p, long begin, long sectors){
+  int retry_count,err,ret=0;
+  struct cdrom_read_audio arg;
+  char *buffer=(char *)p;
+
+  /* read d->nsectors at a time, max. */
+  sectors=(sectors>d->nsectors?d->nsectors:sectors);
+  if(p==NULL)buffer = malloc(sectors*CD_FRAMESIZE_RAW);
+
+  arg.addr.lba = begin;
+  arg.addr_format = CDROM_LBA;
+  arg.nframes = sectors;
+  arg.buf=buffer;
+  retry_count=0;
+
+  do {
+    if((err=timed_ioctl(d,d->ioctl_fd, CDROMREADAUDIO, &arg))){
+      if(!d->error_retry){
+	ret=-7;
+	goto done;
+      }
+      switch(errno){
+      case ENOMEM:
+	/* D'oh.  Possible kernel error. Keep limping */
+	if(sectors==1){
+	  /* Nope, can't continue */
+	  cderror(d,"300: Kernel memory error\n");
+	  ret=-300;
+	  goto done;
+	}
+      case ENXIO:
+      case EBADF:
+      case ENOMEDIUM:
+	errno=ENOMEDIUM;
+	ret=0;
+	goto done;
+      default:
+	if(sectors==1){
+	    
+
+	  /* *Could* be I/O or media error.  I think.  If we're at
+	     30 retries, we better skip this unhappy little
+	     sector. */
+	  if(retry_count>MAX_RETRIES-1){
+	    char b[256];
+	    sprintf(b,"010: Unable to access sector %ld: skipping...\n",
+		    begin);
+	    cderror(d,b);
+	    ret=-10;
+	    goto done;
+	  }
+	  break;
+	}
+      }
+      if(retry_count>4)
+	if(sectors>1)
+	  sectors=sectors*3/4;
+      retry_count++;
+      if(retry_count>MAX_RETRIES){
+	cderror(d,"007: Unknown, unrecoverable error reading data\n");
+	ret=-7;
+	goto done;
+      }
+    }else
+      break;
+  } while (err);
+  
+  ret=sectors;
+
+ done:
+  if(p==NULL && buffer)free(buffer);
+  return ret;
+}
+
+/* hook */
+static int Dummy (cdrom_drive *d,int Switch){
+  return(0);
+}
+
+static int verify_read_command(cdrom_drive *d){
+  int i;
+  int16_t *buff=malloc(CD_FRAMESIZE_RAW);
+  int audioflag=0;
+
+  cdmessage(d,"Verifying drive can read CDDA...\n");
+
+  d->enable_cdda(d,1);
+
+  for(i=1;i<=d->tracks;i++){
+    if(cdda_track_audiop(d,i)==1){
+      long firstsector=cdda_track_firstsector(d,i);
+      long lastsector=cdda_track_lastsector(d,i);
+      long sector=(firstsector+lastsector)>>1;
+      audioflag=1;
+
+      if(d->read_audio(d,buff,sector,1)>0){
+	cdmessage(d,"\tExpected command set reads OK.\n");
+	d->enable_cdda(d,0);
+	free(buff);
+	return(0);
+      }
+    }
+  }
+ 
+  d->enable_cdda(d,0);
+
+  if(!audioflag){
+    cdmessage(d,"\tCould not find any audio tracks on this disk.\n");
+    return(-403);
+  }
+
+  cdmessage(d,"\n\tUnable to read any data; "
+	    "drive probably not CDDA capable.\n");
+  
+  cderror(d,"006: Could not read any data from drive\n");
+
+  free(buff);
+  return(-6);
+}
+
+#include "drive_exceptions.h"
+
+static void check_exceptions(cdrom_drive *d,exception *list){
+
+  int i=0;
+  while(list[i].model){
+    if(!strncmp(list[i].model,d->drive_model,strlen(list[i].model))){
+      if(list[i].bigendianp!=-1)d->bigendianp=list[i].bigendianp;
+      return;
+    }
+    i++;
+  }
+}
+
+/* set function pointers to use the ioctl routines */
+int cooked_init_drive (cdrom_drive *d){
+  int ret;
+
+  switch(d->drive_type){
+  case MATSUSHITA_CDROM_MAJOR:	/* sbpcd 1 */
+  case MATSUSHITA_CDROM2_MAJOR:	/* sbpcd 2 */
+  case MATSUSHITA_CDROM3_MAJOR:	/* sbpcd 3 */
+  case MATSUSHITA_CDROM4_MAJOR:	/* sbpcd 4 */
+    /* don't make the buffer too big; this sucker don't preempt */
+
+    cdmessage(d,"Attempting to set sbpcd buffer size...\n");
+
+    d->nsectors=8;
+    while(1){
+
+      /* this ioctl returns zero on error; exactly wrong, but that's
+         what it does. */
+
+      if(ioctl(d->ioctl_fd, CDROMAUDIOBUFSIZ, d->nsectors)==0){
+	d->nsectors>>=1;
+	if(d->nsectors==0){
+	  char buffer[256];
+	  d->nsectors=8;
+	  sprintf(buffer,"\tTrouble setting buffer size.  Defaulting to %d sectors.\n",
+		  d->nsectors);
+	  cdmessage(d,buffer);
+	  break; /* Oh, well.  Try to read anyway.*/
+	}
+      }else{
+	char buffer[256];
+	sprintf(buffer,"\tSetting read block size at %d sectors (%ld bytes).\n",
+		d->nsectors,(long)d->nsectors*CD_FRAMESIZE_RAW);
+	cdmessage(d,buffer);
+	break;
+      }
+    }
+
+    break;
+  case IDE0_MAJOR:
+  case IDE1_MAJOR:
+  case IDE2_MAJOR:
+  case IDE3_MAJOR:
+    d->nsectors=8; /* it's a define in the linux kernel; we have no
+		      way of determining other than this guess tho */
+    d->bigendianp=0;
+    d->is_atapi=1;
+
+    check_exceptions(d,atapi_list);
+
+    break;
+  default:
+    d->nsectors=40; 
+  }
+  d->enable_cdda = Dummy;
+  d->read_audio = cooked_read;
+  d->read_toc = cooked_readtoc;
+  d->set_speed = cooked_setspeed;
+  ret=d->tracks=d->read_toc(d);
+  if(d->tracks<1)
+    return(ret);
+
+  d->opened=1;
+  if((ret=verify_read_command(d)))return(ret);
+  d->error_retry=1;
+  return(0);
+}
+
Index: interface/interface.c
===================================================================
--- interface/interface.c	(nonexistent)
+++ interface/interface.c	(revision 5)
@@ -0,0 +1,158 @@
+/******************************************************************
+ * CopyPolicy: GNU Lesser General Public License 2.1 applies
+ * Copyright (C) 1998-2008 Monty xiphmont@mit.edu
+ * 
+ * Top-level interface module for cdrom drive access.  SCSI, ATAPI, etc
+ *    specific stuff are in other modules.  Note that SCSI does use
+ *    specialized ioctls; these appear in common_interface.c where the
+ *    generic_scsi stuff is in scsi_interface.c.
+ *
+ ******************************************************************/
+
+#include "low_interface.h"
+#include "common_interface.h"
+#include "utils.h"
+#include "../version.h"
+
+char *cdda_version(void){
+  return VERSIONNUM;
+}
+
+static void _clean_messages(cdrom_drive *d){
+  if(d){
+    if(d->messagebuf)free(d->messagebuf);
+    if(d->errorbuf)free(d->errorbuf);
+    d->messagebuf=NULL;
+    d->errorbuf=NULL;
+  }
+}
+
+/* doubles as "cdrom_drive_free()" */
+int cdda_close(cdrom_drive *d){
+  if(d){
+    if(d->opened)
+      d->enable_cdda(d,0);
+
+    _clean_messages(d);
+    if(d->cdda_device_name)free(d->cdda_device_name);
+    if(d->ioctl_device_name)free(d->ioctl_device_name);
+    if(d->drive_model)free(d->drive_model);
+    if(d->cdda_fd!=-1)close(d->cdda_fd);
+    if(d->ioctl_fd!=-1 && d->ioctl_fd!=d->cdda_fd)close(d->ioctl_fd);
+    if(d->private_data){
+      if(d->private_data->sg_hd)free(d->private_data->sg_hd);
+      free(d->private_data);
+    }
+
+    free(d);
+  }
+  return(0);
+}
+
+/* finish initializing the drive! */
+int cdda_open(cdrom_drive *d){
+  int ret;
+  if(d->opened)return(0);
+
+  switch(d->interface){
+  case SGIO_SCSI_BUGGY1:  
+  case SGIO_SCSI:  
+  case GENERIC_SCSI:  
+    if((ret=scsi_init_drive(d)))
+      return(ret);
+    break;
+  case COOKED_IOCTL:  
+    if((ret=cooked_init_drive(d)))
+      return(ret);
+    break;
+#ifdef CDDA_TEST
+  case TEST_INTERFACE:  
+    if((ret=test_init_drive(d)))
+      return(ret);
+    break;
+#endif
+  default:
+    cderror(d,"100: Interface not supported\n");
+    return(-100);
+  }
+  
+  /* Check TOC, enable for CDDA */
+  
+  /* Some drives happily return a TOC even if there is no disc... */
+  {
+     int i;
+    for(i=0;i<d->tracks;i++)
+      if(d->disc_toc[i].dwStartSector<0 ||
+	 d->disc_toc[i+1].dwStartSector==0){
+	d->opened=0;
+	cderror(d,"009: CDROM reporting illegal table of contents\n");
+	return(-9);
+      }
+  }
+
+  if((ret=d->enable_cdda(d,1)))
+    return(ret);
+    
+  /*  d->select_speed(d,d->maxspeed); most drives are full speed by default */
+  if(d->bigendianp==-1)d->bigendianp=data_bigendianp(d);
+  return(0);
+}
+
+int cdda_speed_set(cdrom_drive *d, int speed)
+{
+  if(d->set_speed)
+    if(!d->set_speed(d,speed))return 0;
+  
+  cderror(d,"405: Option not supported by drive\n");
+  return -405;
+}
+
+long cdda_read_timed(cdrom_drive *d, void *buffer, long beginsector, long sectors, int *ms){
+  if(ms)*ms= -1;
+  if(d->opened){
+    if(sectors>0){
+      sectors=d->read_audio(d,buffer,beginsector,sectors);
+
+      if(sectors>0){
+	/* byteswap? */
+	if(d->bigendianp==-1) /* not determined yet */
+	  d->bigendianp=data_bigendianp(d);
+	
+	if(d->bigendianp!=bigendianp()){
+	  int i;
+	  u_int16_t *p=(u_int16_t *)buffer;
+	  long els=sectors*CD_FRAMESIZE_RAW/2;
+	  
+	  for(i=0;i<els;i++)p[i]=swap16(p[i]);
+	}
+      }	
+    }
+    if(ms)*ms=d->private_data->last_milliseconds;
+    return(sectors);
+  }
+  
+  cderror(d,"400: Device not open\n");
+  return(-400);
+}
+
+long cdda_read(cdrom_drive *d, void *buffer, long beginsector, long sectors){
+  return cdda_read_timed(d,buffer,beginsector,sectors,NULL);
+}
+
+void cdda_verbose_set(cdrom_drive *d,int err_action, int mes_action){
+  d->messagedest=mes_action;
+  d->errordest=err_action;
+}
+
+extern char *cdda_messages(cdrom_drive *d){
+  char *ret=d->messagebuf;
+  d->messagebuf=NULL;
+  return(ret);
+}
+
+extern char *cdda_errors(cdrom_drive *d){
+  char *ret=d->errorbuf;
+  d->errorbuf=NULL;
+  return(ret);
+}
+
Index: interface/scan_devices.c
===================================================================
--- interface/scan_devices.c	(nonexistent)
+++ interface/scan_devices.c	(revision 5)
@@ -0,0 +1,831 @@
+/******************************************************************
+ * CopyPolicy: GNU Lesser General Public License 2.1 applies
+ * Copyright (C) 1998-2008 Monty xiphmont@mit.edu
+ * 
+ * Autoscan for or verify presence of a cdrom device
+ * 
+ ******************************************************************/
+
+#define _GNU_SOURCE /* get cuserid */
+#define _USE_XOPEN /* get cuserid */
+#include <limits.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "cdda_interface.h"
+#include "low_interface.h"
+#include "common_interface.h"
+#include "utils.h"
+
+#define MAX_DEV_LEN 20 /* Safe because strings only come from below */
+/* must be absolute paths! */
+static char *scsi_cdrom_prefixes[]={
+  "/dev/scd",
+  "/dev/sr",
+  NULL};
+static char *scsi_generic_prefixes[]={
+  "/dev/sg",
+  NULL};
+
+static char *devfs_scsi_test="/dev/scsi/";
+static char *devfs_scsi_cd="cd";
+static char *devfs_scsi_generic="generic";
+
+static char *cdrom_devices[]={
+  "/dev/cdrom",
+  "/dev/cdroms/cdrom?",
+  "/dev/hd?",
+  "/dev/sg?",
+  "/dev/cdu31a",
+  "/dev/cdu535",
+  "/dev/sbpcd",
+  "/dev/sbpcd?",
+  "/dev/sonycd",
+  "/dev/mcd",
+  "/dev/sjcd",
+  /* "/dev/aztcd", timeout is too long */
+  "/dev/cm206cd",
+  "/dev/gscd",
+  "/dev/optcd",NULL};
+
+/* Functions here look for a cdrom drive; full init of a drive type
+   happens in interface.c */
+
+cdrom_drive *cdda_find_a_cdrom(int messagedest,char **messages){
+  /* Brute force... */
+  
+  int i=0;
+  cdrom_drive *d;
+
+  while(cdrom_devices[i]!=NULL){
+
+    /* is it a name or a pattern? */
+    char *pos;
+    if((pos=strchr(cdrom_devices[i],'?'))){
+      int j;
+      /* try first eight of each device */
+      for(j=0;j<4;j++){
+	char *buffer=copystring(cdrom_devices[i]);
+
+	/* number, then letter */
+	
+	buffer[pos-(cdrom_devices[i])]=j+48;
+	if((d=cdda_identify(buffer,messagedest,messages)))
+	  return(d);
+	idmessage(messagedest,messages,"",NULL);
+	buffer[pos-(cdrom_devices[i])]=j+97;
+	if((d=cdda_identify(buffer,messagedest,messages)))
+	  return(d);
+	idmessage(messagedest,messages,"",NULL);
+      }
+    }else{
+      /* Name.  Go for it. */
+      if((d=cdda_identify(cdrom_devices[i],messagedest,messages)))
+	return(d);
+      
+      idmessage(messagedest,messages,"",NULL);
+    }
+    i++;
+  }
+  idmessage(messagedest,messages,
+	    "\n\nNo cdrom drives accessible to %s found.\n",
+	    cuserid(NULL));
+  return(NULL);
+}
+
+cdrom_drive *cdda_identify(const char *device, int messagedest,char **messages){
+  struct stat st;
+  cdrom_drive *d=NULL;  
+
+  idmessage(messagedest,messages,"Checking %s for cdrom...",device);
+
+  if(stat(device,&st)){
+    idperror(messagedest,messages,"\tCould not stat %s",device);
+    return(NULL);
+  }
+    
+#ifndef CDDA_TEST
+  if (!S_ISCHR(st.st_mode) &&
+      !S_ISBLK(st.st_mode)){
+    idmessage(messagedest,messages,"\t%s is not a block or character device",device);
+    return(NULL);
+  }
+#endif
+
+  /* an IDE device may have scsi-ide support, SG_IO support and cooked
+     support.  Prefer the SCSI variants, they give the most control */
+  d=cdda_identify_scsi(NULL,device,messagedest,messages);
+  if(!d)d=cdda_identify_cooked(device,messagedest,messages);
+  
+#ifdef CDDA_TEST
+  if(!d)d=cdda_identify_test(device,messagedest,messages);
+#endif
+
+  return(d);
+}
+
+char *test_resolve_symlink(const char *file,int messagedest,char **messages){
+  char resolved[PATH_MAX];
+  struct stat st;
+  if(lstat(file,&st)){
+    idperror(messagedest,messages,"\t\tCould not stat %s",file);
+    return(NULL);
+  }
+
+  if(realpath(file,resolved))
+    return(strdup(resolved));
+
+  idperror(messagedest,messages,"\t\tCould not resolve symlink %s",file);
+  return(NULL);
+
+}
+
+cdrom_drive *cdda_identify_cooked(const char *dev, int messagedest,
+				  char **messages){
+
+  cdrom_drive *d=NULL;
+  struct stat st;
+  int fd=-1, i;
+  int type;
+  char *description=NULL;
+  char *device;
+
+  idmessage(messagedest,messages,"\tTesting %s for cooked ioctl() interface",dev);
+
+  device=test_resolve_symlink(dev,messagedest,messages);
+  if(device==NULL)return(NULL);
+
+  if(stat(device,&st)){
+    idperror(messagedest,messages,"\t\tCould not stat %s",device);
+    free(device);
+    return(NULL);
+  }
+    
+  if (!S_ISCHR(st.st_mode) &&
+      !S_ISBLK(st.st_mode)){
+    idmessage(messagedest,messages,"\t\t%s is not a block or character device",device);
+    free(device);
+    return(NULL);
+  }
+
+  type=(int)(st.st_rdev>>8);
+  switch (type) {
+  case IDE0_MAJOR:
+  case IDE1_MAJOR:
+  case IDE2_MAJOR:
+  case IDE3_MAJOR:
+    /* Yay, ATAPI... */
+    /* Ping for CDROM-ness */
+    
+    fd=open(device,O_RDONLY|O_NONBLOCK);
+    if(fd==-1){
+      idperror(messagedest,messages,"\t\tUnable to open %s",device);
+      free(device);
+      return(NULL);
+    }
+  
+    if(ioctl_ping_cdrom(fd)){
+      idmessage(messagedest,messages,"\t\tDevice %s is not a CDROM",device);
+      close(fd);
+      free(device);
+      return(NULL);
+    }
+    {
+      char *temp=atapi_drive_info(fd);
+      description=catstring(NULL,"ATAPI compatible ");
+      description=catstring(description,temp);
+      free(temp);
+    }
+    
+    break;
+  case CDU31A_CDROM_MAJOR:
+    /* major indicates this is a cdrom; no ping necessary. */
+    description=copystring("Sony CDU31A or compatible");
+    break;
+  case CDU535_CDROM_MAJOR:
+    /* major indicates this is a cdrom; no ping necessary. */
+    description=copystring("Sony CDU535 or compatible");
+    break;
+
+  case MATSUSHITA_CDROM_MAJOR:
+  case MATSUSHITA_CDROM2_MAJOR:
+  case MATSUSHITA_CDROM3_MAJOR:
+  case MATSUSHITA_CDROM4_MAJOR:
+    /* major indicates this is a cdrom; no ping necessary. */
+    description=copystring("non-ATAPI IDE-style Matsushita/Panasonic CR-5xx or compatible");
+    break;
+  case SANYO_CDROM_MAJOR:
+    description=copystring("Sanyo proprietary or compatible: NOT CDDA CAPABLE");
+    break;
+  case MITSUMI_CDROM_MAJOR:
+  case MITSUMI_X_CDROM_MAJOR:
+    description=copystring("Mitsumi proprietary or compatible: NOT CDDA CAPABLE");
+    break;
+  case OPTICS_CDROM_MAJOR:
+    description=copystring("Optics Dolphin or compatible: NOT CDDA CAPABLE");
+    break;
+  case AZTECH_CDROM_MAJOR:
+    description=copystring("Aztech proprietary or compatible: NOT CDDA CAPABLE");
+    break;
+  case GOLDSTAR_CDROM_MAJOR:
+    description=copystring("Goldstar proprietary: NOT CDDA CAPABLE");
+    break;
+  case CM206_CDROM_MAJOR:
+    description=copystring("Philips/LMS CM206 proprietary: NOT CDDA CAPABLE");
+    break;
+
+  case SCSI_CDROM_MAJOR:   
+  case SCSI_GENERIC_MAJOR: 
+    /* Nope nope nope */
+    idmessage(messagedest,messages,"\t\t%s is not a cooked ioctl CDROM.",device);
+    free(device);
+    return(NULL);
+  default:
+    /* What the hell is this? */
+    idmessage(messagedest,messages,"\t\t%s is not a cooked ioctl CDROM.",device);
+    free(device);
+    return(NULL);
+  }
+
+  /* Minimum init */
+  
+  d=calloc(1,sizeof(cdrom_drive));
+  d->cdda_device_name=device;
+  d->ioctl_device_name=copystring(device);
+  d->drive_model=description;
+  d->drive_type=type;
+  d->cdda_fd=fd;
+  d->ioctl_fd=fd;
+  d->interface=COOKED_IOCTL;
+  d->bigendianp=-1; /* We don't know yet... */
+  d->nsectors=-1;
+  d->private_data=calloc(1,sizeof(*d->private_data));
+  {
+    /* goddamnit */
+    struct timespec tv;
+    d->private_data->clock=(clock_gettime(CLOCK_MONOTONIC,&tv)<0?CLOCK_REALTIME:CLOCK_MONOTONIC);
+  }
+  idmessage(messagedest,messages,"\t\tCDROM sensed: %s\n",description);
+  return(d);
+}
+
+struct  sg_id {
+  long    l1; /* target | lun << 8 | channel << 16 | low_ino << 24 */
+  long    l2; /* Unique id */
+} sg_id;
+
+typedef struct scsiid{
+  int bus;
+  int id;
+  int lun;
+} scsiid;
+
+/* Even *this* isn't as simple as it bloody well should be :-P */
+/* SG has an easy interface, but SCSI overall does not */
+static int get_scsi_id(int fd, scsiid *id){
+  struct sg_id argid;
+  int busarg;
+
+  /* get the host/id/lun */
+
+  if(fd==-1)return(-1);
+  if(ioctl(fd,SCSI_IOCTL_GET_IDLUN,&argid))return(-1);
+  id->bus=argid.l2; /* for now */
+  id->id=argid.l1&0xff;
+  id->lun=(argid.l1>>8)&0xff;
+
+  if(ioctl(fd,SCSI_IOCTL_GET_BUS_NUMBER,&busarg)==0)
+    id->bus=busarg;
+  
+  return(0);
+}
+
+/* slightly wasteful, but a clean abstraction */
+static char *scsi_match(const char *device,char **prefixes,
+			char *devfs_test,
+			char *devfs_other,
+			char *prompt,int messagedest,char **messages){
+  int dev=open(device,O_RDONLY|O_NONBLOCK);
+  scsiid a,b;
+
+  int i,j;
+  char buffer[200];
+
+  /* if we're running under /devfs, build the device name from the
+     device we already have */
+  if(!strncmp(device,devfs_test,strlen(devfs_test))){
+    char *pos;
+    strcpy(buffer,device);
+    pos=strrchr(buffer,'/');
+    if(pos){
+      int matchf;
+      sprintf(pos,"/%s",devfs_other);
+      matchf=open(buffer,O_RDONLY|O_NONBLOCK);
+      for (i = 0; (i<10) && (matchf==-1); i++) {
+        fprintf(stderr, "Error trying to open %s exclusively (%s). retrying in 1 seconds.\n", buffer, strerror(errno));
+        usleep(1000000 + 100000.0 * rand()/(RAND_MAX+1.0));
+        matchf = open(buffer,O_RDONLY|O_NONBLOCK);
+      }
+      if(matchf!=-1){
+	close(matchf);
+	close(dev);
+	return(strdup(buffer));
+      }
+    }
+  }	
+
+  /* get the host/id/lun */
+  if(dev==-1){
+    idperror(messagedest,messages,"\t\tCould not access device %s",
+	     device);
+    
+    goto matchfail;
+  }
+  if(get_scsi_id(dev,&a)){
+    idperror(messagedest,messages,"\t\tDevice %s could not perform ioctl()",
+	     device);
+
+    goto matchfail;
+  }
+
+  /* go through most likely /dev nodes for a match */
+  for(i=0;i<25;i++){
+    for(j=0;j<2;j++){
+      int pattern=0;
+      int matchf, k;
+      
+      while(prefixes[pattern]!=NULL){
+	switch(j){
+	case 0:
+	  /* number */
+	  sprintf(buffer,"%s%d",prefixes[pattern],i);
+	  break;
+	case 1:
+	  /* number */
+	  sprintf(buffer,"%s%c",prefixes[pattern],i+'a');
+	  break;
+	}
+	
+	matchf=open(buffer,O_RDONLY|O_NONBLOCK);
+	for (k = 0; (k<10) && (matchf==-1); k++) {
+	  fprintf(stderr, "Error trying to open %s exclusively (%s). retrying in 1 second.\n", buffer, strerror(errno));
+	  usleep(1000000 + 100000.0 * rand()/(RAND_MAX+1.0));
+	  matchf=open(buffer,O_RDONLY|O_NONBLOCK);
+	}
+
+	if(matchf!=-1){
+	  if(get_scsi_id(matchf,&b)==0){
+	    if(a.bus==b.bus && a.id==b.id && a.lun==b.lun){
+	      close(matchf);
+	      close(dev);
+	      return(strdup(buffer));
+	    }
+	  }
+	  close(matchf);
+	}
+	pattern++;
+      }
+    }
+  } 
+
+  idmessage(messagedest,messages,prompt,device);
+
+matchfail:
+
+  if(dev!=-1)close(dev);
+  return(NULL);
+}
+
+void strscat(char *a,char *b,int n){
+  int i;
+
+  for(i=n;i>0;i--)
+    if(b[i-1]>' ')break;
+
+  strncat(a,b,i);
+  strcat(a," ");
+}
+
+/* At this point, we're going to punt compatability before SG2, and
+   allow only SG2 and SG3 */
+static int verify_SG_version(cdrom_drive *d,int messagedest,
+			     char **messages){
+  /* are we using the new SG driver by Doug Gilbert? If not, punt */
+  int version,major,minor;
+  char buffer[256];
+  idmessage(messagedest,messages,
+	    "\nFound an accessible SCSI CDROM drive."
+	    "\nLooking at revision of the SG interface in use...","");
+
+  if(ioctl(d->cdda_fd,SG_GET_VERSION_NUM,&version)){
+    /* Up, guess not. */
+    idmessage(messagedest,messages,
+	      "\tOOPS!  Old 2.0/early 2.1/early 2.2.x (non-ac patch) style "
+	      "SG.\n\tCdparanoia no longer supports the old interface.\n","");
+    return(0);
+  }
+  major=version/10000;
+  version-=major*10000;
+  minor=version/100;
+  version-=minor*100;
+  
+  sprintf(buffer,"\tSG interface version %d.%d.%d; OK.",
+	  major,minor,version);
+
+  idmessage(messagedest,messages,buffer,"");
+  return(major);
+}
+
+int check_sgio(const char *device, int messagedest, char **messages){
+  int fd;
+  struct sg_io_hdr hdr;
+
+  if (!device) return 0;
+
+  /* we don't really care what type of device it is -- if it can do
+   * SG_IO, then we'll put it through the normal mmc/atapi/etc tests
+   * later, but it's good enough for now. */
+  fd = open(device, O_RDWR|O_NONBLOCK);
+  if (fd < 0){
+    idperror(messagedest,messages,
+	     "\t\tCould not access device %s to test for SG_IO support",device);    
+    return 0;
+  }
+
+  memset(&hdr, 0, sizeof (struct sg_io_hdr));
+  /* First try with interface_id = 'A'; for all but the sg case,
+   * that'll get us a -EINVAL if it supports SG_IO, and some other
+   * error for all other cases. */
+  hdr.interface_id = 'A';
+  if (ioctl(fd, SG_IO, &hdr)) {
+    switch (errno) {
+    case EINVAL: /* sr and ata give us EINVAL when SG_IO is
+		  * supported but interface_id is bad. */
+      
+    case ENOSYS: /* sg gives us ENOSYS when SG_IO is supported but
+		  * interface_id is bad.  IMHO, this is wrong and
+		  * needs fixing in the kernel. */
+      
+      close(fd);
+      return 1;
+      
+    default: /* everything else gives ENOTTY, I think.  I'm just
+	      * going to be paranoid and reject everything else. */
+      
+      close(fd);
+      return 0;
+      
+    }
+  }
+  /* if we get here, something is dreadfuly wrong. ioctl(fd,SG_IO,&hdr)
+   * handled SG_IO, but took hdr.interface_id = 'A' as valid, and an empty
+   * command as good.  Don't trust it. */
+  close(fd);
+  return 0;
+}
+
+/* scanning is always done by specifying a device name in
+   specialized_device; generic_device is only filled when the generic device
+   force option is used, and in that case, use of SG (not SGIO) should indeed be
+   forced */
+cdrom_drive *cdda_identify_scsi(const char *generic_device, 
+				const char *specialized_device, int messagedest,
+				char **messages){
+
+  cdrom_drive *d=NULL;
+  struct stat i_st;
+  struct stat g_st;
+  int use_sgio=1;
+  int i_fd=-1, i;
+  int g_fd=-1;
+  int version;
+  int type;
+  char *p;
+
+  if(generic_device)
+    idmessage(messagedest,messages,"\tTesting %s for SCSI/MMC interface",
+	      generic_device);
+  else
+    if(specialized_device)
+      idmessage(messagedest,messages,"\tTesting %s for SCSI/MMC interface",
+		specialized_device);
+  
+
+  if(generic_device){
+    use_sgio = 0;
+    idmessage(messagedest,messages,"\t\tgeneric device forced; not testing for SG_IO interface",
+	      generic_device);
+    
+    if(stat(generic_device,&g_st)){
+      idperror(messagedest,messages,"\t\tCould not access device %s",
+	       generic_device);
+      return(NULL);
+    }
+
+    if((int)(g_st.st_rdev>>8)!=SCSI_GENERIC_MAJOR){
+      idmessage(messagedest,messages,"\t\t%s is not a generic SCSI device",
+		generic_device);
+      return(NULL);
+    }
+  }
+  
+  if(specialized_device){ 
+    if(stat(specialized_device,&i_st)){
+      idperror(messagedest,messages,"\t\tCould not access device %s",
+	       specialized_device);
+      return(NULL);
+    }
+  }
+
+  /* we need to resolve any symlinks for the lookup code to work */
+
+  if(generic_device){
+    generic_device=test_resolve_symlink(generic_device,messagedest,messages);
+    if(generic_device==NULL)goto cdda_identify_scsi_fail;
+  }
+  if(specialized_device){
+    specialized_device=test_resolve_symlink(specialized_device,messagedest,messages);
+    if(specialized_device==NULL)goto cdda_identify_scsi_fail;
+  }
+
+  /* sgio is always preferred if it's there, unless user has forced the generic scsi device name */
+  if(use_sgio){
+    if(check_sgio(specialized_device,messagedest,messages)){
+      idmessage(messagedest,messages,"\t\tSG_IO device: %s",specialized_device);
+    }else{
+      idmessage(messagedest,messages,"\t\tno SG_IO support for device: %s",specialized_device);
+      use_sgio=0;
+    }
+  }
+  
+  if(!use_sgio){
+
+    /* was a generic device passed in as the specialized device name? */
+    if(specialized_device){ 
+      if((int)(i_st.st_rdev>>8)==SCSI_GENERIC_MAJOR){
+	char *temp=(char *)generic_device;
+	generic_device=specialized_device;
+	specialized_device=temp;
+      }
+      
+      if(!generic_device || !specialized_device){
+	if(generic_device){
+	  specialized_device=
+	    scsi_match(generic_device,scsi_cdrom_prefixes,
+		       devfs_scsi_test,devfs_scsi_cd,
+		       "\t\tNo cdrom device found to match generic device %s",
+		       messagedest,messages);
+	}else{
+	  generic_device=
+	    scsi_match(specialized_device,scsi_generic_prefixes,
+		       devfs_scsi_test,devfs_scsi_generic,
+		       "\t\tNo generic SCSI device found to match CDROM device %s",
+		       messagedest,messages);
+	  if(!generic_device)	
+	    goto cdda_identify_scsi_fail;
+	}
+      }
+    }
+    
+    idmessage(messagedest,messages,"\t\tgeneric device: %s",generic_device);
+    idmessage(messagedest,messages,"\t\tioctl device: %s",(specialized_device?
+							   specialized_device:
+							   "not found"));
+    if(specialized_device){
+      if(stat(specialized_device,&i_st)){
+	idperror(messagedest,messages,"\t\tCould not access cdrom device "
+		 "%s",specialized_device);
+	goto cdda_identify_scsi_fail;
+      }
+    }
+
+    if(stat(generic_device,&g_st)){
+      idperror(messagedest,messages,"\t\tCould not access generic SCSI device "
+	       "%s",generic_device);
+      
+      goto cdda_identify_scsi_fail;
+    }
+  }
+
+  if(specialized_device) {
+    if(use_sgio)
+      i_fd=open(specialized_device,O_RDWR|O_NONBLOCK);
+    else
+      i_fd=open(specialized_device,O_RDONLY|O_NONBLOCK);
+  }
+
+  if(generic_device)
+    g_fd=open(generic_device,O_RDWR);
+  
+  
+  if(specialized_device && i_fd==-1){
+    idperror(messagedest,messages,"\t\tCould not open cdrom device "
+	     "%s (continuing)",specialized_device);
+    goto cdda_identify_scsi_fail;
+  }
+  
+  if(generic_device && g_fd==-1){
+    idperror(messagedest,messages,"\t\tCould not open generic SCSI device "
+	     "%s",generic_device);
+    goto cdda_identify_scsi_fail;
+  }
+  
+  if(i_fd!=-1){
+    type=(int)(i_st.st_rdev>>8);
+
+    if(!use_sgio){
+      if(type==SCSI_CDROM_MAJOR){
+	if (!S_ISBLK(i_st.st_mode)) {
+	  idmessage(messagedest,messages,"\t\tSCSI CDROM device %s not a "
+		    "block device",specialized_device);
+	  goto cdda_identify_scsi_fail;
+	}
+      }else{
+	idmessage(messagedest,messages,"\t\tSCSI CDROM device %s has wrong "
+		  "major number",specialized_device);
+	goto cdda_identify_scsi_fail;
+      }
+    }
+  }
+  
+  if(g_fd != -1){
+    if((int)(g_st.st_rdev>>8)==SCSI_GENERIC_MAJOR){
+      if (!S_ISCHR(g_st.st_mode)) {
+	idmessage(messagedest,messages,"\t\tGeneric SCSI device %s not a "
+		  "char device",generic_device);
+	goto cdda_identify_scsi_fail;
+      }
+    }else{
+      idmessage(messagedest,messages,"\t\tGeneric SCSI device %s has wrong "
+		"major number",generic_device);
+      goto cdda_identify_scsi_fail;
+    }
+  }
+
+  d=calloc(1,sizeof(cdrom_drive));
+  d->drive_type=type;
+  d->cdda_fd=g_fd;
+  d->ioctl_fd=i_fd;
+  d->bigendianp=-1; /* We don't know yet... */
+  d->nsectors=-1;
+  d->messagedest = messagedest;
+  d->private_data=calloc(1,sizeof(*d->private_data));
+  {
+    /* goddamnit */
+    struct timespec tv;
+    d->private_data->clock=(clock_gettime(CLOCK_MONOTONIC,&tv)<0?CLOCK_REALTIME:CLOCK_MONOTONIC);
+  }
+  if(use_sgio){
+    d->interface=SGIO_SCSI;
+    d->private_data->sg_buffer=(unsigned char *)(d->private_data->sg_hd=malloc(MAX_BIG_BUFF_SIZE));
+    g_fd=d->cdda_fd=dup(d->ioctl_fd);
+  }else{
+    version=verify_SG_version(d,messagedest,messages);
+    switch(version){
+    case -1:case 0:case 1:
+      d->interface=GENERIC_SCSI;
+      goto cdda_identify_scsi_fail;
+    case 2:case 3:
+      d->interface=GENERIC_SCSI;
+      break;
+    }
+
+    /* malloc our big buffer for scsi commands */
+    d->private_data->sg_hd=malloc(MAX_BIG_BUFF_SIZE);
+    d->private_data->sg_buffer=((unsigned char *)d->private_data->sg_hd)+SG_OFF;
+  }
+
+  {
+    /* get the lun */
+    scsiid lun;
+    if(get_scsi_id(i_fd,&lun))
+      d->lun=0; /* a reasonable guess on a failed ioctl */
+    else
+      d->lun=lun.lun;
+  }
+
+  p = (char *)scsi_inquiry(d);
+
+  if (!p){
+
+    /* 2.6 kernels have a bug where the block layer implements
+       SG_DXFER_TO_FROM_DEVICE as a write operation instead of read.
+       I've submitted a kernel patch, but it will take a while to
+       propogate.  Work around it for now. --Monty */
+
+    if(d->interface == SGIO_SCSI){
+      /* test the inquiry with the workaround */
+      d->interface = SGIO_SCSI_BUGGY1;
+      p = (char *)scsi_inquiry(d);
+
+      if(p){
+	idmessage(messagedest,messages,
+		  "\t\tThis kernel's block layer has a buggy SG_DXFER_TO_FROM_DEVICE;\n\t\t   activating workaround.\n",NULL);
+      }else{
+	/* Failed for some reason other than the buggy ioctl(); set the interface type back */
+	d->interface = SGIO_SCSI;
+      }
+    }
+  }
+
+  if (!p){
+    idmessage(messagedest,messages,
+	      "\t\tInquiry command failed; unable to probe drive\n",NULL);
+    goto cdda_identify_scsi_fail;
+  }
+  
+  /* It would seem some TOSHIBA CDROMs gets things wrong */
+  if (p &&
+      !strncmp (p + 8, "TOSHIBA", 7) &&
+      !strncmp (p + 16, "CD-ROM", 6) &&
+      p[0] == TYPE_DISK) {
+    p[0] = TYPE_ROM;
+    p[1] |= 0x80;     /* removable */
+  }
+
+  if (*p != TYPE_ROM && *p != TYPE_WORM) {
+    idmessage(messagedest,messages,
+	      "\t\tDrive is neither a CDROM nor a WORM device\n",NULL);
+    goto cdda_identify_scsi_fail;
+  }
+
+  d->drive_model=calloc(36,1);
+  memcpy(d->inqbytes,p,4);
+  d->cdda_device_name=copystring(generic_device);
+  d->ioctl_device_name=copystring(specialized_device);
+  d->drive_model=calloc(36,1);
+  strscat(d->drive_model,p+8,8);
+  strscat(d->drive_model,p+16,16);
+  strscat(d->drive_model,p+32,4);
+
+  idmessage(messagedest,messages,"\nCDROM model sensed sensed: %s",d->drive_model);
+  return(d);
+  
+cdda_identify_scsi_fail:
+  if(generic_device)free((char *)generic_device);
+  if(specialized_device)free((char *)specialized_device);
+  if(i_fd!=-1)close(i_fd);
+  if(g_fd!=-1)close(g_fd);
+  if(d){
+    if(d->private_data){
+      if(d->private_data->sg_hd)free(d->private_data->sg_hd);
+      free(d->private_data);
+    }
+    free(d);
+  }
+  return(NULL);
+}
+
+#ifdef CDDA_TEST
+
+cdrom_drive *cdda_identify_test(const char *filename, int messagedest,
+				char **messages){
+  
+  cdrom_drive *d=NULL;
+  struct stat st;
+  int fd=-1,i;
+
+  idmessage(messagedest,messages,"\tTesting %s for file/test interface",
+	    filename);
+
+  if(stat(filename,&st)){
+    idperror(messagedest,messages,"\t\tCould not access file %s",
+	     filename);
+    return(NULL);
+  }
+
+  if(!S_ISREG(st.st_mode)){
+    idmessage(messagedest,messages,"\t\t%s is not a regular file",
+		  filename);
+    return(NULL);
+  }
+
+  fd=open(filename,O_RDONLY);
+  if(fd==-1){
+    idperror(messagedest,messages,"\t\tCould not open file %s",filename);
+    return(NULL);
+  }
+  
+  d=calloc(1,sizeof(cdrom_drive));
+
+  d->cdda_device_name=copystring(filename);
+  d->ioctl_device_name=copystring(filename);
+  d->drive_type=-1;
+  d->cdda_fd=fd;
+  d->ioctl_fd=fd;
+  d->interface=TEST_INTERFACE;
+  d->bigendianp=-1; /* We don't know yet... */
+  d->nsectors=-1;
+  d->private_data=calloc(1,sizeof(*d->private_data));
+  d->drive_model=copystring("File based test interface");
+  idmessage(messagedest,messages,"\t\tCDROM sensed: %s\n",d->drive_model);
+  
+  return(d);
+}
+
+#endif
Index: interface/scsi_interface.c
===================================================================
--- interface/scsi_interface.c	(nonexistent)
+++ interface/scsi_interface.c	(revision 5)
@@ -0,0 +1,1750 @@
+/******************************************************************
+ * CopyPolicy: GNU Lesser General Public License 2.1 applies
+ * Original interface.c Copyright (C) 1994-1997 
+ *            Eissfeldt heiko@colossus.escape.de
+ * Current incarnation Copyright (C) 1998-2008 Monty xiphmont@mit.edu
+ * 
+ * Generic SCSI interface specific code.
+ *
+ ******************************************************************/
+
+#include "low_interface.h"
+#include "common_interface.h"
+#include "utils.h"
+#include <time.h>
+static int timed_ioctl(cdrom_drive *d, int fd, int command, void *arg){
+  struct timespec tv1;
+  struct timespec tv2;
+  int ret1=clock_gettime(d->private_data->clock,&tv1);
+  int ret2=ioctl(fd, command,arg);
+  int ret3=clock_gettime(d->private_data->clock,&tv2);
+  if(ret1<0 || ret3<0){
+    d->private_data->last_milliseconds=-1;
+  }else{
+    d->private_data->last_milliseconds = (tv2.tv_sec-tv1.tv_sec)*1000. + (tv2.tv_nsec-tv1.tv_nsec)/1000000.;
+  }
+  return ret2;
+}
+
+/* hook */
+static int Dummy (cdrom_drive *d,int s){
+  return(0);
+}
+
+#include "drive_exceptions.h"
+static void tweak_SG_buffer(cdrom_drive *d) {
+  int table, reserved, cur, err;
+  char buffer[256];
+
+  /* SG_SET_RESERVED_SIZE doesn't actually allocate or reserve anything.
+   * what it _does_ do is give you an error if you ask for a value
+   * larger than q->max_sectors (the length of the device's bio request
+   * queue).  So we walk it up from 1 sector until it fails, then get
+   * the value we set it to last.
+   */
+  /* start with 2 frames, round down to our queue's sector size */
+  cur = 1;
+  do {
+    cur <<= 1; reserved = cur * (1<<9);
+    err = ioctl(d->cdda_fd, SG_SET_RESERVED_SIZE, &reserved);
+  } while(err >= 0 && (cur*(1<<9) < 0x40000000));
+  ioctl(d->cdda_fd, SG_GET_RESERVED_SIZE, &reserved);
+
+  /* this doesn't currently ever work, but someday somebody might
+     implement working sg lists with SG_IO devices, so who knows... */
+  if (ioctl(d->cdda_fd, SG_GET_SG_TABLESIZE, &table) < 0)
+    table=1;
+
+  sprintf(buffer,"\tDMA scatter/gather table entries: %d\n\t"
+	  "table entry size: %d bytes\n\t"
+	  "maximum theoretical transfer: %d sectors\n",
+	  table, reserved, table*(reserved/CD_FRAMESIZE_RAW));
+  cdmessage(d,buffer);
+
+  cur=reserved; /* only use one entry for now */
+
+  /* so since we never go above q->max_sectors, we should never get -EIO.
+   * we might still get -ENOMEM, but we back off for that later.  Monty
+   * had an old comment: "not too much; new kernels have trouble with DMA
+   * allocation, so be more conservative: 32kB max until I test more 
+   * thoroughly".  We're not currently honoring that, because we should
+   * always get -ENOMEM.
+   *
+   * Updated: but we don't always get -ENOMEM.  Sometimes USB drives 
+   * still fail the wrong way.  This needs some kernel-land investigation.
+   */
+  /* Bumping to 64kB  transfer max --Monty */
+
+  if (!getenv("CDDA_IGNORE_BUFSIZE_LIMIT")) {
+    cur=(cur>1024*64?1024*64:cur);
+  }else{
+    cdmessage(d,"\tEnvironment variable CDDA_IGNORE_BUFSIZE_LIMIT set,\n"
+	      "\t\tforcing maximum possible sector size.  This can break\n"
+	      "\t\tspectacularly; use with caution!\n");
+  }
+  d->nsectors=cur/CD_FRAMESIZE_RAW;
+  d->bigbuff=cur;
+
+  sprintf(buffer,"\tSetting default read size to %d sectors (%d bytes).\n\n",
+	  d->nsectors,d->nsectors*CD_FRAMESIZE_RAW);
+
+  if(cur==0) exit(1);
+
+  cdmessage(d,buffer);
+}
+
+static void clear_garbage(cdrom_drive *d){
+  fd_set fdset;
+  struct timeval tv;
+  struct sg_header *sg_hd=d->private_data->sg_hd;
+  int flag=0;
+
+  /* clear out any possibly preexisting garbage */
+  FD_ZERO(&fdset);
+  FD_SET(d->cdda_fd,&fdset);
+  tv.tv_sec=0;
+  tv.tv_usec=0;
+
+  /* I like select */
+  while(select(d->cdda_fd+1,&fdset,NULL,NULL,&tv)==1){
+    
+    sg_hd->twelve_byte = 0;
+    sg_hd->result = 0;
+    sg_hd->reply_len = SG_OFF;
+    read(d->cdda_fd, sg_hd, 1);
+
+    /* reset for select */
+    FD_ZERO(&fdset);
+    FD_SET(d->cdda_fd,&fdset);
+    tv.tv_sec=0;
+    tv.tv_usec=0;
+    if(!flag && d->report_all)
+      cdmessage(d,"Clearing previously returned data from SCSI buffer\n");
+    flag=1;
+  }
+}
+
+static int check_sbp_error(const unsigned char status,
+			   const unsigned char *sbp) {
+  char key = sbp[2] & 0xf;
+  char ASC = sbp[12];
+  char ASCQ = sbp[13];
+  
+  if(status==0)return 0;
+  if(status==8)return TR_BUSY;
+
+  if (sbp[0]) {  
+    switch (key){
+    case 0:
+      if (errno==0)
+	errno = EIO;
+      return(TR_UNKNOWN);
+    case 1:
+      break;
+    case 2:
+      errno = ENOMEDIUM;
+      return(TR_NOTREADY);
+    case 3: 
+      if ((ASC==0x0C) & (ASCQ==0x09)) {
+	/* loss of streaming */
+	if (errno==0)
+	  errno = EIO;
+	return(TR_STREAMING);
+      } else {
+	if (errno==0)
+	  errno = EIO;
+	return(TR_MEDIUM);
+      }
+    case 4:
+      if (errno==0)
+	errno = EIO;
+      return(TR_FAULT);
+    case 5:
+      if (errno==0)
+	errno = EINVAL;
+      return(TR_ILLEGAL);
+    default:
+      if (errno==0)
+	errno = EIO;
+      return(TR_UNKNOWN);
+    }
+  }
+  return 0;
+}
+
+/* process a complete scsi command. */
+static int sg2_handle_scsi_cmd(cdrom_drive *d,
+			       unsigned char *cmd,
+			       unsigned int cmd_len, 
+			       unsigned int in_size, 
+			       unsigned int out_size,       
+			       unsigned char bytefill,
+			       int bytecheck,
+			       unsigned char *sense_buffer){
+  struct timespec tv1;
+  struct timespec tv2;
+  int tret1,tret2;
+  int status = 0;
+  struct sg_header *sg_hd=d->private_data->sg_hd;
+  long writebytes=SG_OFF+cmd_len+in_size;
+
+  /* generic scsi device services */
+
+  /* clear out any possibly preexisting garbage */
+  clear_garbage(d);
+
+  memset(sg_hd,0,sizeof(sg_hd)); 
+  memset(sense_buffer,0,SG_MAX_SENSE); 
+  memcpy(d->private_data->sg_buffer,cmd,cmd_len+in_size);
+  sg_hd->twelve_byte = cmd_len == 12;
+  sg_hd->result = 0;
+  sg_hd->reply_len = SG_OFF + out_size;
+
+  /* The following is one of the scariest hacks I've ever had to use.
+     The idea is this: We want to know if a command fails.  The
+     generic scsi driver (as of now) won't tell us; it hands back the
+     uninitialized contents of the preallocated kernel buffer.  We
+     force this buffer to a known value via another bug (nonzero data
+     length for a command that doesn't take data) such that we can
+     tell if the command failed.  Scared yet? */
+
+  if(bytecheck && out_size>in_size){
+    memset(d->private_data->sg_buffer+cmd_len+in_size,bytefill,out_size-in_size); 
+    /* the size does not remove cmd_len due to the way the kernel
+       driver copies buffers */
+    writebytes+=(out_size-in_size);
+  }
+
+  {
+    /* Select on write with a 5 second timeout.  This is a hack until
+       a better error reporting layer is in place; right now, always
+       print a message. */
+
+    fd_set fdset;
+    struct timeval tv;
+
+    FD_ZERO(&fdset);
+    FD_SET(d->cdda_fd,&fdset);
+    tv.tv_sec=60; /* Increased to 1m for plextor, as the drive will
+                     try to get through rough spots on its own and
+                     this can take time 19991129 */
+    tv.tv_usec=0;
+
+    while(1){
+      int ret=select(d->cdda_fd+1,NULL,&fdset,NULL,&tv);
+      if(ret>0)break;
+      if(ret<0 && errno!=EINTR)break;
+      if(ret==0){
+	fprintf(stderr,"\nSCSI transport error: timeout waiting to write"
+		" packet\n\n");
+	return(TR_EWRITE);
+      }
+    }
+  }
+
+  sigprocmask (SIG_BLOCK, &(d->sigset), NULL );
+  tret1=clock_gettime(d->private_data->clock,&tv1);  
+  errno=0;
+  status = write(d->cdda_fd, sg_hd, writebytes );
+
+  if (status<0 || status != writebytes ) {
+    sigprocmask ( SIG_UNBLOCK, &(d->sigset), NULL );
+    if(errno==0)errno=EIO;
+    return(TR_EWRITE);
+  }
+
+  
+  {
+    /* Select on read (and write; this signals an error) with a 5
+       second timeout.  This is a hack until a better error reporting
+       layer is in place; right now, always print a
+       message. */
+
+    fd_set rset;
+    struct timeval tv;
+
+    FD_ZERO(&rset);
+    FD_SET(d->cdda_fd,&rset);
+    tv.tv_sec=60; /* Increased to 1m for plextor, as the drive will
+                     try to get through rough spots on its own and
+                     this can take time 19991129 */
+    tv.tv_usec=0;
+
+    while(1){
+      int ret=select(d->cdda_fd+1,&rset,NULL,NULL,&tv);
+      if(ret<0 && errno!=EINTR)break;
+      if(ret==0){
+	sigprocmask ( SIG_UNBLOCK, &(d->sigset), NULL );
+	fprintf(stderr,"\nSCSI transport error: timeout waiting to read"
+		" packet\n\n");
+	return(TR_EREAD);
+      }
+      if(ret>0){
+	/* is it readable or something else? */
+	if(FD_ISSET(d->cdda_fd,&rset))break;
+	sigprocmask ( SIG_UNBLOCK, &(d->sigset), NULL );
+	fprintf(stderr,"\nSCSI transport: error reading packet\n\n");
+	return(TR_EREAD);
+      }
+    }
+  }
+
+  tret2=clock_gettime(d->private_data->clock,&tv2);  
+  errno=0;
+  status = read(d->cdda_fd, sg_hd, SG_OFF + out_size);
+  sigprocmask ( SIG_UNBLOCK, &(d->sigset), NULL );
+  memcpy(sense_buffer,sg_hd->sense_buffer,SG_MAX_SENSE);
+
+  if (status<0)return status;
+
+  if(status != SG_OFF + out_size || sg_hd->result){
+    if(errno==0)errno=EIO;
+    return(TR_EREAD);
+  }
+
+  status = check_sbp_error(sg_hd->target_status, sense_buffer);
+  if(status)return status;
+
+  /* Failed/Partial DMA transfers occasionally get through.  Why?  No clue,
+     but it's been demonstrated in practice that we occasionally get
+     fewer bytes than we request with no indication anything went
+     wrong. */
+  
+  if(bytecheck && in_size+cmd_len<out_size){
+    long i,flag=0;
+    for(i=in_size;i<out_size;i++)
+      if(d->private_data->sg_buffer[i]!=bytefill){
+	flag=1;
+	break;
+      }
+    
+    if(!flag){
+      errno=EINVAL;
+      return(TR_ILLEGAL);
+    }
+  }
+
+  errno=0;
+  if(tret1<0 || tret2<0){
+    d->private_data->last_milliseconds=-1;
+  }else{
+    d->private_data->last_milliseconds = (tv2.tv_sec-tv1.tv_sec)*1000 + (tv2.tv_nsec-tv1.tv_nsec)/1000000;
+  }
+  return(0);
+}
+
+static int sgio_handle_scsi_cmd(cdrom_drive *d,
+				unsigned char *cmd,
+				unsigned int cmd_len, 
+				unsigned int in_size, 
+				unsigned int out_size,       
+				unsigned char bytefill,
+				int bytecheck,
+				unsigned char *sense){
+
+  int status = 0;
+  struct sg_io_hdr hdr;
+
+  memset(&hdr,0,sizeof(hdr));
+  memset(sense,0,sizeof(sense));
+  memcpy(d->private_data->sg_buffer,cmd+cmd_len,in_size);
+
+  hdr.cmdp = cmd;
+  hdr.cmd_len = cmd_len;
+  hdr.sbp = sense;
+  hdr.mx_sb_len = SG_MAX_SENSE;
+  hdr.timeout = 50000;
+  hdr.interface_id = 'S';
+  hdr.dxferp =  d->private_data->sg_buffer;
+  hdr.flags = SG_FLAG_DIRECT_IO;  /* direct IO if we can get it */
+
+  /* scary buffer fill hack */
+  if(bytecheck && out_size>in_size)
+    memset(hdr.dxferp+in_size,bytefill,out_size-in_size); 
+
+  if (in_size) {
+    hdr.dxfer_len = in_size;
+    hdr.dxfer_direction = SG_DXFER_TO_DEV;
+    
+    errno = 0;
+    status = ioctl(d->ioctl_fd, SG_IO, &hdr);
+    if (status >= 0 && hdr.status){
+      status = check_sbp_error(hdr.status,hdr.sbp);
+      if(status) return status;
+    }
+    if (status < 0) return TR_EWRITE;
+  }
+
+  if (!in_size | out_size) {
+    hdr.dxfer_len = out_size;
+
+    if(bytecheck && d->interface != SGIO_SCSI_BUGGY1)
+      hdr.dxfer_direction = out_size ? SG_DXFER_TO_FROM_DEV : SG_DXFER_NONE;
+    else
+      hdr.dxfer_direction = out_size ? SG_DXFER_FROM_DEV : SG_DXFER_NONE;
+
+    errno = 0;
+    status = timed_ioctl(d,d->ioctl_fd, SG_IO, &hdr);
+    if (status >= 0 && hdr.status){
+      status = check_sbp_error(hdr.status,hdr.sbp);
+      if(status) return status;
+    }
+    if (status < 0) return status;
+  }
+
+  /* Failed/Partial DMA transfers occasionally get through.  Why?  No clue,
+     but it's been demonstrated in practice that we occasionally get
+     fewer bytes than we request with no indication anything went
+     wrong. */
+  
+  if(bytecheck && in_size<out_size){
+    long i,flag=0;
+    for(i=in_size;i<out_size;i++)
+      if(d->private_data->sg_buffer[i]!=bytefill){
+	flag=1;
+	break;
+      }
+    
+    if(!flag){
+      errno=EINVAL;
+      return(TR_ILLEGAL);
+    }
+  }
+
+  /* Can't rely on .duration because we can't be certain kernel has HZ set to something useful */
+  /* d->private_data->last_milliseconds = hdr.duration; */
+
+  errno = 0;
+  return 0;
+}
+
+static int handle_scsi_cmd(cdrom_drive *d,
+			   unsigned char *cmd,
+			   unsigned int cmd_len, 
+			   unsigned int in_size, 
+			   unsigned int out_size,       
+			   unsigned char bytefill,
+			   int bytecheck,
+			   unsigned char *sense){
+
+  if(d->interface == SGIO_SCSI || d->interface == SGIO_SCSI_BUGGY1)
+    return sgio_handle_scsi_cmd(d,cmd,cmd_len,in_size,out_size,bytefill,bytecheck,sense);
+  return sg2_handle_scsi_cmd(d,cmd,cmd_len,in_size,out_size,bytefill,bytecheck,sense);
+
+}
+
+static int test_unit_ready(cdrom_drive *d){
+  unsigned char sense[SG_MAX_SENSE];
+  unsigned char key, ASC, ASCQ;
+  unsigned char cmd[6] =  { 0x00, /* TEST_UNIT_READY */	
+			    0, /* reserved */
+			    0, /* reserved */
+			    0, /* reserved */
+			    0, /* reserved */
+			    0};/* control */		
+
+  handle_scsi_cmd(d, cmd, 6, 0, 56, 0,0, sense);
+
+  key = d->private_data->sg_buffer[2] & 0xf;
+  ASC = d->private_data->sg_buffer[12];
+  ASCQ = d->private_data->sg_buffer[13];
+  
+  if(key == 2 && ASC == 4 && ASCQ == 1) return 0;
+  return 1;
+}
+
+static void reset_scsi(cdrom_drive *d){
+  int arg,tries=0;
+  d->enable_cdda(d,0);
+
+  cdmessage(d,"sending SG SCSI reset... ");
+  if(ioctl(d->cdda_fd,SG_SCSI_RESET,&arg))
+    cdmessage(d,"FAILED: EBUSY\n");
+  else
+    cdmessage(d,"OK\n");
+
+  while(1) {
+    if(test_unit_ready(d))break;
+    tries++;
+    usleep(10);
+  }
+  
+  d->enable_cdda(d,1);
+}
+
+static int mode_sense_atapi(cdrom_drive *d,int size,int page){ 
+  unsigned char sense[SG_MAX_SENSE];
+  unsigned char cmd[10]= {0x5A,   /* MODE_SENSE */
+			  0x00, /* reserved */
+			  0x00, /* page */
+			  0,    /* reserved */
+			  0,    /* reserved */
+			  0,    /* reserved */
+			  0,    /* reserved */
+			  0,    /* MSB (0) */
+			  0,    /* sizeof(modesense - SG_OFF) */
+			  0};   /* reserved */ 
+
+  cmd[1]=d->lun<<5;
+  cmd[2]=0x3F&page;
+  cmd[8]=size+4;
+
+  if (handle_scsi_cmd (d, cmd, 10, 0, size+4,'\377',1,sense)) return(1);
+
+  {
+    unsigned char *b=d->private_data->sg_buffer;
+    if(b[0])return(1); /* Handles only up to 256 bytes */
+    if(b[6])return(1); /* Handles only up to 256 bytes */
+
+    b[0]=b[1]-3;
+    b[1]=b[2];
+    b[2]=b[3];
+    b[3]=b[7];
+
+    memmove(b+4,b+8,size-4);
+  }
+  return(0);
+}
+
+/* group 0 (6b) command */
+
+static int mode_sense_scsi(cdrom_drive *d,int size,int page){  
+  unsigned char sense[SG_MAX_SENSE];
+  unsigned char cmd[6]={0x1A, /* MODE_SENSE */
+			0x00, /* return block descriptor/lun */
+			0x00, /* page */
+			0,    /* reserved */
+			0,    /* sizeof(modesense - SG_OFF) */
+			0};   /* control */ 
+  
+  cmd[1]=d->lun<<5;
+  cmd[2]=(0x3F&page);
+  cmd[4]=size;
+  
+  if (handle_scsi_cmd (d, cmd, 6, 0, size, '\377',1, sense)) return(1);
+
+  /* dump it all... */
+  
+
+  return(0);
+}
+
+static int mode_sense(cdrom_drive *d,int size,int page){
+  if(d->is_atapi)
+    return(mode_sense_atapi(d,size,page));
+  return(mode_sense_scsi(d,size,page));
+}
+
+/* Current SG/SGIO impleenmtations specifically disallow mode set
+   unless running as root (or setuid).  One can see why (could be
+   disastrous on, eg, a SCSI disk), but it curtails what we can do
+   with older SCSI cdroms. */
+static int mode_select(cdrom_drive *d,int density,int secsize){
+  /* short circut the way Heiko does it; less flexible, but shorter */
+  unsigned char sense[SG_MAX_SENSE];
+  if(d->is_atapi){
+    unsigned char cmd[26] = { 0x55, /* MODE_SELECT */
+			      0x10, /* no save page */
+			      0,    /* reserved */
+			      0,    /* reserved */
+			      0,    /* reserved */
+			      0,    /* reserved */
+			      0,    /* reserved */
+			      0,    /* reserved */
+			      16,   /* sizeof(mode) */
+			      0,    /* reserved */
+			      
+			      /* mode parameter header */
+			      0, 0, 0, 0,  0, 0, 0, 
+			      8, /* Block Descriptor Length */
+			      
+			      /* descriptor block */
+			      0,       /* Density Code */
+			      0, 0, 0, /* # of Blocks */
+			      0,       /* reserved */
+			      0, 0, 0};/* Blocklen */
+    unsigned char *mode = cmd + 18;
+    
+    /* prepare to read cds in the previous mode */
+    mode [0] = density;
+    mode [6] = secsize >> 8;   /* block length "msb" */
+    mode [7] = secsize & 0xFF; /* block length lsb */
+
+    /* do the scsi cmd */
+    return(handle_scsi_cmd (d,cmd,10, 16, 0,0,0,sense));
+
+  }else{
+    unsigned char cmd[18] = { 0x15, /* MODE_SELECT */
+			      0x10, /* no save page */
+			      0, /* reserved */
+			      0, /* reserved */
+			      12, /* sizeof(mode) */
+			      0, /* reserved */
+			      /* mode section */
+			      0, 
+			      0, 0, 
+			      8,       /* Block Descriptor Length */
+			      0,       /* Density Code */
+			      0, 0, 0, /* # of Blocks */
+			      0,       /* reserved */
+			      0, 0, 0};/* Blocklen */
+    unsigned char *mode = cmd + 10;
+
+    /* prepare to read cds in the previous mode */
+    mode [0] = density;
+    mode [6] =  secsize >> 8;   /* block length "msb" */
+    mode [7] =  secsize & 0xFF; /* block length lsb */
+
+    /* do the scsi cmd */
+    return(handle_scsi_cmd (d,cmd,6, 12, 0,0,0,sense));
+  }
+}
+
+/* get current sector size from SCSI cdrom drive */
+static unsigned int get_orig_sectorsize(cdrom_drive *d){
+  if(mode_sense(d,12,0x01))return(-1);
+
+  d->orgdens = d->private_data->sg_buffer[4];
+  return(d->orgsize = ((int)(d->private_data->sg_buffer[10])<<8)+d->private_data->sg_buffer[11]);
+}
+
+/* switch CDROM scsi drives to given sector size  */
+static int set_sectorsize (cdrom_drive *d,unsigned int secsize){
+  return(mode_select(d,d->orgdens,secsize));
+}
+
+/* switch old Toshiba/DEC and HP drives from/to cdda density */
+int scsi_enable_cdda (cdrom_drive *d, int fAudioMode){
+  if (fAudioMode) {
+    if(mode_select(d,d->density,CD_FRAMESIZE_RAW)){
+      if(d->error_retry)
+	cderror(d,"001: Unable to set CDROM to read audio mode\n");
+      return(-1);
+    }
+  } else {
+    if(mode_select(d,d->orgdens,d->orgsize)){
+      if(d->error_retry)
+	cderror(d,"001: Unable to set CDROM to read audio mode\n");
+      return(-1);
+    }
+  }
+  return(0);
+}
+
+typedef struct scsi_TOC {  /* structure of scsi table of contents (cdrom) */
+  unsigned char reserved1;
+  unsigned char bFlags;
+  unsigned char bTrack;
+  unsigned char reserved2;
+  signed char start_MSB;
+  unsigned char start_1;
+  unsigned char start_2;
+  unsigned char start_LSB;
+} scsi_TOC;
+
+
+/* read the table of contents from the cd and fill the TOC array */
+/* Do it like the kernel ioctl driver; the 'all at once' approach
+   fails on at least one Kodak drive. */
+
+static int scsi_read_toc (cdrom_drive *d){
+  int i,first,last;
+  unsigned tracks;
+
+  /* READTOC, MSF format flag, res, res, res, res, Start track, len msb,
+     len lsb, flags */
+
+  /* read the header first */
+  unsigned char sense[SG_MAX_SENSE];
+  unsigned char cmd[10] = { 0x43, 0, 0, 0, 0, 0, 1, 0, 12, 0};
+  cmd[1]=d->lun<<5;
+
+  if (handle_scsi_cmd (d,cmd,10, 0, 12,'\377',1,sense)){
+    cderror(d,"004: Unable to read table of contents header\n");
+    return(-4);
+  }
+
+  first=d->private_data->sg_buffer[2];
+  last=d->private_data->sg_buffer[3];
+  tracks=last-first+1;
+
+  if (last > MAXTRK || first > MAXTRK || last<0 || first<0) {
+    cderror(d,"003: CDROM reporting illegal number of tracks\n");
+    return(-3);
+  }
+
+  for (i = first; i <= last; i++){
+    memcpy(cmd, (char []){ 0x43, 0, 0, 0, 0, 0, 0, 0, 12, 0}, 10);
+    cmd[1]=d->lun<<5;
+    cmd[6]=i;
+    
+    if (handle_scsi_cmd (d,cmd, 10, 0, 12,'\377',1,sense)){
+      cderror(d,"005: Unable to read table of contents entry\n");
+      return(-5);
+    }
+    {
+      scsi_TOC *toc=(scsi_TOC *)(d->private_data->sg_buffer+4);
+
+      d->disc_toc[i-first].bFlags=toc->bFlags;
+      d->disc_toc[i-first].bTrack=i;
+      d->disc_toc[i-first].dwStartSector= d->adjust_ssize * 
+	(((int)(toc->start_MSB)<<24) | 
+	 (toc->start_1<<16)|
+	 (toc->start_2<<8)|
+	 (toc->start_LSB));
+    }
+  }
+
+  memcpy(cmd, (char []){ 0x43, 0, 0, 0, 0, 0, 0, 0, 12, 0}, 10);
+  cmd[1]=d->lun<<5;
+  cmd[6]=0xAA;
+    
+  if (handle_scsi_cmd (d,cmd,10, 0, 12,'\377',1,sense)){
+    cderror(d,"002: Unable to read table of contents lead-out\n");
+    return(-2);
+  }
+  {
+    scsi_TOC *toc=(scsi_TOC *)(d->private_data->sg_buffer+4);
+    
+    d->disc_toc[i-first].bFlags=toc->bFlags;
+    d->disc_toc[i-first].bTrack=0xAA;
+    d->disc_toc[i-first].dwStartSector= d->adjust_ssize * 
+	(((int)(toc->start_MSB)<<24) | 
+	 (toc->start_1<<16)|
+	 (toc->start_2<<8)|
+	 (toc->start_LSB));
+  }
+
+  d->cd_extra = FixupTOC(d,tracks+1); /* include lead-out */
+  return(tracks);
+}
+
+/* a contribution from Boris for IMS cdd 522 */
+/* check this for ACER/Creative/Foo 525,620E,622E, etc? */
+static int scsi_read_toc2 (cdrom_drive *d){
+  u_int32_t foo,bar;
+
+  int i;
+  unsigned tracks;
+
+  unsigned char cmd[10] = { 0xe5, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+  unsigned char sense[SG_MAX_SENSE];
+  cmd[5]=1;
+  cmd[8]=255;
+
+  if (handle_scsi_cmd (d,cmd,10, 0, 256,'\377',1,sense)){
+    cderror(d,"004: Unable to read table of contents header\n");
+    return(-4);
+  }
+
+  /* copy to our structure and convert start sector */
+  tracks = d->private_data->sg_buffer[1];
+  if (tracks > MAXTRK) {
+    cderror(d,"003: CDROM reporting illegal number of tracks\n");
+    return(-3);
+  }
+
+  for (i = 0; i < tracks; i++){
+    memcpy(cmd, (char[]){ 0xe5, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 10);
+    cmd[5]=i+1;
+    cmd[8]=255;
+    
+    if (handle_scsi_cmd (d,cmd,10, 0, 256,'\377',1,sense)){
+      cderror(d,"005: Unable to read table of contents entry\n");
+      return(-5);
+    }
+    
+    d->disc_toc[i].bFlags = d->private_data->sg_buffer[10];
+    d->disc_toc[i].bTrack = i + 1;
+
+    d->disc_toc[i].dwStartSector= d->adjust_ssize * 
+	(((signed char)(d->private_data->sg_buffer[2])<<24) | 
+	 (d->private_data->sg_buffer[3]<<16)|
+	 (d->private_data->sg_buffer[4]<<8)|
+	 (d->private_data->sg_buffer[5]));
+  }
+
+  d->disc_toc[i].bFlags = 0;
+  d->disc_toc[i].bTrack = i + 1;
+  memcpy (&foo, d->private_data->sg_buffer+2, 4);
+  memcpy (&bar, d->private_data->sg_buffer+6, 4);
+  d->disc_toc[i].dwStartSector = d->adjust_ssize * (be32_to_cpu(foo) +
+						    be32_to_cpu(bar));
+
+  d->disc_toc[i].dwStartSector= d->adjust_ssize * 
+    ((((signed char)(d->private_data->sg_buffer[2])<<24) | 
+      (d->private_data->sg_buffer[3]<<16)|
+      (d->private_data->sg_buffer[4]<<8)|
+      (d->private_data->sg_buffer[5]))+
+     
+     ((((signed char)(d->private_data->sg_buffer[6])<<24) | 
+       (d->private_data->sg_buffer[7]<<16)|
+       (d->private_data->sg_buffer[8]<<8)|
+       (d->private_data->sg_buffer[9]))));
+
+
+  d->cd_extra = FixupTOC(d,tracks+1);
+  return(tracks);
+}
+
+static int scsi_set_speed (cdrom_drive *d, int speed){
+  unsigned char cmd[12]={0xBB, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0, 0, 0};
+  unsigned char sense[SG_MAX_SENSE];
+
+  if(speed>=0)
+    speed=speed*44100*4/1024;
+  else
+    speed=-1;
+  cmd[2] = (speed >> 8) & 0xFF;
+  cmd[3] = (speed) & 0xFF;
+  return handle_scsi_cmd(d,cmd,12,0,0,0,0,sense);
+}
+
+/* These do one 'extra' copy in the name of clean code */
+
+static int i_read_28 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[10]={0x28, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+  if(d->fua)
+    cmd[1]=0x08;
+
+  cmd[1]|=d->lun<<5;
+
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[8] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,10,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_A8 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+  
+  if(d->fua)
+    cmd[1]=0x08;
+  
+  cmd[1]|=d->lun<<5;
+
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[9] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_D4_10 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[10]={0xd4, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+  
+  if(d->fua)
+    cmd[1]=0x08;
+
+  cmd[1]|=d->lun<<5;
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[8] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,10,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_D4_12 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xd4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+  if(d->fua)
+    cmd[1]=0x08;
+
+  cmd[1]|=d->lun<<5;
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[9] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_D5 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[10]={0xd5, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+  if(d->fua)
+    cmd[1]=0x08;
+
+  cmd[1]|=d->lun<<5;
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[8] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,10,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_D8 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xd8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+  if(d->fua)
+    cmd[1]=0x08;
+
+  cmd[1]|=d->lun<<5;
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[9] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_mmc (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xbe, 0x2, 0, 0, 0, 0, 0, 0, 0, 0x10, 0, 0};
+
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[8] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_mmcB (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xbe, 0x0, 0, 0, 0, 0, 0, 0, 0, 0x10, 0, 0};
+
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[8] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_mmc2 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xbe, 0x2, 0, 0, 0, 0, 0, 0, 0, 0xf8, 0, 0};
+
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[8] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_mmc2B (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xbe, 0x0, 0, 0, 0, 0, 0, 0, 0, 0xf8, 0, 0};
+
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[8] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_mmc3 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xbe, 0x6, 0, 0, 0, 0, 0, 0, 0, 0xf8, 0, 0};
+
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[8] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_mmc3B (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xbe, 0x4, 0, 0, 0, 0, 0, 0, 0, 0xf8, 0, 0};
+
+  cmd[3] = (begin >> 16) & 0xFF;
+  cmd[4] = (begin >> 8) & 0xFF;
+  cmd[5] = begin & 0xFF;
+  cmd[8] = sectors;
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+/* straight from the MMC3 spec */
+static inline void LBA_to_MSF(long lba,
+			      unsigned char *M, 
+			      unsigned char *S, 
+			      unsigned char *F){
+  if(lba>=-150){
+    *M=(lba+150)/(60*75);
+    lba-=(*M)*60*75;
+    *S=(lba+150)/75;
+    lba-=(*S)*75;
+    *F=(lba+150);
+  }else{
+    *M=(lba+450150)/(60*75);
+    lba-=(*M)*60*75;
+    *S=(lba+450150)/75;
+    lba-=(*S)*75;
+    *F=(lba+450150);
+  }
+}
+
+
+static int i_read_msf (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xb9, 0, 0, 0, 0, 0, 0, 0, 0, 0x10, 0, 0};
+
+  LBA_to_MSF(begin,cmd+3,cmd+4,cmd+5);
+  LBA_to_MSF(begin+sectors,cmd+6,cmd+7,cmd+8);
+
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_msf2 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xb9, 0, 0, 0, 0, 0, 0, 0, 0, 0xf8, 0, 0};
+
+  LBA_to_MSF(begin,cmd+3,cmd+4,cmd+5);
+  LBA_to_MSF(begin+sectors,cmd+6,cmd+7,cmd+8);
+
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+static int i_read_msf3 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
+  int ret;
+  unsigned char cmd[12]={0xb9, 4, 0, 0, 0, 0, 0, 0, 0, 0xf8, 0, 0};
+  
+  LBA_to_MSF(begin,cmd+3,cmd+4,cmd+5);
+  LBA_to_MSF(begin+sectors,cmd+6,cmd+7,cmd+8);
+  
+  if((ret=handle_scsi_cmd(d,cmd,12,0,sectors * CD_FRAMESIZE_RAW,'\177',1,sense)))
+    return(ret);
+  if(p)memcpy(p,d->private_data->sg_buffer,sectors*CD_FRAMESIZE_RAW);
+  return(0);
+}
+
+
+static long scsi_read_map (cdrom_drive *d, void *p, long begin, long sectors,
+			   int (*map)(cdrom_drive *, void *, long, long, 
+				      unsigned char *)){
+  unsigned char sense[SG_MAX_SENSE];
+  int retry_count,err;
+  char *buffer=(char *)p;
+
+  /* read d->nsectors at a time, max. */
+  sectors=(sectors>d->nsectors?d->nsectors:sectors);
+  sectors=(sectors<1?1:sectors);
+
+  retry_count=0;
+  
+  while(1) {
+
+    if((err=map(d,(p?buffer:NULL),begin,sectors,sense))){
+      if(d->report_all){
+	char b[256];
+
+	sprintf(b,"scsi_read error: sector=%ld length=%ld retry=%d\n",
+		begin,sectors,retry_count);
+	cdmessage(d,b);
+	sprintf(b,"                 Sense key: %x ASC: %x ASCQ: %x\n",
+		(int)(sense[2]&0xf),
+		(int)(sense[12]),
+		(int)(sense[13]));
+	cdmessage(d,b);
+	sprintf(b,"                 Transport error: %s\n",strerror_tr[err]);
+	cdmessage(d,b);
+	sprintf(b,"                 System error: %s\n",strerror(errno));
+	cdmessage(d,b);
+
+	fprintf(stderr,"scsi_read error: sector=%ld length=%ld retry=%d\n",
+		begin,sectors,retry_count);
+	fprintf(stderr,"                 Sense key: %x ASC: %x ASCQ: %x\n",
+		(int)(sense[2]&0xf),
+		(int)(sense[12]),
+		(int)(sense[13]));
+	fprintf(stderr,"                 Transport error: %s\n",strerror_tr[err]);
+	fprintf(stderr,"                 System error: %s\n",strerror(errno));
+      }
+      
+      switch(errno){
+      case EINTR:
+	usleep(100);
+	continue;
+      case ENOMEM:
+	/* D'oh.  Possible kernel error. Keep limping */
+	usleep(100);
+	if(sectors==1){
+	  /* Nope, can't continue */
+	  cderror(d,"300: Kernel memory error\n");
+	  return(-300);  
+	}
+	if(d->report_all){
+	  char b[256];
+	  sprintf(b,"scsi_read: kernel couldn't alloc %ld bytes.  "
+		  "backing off...\n",sectors*CD_FRAMESIZE_RAW);
+	    
+	  cdmessage(d,b);
+	}
+	sectors--;
+	continue;
+      case ENOMEDIUM:
+	cderror(d,"404: No medium present\n");
+	return(-404);
+
+      default:
+	if(sectors==1){
+	  if(errno==EIO)
+	    if(d->fua==-1) /* testing for FUA support */
+	      return(-7);
+	  
+	  /* *Could* be I/O or media error.  I think.  If we're at
+	     30 retries, we better skip this unhappy little
+	     sector. */
+	  if(retry_count>MAX_RETRIES-1){
+	    char b[256];
+	    sprintf(b,"010: Unable to access sector %ld\n",
+		    begin);
+	    cderror(d,b);
+	    return(-10);
+	    
+	  }
+	  break;
+	}
+
+	/* Hmm.  OK, this is just a tad silly.  just in case this was
+           a timeout and a reset happened, we need to set the drive
+           back to cdda */
+	reset_scsi(d);
+      }
+      if(!d->error_retry)return(-7);
+
+    }else{
+
+      /* Did we get all the bytes we think we did, or did the kernel
+         suck? */
+      if(buffer){
+	long i;
+	for(i=sectors*CD_FRAMESIZE_RAW;i>1;i-=2)
+	  if(buffer[i-1]!='\177' || buffer[i-2]!='\177')
+	    break;
+
+	i/=CD_FRAMESIZE_RAW;
+	if(i!=sectors){
+	  if(d->report_all){
+	    char b[256];
+	    sprintf(b,"scsi_read underrun: pos=%ld len=%ld read=%ld retry=%d\n",
+		    begin,sectors,i,retry_count);
+	    
+	    cdmessage(d,b);
+	  }
+	  reset_scsi(d);
+	}
+	
+	if(i>0)return(i);
+       
+      }else
+	break;
+    }
+    
+    retry_count++;
+    if(sectors==1 && retry_count>MAX_RETRIES){
+      cderror(d,"007: Unknown, unrecoverable error reading data\n");
+      return(-7);
+    }
+    if(sectors>1)sectors=sectors/2;
+    d->enable_cdda(d,0);
+    d->enable_cdda(d,1);
+
+  }
+  return(sectors);
+}
+
+long scsi_read_28 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_28));
+}
+
+long scsi_read_A8 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_A8));
+}
+
+long scsi_read_D4_10 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_D4_10));
+}
+
+long scsi_read_D4_12 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_D4_12));
+}
+
+long scsi_read_D5 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_D5));
+}
+
+long scsi_read_D8 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_D8));
+}
+
+long scsi_read_mmc (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_mmc));
+}
+
+long scsi_read_mmc2 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_mmc2));
+}
+
+long scsi_read_mmc3 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_mmc3));
+}
+
+long scsi_read_mmcB (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_mmcB));
+}
+
+long scsi_read_mmc2B (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_mmc2B));
+}
+
+long scsi_read_mmc3B (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_mmc3B));
+}
+
+long scsi_read_msf (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_msf));
+}
+
+long scsi_read_msf2 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_msf2));
+}
+
+long scsi_read_msf3 (cdrom_drive *d, void *p, long begin, 
+			       long sectors){
+  return(scsi_read_map(d,p,begin,sectors,i_read_msf3));
+}
+
+
+/* Some drives, given an audio read command, return only 2048 bytes
+   of data as opposed to 2352 bytes.  Look for bytess at the end of the
+   single sector verification read */
+
+static int count_2352_bytes(cdrom_drive *d){
+  long i;
+  for(i=2351;i>=0;i--)
+    if(d->private_data->sg_buffer[i]!=(unsigned char)'\177')
+      return(((i+3)>>2)<<2);
+
+  return(0);
+}
+
+static int verify_nonzero(cdrom_drive *d){
+  long i,flag=0;
+  for(i=0;i<2352;i++)
+    if(d->private_data->sg_buffer[i]!=0){
+      flag=1;
+      break;
+    }
+  
+  return(flag);
+}
+
+/* So many different read commands, densities, features...
+   Verify that our selected 'read' command actually reads 
+   nonzero data, else search through other possibilities */
+
+static int verify_read_command(cdrom_drive *d){
+  int i,j,k;
+  int audioflag=0;
+
+  int  (*enablecommand)  (struct cdrom_drive *d, int speed);
+  long (*readcommand)   (struct cdrom_drive *d, void *p, long begin, 
+		       long sectors);
+  unsigned char density;
+  
+  int16_t *buff=malloc(CD_FRAMESIZE_RAW);
+
+  cdmessage(d,"Verifying CDDA command set...\n");
+
+  /* try the expected command set; grab the center of each track, look
+     for data */
+
+  if(d->enable_cdda(d,1)==0){
+    
+    for(i=1;i<=d->tracks;i++){
+      if(cdda_track_audiop(d,i)==1){
+	long firstsector=cdda_track_firstsector(d,i);
+	long lastsector=cdda_track_lastsector(d,i);
+	long sector=(firstsector+lastsector)>>1;
+	audioflag=1;
+
+	if(d->read_audio(d,buff,sector,1)>0){
+	  if(count_2352_bytes(d)==2352){
+	    cdmessage(d,"\tExpected command set reads OK.\n");
+	    d->enable_cdda(d,0);
+	    free(buff);
+	    return(0);
+	  }
+	}
+      }
+    }
+    
+    d->enable_cdda(d,0);
+  }
+  if(!audioflag){
+    cdmessage(d,"\tCould not find any audio tracks on this disk.\n");
+    return(-403);
+  }
+
+  {
+    char *es="",*rs="";
+    d->bigendianp=-1;
+    density=d->density;
+    readcommand=d->read_audio;
+    enablecommand=d->enable_cdda;
+
+
+    /* No nonzeroes?  D'oh.  Exhaustive search */
+    cdmessage(d,"\tExpected command set FAILED!\n"
+	      "\tPerforming full probe for CDDA command set...\n");
+    
+    /* loops:  
+       density/enable no,  0x0/org,  0x04/org, 0x82/org
+       read command read_10 read_12 read_nec read_sony read_mmc read_mmc2 */
+
+    /* NEC test must come before sony; the nec drive expects d8 to be
+       10 bytes, and a 12 byte verson (Sony) crashes the drive */
+
+    for(j=0;j<15;j++){
+      int densitypossible=1;
+
+      switch(j){
+      case 0:
+	d->read_audio=scsi_read_28;
+	rs="28 0x,00";
+	break;
+      case 1:
+	d->read_audio=scsi_read_A8;
+	rs="a8 0x,00";
+	break;
+
+      case 2:
+	d->read_audio=scsi_read_mmcB;
+	rs="be 02,10";
+	densitypossible=0;
+	break;
+      case 3:
+	d->read_audio=scsi_read_mmc2B;
+	rs="be 02,f8";
+	densitypossible=0;
+	break;
+      case 4:
+	d->read_audio=scsi_read_mmc3B;
+	rs="be 06,f8";
+	densitypossible=0;
+	break;
+
+      case 5:
+	d->read_audio=scsi_read_mmc;
+	rs="be 00,10";
+	densitypossible=0;
+	break;
+      case 6:
+	d->read_audio=scsi_read_mmc2;
+	rs="be 00,f8";
+	densitypossible=0;
+	break;
+      case 7:
+	d->read_audio=scsi_read_mmc3;
+	rs="be 04,f8";
+	densitypossible=0;
+	break;
+
+      case 8:
+	d->read_audio=scsi_read_msf;
+	rs="b9 00,10";
+	densitypossible=0;
+	break;
+      case 9:
+	d->read_audio=scsi_read_msf2;
+	rs="b9 00,f8";
+	densitypossible=0;
+	break;
+      case 10:
+	d->read_audio=scsi_read_msf3;
+	rs="b9 04,f8";
+	densitypossible=0;
+	break;
+
+      case 11:
+	d->read_audio=scsi_read_D4_10;
+	rs="d4(10)0x";
+	break;
+      case 12:
+	d->read_audio=scsi_read_D4_12;
+	rs="d4(12)0x";
+	break;
+      case 13:
+	d->read_audio=scsi_read_D5;
+	rs="d5 0x,00";
+	break;
+      case 14:
+	d->read_audio=scsi_read_D8;
+	rs="d8 0x,00";
+	break;
+      }
+      
+      for(i=0;i<5;i++){
+	switch(i){
+	case 0:
+	  d->density=0;
+	  d->enable_cdda=Dummy;
+	  es="none    ";
+	  if(!densitypossible)i=5; /* short circuit MMC style commands */
+	  break;
+	case 1:
+	  d->density=0;
+	  d->enable_cdda=scsi_enable_cdda;
+	  es="yes/0x00";
+	  break;
+	case 2:
+	  d->density=0x04;
+	  d->enable_cdda=scsi_enable_cdda;
+	  es="yes/0x04";
+	  break;
+	case 3:
+	  d->density=0x82;
+	  d->enable_cdda=scsi_enable_cdda;
+	  es="yes/0x82";
+	  break;
+	case 4:
+	  d->density=0x81;
+	  d->enable_cdda=scsi_enable_cdda;
+	  es="yes/0x81";
+	  break;
+	}
+
+	cdmessage(d,"\ttest -> density: [");
+	cdmessage(d,es);
+	cdmessage(d,"]  command: [");
+	cdmessage(d,rs);
+	cdmessage(d,"]\n");
+
+	{
+	  int densityflag=0;
+	  int rejectflag=0;
+	  int zeroflag=0;
+	  int lengthflag=0;
+
+	  if(d->enable_cdda(d,1)==0){
+	    for(k=1;k<=d->tracks;k++){
+	      if(cdda_track_audiop(d,k)==1){
+		long firstsector=cdda_track_firstsector(d,k);
+		long lastsector=cdda_track_lastsector(d,k);
+		long sector=(firstsector+lastsector)>>1;
+		
+		if(d->read_audio(d,buff,sector,1)>0){
+		  if((lengthflag=count_2352_bytes(d))==2352){
+		    if(verify_nonzero(d)){
+		      cdmessage(d,"\t\tCommand set FOUND!\n");
+		      
+		      free(buff);
+		      d->enable_cdda(d,0);
+		      return(0);
+		    }else{
+		      zeroflag++;
+		    }
+		  }
+		}else{
+		  rejectflag++;
+		  break;
+		}
+	      }
+	    }
+	    d->enable_cdda(d,0);
+	  }else{
+	    densityflag++;
+	  }
+	  
+	  if(densityflag)
+	    cdmessage(d,"\t\tDrive rejected density set\n");
+	  if(rejectflag){
+	    char buffer[256];
+	    sprintf(buffer,"\t\tDrive rejected read command packet(s)\n");
+	    cdmessage(d,buffer);
+	  }
+	  if(lengthflag>0 && lengthflag<2352){
+	    char buffer[256];
+	    sprintf(buffer,"\t\tDrive returned at least one packet, but with\n"
+		        "\t\tincorrect size (%d)\n",lengthflag);
+	    cdmessage(d,buffer);
+	  }
+	  if(zeroflag){
+	    char buffer[256];
+	    sprintf(buffer,"\t\tDrive returned %d packet(s), but contents\n"
+		        "\t\twere entirely zero\n",zeroflag);
+	    cdmessage(d,buffer);
+	  }
+	}
+      }
+    }
+
+    /* D'oh. */
+    d->density=density;
+    d->read_audio=readcommand;
+    d->enable_cdda=enablecommand;
+
+    cdmessage(d,"\tUnable to find any suitable command set from probe;\n"
+	      "\tdrive probably not CDDA capable.\n");
+
+    cderror(d,"006: Could not read any data from drive\n");
+
+  }
+  free(buff);
+  return(-6);
+}
+
+static void check_cache(cdrom_drive *d){
+  long i;
+
+  if(!(d->read_audio==scsi_read_mmc ||
+     d->read_audio==scsi_read_mmc2 ||
+     d->read_audio==scsi_read_mmc3 ||
+     d->read_audio==scsi_read_mmcB ||
+     d->read_audio==scsi_read_mmc2B ||
+       d->read_audio==scsi_read_mmc3B)){
+
+    cdmessage(d,"This command set may use a Force Unit Access bit.");
+    cdmessage(d,"\nChecking drive for FUA bit support...\n");
+    
+    d->enable_cdda(d,1);
+    d->fua=1;
+    
+    for(i=1;i<=d->tracks;i++){
+      if(cdda_track_audiop(d,i)==1){
+	long firstsector=cdda_track_firstsector(d,i);
+	long lastsector=cdda_track_lastsector(d,i);
+	long sector=(firstsector+lastsector)>>1;
+	
+	if(d->read_audio(d,NULL,sector,1)>0){
+	  cdmessage(d,"\tDrive accepted FUA bit.\n");
+	  d->enable_cdda(d,0);
+	  return;
+	}
+      }
+    }
+    
+    d->fua=0;
+    cdmessage(d,"\tDrive rejected FUA bit.\n");
+
+    /* we only use the FUA bit as a possible extra layer of
+       redundancy; too many drives accept it, but still don't force
+       unit access. Still use the old cachebusting algo. */
+    return;
+  }
+}
+
+static int check_atapi(cdrom_drive *d){
+  int atapiret=-1;
+  int fd = d->cdda_fd; /* check the device we'll actually be using to read */
+			  
+  cdmessage(d,"\nChecking for SCSI emulation...\n");
+
+  if (ioctl(fd,SG_EMULATED_HOST,&atapiret)){
+    cderror(d,"\tSG_EMULATED_HOST ioctl() failed!\n");
+    return(-1);
+  } else {
+    if(atapiret==1){
+      if(d->interface == SGIO_SCSI){
+	cdmessage(d,"\tDrive is ATAPI (using SG_IO host adaptor emulation)\n");
+      }else if(d->interface == SGIO_SCSI_BUGGY1){
+	cdmessage(d,"\tDrive is ATAPI (using SG_IO host adaptor emulation with workarounds)\n");
+      }else{
+	cdmessage(d,"\tDrive is ATAPI (using SCSI host adaptor emulation)\n");
+	/* Disable kernel SCSI command translation layer for access through sg */
+	if (ioctl(fd,SG_SET_TRANSFORM,0))
+	  cderror(d,"\tCouldn't disable kernel command translation layer\n");
+      }
+      d->is_atapi=1;
+    }else{
+      cdmessage(d,"\tDrive is SCSI\n");
+      d->is_atapi=0;
+    }
+
+    return(d->is_atapi);
+  }
+}  
+
+static int check_mmc(cdrom_drive *d){
+  unsigned char *b;
+  cdmessage(d,"\nChecking for MMC style command set...\n");
+
+  d->is_mmc=0;
+  if(mode_sense(d,22,0x2A)==0){
+  
+    b=d->private_data->sg_buffer;
+    b+=b[3]+4;
+    
+    if((b[0]&0x3F)==0x2A){
+      /* MMC style drive! */
+      d->is_mmc=1;
+      
+      if(b[1]>=4){
+	if(b[5]&0x1){
+	  cdmessage(d,"\tDrive is MMC style\n");
+	  return(1);
+	}else{
+	  cdmessage(d,"\tDrive is MMC, but reports CDDA incapable.\n");
+	  cdmessage(d,"\tIt will likely not be able to read audio data.\n");
+	  return(1);
+	}
+      }
+    }
+  }
+  
+  cdmessage(d,"\tDrive does not have MMC CDDA support\n");
+  return(0);
+}
+
+static void check_exceptions(cdrom_drive *d,exception *list){
+
+  int i=0;
+  while(list[i].model){
+    if(!strncmp(list[i].model,d->drive_model,strlen(list[i].model))){
+      if(list[i].density)d->density=list[i].density;
+      if(list[i].enable)d->enable_cdda=list[i].enable;
+      if(list[i].read)d->read_audio=list[i].read;
+      if(list[i].bigendianp!=-1)d->bigendianp=list[i].bigendianp;
+      return;
+    }
+    i++;
+  }
+}
+
+/* request vendor brand and model */
+unsigned char *scsi_inquiry(cdrom_drive *d){
+  unsigned char sense[SG_MAX_SENSE];
+  unsigned char cmd[6]={ 0x12,0,0,0,56,0 };
+  
+  if(handle_scsi_cmd(d,cmd,6, 0, 56,'\377',1,sense)) {
+    cderror(d,"008: Unable to identify CDROM model\n");
+    return(NULL);
+  }
+  return (d->private_data->sg_buffer);
+}
+
+int scsi_init_drive(cdrom_drive *d){
+  int ret;
+
+  check_atapi(d);
+  check_mmc(d);
+
+  /* generic Sony type defaults; specialize from here */
+  d->density = 0x0;
+  d->enable_cdda = Dummy;
+  d->read_audio = scsi_read_D8;
+  d->fua=0x0;
+  if(d->is_atapi)d->lun=0; /* it should already be; just to make sure */
+      
+  if(d->is_mmc){
+
+    d->read_audio = scsi_read_mmc2B;
+    d->bigendianp=0;
+
+    check_exceptions(d,mmc_list);
+
+  }else{
+    
+    if(d->is_atapi){
+      /* Not MMC maybe still uses 0xbe */
+
+      d->read_audio = scsi_read_mmc2B;
+      d->bigendianp=0;
+
+      check_exceptions(d,atapi_list);
+
+    }else{
+
+      check_exceptions(d,scsi_list);
+
+    }
+  }
+
+  if(!d->is_atapi)set_sectorsize(d,2048); /* we really do want the
+					     sector size at 2048 to begin.*/
+  d->enable_cdda(d,0);
+
+  d->read_toc = (!memcmp(d->drive_model, "IMS", 3) && !d->is_atapi) ? scsi_read_toc2 : 
+    scsi_read_toc;
+  d->set_speed = scsi_set_speed;
+
+  if(!d->is_atapi){
+    unsigned sector_size= get_orig_sectorsize(d);
+    
+    if(sector_size<2048 && set_sectorsize(d,2048))
+      d->adjust_ssize = 2048 / sector_size;
+    else
+      d->adjust_ssize = 1;
+  }else
+    d->adjust_ssize = 1;
+  
+  d->tracks=d->read_toc(d);
+  if(d->tracks<1)
+    return(d->tracks);
+
+  tweak_SG_buffer(d);
+  d->opened=1;
+
+  if((ret=verify_read_command(d)))return(ret);
+  check_cache(d);
+
+  d->error_retry=1;
+  d->private_data->sg_hd=realloc(d->private_data->sg_hd,d->nsectors*CD_FRAMESIZE_RAW + SG_OFF + 128);
+  d->private_data->sg_buffer=((unsigned char *)d->private_data->sg_hd)+SG_OFF;
+  d->report_all=1;
+  return(0);
+}
+
Index: interface/test_interface.c
===================================================================
--- interface/test_interface.c	(nonexistent)
+++ interface/test_interface.c	(revision 5)
@@ -0,0 +1,224 @@
+/******************************************************************
+ * CopyPolicy: GNU Lessger General Public License 2.1 applies
+ * Copyright (C) Monty xiphmont@mit.edu
+ *
+ * Fake interface backend for testing paranoia layer
+ *
+ ******************************************************************/
+
+#ifdef CDDA_TEST
+#include "low_interface.h"
+#include "utils.h"
+
+/* Build which test model? */
+#define  CDDA_TEST_OK
+#undef  CDDA_TEST_JITTER_SMALL
+#undef  CDDA_TEST_JITTER_LARGE
+#undef  CDDA_TEST_JITTER_MASSIVE
+#undef  CDDA_TEST_FRAG_SMALL
+#undef  CDDA_TEST_FRAG_LARGE
+#undef  CDDA_TEST_FRAG_MASSIVE
+#undef  CDDA_TEST_BOGUS_BYTES
+#undef  CDDA_TEST_DROPDUPE_BYTES
+#undef  CDDA_TEST_SCRATCH
+#undef  CDDA_TEST_UNDERRUN
+
+#undef  CDDA_TEST_ALLJITTER
+#undef  CDDA_TEST_SOMEJITTER
+#undef  CDDA_TEST_SEEKJITTER
+
+static int test_readtoc (cdrom_drive *d){
+  int tracks=0;
+  long bytes;
+  long sectors;
+
+  /* only one track, as many sectors as the file */
+
+  bytes=lseek(d->cdda_fd,0,SEEK_END);
+  lseek(d->cdda_fd,0,SEEK_SET);
+  sectors=bytes/CD_FRAMESIZE_RAW;
+
+  d->disc_toc[0].bFlags = 0;
+  d->disc_toc[0].bTrack = 1;
+  d->disc_toc[0].dwStartSector = 37;
+
+  d->disc_toc[1].bFlags = 0x4;
+  d->disc_toc[1].bTrack = CDROM_LEADOUT;
+  d->disc_toc[1].dwStartSector = sectors+37;
+
+  tracks=2;
+  d->cd_extra=0;
+  return(--tracks);  /* without lead-out */
+}
+
+/* we emulate jitter, scratches, atomic jitter and bogus bytes on
+   boundaries, etc */
+
+static long test_read(cdrom_drive *d, void *p, long begin, long sectors){
+  int jitter_flag=0;
+  int los_flag=0;
+  static int jitter=0;
+  int bytes_so_far=0;
+  long bytestotal;
+  static FILE *fd=NULL;
+  static long lastread=0;
+
+  if(!fd)fd=fdopen(d->cdda_fd,"r");
+
+  if(begin<lastread)
+    d->private_data->last_milliseconds=20;
+  else
+    d->private_data->last_milliseconds=sectors;
+
+#ifdef CDDA_TEST_UNDERRUN
+  sectors-=1;
+#endif
+
+#ifdef CDDA_TEST_SEEKJITTER
+  if(lastread!=begin)jitter_flag=1;
+#else
+#ifdef CDDA_TEST_ALLJITTER
+  jitter_flag=1;
+#else
+#ifdef CDDA_TEST_SOMEJITTER
+  jitter_flag=(drand48()>.9?1:0);
+  los_flag=(drand48()>.9?1:0);
+#else
+  los_flag=1;
+#endif
+#endif
+#endif
+
+  lastread=begin+sectors;
+  bytestotal=sectors*CD_FRAMESIZE_RAW;
+
+  begin*=CD_FRAMESIZE_RAW;
+
+  while(bytes_so_far<bytestotal){
+    int inner_bytes=bytestotal-bytes_so_far;
+    char *inner_buf=(p ? p+bytes_so_far : NULL);
+    long seeki;
+    long rbytes;
+    long this_bytes=inner_bytes;
+
+#ifdef CDDA_TEST_OK
+    
+#else
+#ifdef CDDA_TEST_JITTER_SMALL
+    if(jitter_flag)jitter=4*(int)((drand48()-.5)*CD_FRAMESIZE_RAW/8);
+
+#else
+#ifdef CDDA_TEST_JITTER_LARGE
+    if(jitter_flag)jitter=32*(int)((drand48()-.5)*CD_FRAMESIZE_RAW/8);
+
+#else
+#ifdef CDDA_TEST_JITTER_MASSIVE
+    if(jitter_flag)jitter=128*(int)((drand48()-.5)*CD_FRAMESIZE_RAW/8);
+
+#else
+#ifdef CDDA_TEST_FRAG_SMALL
+    if(los_flag)this_bytes=256*(int)(drand48()*CD_FRAMESIZE_RAW/8);
+    if(jitter_flag)jitter=4*(int)((drand48()-.5)*CD_FRAMESIZE_RAW/8);
+
+#else
+#ifdef CDDA_TEST_FRAG_LARGE
+    if(los_flag)this_bytes=16*(int)(drand48()*CD_FRAMESIZE_RAW/8);
+    if(jitter_flag)jitter=4*(int)((drand48()-.5)*CD_FRAMESIZE_RAW/8);
+
+#else
+#ifdef CDDA_TEST_FRAG_MASSIVE
+    if(los_flag)this_bytes=8*(int)(drand48()*CD_FRAMESIZE_RAW/8);
+    if(jitter_flag)jitter=32*(int)((drand48()-.5)*CD_FRAMESIZE_RAW/8);
+
+#else
+#ifdef CDDA_TEST_DROPDUPE_BYTES
+    if(los_flag)this_bytes=CD_FRAMESIZE_RAW;
+    if(jitter_flag)
+      if (drand48()>.8)
+	this_jitter=32;
+      else
+	this_jitter=0;
+
+#endif
+#endif
+#endif
+#endif
+#endif
+#endif
+#endif
+#endif
+
+
+    if(this_bytes>inner_bytes)this_bytes=inner_bytes;
+    if(begin+jitter+bytes_so_far<0)jitter=0;    
+    seeki=begin+bytes_so_far+jitter;
+
+    if(fseek(fd,seeki,SEEK_SET)<0){
+      return(0);
+    }
+    if(!inner_buf){
+      char *temp = malloc(this_bytes);
+      rbytes=fread(temp,1,this_bytes,fd);
+      free(temp);
+    }else
+      rbytes=fread(inner_buf,1,this_bytes,fd);
+
+
+    bytes_so_far+=rbytes;
+    if(rbytes==0)break;
+
+#ifdef CDDA_TEST_SEEKJITTER
+    jitter_flag=0;
+    los_flag=0;
+#else
+#ifdef CDDA_TEST_ALLJITTER
+    jitter_flag=1;
+    los_flag=0;
+#else
+#ifdef CDDA_TEST_SOMEJITTER
+    jitter_flag=(drand48()>.9?1:0);
+    los_flag=(drand48()>.9?1:0);
+#else
+    los_flag=1;
+#endif
+#endif
+#endif
+
+  }
+
+#ifdef CDDA_TEST_SCRATCH
+  {
+    long location=300*CD_FRAMESIZE_RAW+(drand48()*56)+512;
+
+    if(begin<=location && begin+bytestotal>location){
+      if(p)memset(p+location-begin,(int)(drand48()*256),1100);
+    }
+  }
+#endif
+
+  return(sectors);
+}
+
+/* hook */
+static int Dummy (cdrom_drive *d,int Switch){
+  return(0);
+}
+
+/* set function pointers to use the ioctl routines */
+int test_init_drive (cdrom_drive *d){
+
+  d->nsectors=13;
+  d->enable_cdda = Dummy;
+  d->read_audio = test_read;
+  d->read_toc = test_readtoc;
+  d->set_speed = Dummy;
+  d->tracks=d->read_toc(d);
+  if(d->tracks==-1)
+    return(d->tracks);
+  d->opened=1;
+  srand48(0);
+  return(0);
+}
+
+#endif
+
Index: interface
===================================================================
--- interface	(nonexistent)
+++ interface	(revision 5)

Property changes on: interface
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~
Index: .
===================================================================
--- .	(nonexistent)
+++ .	(revision 5)

Property changes on: .
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~