/* @(#)cdrecord.c	1.151 01/11/27 Copyright 1995-2001 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)cdrecord.c	1.151 01/11/27 Copyright 1995-2001 J. Schilling";
#endif
/*
 *	Record data on a CD/CVD-Recorder
 *
 *	Copyright (c) 1995-2002 J. Schilling
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * 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 <stdxlib.h>
#include <fctldefs.h>
#include <errno.h>
#include <timedefs.h>
#ifdef	HAVE_SYS_RESOURCE_H
#include <sys/resource.h>	/* for rlimit */
#endif
#include <statdefs.h>
#include <unixstd.h>
#ifdef	HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#include <strdefs.h>
#include <utypes.h>
#include <intcvt.h>
#include <signal.h>
#include <schily.h>

#include <scg/scsireg.h>	/* XXX wegen SC_NOT_READY */
#include <scg/scsitransp.h>
#include <scg/scgcmd.h>		/* XXX fuer read_buffer */
#include "scsi_scan.h"

#include "auheader.h"
#include "cdrecord.h"
#include "movesect.h"

static int is_dvd = 0;
static int grace_time = 9;

char	cdr_version[] = "1.11a15";

/*
 * Map toc/track types into names.
 */
char	*toc2name[] = {
		"CD-DA",
		"CD-ROM",
		"CD-ROM XA mode 1",
		"CD-ROM XA mode 2",
		"CD-I",
		"Illegal toc type 5",
		"Illegal toc type 6",
		"Illegal toc type 7",
};

/*
 * Map sector types into names.
 */
char	*st2name[] = {
		"Illegal sector type 0",
		"CD-ROM mode 1",
		"CD-ROM mode 2",
		"Illegal sector type 3",
		"CD-DA without preemphasis",
		"CD-DA with preemphasis",
		"Illegal sector type 6",
		"Illegal sector type 7",
};

/*
 * Map data block types into names.
 */
char	*db2name[] = {
		"Raw (audio)",
		"Raw (audio) with P/Q sub channel",
		"Raw (audio) with P/W packed sub channel",
		"Raw (audio) with P/W raw sub channel",
		"Reserved mode 4",
		"Reserved mode 5",
		"Reserved mode 6",
		"Vendor unique mode 7",
		"CD-ROM mode 1",
		"CD-ROM mode 2",
		"CD-ROM XA mode 2 form 1",
		"CD-ROM XA mode 2 form 1 (with subheader)",
		"CD-ROM XA mode 2 form 2",
		"CD-ROM XA mode 2 form 1/2/mix",
		"Reserved mode 14",
		"Vendor unique mode 15",
};

int		debug;		/* print debug messages */
LOCAL	int	kdebug;		/* print kernel debug messages */
LOCAL	int	scsi_verbose;	/* SCSI verbose flag */
LOCAL	int	silent;		/* SCSI silent flag */
int		lverbose;	/* local verbose flag */
int		xdebug;		/* extended debug flag */

char	*buf;			/* The transfer buffer */
long	bufsize;		/* The size of the transfer buffer */

BOOL	isgui = FALSE;
int	didintr;
char	*driveropts;

struct timeval	starttime;
struct timeval	stoptime;
struct timeval	fixtime;

static	long	fs = -1L;	/* fifo (ring buffer) size */

EXPORT	int 	main		__PR((int ac, char **av));
LOCAL	void	usage		__PR((int));
LOCAL	void	blusage		__PR((int));
LOCAL	void	intr		__PR((int sig));
LOCAL	void	intfifo		__PR((int sig));
LOCAL	void	exscsi		__PR((int excode, void *arg));
LOCAL	void	excdr		__PR((int excode, void *arg));
EXPORT	int	read_buf	__PR((int f, char *bp, int size));
EXPORT	int	fill_buf	__PR((int f, track_t *trackp, long secno,
							char *bp, int size));
EXPORT	int	get_buf		__PR((int f, track_t *trackp, long secno,
							char **bpp, int size));
EXPORT	int	write_secs	__PR((SCSI *scgp, cdr_t *dp, char *bp,
						long startsec, int bytespt,
						int secspt, BOOL islast));
LOCAL	int	write_track_data __PR((SCSI *scgp, cdr_t *, int , track_t *));
EXPORT	int	pad_track	__PR((SCSI *scgp, cdr_t *dp,
				     int track, track_t *trackp,
				     long startsec, Llong amt,
				     BOOL dolast, Llong *bytesp));
EXPORT	int	write_buf	__PR((SCSI *scgp, cdr_t *dp,
				     int track, track_t *trackp,
				     char *bp, long startsec, Llong amt,
				     int secsize,
				     BOOL dolast, Llong *bytesp));
LOCAL	void	printdata	__PR((int, track_t *));
LOCAL	void	printaudio	__PR((int, track_t *));
LOCAL	void	checkfile	__PR((int, track_t *));
LOCAL	int	checkfiles	__PR((int, track_t *));
LOCAL	void	setleadinout	__PR((int, track_t *));
LOCAL	void	setpregaps	__PR((int, track_t *));
LOCAL	long	checktsize	__PR((int, track_t *));
LOCAL	void	checksize	__PR((track_t *));
LOCAL	BOOL	checkdsize	__PR((SCSI *scgp, cdr_t *dp, dstat_t *dsp,
					long tsize, int flags));
LOCAL	void	raise_fdlim	__PR((void));
LOCAL	void	gargs		__PR((int, char **, int *, track_t *, char **,
					int *, cdr_t **,
					int *, long *, int *, int *));
LOCAL	void	set_trsizes	__PR((cdr_t *, int, track_t *));
EXPORT	void	load_media	__PR((SCSI *scgp, cdr_t *, BOOL));
EXPORT	void	unload_media	__PR((SCSI *scgp, cdr_t *, int));
EXPORT	void	reload_media	__PR((SCSI *scgp, cdr_t *));
EXPORT	void	set_secsize	__PR((SCSI *scgp, int secsize));
LOCAL	BOOL	do_opc		__PR((SCSI *scgp, cdr_t *, int));
LOCAL	void	check_recovery	__PR((SCSI *scgp, cdr_t *, int));
	void	audioread	__PR((SCSI *scgp, cdr_t *, int));
LOCAL	void	print_msinfo	__PR((SCSI *scgp, cdr_t *));
LOCAL	void	print_toc	__PR((SCSI *scgp, cdr_t *));
LOCAL	void	print_track	__PR((int, long, struct msf *, int, int, int));
LOCAL	void	prtimediff	__PR((const char *fmt,
					struct timeval *start,
					struct timeval *stop));
#if !defined(HAVE_SYS_PRIOCNTL_H)
LOCAL	int	rt_raisepri	__PR((int));
#endif
EXPORT	void	raisepri	__PR((int));
LOCAL	void	wait_input	__PR((void));
LOCAL	void	checkgui	__PR((void));
LOCAL	Llong	number		__PR((char* arg, int* retp));
EXPORT	int	getnum		__PR((char* arg, long* valp));
EXPORT	int	getllnum	__PR((char *arg, Llong* lvalp));
LOCAL	int	getbltype	__PR((char* optstr, long *typep));
LOCAL	void	print_wrmodes	__PR((cdr_t *dp));
LOCAL	BOOL	check_wrmode	__PR((cdr_t *dp, int wmode, int tflags));

struct exargs {
	SCSI	*scgp;
	cdr_t	*dp;
	int	old_secsize;
	int	flags;
	int	exflags;
} exargs;

EXPORT int main(int ac, char **av)
{
	char	*dev = NULL;
	int	timeout = 40;	/* Set default timeout to 40s CW-7502 is slow*/
	int	speed = 1;
	long	flags = 0L;
	int	toctype = -1;
	int	blanktype = 0;
	int	i;
	int	tracks = 0;
	int	trackno;
	long	tsize;
	track_t	track[MAX_TRACK+2];	/* Max tracks + track 0 + track AA */
	cdr_t	*dp = (cdr_t *)0;
	dstat_t	ds;
	long	startsec = 0L;
	int	errs = 0;
	SCSI	*scgp = NULL;
	char	errstr[80];

#ifdef __EMX__ 
	/* This gives wildcard expansion with Non-Posix shells with EMX */ 
	_wildcard(&ac, &av); 
#endif 
	save_args(ac, av);

	memset(track, 0, sizeof(track));
	raise_fdlim();
	gargs(ac, av, &tracks, track, &dev, &timeout, &dp, &speed, &flags,
							&toctype, &blanktype);
	if (toctype < 0)
		comerrno(EX_BAD, "Internal error: Bad TOC type.\n");

	/*
	 * The following kludge is ugly, but required since the cdrecord
	 * code below may not be modified. :/
	 */
	i = set_cdrcmds("mmc_mdvd", (cdr_t **)NULL);
	if ((flags & F_MSINFO) == 0 || lverbose || flags & F_VERSION) {
		printf("dvdrtools v" VERSION "\n"
		       "Portions (C) 2002-2006 Ark Linux <bero@arklinux.org>\n"
		       "This program is free software; you can redistribute it and/or modify it under\n"
		       "the terms of the GNU General Public License as published by the Free Software\n"
		       "Foundation; either version 2, or (at your option) any later version.\n\n"
		       "This program is distributed in the hope that it will be useful, but WITHOUT ANY\n"
		      "WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\n"
		      "A PARTICULAR PURPOSE.  See the GNU General Public License for more details.\n\n"
		      "You should have received a copy of the GNU General Public License along with\n"
		      "this program; see the file COPYING.  If not, write to the Free Software\n"
		      "Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.\n"
		       "Based on:\n");
		printf("Cdrecord %s (%s-%s-%s) Copyright (C) 1995-2001 Jörg Schilling\n",
								cdr_version,
								HOST_CPU, HOST_VENDOR, HOST_OS);

	}
	if (flags & F_VERSION)
		exit(0);

	checkgui();

	if (debug || lverbose) {
		printf("TOC Type: %d = %s\n",
			toctype, toc2name[toctype & TOC_MASK]);
	}

	if ((flags & (F_MSINFO|F_TOC|F_PRATIP|F_FIX|F_VERSION|F_CHECKDRIVE|F_INQUIRY|F_SCANBUS|F_RESET)) == 0) {
		/*
		 * Try to lock us im memory (will only work for root)
		 * but you need access to root anyway to use /dev/scg?
		 */
#if defined(HAVE_MLOCKALL) || defined(_POSIX_MEMLOCK)
		if (mlockall(MCL_CURRENT|MCL_FUTURE) < 0) {
			errmsg("WARNING: Cannot do mlockall(2).\n");
			errmsgno(EX_BAD, "WARNING: This causes a high risk for buffer underruns.\n");
		}
#endif

		raisepri(0); /* max priority */
		init_fifo(fs);
	}

	if ((flags & F_WAITI) != 0) {
		if (lverbose)
			printf("Waiting for data on stdin...\n");
		wait_input();
	}


	/*
	 * 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 ((scgp = scg_open(dev, errstr, sizeof(errstr),
				debug, (flags & F_MSINFO) == 0 || lverbose)) == (SCSI *)0) {
			errmsg("%s%sCannot open SCSI driver.\n", errstr, errstr[0]?". ":"");
			comerrno(EX_BAD, "For possible targets try 'cdrecord -scanbus'. Make sure you are root.\n");
	}
	scg_settimeout(scgp, timeout);
	scgp->verbose = scsi_verbose;
	scgp->silent = silent;
	scgp->debug = debug;
	scgp->kdebug = kdebug;
	scgp->cap->c_bsize = DATA_SEC_SIZE;


	if ((flags & F_MSINFO) == 0 || lverbose) {
		char	*vers;
		char	*auth;

		/*
		 * Warning: you are not allowed to modify or to remove this 
		 * version checking code!
		 */
		vers = scg_version(0, SCG_VERSION);
		auth = scg_version(0, SCG_AUTHOR);
		printf("Using libscg version '%s-%s'\n", auth, vers);
		if (auth == 0 || strcmp("schily", auth) != 0) {
			errmsgno(EX_BAD,
			"Warning: using inofficial version of libscg (%s-%s '%s').\n",
				auth, vers, scg_version(0, SCG_SCCS_ID));
		}

		vers = scg_version(scgp, SCG_VERSION);
		auth = scg_version(scgp, SCG_AUTHOR);
		if (lverbose > 1)
			error("Using libscg transport code version '%s-%s'\n", auth, vers);
		if (auth == 0 || strcmp("schily", auth) != 0) {
			errmsgno(EX_BAD,
			"Warning: using inofficial libscg transport code version (%s-%s '%s').\n",
				auth, vers, scg_version(scgp, SCG_SCCS_ID));
		}

		vers = scg_version(scgp, SCG_RVERSION);
		auth = scg_version(scgp, SCG_RAUTHOR);
		if (lverbose > 1 && vers && auth)
			error("Using remote transport code version '%s-%s'\n", auth, vers);
		if (auth != 0 && strcmp("schily", auth) != 0) {
			errmsgno(EX_BAD,
			"Warning: using inofficial remote transport code version (%s-%s '%s').\n",
				auth, vers, scg_version(scgp, SCG_RSCCS_ID));
		}
	}
	if (lverbose && driveropts)
		printf("Driveropts: '%s'\n", driveropts);

	bufsize = scg_bufsize(scgp, CDR_BUF_SIZE);
	if (debug)
		error("SCSI buffer size: %ld\n", bufsize);
	if ((buf = (char*)scg_getbuf(scgp, bufsize)) == NULL)
		comerr("Cannot get SCSI I/O buffer.\n");

	if ((flags & F_SCANBUS) != 0) {
		select_target(scgp, stdout);
		exit(0);
	}
	if ((flags & F_RESET) != 0) {
		if (scg_reset(scgp, SCG_RESET_NOP) < 0)
			comerr("Cannot reset (OS does not implement reset).\n");
		if (scg_reset(scgp, SCG_RESET_TGT) >= 0)
			exit(0);
		if (scg_reset(scgp, SCG_RESET_BUS) < 0)
			comerr("Cannot reset target.\n");
		exit(0);
	}
	/*
	 * First try to check which type of SCSI device we
	 * have.
	 */
	if (debug || lverbose)
		printf("atapi: %d\n", scg_isatapi(scgp));
	scgp->silent++;
	test_unit_ready(scgp);	/* eat up unit attention */
	scgp->silent--;
	if (!do_inquiry(scgp, (flags & F_MSINFO) == 0 || lverbose)) {
		errmsgno(EX_BAD, "Cannot do inquiry for CD/DVD-Recorder.\n");
		if (unit_ready(scgp))
			errmsgno(EX_BAD, "The unit seems to be hung and needs power cycling.\n");
		exit(EX_BAD);
	}

	if ((flags & F_PRCAP) != 0) {
		print_capabilities(scgp);
		exit(0);
	}
	if ((flags & F_INQUIRY) != 0)
		exit(0);

	if (dp == (cdr_t *)NULL) {	/* No driver= option specified */
		dp = get_cdrcmds(scgp);
	} else if (!is_unknown_dev(scgp) && dp != get_cdrcmds(scgp)) {
		errmsgno(EX_BAD, "WARNING: Trying to use other driver on known device.\n");
	}

	if (!is_cddrive(scgp))
		comerrno(EX_BAD, "Sorry, no CD/DVD-Drive found on this target.\n");
	if (dp == (cdr_t *)0)
		comerrno(EX_BAD, "Sorry, no supported CD/DVD-Recorder found on this target.\n");
	{
		cdr_t	*ndp;

		ndp = (cdr_t*)malloc(sizeof(cdr_t));
		if (ndp == NULL)
			comerr("Cannot allocate memory for driver structure.\n");
		memcpy(ndp, dp, sizeof(cdr_t));
		dp = ndp;
		dp->cdr_flags |= CDR_ALLOC;
		dp->cdr_cmdflags = flags;
	}

	if ((flags & (F_MSINFO|F_TOC|F_LOAD|F_EJECT)) == 0 || tracks > 0) {
		BOOL	is_cdwr = FALSE;
		BOOL	is_dvdwr = FALSE;

		if ((dp->cdr_flags & CDR_ISREADER) != 0) {
			errmsgno(EX_BAD,
			"Sorry, no CD/DVD-Recorder or unsupported CD/DVD-Recorder found on this target.\n");
		}

		if (!is_mmc(scgp, &is_cdwr, &is_dvdwr))
			is_cdwr = TRUE;			/* If it is not MMC, it must be a CD writer */

		if (!is_cdwr && !is_dvdwr)
			exit(EX_BAD);
	}

	if ((*dp->cdr_attach)(scgp, dp) != 0)
		comerrno(EX_BAD, "Cannot attach driver for CD/DVD-Recorder.\n");

	exargs.scgp	   = scgp;
	exargs.dp	   = dp;
	exargs.old_secsize = -1;
	exargs.flags	   = flags;

	if ((flags & F_MSINFO) == 0 || lverbose) {
		printf("Using %s (%s).\n", dp->cdr_drtext, dp->cdr_drname);
		printf("Driver flags   : ");
		if ((dp->cdr_flags & CDR_SWABAUDIO) != 0)
			printf("SWABAUDIO ");
		if ((dp->cdr_flags & CDR_BURNFREE) != 0)
			printf("BURNFREE ");
		printf("\n");
		print_wrmodes(dp);
	}
	scgp->silent++;
	if ((debug || lverbose)) {
		tsize = -1;
		if ((*dp->cdr_buffer_cap)(scgp, &tsize, (long *)0) < 0 || tsize < 0) {
			if (read_buffer(scgp, buf, 4, 0) >= 0)
				tsize = a_to_u_4_byte(buf);
		}
		if (tsize > 0) {
			printf("Drive buf size : %lu = %lu KB\n",
						tsize, tsize >> 10);
		}
	}
	scgp->silent--;

	if (tracks > 0 && (debug || lverbose))
		printf("FIFO size      : %lu = %lu KB\n", fs, fs >> 10);

	if ((flags & F_CHECKDRIVE) != 0)
		exit(0);

	if (tracks == 0 && (flags & (F_FIX|F_BLANK)) == 0 && (flags & F_EJECT) != 0) {
		/*
		 * Do not check if the unit is ready here to allow to open
		 * an empty unit too.
		 */
		unload_media(scgp, dp, flags);
		exit(0);
	}

	flush();
	if (tracks > 1)
		sleep(2);	/* Let the user watch the inquiry messages */

	if (tracks > 0 && !check_wrmode(dp, flags, track[1].flags))
		comerrno(EX_BAD, "Illegal write mode for this drive.\n");

	setleadinout(tracks, track);
	set_trsizes(dp, tracks, track);
	setpregaps(tracks, track);
	checkfiles(tracks, track);
	tsize = checktsize(tracks, track);
	do_cue(tracks, track, 0);
	do_leadin(tracks, track);


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

	if ((flags & F_FORCE) == 0)
		load_media(scgp, dp, TRUE);

	if ((flags & F_LOAD) != 0) {
		scgp->silent++;			/* silently	     */
		scsi_prevent_removal(scgp, 0);	/* allow manual open */
		scgp->silent--;			/* if load failed... */
		exit(0);			/* we did not change status */
	}
	exargs.old_secsize = sense_secsize(scgp, 1);
	if (exargs.old_secsize < 0)
		exargs.old_secsize = sense_secsize(scgp, 0);
	if (debug)
		printf("Current Secsize: %d\n", exargs.old_secsize);
	scgp->silent++;
	read_capacity(scgp);
	scgp->silent--;
	if (exargs.old_secsize < 0)
		exargs.old_secsize = scgp->cap->c_bsize;
	if (exargs.old_secsize != scgp->cap->c_bsize)
		errmsgno(EX_BAD, "Warning: blockdesc secsize %d differs from cap secsize %d\n",
				exargs.old_secsize, scgp->cap->c_bsize);

	if (lverbose)
		printf("Current Secsize: %d\n", exargs.old_secsize);

	if (exargs.old_secsize > 0 && exargs.old_secsize != DATA_SEC_SIZE) {
		/*
		 * Some drives (e.g. Plextor) don't like to write correctly
		 * in DAO mode if the sector size is set to 512 bytes.
		 * In addition, cdrecord -msinfo will not work properly
		 * if the sector size is not 2048 bytes.
		 */
		set_secsize(scgp, DATA_SEC_SIZE);
	}

	/*
	 * Is this the right place to do this ?
	 */
	check_recovery(scgp, dp, flags);

/*audioread(dp, flags);*/
/*unload_media(scgp, dp, flags);*/
/*return 0;*/
	memset(&ds, 0, sizeof(ds));
	if (flags & F_WRITE)
		ds.ds_cdrflags = RF_WRITE;
	if (flags & F_BLANK)
		ds.ds_cdrflags = RF_BLANK;
	if (flags & F_PRATIP) {
		lverbose++;			/* XXX Hack */
	}
	if ((*dp->cdr_getdisktype)(scgp, dp, &ds) < 0) {
		errmsgno(EX_BAD, "Cannot get disk type.\n");
		if ((flags & F_FORCE) == 0)
			comexit(EX_BAD);
	}
	if (flags & F_PRATIP) {
		lverbose--;			/* XXX Hack */
		comexit(0);
	}
	/*
	 * The next actions should depend on the disk type.
	 */

	/*
	 * Dirty hack!
	 * At least MMC drives will not return the next writable
	 * address we expect when the drive's write mode is set
	 * to DAO/SAO. We need this address for mkisofs and thus
	 * it must be the first user accessible sector and not the
	 * first sector of the pregap. Set_speed_dummy() witha a
	 * 'speedp' f 0 sets the write mode to TAO on MMC drives.
	 *
	 * We set TAO unconditionally to make checkdsize() work
	 * currectly in DAO mode too.
	 *
	 * XXX The ACER drive:
	 * XXX Vendor_info    : 'ATAPI   '
	 * XXX Identifikation : 'CD-R/RW 8X4X32  '
	 * XXX Revision       : '5.EW'
	 * XXX Will not return from -dummy to non-dummy without
	 * XXX opening the tray.
	 */
	scgp->silent++;
	(*dp->cdr_set_speed_dummy)(scgp, 0, flags & F_DUMMY);
	scgp->silent--;

	if (flags & F_MSINFO) {
		print_msinfo(scgp, dp);
		comexit(0);
	}
	if (flags & F_TOC) {
		print_toc(scgp, dp);
		comexit(0);
	}

#ifdef	XXX
	if ((*dp->cdr_check_session)() < 0) {
		comexit(EX_BAD);
	}
#endif
	if (tsize == 0) {
		if (tracks > 0) {
			errmsgno(EX_BAD,
			"WARNING: Total disk size unknown. Data may not fit on disk.\n");
		}
	} else if (!checkdsize(scgp, dp, &ds, tsize, flags)) {
		comexit(EX_BAD);
	}
	if (tracks > 0 && fs > 0l) {
		/*
		 * Start the extra process needed for improved buffering.
		 */
		if (!init_faio(tracks, track, bufsize))
			fs = 0L;
		else
			on_comerr(excdr, &exargs);
	}
	if ((*dp->cdr_set_speed_dummy)(scgp, &speed, flags & F_DUMMY) < 0) {
		errmsgno(EX_BAD, "Cannot set speed/dummy.\n");
		if ((flags & F_FORCE) == 0)
			comexit(EX_BAD);
	}
	if ((flags & (F_BLANK|F_FORCE)) == (F_BLANK|F_FORCE)) {
		wait_unit_ready(scgp, 120);
		scsi_blank(scgp, 0L, blanktype, FALSE);
		comexit(0);
	}
	if(grace_time>=0 && !isgui) {
		/*
		 * Last chance to quit!
		 */
		printf("Starting to write CD/DVD at speed %d in %s mode for %s session.\n",
			speed,
			(flags & F_DUMMY) ? "dummy" : "write",
			(flags & F_MULTI) ? "multi" : "single");
		printf("Last chance to quit, starting %s write in 9 seconds.",
			(flags & F_DUMMY)?"dummy":"real");
		flush();
		signal(SIGINT, intr);
		/*
		 * Note to people who like to change this: I am geting patch requests
		 * for an option to reduce the grace_time two times a year. I am not
		 * willing to change things with respect to grace_time because it would
		 * not reduce the needed time in a significant amount and because it
		 * would break other things.
		 */
		for (i = grace_time; --i >= 0;) {
			sleep(1);
			if (didintr) {
				printf("\n");
				excdr(SIGINT, &exargs);
				signal(SIGINT, SIG_DFL);
				kill(getpid(), SIGINT);
				/*
				 * In case kill() did not work ;-)
				 */
				errs++;
				goto restore_it;
			}
			printf("\b\b\b\b\b\b\b\b\b\b%d seconds.", i);
			flush();
		}
		printf(" Operation starts.");
	}
	flush();
	signal(SIGINT, SIG_DFL);
	signal(SIGINT, intfifo);
	signal(SIGTERM, intfifo);
	printf("\n");
	if (tracks > 0 && fs > 0l) {
		/*
		 * Wait for the read-buffer to become full.
		 * This should be take no extra time if the input is a file.
		 * If the input is a pipe (e.g. mkisofs) this can take a
		 * while. If mkisofs dumps core before it starts writing,
		 * we abort before the writing process started.
		 */
		if (!await_faio()) {
			comerrno(EX_BAD, "Input buffer error, aborting.\n");
		}
	}
	wait_unit_ready(scgp, 120);
	if (gettimeofday(&starttime, (struct timezone *)0) < 0)
		errmsg("Cannot get start time\n");

	/*
	 * Blank the media if we were requested to do so
	 */
	if (flags & F_BLANK) {
		/*
		 * Do not abort if OPC failes. Just give it a chance
		 * for better laser power calibration than without OPC.
		 *
		 * Ricoh drives return with a vendor unique sense code.
		 * This is most likely because they refuse to do OPC
		 * on a non blank media.
		 */
		scgp->silent++;
		do_opc(scgp, dp, flags);
		scgp->silent--;
		wait_unit_ready(scgp, 120);
		if (gettimeofday(&starttime, (struct timezone *)0) < 0)
			errmsg("Cannot get start time\n");

		if ((*dp->cdr_blank)(scgp, 0L, blanktype) < 0) {
			comerrno(EX_BAD, "Cannot blank disk, aborting.\n");
		}
		if (gettimeofday(&fixtime, (struct timezone *)0) < 0)
			errmsg("Cannot get blank time\n");
		if (lverbose)
			prtimediff("Blanking time: ", &starttime, &fixtime);

		if (!wait_unit_ready(scgp, 240) || tracks == 0) {
			comexit(0);
		}
		/*
		 * Reset start time so we will not see blanking time and
		 * writing time counted together.
		 */
		if (gettimeofday(&starttime, (struct timezone *)0) < 0)
			errmsg("Cannot get start time\n");
	}
	/*
	 * Get the number of the next recordable track.
	 */
	scgp->silent++;
	if (read_tochdr(scgp, dp, NULL, &trackno) < 0) {
		trackno = 0;
	}
	scgp->silent--;
	
	/* If it is DVD, the information in TOC is fabricated :)
	 The real information is from read disk info command*/
	if((ds.ds_disktype&DT_DVD) && (ds.ds_trlast>0)){
	    is_dvd=1;
	    trackno=ds.ds_trlast-1;
	    printf("trackno=%d\n",trackno);
	}


	for (i = 1; i <= tracks; i++) {
		track[i].trackno = i + trackno;
	}
	trackno++;
	track[0].trackno = trackno;	/* XXX Hack for TEAC fixate */

	/*
	 * Now we actually start writing to the CD/DVD.
	 * XXX Check total size of the tracks and remaining size of disk.
	 */
	if ((*dp->cdr_open_session)(scgp, dp, tracks, track, toctype, flags & F_MULTI) < 0) {
		comerrno(EX_BAD, "Cannot open new session.\n");
	}
	if (!do_opc(scgp, dp, flags))
		comexit(EX_BAD);

	/*
	 * As long as open_session() will do nothing but
	 * set up parameters, we may leave fix_it here.
	 * I case we have to add an open_session() for a drive
	 * that wants to do something that modifies the disk
	 * We have to think about a new solution.
	 */
	if (flags & F_FIX)
		goto fix_it;

	if (flags & F_SAO) {
		if (debug || lverbose) {
			printf("Sending CUE sheet...\n");
			flush();
		}
		if ((*dp->cdr_send_cue)(scgp, tracks, track) < 0) {
			comerrno(EX_BAD, "Cannot send CUE sheet.\n");
		}

		(*dp->cdr_next_wr_address)(scgp, 0, &track[0], &startsec);
		if (track[0].flags & TI_TEXT) {
			startsec = ds.ds_first_leadin;
			printf("SAO startsec: %ld\n", startsec);
		} else if (startsec <= 0 && startsec != -150) {
			errmsgno(EX_BAD, "WARNING: Drive returns wrong startsec (%ld) using -150\n",
					startsec);
			startsec = -150;
		}
		if (debug)
			printf("SAO startsec: %ld\n", startsec);

		if (track[0].flags & TI_TEXT) {
			if (startsec > 0)
				comerrno(EX_BAD, "CD-Text must be in first session.\n");
			if (debug || lverbose)
				printf("Writing lead-in...\n");
			write_cdtext(scgp, dp, startsec);
		} else for (i = 1; i <= tracks; i++) {
			track[i].trackstart += startsec +150;
		}
#ifdef	XXX
		if (debug || lverbose)
			printf("Writing lead-in...\n");

		pad_track(scgp, dp, 1, &track[1], -150, (Llong)0,
					FALSE, 0);
#endif
	}
	if (flags & F_RAW) {
		(*dp->cdr_next_wr_address)(scgp, 0, &track[0], &startsec);
		if (startsec > -4500) {
			/*
			 * There must be at least 1 minute lead-in.
			 */
			errmsgno(EX_BAD, "WARNING: Drive returns wrong startsec (%ld) using %ld from ATIP\n",
					startsec, ds.ds_first_leadin);
			startsec = ds.ds_first_leadin;
		}
		if (startsec > -4500) {
			comerrno(EX_BAD, "Illegal startsec (%ld)\n", startsec);
		}
		if (debug || lverbose)
			printf("Writing lead-in at sector %ld\n", startsec);
		if (write_leadin(scgp, dp, tracks, track, startsec) < 0)
			comerrno(EX_BAD, "Could not write Lead-in.\n");

		if (gettimeofday(&fixtime, (struct timezone *)0) < 0)
			errmsg("Cannot get lead-in write time\n");
		if (lverbose)
			prtimediff("Lead-in write time: ", &starttime, &fixtime);
	}
	/*
	 * Need to set trackno to the real value from
	 * the current disk status.
	 */
	for (i = 1; i <= tracks; i++, trackno++) {
		startsec = 0L;

		/*
		 * trackno is the "real" track number while 'i' is a counter
		 * going from 1 to tracks.
		 */
		if ((*dp->cdr_open_track)(scgp, dp, trackno, &track[i]) < 0) {
			errs++;
			break;
		}

		if ((flags & (F_SAO|F_RAW)) == 0) {
			if ((*dp->cdr_next_wr_address)(scgp, trackno, &track[i], &startsec) < 0) {
				errs++;
				break;
			}
			track[i].trackstart = startsec;
		}
		if (debug || lverbose) {
			printf("Starting new track at sector: %ld\n",
						track[i].trackstart);
			flush();
		}
		if (write_track_data(scgp, dp, trackno, &track[i]) < 0) {
			errs++;
			sleep(5);
			request_sense(scgp);
			(*dp->cdr_close_track)(scgp, trackno, &track[i]);
			break;
		}
		if ((*dp->cdr_close_track)(scgp, trackno, &track[i]) < 0) {
			/*
			 * Check for "Dummy blocks added" message first.
			 */
			if (scg_sense_key(scgp) != SC_ILLEGAL_REQUEST ||
					scg_sense_code(scgp) != 0xB5) {
				errs++;
				break;
			}
		}
	}
fix_it:
	if (gettimeofday(&stoptime, (struct timezone *)0) < 0)
		errmsg("Cannot get stop time\n");
	if (lverbose)
		prtimediff("Writing  time: ", &starttime, &stoptime);

	if (flags & F_RAW) {
		if (lverbose) {
			printf("Writing Leadout...\n");
			flush();
		}
		write_leadout(scgp, dp, tracks, track);
	}
	if ((flags & F_NOFIX) == 0) {
		if (lverbose) {
			printf("Fixating...\n");
			flush();
		}
		if ((*dp->cdr_fixate)(scgp, flags & F_MULTI, flags & F_DUMMY,
				toctype, tracks, track) < 0) {
			/*
			 * Ignore fixating errors in dummy mode.
			 */
			if ((flags & F_DUMMY) == 0)
				errs++;
		}
		if (gettimeofday(&fixtime, (struct timezone *)0) < 0)
			errmsg("Cannot get fix time\n");
		if (lverbose)
			prtimediff("Fixating time: ", &stoptime, &fixtime);
	}
	if ((flags & (F_RAW|F_EJECT)) == F_RAW) {
		/*
		 * Most drives seem to forget to reread the TOC from disk
		 * if they are in RAW mode.
		 */
		scgp->silent++;
		if (read_tochdr(scgp, dp, NULL, NULL) < 0) {
			scgp->silent--;
			reload_media(scgp, dp);
		} else {
			scgp->silent--;
		}
	}

restore_it:
	/*
	 * Try to restore the old sector size and stop FIFO.
	 */
	comexit(errs?-2:0);
	return (0);
}

LOCAL void usage(int excode)
{
	errmsgno(EX_BAD, "Usage: %s [options] track1...trackn\n",
		get_progname());

	error("Options:\n");
	error("\t-version	print version information and exit\n");
	error("\tdev=target	SCSI target to use as CD/DVD-Recorder\n");
	error("\ttimeout=#	set the default SCSI command timeout to #.\n");
	error("\tdebug=#,-d	Set to # or increment misc debug level\n");
	error("\tkdebug=#,kd=#	do Kernel debugging\n");
	error("\t-verbose,-v	increment general verbose level by one\n");
	error("\t-Verbose,-V	increment SCSI command transport verbose level by one\n");
	error("\t-silent,-s	do not print status of failed SCSI commands\n");
	error("\tdriver=name	user supplied driver name, use with extreme care\n");
	error("\tdriveropts=opt	a comma separated list of driver specific options\n");
	error("\t-checkdrive	check if a driver for the drive is present\n");
	error("\t-prcap		print drive capabilities for MMC compliant drives\n");
	error("\t-inq		do an inquiry for the drive and exit\n");
	error("\t-scanbus	scan the SCSI bus and exit\n");
	error("\t-reset		reset the SCSI bus with the cdrecorder (if possible)\n");
	error("\t-overburn	allow to write more than the official size of a medium\n");
	error("\t-ignsize	ignore the known size of a medium (may cause problems)\n");
	error("\t-useinfo	use *.inf files to overwrite audio options.\n");
	error("\tspeed=#		set speed of drive\n");
	error("\tblank=type	blank a CD-RW disc (see blank=help)\n");
#ifdef	FIFO
	error("\tfs=#		Set fifo size to # (0 to disable, default is %ld MB)\n",
							DEFAULT_FIFOSIZE/(1024L*1024L));
#endif
	error("\t-load		load the disk and exit (works only with tray loader)\n");
	error("\t-eject		eject the disk after doing the work\n");
	error("\t-dummy		do everything with laser turned off\n");
	error("\t-msinfo		retrieve multi-session info for mkisofs >= 1.10\n");
	error("\t-toc		retrieve and print TOC/PMA data\n");
	error("\t-atip		retrieve and print ATIP data\n");
	error("\t-multi		generate a TOC that allows multi session\n");
	error("\t		In this case default track type is CD-ROM XA2\n");
	error("\t-fix		fixate a corrupt or unfixated disk (generate a TOC)\n");
	error("\t-nofix		do not fixate disk after writing tracks\n");
	error("\t-waiti		wait until input is available before opening SCSI\n");
	error("\t-force		force to continue on some errors to allow blanking bad disks\n");
	error("\t-dao		Write disk in DAO mode. This option will be replaced in the future.\n");
	error("\t-raw		Write disk in RAW mode. This option will be replaced in the future.\n");
	error("\t-raw96r		Write disk in RAW/RAW96R mode. This option will be replaced in the future.\n");
	error("\t-raw96p		Write disk in RAW/RAW96P mode. This option will be replaced in the future.\n");
	error("\t-raw16		Write disk in RAW/RAW16 mode. This option will be replaced in the future.\n");
	error("\ttsize=#		Length of valid data in next track\n");
	error("\tpadsize=#	Amount of padding for next track\n");
	error("\tpregap=#	Amount of pre-gap sectors before next track\n");
	error("\tdefpregap=#	Amount of pre-gap sectors for all but track #1\n");
	error("\tmcn=text	Set the media catalog number for this CD to 'text'\n");
	error("\tisrc=text	Set the ISRC number for the next track to 'text'\n");
	error("\tindex=list	Set the index list for the next track to 'list'\n");
	error("\t-text		Write CD-Text from information from *.inf files\n");
	error("\ttextfile=name	Set the file with CD-Text data to 'name'\n");

	error("\t-audio		Subsequent tracks are CD-DA audio tracks\n");
	error("\t-data		Subsequent tracks are CD-ROM data mode 1 (default)\n");
	error("\t-mode2		Subsequent tracks are CD-ROM data mode 2\n");
	error("\t-xa1		Subsequent tracks are CD-ROM XA mode 1\n");
	error("\t-xa2		Subsequent tracks are CD-ROM XA mode 2\n");
	error("\t-cdi		Subsequent tracks are CDI tracks\n");
	error("\t-isosize	Use iso9660 file system size for next data track\n");
	error("\t-preemp		Audio tracks are mastered with 50/15 µs preemphasis\n");
	error("\t-nopreemp	Audio tracks are mastered with no preemphasis (default)\n");
	error("\t-copy		Audio tracks have unlimited copy permission\n");
	error("\t-nocopy		Audio tracks may only be copied once for personal use (default)\n");
	error("\t-scms		Audio tracks willl not not have any copy permission at all\n");
	error("\t-pad		Pad data tracks with %d zeroed sectors\n", PAD_SECS);
	error("\t		Pad audio tracks to a multiple of %d bytes\n", AUDIO_SEC_SIZE);
	error("\t-nopad		Do not pad data tracks (default)\n");
	error("\t-shorttrack	Subsequent tracks may be non Red Book < 4 seconds if in DAO mode\n");
	error("\t-noshorttrack	Subsequent tracks must be >= 4 seconds\n");
	error("\t-swab		Audio data source is byte-swapped (little-endian/Intel)\n");
	error("\t-gui		Make output more GUI- (and less human-) readable\n");
	error("\t-delay x	Set delay before writing to x seconds (default: 9)\n");
	error("The type of the first track is used for the toc type.\n");
	error("Currently only form 1 tracks are supported.\n");
	exit(excode);
}

LOCAL void blusage(int ret)
{
	error("Blanking options:\n");
	error("\tall\t\tblank the entire disk\n");
	error("\tdisc\t\tblank the entire disk\n");
	error("\tdisk\t\tblank the entire disk\n");
	error("\tfast\t\tminimally blank the entire disk (PMA, TOC, pregap)\n");
	error("\tminimal\t\tminimally blank the entire disk (PMA, TOC, pregap)\n");
	error("\ttrack\t\tblank a track\n");
	error("\tunreserve\tunreserve a track\n");
	error("\ttrtail\t\tblank a track tail\n");
	error("\tunclose\t\tunclose last session\n");
	error("\tsession\t\tblank last session\n");

	exit(ret);
	/* NOTREACHED */
}

/* ARGSUSED */
LOCAL void intr(int sig)
{
	sig = 0;	/* Fake usage for gcc */

	signal(SIGINT, intr);

	didintr++;
}

LOCAL void intfifo(int sig)
{
	comexit(sig);
}

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

	/*
	 * Try to restore the old sector size.
	 */
	if (exp != NULL && exp->exflags == 0) {

		set_secsize(exp->scgp, exp->old_secsize);
		unload_media(exp->scgp, exp->dp, exp->flags);

		exp->exflags++;	/* Make sure that it only get called once */
	}
}

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

#ifdef	FIFO
	kill_faio();
	wait_faio();
	if (debug || lverbose)
		fifo_stats();
#endif
}

EXPORT int read_buf(int f, char *bp, int size)
{
	char	*p = bp;
	int	amount = 0;
	int	n;

	do {
		do {
			n = read(f, p, size-amount);
		} while (n < 0 && (errno == EAGAIN || errno == EINTR));
		if (n < 0)
			return (n);
		amount += n;
		p += n;

	} while (amount < size && n > 0);
	return (amount);
}

EXPORT int fill_buf(int f, track_t *trackp, long secno, char *bp, int size)
{
	int	amount = 0;
	int	nsecs;
	int	rsize;
	int	rmod;
	int	readoffset = 0;

	nsecs = size / trackp->secsize;
	if (nsecs < trackp->secspt) {
		/*
		 * Clear buffer to prepare for last transfer.
		 * Make sure that a partial sector ends with NULs
		 */
		memset(bp, 0, trackp->secspt * trackp->secsize);
	}

	if (!is_raw(trackp)) {
		amount = read_buf(f, bp, size);
		if (amount != size) {
			if (amount < 0)
				return (amount);
			/*
			 * We got less than expected, clear rest of buf.
			 */
			memset(&bp[amount], 0, size-amount);
		}
		if (is_swab(trackp))
			swabbytes(bp, amount);
		return (amount);
	}

	rsize = nsecs * trackp->isecsize;
	rmod  = size % trackp->secsize;
	if (rmod > 0) {
		rsize += rmod;
		nsecs++;
	}

	readoffset = trackp->dataoff;
	amount = read_buf(f, bp + readoffset, rsize);
	if (is_swab(trackp))
		swabbytes(bp + readoffset, amount);

	if (trackp->isecsize == 2448 && trackp->secsize == 2368)
		subrecodesecs(trackp, (Uchar *)bp, secno, nsecs);

	scatter_secs(trackp, bp + readoffset, nsecs);

	if (amount != rsize) {
		if (amount < 0)
			return (amount);
		/*
		 * We got less than expected, clear rest of buf.
		 */
		memset(&bp[amount], 0, rsize-amount);
		nsecs = amount / trackp->isecsize;
		rmod  = amount % trackp->isecsize;
		amount = nsecs * trackp->secsize;
		if (rmod > 0) {
			nsecs++;
			amount += rmod;
		}
	} else {
		amount = size;
	}
	if ((trackp->sectype & ST_MODE_RAW) == 0) {
		encsectors(trackp, (Uchar *)bp, secno, nsecs);
		fillsubch(trackp, (Uchar *)bp, secno, nsecs);
	} else {
		scrsectors(trackp, (Uchar *)bp, secno, nsecs);
	}
	return (amount);
}

EXPORT int get_buf(int f, track_t *trackp, long secno, char **bpp, int size)
{
	if (fs > 0) {
/*		return (faio_read_buf(f, *bpp, size));*/
		return (faio_get_buf(f, bpp, size));
	} else {
		return (fill_buf(f, trackp, secno, *bpp, size));
	}
}

EXPORT int write_secs(SCSI *scgp, cdr_t *dp, char *bp, long startsec, int bytespt, int secspt, BOOL islast)
{
	int	amount;

again:
	scgp->silent++;
	amount = (*dp->cdr_write_trackdata)(scgp, bp, startsec, bytespt, secspt, islast);
	scgp->silent--;
	if (amount < 0) {
		if (scsi_in_progress(scgp)) {
			usleep(100000);
			goto again;
		}
		return (-1);
	}
	return (amount);
}

LOCAL int write_track_data(SCSI *scgp, cdr_t *dp, int track, track_t *trackp)
{
	int	f;
	int	isaudio;
	long	startsec;
	Llong	bytes_read = 0;
	Llong	bytes	= 0;
	Llong	savbytes = 0;
	int	count;
	Llong	tracksize;
	int	secsize;
	int	secspt;
	int	bytespt;
	int	bytes_to_read;
	long	amount;
	int	pad;
	BOOL	neednl	= FALSE;
	BOOL	islast	= FALSE;
	char	*bp	= buf;
	long bsize;
	long bfree;
/*#define	BCAP*/
	int per = 0;
#ifdef	BCAP
	int oper = -1;
#endif

	scgp->silent++;
	if ((*dp->cdr_buffer_cap)(scgp, &bsize, &bfree) < 0)
		bsize = -1L;
	if (bsize == 0)		/* If we have no (known) buffer, we cannot */
		bsize = -1L;	/* retrieve the buffer fill ratio	   */
	scgp->silent--;


	if (is_packet(trackp))	/* XXX Ugly hack for now */
		return (write_packet_data(scgp, dp, track, trackp));

	f = trackp->f;
	isaudio = is_audio(trackp);
	tracksize = trackp->tracksize;
	startsec = trackp->trackstart;

	secsize = trackp->secsize;
	secspt = trackp->secspt;
	bytespt = secsize * secspt;
	
	pad = !isaudio && is_pad(trackp);	/* Pad only data tracks */

	if (debug) {
		printf("secsize:%d secspt:%d bytespt:%d audio:%d pad:%d\n",
			secsize, secspt, bytespt, isaudio, pad);
	}

	if (lverbose) {
		if(isgui) {
			printf("@%02dT%06d\n", track, tracksize >> 20);
		} else {
			if (tracksize > 0)
				printf("\rTrack %02d:   0 of %4lld MB written.",
				       track, tracksize >> 20);
			else
				printf("\rTrack %02d:   0 MB written.", track);
		}
		flush();
		neednl = TRUE;
	}

	do {
		bytes_to_read = bytespt;
		if (tracksize > 0) {
			if ((tracksize - bytes_read) > bytespt)
				bytes_to_read = bytespt;
			else
				bytes_to_read = tracksize - bytes_read;				
		}
		count = get_buf(f, trackp, startsec, &bp, bytes_to_read);

		if (count < 0)
			comerr("read error on input file\n");
		if (count == 0)
			break;
		bytes_read += count;
		if (tracksize >= 0 && bytes_read >= tracksize) {
			count -= bytes_read - tracksize;
			/*
			 * Paranoia: tracksize is known (trackp->tracksize >= 0)
			 * At this point, trackp->padsize should alway be set
			 * if the tracksize is less than 300 sectors.
			 */
			if (trackp->padsize == 0 &&
			    (is_shorttrk(trackp) || (bytes_read/secsize) >= 300))
				islast = TRUE;
		}

		if (count < bytespt) {
			if (debug) {
				printf("\nNOTICE: reducing block size for last record.\n");
				neednl = FALSE;
			}

			if ((amount = count % secsize) != 0) {
				amount = secsize - amount;
				count += amount;
				printf("\nWARNING: padding up to secsize.\n");
				neednl = FALSE;
			}
			bytespt = count;
			secspt = count / secsize;
			/*
			 * If tracksize is not known (trackp->tracksize < 0)
			 * we may need to set trackp->padsize 
			 * if the tracksize is less than 300 sectors.
			 */
			if (trackp->padsize == 0 &&
			    (is_shorttrk(trackp) || (bytes_read/secsize) >= 300))
				islast = TRUE;
		}

		amount = write_secs(scgp, dp, bp, startsec, bytespt, secspt, islast);
		if (amount < 0) {
			printf("%swrite track data: error after %lld bytes\n",
							neednl?"\n":"", bytes);
			return (-1);
		}
		bytes += amount;
		startsec += amount / secsize;

		if (lverbose && (bytes >= (savbytes + 0x100000))) {
			int	fper;

			if(isgui) {
				printf("@%02d:%06d\n", track, bytes >> 20);
			} else {
				printf("\rTrack %02d: %4lld", track, bytes >> 20);
				if (tracksize > 0)
					printf(" of %4lld MB", tracksize >> 20);
				else
					printf(" MB");
				printf(" written");
				fper = fifo_percent(TRUE);
				if (fper >= 0)
					printf(" (fifo %3d%%)", fper);

				if (bsize > 0) {			/* buffer size known */
					scgp->silent++;
					per = (*dp->cdr_buffer_cap)(scgp, (long *)0, &bfree);
					scgp->silent--;
					if (per >= 0) {
						per = 100*(bsize - bfree) / bsize;
						printf(" [buf %3d%%]", per);
#ifdef	BCAPDBG
						printf(" %3ld %3ld", bsize >> 10, bfree >> 10);
#endif
					}
				}
				printf(".");
			}
			savbytes = (bytes >> 20) << 20;
			flush();
			neednl = TRUE;
		}
#ifdef	BCAP
		if (bsize > 0) {			/* buffer size known */
			(*dp->cdr_buffer_cap)(scgp, (long *)0, &bfree);
			per = 100*(bsize - bfree) / bsize;
			if (per != oper)
				printf("[buf %3d%%] %3ld %3ld\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b",
					per, bsize >> 10, bfree >> 10);
			oper = per;
			flush();
		}
#endif
	} while (tracksize < 0 || bytes_read < tracksize);

	if (!is_shorttrk(trackp) && (bytes / secsize) < 300) {
		/*
		 * If tracksize is not known (trackp->tracksize < 0) or 
		 * for some strange reason we did not set padsize properly
		 * we may need to modify trackp->padsize if
		 * tracksize+padsize is less than 300 sectors.
		 */
		savbytes = roundup(trackp->padsize, secsize);
		if (((bytes+savbytes) / secsize) < 300)
			trackp->padsize = 300 * secsize - bytes;
	}
	if (trackp->padsize) {
		if (neednl) {
			printf("\n");
			neednl = FALSE;
		}
		if ((trackp->padsize >> 20) > 0) {
			neednl = TRUE;
		} else if (lverbose) {
			printf("Track %02d: writing %3lld KB of pad data.\n",
					track, (Llong)(trackp->padsize >> 10));
			neednl = FALSE;
		}
		pad_track(scgp, dp, track, trackp, startsec, trackp->padsize,
					TRUE, &savbytes);
		bytes += savbytes;
		startsec += savbytes / secsize;
	}
	printf("%sTrack %02d: Total bytes read/written: %lld/%lld (%lld sectors).\n",
	       neednl?"\n":"", track, bytes_read, bytes, bytes/secsize);
	flush();
	return 0;
}

EXPORT int pad_track(SCSI *scgp, cdr_t *dp, int track, track_t *trackp, long startsec, Llong amt, BOOL dolast, Llong *bytesp)
{
	Llong	bytes	= 0;
	Llong	savbytes = 0;
	int	secsize;
	int	secspt;
	int	bytespt;
	int	amount;
	BOOL	neednl	= FALSE;
	BOOL	islast	= FALSE;

	secsize = trackp->secsize;
	secspt = trackp->secspt;
	bytespt = secsize * secspt;
	
	memset(buf, 0, bytespt);

	if ((amt >> 20) > 0) {
		printf("\rTrack %02d:   0 of %4lld MB pad written.",
						track, amt >> 20);
		flush();
	}
	do {
		if (amt < bytespt) {
			bytespt = roundup(amt, secsize);
			secspt = bytespt / secsize;	
		}
		if (dolast && (amt - bytespt) <= 0)
			islast = TRUE;

		if (is_raw(trackp)) {
			encsectors(trackp, (Uchar *)buf, startsec, secspt);
			fillsubch(trackp, (Uchar *)buf, startsec, secspt);
		}

		amount = write_secs(scgp, dp, buf, startsec, bytespt, secspt, islast);
		if (amount < 0) {
			printf("%swrite track pad data: error after %lld bytes\n",
							neednl?"\n":"", bytes);
			if (bytesp)
				*bytesp = bytes;
(*dp->cdr_buffer_cap)(scgp, (long *)0, (long *)0);
			return (-1);
		}
		amt -= amount;
		bytes += amount;
		startsec += amount / secsize;

		if (lverbose && (bytes >= (savbytes + 0x100000))) {
			printf("\rTrack %02d: %4lld", track, bytes >> 20);
			savbytes = (bytes >> 20) << 20;
			flush();
			neednl = TRUE;
		}
	} while (amt > 0);

	if (bytesp)
		*bytesp = bytes;
	if (bytes == 0)
		return (0);
	return (bytes > 0 ? 1:-1);
}

#ifdef	USE_WRITE_BUF
EXPORT int write_buf(SCSI *scgp, cdr_t *dp, int track, track_t *trackp, char *bp, long startsec, Llong amt, int secsize, BOOL dolast, Llong *bytesp)
{
	Llong	bytes	= 0;
	Llong	savbytes = 0;
/*	int	secsize;*/
	int	secspt;
	int	bytespt;
	int	amount;
	BOOL	neednl	= FALSE;
	BOOL	islast	= FALSE;

/*	secsize = trackp->secsize;*/
/*	secspt = trackp->secspt;*/

	secspt = bufsize/secsize;
	secspt = min(255, secspt);
	bytespt = secsize * secspt;
	
/*	memset(buf, 0, bytespt);*/

	if ((amt >> 20) > 0) {
		printf("\rTrack %02d:   0 of %3ld MB pad written.",
						track, amt >> 20);
		flush();
	}
	do {
		if (amt < bytespt) {
			bytespt = roundup(amt, secsize);
			secspt = bytespt / secsize;	
		}
		if (dolast && (amt - bytespt) <= 0)
			islast = TRUE;

		amount = write_secs(scgp, dp, bp, startsec, bytespt, secspt, islast);
		if (amount < 0) {
			printf("%swrite track data: error after %ld bytes\n",
							neednl?"\n":"", bytes);
			if (bytesp)
				*bytesp = bytes;
(*dp->cdr_buffer_cap)(scgp, (long *)0, (long *)0);
			return (-1);
		}
		amt -= amount;
		bytes += amount;
		startsec += amount / secsize;

		if (lverbose && (bytes >= (savbytes + 0x100000))) {
			printf("\rTrack %02d: %3ld", track, bytes >> 20);
			savbytes = (bytes >> 20) << 20;
			flush();
			neednl = TRUE;
		}
	} while (amt > 0);

	if (bytesp)
		*bytesp = bytes;
	return (bytes);
}
#endif	/* USE_WRITE_BUF */

LOCAL void printdata(int track, track_t *trackp)
{
	if (trackp->itracksize >= 0) {
		printf("Track %02d: data  %4lld MB        ",
					track, (Llong)(trackp->itracksize >> 20));
	} else {
		printf("Track %02d: data  unknown length",
					track);
	}
	if (trackp->padsize > 0) {
		if ((trackp->padsize >> 20) > 0)
			printf(" padsize: %4lld MB", (Llong)(trackp->padsize >> 20));
		else
			printf(" padsize: %4lld KB", (Llong)(trackp->padsize >> 10));
	}
	if (trackp->pregapsize != 150) {
		printf(" pregapsize: %3ld", trackp->pregapsize);
	}
	if (xdebug)
		printf(" START: %ld SECTORS: %ld INDEX0 %ld",
			trackp->trackstart, trackp->tracksecs, trackp->index0start);
	printf("\n");
}

LOCAL void printaudio(int track, track_t *trackp)
{
	if (trackp->itracksize >= 0) {
		printf("Track %02d: audio %4lld MB (%02d:%02d.%02d) %spreemp%s%s",
			track, (Llong)(trackp->itracksize >> 20),
			minutes(trackp->itracksize),
			seconds(trackp->itracksize),
			hseconds(trackp->itracksize),
			is_preemp(trackp) ? "" : "no ",
			is_swab(trackp) ? " swab":"",
			((trackp->itracksize < 300L*trackp->isecsize) ||
			(trackp->itracksize % trackp->isecsize)) &&
			is_pad(trackp) ? " pad" : "");
	} else {
		printf("Track %02d: audio unknown length    %spreemp%s%s",
			track, is_preemp(trackp) ? "" : "no ",
			is_swab(trackp) ? " swab":"",
			(trackp->itracksize % trackp->isecsize) && is_pad(trackp) ? " pad" : "");
	}
	if (trackp->padsize > 0) {
		if ((trackp->padsize >> 20) > 0)
			printf(" padsize: %4lld MB", (Llong)(trackp->padsize >> 20));
		else
			printf(" padsize: %4lld KB", (Llong)(trackp->padsize >> 10));
		printf(" (%02d:%02d.%02d)",
			minutes(trackp->padsize),
			seconds(trackp->padsize),
			hseconds(trackp->padsize));
	}
	if (trackp->pregapsize != 150 || xdebug > 0) {
		printf(" pregapsize: %3ld", trackp->pregapsize);
	}
	if (xdebug)
		printf(" START: %ld SECTORS: %ld INDEX0 %ld",
			trackp->trackstart, trackp->tracksecs, trackp->index0start);
	printf("\n");
}

LOCAL void checkfile(int track, track_t *trackp)
{
	if (trackp->itracksize > 0 &&
			is_audio(trackp) &&
			( (!is_shorttrk(trackp) &&
			  (trackp->itracksize < 300L*trackp->isecsize)) ||
			(trackp->itracksize % trackp->isecsize)) &&
						!is_pad(trackp)) {
		errmsgno(EX_BAD, "Bad audio track size %lld for track %02d.\n",
				(Llong)trackp->itracksize, track);
		errmsgno(EX_BAD, "Audio tracks must be at least %ld bytes and a multiple of %d.\n",
				300L*trackp->isecsize, trackp->isecsize);
		comerrno(EX_BAD, "See -pad option.\n");
	}
	
	if (lverbose == 0 && xdebug == 0)
		return;

	if (is_audio(trackp))
		printaudio(track, trackp);
	else
		printdata(track, trackp);
}

LOCAL int checkfiles(int tracks, track_t *trackp)
{
	int	i;
	int	isaudio = 1;
	int	starttrack = 1;
	int	endtrack = tracks;

	if (xdebug) {
		/*
		 * Include Lead-in & Lead-out.
		 */
		starttrack--;
		endtrack++;
	}
	for (i = starttrack; i <= endtrack; i++) {
		if (!is_audio(&trackp[i]))
			isaudio = 0;
		if (xdebug)
			printf("SECTYPE %X ", trackp[i].sectype);
		checkfile(i, &trackp[i]);
	}
	return (isaudio);
}

LOCAL void setleadinout(int tracks, track_t *trackp)
{
	/*
	 * Set some values for track 0 (the lead-in)
	 */
	if (!is_clone(&trackp[0])) {
		trackp[0].sectype = trackp[1].sectype;
		trackp[0].dbtype  = trackp[1].dbtype;
		trackp[0].dataoff     = trackp[1].dataoff;

		/*
		 * XXX Which other flags should be copied to Track 0 ?
		 */
		if (is_audio(&trackp[1]))
			trackp[0].flags |= TI_AUDIO;
	}

	/*
	 * Set some values for track 0xAA (the lead-out)
	 */
	trackp[tracks+1].pregapsize = 0;
	trackp[tracks+1].isecsize   = trackp[tracks].isecsize;
	trackp[tracks+1].secsize    = trackp[tracks].secsize;

	if (!is_clone(&trackp[0])) {
		trackp[tracks+1].sectype    = trackp[tracks].sectype;
		trackp[tracks+1].dbtype     = trackp[tracks].dbtype;
		trackp[tracks+1].dataoff     = trackp[tracks].dataoff;
	}

	trackp[tracks+1].flags      = trackp[tracks].flags;
}

LOCAL void setpregaps(int tracks, track_t *trackp)
{
	int	i;
	int	sectype;
	long	pregapsize;
	track_t	*tp;

	sectype = trackp[1].sectype;
	sectype &= ST_MASK;

	for (i = 1; i <= tracks; i++) {
		tp = &trackp[i];
		if (tp->pregapsize == -1L) {
			tp->pregapsize = 150;		/* Default Pre GAP */
			if (sectype != (tp->sectype & ST_MASK)) {
				tp->pregapsize = 255;	/* Pre GAP is 255 */
				tp->flags &= ~TI_PREGAP;
			}
		}
		sectype = tp->sectype & ST_MASK;	/* Save old sectype */
	}
	trackp[tracks+1].pregapsize = 0;
	trackp[tracks+1].index0start = 0;

	for (i = 1; i <= tracks; i++) {
		/*
		 * index0start is set below tracksecks if this track contains
		 * the pregap (index 0) of the next track.
		 */
		trackp[i].index0start = trackp[i].tracksecs;

		pregapsize = trackp[i+1].pregapsize;
		if (is_pregap(&trackp[i+1]) && pregapsize > 0)
			trackp[i].index0start -= pregapsize;
	}
}

LOCAL long checktsize(int tracks, track_t *trackp)
{
	int	i;
	Llong	curr;
	Llong	total = -150;
	Ullong	btotal;
	track_t	*tp;

	for (i = 1; i <= tracks; i++) {
		tp = &trackp[i];
		if (!is_pregap(tp))
			total += tp->pregapsize;

		if (lverbose > 1) {
			printf("track: %d start: %lld pregap: %ld\n",
					i, total, tp->pregapsize);
		}
		tp->trackstart = total;
		if (tp->itracksize >= 0) {
			curr = (tp->itracksize + (tp->isecsize-1)) / tp->isecsize;
			curr += (tp->padsize + (tp->isecsize-1)) / tp->isecsize;
			/*
			 * Minimum track size is 4s
			 */
			if (!is_shorttrk(tp) && curr < 300)
				curr = 300;
			if (is_tao(tp) && !is_audio(tp)) {
				curr += 2;
			}
			total += curr;
		}
	}
	tp = &trackp[i];
	tp->trackstart = total;
	tp->tracksecs = 6750;
	if (!lverbose)
		return (total);

	btotal = (Ullong)total * 2352;
/* XXX Sector Size ??? */
	if (tracks > 0) {
		printf("Total size:     %3llu MB (%02d:%02d.%02d) = %lld sectors\n",
			btotal >> 20,
			minutes(btotal),
			seconds(btotal),
			hseconds(btotal), total);
		btotal += 150 * 2352;
		printf("Lout start:     %3llu MB (%02d:%02d/%02d) = %lld sectors\n",
			btotal >> 20,
			minutes(btotal),
			seconds(btotal),
			frames(btotal), total);
	}
	return (total);
}

LOCAL void checksize(track_t *trackp)
{
	struct stat	st;
	Llong		lsize;

	/*
	 * If the current input file is a regular file and
	 * 'padsize=' has not been specified,
	 * use fstat() or file parser to get the size of the file.
	 */
	if (trackp->itracksize < 0 && (trackp->flags & TI_ISOSIZE) != 0) {
		lsize = isosize(trackp->f);
		trackp->itracksize = lsize;
		if (trackp->itracksize != lsize)
			comerrno(EX_BAD, "This OS cannot handle large ISO-9660 images.\n");
	}
	if (trackp->itracksize < 0 && (trackp->flags & TI_NOAUHDR) == 0) {
		lsize = ausize(trackp->f);
		trackp->itracksize = lsize;
		if (trackp->itracksize != lsize)
			comerrno(EX_BAD, "This OS cannot handle large audio images.\n");
	}
	if (trackp->itracksize < 0 && (trackp->flags & TI_NOAUHDR) == 0) {
		lsize = wavsize(trackp->f);
		trackp->itracksize = lsize;
		if (trackp->itracksize != lsize)
			comerrno(EX_BAD, "This OS cannot handle large WAV images.\n");
		if (trackp->itracksize > 0)	/* Force little endian input */
			trackp->flags |= TI_SWAB;
	}
	if (trackp->itracksize == AU_BAD_CODING) {
		comerrno(EX_BAD, "Inappropriate audio coding in '%s'.\n",
							trackp->filename);
	}
	if (trackp->itracksize < 0 &&
			fstat(trackp->f, &st) >= 0 && S_ISREG(st.st_mode)) {
		trackp->itracksize = st.st_size;
	}
	if (trackp->itracksize >= 0) {
		/*
		 * We do not allow cdrecord to start if itracksize is not
		 * a multiple of isecsite or we are allowed to pad to secsize via -pad.
		 * For this reason, we may safely always assume padding.
		 */
		trackp->tracksecs = (trackp->itracksize + trackp->isecsize -1) / trackp->isecsize;
		trackp->tracksize = (trackp->itracksize / trackp->isecsize) * trackp->secsize
					+ trackp->itracksize % trackp->isecsize;
	} else {
		trackp->tracksecs = -1L;
	}
}

LOCAL BOOL checkdsize(SCSI *scgp, cdr_t *dp, dstat_t *dsp, long tsize, int flags)
{
	long	startsec = 0L;
	long	endsec = 0L;

	scgp->silent++;
	(*dp->cdr_next_wr_address)(scgp, /*i*/ 0, (track_t *)0, &startsec);
	scgp->silent--;

	/*
	 * This only should happen when the drive is currently in DAO mode.
	 * We rely on the drive being in TAO mode, a negative value for
	 * startsec is not correct here it may be caused by bad firmware or
	 * by a drive in DAO mode. In DAO mode the drive will report the
	 * pre-gap as part of the writable area.
	 */
	if (startsec < 0)
		startsec = 0;

	/*
	 * Size limitations (sectors) for CD's:
	 *
	 *		404850 == 90 min	Red book calls this the
	 *					first negative time
	 *					allows lead out start up to
	 *					block 404700
	 *
	 *		449850 == 100 min	This is the first time that
	 *					is no more representable
	 *					in a two digit BCD number.
	 *					allows lead out start up to
	 *					block 449700
	 *
	 *		~540000 == 120 min	The largest CD ever made.
	 *
	 *		~650000 == 1.3 GB	a Double Density (DD) CD.
	 */

	endsec = startsec + tsize;


	if (dsp->ds_maxblocks > 0) {
		/*
		 * dsp->ds_maxblocks > 0 (disk capacity is known).
		 */
		if (lverbose)
			printf("Blocks total: %ld Blocks current: %ld Blocks remaining: %ld\n",
					dsp->ds_maxblocks,
					dsp->ds_maxblocks - startsec,
					dsp->ds_maxblocks - endsec);

		if (endsec > dsp->ds_maxblocks) {
			if (dsp->ds_flags & DSF_DVD) {	/* A DVD and not a CD */
				errmsgno(EX_BAD,
				"Data does not fit on current disk.\n");
				goto toolarge;
			}
			errmsgno(EX_BAD,
			"WARNING: Data may not fit on current disk.\n");

			/* XXX Check for flags & CDR_NO_LOLIMIT */
/*			goto toolarge;*/
		}
		if (lverbose && dsp->ds_maxrblocks > 0)
			printf("RBlocks total: %ld RBlocks current: %ld RBlocks remaining: %ld\n",
					dsp->ds_maxrblocks,
					dsp->ds_maxrblocks - startsec,
					dsp->ds_maxrblocks - endsec);
		if (dsp->ds_maxrblocks > 0 && endsec > dsp->ds_maxrblocks) {
			errmsgno(EX_BAD,
			"Data does not fit on current disk.\n");
			goto toolarge;
		}
		if ((endsec > dsp->ds_maxblocks && endsec > 404700) ||
		    (dsp->ds_maxrblocks > 404700 && 449850 > dsp->ds_maxrblocks)) {
			/*
			 * Assume that this must be a CD and not a DVD.
			 * So this is a non Red Book compliant CD with a
			 * capacity between 90 and 99 minutes.
			 */
			if (dsp->ds_maxrblocks > 404700)
				printf("RedBook total: %ld RedBook current: %ld RedBook remaining: %ld\n",
					404700L,
					404700L - startsec,
					404700L - endsec);
			if (endsec > dsp->ds_maxblocks && endsec > 404700) {
				if ((flags & (F_IGNSIZE|F_FORCE)) == 0) {
					errmsgno(EX_BAD,
					"Notice: Most recorders cannot write CD's >= 90 minutes.\n");
					errmsgno(EX_BAD,
					"Notice: Use -ignsize option to allow >= 90 minutes.\n");
				}
				goto toolarge;
			}
		}
	} else {
		/*
		 * dsp->ds_maxblocks == 0 (disk capacity is unknown).
		 */
		if (endsec >= (405000-300)) {			/*<90 min disk*/
			errmsgno(EX_BAD,
				"Data will not fit on any disk.\n");
			goto toolarge;
		} else if (endsec >= (333000-150)) {		/* 74 min disk*/
			errmsgno(EX_BAD,
			"WARNING: Data may not fit on standard 74min disk.\n");
		}
	}
	if (dsp->ds_maxblocks <= 0 || endsec <= dsp->ds_maxblocks)
		return (TRUE);
	/* FALLTHROUGH */
toolarge:
	if (dsp->ds_maxblocks > 0 && endsec > dsp->ds_maxblocks) {
		if ((flags & (F_OVERBURN|F_IGNSIZE|F_FORCE)) != 0) {
			if (dsp->ds_flags & DSF_DVD) {	/* A DVD and not a CD */
				errmsgno(EX_BAD,
				"Notice: -overburn is not expected to work with DVD media.\n");
			}
			errmsgno(EX_BAD,
				"Notice: Overburning active. Trying to write more than the official disk capacity.\n");
			return (TRUE);
		} else {
			if ((dsp->ds_flags & DSF_DVD) == 0) {	/* A CD and not a DVD */
				errmsgno(EX_BAD,
				"Notice: Use -overburn option to write more than the official disk capacity.\n");
				errmsgno(EX_BAD,
				"Notice: Most CD-writers do overburning only on DAO mode.\n");
			}
			return (FALSE);
		}
	}
	if (dsp->ds_maxblocks < 449850) {
		/*
		 * Assume that this must be a CD and not a DVD.
		 */
		if (endsec > 449700) {
			errmsgno(EX_BAD, "Cannot write CD's >= 100 minutes.\n");
			return (FALSE);
		}
	}
	if ((flags & (F_IGNSIZE|F_FORCE)) != 0)
		return (TRUE);
	return (FALSE);
}

LOCAL void raise_fdlim()
{
#ifdef	RLIMIT_NOFILE

	struct rlimit	rlim;

	/*
	 * Set max # of file descriptors to be able to hold all files open
	 */
	getrlimit(RLIMIT_NOFILE, &rlim);
	rlim.rlim_cur = MAX_TRACK + 10;
	if (rlim.rlim_cur > rlim.rlim_max)
		errmsgno(EX_BAD,
			"warning: low file descriptor limit (%lld)\n",
						(Llong)rlim.rlim_max);
	setrlimit(RLIMIT_NOFILE, &rlim);

#endif	/* RLIMIT_NOFILE */
}

char	*opts =
"help,version,checkdrive,prcap,inq,scanbus,reset,overburn,ignsize,useinfo,dev*,timeout#,driver*,driveropts*,tsize&,padsize&,pregap&,defpregap&,speed#,load,eject,dummy,msinfo,toc,atip,multi,fix,nofix,waiti,debug#,d+,kdebug#,kd#,verbose+,v+,Verbose+,V+,x+,xd#,silent,s,audio,data,mode2,xa1,xa2,cdi,isosize,nopreemp,preemp,nocopy,copy,nopad,pad,swab,gui,delay#,fs&,blank&,pktsize#,packet,noclose,force,dao,raw,raw96r,raw96p,raw16,clone,scms,isrc*,mcn*,index*,textfile*,text,shorttrack,noshorttrack";

LOCAL void gargs(int ac, char **av, int *tracksp, track_t *trackp, char **devp, int *timeoutp, cdr_t **dpp, int *speedp, long *flagsp, int *toctypep, int *blankp)
{
	int	cac;
	char	* const*cav;
	char	*driver = NULL;
	char	*dev = NULL;
	char	*isrc = NULL;
	char	*mcn = NULL;
	char	*tindex = NULL;
	char	*textfile = NULL;
	long	bltype = -1;
	Llong	tracksize;
	Llong	padsize;
	long	pregapsize;
	long	defpregap = -1L;
	int	pktsize;
	int	speed = -1;
	int	help = 0;
	int	version = 0;
	int	checkdrive = 0;
	int	prcap = 0;
	int	inq = 0;
	int	scanbus = 0;
	int	reset = 0;
	int	overburn = 0;
	int	ignsize = 0;
	int	useinfo = 0;
	int	load = 0;
	int	eject = 0;
	int	dummy = 0;
	int	msinfo = 0;
	int	toc = 0;
	int	atip = 0;
	int	multi = 0;
	int	fix = 0;
	int	nofix = 0;
	int	waiti = 0;
	int	audio;
	int	autoaudio = 0;
	int	data;
	int	mode2;
	int	xa1;
	int	xa2;
	int	cdi;
	int	isize;
	int	ispacket = 0;
	int	noclose = 0;
	int	force = 0;
	int	dao = 0;
	int	raw = 0;
	int	raw96r = 0;
	int	raw96p = 0;
	int	raw16 = 0;
	int	clone = 0;
	int	scms = 0;
	int	preemp = 0;
	int	nopreemp;
	int	copy = 0;
	int	nocopy;
	int	pad = 0;
	int	bswab = 0;
	int	gui = 0;
	int	nopad;
	int	usetext = 0;
	int	shorttrack = 0;
	int	noshorttrack;
	int	flags;
	int	tracks = *tracksp;
	int	tracktype = TOC_ROM;
/*	int	sectype = ST_ROM_MODE1 | ST_MODE_1;*/
	int	sectype = SECT_ROM;
	int	dbtype = DB_ROM_MODE1;
	int	secsize = DATA_SEC_SIZE;
	int	dataoff;
	int	got_track;

	trackp[0].flags |= TI_TAO;
	trackp[1].pregapsize = -1;
	*flagsp |= F_WRITE;

	cac = --ac;
	cav = ++av;
	for (;; cac--, cav++) {
		tracksize = (Llong)-1L;
		padsize = (Llong)0L;
		pregapsize = defpregap;
		audio = data = mode2 = xa1 = xa2 = cdi = 0;
		isize = nopreemp = nocopy = nopad = noshorttrack = 0;
		dataoff = pktsize = 0;
		isrc = NULL;
		tindex = NULL;
		/*
		 * Get options up to next file type arg.
		 */
		if (getargs(&cac, &cav, opts,
				&help, &version, &checkdrive, &prcap,
				&inq, &scanbus, &reset, &overburn, &ignsize,
				&useinfo,
				devp, timeoutp, &driver, &driveropts,
				getllnum, &tracksize,
				getllnum, &padsize,
				getnum, &pregapsize,
				getnum, &defpregap,
				&speed,
				&load, &eject, &dummy, &msinfo, &toc, &atip,
				&multi, &fix, &nofix, &waiti,
				&debug, &debug,
				&kdebug, &kdebug,
				&lverbose, &lverbose,
				&scsi_verbose, &scsi_verbose,
				&xdebug, &xdebug,
				&silent, &silent,
				&audio, &data, &mode2,
				&xa1, &xa2, &cdi,
				&isize,
				&nopreemp, &preemp,
				&nocopy, &copy,
				&nopad, &pad, &bswab, &gui, &grace_time, getnum, &fs,
				getbltype, &bltype, &pktsize,
				&ispacket, &noclose, &force,
				&dao, &raw, &raw96r, &raw96p, &raw16,
				&clone,
				&scms, &isrc, &mcn, &tindex,
				&textfile, &usetext,
				&shorttrack, &noshorttrack) < 0) {
			errmsgno(EX_BAD, "Bad Option: %s.\n", cav[0]);
			usage(EX_BAD);
		}
		if (help)
			usage(0);
		if (gui)
			isgui = TRUE;
		if (tracks == 0) {
			if (driver)
				set_cdrcmds(driver, dpp);
			if (version)
				*flagsp |= F_VERSION;
			if (checkdrive)
				*flagsp |= F_CHECKDRIVE;
			if (prcap)
				*flagsp |= F_PRCAP;
			if (inq)
				*flagsp |= F_INQUIRY;
			if (scanbus)
				*flagsp |= F_SCANBUS;
			if (reset)
				*flagsp |= F_RESET;
			if (overburn)
				*flagsp |= F_OVERBURN;
			if (ignsize)
				*flagsp |= F_IGNSIZE;
			if (load)
				*flagsp |= F_LOAD;
			if (eject)
				*flagsp |= F_EJECT;
			if (dummy)
				*flagsp |= F_DUMMY;
			if (msinfo)
				*flagsp |= F_MSINFO;
			if (toc) {
				*flagsp |= F_TOC;
				*flagsp &= ~F_WRITE;
			}
			if (atip) {
				*flagsp |= F_PRATIP;
				*flagsp &= ~F_WRITE;
			}
			if (multi) {
				*flagsp |= F_MULTI;
				tracktype = TOC_XA2;
				sectype = ST_ROM_MODE2 | ST_MODE_2_FORM_1;
				sectype = SECT_MODE_2_F1;
				dbtype = DB_XA_MODE2;	/* XXX -multi nimmt DB_XA_MODE2_F1 !!! */
				secsize = DATA_SEC_SIZE;
			}
			if (fix)
				*flagsp |= F_FIX;
			if (nofix)
				*flagsp |= F_NOFIX;
			if (waiti)
				*flagsp |= F_WAITI;
			if (force) 
				*flagsp |= F_FORCE;

			if (bltype >= 0) {
				*flagsp |= F_BLANK;
				*blankp = bltype;
			}
			if(ispacket && is_dvd) {
			        trackp[0].flags |= TI_PACKET;
				trackp[0].flags &= ~TI_TAO;
			}
			if (dao) {
				*flagsp |= F_SAO;
				trackp[0].flags &= ~TI_TAO;

			} else if ((raw == 0) && (raw96r + raw96p + raw16) > 0)
				raw = 1;
			if ((raw != 0) && (raw96r + raw96p + raw16) == 0)
				raw96r = 1;
			if (raw96r) {
				if (!dao)
					*flagsp |= F_RAW;
				trackp[0].flags &= ~TI_TAO;
				trackp[0].flags |= TI_RAW;
				trackp[0].flags |= TI_RAW96R;
			}
			if (raw96p) {
				if (!dao)
					*flagsp |= F_RAW;
				trackp[0].flags &= ~TI_TAO;
				trackp[0].flags |= TI_RAW;
			}
			if (raw16) {
				if (!dao)
					*flagsp |= F_RAW;
				trackp[0].flags &= ~TI_TAO;
				trackp[0].flags |= TI_RAW;
				trackp[0].flags |= TI_RAW16;
			}
			if (mcn) {
#ifdef	AUINFO
				setmcn(mcn, &trackp[0]);
#else
				trackp[0].isrc = malloc(16);
				memset(trackp[0].isrc, 0, 16);
				strncpy(trackp[0].isrc, mcn, 13);
#endif
				mcn = NULL;
			}
			if ((raw96r + raw96p + raw16) > 1) {
				errmsgno(EX_BAD, "Too many raw modes.\n");
				comerrno(EX_BAD, "Only one of -raw16, -raw96p, -raw96r allowed.\n");
			}
			if ((ispacket + dao + raw) > 1) {
				errmsgno(EX_BAD, "Too many write modes.\n");
				comerrno(EX_BAD, "Only one of -packet, -dao, -raw allowed.\n");
			}
			if (dao && (raw96r + raw96p + raw16) > 0) {
				if (raw16)
					comerrno(EX_BAD, "SAO RAW writing does not allow -raw16.\n");
				if (!clone)
					comerrno(EX_BAD, "SAO RAW writing only makes sense in clone mode.\n");
				comerrno(EX_BAD, "SAO RAW writing not yet implemented.\n");
				comerrno(EX_BAD, "SAO RAW writing not yet implemented.\n");
			}
			if (clone) {
				*flagsp |= F_CLONE;
				trackp[0].flags |= TI_CLONE;
				comerrno(EX_BAD, "Clone writing not yet implemented.\n");
			}
			if (textfile) {
				if (!checktextfile(textfile)) {
					if ((*flagsp & F_WRITE) != 0) {
						comerrno(EX_BAD,
							"Cannot use '%s' as CD-Text file.\n",
							textfile);
					}
				}
				if ((*flagsp & F_WRITE) != 0) {
					if ((dao + raw96r + raw96p) == 0)
						comerrno(EX_BAD,
							"CD-Text needs -dao, -raw96r or -raw96p.\n");
				}
				trackp[0].flags |= TI_TEXT;
			}
			version = checkdrive = prcap = inq = scanbus = reset =
			overburn = ignsize =
			load = eject = dummy = msinfo = toc = atip = multi = fix = nofix =
			waiti = force = dao = 0;
			raw96r = raw96p = raw16 = clone = 0;
		} else if ((version + checkdrive + prcap + inq + scanbus +
			    reset + overburn + ignsize +
			    load + eject + dummy + msinfo + toc + atip + multi + fix + nofix +
			    waiti + force + dao +
			    raw96r + raw96p + raw16 + clone) > 0 ||
				mcn != NULL)
			comerrno(EX_BAD, "Badly placed option. Global options must be before any track.\n");

		if (nopreemp)
			preemp = 0;
		if (nocopy)
			copy = 0;
		if (nopad)
			pad = 0;
		if (noshorttrack)
			shorttrack = 0;

		if ((audio + data + mode2 + xa1 + xa2 + cdi) > 1) {
			errmsgno(EX_BAD, "Too many types for track %d.\n", tracks+1);
			comerrno(EX_BAD, "Only one of -audio, -data, -mode2, -xa1, -xa2, -cdi allowed.\n");
		}
		if (ispacket && audio) {
			comerrno(EX_BAD, "Audio data cannot be written in packet mode.\n");
		}
		/*
		 * Check whether the next argument is a file type arg.
		 * If this is true, then we got a track file name.
		 */
		got_track = getfiles(&cac, &cav, opts);
		if (autoaudio) {
			autoaudio = 0;
			tracktype = TOC_ROM;
			sectype = ST_ROM_MODE1 | ST_MODE_1;
			sectype = SECT_ROM;
			dbtype = DB_ROM_MODE1;
			secsize = DATA_SEC_SIZE;
		}
		if (got_track == 1 && (is_auname(cav[0]) || is_wavname(cav[0]))) {
			/*
			 * We got a track and autodetection decided that it
			 * is an audio track.
			 */
			autoaudio++;
			audio++;
		}
		if (data) {
			tracktype = TOC_ROM;
			sectype = ST_ROM_MODE1 | ST_MODE_1;
			sectype = SECT_ROM;
			dbtype = DB_ROM_MODE1;
			secsize = DATA_SEC_SIZE;
			dataoff = 16;
		}
		if (mode2) {
			tracktype = TOC_ROM;
			sectype = ST_ROM_MODE2 | ST_MODE_2;
			sectype = SECT_MODE_2;
			dbtype = DB_ROM_MODE2;
			secsize = MODE2_SEC_SIZE;
			dataoff = 16;
		}
		if (audio) {
			tracktype = TOC_DA;
			sectype = preemp ? ST_AUDIO_PRE : ST_AUDIO_NOPRE;
			sectype |= ST_MODE_AUDIO;
			sectype = SECT_AUDIO;
			if (preemp)
				sectype |= ST_PREEMPMASK;
			dbtype = DB_RAW;
			secsize = AUDIO_SEC_SIZE;
			dataoff = 0;
		}
		if (xa1) {
			tracktype = TOC_XA2;
			sectype = ST_ROM_MODE2 | ST_MODE_2_FORM_1;
			sectype = SECT_MODE_2_F1;
			dbtype = DB_XA_MODE2;
			secsize = DATA_SEC_SIZE;
			dataoff = 24;
		}
		if (xa2) {
			tracktype = TOC_XA2;
			sectype = ST_ROM_MODE2 | ST_MODE_2_FORM_2;
			sectype = SECT_MODE_2_F2 ;
			dbtype = DB_XA_MODE2_F2;	/* XXX Das unterscheidet sich von -multi !!! */
			secsize = 2056;
			dataoff = 24;
		}
		if (cdi) {
			tracktype = TOC_CDI;
			sectype = ST_ROM_MODE2 /* | XXX */;
			dbtype = DB_XA_MODE2_F1;
			secsize = 2056;
			dataoff = 24;
		}
		if (tracks == 0) {
			*toctypep = tracktype;
			trackp[0].tracktype = tracktype;
			trackp[0].dbtype = dbtype;
			trackp[0].isecsize = secsize;
			trackp[0].secsize = secsize;
			if ((*flagsp & F_RAW) != 0) {
				trackp[0].secsize = is_raw16(&trackp[0]) ?
						RAW16_SEC_SIZE:RAW96_SEC_SIZE;
			}
		}

		flags = trackp[0].flags;

		if ((sectype & ST_AUDIOMASK) != 0)
			flags |= TI_AUDIO;
		if (isize) {
			flags |= TI_ISOSIZE;
			if ((*flagsp & F_MULTI) != 0)
				comerrno(EX_BAD, "Cannot get isosize for multi session disks.\n");
			/*
			 * As we do not get the padding from the ISO-9660
			 * formatting utility, we need to force padding here.
			 */
			flags |= TI_PAD;
			if (padsize == (Llong)0L)
				padsize = (Llong)PAD_SIZE;
		}

		if ((flags & TI_AUDIO) != 0) {
			if (preemp)
				flags |= TI_PREEMP;
			if (copy)
				flags |= TI_COPY;
			if (scms)
				flags |= TI_SCMS;
		}
		if ((flags & TI_AUDIO) == 0 && padsize > (Llong)0L)
			pad = TRUE;
		if (pad) {
			flags |= TI_PAD;
			if ((flags & TI_AUDIO) == 0 && padsize == (Llong)0L)
				padsize = (Llong)PAD_SIZE;
		}
		if (shorttrack && (*flagsp & F_SAO) != 0)
			flags |= TI_SHORT_TRACK;
		if (noshorttrack)
			flags &= ~TI_SHORT_TRACK;
		if (bswab)
			flags |= TI_SWAB;
		if (ispacket) 
			flags |= TI_PACKET;
		if (noclose) 
			flags |= TI_NOCLOSE;

		if (got_track == 0) {
			/*
			 * All options have already been parsed and no more
			 * file type arguments are present.
			 */
			break;
		}
		tracks++;

		if (tracks > MAX_TRACK)
			comerrno(EX_BAD, "Track limit (%d) exceeded\n",
								MAX_TRACK);

		if (strcmp("-", cav[0]) == 0) {
			trackp[tracks].f = STDIN_FILENO;

#if	defined(__CYGWIN32__) || defined(__EMX__)
			setmode(STDIN_FILENO, O_BINARY);
#endif
		} else {
			if (access(cav[0], R_OK) < 0)
				comerr("No read access for '%s'.\n", cav[0]);

			if ((trackp[tracks].f = open(cav[0], O_RDONLY|O_BINARY)) < 0)
				comerr("Cannot open '%s'.\n", cav[0]);
		}
		if (!is_auname(cav[0]) && !is_wavname(cav[0]))
			flags |= TI_NOAUHDR;

		if ((*flagsp & (F_SAO|F_RAW)) != 0 && (flags & TI_AUDIO) != 0)
			flags |= TI_PREGAP;	/* Hack for now */
		if (tracks == 1)
			flags &= ~TI_PREGAP;

		if (tracks == 1 && (pregapsize != -1L && pregapsize != 150))
			pregapsize = -1L;
		trackp[tracks].filename = cav[0];;
		trackp[tracks].trackstart = 0L;
		trackp[tracks].itracksize = tracksize;
		trackp[tracks].tracksize = tracksize;
		trackp[tracks].tracksecs = -1L;
		if (trackp[tracks].pregapsize < 0)
			trackp[tracks].pregapsize = pregapsize;
		trackp[tracks+1].pregapsize = -1;
		trackp[tracks].padsize = padsize;
		trackp[tracks].isecsize = secsize;
		trackp[tracks].secsize = secsize;
		trackp[tracks].flags = flags;
		if ((*flagsp & F_RAW) != 0) {
			if (is_raw16(&trackp[tracks]))
				trackp[tracks].secsize = RAW16_SEC_SIZE;
			else
				trackp[tracks].secsize = RAW96_SEC_SIZE;
#ifndef	HAVE_LIB_EDC_ECC
			if ((sectype & ST_MODE_MASK) != ST_MODE_AUDIO)
				comerrno(EX_BAD,
					"Data sectors are not yet supported in RAW mode.\n");
#endif
		}
		trackp[tracks].secspt = 0;	/* transfer size is set up in set_trsizes() */
		trackp[tracks].pktsize = pktsize;
		trackp[tracks].trackno = tracks;
		trackp[tracks].sectype = sectype;
		trackp[tracks].dataoff = dataoff;
		trackp[tracks].tracktype = tracktype;
		trackp[tracks].dbtype = dbtype;
		trackp[tracks].flags = flags;
		trackp[tracks].nindex = 1;
		trackp[tracks].tindex = 0;
		checksize(&trackp[tracks]);
		tracksize = trackp[tracks].itracksize;
		if (!is_shorttrk(&trackp[tracks]) &&
		    tracksize > 0 && (tracksize / secsize) < 300) {
			/*
			 * This is really input sector size...
			 * XXX We need to check what to do for padding... XXX
			 */
			tracksize = roundup(tracksize, secsize);
			padsize = tracksize + roundup(padsize, secsize);
			if ((padsize / secsize) < 300) {
				trackp[tracks].padsize =
					300 * secsize - tracksize;
			}
			if (xdebug) {
				printf("TRACK %d SECTORS: %ld",
					tracks, trackp[tracks].tracksecs);
				printf(" pasdize %lld (%lld sectors)\n",
					(Llong)trackp[tracks].padsize,
					(Llong)trackp[tracks].padsize / secsize);
			}
		}
#ifdef	AUINFO
		if (useinfo) {
			auinfo(cav[0], tracks, trackp);
			if (tracks == 1)
				printf("pregap1: %ld\n", trackp[1].pregapsize);
		}
#endif
		/*
		 * This is really input sector size...
		 * trecksecks is total numbers of sectors in track (starting from
		 * index 0).
		 * XXX We need to check what to do for padding... XXX
		 */
		if (trackp[tracks].padsize > 0)
			trackp[tracks].tracksecs += trackp[tracks].padsize / secsize;

		if (isrc) {
#ifdef	AUINFO
			setisrc(isrc, &trackp[tracks]);
#else
			trackp[tracks].isrc = malloc(16);
			memset(trackp[tracks].isrc, 0, 16);
			strncpy(trackp[tracks].isrc, isrc, 12);
#endif
		}
		if (tindex) {
#ifdef	AUINFO
			setindex(tindex, &trackp[tracks]);
#endif
		}

		if (debug) {
			printf("File: '%s' tracksize: %lld secsize: %d tracktype: %d = %s sectype: %X = %s dbtype: %s flags %X\n",
				cav[0], (Llong)trackp[tracks].itracksize, 
				trackp[tracks].isecsize, 
				tracktype, toc2name[tracktype & TOC_MASK],
				sectype, st2name[sectype & ST_MASK], db2name[dbtype], flags);
		}
	}

	if (speed < 0 && speed != -1)
		comerrno(EX_BAD, "Bad speed option.\n");

	if (fs < 0L && fs != -1L)
		comerrno(EX_BAD, "Bad fifo size option.\n");

	dev = *devp;
	cdr_defaults(&dev, &speed, &fs);
	if (debug)
		printf("dev: %s speed: %d fs: %ld\n", dev, speed, fs);

	if (speed >= 0)
		*speedp = speed;

	if (fs < 0L)
		fs = DEFAULT_FIFOSIZE;

	if (dev != *devp && (*flagsp & F_SCANBUS) == 0)
		*devp = dev;

	if (!*devp && (*flagsp & (F_VERSION|F_SCANBUS)) == 0) {
		errmsgno(EX_BAD, "No CD/DVD-Recorder device specified.\n");
		usage(EX_BAD);
	}
	if (*flagsp & (F_LOAD|F_MSINFO|F_TOC|F_PRATIP|F_FIX|F_VERSION|F_CHECKDRIVE|F_PRCAP|F_INQUIRY|F_SCANBUS|F_RESET)) {
		if (tracks != 0) {
			errmsgno(EX_BAD, "No tracks allowed with this option\n");
			usage(EX_BAD);
		}
		return;
	}
	if (tracks == 0 && (*flagsp & (F_LOAD|F_EJECT|F_BLANK)) == 0) {
		errmsgno(EX_BAD, "No tracks specified. Need at least one.\n");
		usage(EX_BAD);
	}
	*tracksp = tracks;
	if (*flagsp & F_SAO) {
		/*
		 * Make sure that you change WRITER_MAXWAIT & READER_MAXWAIT
		 * too if you change this timeout.
		 */
		if (*timeoutp < 200)		/* Lead in size is 2:30 */
			*timeoutp = 200;	/* 200s is 150s *1.33	*/
	}
	if (usetext) {
		packtext(tracks, trackp);
		trackp[0].flags |= TI_TEXT;
	}
}

LOCAL void set_trsizes(cdr_t *dp, int tracks, track_t *trackp)
{
	int	i;
	int	secsize;
	int	secspt;

	trackp[1].flags		|= TI_FIRST;
	trackp[tracks].flags	|= TI_LAST;
	
	if (xdebug)
		printf("Set Transfersizes start\n");
	for (i = 0; i <= tracks+1; i++) {
		if ((dp->cdr_flags & CDR_SWABAUDIO) != 0 &&
					is_audio(&trackp[i])) {
			trackp[i].flags ^= TI_SWAB;
		}
		if (!is_audio(&trackp[i]))
			trackp[i].flags &= ~TI_SWAB;	/* Only swab audio  */

		/*
		 * Use the biggest sector size to compute how many
		 * sectors may fit into one single DMA buffer.
		 */
		secsize = trackp[i].secsize;
		if (trackp[i].isecsize > secsize)
			secsize = trackp[i].isecsize;

		/*
		 * We are using SCSI Group 0 write
		 * and cannot write more than 255 secs at once.
		 */
		secspt = bufsize/secsize;
		secspt = min(255, secspt);
		trackp[i].secspt = secspt;

		if (is_packet(&trackp[i]) && trackp[i].pktsize > 0) {
			if (trackp[i].secspt >= trackp[i].pktsize) {
				trackp[i].secspt = trackp[i].pktsize;
			} else {
				comerrno(EX_BAD,
					"Track %d packet size %d exceeds buffer limit of %d sectors",
					i, trackp[i].pktsize, trackp[i].secspt);
			}
		}
		if (xdebug) {
			printf("Track %d flags %X secspt %d secsize: %d isecsize: %d\n",
				i, trackp[i].flags, trackp[i].secspt,
				trackp[i].secsize, trackp[i].isecsize);
		}
	}
	if (xdebug)
		printf("Set Transfersizes end\n");
}

EXPORT void load_media(SCSI *scgp, cdr_t *dp, BOOL doexit)
{
	int	code;
	int	key;

	/*
	 * Do some preparation before...
	 */
	scgp->silent++;			/* Be quiet if this fails	*/
	test_unit_ready(scgp);		/* First eat up unit attention	*/
	(*dp->cdr_load)(scgp);		/* now try to load media and	*/
	scsi_start_stop_unit(scgp, 1, 0);/* start unit in silent mode	*/
	scgp->silent--;

	if (!wait_unit_ready(scgp, 60)) {
		code = scg_sense_code(scgp);
		key = scg_sense_key(scgp);
		scgp->silent++;
		scsi_prevent_removal(scgp, 0);/* In case someone locked it */
		scgp->silent--;

		if (!doexit)
			return;
		if (key == SC_NOT_READY && (code == 0x3A || code == 0x30))
			comerrno(EX_BAD, "No disk / Wrong disk!\n");
		comerrno(EX_BAD, "CD/DVD-Recorder not ready.\n");
	}

	scsi_prevent_removal(scgp, 1);
	scsi_start_stop_unit(scgp, 1, 0);
	wait_unit_ready(scgp, 120);
	scgp->silent++;
	rezero_unit(scgp);	/* Is this needed? Not supported by some drvives */
	scgp->silent--;
	test_unit_ready(scgp);
	scsi_start_stop_unit(scgp, 1, 0);
	wait_unit_ready(scgp, 120);
}

EXPORT void unload_media(SCSI *scgp, cdr_t *dp, int flags)
{
	scsi_prevent_removal(scgp, 0);
	if ((flags & F_EJECT) != 0)
		(*dp->cdr_unload)(scgp);
}

EXPORT void reload_media(SCSI *scgp, cdr_t *dp)
{
	char	ans[2];

	errmsgno(EX_BAD, "Drive needs to reload the media to return to proper status.\n");
	unload_media(scgp, dp, F_EJECT);

	if ((dp->cdr_flags & CDR_TRAYLOAD) != 0) {
		scgp->silent++;
		load_media(scgp, dp, FALSE);
		scgp->silent--;
	}

	scgp->silent++;
	if (((dp->cdr_flags & CDR_TRAYLOAD) == 0) ||
				!wait_unit_ready(scgp, 5)) {

		printf("Re-load disk and hit <CR>");
		if (isgui)
			printf("\n");
		flush();
		s_getline(ans, 1);
	}
	scgp->silent--;

	load_media(scgp, dp, TRUE);
}

EXPORT void set_secsize(SCSI *scgp, int secsize)
{
	if (secsize > 0) {
		/*
		 * Try to restore the old sector size.
		 */
		scgp->silent++;
		select_secsize(scgp, secsize);
		scgp->silent--;
	}
}

LOCAL BOOL do_opc(SCSI *scgp, cdr_t *dp, int flags)
{
	if ((flags & F_DUMMY) == 0 && dp->cdr_opc) {
		if (debug || lverbose) {
			printf("Performing OPC...\n");
			flush();
		}
		if (dp->cdr_opc(scgp, NULL, 0, TRUE) < 0) {
			errmsgno(EX_BAD, "OPC failed.\n");
			if ((flags & F_FORCE) == 0)
				return (FALSE);
		}
	}
	return (TRUE);
}

LOCAL void check_recovery(SCSI *scgp, cdr_t *dp, int flags)
{
	if ((*dp->cdr_check_recovery)(scgp)) {
		errmsgno(EX_BAD, "Recovery needed.\n");
		unload_media(scgp, dp, flags);
		comexit(EX_BAD);
	}
}

#ifndef	DEBUG
#define	DEBUG
#endif
void audioread(SCSI *scgp, cdr_t *dp, int flags)
{
#ifdef	DEBUG
	int speed = 1;
	int dummy = 0;

	if ((*dp->cdr_set_speed_dummy)(scgp, &speed, dummy) < 0)
		comexit(-1);
	if ((*dp->cdr_set_secsize)(scgp, 2352) < 0)
		comexit(-1);
	scgp->cap->c_bsize = 2352;

	read_scsi(scgp, buf, 1000, 1);
	printf("XXX:\n");
	write(1, buf, 512);
	unload_media(scgp, dp, flags);
	comexit(0);
#endif
}

LOCAL void print_msinfo(SCSI *scgp, cdr_t *dp)
{
	long	off;
	long	fa;

	if ((*dp->cdr_session_offset)(scgp, &off) < 0) {
		errmsgno(EX_BAD, "Cannot read session offset\n");
		return;
	}
	if (lverbose)
		printf("session offset: %ld\n", off);

	if (dp->cdr_next_wr_address(scgp, 0, (track_t *)0, &fa) < 0) {
		errmsgno(EX_BAD, "Cannot read first writable address\n");
		return;
	}
	printf("%ld,%ld\n", off, fa);
}

LOCAL void print_toc(SCSI *scgp, cdr_t *dp)
{
	int	first;
	int	last;
	long	lba;
	long	xlba;
	struct msf msf;
	int	adr;
	int	control;
	int	mode;
	int	i;

	scgp->silent++;
	if (read_capacity(scgp) < 0) {
		scgp->silent--;
		errmsgno(EX_BAD, "Cannot read capacity\n");
		return;
	}
	scgp->silent--;
	if (read_tochdr(scgp, dp, &first, &last) < 0) {
		errmsgno(EX_BAD, "Cannot read TOC/PMA\n");
		return;
	}
	printf("first: %d last %d\n", first, last);
	for (i = first; i <= last; i++) {
		read_trackinfo(scgp, i, &lba, &msf, &adr, &control, &mode);
		xlba = -150 +
			msf.msf_frame + (75*msf.msf_sec) + (75*60*msf.msf_min);
		if (xlba == lba/4)
			lba = xlba;
		print_track(i, lba, &msf, adr, control, mode);
	}
	i = 0xAA;
	read_trackinfo(scgp, i, &lba, &msf, &adr, &control, &mode);
	xlba = -150 +
		msf.msf_frame + (75*msf.msf_sec) + (75*60*msf.msf_min);
	if (xlba == lba/4)
		lba = xlba;
	print_track(i, lba, &msf, adr, control, mode);
	if (lverbose > 1) {
		scgp->silent++;
		if (read_cdtext(scgp) < 0)
			errmsgno(EX_BAD, "No CD-Text or CD-Text unaware drive.\n");
		scgp->silent++;
	}
}

LOCAL void print_track(int track, long lba, struct msf *msp, int adr, int control, int mode)
{
	long	lba_512 = lba*4;

	if (track == 0xAA)
		printf("track:lout ");
	else
		printf("track: %3d ", track);

	printf("lba: %9ld (%9ld) %02d:%02d:%02d adr: %X control: %X mode: %d\n",
			lba, lba_512,
			msp->msf_min,
			msp->msf_sec,
			msp->msf_frame,
			adr, control, mode);
}

LOCAL void prtimediff(const char *fmt, struct timeval *start, struct timeval *stop)
{
	struct timeval tv;

	tv.tv_sec = stop->tv_sec - start->tv_sec;
	tv.tv_usec = stop->tv_usec - start->tv_usec;
	while (tv.tv_usec > 1000000) {
		tv.tv_usec -= 1000000;
		tv.tv_sec += 1;
	}
	while (tv.tv_usec < 0) {
		tv.tv_usec += 1000000;
		tv.tv_sec -= 1;
	}
	/*
	 * We need to cast timeval->* to long because
	 * of the broken sys/time.h in Linux.
	 */
	printf("%s%4ld.%03lds\n", fmt, (long)tv.tv_sec, (long)tv.tv_usec/1000);
	flush();
}

#ifdef	HAVE_SYS_PRIOCNTL_H

#include <sys/procset.h>	/* Needed for SCO Openserver */
#include <sys/priocntl.h>
#include <sys/rtpriocntl.h>

EXPORT	void raisepri(int pri)
{
	int		pid;
	int		classes;
	int		ret;
	pcinfo_t	info;
	pcparms_t	param;
	rtinfo_t	rtinfo;
	rtparms_t	rtparam;

	pid = getpid();

	/* get info */
	strcpy(info.pc_clname, "RT");
	classes = priocntl(P_PID, pid, PC_GETCID, (void *)&info);
	if (classes == -1)
		comerr("Cannot get priority class id priocntl(PC_GETCID)\n");

	memmove(&rtinfo, info.pc_clinfo, sizeof(rtinfo_t));
 
	/* set priority to max */
	rtparam.rt_pri = rtinfo.rt_maxpri - pri;
	rtparam.rt_tqsecs = 0;
	rtparam.rt_tqnsecs = RT_TQDEF;
	param.pc_cid = info.pc_cid;
	memmove(param.pc_clparms, &rtparam, sizeof(rtparms_t));
	ret = priocntl(P_PID, pid, PC_SETPARMS, (void *)&param);
	if (ret == -1) {
		errmsg("WARNING: Cannot set priority class parameters priocntl(PC_SETPARMS)\n");
		errmsgno(EX_BAD, "WARNING: This causes a high risk for buffer underruns.\n");
	}
}

#else	/* HAVE_SYS_PRIOCNTL_H */

#if defined(_POSIX_PRIORITY_SCHEDULING)
/*
 * XXX Ugly but needed because of a typo in /usr/iclude/sched.h on Linux.
 * XXX This should be removed as soon as we are sure that Linux-2.0.29 is gone.
 */
#ifdef	linux
#define	_P	__P
#endif

#include <sched.h>

#ifdef	linux
#undef	_P
#endif

LOCAL	int rt_raisepri(int pri)
{
	struct sched_param scp;

	/*
	 * Verify that scheduling is available
	 */
#ifdef	_SC_PRIORITY_SCHEDULING
	if (sysconf(_SC_PRIORITY_SCHEDULING) == -1) {
		errmsg("WARNING: RR-scheduler not available, disabling.\n");
		return(-1);
	}
#endif
	memset(&scp, 0, sizeof(scp));
	scp.sched_priority = sched_get_priority_max(SCHED_RR) - pri;
	if (sched_setscheduler(0, SCHED_RR, &scp) < 0) {
		errmsg("WARNING: Cannot set RR-scheduler\n");
		return (-1);
	}
	return (0);
}

#else	/* _POSIX_PRIORITY_SCHEDULING */

#ifdef	__CYGWIN32__

/*
 * NOTE: Base.h from Cygwin-B20 has a second typedef for BOOL.
 *	 We define BOOL to make all local code use BOOL
 *	 from Windows.h and use the hidden __SBOOL for
 *	 our global interfaces.
 *
 * NOTE: windows.h from Cygwin-1.x includes a structure field named sample,
 *	 so me may not define our own 'sample' or need to #undef it now.
 *	 With a few nasty exceptions, Microsoft assumes that any global
 *	 defines or identifiers will begin with an Uppercase letter, so
 *	 there may be more of these problems in the future.
 *
 * NOTE: windows.h defines interface as an alias for struct, this 
 *	 is used by COM/OLE2, I guess it is class on C++
 *	 We man need to #undef 'interface'
 */
#define	BOOL	WBOOL		/* This is the Win BOOL		*/
#define	format	__format	/* Avoid format parameter hides global ... */
#include <windows.h>
#undef format
#undef interface

LOCAL	int rt_raisepri(int pri)
{
	int prios[] = {THREAD_PRIORITY_TIME_CRITICAL, THREAD_PRIORITY_HIGHEST};

	/* set priority class */
	if (SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == FALSE) {
		errmsgno(EX_BAD, "No realtime priority class possible.\n");
		return (-1);
	}

	/* set thread priority */
	if (pri >= 0 && pri <= 1 && SetThreadPriority(GetCurrentThread(), prios[pri]) == FALSE) {
		errmsgno(EX_BAD, "Could not set realtime priority.\n");
		return (-1);
	}
	return (0);
}

#else

LOCAL	int rt_raisepri(int pri)
{
	return (-1);
}

#endif	/* __CYGWIN32__ */

#endif	/* _POSIX_PRIORITY_SCHEDULING */

EXPORT	void raisepri(int pri)
{
	if (rt_raisepri(pri) >= 0)
		return;
#if	defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)

	if (setpriority(PRIO_PROCESS, getpid(), -20 + pri) < 0) {
		errmsg("WARNING: Cannot set priority using setpriority().\n");
		errmsgno(EX_BAD, "WARNING: This causes a high risk for buffer underruns.\n");
	}
#else
#ifdef	HAVE_DOSSETPRIORITY	/* RT priority on OS/2 */
	/*
	 * Set priority to timecritical 31 - pri (arg)
	 */
	DosSetPriority(0, 3, 31, 0);
	DosSetPriority(0, 3, -pri, 0);
#else
#ifdef	HAVE_NICE
	if (nice(-20 + pri) == -1) {
		errmsg("WARNING: Cannot set priority using nice().\n");
		errmsgno(EX_BAD, "WARNING: This causes a high risk for buffer underruns.\n");
	}
#else
	errmsgno(EX_BAD, "WARNING: Cannot set priority on this OS.\n");
	errmsgno(EX_BAD, "WARNING: This causes a high risk for buffer underruns.\n");
#endif
#endif
#endif
}

#endif	/* HAVE_SYS_PRIOCNTL_H */

#ifdef	HAVE_SELECT
/*
 * sys/types.h and sys/time.h are already included.
 */
#else
#	include	<stropts.h>
#	include	<poll.h>

#ifndef	INFTIM
#define	INFTIM	(-1)
#endif
#endif

#if	defined(HAVE_SELECT) && defined(NEED_SYS_SELECT_H)
#include <sys/select.h>
#endif
#if	defined(HAVE_SELECT) && defined(NEED_SYS_SOCKET_H)
#include <sys/socket.h>
#endif

LOCAL void wait_input()
{
#ifdef	HAVE_SELECT
	fd_set	in;

	FD_ZERO(&in);
	FD_SET(STDIN_FILENO, &in);
	select(1, &in, NULL, NULL, 0);
#else
	struct pollfd pfd;

	pfd.fd = STDIN_FILENO;
	pfd.events = POLLIN;
	pfd.revents = 0;
	poll(&pfd, (unsigned long)1, INFTIM);
#endif
}

LOCAL void checkgui()
{
	struct stat st;

	if(isgui)
		return;

	if (fstat(STDERR_FILENO, &st) >= 0 && !S_ISCHR(st.st_mode)) {
		isgui = TRUE;
		if (lverbose > 1)
			printf("Using remote (pipe) mode for interactive i/o.\n");
	}
}

LOCAL Llong number(register char *arg, int *retp)
{
	Llong	val	= 0;

	if (*retp != 1)
		return (val);
	if (*arg == '\0') {
		*retp = -1;
	} else if (*(arg = astoll(arg, &val))) {
		if (*arg == 'p' || *arg == 'P') {
			val *= (1024*1024);
			val *= (1024*1024*1024);
			arg++;

		} else if (*arg == 't' || *arg == 'T') {
			val *= (1024*1024);
			val *= (1024*1024);
			arg++;

		} else if (*arg == 'g' || *arg == 'G') {
			val *= (1024*1024*1024);
			arg++;

		} else if (*arg == 'm' || *arg == 'M') {
			val *= (1024*1024);
			arg++;

		} else if (*arg == 'f' || *arg == 'F') {
			val *= 2352;
			arg++;

		} else if (*arg == 's' || *arg == 'S') {
			val *= 2048;
			arg++;

		} else if (*arg == 'k' || *arg == 'K') {
			val *= 1024;
			arg++;

		} else if (*arg == 'b' || *arg == 'B') {
			val *= 512;
			arg++;

		} else if (*arg == 'w' || *arg == 'W') {
			val *= 2;
			arg++;
		}
		if (*arg == '*' || *arg == 'x')
			val *= number(++arg, retp);
		else if (*arg != '\0')
			*retp = -1;
	}
	return (val);
}

EXPORT int getnum(char *arg, long *valp)
{
	int	ret = 1;

	*valp = (long)number(arg, &ret);
	return (ret);
}

EXPORT int getllnum(char *arg, Llong *lvalp)
{
	int	ret = 1;

	*lvalp = number(arg, &ret);
	return (ret);
}

LOCAL int getbltype(char *optstr, long *typep)
{
	if (streql(optstr, "all")) {
		*typep = BLANK_DISC;
	} else if (streql(optstr, "disc")) {
		*typep = BLANK_DISC;
	} else if (streql(optstr, "disk")) {
		*typep = BLANK_DISC;
	} else if (streql(optstr, "fast")) {
		*typep = BLANK_MINIMAL;
	} else if (streql(optstr, "minimal")) {
		*typep = BLANK_MINIMAL;
	} else if (streql(optstr, "track")) {
		*typep = BLANK_TRACK;
	} else if (streql(optstr, "unreserve")) {
		*typep = BLANK_UNRESERVE;
	} else if (streql(optstr, "trtail")) {
		*typep = BLANK_TAIL;
	} else if (streql(optstr, "unclose")) {
		*typep = BLANK_UNCLOSE;
	} else if (streql(optstr, "session")) {
		*typep = BLANK_SESSION;
	} else if (streql(optstr, "help")) {
		blusage(0);
	} else {
		error("Illegal blanking type '%s'.\n", optstr);
		blusage(EX_BAD);
		return (-1);
	}
	return (TRUE);
}

LOCAL void print_wrmodes(cdr_t *dp)
{
	BOOL	needblank = FALSE;

	printf("Supported modes: ");
	if ((dp->cdr_flags & CDR_TAO) != 0) {
		printf("TAO");
		needblank = TRUE;
	}
	if ((dp->cdr_flags & CDR_PACKET) != 0) {
		printf("%sPACKET", needblank?" ":"");
		needblank = TRUE;
	}
	if ((dp->cdr_flags & CDR_SAO) != 0) {
		printf("%sSAO", needblank?" ":"");
		needblank = TRUE;
	}
#ifdef	__needed__
	if ((dp->cdr_flags & (CDR_SAO|CDR_SRAW16)) == (CDR_SAO|CDR_SRAW16)) {
		printf("%sSAO/R16", needblank?" ":"");
		needblank = TRUE;
	}
#endif
	if ((dp->cdr_flags & (CDR_SAO|CDR_SRAW96P)) == (CDR_SAO|CDR_SRAW96P)) {
		printf("%sSAO/R96P", needblank?" ":"");
		needblank = TRUE;
	}
	if ((dp->cdr_flags & (CDR_SAO|CDR_SRAW96R)) == (CDR_SAO|CDR_SRAW96R)) {
		printf("%sSAO/R96R", needblank?" ":"");
		needblank = TRUE;
	}
	if ((dp->cdr_flags & (CDR_RAW|CDR_RAW16)) == (CDR_RAW|CDR_RAW16)) {
		printf("%sRAW/R16", needblank?" ":"");
		needblank = TRUE;
	}
	if ((dp->cdr_flags & (CDR_RAW|CDR_RAW96P)) == (CDR_RAW|CDR_RAW96P)) {
		printf("%sRAW/R96P", needblank?" ":"");
		needblank = TRUE;
	}
	if ((dp->cdr_flags & (CDR_RAW|CDR_RAW96R)) == (CDR_RAW|CDR_RAW96R)) {
		printf("%sRAW/R96R", needblank?" ":"");
		needblank = TRUE;
	}
	printf("\n");
}

LOCAL BOOL check_wrmode(cdr_t *dp, int wmode, int tflags)
{
	int	cdflags = dp->cdr_flags;

	if ((tflags & TI_TAO) != 0 && (cdflags & CDR_TAO) == 0) {
		errmsgno(EX_BAD, "Drive does not support TAO recording.\n");
		return (FALSE);
	}
	if ((tflags & TI_PACKET) != 0 && (cdflags & CDR_PACKET) == 0) {
		errmsgno(EX_BAD, "Drive does not support PACKET recording.\n");
		return (FALSE);
	}
	if ((wmode & F_SAO) != 0) {
		if ((cdflags & CDR_SAO) == 0) {
			errmsgno(EX_BAD, "Drive does not support SAO recording.\n");
			if ((cdflags & CDR_RAW) != 0)
				errmsgno(EX_BAD, "Try -raw option.\n");
			return (FALSE);
		}
#ifdef	__needed__
		if ((tflags & TI_RAW16) != 0 && (cdflags & CDR_SRAW16) == 0) {
			errmsgno(EX_BAD, "Drive does not support SAO/RAW16.\n");
			goto badsecs;
		}
#endif
		if ((tflags & (TI_RAW|TI_RAW16|TI_RAW96R)) == TI_RAW && (cdflags & CDR_SRAW96P) == 0) {
			errmsgno(EX_BAD, "Drive does not support SAO/RAW96P.\n");
			goto badsecs;
		}
		if ((tflags & (TI_RAW|TI_RAW16|TI_RAW96R)) == (TI_RAW|TI_RAW96R) && (cdflags & CDR_SRAW96R) == 0) {
			errmsgno(EX_BAD, "Drive does not support SAO/RAW96R.\n");
			goto badsecs;
		}
	}
	if ((wmode & F_RAW) != 0) {
		if ((cdflags & CDR_RAW) == 0) {
			errmsgno(EX_BAD, "Drive does not support RAW recording.\n");
			return (FALSE);
		}
		if ((tflags & TI_RAW16) != 0 && (cdflags & CDR_RAW16) == 0) {
			errmsgno(EX_BAD, "Drive does not support RAW/RAW16.\n");
			goto badsecs;
		}
		if ((tflags & (TI_RAW|TI_RAW16|TI_RAW96R)) == TI_RAW && (cdflags & CDR_RAW96P) == 0) {
			errmsgno(EX_BAD, "Drive does not support RAW/RAW96P.\n");
			goto badsecs;
		}
		if ((tflags & (TI_RAW|TI_RAW16|TI_RAW96R)) == (TI_RAW|TI_RAW96R) && (cdflags & CDR_RAW96R) == 0) {
			errmsgno(EX_BAD, "Drive does not support RAW/RAW96R.\n");
			goto badsecs;
		}
	}
	return (TRUE);

badsecs:
	if ((wmode & F_SAO) != 0)
		cdflags &= ~(CDR_RAW16|CDR_RAW96P|CDR_RAW96R);
	if ((wmode & F_RAW) != 0)
		cdflags &= ~(CDR_SRAW96P|CDR_SRAW96R);
	
	if ((cdflags & (CDR_SRAW96R|CDR_RAW96R)) != 0)
		errmsgno(EX_BAD, "Try -raw96r option.\n");
	else if ((cdflags & (CDR_SRAW96P|CDR_RAW96P)) != 0)
		errmsgno(EX_BAD, "Try -raw96p option.\n");
	else if ((cdflags & CDR_RAW16) != 0)
		errmsgno(EX_BAD, "Try -raw16 option.\n");
	return (FALSE);
}
