/*
 * 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.
 *
 */

/* @(#)readcd.c	1.80 06/02/05 Copyright 1987, 1995-2006 J. Schilling */
/*
 *	Skeleton for the use of the usal genearal SCSI - driver
 *
 *	Copyright (c) 1987, 1995-2004 J. Schilling
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <mconfig.h>
#include <stdio.h>
#include <standard.h>
#include <unixstd.h>
#include <stdxlib.h>
#include <strdefs.h>
#include <fctldefs.h>
#include <timedefs.h>
#include <signal.h>
#include <schily.h>
#ifdef	HAVE_PRIV_H
#include <priv.h>
#endif

#ifdef	NEED_O_BINARY
#include <io.h>					/* for setmode() prototype */
#endif

#include <usal/usalcmd.h>
#include <usal/scsireg.h>
#include <usal/scsitransp.h>

#include "scsi_scan.h"
#include "scsimmc.h"
#define	qpto96	__nothing__
#include "wodim.h"
#include "defaults.h"
#undef	qpto96
#include "movesect.h"

#ifdef __DJGPP__
#define caddr_t char *
#endif

char	cdr_version[] = "2.01.01a05";

#if	defined(PROTOTYPES)
#define	UINT_C(a)	(a##u)
#define	ULONG_C(a)	(a##ul)
#define	USHORT_C(a)	(a##uh)
#define	CONCAT(a, b)	a##b
#else
#define	UINT_C(a)	((unsigned)(a))
#define	ULONG_C(a)	((unsigned long)(a))
#define	USHORT_C(a)	((unsigned short)(a))
/* CSTYLED */
#define	CONCAT(a, b)	a/**/b
#endif

extern	BOOL	getlong(char *, long *, long, long);
extern	BOOL	getint(char *, int *, int, int);

typedef struct {
	long	start;
	long	end;
	long	sptr;		/* sectors per transfer */
	BOOL	askrange;
	char	*name;
} parm_t;

typedef struct {
	int	errors;
	int	c2_errors;
	int	c2_maxerrs;
	int	c2_errsecs;
	int	c2_badsecs;
	int	secsize;
	BOOL	ismmc;
} rparm_t;

struct exargs {
	SCSI	*usalp;
	int	old_secsize;
	int	flags;
	int	exflags;
	char	oerr[3];
} exargs;

BOOL	cvt_cyls(void);
BOOL	cvt_bcyls(void);
void	print_defect_list(void);
static	void	usage(int ret);
static	void	intr(int sig);
static	void	exscsi(int excode, void *arg);
static	void	excdr(int excode, void *arg);
static	int	prstats(void);
static	int	prstats_silent(void);
static	void	dorw(SCSI *usalp, char *filename, char *sectors);
static	void	doit(SCSI *usalp);
static	void	read_disk(SCSI *usalp, parm_t *parmp);
#ifdef	CLONE_WRITE
static	void	readcd_disk(SCSI *usalp, parm_t *parmp);
static	void	read_lin(SCSI *usalp, parm_t *parmp);
static	int	read_secheader(SCSI *usalp, long addr);
static	int	read_ftoc(SCSI *usalp, parm_t *parmp, BOOL do_sectype);
static	void	read_sectypes(SCSI *usalp, FILE *f);
static	void	get_sectype(SCSI *usalp, long addr, char *st);
#endif

static	void	readc2_disk(SCSI *usalp, parm_t *parmp);
static	int	fread_data(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, 
								  int cnt);
#ifdef	CLONE_WRITE
static	int	fread_2448(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, 
								  int cnt);
static	int	fread_2448_16(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, 
									  int cnt);
static	int	fread_2352(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, 
								  int cnt);
static	int	fread_lin(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, 
								 int cnt);
#endif
static	int	bits(int c);
static	int	bitidx(int c);
static	int	fread_c2(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, 
								int cnt);

static	int	fdata_null(rparm_t *rp, caddr_t bp, long addr, int cnt);
static	int	fdata_c2(rparm_t *rp, caddr_t bp, long addr, int cnt);

#ifdef	used
static	int read_scsi_g1(SCSI *usalp, caddr_t bp, long addr, int cnt);
#endif

int	write_scsi(SCSI *usalp, caddr_t bp, long addr, int cnt);
int	write_g0(SCSI *usalp, caddr_t bp, long addr, int cnt);
int	write_g1(SCSI *usalp, caddr_t bp, long addr, int cnt);

#ifdef	used
static	void	Xrequest_sense(SCSI *usalp);
#endif
static	int	read_retry(SCSI *usalp, caddr_t bp, long addr, long cnt,
								  int (*rfunc)(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt),
								  rparm_t *rp);
static	void	read_generic(SCSI *usalp, parm_t *parmp,
									 int (*rfunc)(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt),
									 rparm_t *rp,
									 int (*dfunc)(rparm_t *rp, caddr_t bp, long addr, int cnt));
static	void	write_disk(SCSI *usalp, parm_t *parmp);
static	int	choice(int n);
static	void	ra(SCSI *usalp);

int	read_da(SCSI *usalp, caddr_t bp, long addr, int cnt, int framesize, 
				  int subcode);
int	read_cd(SCSI *usalp, caddr_t bp, long addr, int cnt, int framesize, 
				  int data, int subch);

static	void	oldmode(SCSI *usalp, int *errp, int *retrp);
static	void	domode(SCSI *usalp, int err, int retr);

static	void	qpto96(Uchar *sub, Uchar *subq, int dop);
static	void	ovtime(SCSI *usalp);
static	void	add_bad(long addr);
static	void	print_bad(void);

struct timeval	starttime;
struct timeval	stoptime;
int	didintr;
int	exsig;

char	*Sbuf;
long	Sbufsize;

/*#define	MAX_RETRY	32*/
#define	MAX_RETRY	128

int	help;
int	xdebug;
int	lverbose;
int	quiet;
BOOL	is_suid;
BOOL	is_cdrom;
BOOL	is_dvd;
BOOL	do_write;
BOOL	c2scan;
BOOL	fulltoc;
BOOL	clonemode;
BOOL	noerror;
BOOL	nocorr;
BOOL	notrunc;
int	retries = MAX_RETRY;
int	maxtry = 0;
int	meshpoints;
BOOL	do_factor;

struct	scsi_format_data fmt;

/*XXX*/EXPORT	BOOL cvt_cyls(void) { return (FALSE); }
/*XXX*/EXPORT	BOOL cvt_bcyls(void) { return (FALSE); }
/*XXX*/EXPORT	void print_defect_list(void) {}

static void
usage(int ret)
{
	fprintf(stderr, "Usage:\treadom [options]\n");
	fprintf(stderr, "options:\n");
	fprintf(stderr, "\t-version	print version information and exit\n");
	fprintf(stderr, "\tdev=target	SCSI target to use\n");
	fprintf(stderr, "\tf=filename	Name of file to read/write\n");
	fprintf(stderr, "\tsectors=range	Range of sectors to read/write\n");
	fprintf(stderr, "\tspeed=#		set speed of drive (MMC only)\n");
	fprintf(stderr, "\tts=#		set maximum transfer size for a single SCSI command\n");
	fprintf(stderr, "\t-w		Switch to write mode\n");
	fprintf(stderr, "\t-c2scan		Do a C2 error scan\n");
#ifdef	CLONE_WRITE
	fprintf(stderr, "\t-fulltoc	Retrieve the full TOC\n");
	fprintf(stderr, "\t-clone		Retrieve the full TOC and all data\n");
#endif
	fprintf(stderr, "\ttimeout=#	set the default SCSI command timeout to #.\n");
	fprintf(stderr, "\tdebug=#,-d	Set to # or increment misc debug level\n");
	fprintf(stderr, "\tkdebug=#,kd=#	do Kernel debugging\n");
	fprintf(stderr, "\t-quiet,-q	be more quiet in error retry mode\n");
	fprintf(stderr, "\t-verbose,-v	increment general verbose level by one\n");
	fprintf(stderr, "\t-Verbose,-V	increment SCSI command transport verbose level by one\n");
	fprintf(stderr, "\t-silent,-s	do not print status of failed SCSI commands\n");
	fprintf(stderr, "\t-scanbus	scan the SCSI bus and exit\n");
	fprintf(stderr, "\t-noerror	do not abort on error\n");
#ifdef	CLONE_WRITE
	fprintf(stderr, "\t-nocorr		do not apply error correction in drive\n");
#endif
	fprintf(stderr, "\t-notrunc	do not truncate outputfile in read mode\n");
	fprintf(stderr, "\tretries=#	set retry count (default is %d)\n", retries);
	fprintf(stderr, "\t-overhead	meter SCSI command overhead times\n");
	fprintf(stderr, "\tmeshpoints=#	print read-speed at # locations\n");
	fprintf(stderr, "\t-factor		try to use speed factor with meshpoints=# if possible\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "sectors=0-0 will read nothing, sectors=0-1 will read one sector starting from 0\n");
	exit(ret);
}	

/* CSTYLED */
char	opts[]   = "debug#,d+,kdebug#,kd#,timeout#,quiet,q,verbose+,v+,Verbose+,V+,x+,xd#,silent,s,help,h,version,scanbus,dev*,sectors*,w,c2scan,fulltoc,clone,noerror,nocorr,notrunc,retries#,factor,f*,speed#,ts&,overhead,meshpoints#";

int
main(int argc, char *argv[])
{
	char	*dev = NULL;
	int	fcount;
	int	cac;
	char	* const *cav;
	int	scsibus	= -1;
	int	target	= -1;
	int	lun	= -1;
	int	silent	= 0;
	int	verbose	= 0;
	int	kdebug	= 0;
	int	debug	= 0;
	int	deftimeout = 40;
	int	pversion = 0;
	int	scanbus = 0;
	int	speed	= -1;
	int	dooverhead = 0;
	SCSI	*usalp;
	char	*filename = NULL;
	char	*sectors = NULL;

	save_args(argc, argv);

	cac = --argc;
	cav = ++argv;

	if (getallargs(&cac, &cav, opts,
			&debug, &debug,
			&kdebug, &kdebug,
			&deftimeout,
			&quiet, &quiet,
			&lverbose, &lverbose,
			&verbose, &verbose,
			&xdebug, &xdebug,
			&silent, &silent,
			&help, &help, &pversion,
			&scanbus, &dev, &sectors, &do_write,
			&c2scan,
			&fulltoc, &clonemode,
			&noerror, &nocorr,
			&notrunc, &retries, &do_factor, &filename,
			&speed, getnum, &Sbufsize,
			&dooverhead, &meshpoints) < 0) {
		errmsgno(EX_BAD, "Bad flag: %s.\n", cav[0]);
		usage(EX_BAD);
	}
	if (help)
		usage(0);
	if (pversion) {
		printf("readcd %s is not what you see here. This line is only a fake for too clever\n"
				"GUIs and other frontend applications. In fact, this program is:\n", cdr_version);

		printf("readom " CDRKIT_VERSION " (" HOST_SYSTEM ")\n"
				"Copyright (C) 1987, 1995-2006 Joerg Schilling\n"
				"Copyright (C) 2006 Cdrkit maintainers\n"
				"(modified version of <censored> -- "
				"don't bother Joerg Schilling with problems)\n");
		exit(0);
	}

	fcount = 0;
	cac = argc;
	cav = argv;

	while (getfiles(&cac, &cav, opts) > 0) {
		fcount++;
		if (fcount == 1) {
			if (*astoi(cav[0], &target) != '\0') {
				errmsgno(EX_BAD,
					"Target '%s' is not a Number.\n",
								cav[0]);
				usage(EX_BAD);
				/* NOTREACHED */
			}
		}
		if (fcount == 2) {
			if (*astoi(cav[0], &lun) != '\0') {
				errmsgno(EX_BAD,
					"Lun is '%s' not a Number.\n",
								cav[0]);
				usage(EX_BAD);
				/* NOTREACHED */
			}
		}
		if (fcount == 3) {
			if (*astoi(cav[0], &scsibus) != '\0') {
				errmsgno(EX_BAD,
					"Scsibus is '%s' not a Number.\n",
								cav[0]);
				usage(EX_BAD);
				/* NOTREACHED */
			}
		}
		cac--;
		cav++;
	}
/*fprintf(stderr, "dev: '%s'\n", dev);*/
	if (!scanbus)
		cdr_defaults(&dev, NULL, NULL, NULL);
	if (debug) {
		printf("dev: '%s'\n", dev);
	}
	if (!scanbus && dev == NULL &&
	    scsibus == -1 && (target == -1 || lun == -1)) {
		errmsgno(EX_BAD, "No SCSI device specified.\n");
		usage(EX_BAD);
	}
	if (dev || scanbus) {
		char	errstr[80];

		/*
		 * 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 (dev != NULL &&
		    ((strncmp(dev, "HELP", 4) == 0) ||
		    (strncmp(dev, "help", 4) == 0))) {
			usal_help(stderr);
			exit(0);
		}
		if ((usalp = usal_open(dev, errstr, sizeof (errstr), debug, lverbose)) == (SCSI *)0) {
			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.":"");
			errmsgno(EX_BAD, "For possible transport specifiers try 'wodim dev=help'.\n");
			exit(err);
		}
	} else {
		if (scsibus == -1 && target >= 0 && lun >= 0)
			scsibus = 0;

		usalp = usal_smalloc();
		usalp->debug = debug;
		usalp->kdebug = kdebug;

		usal_settarget(usalp, scsibus, target, lun);
		if (usal__open(usalp, NULL) <= 0)
			comerr("Cannot open SCSI driver.\n");
	}
	usalp->silent = silent;
	usalp->verbose = verbose;
	usalp->debug = debug;
	usalp->kdebug = kdebug;
	usal_settimeout(usalp, deftimeout);

	if (Sbufsize == 0)
		Sbufsize = 256*1024L;
	Sbufsize = usal_bufsize(usalp, Sbufsize);
	if ((Sbuf = usal_getbuf(usalp, Sbufsize)) == NULL)
		comerr("Cannot get SCSI I/O buffer.\n");

#ifdef	HAVE_PRIV_SET
	is_suid = priv_ineffect(PRIV_FILE_DAC_READ) &&
		    !priv_ineffect(PRIV_PROC_SETID);
	/*
	 * Give up privs we do not need anymore.
	 * We no longer need:
	 *	file_dac_read,net_privaddr
	 * We still need:
	 *	sys_devices
	 */
	priv_set(PRIV_OFF, PRIV_EFFECTIVE,
		PRIV_FILE_DAC_READ, PRIV_NET_PRIVADDR, NULL);
	priv_set(PRIV_OFF, PRIV_PERMITTED,
		PRIV_FILE_DAC_READ, PRIV_NET_PRIVADDR, NULL);
	priv_set(PRIV_OFF, PRIV_INHERITABLE,
		PRIV_FILE_DAC_READ, PRIV_NET_PRIVADDR, PRIV_SYS_DEVICES, NULL);
#endif
	/*
	 * This is only for OS that do not support fine grained privs.
	 */
	if (!is_suid)
		is_suid = geteuid() != getuid();
	/*
	 * We don't need root privilleges anymore.
	 */
#ifdef	HAVE_SETREUID
	if (setreuid(-1, getuid()) < 0)
#else
#ifdef	HAVE_SETEUID
	if (seteuid(getuid()) < 0)
#else
	if (setuid(getuid()) < 0)
#endif
#endif
		comerr("Panic cannot set back effective uid.\n");

	/* code to use SCG */

	if (scanbus) {
		select_target(usalp, stdout);
		exit(0);
	}
	do_inquiry(usalp, FALSE);
	allow_atapi(usalp, TRUE);    /* Try to switch to 10 byte mode cmds */
	if (is_mmc(usalp, NULL, NULL)) {
		int	rspeed;
		int	wspeed;
		/*
		 * At this point we know that we have a SCSI-3/mmc compliant drive.
		 * Unfortunately ATAPI drives violate the SCSI spec in returning
		 * a response data format of '1' which from the SCSI spec would
		 * tell us not to use the "PF" bit in mode select. As ATAPI drives
		 * require the "PF" bit to be set, we 'correct' the inquiry data.
		 */
		if (usalp->inq->data_format < 2)
			usalp->inq->data_format = 2;

		if ((rspeed = get_curprofile(usalp)) >= 0) {
			if (rspeed >= 0x08 && rspeed < 0x10)
				is_cdrom = TRUE;
			if (rspeed >= 0x10 && rspeed < 0x20)
				is_dvd = TRUE;
		} else {
			BOOL	dvd;

			mmc_check(usalp, NULL, NULL, NULL, NULL, &dvd, NULL);
			if (dvd == FALSE) {
				is_cdrom = TRUE;
			} else {
				char	xb[32];

				if (read_dvd_structure(usalp, (caddr_t)xb, 32, 0, 0, 0) >= 0) {
				/*
				 * If read DVD structure is supported and works, then
				 * we must have a DVD media in the drive. Signal to
				 * use the DVD driver.
				 */
					is_dvd = TRUE;
				} else {
					is_cdrom = TRUE;
				}
			}
		}

		if (speed > 0)
			speed *= 177;
		if (speed > 0xFFFF || speed < 0)
			speed = 0xFFFF;
		scsi_set_speed(usalp, speed, speed, ROTCTL_CLV);
		if (scsi_get_speed(usalp, &rspeed, &wspeed) >= 0) {
			fprintf(stderr, "Read  speed: %5d kB/s (CD %3dx, DVD %2dx).\n",
				rspeed, rspeed/176, rspeed/1385);
			fprintf(stderr, "Write speed: %5d kB/s (CD %3dx, DVD %2dx).\n",
				wspeed, wspeed/176, wspeed/1385);
		}
	}
	exargs.usalp	   = usalp;
	exargs.old_secsize = -1;
/*	exargs.flags	   = flags;*/
	exargs.oerr[2]	   = 0;

	/*
	 * Install exit handler before we change the drive status.
	 */
	on_comerr(exscsi, &exargs);
	signal(SIGINT, intr);
	signal(SIGTERM, intr);

	if (dooverhead) {
		ovtime(usalp);
		comexit(0);
	}

	if (is_suid) {
		if (usalp->inq->type != INQ_ROMD)
			comerrno(EX_BAD, "Not root. Will only work on CD-ROM in suid/priv mode\n");
	}

	if (filename || sectors || c2scan || meshpoints || fulltoc || clonemode) {
		dorw(usalp, filename, sectors);
	} else {
		doit(usalp);
	}
	comexit(0);
	return (0);
}

/*
 * XXX Leider kann man vim Signalhandler keine SCSI Kommandos verschicken
 * XXX da meistens das letzte SCSI Kommando noch laeuft.
 * XXX Eine Loesung waere ein Abort Callback in SCSI *.
 */
static void
intr(int sig)
{
	didintr++;
	exsig = sig;
/*	comexit(sig);*/
}

/* ARGSUSED */
static void
exscsi(int excode, void *arg)
{
	struct exargs	*exp = (struct exargs *)arg;
		int	i;

	/*
	 * Try to restore the old sector size.
	 */
	if (exp != NULL && exp->exflags == 0) {
		for (i = 0; i < 10*100; i++) {
			if (!exp->usalp->running)
				break;
			if (i == 10) {
				errmsgno(EX_BAD,
					"Waiting for current SCSI command to finish.\n");
			}
			usleep(100000);
		}

		if (!exp->usalp->running) {
			if (exp->oerr[2] != 0) {
				domode(exp->usalp, exp->oerr[0], exp->oerr[1]);
			}
			if (exp->old_secsize > 0 && exp->old_secsize != 2048)
				select_secsize(exp->usalp, exp->old_secsize);
		}
		exp->exflags++;	/* Make sure that it only get called once */
	}
}

static void
excdr(int excode, void *arg)
{
	exscsi(excode, arg);

#ifdef	needed
	/* Do several other restores/statistics here (see cdrecord.c) */
#endif
}

/*
 * Return milliseconds since start time.
 */
static int
prstats(void)
{
	int	sec;
	int	usec;
	int	tmsec;

	if (gettimeofday(&stoptime, (struct timezone *)0) < 0)
		comerr("Cannot get time\n");

	sec = stoptime.tv_sec - starttime.tv_sec;
	usec = stoptime.tv_usec - starttime.tv_usec;
	tmsec = sec*1000 + usec/1000;
#ifdef	lint
	tmsec = tmsec;	/* Bisz spaeter */
#endif
	if (usec < 0) {
		sec--;
		usec += 1000000;
	}

	fprintf(stderr, "Time total: %d.%03dsec\n", sec, usec/1000);
	return (1000*sec + (usec / 1000));
}

/*
 * Return milliseconds since start time, but be silent this time.
 */
static int
prstats_silent(void)
{
	int	sec;
	int	usec;
	int	tmsec;

	if (gettimeofday(&stoptime, (struct timezone *)0) < 0)
		comerr("Cannot get time\n");

	sec = stoptime.tv_sec - starttime.tv_sec;
	usec = stoptime.tv_usec - starttime.tv_usec;
	tmsec = sec*1000 + usec/1000;
#ifdef	lint
	tmsec = tmsec;	/* Bisz spaeter */
#endif
	if (usec < 0) {
		sec--;
		usec += 1000000;
	}

	return (1000*sec + (usec / 1000));
}

static void
dorw(SCSI *usalp, char *filename, char *sectors)
{
	parm_t	params;
	char	*p = NULL;

	params.start = 0;
	params.end = -1;
	params.sptr = -1;
	params.askrange = FALSE;
	params.name = NULL;

	if (filename)
		params.name = filename;
	if (meshpoints > 0) {
		if (params.name == NULL)
			params.name = "/dev/null";
	}
	if (sectors)
		p = astol(sectors, &params.start);
	if (p && *p == '-')
		p = astol(++p, &params.end);
	if (p && *p != '\0')
		comerrno(EX_BAD, "Not a valid sector range '%s'\n", sectors);

	if (!wait_unit_ready(usalp, 60))
		comerrno(EX_BAD, "Device not ready.\n");

#ifdef	CLONE_WRITE
	if (fulltoc) {
		if (params.name == NULL)
			params.name = "/dev/null";
		read_ftoc(usalp, &params, FALSE);
	} else if (clonemode) {
		if (!is_mmc(usalp, NULL, NULL))
			comerrno(EX_BAD, "Unsupported device for clone mode.\n");
		noerror = TRUE;
		if (retries == MAX_RETRY)
			retries = 10;
		if (params.name == NULL)
			params.name = "/dev/null";

		if (read_ftoc(usalp, &params, TRUE) < 0)
			comerrno(EX_BAD, "Read fulltoc problems.\n");
		readcd_disk(usalp, &params);
	} else
#endif
	if (c2scan) {
		noerror = TRUE;
		if (retries == MAX_RETRY)
			retries = 10;
		if (params.name == NULL)
			params.name = "/dev/null";
		readc2_disk(usalp, &params);
	} else if (do_write)
		write_disk(usalp, &params);
	else
		read_disk(usalp, &params);
}

static void
doit(SCSI *usalp)
{
	int	i = 0;
	parm_t	params;

	params.start = 0;
	params.end = -1;
	params.sptr = -1;
	params.askrange = TRUE;
	params.name = "/dev/null";

	for (;;) {
		if (!wait_unit_ready(usalp, 60))
			comerrno(EX_BAD, "Device not ready.\n");

		printf("0:read 1:veri   2:erase   3:read buffer 4:cache 5:ovtime 6:cap\n");
		printf("7:wne  8:floppy 9:verify 10:checkcmds  11:read disk 12:write disk\n");
		printf("13:scsireset 14:seektest 15: readda 16: reada 17: c2err\n");
#ifdef	CLONE_WRITE
		printf("18:readom 19: lin 20: full toc\n");
#endif

		getint("Enter selection:", &i, 0, 20);
		if (didintr)
			return;

		switch (i) {

		case 5:		ovtime(usalp);		break;
		case 11:	read_disk(usalp, 0);	break;
		case 12:	write_disk(usalp, 0);	break;
		case 15:	ra(usalp);		break;
/*		case 16:	reada_disk(usalp, 0, 0);	break;*/
		case 17:	readc2_disk(usalp, &params);	break;
#ifdef	CLONE_WRITE
		case 18:	readcd_disk(usalp, 0);	break;
		case 19:	read_lin(usalp, 0);	break;
		case 20:	read_ftoc(usalp, 0, FALSE);	break;
#endif
		}
	}
}

static void
read_disk(SCSI *usalp, parm_t *parmp)
{
	rparm_t	rp;

	read_capacity(usalp);
	print_capacity(usalp, stderr);

	rp.errors = 0;
	rp.c2_errors = 0;
	rp.c2_maxerrs = 0;
	rp.c2_errsecs = 0;
	rp.c2_badsecs = 0;
	rp.secsize = usalp->cap->c_bsize;

	read_generic(usalp, parmp, fread_data, &rp, fdata_null);
}

#ifdef	CLONE_WRITE
static void
readcd_disk(SCSI *usalp, parm_t *parmp)
{
	rparm_t	rp;
	int	osecsize = 2048;
	int	oerr = 0;
	int	oretr = 10;
	int	(*funcp)(SCSI *_usalp, rparm_t *_rp, caddr_t bp, long addr, int cnt);

	usalp->silent++;
	if (read_capacity(usalp) >= 0)
		osecsize = usalp->cap->c_bsize;
	usalp->silent--;
	if (osecsize != 2048)
		select_secsize(usalp, 2048);

	read_capacity(usalp);
	print_capacity(usalp, stderr);

	rp.errors = 0;
	rp.c2_errors = 0;
	rp.c2_maxerrs = 0;
	rp.c2_errsecs = 0;
	rp.c2_badsecs = 0;
	rp.secsize = 2448;
	rp.ismmc = is_mmc(usalp, NULL, NULL);
	funcp = fread_2448;

	wait_unit_ready(usalp, 10);
	if (fread_2448(usalp, &rp, Sbuf, 0, 0) < 0) {
		errmsgno(EX_BAD, "read 2448 failed\n");
		if (rp.ismmc &&
		    fread_2448_16(usalp, &rp, Sbuf, 0, 0) >= 0) {
			errmsgno(EX_BAD, "read 2448_16 : OK\n");

			funcp = fread_2448_16;
		}
	}

	oldmode(usalp, &oerr, &oretr);
	exargs.oerr[0] = oerr;
	exargs.oerr[1] = oretr;
	exargs.oerr[2] = 0xFF;
	if (parmp == NULL)		/* XXX Nur am Anfang!!! */
		domode(usalp, -1, -1);
	else
		domode(usalp, nocorr?0x21:0x20, 10);

	read_generic(usalp, parmp, funcp, &rp, fdata_null);
	if (osecsize != 2048)
		select_secsize(usalp, osecsize);
	domode(usalp, oerr, oretr);
}

/* ARGSUSED */
static void
read_lin(SCSI *usalp, parm_t *parmp)
{
	parm_t	parm;
	rparm_t	rp;

	read_capacity(usalp);
	print_capacity(usalp, stderr);

	parm.start = ULONG_C(0xF0000000);
	parm.end =   ULONG_C(0xFF000000);
	parm.name = "DDD";

	rp.errors = 0;
	rp.c2_errors = 0;
	rp.c2_maxerrs = 0;
	rp.c2_errsecs = 0;
	rp.c2_badsecs = 0;
	rp.secsize = 2448;
	rp.ismmc = is_mmc(usalp, NULL, NULL);
	domode(usalp, -1, -1);
	read_generic(usalp, &parm, fread_lin, &rp, fdata_null);
}

static int
read_secheader(SCSI *usalp, long addr)
{
	rparm_t	rp;
	int	osecsize = 2048;
	int	ret = 0;

	usalp->silent++;
	if (read_capacity(usalp) >= 0)
		osecsize = usalp->cap->c_bsize;
	usalp->silent--;
	if (osecsize != 2048)
		select_secsize(usalp, 2048);

	read_capacity(usalp);

	rp.errors = 0;
	rp.c2_errors = 0;
	rp.c2_maxerrs = 0;
	rp.c2_errsecs = 0;
	rp.c2_badsecs = 0;
	rp.secsize = 2352;
	rp.ismmc = is_mmc(usalp, NULL, NULL);

	wait_unit_ready(usalp, 10);

	fillbytes(Sbuf, 2352, '\0');
	if (fread_2352(usalp, &rp, Sbuf, addr, 1) < 0) {
		ret = -1;
	}
	if (osecsize != 2048)
		select_secsize(usalp, osecsize);
	return (ret);
}

/* ARGSUSED */
static int
read_ftoc(SCSI *usalp, parm_t *parmp, BOOL do_sectype)
{
	FILE	*f;
	int	i;
	char	filename[1024];
	struct	tocheader *tp;
	char	*p;
	char	xb[256];
	int	len;
	char	xxb[10000];


	strcpy(filename, "toc.dat");
	if (strcmp(parmp->name, "/dev/null") != 0) {

		len = strlen(parmp->name);
		if (len > (sizeof (filename)-5)) {
			len = sizeof (filename)-5;
		}
		snprintf(filename, sizeof (filename), "%.*s.toc", len, parmp->name);
	}

	tp = (struct tocheader *)xb;

	fillbytes((caddr_t)xb, sizeof (xb), '\0');
	if (read_toc(usalp, xb, 0, sizeof (struct tocheader), 0, FMT_FULLTOC) < 0) {
		if (usalp->silent == 0 || usalp->verbose > 0)
			errmsgno(EX_BAD, "Cannot read TOC header\n");
		return (-1);
	}
	len = a_to_u_2_byte(tp->len) + sizeof (struct tocheader)-2;
	fprintf(stderr, "TOC len: %d. First Session: %d Last Session: %d.\n", len, tp->first, tp->last);

	if (read_toc(usalp, xxb, 0, len, 0, FMT_FULLTOC) < 0) {
		if (len & 1) {
			/*
			 * Work around a bug in some operating systems that do not
			 * handle odd byte DMA correctly for ATAPI drives.
			 */
			wait_unit_ready(usalp, 30);
			read_toc(usalp, xb, 0, sizeof (struct tocheader), 0, FMT_FULLTOC);
			wait_unit_ready(usalp, 30);
			if (read_toc(usalp, xxb, 0, len+1, 0, FMT_FULLTOC) >= 0) {
				goto itworked;
			}
		}
		if (usalp->silent == 0)
			errmsgno(EX_BAD, "Cannot read full TOC\n");
		return (-1);
	}

itworked:
	f = fileopen(filename, "wctb");

	if (f == NULL)
		comerr("Cannot open '%s'.\n", filename);
	filewrite(f, xxb, len);
	if (do_sectype)
		read_sectypes(usalp, f);
	fflush(f);
	fclose(f);

	p = &xxb[4];
	for (; p < &xxb[len]; p += 11) {
		for (i = 0; i < 11; i++)
			fprintf(stderr, "%02X ", p[i] & 0xFF);
		fprintf(stderr, "\n");
	}
	/*
	 * List all lead out start times to give information about multi
	 * session disks.
	 */
	p = &xxb[4];
	for (; p < &xxb[len]; p += 11) {
		if ((p[3] & 0xFF) == 0xA2) {
			fprintf(stderr, "Lead out %d: %ld\n", p[0], msf_to_lba(p[8], p[9], p[10], TRUE));
		}
	}
	return (0);
}

static void
read_sectypes(SCSI *usalp, FILE *f)
{
	char	sect;

	sect = SECT_AUDIO;
	get_sectype(usalp, 4, &sect);
	if (f != NULL)
		filewrite(f, &sect, 1);
	if (xdebug)
		usal_prbytes("sec 0", (Uchar *)Sbuf, 16);

	sect = SECT_AUDIO;
	get_sectype(usalp, usalp->cap->c_baddr-4, &sect);
	if (f != NULL)
		filewrite(f, &sect, 1);
	if (xdebug) {
		usal_prbytes("sec E", (Uchar *)Sbuf, 16);
		fprintf(stderr, "baddr: %ld\n", (long)usalp->cap->c_baddr);
	}
}

static void
get_sectype(SCSI *usalp, long addr, char *st)
{
	char	*synchdr = "\0\377\377\377\377\377\377\377\377\377\377\0";
	int	sectype = SECT_AUDIO;
	int	i;
	long	raddr = addr;
#define	_MAX_TRY_	20

	usalp->silent++;
	for (i = 0; i < _MAX_TRY_ && read_secheader(usalp, raddr) < 0; i++) {
		if (addr == 0)
			raddr++;
		else
			raddr--;
	}
	usalp->silent--;
	if (i >= _MAX_TRY_) {
		fprintf(stderr, "Sectype (%ld) is CANNOT\n", addr);
		return;
	} else if (i > 0) {
		fprintf(stderr, "Sectype (%ld) needed %d retries\n", addr, i);
	}
#undef	_MAX_TRY_

	if (cmpbytes(Sbuf, synchdr, 12) < 12) {
		if (xdebug)
			fprintf(stderr, "Sectype (%ld) is AUDIO\n", addr);
		if (st)
			*st = SECT_AUDIO;
		return;
	}
	if (xdebug)
		fprintf(stderr, "Sectype (%ld) is DATA\n", addr);
	if (Sbuf[15] == 0) {
		if (xdebug)
			fprintf(stderr, "Sectype (%ld) is MODE 0\n", addr);
		sectype = SECT_MODE_0;

	} else if (Sbuf[15] == 1) {
		if (xdebug)
			fprintf(stderr, "Sectype (%ld) is MODE 1\n", addr);
		sectype = SECT_ROM;

	} else if (Sbuf[15] == 2) {
		if (xdebug)
			fprintf(stderr, "Sectype (%ld) is MODE 2\n", addr);

		if ((Sbuf[16+2]  & 0x20) == 0 &&
		    (Sbuf[16+4+2]  & 0x20) == 0) {
			if (xdebug)
				fprintf(stderr, "Sectype (%ld) is MODE 2 form 1\n", addr);
			sectype = SECT_MODE_2_F1;

		} else if ((Sbuf[16+2]  & 0x20) != 0 &&
		    (Sbuf[16+4+2]  & 0x20) != 0) {
			if (xdebug)
				fprintf(stderr, "Sectype (%ld) is MODE 2 form 2\n", addr);
			sectype = SECT_MODE_2_F2;
		} else {
			if (xdebug)
				fprintf(stderr, "Sectype (%ld) is MODE 2 formless\n", addr);
			sectype = SECT_MODE_2;
		}
	} else {
		fprintf(stderr, "Sectype (%ld) is UNKNOWN\n", addr);
	}
	if (st)
		*st = sectype;
	if (xdebug)
		fprintf(stderr, "Sectype (%ld) is 0x%02X\n", addr, sectype);
}

#endif	/* CLONE_WRITE */

char	zeroblk[512];

static void
readc2_disk(SCSI *usalp, parm_t *parmp)
{
	rparm_t	rp;
	int	osecsize = 2048;
	int	oerr = 0;
	int	oretr = 10;

	usalp->silent++;
	if (read_capacity(usalp) >= 0)
		osecsize = usalp->cap->c_bsize;
	usalp->silent--;
	if (osecsize != 2048)
		select_secsize(usalp, 2048);

	read_capacity(usalp);
	print_capacity(usalp, stderr);

	rp.errors = 0;
	rp.c2_errors = 0;
	rp.c2_maxerrs = 0;
	rp.c2_errsecs = 0;
	rp.c2_badsecs = 0;
	rp.secsize = 2352 + 294;
	rp.ismmc = is_mmc(usalp, NULL, NULL);

	oldmode(usalp, &oerr, &oretr);
	exargs.oerr[0] = oerr;
	exargs.oerr[1] = oretr;
	exargs.oerr[2] = 0xFF;
	domode(usalp, 0x21, 10);


	read_generic(usalp, parmp, fread_c2, &rp, fdata_c2);
	if (osecsize != 2048)
		select_secsize(usalp, osecsize);
	domode(usalp, oerr, oretr);

	printf("Total of %d hard read errors.\n", rp.errors);
	printf("C2 errors total: %d bytes in %d sectors on disk\n", rp.c2_errors, rp.c2_errsecs);
	printf("C2 errors rate: %f%% \n", (100.0*rp.c2_errors)/usalp->cap->c_baddr/2352);
	printf("C2 errors on worst sector: %d, sectors with 100+ C2 errors: %d\n", rp.c2_maxerrs, rp.c2_badsecs);
}

/* ARGSUSED */
static int
fread_data(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt)
{
	return (read_g1(usalp, bp, addr, cnt));
}

#ifdef	CLONE_WRITE
static int
fread_2448(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt)
{
	if (rp->ismmc) {
		return (read_cd(usalp, bp, addr, cnt, rp->secsize,
			/* Sync + all headers + user data + EDC/ECC */
			(1 << 7 | 3 << 5 | 1 << 4 | 1 << 3),
			/* plus all subchannels RAW */
			1));
	} else {
		return (read_da(usalp, bp, addr, cnt, rp->secsize,
			/* Sync + all headers + user data + EDC/ECC + all subch */
			0x02));
	}
}

static int
fread_2448_16(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt)
{

	if (rp->ismmc) {
		track_t trackdesc;
		int	ret;
		int	i;
		char	*p;

		trackdesc.isecsize = 2368;
		trackdesc.secsize = 2448;
		ret = read_cd(usalp, bp, addr, cnt, 2368,
			/* Sync + all headers + user data + EDC/ECC */
			(1 << 7 | 3 << 5 | 1 << 4 | 1 << 3),
			/* subchannels P/Q */
			2);
		if (ret < 0)
			return (ret);

		scatter_secs(&trackdesc, bp, cnt);
		for (i = 0, p = bp+2352; i < cnt; i++) {
#ifdef	more_than_q_sub
			if ((p[15] & 0x80) != 0)
				printf("P");
#endif
			/*
			 * As the drives don't return P-sub, we check
			 * whether the index equals 0.
			 */
			qpto96((Uchar *)p, (Uchar *)p, p[2] == 0);
			p += 2448;
		}
		return (ret);
	} else {
		comerrno(EX_BAD, "Cannot fread_2448_16 on non MMC drives\n");

		return (read_da(usalp, bp, addr, cnt, rp->secsize,
			/* Sync + all headers + user data + EDC/ECC + all subch */
			0x02));
	}
}

static int
fread_2352(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt)
{
	if (rp->ismmc) {
		return (read_cd(usalp, bp, addr, cnt, rp->secsize,
			/* Sync + all headers + user data + EDC/ECC */
			(1 << 7 | 3 << 5 | 1 << 4 | 1 << 3),
			/* NO subchannels */
			0));
	} else {
		comerrno(EX_BAD, "Cannot fread_2352 on non MMC drives\n");

		return (read_da(usalp, bp, addr, cnt, rp->secsize,
			/* Sync + all headers + user data + EDC/ECC + all subch */
			0x02));
	}
}

static int
fread_lin(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt)
{
	if (addr != ULONG_C(0xF0000000))
		addr = ULONG_C(0xFFFFFFFF);

	return (read_cd(usalp, bp, addr, cnt, rp->secsize,
		/* Sync + all headers + user data + EDC/ECC */
		(1 << 7 | 3 << 5 | 1 << 4 | 1 << 3),
		/* plus all subchannels RAW */
		1));
}
#endif	/* CLONE_WRITE */

static int
bits(int c)
{
	int	n = 0;

	if (c & 0x01)
		n++;
	if (c & 0x02)
		n++;
	if (c & 0x04)
		n++;
	if (c & 0x08)
		n++;
	if (c & 0x10)
		n++;
	if (c & 0x20)
		n++;
	if (c & 0x40)
		n++;
	if (c & 0x80)
		n++;
	return (n);
}

static int
bitidx(int c)
{
	if (c & 0x80)
		return (0);
	if (c & 0x40)
		return (1);
	if (c & 0x20)
		return (2);
	if (c & 0x10)
		return (3);
	if (c & 0x08)
		return (4);
	if (c & 0x04)
		return (5);
	if (c & 0x02)
		return (6);
	if (c & 0x01)
		return (7);
	return (-1);
}

static int
fread_c2(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt)
{
	if (rp->ismmc) {
		return (read_cd(usalp, bp, addr, cnt, rp->secsize,
			/* Sync + all headers + user data + EDC/ECC + C2 */
/*			(1 << 7 | 3 << 5 | 1 << 4 | 1 << 3 | 2 << 1),*/
			(1 << 7 | 3 << 5 | 1 << 4 | 1 << 3 | 1 << 1),
			/* without subchannels */
			0));
	} else {
		return (read_da(usalp, bp, addr, cnt, rp->secsize,
			/* Sync + all headers + user data + EDC/ECC + C2 */
			0x04));
	}
}

/* ARGSUSED */
static int
fdata_null(rparm_t *rp, caddr_t bp, long addr, int cnt)
{
	return (0);
}

static int
fdata_c2(rparm_t *rp, caddr_t bp, long addr, int cnt)
{
	int	i;
	int	j;
	int	k;
	char	*p;

	p = &bp[2352];

	for (i = 0; i < cnt; i++, p += (2352+294)) {
/*		usal_prbytes("XXX ", p, 294);*/
		if ((j = cmpbytes(p, zeroblk, 294)) < 294) {
			printf("C2 in sector: %3ld first at byte: %4d (0x%02X)", addr+i,
				j*8 + bitidx(p[j]), p[j]&0xFF);
			for (j = 0, k = 0; j < 294; j++)
				k += bits(p[j]);
			printf(" total: %4d errors\n", k);
/*			usal_prbytes("XXX ", p, 294);*/
			rp->c2_errors += k;
			if (k > rp->c2_maxerrs)
				rp->c2_maxerrs = k;
			rp->c2_errsecs++;
			if (k >= 100)
				rp->c2_badsecs += 1;
		}
	}
	return (0);
}

#ifdef	used
static int
read_scsi_g1(SCSI *usalp, caddr_t bp, long addr, int cnt)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
/*	scmd->size = cnt*512;*/
	scmd->size = cnt*usalp->cap->c_bsize;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x28;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	g1_cdbaddr(&scmd->cdb.g1_cdb, addr);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	usalp->cmdname = "read extended";

	return (usal_cmd(usalp));
}
#endif

#define	G0_MAXADDR	0x1FFFFFL

int
write_scsi(SCSI *usalp, caddr_t bp, long addr, int cnt)
{
	if (addr <= G0_MAXADDR)
		return (write_g0(usalp, bp, addr, cnt));
	else
		return (write_g1(usalp, bp, addr, cnt));
}

int
write_g0(SCSI *usalp, caddr_t bp, long addr, int cnt)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	if (usalp->cap->c_bsize <= 0)
		raisecond("capacity_not_set", 0L);

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt*usalp->cap->c_bsize;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_WRITE;
	scmd->cdb.g0_cdb.lun = usal_lun(usalp);
	g0_cdbaddr(&scmd->cdb.g0_cdb, addr);
	scmd->cdb.g0_cdb.count = (Uchar)cnt;

	usalp->cmdname = "write_g0";

	return (usal_cmd(usalp));
}

int
write_g1(SCSI *usalp, caddr_t bp, long addr, int cnt)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	if (usalp->cap->c_bsize <= 0)
		raisecond("capacity_not_set", 0L);

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt*usalp->cap->c_bsize;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = SC_EWRITE;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	g1_cdbaddr(&scmd->cdb.g1_cdb, addr);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	usalp->cmdname = "write_g1";

	return (usal_cmd(usalp));
}

#ifdef	used
static void
Xrequest_sense(SCSI *usalp)
{
	char	sense_buf[32];
	struct	usal_cmd ocmd;
	int	sense_count;
	char	*cmdsave;
	register struct	usal_cmd	*scmd = usalp->scmd;

	cmdsave = usalp->cmdname;

	movebytes(scmd, &ocmd, sizeof (*scmd));

	fillbytes((caddr_t)sense_buf, sizeof (sense_buf), '\0');

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)sense_buf;
	scmd->size = sizeof (sense_buf);
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x3;
	scmd->cdb.g1_cdb.lun = usal_lun(usalp);
	scmd->cdb.g0_cdb.count = sizeof (sense_buf);

	usalp->cmdname = "request sense";

	usal_cmd(usalp);

	sense_count = sizeof (sense_buf) - usal_getresid(usalp);
	movebytes(&ocmd, scmd, sizeof (*scmd));
	scmd->sense_count = sense_count;
	movebytes(sense_buf, (Uchar *)&scmd->sense, scmd->sense_count);

	usalp->cmdname = cmdsave;
	usal_printerr(usalp);
	usal_printresult(usalp);	/* XXX restore key/code in future */
}
#endif

static int
read_retry(SCSI *usalp, caddr_t bp, long addr, long cnt, 
			  int (*rfunc)(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt), 
			  rparm_t *rp)
{
/*	int	secsize = usalp->cap->c_bsize;*/
	int	secsize = rp->secsize;
	int	try = 0;
	int	err;
	char	dummybuf[8192];

	if (secsize > sizeof (dummybuf)) {
		errmsgno(EX_BAD, "Cannot retry, sector size %d too big.\n", secsize);
		return (-1);
	}

	errmsgno(EX_BAD, "Retrying from sector %ld.\n", addr);
	while (cnt > 0) {
		fprintf(stderr, ".");

		do {
			if (didintr)
				comexit(exsig);		/* XXX besseres Konzept?!*/
			wait_unit_ready(usalp, 120);
			if (try >= 10) {		/* First 10 retries without seek */
				if ((try % 8) == 0) {
					fprintf(stderr, "+");	/* Read last sector */
					usalp->silent++;
					(*rfunc)(usalp, rp, dummybuf, usalp->cap->c_baddr, 1);
					usalp->silent--;
				} else if ((try % 4) == 0) {
					fprintf(stderr, "-");	/* Read first sector */
					usalp->silent++;
					(*rfunc)(usalp, rp, dummybuf, 0, 1);
					usalp->silent--;
				} else {
					fprintf(stderr, "~");	/* Read random sector */
					usalp->silent++;
					(*rfunc)(usalp, rp, dummybuf, choice(usalp->cap->c_baddr), 1);
					usalp->silent--;
				}
				if (didintr)
					comexit(exsig);		/* XXX besseres Konzept?!*/
				wait_unit_ready(usalp, 120);
			}
			if (didintr)
				comexit(exsig);		/* XXX besseres Konzept?!*/

			fillbytes(bp, secsize, 0);

			usalp->silent++;
			err = (*rfunc)(usalp, rp, bp, addr, 1);
			usalp->silent--;

			if (err < 0) {
				err = usalp->scmd->ux_errno;
/*				fprintf(stderr, "\n");*/
/*				errmsgno(err, "Cannot read source disk\n");*/
			} else {
				if (usal_getresid(usalp)) {
					fprintf(stderr, "\nresid: %d\n", usal_getresid(usalp));
					return (-1);
				}
				break;
			}
		} while (++try < retries);

		if (try >= retries) {
			fprintf(stderr, "\n");
			errmsgno(err, "Error on sector %ld not corrected. Total of %d errors.\n",
					addr, ++rp->errors);

			if (usalp->silent <= 1 && lverbose > 0)
				usal_printerr(usalp);

			add_bad(addr);

			if (!noerror)
				return (-1);
			errmsgno(EX_BAD, "-noerror set, continuing ...\n");
		} else {
			if (try >= maxtry)
				maxtry = try;

			if (try > 1) {
				fprintf(stderr, "\n");
				errmsgno(EX_BAD,
				"Error on sector %ld corrected after %d tries. Total of %d errors.\n",
					addr, try, rp->errors);
			}
		}
		try = 0;
		cnt -= 1;
		addr += 1;
		bp += secsize;
	}
	return (0);
}

static void
read_generic(SCSI *usalp, parm_t *parmp, 
				 int (*rfunc)(SCSI *usalp, rparm_t *rp, caddr_t bp, long addr, int cnt),
				 rparm_t *rp,
				 int (*dfunc)(rparm_t *rp, caddr_t bp, long addr, int cnt))
{
	char	filename[512];
	char	*defname = NULL;
	FILE	*f;
	long	addr = 0L;
	long	old_addr = 0L;
	long	num;
	long	end = 0L;
	long	start = 0L;
	long	cnt = 0L;
	long	next_point = 0L;
	long	secs_per_point = 0L;
	double  speed;
	int	msec;
	int	old_msec = 0;
	int	err = 0;
	BOOL	askrange = FALSE;
	BOOL	isrange = FALSE;
	int	secsize = rp->secsize;
	int	i = 0;

	if (is_suid) {
		if (usalp->inq->type != INQ_ROMD)
			comerrno(EX_BAD, "Not root. Will only read from CD in suid/priv mode\n");
	}

	if (parmp == NULL || parmp->askrange)
		askrange = TRUE;
	if (parmp != NULL && !askrange && (parmp->start <= parmp->end))
		isrange = TRUE;

	filename[0] = '\0';

	usalp->silent++;
	if (read_capacity(usalp) >= 0)
		end = usalp->cap->c_baddr + 1;
	usalp->silent--;

	if ((end <= 0 && isrange) || (askrange && usal_yes("Ignore disk size? ")))
		end = 10000000;	/* Hack to read empty (e.g. blank=fast) disks */

	if (parmp) {
		if (parmp->name)
			defname = parmp->name;
		if (defname != NULL) {
			fprintf(stderr, "Copy from SCSI (%d,%d,%d) disk to file '%s'\n",
					usal_scsibus(usalp), usal_target(usalp), usal_lun(usalp),
					defname);
		}

		addr = start = parmp->start;
		if (parmp->end != -1 && parmp->end < end)
			end = parmp->end;
		cnt = Sbufsize / secsize;
	}

	if (defname == NULL) {
		defname = "disk.out";
		fprintf(stderr, "Copy from SCSI (%d,%d,%d) disk to file\n",
					usal_scsibus(usalp), usal_target(usalp), usal_lun(usalp));
		fprintf(stderr, "Enter filename [%s]: ", defname); flush();
		(void) getline(filename, sizeof (filename));
	}

	if (askrange) {
		addr = start;
		getlong("Enter starting sector for copy:", &addr, start, end-1);
/*		getlong("Enter starting sector for copy:", &addr, -300, end-1);*/
		start = addr;
	}

	if (askrange) {
		num = end - addr;
		getlong("Enter number of sectors to copy:", &num, 1L, num);
		end = addr + num;
	}

	if (askrange) {
/* XXX askcnt */
		cnt = Sbufsize / secsize;
		getlong("Enter number of sectors per copy:", &cnt, 1L, cnt);
	}

	if (filename[0] == '\0')
		strncpy(filename, defname, sizeof (filename));
	filename[sizeof (filename)-1] = '\0';
	if (streql(filename, "-")) {
		f = stdout;
#ifdef	NEED_O_BINARY
		setmode(STDOUT_FILENO, O_BINARY);
#endif
	} else if ((f = fileopen(filename, notrunc?"wcub":"wctub")) == NULL)
		comerr("Cannot open '%s'.\n", filename);

	fprintf(stderr, "end:  %8ld\n", end);
	if (gettimeofday(&starttime, (struct timezone *)0) < 0)
		comerr("Cannot get start time\n");

	if (meshpoints > 0) {
		if ((end-start) < meshpoints)
			secs_per_point = 1;
		else
			secs_per_point = (end-start) / meshpoints;
		next_point = start + secs_per_point;
		old_addr = start;
	}

	for (; addr < end; addr += cnt) {
		if (didintr)
			comexit(exsig);		/* XXX besseres Konzept?!*/

		if ((addr + cnt) > end)
			cnt = end - addr;

		if (meshpoints > 0) {
			if (addr > next_point) {

				msec = prstats_silent();
				if ((msec - old_msec) == 0)		/* Avoid division by zero */
					msec = old_msec + 1;
				speed = ((addr - old_addr)/(1000.0/secsize)) / (0.001*(msec - old_msec));
				if (do_factor) {
					if (is_cdrom)
						speed /= 176.400;
					else if (is_dvd)
						speed /= 1385.0;
				}
				fprintf(stderr, "addr: %8ld cnt: %ld", addr, cnt);
				printf("%8ld %8.2f\n", addr, speed);
				fprintf(stderr, "\r");
				next_point += secs_per_point;
				old_addr = addr;
				old_msec = msec;
				i++;
				if (meshpoints < 100)
					flush();
				else if (i % (meshpoints/100) == 0)
					flush();
			}
		}
		fprintf(stderr, "addr: %8ld cnt: %ld\r", addr, cnt);

		usalp->silent++;
		if ((*rfunc)(usalp, rp, Sbuf, addr, cnt) < 0) {
			usalp->silent--;
			err = usalp->scmd->ux_errno;
			if (quiet) {
				fprintf(stderr, "\n");
			} else if (usalp->silent == 0) {
				usal_printerr(usalp);
			}
			errmsgno(err, "Cannot read source disk\n");

			if (read_retry(usalp, Sbuf, addr, cnt, rfunc, rp) < 0)
				goto out;
		} else {
			usalp->silent--;
			if (usal_getresid(usalp)) {
				fprintf(stderr, "\nresid: %d\n", usal_getresid(usalp));
				goto out;
			}
		}
		(*dfunc)(rp, Sbuf, addr, cnt);
		if (filewrite(f, Sbuf, cnt * secsize) < 0) {
			err = geterrno();
			fprintf(stderr, "\n");
			errmsgno(err, "Cannot write '%s'\n", filename);
			break;
		}
	}
	fprintf(stderr, "addr: %8ld", addr);
out:
	fprintf(stderr, "\n");
	msec = prstats();
	if (msec == 0)		/* Avoid division by zero */
		msec = 1;
#ifdef	OOO
	fprintf(stderr, "Read %.2f kB at %.1f kB/sec.\n",
		(double)(addr - start)/(1024.0/usalp->cap->c_bsize),
		(double)((addr - start)/(1024.0/usalp->cap->c_bsize)) / (0.001*msec));
#else
	fprintf(stderr, "Read %.2f kB at %.1f kB/sec.\n",
		(double)(addr - start)/(1024.0/secsize),
		(double)((addr - start)/(1024.0/secsize)) / (0.001*msec));
#endif
	print_bad();
}

static void
write_disk(SCSI *usalp, parm_t *parmp)
{
	char	filename[512];
	char	*defname = "disk.out";
	FILE	*f;
	long	addr = 0L;
	long	cnt;
	long	amt;
	long	end;
	int	msec;
	int	start;

	if (is_suid)
		comerrno(EX_BAD, "Not root. Will not write in suid/priv mode\n");

	filename[0] = '\0';
	if (read_capacity(usalp) >= 0) {
		end = usalp->cap->c_baddr + 1;
		print_capacity(usalp, stderr);
	}

	if (end <= 1)
		end = 10000000;	/* Hack to write empty disks */

	if (parmp) {
		if (parmp->name)
			defname = parmp->name;
		fprintf(stderr, "Copy from file '%s' to SCSI (%d,%d,%d) disk\n",
					defname,
					usal_scsibus(usalp), usal_target(usalp), usal_lun(usalp));

		addr = start = parmp->start;
		if (parmp->end != -1 && parmp->end < end)
			end = parmp->end;
		cnt = Sbufsize / usalp->cap->c_bsize;
	} else {
		fprintf(stderr, "Copy from file to SCSI (%d,%d,%d) disk\n",
					usal_scsibus(usalp), usal_target(usalp), usal_lun(usalp));
		fprintf(stderr, "Enter filename [%s]: ", defname); flush();
		(void) getline(filename, sizeof (filename));
		fprintf(stderr, "Notice: reading from file always starts at file offset 0.\n");

		getlong("Enter starting sector for copy:", &addr, 0L, end-1);
		start = addr;
		cnt = end - addr;
		getlong("Enter number of sectors to copy:", &end, 1L, end);
		end = addr + cnt;

		cnt = Sbufsize / usalp->cap->c_bsize;
		getlong("Enter number of sectors per copy:", &cnt, 1L, cnt);
/*		fprintf(stderr, "end:  %8ld\n", end);*/
	}

	if (filename[0] == '\0')
		strncpy(filename, defname, sizeof (filename));
	filename[sizeof (filename)-1] = '\0';
	if (streql(filename, "-")) {
		f = stdin;
#ifdef	NEED_O_BINARY
		setmode(STDIN_FILENO, O_BINARY);
#endif
	} else if ((f = fileopen(filename, "rub")) == NULL)
		comerr("Cannot open '%s'.\n", filename);

	fprintf(stderr, "end:  %8ld\n", end);
	if (gettimeofday(&starttime, (struct timezone *)0) < 0)
		comerr("Cannot get start time\n");

	for (; addr < end; addr += cnt) {
		if (didintr)
			comexit(exsig);		/* XXX besseres Konzept?!*/

		if ((addr + cnt) > end)
			cnt = end - addr;

		fprintf(stderr, "addr: %8ld cnt: %ld\r", addr, cnt);

		if ((amt = fileread(f, Sbuf, cnt * usalp->cap->c_bsize)) < 0)
			comerr("Cannot read '%s'\n", filename);
		if (amt == 0)
			break;
		if ((amt / usalp->cap->c_bsize) < cnt)
			cnt = amt / usalp->cap->c_bsize;
		if (write_scsi(usalp, Sbuf, addr, cnt) < 0)
			comerrno(usalp->scmd->ux_errno,
					"Cannot write destination disk\n");
	}
	fprintf(stderr, "addr: %8ld\n", addr);
	msec = prstats();
	if (msec == 0)		/* Avoid division by zero */
		msec = 1;
	fprintf(stderr, "Wrote %.2f kB at %.1f kB/sec.\n",
		(double)(addr - start)/(1024.0/usalp->cap->c_bsize),
		(double)((addr - start)/(1024.0/usalp->cap->c_bsize)) / (0.001*msec));
}

static int
choice(int n)
{
#if	defined(HAVE_DRAND48)
	extern	double	drand48(void);

	return (drand48() * n);
#else
#	if	defined(HAVE_RAND)
	extern	int	rand(void);

	return (rand() % n);
#	else
	return (0);
#	endif
#endif
}

static void
ra(SCSI *usalp)
{
/*	char	filename[512];*/
	FILE	*f;
/*	long	addr = 0L;*/
/*	long	cnt;*/
/*	long	end;*/
/*	int	msec;*/
/*	int	start;*/
/*	int	err = 0;*/

	select_secsize(usalp, 2352);
	read_capacity(usalp);
	print_capacity(usalp, stderr);
	fillbytes(Sbuf, 50*2352, 0);
	if (read_g1(usalp, Sbuf, 0, 50) < 0)
		errmsg("read CD\n");
	f = fileopen("DDA", "wctb");
/*	filewrite(f, Sbuf, 50 * 2352 - usal_getresid(usalp));*/
	filewrite(f, Sbuf, 50 * 2352);
	fclose(f);
}

#define	g5x_cdblen(cdb, len)	((cdb)->count[0] = ((len) >> 16L)& 0xFF,\
				(cdb)->count[1] = ((len) >> 8L) & 0xFF,\
				(cdb)->count[2] = (len) & 0xFF)

int
read_da(SCSI *usalp, caddr_t bp, long addr, int cnt, int framesize, int subcode)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	if (usalp->cap->c_bsize <= 0)
		raisecond("capacity_not_set", 0L);

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt*framesize;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G5_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g5_cdb.cmd = 0xd8;
	scmd->cdb.g5_cdb.lun = usal_lun(usalp);
	g5_cdbaddr(&scmd->cdb.g5_cdb, addr);
	g5_cdblen(&scmd->cdb.g5_cdb, cnt);
	scmd->cdb.g5_cdb.res10 = subcode;

	usalp->cmdname = "read_da";

	return (usal_cmd(usalp));
}

int
read_cd(SCSI *usalp, caddr_t bp, long addr, int cnt, int framesize, int data, 
		  int subch)
{
	register struct	usal_cmd	*scmd = usalp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt*framesize;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G5_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g5_cdb.cmd = 0xBE;
	scmd->cdb.g5_cdb.lun = usal_lun(usalp);
	scmd->cdb.g5_cdb.res = 0;	/* expected sector type field ALL */
	g5_cdbaddr(&scmd->cdb.g5_cdb, addr);
	g5x_cdblen(&scmd->cdb.g5_cdb, cnt);

	scmd->cdb.g5_cdb.count[3] = data & 0xFF;
	scmd->cdb.g5_cdb.res10 = subch & 0x07;

	usalp->cmdname = "read_cd";

	return (usal_cmd(usalp));
}

static void
oldmode(SCSI *usalp, int *errp, int *retrp)
{
	Uchar	mode[0x100];
	Uchar	cmode[0x100];
	Uchar	*p;
	int	i;
	int	len;

	fillbytes(mode, sizeof (mode), '\0');
	fillbytes(cmode, sizeof (cmode), '\0');

	if (!get_mode_params(usalp, 0x01, "CD error recovery parameter",
			mode, (Uchar *)0, (Uchar *)cmode, (Uchar *)0, &len)) {
		return;
	}
	if (xdebug)
		usal_prbytes("Mode Sense Data", mode, len);

	mode[0] = 0;
	mode[2] = 0; /* ??? ist manchmal 0x80 */
	p = mode;
	p += mode[3] + 4;
	*p &= 0x3F;

	if (xdebug)
		usal_prbytes("Mode page 1:", p, 0x10);

	i = p[2];
	if (errp != NULL)
		*errp = i;

	i = p[3];
	if (retrp != NULL)
		*retrp = i;
}

static void
domode(SCSI *usalp, int err, int retr)
{
	Uchar	mode[0x100];
	Uchar	cmode[0x100];
	Uchar	*p;
	int	i;
	int	len;

	fillbytes(mode, sizeof (mode), '\0');
	fillbytes(cmode, sizeof (cmode), '\0');

	if (!get_mode_params(usalp, 0x01, "CD error recovery parameter",
			mode, (Uchar *)0, (Uchar *)cmode, (Uchar *)0, &len)) {
		return;
	}
	if (xdebug || (err == -1 && retr == -1)) {
		usal_prbytes("Mode Sense Data", mode, len);
	}

	mode[0] = 0;
	mode[2] = 0; /* ??? ist manchmal 0x80 */
	p = mode;
	p += mode[3] + 4;
	*p &= 0x3F;

	if (xdebug || (err == -1 && retr == -1))
		usal_prbytes("Mode page 1:", p, 0x10);

	i = p[2];
	if (err == -1) {
		getint("Error handling? ", &i, 0, 255);
		p[2] = i;
	} else {
		if (xdebug)
			fprintf(stderr, "Error handling set from %02X to %02X\n",
		p[2], err);
		p[2] = err;
	}

	i = p[3];
	if (retr == -1) {
		getint("Retry count? ", &i, 0, 255);
		p[3] = i;
	} else {
		if (xdebug)
			fprintf(stderr, "Retry count set from %d to %d\n",
		p[3] & 0xFF, retr);
		p[3] = retr;
	}

	if (xdebug || (err == -1 && retr == -1))
		usal_prbytes("Mode Select Data", mode, len);
	mode_select(usalp, mode, len, 0, usalp->inq->data_format >= 2);
}


/*--------------------------------------------------------------------------*/
static	void	qpto96(Uchar *sub, Uchar *subq, int dop);
/*EXPORT	void	qpto96		__PR((Uchar *sub, Uchar *subq, int dop));*/
/*
 * Q-Sub auf 96 Bytes blähen und P-Sub addieren
 *
 * OUT: sub, IN: subqptr
 */
static void
/*EXPORT void*/
qpto96(Uchar *sub, Uchar *subqptr, int dop)
{
	Uchar	tmp[16];
	Uchar	*p;
	int	c;
	int	i;

	if (subqptr == sub) {
		movebytes(subqptr, tmp, 12);
		subqptr = tmp;
	}
	fillbytes(sub, 96, '\0');

	/* CSTYLED */
	if (dop) for (i = 0, p = sub; i < 96; i++) {
		*p++ |= 0x80;
	}
	for (i = 0, p = sub; i < 12; i++) {
		c = subqptr[i] & 0xFF;
/*printf("%02X\n", c);*/
		if (c & 0x80)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x40)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x20)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x10)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x08)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x04)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x02)
			*p++ |= 0x40;
		else
			p++;
		if (c & 0x01)
			*p++ |= 0x40;
		else
			p++;
	}
}

/*--------------------------------------------------------------------------*/

static void
ovtime(SCSI *usalp)
{
	register int	i;

	usalp->silent++;
	(void) test_unit_ready(usalp);
	usalp->silent--;
	if (test_unit_ready(usalp) < 0)
		return;

	printf("Doing 1000 'TEST UNIT READY' operations.\n");

	if (gettimeofday(&starttime, (struct timezone *)0) < 0)
		comerr("Cannot get start time\n");

	for (i = 1000; --i >= 0; ) {
		(void) test_unit_ready(usalp);

		if (didintr)
			return;
	}

	prstats();

	/*
	 * ATAPI drives do not like seek_g0()
	 */
	usalp->silent++;
	i = seek_g0(usalp, 0L);
	usalp->silent--;

	if (i >= 0) {
		printf("Doing 1000 'SEEK_G0 (0)' operations.\n");

		if (gettimeofday(&starttime, (struct timezone *)0) < 0)
			comerr("Cannot get start time\n");

		for (i = 1000; --i >= 0; ) {
			(void) seek_g0(usalp, 0L);

			if (didintr)
				return;
		}

		prstats();
	}

	usalp->silent++;
	i = seek_g1(usalp, 0L);
	usalp->silent--;
	if (i < 0)
		return;

	printf("Doing 1000 'SEEK_G1 (0)' operations.\n");

	if (gettimeofday(&starttime, (struct timezone *)0) < 0)
		comerr("Cannot get start time\n");

	for (i = 1000; --i >= 0; ) {
		(void) seek_g1(usalp, 0L);

		if (didintr)
			return;
	}

	prstats();
}

#define	BAD_INC		16
long	*badsecs;
int	nbad;
int	maxbad;

static void
add_bad(long addr)
{
	if (maxbad == 0) {
		maxbad = BAD_INC;
		badsecs = malloc(maxbad * sizeof (long));
		if (badsecs == NULL)
			comerr("No memory for bad sector list\n.");
	}
	if (nbad >= maxbad) {
		maxbad += BAD_INC;
		badsecs = realloc(badsecs, maxbad * sizeof (long));
		if (badsecs == NULL)
			comerr("No memory to grow bad sector list\n.");
	}
	badsecs[nbad++] = addr;
}

static void
print_bad(void)
{
	int	i;

	if (nbad == 0)
		return;

	fprintf(stderr, "Max corected retry count was %d (limited to %d).\n", maxtry, retries);
	fprintf(stderr, "The following %d sector(s) could not be read correctly:\n", nbad);
	for (i = 0; i < nbad; i++)
		fprintf(stderr, "%ld\n", badsecs[i]);
}
