/*
 * This file has been modified for the cdrkit suite.
 *
 * The behaviour and appearence of the program code below can differ to a major
 * extent from the version distributed by the original author(s).
 *
 * For details, see Changelog file distributed with the cdrkit package. If you
 * received this file from another source then ask the distributing person for
 * a log of modifications.
 *
 */

/* @(#)interface.c	1.40 06/02/19 Copyright 1998-2002 Heiko Eissfeldt, Copyright 2006 J. Schilling */
/***
 * 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() (Linux only)
 *    : 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 "config.h"
#include <stdio.h>
#include <standard.h>
#include <stdxlib.h>
#include <unixstd.h>
#include <strdefs.h>
#include <errno.h>
#include <signal.h>
#include <fctldefs.h>
#include <assert.h>
#include <schily.h>
#include <device.h>

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

/* "major" and "minor" macros will be moved to sysmacros.h
 * silence the warning
 * added by cdrkit-1.1.11-sysmacros.patch */
#include <sys/sysmacros.h>


#include "mycdrom.h"
#include "lowlevel.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
# endif
#endif

#include <usal/scsitransp.h>

#include "mytype.h"
#include "byteorder.h"
#include "interface.h"
#include "icedax.h"
#include "semshm.h"
#include "setuid.h"
#include "ringbuff.h"
#include "toc.h"
#include "global.h"
#include "ioctl.h"
#include "exitcodes.h"
#include "scsi_cmds.h"

#include <utypes.h>
#include <wodim.h>
#include "scsi_scan.h"

unsigned interface;

int trackindex_disp = 0;

void	priv_init(void);
void	priv_on(void);
void	priv_off(void);

void		(*EnableCdda)(SCSI *, int Switch, unsigned uSectorsize);
unsigned (*doReadToc)(SCSI *usalp);
void	 	(*ReadTocText)(SCSI *usalp);
unsigned (*ReadLastAudio)(SCSI *usalp);
int      (*ReadCdRom)(SCSI *usalp, UINT4 *p, unsigned lSector, 
							 unsigned SectorBurstVal);
int      (*ReadCdRomData)(SCSI *usalp, unsigned char *p, unsigned lSector, 
								  unsigned SectorBurstVal);
int      (*ReadCdRomSub)(SCSI *usalp, UINT4 *p, unsigned lSector, 
								 unsigned SectorBurstVal);
subq_chnl *(*ReadSubChannels)(SCSI *usalp, unsigned lSector);
subq_chnl *(*ReadSubQ)(SCSI *usalp, unsigned char sq_format, 
							  unsigned char track);
void     (*SelectSpeed)(SCSI *usalp, unsigned speed);
int		(*Play_at)(SCSI *usalp, unsigned int from_sector, unsigned int sectors);
int		(*StopPlay)(SCSI *usalp);
void		(*trash_cache)(UINT4 *p, unsigned lSector, unsigned SectorBurstVal);

#if	0 //defined	USE_PARANOIA
long cdda_read(void *d, void *buffer, long beginsector, long sectors);

long cdda_read(void *d, void *buffer, long beginsector, long sectors)
{
	long ret = ReadCdRom(d, buffer, beginsector, sectors);
	return ret;
}
#endif

typedef struct string_len {
  char *str;
  unsigned int sl;
} mystring;

static mystring drv_is_not_mmc[] = {
	{"DEC     RRD47   (C) DEC ",24},
/*	{"SONY    CD-ROM CDU625    1.0",28}, */
	{NULL,0}	/* must be last entry */
};

static mystring drv_has_mmc_cdda[] = {
	{"HITACHI CDR-7930",16},
/*	{"TOSHIBA CD-ROM XM-5402TA3605",28}, */
	{NULL,0}	/* must be last entry */
};

static int	Is_a_Toshiba3401;

int Toshiba3401(void);

int Toshiba3401() 
{
  return Is_a_Toshiba3401;
}

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

static SCSI    *usalp;

SCSI *get_scsi_p(void);

SCSI *get_scsi_p()
{
    return usalp;
}

#if !defined(SIM_CD)

static void trash_cache_SCSI(UINT4 *p, unsigned lSector, 
									  unsigned SectorBurstVal);

static void trash_cache_SCSI(UINT4 *p, unsigned lSector, 
									  unsigned SectorBurstVal)
{
      /* trash the cache */
      ReadCdRom(get_scsi_p(), p, find_an_off_sector(lSector, SectorBurstVal), min(global.nsectors,6));
}



static void Check_interface_for_device(struct stat *statstruct, 
													char *pdev_name);
static int OpenCdRom(char *pdev_name);

static void SetupSCSI(void);

static void SetupSCSI()
{
    unsigned char *p;

    if (interface != GENERIC_SCSI) {
	/* unfortunately we have the wrong interface and are
	 * not able to change on the fly */
	fprintf(stderr, "The generic SCSI interface and devices are required\n");
	exit(SYNTAX_ERROR);
    }

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

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

#undef TYPE_ROM
#define TYPE_ROM 5
#undef TYPE_WORM
#define TYPE_WORM  4
    if (p == NULL) {
	fprintf(stderr, "Inquiry command failed. Aborting...\n");
	exit(DEVICE_ERROR);
    }

    if ((*p != TYPE_ROM && *p != TYPE_WORM)) {
	fprintf(stderr, "this is neither a scsi cdrom nor a worm device\n");
	exit(SYNTAX_ERROR);
    }

    if (global.quiet == 0) {
	fprintf(stderr,
		 "Type: %s, Vendor '%8.8s' Model '%16.16s' Revision '%4.4s' ",
		 *p == TYPE_ROM ? "ROM" : "WORM"
		 ,p+8
		 ,p+16
		 ,p+32);
    }
    /* generic Sony type defaults */
    density = 0x0;
    accepts_fua_bit = -1;
    EnableCdda = (void (*)(SCSI *, int, unsigned))Dummy;
    ReadCdRom = ReadCdda12;
    ReadCdRomSub = ReadCddaSubSony;
    ReadCdRomData = (int (*)(SCSI *, unsigned char *, unsigned, unsigned))ReadStandardData;
    ReadLastAudio = ReadFirstSessionTOCSony;
    SelectSpeed = SpeedSelectSCSISony;
    Play_at = Play_atSCSI;
    StopPlay = StopPlaySCSI;
    trash_cache = trash_cache_SCSI;
    ReadTocText = ReadTocTextSCSIMMC;
    doReadToc = ReadTocSCSI;
    ReadSubQ = ReadSubQSCSI;
    ReadSubChannels = NULL;

    /* check for brands and adjust special peculiaritites */

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

       global.in_lendian: 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.in_lendian.

     */

    {
      int mmc_code;

      usalp->silent ++;
      allow_atapi(usalp, 1);
      if (*p == TYPE_ROM) {
        mmc_code = heiko_mmc(usalp);
      } else {
        mmc_code = 0;
      }
      usalp->silent --;

      /* Exceptions for drives that report incorrect MMC capability */
      if (mmc_code != 0) {
	/* these drives are NOT capable of MMC commands */
        mystring *pp = drv_is_not_mmc;
	while (pp->str != NULL) {
	  if (!strncmp(pp->str, (char *)p+8,pp->sl)) {
	    mmc_code = 0;
	    break;
	  }
	  pp++;
        }
      }
      {
	/* these drives flag themselves as non-MMC, but offer CDDA reading
	   only with a MMC method. */
        mystring *pp = drv_has_mmc_cdda;
	while (pp->str != NULL) {
	  if (!strncmp(pp->str, (char *)p+8,pp->sl)) {
	    mmc_code = 1;
	    break;
	  }
	  pp++;
        }
      }

      switch (mmc_code) {
       case 2:      /* SCSI-3 cdrom drive with accurate audio stream */
	/* fall through */
       case 1:      /* SCSI-3 cdrom drive with no accurate audio stream */
	/* fall through */
lost_toshibas:
	 global.in_lendian = 1;
         if (mmc_code == 2)
	   global.overlap = 0;
	 else
           global.overlap = 1;
         ReadCdRom = ReadCddaFallbackMMC;
	 ReadCdRomSub = ReadCddaSubSony;
         ReadLastAudio = ReadFirstSessionTOCMMC;
         SelectSpeed = SpeedSelectSCSIMMC;
    	 ReadTocText = ReadTocTextSCSIMMC;
	 doReadToc = ReadTocMMC;
	 ReadSubChannels = ReadSubChannelsFallbackMMC;
	 if (!memcmp(p+8,"SONY    CD-RW  CRX100E  1.0", 27)) ReadTocText = NULL;
	 if (!global.quiet) fprintf(stderr, "MMC+CDDA\n");
       break;
       case -1: /* "MMC drive does not support cdda reading, sorry\n." */
	 doReadToc = ReadTocMMC;
	 if (!global.quiet) fprintf(stderr, "MMC-CDDA\n");
	 /* FALLTHROUGH */
       case 0:      /* non SCSI-3 cdrom drive */
	 if (!global.quiet) fprintf(stderr, "no MMC\n");
         ReadLastAudio = NULL;
    if (!memcmp(p+8,"TOSHIBA", 7) ||
        !memcmp(p+8,"IBM", 3) ||
        !memcmp(p+8,"DEC", 3)) {
	    /*
	     * older Toshiba ATAPI drives don't identify themselves as MMC.
	     * The last digit of the model number is '2' for ATAPI drives.
	     * These are treated as MMC.
	     */
	    if (!memcmp(p+15, " CD-ROM XM-", 11) && p[29] == '2') {
         	goto lost_toshibas;
	    }
	density = 0x82;
	EnableCdda = EnableCddaModeSelect;
	ReadSubChannels = ReadStandardSub;
 	ReadCdRom = ReadStandard;
        SelectSpeed = SpeedSelectSCSIToshiba;
        if (!memcmp(p+15, " CD-ROM XM-3401",15)) {
	   Is_a_Toshiba3401 = 1;
	}
	global.in_lendian = 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)) {
	EnableCdda = EnableCddaModeSelect;
	ReadCdRom = ReadStandard;
        SelectSpeed = SpeedSelectSCSIPhilipsCDD2600;

	/* treat all of these as bigendian */
	global.in_lendian = 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)) {
	EnableCdda = EnableCddaModeSelect;
        SelectSpeed = SpeedSelectSCSIYamaha;

	/* no overlap reading for cd-writers */
	global.overlap = 0;
	global.in_lendian = 1;
    } else if (!memcmp(p+8,"PLEXTOR",7)) {
	global.in_lendian = 1;
	global.overlap = 0;
        ReadLastAudio = ReadFirstSessionTOCSony;
    	ReadTocText = ReadTocTextSCSIMMC;
	doReadToc = ReadTocSony;
	ReadSubChannels = ReadSubChannelsSony;
    } else if (!memcmp(p+8,"SONY",4)) {
	global.in_lendian = 1;
        if (!memcmp(p+16, "CD-ROM CDU55E",13)) {
	   ReadCdRom = ReadCddaMMC12;
	}
        ReadLastAudio = ReadFirstSessionTOCSony;
    	ReadTocText = ReadTocTextSCSIMMC;
	doReadToc = ReadTocSony;
	ReadSubChannels = ReadSubChannelsSony;
    } else if (!memcmp(p+8,"NEC",3)) {
	ReadCdRom = ReadCdda10;
        ReadTocText = NULL;
        SelectSpeed = SpeedSelectSCSINEC;
	global.in_lendian = 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 */
    } else if (!memcmp(p+8,"MATSHITA",8)) {
	ReadCdRom = ReadCdda12Matsushita;
	global.in_lendian = 1;
    }
    } /* switch (get_mmc) */
    }


    /* look if caddy is loaded */
    if (interface == GENERIC_SCSI) {
	usalp->silent++;
	while (!wait_unit_ready(usalp, 60)) {
		fprintf(stderr,"load cdrom please and press enter");
		getchar();
	}
	usalp->silent--;
    }
}

/* Check to see if the device will support SCSI generic commands. A
 * better check than simply looking at the device name. Open the
 * device, issue an inquiry. If they both succeed, there's a good
 * chance that the device works... */
#if defined(__linux__)
static int check_linux_scsi_interface(char *pdev_name)
{
    SCSI *dev = NULL;
    unsigned char *p = NULL;
	char	errstr[80];
    
	dev = usal_open(pdev_name, errstr, sizeof(errstr), 0, 0);
    if (NULL == dev)
        return EINVAL;
    p = Inquiry(dev);
    if (p)
    {
        usal_close(dev);
        return 0;
    }
    usal_close(dev);
    return EINVAL;
}
#endif

/********************** 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(__linux__)
    int is_scsi = 1;
#endif
#ifndef STAT_MACROS_BROKEN
    if (!S_ISCHR(statstruct->st_mode) &&
	!S_ISBLK(statstruct->st_mode)) {
      fprintf(stderr, "%s is not a device\n",pdev_name);
      exit(SYNTAX_ERROR);
    }
#endif

/* Check what type of device we have */
#if defined (__linux__)
    if (check_linux_scsi_interface(pdev_name))
        is_scsi = 0;
    if (interface == GENERIC_SCSI && !is_scsi)
    {
        fprintf(stderr, "device %s does not support generic_scsi; falling back to cooked_ioctl instead\n", pdev_name);
        interface = COOKED_IOCTL;
    }
    if ((interface == COOKED_IOCTL) &&
        is_scsi &&
        (SCSI_GENERIC_MAJOR == major(statstruct->st_rdev)))
    {
        fprintf(stderr, "device %s is generic_scsi NOT cooked_ioctl\n", pdev_name);
        interface = GENERIC_SCSI;
    }
#else
    
#if defined (HAVE_ST_RDEV)
    switch (major(statstruct->st_rdev)) {
#if defined (__linux__)
    case SCSI_GENERIC_MAJOR:	/* generic */
#else
    default:			/* ??? what is the proper value here */
#endif
#ifndef STAT_MACROS_BROKEN
#if defined (__linux__)
       if (!S_ISCHR(statstruct->st_mode)) {
	 fprintf(stderr, "%s is not a char device\n",pdev_name);
	 exit(SYNTAX_ERROR);
       }

       if (interface != GENERIC_SCSI) {
	 fprintf(stderr, "wrong interface (cooked_ioctl) for this device (%s)\nset to generic_scsi\n", pdev_name);
	 interface = GENERIC_SCSI;
       }
#endif
#else
    default:			/* ??? what is the proper value here */
#endif
       break;

#if defined (__linux__) || defined (__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
#if defined (__linux__)
    case SCSI_CDROM_MAJOR:     /* scsi cd */
    default:			/* for example ATAPI cds */
#else
#if defined (__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
#if __FreeBSD_version >= 600021
    case 0:	/* majors abandoned */
	/* FALLTHROUGH */
#endif
#if __FreeBSD_version >= 501113
    case 4:	/* GEOM */
	/* FALLTHROUGH */
#endif
    case 117:	/* pre-GEOM atapi cd */
	if (!S_ISCHR(statstruct->st_mode)) {
	    fprintf(stderr, "%s is not a char device\n",pdev_name);
	    exit(SYNTAX_ERROR);
	}
	if (interface != COOKED_IOCTL) {
	    fprintf(stderr,
"cdrom device (%s) is not of type generic SCSI. \
Setting interface to cooked_ioctl.\n", pdev_name);
	    interface = COOKED_IOCTL;
	}
	break;
    case 19:     /* first atapi cd */
#endif
#endif
	if (!S_ISBLK(statstruct->st_mode)) {
	    fprintf(stderr, "%s is not a block device\n",pdev_name);
	    exit(SYNTAX_ERROR);
	}
#if defined (__linux__)
#if LINUX_VERSION_CODE >= 0x20600
	/* In Linux kernel 2.6 it is better to use the SCSI interface
	 * with the device.
	 */
	break;
#endif
#endif
	if (interface != COOKED_IOCTL) {
	    fprintf(stderr, 
"cdrom device (%s) is not of type generic SCSI. \
Setting interface to cooked_ioctl.\n", pdev_name);
	    interface = COOKED_IOCTL;
	}

	if (interface == COOKED_IOCTL) {
		fprintf(stderr, "\nW: The cooked_ioctl interface is functionally very limited!!\n");
#if	defined (__linux__)
		fprintf(stderr, "\nW: For good sampling quality simply use the generic SCSI interface!\n"
				"For example dev=ATA:1,0,0\n");
#endif
	}

	break;
#endif
    }
#endif
#endif 
    if (global.overlap >= global.nsectors)
      global.overlap = global.nsectors-1;
}

/* open the cdrom device */
static int OpenCdRom(char *pdev_name)
{
  int retval = 0;
  struct stat 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.
   */
#ifdef HAVE_IOCTL_INTERFACE
  struct stat statstruct;
  int have_named_device = 0;

	have_named_device = FALSE;
	if (pdev_name) {
		have_named_device = strchr(pdev_name, ':') == NULL
					&& memcmp(pdev_name, "/dev/", 5) == 0;
	}

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

  if (interface == GENERIC_SCSI) {
	char	errstr[80];

	priv_on();
	needroot(0);
	needgroup(0);
	/*
	 * Call usal_remote() to force loading the remote SCSI transport library
	 * code that is located in librusal instead of the dummy remote routines
	 * that are located inside libusal.
	 */
	usal_remote();
	if (pdev_name != NULL &&
	    ((strncmp(pdev_name, "HELP", 4) == 0) ||
	     (strncmp(pdev_name, "help", 4) == 0))) {
		usal_help(stderr);
		exit(NO_ERROR);
	}
	/* device name, debug, verboseopen */
	usalp = usal_open(pdev_name, errstr, sizeof(errstr), 0, 0);

	if (usalp == NULL) {
		int	err = geterrno();

		errmsgno(err, "%s%sCannot open SCSI driver.\n", errstr, errstr[0]?". ":"");
		errmsgno(EX_BAD, "For possible targets try 'wodim -scanbus'.%s\n",
					geteuid() ? " Make sure you are root.":"");
		priv_off();
        	dontneedgroup();
        	dontneedroot();
#if defined(sun) || defined(__sun)
		fprintf(stderr, "On SunOS/Solaris make sure you have Joerg Schillings usal SCSI driver installed.\n");
#endif
#if defined (__linux__)
	        fprintf(stderr, "Use the script scan_scsi.linux to find out more.\n");
#endif
	        fprintf(stderr, "Probably you did not define your SCSI device.\n");
	        fprintf(stderr, "Set the CDDA_DEVICE environment variable or use the -D option.\n");
	        fprintf(stderr, "You can also define the default device in the Makefile.\n");
		fprintf(stderr, "For possible transport specifiers try 'wodim dev=help'.\n");
	        exit(SYNTAX_ERROR);
	}
	usal_settimeout(usalp, 300);
	usal_settimeout(usalp, 60);
	usalp->silent = global.scsi_silent;
	usalp->verbose = global.scsi_verbose;

	if (global.nsectors > (unsigned) usal_bufsize(usalp, 3*1024*1024)/CD_FRAMESIZE_RAW)
		global.nsectors = usal_bufsize(usalp, 3*1024*1024)/CD_FRAMESIZE_RAW;
	if (global.overlap >= global.nsectors)
		global.overlap = global.nsectors-1;

	/*
	 * Newer versions of Linux seem to introduce an incompatible change
	 * and require root privileges or limit RLIMIT_MEMLOCK infinity
	 * in order to get a SCSI buffer in case we did call mlockall(MCL_FUTURE).
	 */
	init_scsibuf(usalp, global.nsectors*CD_FRAMESIZE_RAW);
	priv_off();
	dontneedgroup();
	dontneedroot();

	if (global.scandevs) {
		list_devices(usalp, stdout, 0);
		exit(0);
	}

	if (global.scanbus) {
		select_target(usalp, stdout);
		exit(0);
	}
  } else {
      needgroup(0);
      retval = open(pdev_name,O_RDONLY
#ifdef	linux
				| O_NONBLOCK
#endif
	);
      dontneedgroup();

      if (retval < 0) {
        fprintf(stderr, "while opening %s :", pdev_name);
        perror("");
        exit(DEVICEOPEN_ERROR);
      }

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

#if defined HAVE_IOCTL_INTERFACE
      /* 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(RACE_ERROR);
      }
#endif
	/*
	 * The structure looks like a desaster :-(
	 * We do this more than once as it is impossible to understand where
	 * the right place would be to do this....
	 */
	if (usalp != NULL) {
		usalp->verbose = global.scsi_verbose;
	}
  }
  return retval;
}
#endif /* SIM_CD */

/******************* Simulation interface *****************/
#if	defined SIM_CD
#include "toc.h"
static unsigned long sim_pos=0;

/* read 'SectorBurst' adjacent sectors of audio sectors 
 * to Buffer '*p' beginning at sector 'lSector'
 */
static int ReadCdRom_sim(SCSI *x, UINT4 *p, unsigned lSector, 
								 unsigned SectorBurstVal);
static int ReadCdRom_sim(SCSI *x, UINT4 *p, unsigned lSector, 
								 unsigned SectorBurstVal)
{
  unsigned int loop=0;
  Int16_t *q = (Int16_t *) p;
  int joffset = 0;

  if (lSector > g_toc[cdtracks].dwStartSector || lSector + SectorBurstVal > g_toc[cdtracks].dwStartSector + 1) {
    fprintf(stderr, "Read request out of bounds: %u - %u (%d - %d allowed)\n",
	lSector, lSector + SectorBurstVal, 0, g_toc[cdtracks].dwStartSector);
  }
#if 0
  /* jitter with a probability of jprob */
  if (random() <= jprob) {
    /* jitter up to jmax samples */
    joffset = random();
  }
#endif

#ifdef DEBUG_SHM
  fprintf(stderr, ", last_b = %p\n", *last_buffer);
#endif
  for (loop = lSector*CD_FRAMESAMPLES + joffset; 
       loop < (lSector+SectorBurstVal)*CD_FRAMESAMPLES + joffset; 
       loop++) {
    *q++ = loop;
    *q++ = ~loop;
  }
#ifdef DEBUG_SHM
  fprintf(stderr, "sim wrote from %p upto %p - 4 (%d), last_b = %p\n",
          p, q, SectorBurstVal*CD_FRAMESAMPLES, *last_buffer);
#endif
  sim_pos = (lSector+SectorBurstVal)*CD_FRAMESAMPLES + joffset; 
  return SectorBurstVal;
}

static int Play_at_sim(SCSI *x, unsigned int from_sector, unsigned int sectors);
static int Play_at_sim(SCSI *x, unsigned int from_sector, unsigned int sectors)
{
  sim_pos = from_sector*CD_FRAMESAMPLES; 
  return 0;
}

static unsigned sim_indices;


/* read the table of contents (toc) via the ioctl interface */
static unsigned ReadToc_sim(SCSI *x, TOC *toc);
static unsigned ReadToc_sim(SCSI *x, TOC *toc)
{
    unsigned int scenario;
    int scen[12][3] = { 
      {1,1,500}, 
      {1,2,500}, 
      {1,99,150*99}, 
      {2,1,500}, 
      {2,2,500}, 
      {2,99,150*99},
      {2,1,500}, 
      {5,2,500}, 
      {5,99,150*99}, 
      {99,1,1000}, 
      {99,2,1000}, 
      {99,99,150*99}, 
    };
    unsigned int i;
    unsigned trcks;
#if 0
    fprintf(stderr, "select one of the following TOCs\n"
	    "0 :  1 track  with  1 index\n"
	    "1 :  1 track  with  2 indices\n"
	    "2 :  1 track  with 99 indices\n"
	    "3 :  2 tracks with  1 index each\n"
	    "4 :  2 tracks with  2 indices each\n"
	    "5 :  2 tracks with 99 indices each\n"
	    "6 :  2 tracks (data and audio) with  1 index each\n"
	    "7 :  5 tracks with  2 indices each\n"
	    "8 :  5 tracks with 99 indices each\n"
	    "9 : 99 tracks with  1 index each\n"
	    "10: 99 tracks with  2 indices each\n"
	    "11: 99 tracks with 99 indices each\n"
	    );

    do {
      scanf("%u", &scenario);
    } while (scenario > sizeof(scen)/2/sizeof(int));
#else
    scenario = 6;
#endif
    /* build table of contents */

#if 0
    trcks = scen[scenario][0] + 1;
    sim_indices = scen[scenario][1];

    for (i = 0; i < trcks; i++) {
        toc[i].bFlags = (scenario == 6 && i == 0) ? 0x40 : 0xb1;
        toc[i].bTrack = i + 1;
        toc[i].dwStartSector = i * scen[scenario][2];
        toc[i].mins = (toc[i].dwStartSector+150) / (60*75);
        toc[i].secs = (toc[i].dwStartSector+150 / 75) % (60);
        toc[i].frms = (toc[i].dwStartSector+150) % (75);
    }
    toc[i].bTrack = 0xaa;
    toc[i].dwStartSector = i * scen[scenario][2];
    toc[i].mins = (toc[i].dwStartSector+150) / (60*75);
    toc[i].secs = (toc[i].dwStartSector+150 / 75) % (60);
    toc[i].frms = (toc[i].dwStartSector+150) % (75);
#else
    {
      int starts[15] = { 23625, 30115, 39050, 51777, 67507, 
		88612, 112962, 116840, 143387, 162662,
		173990, 186427, 188077, 209757, 257120};
      trcks = 14 + 1;
      sim_indices = 1;

      for (i = 0; i < trcks; i++) {
        toc[i].bFlags = 0x0;
        toc[i].bTrack = i + 1;
        toc[i].dwStartSector = starts[i];
        toc[i].mins = (starts[i]+150) / (60*75);
        toc[i].secs = (starts[i]+150 / 75) % (60);
        toc[i].frms = (starts[i]+150) % (75);
      }
      toc[i].bTrack = 0xaa;
      toc[i].dwStartSector = starts[i];
      toc[i].mins = (starts[i]) / (60*75);
      toc[i].secs = (starts[i] / 75) % (60);
      toc[i].frms = (starts[i]) % (75);
    }
#endif
    return --trcks;           /* without lead-out */
}


static subq_chnl *ReadSubQ_sim(SCSI *usalp, unsigned char sq_format, 
										 unsigned char track);
/* request sub-q-channel information. This function may cause confusion
 * for a drive, when called in the sampling process.
 */
static subq_chnl *ReadSubQ_sim(SCSI *usalp, unsigned char sq_format, 
										 unsigned char track)
{
    subq_chnl *SQp = (subq_chnl *) (SubQbuffer);
    subq_position *SQPp = (subq_position *) &SQp->data;
    unsigned long sim_pos1;
    unsigned long sim_pos2;

    if ( sq_format != GET_POSITIONDATA ) return NULL;  /* not supported by sim */

    /* simulate CDROMSUBCHNL ioctl */

    /* copy to SubQbuffer */
    SQp->audio_status 	= 0;
    SQp->format 	= 0xff;
    SQp->control_adr	= 0xff;
    sim_pos1 = sim_pos/CD_FRAMESAMPLES;
    sim_pos2 = sim_pos1 % 150;
    SQp->track 		= (sim_pos1 / 5000) + 1;
    SQp->index 		= ((sim_pos1 / 150) % sim_indices) + 1;
    sim_pos1 += 150;
    SQPp->abs_min 	= sim_pos1 / (75*60);
    SQPp->abs_sec 	= (sim_pos1 / 75) % 60;
    SQPp->abs_frame 	= sim_pos1 % 75;
    SQPp->trel_min 	= sim_pos2 / (75*60);
    SQPp->trel_sec 	= (sim_pos2 / 75) % 60;
    SQPp->trel_frame 	= sim_pos2 % 75;

    return (subq_chnl *)(SubQbuffer);
}

static void SelectSpeed_sim(SCSI *x, unsigned sp);
/* ARGSUSED */
static void SelectSpeed_sim(SCSI *x, unsigned sp)
{
}

static void trash_cache_sim(UINT4 *p, unsigned lSector, 
									 unsigned SectorBurstVal);

/* ARGSUSED */
static void trash_cache_sim(UINT4 *p, unsigned lSector, 
									 unsigned SectorBurstVal)
{
}

static void SetupSimCd(void);

static void SetupSimCd()
{
    EnableCdda = (void (*)(SCSI *, int, unsigned))Dummy;
    ReadCdRom = ReadCdRom_sim;
    ReadCdRomData = (int (*)(SCSI *, unsigned char *, unsigned, unsigned))ReadCdRom_sim;
    doReadToc = ReadToc_sim;
    ReadTocText = NULL;
    ReadSubQ = ReadSubQ_sim;
    ReadSubChannels = NULL;
    ReadLastAudio = NULL;
    SelectSpeed = SelectSpeed_sim;
    Play_at = Play_at_sim;
    StopPlay = (int (*)(SCSI *))Dummy;
    trash_cache = trash_cache_sim;
 
}

#endif /* def SIM_CD */

/* perform initialization depending on the interface used. */
void SetupInterface()
{
#if	defined SIM_CD
    fprintf( stderr, "SIMULATION MODE !!!!!!!!!!!\n");
#else
    /* ensure interface is setup correctly */
    global.cooked_fd = OpenCdRom ( global.dev_name );
#endif

    global.pagesize = getpagesize();

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

#if	defined SIM_CD
    usalp = malloc(sizeof(* usalp));
    if (usalp == NULL) {
	FatalError("No memory for SCSI structure.\n");
    }
    usalp->silent = 0;
    SetupSimCd();
#else
    /* if drive is of type scsi, get vendor name */
    if (interface == GENERIC_SCSI) {
        unsigned sector_size;

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

	/* get cache setting */

	/* set cache to zero */

    } else {
#if defined (HAVE_IOCTL_INTERFACE)
	usalp = malloc(sizeof(* usalp));
	if (usalp == NULL) {
		FatalError("No memory for SCSI structure.\n");
	}
	usalp->silent = 0;
	SetupCookedIoctl( global.dev_name );
#else
	FatalError("Sorry, there is no known method to access the device.\n");
#endif
    }
#endif	/* if def SIM_CD */
	/*
	 * The structure looks like a desaster :-(
	 * We do this more than once as it is impossible to understand where
	 * the right place would be to do this....
	 */
	if (usalp != NULL) {
		usalp->verbose = global.scsi_verbose;
	}
}

#ifdef	HAVE_PRIV_H
#include <priv.h>
#endif

void
priv_init()
{
#ifdef	HAVE_PRIV_SET
	/*
	 * Give up privs we do not need anymore.
	 * We no longer need:
	 *	file_dac_read,sys_devices,proc_priocntl,net_privaddr
	 */
	priv_set(PRIV_OFF, PRIV_EFFECTIVE,
		PRIV_FILE_DAC_READ, PRIV_PROC_PRIOCNTL,
		PRIV_NET_PRIVADDR, NULL);
	priv_set(PRIV_OFF, PRIV_INHERITABLE,
		PRIV_FILE_DAC_READ, PRIV_PROC_PRIOCNTL,
		PRIV_NET_PRIVADDR, PRIV_SYS_DEVICES, NULL);
#endif
}

void
priv_on()
{
#ifdef	HAVE_PRIV_SET
	/*
	 * Get back privs we may need now.
	 * We need:
	 *	file_dac_read,sys_devices,proc_priocntl,net_privaddr
	 */
	priv_set(PRIV_ON, PRIV_EFFECTIVE,
		PRIV_FILE_DAC_READ, PRIV_PROC_PRIOCNTL,
		PRIV_NET_PRIVADDR, NULL);
#endif
}

void
priv_off()
{
#ifdef	HAVE_PRIV_SET
	/*
	 * Give up privs we do not need anymore.
	 * We no longer need:
	 *	file_dac_read,sys_devices,proc_priocntl,net_privaddr
	 */
	priv_set(PRIV_OFF, PRIV_EFFECTIVE,
		PRIV_FILE_DAC_READ, PRIV_PROC_PRIOCNTL,
		PRIV_NET_PRIVADDR, NULL);
#endif
}
