/***
 * CopyPolicy: GNU Public License 2 applies
 * Copyright (C) 1994-1997 Heiko Eissfeldt heiko@colossus.escape.de
 *
 * Interface module for cdrom drive access
 *
 * Two interfaces are possible.
 *
 * 1. using 'cooked' ioctls()
 *    : available for atapi, sbpcd and cdu31a drives only.
 *
 * 2. using the generic scsi device (for details see SCSI Prog. HOWTO).
 *    NOTE: a bug/misfeature in the kernel requires blocking signal
 *          SIGINT during SCSI command handling. Once this flaw has
 *          been removed, the sigprocmask SIG_BLOCK and SIG_UNBLOCK calls
 *          should removed, thus saving context switches.
 *
 * For testing purposes I have added a third simulation interface.
 *
 * Version 0.8: used experiences of Jochen Karrer.
 *              SparcLinux port fixes
 *              AlphaLinux port fixes
 *
 */
#if 0
#define SIM_CD
#endif

#include <stdio.h>
#include <stdlib.h>
#if defined (HAVE_UNISTD_H) && (HAVE_UNISTD_H == 1)
#include <sys/types.h>
#include <unistd.h>
#endif
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <assert.h>

#include <sys/ioctl.h>
#include <sys/stat.h>


#include "mycdrom.h"
#include "lowlevel.h"
#include "ringbuff.h"
/* some include file locations have changed with newer kernels */
#if defined (__linux__)
# if LINUX_VERSION_CODE > 0x10300 + 97
#  if LINUX_VERSION_CODE < 0x200ff
#   include <linux/sbpcd.h>
#   include <linux/ucdrom.h>
#  endif
#  if !defined(CDROM_SELECT_SPEED)
#   include <linux/ucdrom.h>
#  endif
#  include <scsi/scsi.h>
# else /* old stuff */
#  include <linux/../../drivers/scsi/sg.h>
#  include <linux/../../drivers/scsi/scsi.h>
# endif

/* Hack hack hack hack hack... makes the code clean, though. Zygo was here */
# ifndef SG_BIG_BUFF
#  define SG_BIG_BUFF 4096	/* FIXME: should be the size of page */
# endif
/* HACK! the kernel header files were misdesigned for
 *  user applications.
 * #include <linux/blkdev.h> did not work
 */
struct request {		/* this is and may not used */
  int dummy;
};
#endif

#include <scsitransp.h>

#include "byteorder.h"
#include "readall.h"
#include "interface.h"
#include "global.h"
#include "scsi_cmds.h"

sigset_t signalset;    /* to circumvent a kernel problem with the generic driver */
struct sigaction sigac;

TOC g_toc [MAXTRK]; /* 100 */

void     (*ReadCdRom) (unsigned char *p, long lSector, unsigned long SectorBurstVal );
void     (*SelectSpeed) ( unsigned speed );

/* buffer for cdrom audio data sector */
unsigned char *bufferCdda;

static int	Is_a_Toshiba3401;

int Toshiba3401( void) {
  return Is_a_Toshiba3401;
}

/* hook */
static void Dummy ( void )
{
}

static void SetupSCSI( void )
{
    unsigned char *p;

    /* do a test unit ready to 'init' the device. */
    TestForMedium();

    /* check for the correct type of unit. */
    p = Inquiry();

#undef TYPE_ROM
#define TYPE_ROM 5
#undef TYPE_WORM
#define TYPE_WORM  4
    if (p == NULL || (*p != TYPE_ROM && *p != TYPE_WORM)) {
	fprintf(stderr, "this is neither a scsi cdrom nor a worm device\n");
	exit(1);
    }

    /* generic Sony type defaults */
    density = 0x0;
    ReadCdRom = ReadCdda12;
    SelectSpeed = SpeedSelectSCSISony;

    /* check for brands and adjust special peculiaritites */

    /* If your drive is not treated correctly, you can adjust some things
       here:

       global.littleendian: should be to 1, if the CDROM drive or CD-Writer
		  delivers the samples in the native byteorder of the audio cd
		  (LSB first).
		  HP CD-Writers need it set to 0.
       NOTE: If you get correct wav files when using sox with the '-x' option,
             the endianess is wrong. You can use the -C option to specify
	     the value of global.littleendian.

     */

    {
      int mmc_code;

      allow_atapi(1);
      if (*p == TYPE_ROM) {
        mmc_code = is_mmc();
      } else {
        mmc_code = 0;
      }
      switch (mmc_code) {
       case 2:      /* SCSI-3 cdrom drive with accurate audio stream */
         global.overlap = 0;
         ReadCdRom = ReadCddaMMC12;
         SelectSpeed = SpeedSelectSCSIMMC;
       break;
       case 1:      /* SCSI-3 cdrom drive with no accurate audio stream */
         ReadCdRom = ReadCddaMMC12;
         SelectSpeed = SpeedSelectSCSIMMC;
         break;
       case -1: /* "MMC drive does not support cdda reading, sorry\n." */
	/* fall through */
       case 0:      /* non SCSI-3 cdrom drive */
    if (!memcmp(p+8,"TOSHIBA", 7) ||
        !memcmp(p+8,"IBM", 3) ||
        !memcmp(p+8,"DEC", 3)) {
	density = 0x82;
 	ReadCdRom = ReadStandard;
        SelectSpeed = SpeedSelectSCSIToshiba;
        if (!memcmp(p+16, "CD-ROM XM-3401",14)) {
	   Is_a_Toshiba3401 = 1;
	}
    } else if (!memcmp(p+8,"IMS",3) ||
               !memcmp(p+8,"KODAK",5) ||
               !memcmp(p+8,"RICOH",5) ||
               !memcmp(p+8,"HP",2) ||
               !memcmp(p+8,"PHILIPS",7) ||
               !memcmp(p+8,"PLASMON",7) ||
               !memcmp(p+8,"GRUNDIG CDR100IPW",17) ||
               !memcmp(p+8,"MITSUMI CD-R ",13)) {
	ReadCdRom = ReadStandard;
        SelectSpeed = SpeedSelectSCSIPhilipsCDD2600;

	/* treat all of these as bigendian */
	if ( global.littleendian == -1 )
		global.littleendian = 0;

	/* no overlap reading for cd-writers */
	global.overlap = 0;
    } else if (!memcmp(p+8,"NRC",3)) {
        SelectSpeed = NULL;
    } else if (!memcmp(p+8,"YAMAHA",6)) {
        SelectSpeed = SpeedSelectSCSIYamaha;

	/* no overlap reading for cd-writers */
	global.overlap = 0;
	if ( global.littleendian == -1 )
		global.littleendian = 1;
    } else if (!memcmp(p+8,"PLEXTOR",7) ||
               !memcmp(p+8,"SONY",4)) {
	if ( global.littleendian == -1 )
		global.littleendian = 1;
        if (!memcmp(p+16, "CD-ROM CDU55E",13)) {
	   ReadCdRom = ReadCddaMMC12;
	}
    } else if (!memcmp(p+8,"NEC",3)) {
	ReadCdRom = ReadCdda10;
        SelectSpeed = SpeedSelectSCSINEC;
	if ( global.littleendian == -1 )
		global.littleendian = 1;
        if (!memcmp(p+29,"5022.0r",3)) /* I assume all versions of the 502 require this? */
               global.overlap = 0;           /* no overlap reading for NEC CD-ROM 502 */
    }
    } /* switch (get_mmc) */
    }

    /* look if caddy is loaded */
    while (!TestForMedium()) {
	fprintf(stderr,"load cdrom please and press enter");
	getchar();
    }
}


/********************** General setup *******************************/

/* As the name implies, interfaces and devices are checked.  We also
   adjust nsectors, overlap, and interface for the first time here.
   Any unnecessary privileges (setuid, setgid) are also dropped here.
*/
static void Check_interface_for_device( struct stat *statstruct, char *pdev_name)
{

#if !defined (STAT_MACROS_BROKEN) || (STAT_MACROS_BROKEN != 1)
    if (!S_ISCHR(statstruct->st_mode) &&
	!S_ISBLK(statstruct->st_mode)) {
      fprintf(stderr, "%s is not a device\n",pdev_name);
      exit(1);
    }
#endif

#if defined (HAVE_ST_RDEV) && (HAVE_ST_RDEV == 1)
    switch ((int) (statstruct->st_rdev >> 8L)) {
#if defined (__linux__)
    case SCSI_GENERIC_MAJOR:	/* generic */
#else
    default:			/* ??? what is the proper value here */
#endif
#if !defined (STAT_MACROS_BROKEN) || (STAT_MACROS_BROKEN != 1)
       if (!S_ISCHR(statstruct->st_mode)) {
	 fprintf(stderr, "%s is not a char device\n",pdev_name);
	 exit(1);
       }
#endif
       if (global.nsectors > scsi_bufsize(100*1024*1024)/CD_FRAMESIZE_RAWER)
	       global.nsectors = scsi_bufsize(100*1024*1024)/CD_FRAMESIZE_RAWER;
       break;
#if defined (__linux__)
    case SCSI_CDROM_MAJOR:     /* scsi cd */
    default:
	{
	    fprintf(stderr, "%s is not a supported device\n",pdev_name);
	    exit(1);
	}
	break;
#endif
    }
#endif
    if (global.overlap >= global.nsectors)
      global.overlap = global.nsectors-1;
}

/* open the scsi device */
static int open_scsi(char *scsidev, int timeout, int be_verbose)
{
	int	x1, x2, x3;
	int	n = 0;

	if (timeout >= 0)
		deftimeout = timeout;

	if (scsidev != NULL)
		n = sscanf(scsidev, "%d,%d,%d", &x1, &x2, &x3);
	if (n == 3) {
		scsibus = x1;
		target = x2;
		lun = x3;
	} else if (n == 2) {
		scsibus = 0;
		target = x1;
		lun = x2;
	} else if (scsidev != NULL) {
#ifdef __linux__
		fprintf(stderr, "CHANGE!! The device has to be given in the form a,b,c instead of /dev/sg?\n");
		fprintf( stderr, "where a=SCSI-bus,  b=SCSI-TargetID and c= SCSI-lun.\n");
#endif
		fprintf( stderr, "WARNING: device not valid, trying to use default target...\n");
		scsibus = 0;
		target = 6;
		lun = 0;
		fprintf( stderr, "scsibus: %d target: %d lun: %d\n",
						scsibus, target, lun);
	} else {
		return 0;
        }
	if (be_verbose && scsidev != NULL) {
		fprintf( stderr, "scsidev: '%s',  ", scsidev);
		fprintf( stderr, "scsibus: %d target: %d lun: %d\n",
						scsibus, target, lun);
	}
	global.target = target;
	global.lun = lun;

	return scsi_open();
}


/* open the cdrom device */
static int OpenCdRom ( char *pdev_name )
{
  int retval;
  int have_named_device = 0;
  struct stat statstruct, fstatstruct;

  /*  The device (given by pdevname) can be:
      a. an SCSI device specified with a /dev/xxx name,
      b. an SCSI device specified with bus,target,lun numbers,
      c. a non-SCSI device such as ATAPI or proprietary CDROM devices.
   */

  if (memcmp(pdev_name, "/dev/", 5) != 0) {
      /* map bus, target id, lun to corresponding device */
  } else {
    have_named_device = 1;
  }

  if (have_named_device) {
    if (stat(pdev_name, &statstruct)) {
      fprintf(stderr, "cannot stat device %s\n", pdev_name);
      exit(1);
    } else {
      Check_interface_for_device( &statstruct, pdev_name );
    }
  }

  if (1) {
      retval = open_scsi(pdev_name, 100, 0);

      if (retval <= 0) {
        fprintf(stderr, "open(%s) in file %s, line %d: ",pdev_name, __FILE__, __LINE__);
        perror("");
#if defined (__linux__)
        fprintf(stderr, "On Linux make sure you have the generic SCSI driver installed.\n");
#endif
#if defined(sun) || defined(__sun) || defined(__sun__)
        fprintf(stderr, "On SunOS/Solaris make sure you have Joerg Schillings scg SCSI driver installed.\n");
#endif
        fprintf(stderr, "Probably you did not define your SCSI device.\n");
        fprintf(stderr, "Set the CDR_DEVICE environment variable or use the -D option.\n");
        fprintf(stderr, "You can also define the default device in the Makefile.\n");
        exit(1);
      }
       if (global.nsectors > scsi_bufsize(100*1024*1024)/CD_FRAMESIZE_RAWER)
	      global.nsectors = scsi_bufsize(100*1024*1024)/CD_FRAMESIZE_RAWER;
      if (global.overlap >= global.nsectors)
        global.overlap = global.nsectors-1;
  } else {
      retval = open(pdev_name,O_RDONLY);

      /* Do final security checks here */
      if (fstat(retval, &fstatstruct)) {
        fprintf(stderr, "Could not fstat %s (fd %d): ", pdev_name, retval);
        perror("");
        exit(1);
      }
      Check_interface_for_device( &fstatstruct, pdev_name );

      /* Watch for race conditions */
      if (have_named_device 
          && (fstatstruct.st_dev != statstruct.st_dev ||
              fstatstruct.st_ino != statstruct.st_ino)) {
         fprintf(stderr,"Race condition attempted in OpenCdRom.  Exiting now.\n");
         exit(1);
      }
  }
  return retval;
}

/* perform initialization depending on the interface used. */
void SetupInterface( void )
{
    /* build signal set to block for during generic scsi */
    sigemptyset (&signalset);
    sigaddset (&signalset, SIGINT);
    sigaddset (&signalset, SIGPIPE);
    sigac.sa_handler = exit;
    sigemptyset(&sigac.sa_mask);
    sigac.sa_flags = 0;
    sigaction( SIGINT, &sigac, NULL);
    sigaction( SIGQUIT, &sigac, NULL);
    sigaction( SIGTERM, &sigac, NULL);
    sigaction( SIGHUP, &sigac, NULL);
    sigaction( SIGPIPE, &sigac, NULL);
    sigaction( SIGTRAP, &sigac, NULL);
    sigaction( SIGIOT, &sigac, NULL);

    /* ensure interface is setup correctly */
    global.cooked_fd = OpenCdRom ( global.dev_name );

    /* Value of 'nsectors' must be defined here */
    assert(global.nsectors > 0);

fprintf(stderr, "nsectors = %d\n", global.nsectors);
    bufferCdda = malloc(entry_size);
    if ( !bufferCdda ) {
       fprintf( stderr, "Too low on memory (1). Giving up.\n");
       exit(2);
    }

    /* request one sector for table of contents */
    bufferTOC = malloc( CD_FRAMESIZE );      /* assumes sufficient aligned addresses */
    cmd = malloc( 18 );                      /* assumes sufficient aligned addresses */
    if ( !bufferTOC || !cmd ) {
       fprintf( stderr, "Too low on memory (2). Giving up.\n");
       exit(2);
    }

    adjust_ssize = 1;
    /* if drive is of type scsi, get vendor name */
    if (1) {
        unsigned sector_size;

	SetupSCSI();
        sector_size = get_orig_sectorsize(&orgmode4, &orgmode10, &orgmode11);
	if (!SCSI_emulated_ATAPI_on()) {
          if ( sector_size != 2048 && set_sectorsize(2048,-1) ) {
	    fprintf( stderr, "Could not change sector size from %d to 2048\n", sector_size );
	    adjust_ssize = 2048 / sector_size;
          }
        } else {
          sector_size = 2048;
          adjust_ssize = 1;
        }
    }
}
