/* @(#)interface.c	1.56 07/07/07 Copyright 1998-2002 Heiko Eissfeldt, Copyright 2006-2007 J. Schilling */
#ifndef lint
static char	sccsid[] =
"@(#)interface.c	1.56 07/07/07 Copyright 1998-2002 Heiko Eissfeldt, Copyright 2006-2007 J. Schilling";

#endif
/*
 * Copyright (C) 1994-1997 Heiko Eissfeldt heiko@colossus.escape.de
 * Copyright (c) 2006-2007 J. Schilling
 *
 * 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
 *
 */
/*
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * See the file CDDL.Schily.txt in this distribution for details.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file CDDL.Schily.txt from this distribution.
 */
#if 0
#define	SIM_CD
#endif

#include "config.h"
#include <stdio.h>
#include <schily/standard.h>
#include <schily/stdlib.h>
#include <schily/unistd.h>
#include <schily/string.h>
#include <schily/errno.h>
#include <signal.h>
#include <schily/fcntl.h>
#include <assert.h>
#include <schily/schily.h>
#include <schily/device.h>

#include <schily/ioctl.h>
#include <schily/stat.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 <scg/scsireg.h>
#include <scg/scsitransp.h>

#include "mytype.h"
#include "byteorder.h"
#include "interface.h"
#include "cdda2wav.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 <schily/utypes.h>
#include <cdrecord.h>
#include "scsi_scan.h"

unsigned interface;

int trackindex_disp = 0;

EXPORT	void	priv_init	__PR((void));
EXPORT	void	priv_on		__PR((void));
EXPORT	void	priv_off	__PR((void));

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

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

long
cdda_read(d, buffer, beginsector, sectors)
	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 __PR((void));

int
Toshiba3401() {
	return (Is_a_Toshiba3401);
}

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

static SCSI	*scgp;

SCSI	*get_scsi_p	__PR((void));

SCSI *
get_scsi_p()
{
	return (scgp);
}

#if !defined(SIM_CD)

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

static void
trash_cache_SCSI(p, lSector, SectorBurstVal)
	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 __PR((struct stat *statstruct,
						char *pdev_name));
LOCAL	int	OpenCdRom	__PR((char *pdev_name));
LOCAL	void	scg_openerr	__PR((char *errstr));
LOCAL	int	find_drive	__PR((SCSI *scgp, char *dev));

static void	SetupSCSI	__PR((void));

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

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

	/*
	 * do a test unit ready to 'init' the device.
	 */
	seterrno(0);
	TestForMedium(scgp);
	err = geterrno();
	if (err == EPERM || err == EACCES) {
		scg_openerr("");
		/* NOTREACHED */
	}

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

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

	if ((*p != TYPE_ROM && *p != TYPE_WORM)) {
		errmsgno(EX_BAD,
		"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 (*) __PR((SCSI *, int, unsigned)))Dummy;
	ReadCdRom = ReadCdda12;
	ReadCdRomSub = ReadCddaSubSony;
	ReadCdRomData = (int (*) __PR((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 = (subq_chnl * (*) __PR((SCSI *, unsigned)))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;

		scgp->silent++;
		allow_atapi(scgp, 1);
		if (*p == TYPE_ROM) {
			mmc_code = heiko_mmc(scgp);
		} else {
			mmc_code = 0;
		}
		scgp->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) == 0) {
					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) == 0) {
					mmc_code = 1;
					break;
				}
				pp++;
			}
		}

		switch (mmc_code) {
		case 2:	/* SCSI-3 cdrom drive with accurate audio stream */
			/* FALLTHROUGH */
		case 1:	/* SCSI-3 cdrom drive with no accurate audio stream */
			/* FALLTHROUGH */
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 = (void (*) __PR((SCSI *)))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 = (unsigned (*) __PR((SCSI *)))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 = (void (*) __PR((SCSI *, unsigned)))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 = (void (*) __PR((SCSI *)))NULL;
				SelectSpeed = SpeedSelectSCSINEC;
				global.in_lendian = 1;
				/*
				 * I assume all versions of the 502 require
				 * this?
				 * no overlap reading for NEC CD-ROM 502!
				 */
				if (!memcmp(p+29, "5022.0r", 3))
					global.overlap = 0;
			} 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) {
		scgp->silent++;
		while (!wait_unit_ready(scgp, 60)) {
			fprintf(stderr,
			"load cdrom please and press enter");
			getchar();
		}
		scgp->silent--;
	}
}

/********************** 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(statstruct, pdev_name)
	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)) {
		errmsgno(EX_BAD, "%s is not a device.\n",
			pdev_name);
		exit(SYNTAX_ERROR);
	}
#endif

#if defined(HAVE_ST_RDEV) && (HAVE_ST_RDEV == 1)
	switch (major(statstruct->st_rdev)) {
#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 defined(__linux__)
		if (!S_ISCHR(statstruct->st_mode)) {
			errmsgno(EX_BAD, "%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(__FreeBSD_kernel__) || defined(__DragonFly__)
#if defined(__linux__)
	case SCSI_CDROM_MAJOR:		/* scsi cd */
	default:			/* for example ATAPI cds */
#else
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
#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)) {
			errmsgno(EX_BAD, "%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)) {
			errmsgno(EX_BAD, "%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
	if (global.overlap >= global.nsectors)
		global.overlap = global.nsectors-1;
}

/*
 * open the cdrom device
 */
static int
OpenCdRom(pdev_name)
	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)) {
			errmsg("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 scg_remote() to force loading the remote SCSI transport
		 * library code that is located in librscg instead of the dummy
		 * remote routines that are located inside libscg.
		 */
		scg_remote();
		if (pdev_name != NULL &&
		    ((strncmp(pdev_name, "HELP", 4) == 0) ||
		    (strncmp(pdev_name, "help", 4) == 0))) {
			scg_help(stderr);
			exit(NO_ERROR);
		}

		/*
		 * device name, debug, verboseopen
		 */
		scgp = scg_open(pdev_name, errstr, sizeof (errstr), 0, 0);

		if (scgp == NULL) {
			scg_openerr(errstr);
			/* NOTREACHED */
		}
		scg_settimeout(scgp, 300);
		scg_settimeout(scgp, 60);
		scgp->silent = global.scsi_silent;
		scgp->verbose = global.scsi_verbose;

		global.bufsize = scg_bufsize(scgp, global.bufsize);
		if (global.nsectors >
		    (unsigned)global.bufsize/CD_FRAMESIZE_RAW) {
			global.nsectors = global.bufsize/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(scgp, global.bufsize);
		priv_off();
		dontneedgroup();
		dontneedroot();

		if (global.scanbus) {
			int	i = select_target(scgp, stdout);

			if (i < 0) {
				scg_openerr("");
				/* NOTREACHED */
			}
			exit(0);
		}
		if (scg_scsibus(scgp) < 0 &&
				scg_target(scgp) < 0 && scg_lun(scgp) < 0) {
			int	i = find_drive(scgp, pdev_name);

			if (i < 0) {
				scg_openerr("");
				/* NOTREACHED */
			}
		}
	} else {
		needgroup(0);
		retval = open(pdev_name,
#ifdef	linux
					O_NONBLOCK |
#endif
					O_RDONLY);
		dontneedgroup();

		if (retval < 0) {
			errmsg("Cannot open '%s'.\n", pdev_name);
			exit(DEVICEOPEN_ERROR);
		}

		/*
		 * Do final security checks here
		 */
		if (fstat(retval, &fstatstruct)) {
			errmsg("Could not fstat %s (fd %d).\n",
				pdev_name, retval);
			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)) {
			errmsgno(EX_BAD,
			"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 (scgp != NULL) {
			scgp->verbose = global.scsi_verbose;
		}
	}
	return (retval);
}

LOCAL void
scg_openerr(errstr)
	char	*errstr;
{
	int	err = geterrno();

	errmsgno(err, "%s%sCannot open or use SCSI driver.\n",
			errstr, errstr[0]?". ":"");
	errmsgno(EX_BAD,
	"For possible targets try 'cdda2wav -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 scg 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 'cdda2wav dev=help'.\n");
	exit(SYNTAX_ERROR);
}

LOCAL int
find_drive(scgp, dev)
	SCSI	*scgp;
	char	*dev;
{
	int	ntarget;

	error("No target specified, trying to find one...\n");
	ntarget = find_target(scgp, INQ_ROMD, -1);
	if (ntarget < 0)
		return (ntarget);
	if (ntarget == 1) {
		/*
		 * Simple case, exactly one CD-ROM found.
		 */
		find_target(scgp, INQ_ROMD, 1);
	} else if (ntarget <= 0 &&
			(ntarget = find_target(scgp, INQ_WORM, -1)) == 1) {
		/*
		 * Exactly one CD-ROM acting as WORM found.
		 */
		find_target(scgp, INQ_WORM, 1);
	} else if (ntarget <= 0) {
		/*
		 * No single CD-ROM or WORM found.
		 */
		errmsgno(EX_BAD, "No CD/DVD/BD-Recorder target found.\n");
		errmsgno(EX_BAD,
		"Your platform may not allow to scan for SCSI devices.\n");
		comerrno(EX_BAD,
		"Call 'cdda2wav dev=help' or ask your sysadmin for possible targets.\n");
	} else {
		errmsgno(EX_BAD, "Too many CD/DVD/BD-Recorder targets found.\n");
		select_target(scgp, stdout);
		comerrno(EX_BAD,
		"Select a target from the list above and use 'cdda2wav dev=%s%sb,t,l'.\n",
			dev?dev:"", dev?(dev[strlen(dev)-1] == ':'?"":":"):"");
	}
	error("Using dev=%s%s%d,%d,%d.\n",
			dev?dev:"", dev?(dev[strlen(dev)-1] == ':'?"":":"):"",
			scg_scsibus(scgp), scg_target(scgp), scg_lun(scgp));
	return (ntarget);
}


#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	__PR((SCSI *x, UINT4 *p, unsigned lSector,
						unsigned SectorBurstVal));
static int
ReadCdRom_sim(x, p, lSector, SectorBurstVal)
	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	__PR((SCSI *x, unsigned int from_sector,
							unsigned int sectors));
static int
Play_at_sim(x, from_sector, sectors)
	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	__PR((SCSI *x, TOC *toc));
static unsigned
ReadToc_sim(x, toc)
	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	__PR((SCSI *scgp,
						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(scgp, sq_format, track)
	SCSI		*scgp;
	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_frameb	= sim_pos2 % 75;

	return ((subq_chnl *)(SubQbuffer));
}

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

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

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

static void	SetupSimCd	__PR((void));

static void
SetupSimCd()
{
	EnableCdda = (void (*) __PR((SCSI *, int, unsigned)))Dummy;
	ReadCdRom = ReadCdRom_sim;
	ReadCdRomData = (int (*) __PR((SCSI *,
					unsigned char *,
					unsigned, unsigned)))ReadCdRom_sim;
	doReadToc = ReadToc_sim;
	ReadTocText = (void (*) __PR((SCSI *)))NULL;
	ReadSubQ = ReadSubQ_sim;
	ReadSubChannels = (subq_chnl * (*) __PR((SCSI *, unsigned)))NULL;
	ReadLastAudio = (unsigned (*) __PR((SCSI *)))NULL;
	SelectSpeed = SelectSpeed_sim;
	Play_at = Play_at_sim;
	StopPlay = (int (*) __PR((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

#ifdef  _SC_PAGESIZE
	global.pagesize = sysconf(_SC_PAGESIZE);
#else
	global.pagesize = getpagesize();
#endif

	/*
	 * 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);	 /* aassumes sufficient aligned addresses */
	if (!bufferTOC || !SubQbuffer || !cmd) {
		errmsg("Too low on memory. Giving up.\n");
		exit(NOMEM_ERROR);
	}

#if	defined SIM_CD
	scgp = malloc(sizeof (* scgp));
	if (scgp == NULL) {
		FatalError(geterrno(), "No memory for SCSI structure.\n");
	}
	scgp->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(scgp, &orgmode4, &orgmode10,
								&orgmode11);
		if (!SCSI_emulated_ATAPI_on(scgp)) {
			if (sector_size != 2048 &&
			    set_sectorsize(scgp, 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)
		scgp = malloc(sizeof (* scgp));
		if (scgp == NULL) {
			FatalError(geterrno(),
				"No memory for SCSI structure.\n");
		}
		scgp->silent = 0;
		SetupCookedIoctl(global.dev_name);
#else
		FatalError(EX_BAD,
		"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 (scgp != NULL) {
		scgp->verbose = global.scsi_verbose;
	}
}

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

EXPORT 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
}

EXPORT 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
}

EXPORT 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
}
