#ifndef _LARGEFILE_SOURCE
#define _LARGEFILE_SOURCE
#endif
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif
#if defined(__linux)
/* ... and "engage" glibc large file support */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#endif

#include "transport.hxx"

#include <time.h>
#include <sys/types.h>

#define SIGS_BLOCK	1
#define SIGS_UNBLOCK	0
extern "C" void sigs_mask (int);
extern "C" void atsignals (void (*)(void));

#define	ONEX 1352	// 1385 * 1000 / 1024

static int		media_written=0,next_track=1,
			is_dao=0,quickgrown=0,do_reload=1;
static unsigned int	stdnap=(256*1000)/ONEX,	// measured in milliseconds
			buf_size=512;		// measured in KBs
static class __velocity__ {
    private:
	int value;
    public:
	int operator=(int v)	{ if (v>=ONEX)	stdnap=(buf_size*500)/v;
				  else		stdnap=(buf_size*500)/ONEX;
				  return value = v;
				}
	operator int()		{ return value; }
} velocity;

static void         *ioctl_handle=(void *)-1;
#ifndef ioctl_fd
#define ioctl_fd ((long)ioctl_handle)
#endif

static unsigned int  next_wr_addr=1;	// it starts as boolean
static unsigned int  dao_blocks=0;
static unsigned char formats[260],disc_info[32];

extern "C" {
extern int	dvd_compat,test_write,no_reload,mmc_profile,_argc,
		wrvfy,no_opc;
extern double	speed_factor;
extern char	*ioctl_device,**_argv;
}

extern "C"
int fumount (int fd)
{ Scsi_Command cmd;
  return cmd.umount(fd);
}

extern "C"
int media_reload (char *name=NULL,struct stat *sb=NULL)
{   if (name==NULL)
    {	Scsi_Command cmd(ioctl_handle);

	pioneer_stop (cmd);

#if defined(RELOAD_NEVER_NEEDED)
#undef RELOAD_NEVER_NEEDED
#define RELOAD_NEVER_NEEDED 1
#else
#define RELOAD_NEVER_NEEDED 0
#endif
	if (RELOAD_NEVER_NEEDED || no_reload>0)
	{   cmd[0] = 0x1E;	// ALLOW MEDIA REMOVAL
	    cmd[5] = 0;
	    cmd.transport ();

	    return (errno=0);
	}
#if !RELOAD_NEVER_NEEDED

	char str[12];
	int n;
	
	if ((n=fcntl (ioctl_fd,F_GETFD))<0) n=0;
	fcntl (ioctl_fd,F_SETFD,n&~FD_CLOEXEC);

	sprintf (str,"%ld",ioctl_fd);
	execlp(_argv[0],no_reload<0?"-eject":"-reload",
			str,ioctl_device,(void *)NULL);
    }
    else
    {
	{ Scsi_Command cmd;

	    if (!cmd.associate (name,sb)) return 1;

	    if (cmd.is_reload_needed())
	    {	fprintf (stderr,"%s: reloading tray\n",name);
		cmd[0] = 0x1E;		// ALLOW MEDIA REMOVAL
		cmd[5] = 0;
		if (cmd.transport ()) return 1;

		while (1)	// Pioneer DVR-x05 needs this...
		{   cmd[0] = 0x1B;	// START/STOP UNIT
		    cmd[1] = 0x1;	// "IMMED"
		    cmd[4] = 0x2;	// "Eject"
		    cmd[5] = 0;
		    if (cmd.transport() == 0x20407) // "OP IN PROGRESS"
		    {	poll (NULL,0,333);
			continue;
		    }
		    break;
		}
		// yes, once again, non-"IMMED"...
		cmd[0] = 0x1B;		// START/STOP UNIT
		cmd[4] = 0x2;		// "Eject"
		cmd[5] = 0;
		if (cmd.transport()) return 1;
	    }
#if defined(__sun) || defined(sun)
	    else if (volmgt_running())
	    {	setuid(getuid());
		execl("/usr/bin/volrmmount","volrmmount","-i",name,(void*)NULL);
		return 0;	// not normally reached
	    }
#endif
	    else return 0;	// m-m-m-m! patched kernel:-)
	}
	if (no_reload>=0)
	{ Scsi_Command cmd;
	    if (cmd.associate (name,sb))
	    {	cmd[0] = 0x1B;		// START/STOP UNIT
		cmd[1] = 0x1;		// "IMMED"
		cmd[4] = 0x3;		// "Load"
		cmd[5] = 0;
		cmd.transport ();
	    }
	    errno=0;	// ignore all errors on load
	}
	return 0;
#endif
    }

  return 1;
}

extern "C"
int get_mmc_profile (void *fd)
{ Scsi_Command cmd(fd);
  unsigned char buf[8],inq[128];
  int profile=0,once=1,blank=0,err;
  unsigned int len;

    // INQUIRY is considered to be "non-intrusive" in a sense that
    // it won't interfere with any other operation nor clear sense
    // data, which might be important to retain for security reasons.

    cmd[0] = 0x12;	// INQUIRY
    cmd[4] = 36;
    cmd[5] = 0;
    if ((err=cmd.transport(READ,inq,36)))
	sperror ("INQUIRY",err),
	exit (FATAL_START(errno));

    // make sure we're talking to MMC unit, for security reasons...
    if ((inq[0]&0x1F) != 5)
	fprintf (stderr,":-( not an MMC unit!\n"),
	exit (FATAL_START(EINVAL));

    do {
	cmd[0] = 0x46;
	cmd[8] = sizeof(buf);
	cmd[9] = 0;
	if ((err=cmd.transport(READ,buf,sizeof(buf))))
	    sperror ("GET CONFIGURATION",err),
	    fprintf (stderr,":-( non-MMC unit?\n"),
	    exit (FATAL_START(errno));

        if ((profile = buf[6]<<8|buf[7]) || !once) break;

	// no media?
	cmd[0] = 0;	// TEST UNIT READY
	cmd[5] = 0;
	if ((cmd.transport()&0xFFF00) != 0x23A00) break;

	// try to load tray...
	cmd[0] = 0x1B;	// START/STOP UNIT
	cmd[4] = 0x3;	// "Load"
	cmd[5] = 0;
	if ((err=cmd.transport ()))
	    sperror ("LOAD TRAY",err),
	    exit (FATAL_START(errno));

#if 1
	wait_for_unit (cmd);
#else
	// consume sense data, most likely "MEDIA MAY HAVE CHANGED"
	cmd[0] = 0;	// TEST UNIT READY
	cmd[5] = 0;
	if ((err=cmd.transport ()) == -1)
	    sperror ("TEST UNIT READY",err),
	    exit (FATAL_START(errno));
#endif
    } while (once--);

    if (profile==0 || (profile&0x30)==0)	// no or non-DVD media...
	return profile;

    cmd[0] = 0x51;	// READ DISC INFORMATION
    cmd[8] = sizeof(disc_info);
    cmd[9] = 0;
    if ((err=cmd.transport (READ,disc_info,sizeof(disc_info))))
	sperror ("READ DISC INFORMATION",err),
	exit (FATAL_START(errno));

    // see if it's blank media
    if ((disc_info[2]&3) == 0)	blank=0x10000;

    if (profile != 0x1A && profile != 0x13 && profile != 0x12)
	return blank|profile;

    cmd[0] = 0x23;	// READ FORMAT CAPACITIES
    cmd[8] = 12;
    cmd[9] = 0;
    if ((err=cmd.transport (READ,formats,12)))
	sperror ("READ FORMAT CAPACITIES",err),
	exit (FATAL_START(errno));

    len = formats[3];
    if (len&7 || len<16)
	fprintf (stderr,":-( FORMAT allocaion length isn't sane"),
	exit (FATAL_START(EINVAL));

    cmd[0] = 0x23;	// READ FORMAT CAPACITIES
    cmd[7] = (4+len)>>8;
    cmd[8] = (4+len)&0xFF;
    cmd[9] = 0;
    if ((err=cmd.transport (READ,formats,4+len)))
	sperror ("READ FORMAT CAPACITIES",err),
	exit (FATAL_START(errno));

    if (len != formats[3])
	fprintf (stderr,":-( parameter length inconsistency\n"),
	exit(FATAL_START(EINVAL));

    // see if it's not formatted
    if ((formats[8]&3) != 2) blank = 0x10000;

  return blank|profile;
}

static unsigned int get_2k_capacity (Scsi_Command &cmd)
{ unsigned char	buf[32];
  unsigned int	ret=0;
  unsigned int	nwa,free_blocks;
  int		i,obligatory,len,err;

    obligatory=0x00;
    switch (mmc_profile&0xFFFF)
    {	case 0x1A:		// DVD+RW
	    obligatory=0x26;
	case 0x13:		// DVD-RW Restricted Overwrite
	    for (i=8,len=formats[3];i<len;i+=8)
		if ((formats [4+i+4]>>2) == obligatory) break;

	    if (i==len)
	    {	fprintf (stderr,":-( can't locate obligatory format descriptor\n");
		return 0;
	    }

	    ret  = formats[4+i+0]<<24;
	    ret |= formats[4+i+1]<<16;
	    ret |= formats[4+i+2]<<8;
	    ret |= formats[4+i+3];
	    nwa = formats[4+5]<<16|formats[4+6]<<8|formats[4+7];
	    if (nwa>2048)	ret *= nwa/2048;
	    else if (nwa<2048)	ret /= 2048/nwa;
	    break;

	case 0x12:		// DVD-RAM
	    // As for the moment of this writing I don't format DVD-RAM.
	    // Therefore I just pull formatted capacity for now...
	    ret  = formats[4+0]<<24;
	    ret |= formats[4+1]<<16;
	    ret |= formats[4+2]<<8;
	    ret |= formats[4+3];
	    nwa = formats[4+5]<<16|formats[4+6]<<8|formats[4+7];
	    if (nwa>2048)	ret *= nwa/2048;
	    else if (nwa<2048)	ret /= 2048/nwa;
	    break;

	case 0x11:		// DVD-R
	case 0x14:		// DVD-RW Sequential
	case 0x15:		// DVD-R Dual Layer Sequential
	case 0x16:		// DVD-R Dual Layer Jump
	case 0x1B:		// DVD+R
	case 0x2B:		// DVD+R Double Layer
	    cmd[0] = 0x52;	// READ TRACK INFORMATION
	    cmd[1] = 1;
	    cmd[4] = next_track>>8;
	    cmd[5] = next_track&0xFF;	// last track, set up earlier
	    cmd[8] = sizeof(buf);
	    cmd[9] = 0;
	    if ((err=cmd.transport (READ,buf,sizeof(buf))))
	    {	sperror ("READ TRACK INFORMATION",err);
		return 0;
	    }

	    nwa = 0;
	    if (buf[7]&1)	// NWA_V
	    {	nwa  = buf[12]<<24;
		nwa |= buf[13]<<16;
		nwa |= buf[14]<<8;
		nwa |= buf[15];
	    }
	    free_blocks  = buf[16]<<24;
	    free_blocks |= buf[17]<<16;
	    free_blocks |= buf[18]<<8;
	    free_blocks |= buf[19];
	    ret = nwa + free_blocks;
	    break;

	default:
	    break;
    }

  return ret;
}

extern "C"
off64_t get_capacity (void *fd)
{ Scsi_Command	cmd(fd);
  return (off64_t)get_2k_capacity(cmd)*2048;
}

extern "C"
double get_buffer_stats (void *fd)
{ Scsi_Command		cmd(fd);
  unsigned char		bcap[12];
  unsigned int		bsize,bfree;

    cmd[0] = 0x5C;              // READ BUFFER CAPACITY
    cmd[8] = sizeof(bcap);
    cmd[9] = 0;
    if (cmd.transport (READ,bcap,sizeof(bcap)))
	return 0.0;

    bsize = bcap[4]<<24|bcap[5]<<16|bcap[6]<<8|bcap[7];
    bfree = bcap[8]<<24|bcap[9]<<16|bcap[10]<<8|bcap[11];

  return (1.0 - (double)bfree/(double)bsize);
}

ssize_t poor_mans_pwrite64 (int fd,const void *_buff,size_t size,off64_t foff)
{ Scsi_Command		cmd(ioctl_handle);	/* screw first argument */
  unsigned char		bcap[12];
  const unsigned char  *buff=(const unsigned char *)_buff;
  unsigned int		lba,nbl,bsize,bfree;
  int			retries=0,errcode;
  static int		dao_toggle=-1;

    if (foff&0x7FFF || size&0x7FFF)	// 32K block size
	return (errno=EINVAL,-1);

    lba = foff>>11;
    nbl = size>>11;

    if (!media_written && next_wr_addr)
    {	if ((lba+nbl) <= next_wr_addr)
	    return size;
	else if (next_wr_addr > lba)
	    nbl  -= (next_wr_addr-lba),
	    size -= (next_wr_addr-lba)<<11,
	    buff += (next_wr_addr-lba)<<11,
	    lba   = next_wr_addr;
    }
#if defined(__sun) || defined(sun)
    else    next_wr_addr = lba;
#endif

    if (dao_toggle<0) dao_toggle=is_dao;

    { static unsigned int first_wr_addr=0;

	if (!media_written)
	{   sigs_mask (SIGS_BLOCK); first_wr_addr = lba;	}
	else if (lba >= (first_wr_addr+buf_size/2)) // measured in 2KBs!
	{   sigs_mask (SIGS_UNBLOCK);			}
    }

    if (dao_blocks!=0 && (lba+nbl)>dao_blocks)
	nbl = dao_blocks-lba;

    while (1)
    {	cmd[0] = wrvfy?0x2E:0x2A;	// WRITE [AND VERIFY] (10)
	cmd[2] = (lba>>24)&0xff;	// Logical Block Addrss
	cmd[3] = (lba>>16)&0xff;
	cmd[4] = (lba>>8)&0xff;
	cmd[5] = lba&0xff;
	cmd[7] = (nbl>>8)&0xff;
	cmd[8] = nbl&0xff;
	cmd[9] = 0;
#if 0
	cmd[0] = 0xAA;			// WRITE(12)
	cmd[2] = (lba>>24)&0xff;	// Logical Block Addrss
	cmd[3] = (lba>>16)&0xff;
	cmd[4] = (lba>>8)&0xff;
	cmd[5] = lba&0xff;
	cmd[8] = (nbl>>8)&0xff;
	cmd[9] = nbl&0xff;
	cmd[10] = 0x80;			// "Streaming"
	cmd[11] = 0;
#endif
	//
	// First writes can be long, especially in DAO mode...
	// I wish I could complement this with "if (lba==0),"
	// but some units might choose to fill the buffer before
	// they take the first nap...
	//
	cmd.timeout(dao_toggle?180:60);
	//
	// It should also be noted that under Linux these values
	// (if actually respected by kernel!) can turn out bogus.
	// The problem is that I scale them to milliseconds as
	// documentation requires/implies, while kernel treats
	// them as "jiffies." I could/should have used HZ macro
	// (or sysconf(_SC_CLK_TCK)), but recent kernels maintain
	// own higher HZ value and disrespects the user-land one.
	// Sending them down as milliseconds is just safer...
	//
	if (!(errcode=cmd.transport (WRITE,(void *)buff,size)))
	    break;

	//--- WRITE failed ---//
#if defined(__sun) || defined(sun)
	//
	// Solaris can slice USB WRITEs to multiple ones. Here I try
	// to find out which slice has failed. I expect we get here
	// only when we re-enter the loop...
	//
	if (lba==next_wr_addr &&
	    errcode==0x52102)		// "INVALID ADDRESS FOR WRITE"
	{ unsigned char track[32];

	    cmd[0] = 0x52;		// READ TRACK INFORMATION
	    cmd[1] = 1;
	    cmd[4] = next_track>>8;
	    cmd[5] = next_track&0xFF;
	    cmd[8] = sizeof(track);
	    cmd[9] = 0;
	    if (!cmd.transport (READ,track,sizeof(track)))
	    {	if (track[7]&1)	// NWA_V
		{   next_wr_addr  = track[12]<<24;
		    next_wr_addr |= track[13]<<16;
		    next_wr_addr |= track[14]<<8;
		    next_wr_addr |= track[15];
		    if (lba<next_wr_addr && (lba+nbl)>next_wr_addr)
		    {	nbl  -= next_wr_addr-lba,
			size -= (next_wr_addr-lba)<<11,
			buff += (next_wr_addr-lba)<<11,
			lba   = next_wr_addr;
			continue;
		    }
		}
	    }
	}
#endif

	if (errcode==0x20408)		// "LONG WRITE IN PROGRESS"
	{   // Apparently only Pioneer units do this...
	    if (velocity == 0)
	    {	if (handle_events(cmd) & (1<<6))
		    continue;
		goto sync_cache;
	    }

	    cmd[0] = 0x5C;		// READ BUFFER CAPACITY
	    cmd[8] = sizeof(bcap);
	    cmd[9] = 0;
	    if (cmd.transport (READ,bcap,sizeof(bcap)))
		bfree=0, bsize=buf_size;
	    else
	    {	bsize = bcap[4]<<24|bcap[5]<<16|bcap[6]<<8|bcap[7];
		bfree = bcap[8]<<24|bcap[9]<<16|bcap[10]<<8|bcap[11];
		bsize /= 1024, bfree /= 1024;	// xlate to KB
	    }
	    // check for sanity and drain 1/2 of the buffer
	    if (bsize < buf_size/2)	bsize = buf_size/2;
	    else			bsize /= 2;
	    if (bfree > bsize)		bfree = bsize;

	    int msecs=0;

	    if ((msecs=(bsize-bfree)*1000) > velocity)
	    {	msecs /= velocity;
		retries++;
		if (dao_toggle) dao_toggle=-1;
	    }
	    else	// lots of free buffer reported?
	    {	if (dao_toggle)
		{   dao_toggle=-1;
		    if ((handle_events(cmd) & (1<<6)))
			continue;
		    msecs = bsize*1000;
		    msecs /= velocity;
		}
		else if (!retries++)	continue;
		// Pioneer units seem to return bogus values at high
		// velocities now and then... I reserve for 4 ECC
		// blocks, which is ~6 ms at 16x. But note that some
		// OSes, Linux 2.4 among then, will nap for 10 or 20.
		msecs = (4*32*1000)/velocity;
	    }

	    // Retry values were increased because high-speed units
	    // start recordings at only portion of velocity and it
	    // takes more retries to write lead-in... Another common
	    // reason for stucks are recalibrations at zone edges...
	    if (retries > (dao_toggle?768:192))
		fprintf (stderr,":-? the LUN appears to be stuck "
				"writing LBA=%xh, keep retrying in %dms\n",
				lba,msecs),
		retries=1;

	    if (msecs>=0)
	    {	poll (NULL,0,msecs);
		continue;
	    }

	    lba |= 0x80000000;		// signal insane bfree...
	}
	else if (lba==0 && errcode==0x20401)	// "IN PROCESS OF BECOMING READY"
	{   if ((handle_events(cmd) & (1<<6)))
		continue;
	    if (++retries > 3)
	    {	fprintf (stderr,":-! the LUN is still in process of becoming ready, "
		    		    "retrying in 5 secs...\n"),
		retries=0;
		sigs_mask (SIGS_UNBLOCK);
		poll (NULL,0,5000);
	    }
	    else
	    	sigs_mask (SIGS_UNBLOCK),
		poll (NULL,0,stdnap);
	    continue;
	}
	else if (errcode==0x20404 ||	// "FORMAT IN PROGRESS"
		 errcode==0x20407 ||	// "OPERATION IN PROGRESS"
		 errcode==0x52C00)	// "COMMAND SEQUENCE ERROR"
	{   // Happens under automounter control? In general I recommend
	    // to disable it! This code is a tribute to users who disregard
	    // the instructions...
	sync_cache:
	    cmd[0] = 0x35;	// SYNC CACHE
	    cmd[9] = 0;
	    if (!cmd.transport())
	    {	if (++retries > 1)
	    	{   fprintf (stderr,":-! the LUN appears to be stuck at %xh, "
		    		    "retrying in 5 secs...\n",lba),
		    retries=0;
		    sigs_mask (SIGS_UNBLOCK);
		    poll (NULL,0,5000);
		}
		else if (errcode==0x52C00)
		    fprintf (stderr,":-! \"COMMAND SEQUENCE ERROR\"@LBA=%xh. "
				    "Is media being read?\n",lba);
		continue;
	    }
	    lba |= 0x40000000;	// signal "can't SYNC CACHE"
	}
	{ char cmdname[64];
	    sprintf (cmdname,"WRITE@LBA=%xh",lba),
	    sperror (cmdname,errcode);
	}
	if (lba==0)
	{   if (ASC(errcode)==0x30)
		fprintf (stderr,":-( media is not formatted or unsupported.\n");
	    else if ((mmc_profile&0xFFFF)==0x14 /*&& ASC(errcode)==0x64*/)
		fprintf (stderr,":-( attempt to re-run with "
				"-dvd-compat -dvd-compat to engage DAO or "
				"apply full blanking procedure\n");
	}
#if 1	// safety net: leave DL sessions open and resumable...
	if ((mmc_profile&0xFFFF)==0x2B && errcode!=0x52100) media_written=0;
#endif
	return -1;
    }

    next_wr_addr = lba+nbl;
    media_written = 1;

    if (dao_toggle<0) dao_toggle=0;

  return size;
}

static void plus_rw_format (Scsi_Command &cmd)
{ int err,i,len;
  unsigned char descr[12];

    if ((formats[4+4]&3) == 1)	// Unformatted media
    {	fprintf (stderr,"%s: pre-formatting blank DVD+RW...\n",ioctl_device);

	for (i=8,len=formats[3];i<len;i+=8)
	    if ((formats [4+i+4]>>2) == 0x26) break;

	if (i==len)
	    fprintf (stderr,":-( can't locate DVD+RW format descriptor\n"),
	    exit(FATAL_START(EMEDIUMTYPE));

	memset (descr,0,sizeof(descr));
	descr[1]=0x02;		// "IMMED" flag
	descr[3]=0x08;		// "Descriptor Length" (LSB)
	memcpy (descr+4,formats+4+i,4);
	descr[8]=0x26<<2;	// "Format type" 0x26

	cmd[0] = 0x04;		// FORMAT UNIT
	cmd[1] = 0x11;		// "FmtData" and "Format Code"
	cmd[5] = 0;
	if ((err=cmd.transport(WRITE,descr,sizeof(descr))))
	    sperror ("FORMAT UNIT",err),
	    exit(FATAL_START(errno));

	wait_for_unit (cmd);

	cmd[0] = 0x35;		// FLUSH CACHE
	cmd[9] = 0;
	if ((err=cmd.transport()))
	    sperror ("FLUSH CACHE",err),
	    exit(FATAL_START(errno));
    }
}

static int plus_rw_restart_format (Scsi_Command &cmd, off64_t size)
{ unsigned char descr[12];
  unsigned int lead_out,blocks;
  int err,i,len;

    if (!dvd_compat && size!=0)
    {	blocks = size/2048;
	blocks += 15, blocks &= ~15;

	lead_out = 0;
	lead_out |= formats[4+0], lead_out <<= 8;
	lead_out |= formats[4+1], lead_out <<= 8;
	lead_out |= formats[4+2], lead_out <<= 8;
	lead_out |= formats[4+3];

	if (blocks<=lead_out)   // no need to resume format...
	    return 0;
    }

    fprintf (stderr,"%s: restarting DVD+RW format...\n",ioctl_device);

    for (i=8,len=formats[3];i<len;i+=8)
	if ((formats [4+i+4]>>2) == 0x26) break;

    if (i==len)
	fprintf (stderr,":-( can't locate DVD+RW format descriptor\n"),
	exit(FATAL_START(EMEDIUMTYPE));

    memset (descr,0,sizeof(descr));
    descr[1]=0x02;		// "IMMED" flag
    descr[3]=0x08;		// "Descriptor Length" (LSB)
#if 1
    memcpy (descr+4,formats+4+i,4);
#else
    memset (descr+4,-1,4);
#endif
    descr[8]=0x26<<2;		// "Format type" 0x26
    descr[11]=1;		// "Restart Format"

    cmd[0] = 0x04;		// FORMAT UNIT
    cmd[1] = 0x11;		// "FmtData" and "Format Code"
    cmd[5] = 0;
    if ((err=cmd.transport(WRITE,descr,sizeof(descr))))
    {	sperror ("RESTART FORMAT",err);
	return 1;
    }

    wait_for_unit(cmd);

  return 0;
}

extern "C"
int plusminus_r_C_parm (void *fd,char *C_parm)
{ unsigned int next_session, prev_session,err;
  Scsi_Command cmd(fd);
  unsigned char buf[36];

    if ((disc_info[2]&3) != 1)
	fprintf (stderr,":-( media is not appendable\n"),
	exit(FATAL_START(EMEDIUMTYPE));

    // allow to resume even --v-- incomplete sessions //
    if (((disc_info[2]>>2)&3) > 1)
	fprintf (stderr,":-( last session is not empty\n"),
	exit(FATAL_START(EMEDIUMTYPE));

    next_track = disc_info[5]|disc_info[10]<<8;

    cmd[0] = 0x52;		// READ TRACK INFORMATION
    cmd[1] = 1;
    cmd[4] = next_track>>8;
    cmd[5] = next_track;	// ask for last track
    cmd[8] = sizeof(buf);
    cmd[9] = 0;
    if ((err=cmd.transport (READ,buf,sizeof(buf))))
    {	sperror ("READ TRACK INFORMATION",err);
	if (ASC(err)==0x24) // hp dvd200i returns 24 if media is full
	    fprintf (stderr,":-( media must be full already\n");
	exit (FATAL_START(errno));
    }

#if 0
    if (buf[7]&1)		// NWA_V
    {	next_session  = buf[12]<<24;
	next_session |= buf[13]<<16;
	next_session |= buf[14]<<8;
	next_session |= buf[15];
    }
    else
	fprintf (stderr,":-( Next Writable Address is invalid\n"),
	exit(FATAL_START(EMEDIUMTYPE));
#else
    next_session  = buf[8]<<24;	// Track Start Address
    next_session |= buf[9]<<16;
    next_session |= buf[10]<<8;
    next_session |= buf[11];
#endif

    //
    // All manuals say the data is fabricated, presumably implying
    // that one should use another command. But we stick to this one
    // because kernel uses this very command to mount multi-session
    // discs.
    //
    cmd[0] = 0x43;		// READ TOC
    cmd[2] = 1;			// "Session info"
    cmd[8] = 12;
    cmd[9] = 0;
    if ((err=cmd.transport (READ,buf,12)))
	sperror ("READ SESSION INFO",err),
	exit (FATAL_START(errno));

    prev_session  = buf[8]<<24;
    prev_session |= buf[9]<<16;
    prev_session |= buf[10]<<8;
    prev_session |= buf[11];

    sprintf (C_parm,"%d,%d",prev_session+16,next_session);

  return next_session;
}

static unsigned char *pull_page2A (Scsi_Command &cmd)
{ unsigned char *page2A,header[12];
  unsigned int   len,bdlen;
  int            err;

    cmd[0] = 0x5A;		// MODE SENSE
    cmd[1] = 0x08;		// "Disable Block Descriptors"
    cmd[2] = 0x2A;		// "Capabilities and Mechanical Status"
    cmd[8] = sizeof(header);	// header only to start with
    cmd[9] = 0;
    if ((err=cmd.transport(READ,header,sizeof(header))))
	sperror ("MODE SENSE#2A",err), exit(FATAL_START(errno));

    len   = (header[0]<<8|header[1])+2;
    bdlen = header[6]<<8|header[7];

    if (bdlen)	// should never happen as we set "DBD" above
    {	if (len < (8+bdlen+30))
	    fprintf (stderr,":-( LUN is impossible to bear with...\n"),
	    exit(FATAL_START(EINVAL));
    }
    else if (len < (8+2+(unsigned int)header[9]))// SANYO does this.
	len = 8+2+header[9];

    page2A = (unsigned char *)malloc(len);
    if (page2A == NULL)
	fprintf (stderr,":-( memory exhausted\n"), exit(FATAL_START(ENOMEM));

    cmd[0] = 0x5A;		// MODE SENSE
    cmd[1] = 0x08;		// "Disable Block Descriptors"
    cmd[2] = 0x2A;		// "Capabilities and Mechanical Status"
    cmd[7] = len>>8;
    cmd[8] = len;		// real length this time
    cmd[9] = 0;
    if ((err=cmd.transport(READ,page2A,len)))
	sperror ("MODE SENSE#2A",err),
	exit(FATAL_START(errno));

    len -= 2;
    if (len < ((unsigned int)page2A[0]<<8|page2A[1]))	// paranoia:-)
	page2A[0] = len>>8, page2A[1] = len;

  return page2A;
}

static int pull_velocity (Scsi_Command &cmd,unsigned char *d)
{ unsigned int len;
  int v,err;
  class autofree perf;

#if 0	// 8x AccessTek derivatives, such as OptoRite DD0405
    if ((d[4]&2) == 0)	return -1;
#endif

    len = (d[0]<<24|d[1]<<16|d[2]<<8|d[3])-4;
    if (len%16)		return -1;
    if (len==0)		return -1;	// LG GCA-4040N:-(

    len += 8;
    if (len == (8+16))
    {	velocity = d[8+ 4]<<24|d[8+ 5]<<16|d[8+ 6]<<8|d[8+ 7];
	v =        d[8+12]<<24|d[8+13]<<16|d[8+14]<<8|d[8+15];
	if (v>velocity) velocity=v;	// CAV?
    }
    else	// ZCLV
    { unsigned int n = (len-8)/16;
      unsigned char *p;

	perf = (unsigned char *)malloc (len);
	if (perf == NULL)
	    fprintf (stderr,":-( memory exhausted\n"),
	    exit(FATAL_START(ENOMEM));

	cmd[0]=0xAC;	// GET PERFORMANCE
	cmd[1]=4;	// ask for "Overall Write Performance"
	cmd[8]=n>>8;
	cmd[9]=n;	// real number of descriptors
	cmd[10]=0;	// ask for descriptor in effect
	cmd[11]=0;
	if ((err=cmd.transport(READ,perf,len)))
	    sperror ("GET CURRENT PERFORMANCE",err),
	    exit (FATAL_START(errno));

	// Pick the highest speed...
	for (p=perf+8,len-=8;len;p+=16,len-=16)
	{   v=p[ 4]<<24|p[ 5]<<16|p[ 6]<<8|p[ 7];
	    if (v > velocity) velocity = v;
	    v=p[12]<<24|p[13]<<16|p[14]<<8|p[15];
	    if (v > velocity) velocity = v;	// ZCAV?
	}
    }

  return 0;
}

static int set_speed_B6h (Scsi_Command &cmd,unsigned int dvddash,
			  unsigned char *page2A)
{ unsigned int len;
  int err;
  unsigned char d[8+16];
  class autofree perf;

    cmd[0]=0xAC;		// GET PERFORMACE
    cmd[9]=1;			// start with one descriptor
    cmd[10]=0x3;		// ask for "Write Speed Descriptor"
    cmd[11]=0;
    if ((err=cmd.transport(READ,d,sizeof(d))))
    {	sperror ("GET PERFORMANCE",err);
	fprintf (stderr,":-( falling down to SET CD SPEED\n");
	return -1;
    }

    len = (d[0]<<24|d[1]<<16|d[2]<<8|d[3])-4;

    if (len%16)			// insane length
    {	fprintf (stderr,":-( GET PERFORMANCE: insane Performance Data Length\n");
	return -1;
    }

    perf = (unsigned char *)malloc(len+=8);
    if (perf == NULL)
	fprintf (stderr,":-( memory exhausted\n"),
	exit(FATAL_START(ENOMEM));

    if (len == sizeof(d))
	memcpy (perf,d,sizeof(d));
    else
    { unsigned int n=(len-8)/16;

	cmd[0]=0xAC;		// GET PERFORMANCE
	cmd[8]=n>>8;
	cmd[9]=n;		// real number of descriptors
	cmd[10]=0x3;		// ask for "Write Speed Descriptor"
	cmd[11]=0;
	if ((err=cmd.transport(READ,perf,len)))
	    sperror ("GET PERFORMANCE",err),
	    exit (FATAL_START(errno));
    }

    int targetv=0,errp=0;
    do {
	memset (d,0,sizeof(d));
	cmd[0]=0xAC;		// GET PERFORMANCE
	cmd[1]=4;		// ask for "Overall Write performance"
	cmd[9]=1;
	cmd[10]=0;		// ask for descriptor in effect
	cmd[11]=0;
	if (errp || (errp=cmd.transport(READ,d,sizeof(d))))
#if 0
	    sperror ("GET CURRENT PERFORMANCE",errp),
	    exit (FATAL_START(errno));	// well, if it passed above, we
					// expect it to pass this too...
#else	// Pioneer doesn't report current speed through GET PERFORMANCE:-(
	{   emulated_err:

	    if (page2A == NULL)	return -1;

	    unsigned int  plen,hlen;

	    plen = (page2A[0]<<8|page2A[1]) + 2;
	    hlen = 8 + (page2A[6]<<8|page2A[7]);

	    unsigned char * const p = page2A + hlen;

	    if (plen<(hlen+32) || p[1]<(32-2))
		return -1;	// well, SET CD SPEED wouldn't work...

	    velocity = p[28]<<8|p[29];
	}
#endif
	else if ((errp = pull_velocity (cmd,d)) < 0)
		goto emulated_err;

	if (speed_factor != 0.0)
	{ int i,j=len-8,v,v0,v1,minv,closesti,closestv=0;
	  unsigned char *wsdp=perf+8;

	    for (minv=0x7fffffff,i=0;i<j;i+=16)
	    {	v=wsdp[i+12]<<24|wsdp[i+13]<<16|wsdp[i+14]<<8|wsdp[i+15];
		if (v<minv) minv=v;
	    } // minv will be treated as 1x

	    // New generation units offer minimum velocities other than 1x
	    if      (minv>(15*ONEX))	speed_factor /= 16.0;
	    else if (minv>(11*ONEX))	speed_factor /= 12.0;
	    else if (minv>(7*ONEX))	speed_factor /= 8.0;
	    else if (minv>(5*ONEX))	speed_factor /= 6.0;
	    else if (minv>(3*ONEX))	speed_factor /= 4.0;	
	    else if (!dvddash && minv>(2*ONEX))
		// 2.4x is like 1x for DVD+, but it turned out that there're
		// units, most notably "DVDRW IDE1004," burning DVD+ at
		// lower speed, so I have to watch out for it...
		speed_factor /= 2.4;
	    else if (minv>=(2*ONEX))
		speed_factor /= 2.0;

	    v0=(int)(speed_factor*minv + 0.5);
	    for (closesti=0,minv=0x7fffffff,i=0;i<j;i+=16)
	    {	v1=wsdp[i+12]<<24|wsdp[i+13]<<16|wsdp[i+14]<<8|wsdp[i+15];
		v=abs(v0-v1);
		if (v<minv) minv=v, closesti=i, closestv=v1;
	    } // currenti is index of descriptor with *closest* velocity

	    targetv=closestv;
	    speed_factor=0.0;

	    if (velocity==targetv)
		break;	// already in shape, nothing to do...

	    unsigned char pd[28];

	    memset (pd,0,sizeof(pd));	// setup "Performance Descriptor"

	    pd[0]=*(wsdp+closesti);	// copy "WRC" and other flags
#if 0
	    memset(pd+8,0xFF,4);
#else	    // I might have to copy the value from current descriptor...
	    unsigned int cap=get_2k_capacity(cmd)-1;

	    pd[8]=cap>>24;
	    pd[9]=cap>>16;
	    pd[10]=cap>>8;
	    pd[11]=cap;
#endif
	    memcpy(pd+12,wsdp+closesti+8,4);	// copy "Read Speed"
	    memcpy(pd+20,wsdp+closesti+12,4);	// copy "Write Speed"
	    pd[18]=pd[26]=1000>>8;		// set both "Read Time" and
	    pd[19]=pd[27]=1000&0xFF;		// "Write Time" to 1000ms

	    cmd[0]=0xB6;		// SET STREAMING
	    cmd[10]=sizeof(pd);
	    cmd[11]=0;
	    if ((err=cmd.transport (WRITE,pd,sizeof(pd))))
		sperror ("SET STREAMING",err),
		exit (FATAL_START(errno));

	    // I pull the Page 2A in either case, because unit might
	    // have provided irrelevant information (Plextor). Not to
	    // mention cases when it simply failed to reply to GET
	    // CURRENT PERFORMANCE (Pioneer, LG).

	    unsigned int plen  = (page2A[0]<<8|page2A[1]) + 2;

	    cmd[0] = 0x5A;	// MODE SENSE
	    cmd[1] = 0x08;	// "Disable Block Descriptors"
	    cmd[2] = 0x2A;	// "Capabilities and Mechanical Status"
	    cmd[7] = plen>>8;
	    cmd[8] = plen;	// real length this time
	    cmd[9] = 0;
	    if ((err=cmd.transport(READ,page2A,plen)))
		sperror ("MODE SENSE#2A",err),
		exit(FATAL_START(errno));
	}
	else if (targetv)
	{   if (targetv!=velocity)
	    {	if (errp==0)	// check Page 2A for speed then...
		{   errp=-1; continue;   }
		if (velocity==0 || velocity>=ONEX)
		{   fprintf(stderr,":-? Failed to change write speed: %d->%d\n",
					(int)velocity,targetv);
		    if (velocity<targetv)	velocity=targetv;
		}
		else
		{   fprintf(stderr,":-( Failed to change write speed: %d->%d\n",
					(int)velocity,targetv);
		    exit (FATAL_START(EINVAL));
		}
	    }
	    break;
	}
    } while (targetv);

  return targetv;
}

static int set_speed_BBh (Scsi_Command &cmd,unsigned char *page2A)
{ unsigned int   plen,hlen;
  int err;

    plen  = (page2A[0]<<8|page2A[1]) + 2;
    hlen = 8 + (page2A[6]<<8|page2A[7]);

    unsigned char * const p = page2A + hlen;

    if (plen<(hlen+32) || p[1]<(32-2))
	return -1;

    int targetv=0;
    do {
	velocity = p[28]<<8|p[29];

	if (speed_factor != 0.0)
	{ int i,j,v,v0,v1,minv,closesti,closestv=0;
	  unsigned char *wsdp=p+32;

	    j=(p[30]<<8|p[31])*4;
	    for (minv=0x7fffffff,i=0;i<j;i+=4)
	    {	v=wsdp[i+2]<<8|wsdp[i+3];
		if (v<minv) minv=v;
	    } // minv will be treated as 1x

	    v0=(int)(speed_factor*minv + 0.5);
	    for (closesti=0,minv=0x7fffffff,i=0;i<j;i+=4)
	    {	v1=wsdp[i+2]<<8|wsdp[i+3];
		v=abs(v0-v1);
		if (v<minv) minv=v, closesti=i, closestv=v1;
	    } // currenti is index of descriptor with *closest* velocity

	    targetv=closestv;
	    speed_factor=0.0;

	    if (velocity==targetv)
		break;	// already in shape, nothing to do...

	    cmd[0]=0xBB;		// SET CD SPEED
	    cmd[1]=wsdp[closesti+1];	// Rotation Control
	    cmd[2]=cmd[3]=0xff;		// Read Speed
	    cmd[4]=wsdp[closesti+2];	// Write Speed
	    cmd[5]=wsdp[closesti+3];
	    cmd[11]=0;
	    if ((err=cmd.transport()))
		sperror ("SET CD SPEED",err),
		exit (FATAL_START(errno));

	    cmd[0] = 0x5A;	// MODE SENSE
	    cmd[1] = 0x08;	// "Disable Block Descriptors"
	    cmd[2] = 0x2A;	// "Capabilities and Mechanical Status"
	    cmd[7] = plen>>8;
	    cmd[8] = plen;	// real length this time
	    cmd[9] = 0;
	    if ((err=cmd.transport(READ,page2A,plen)))
		sperror ("MODE SENSE#2A",err),
		exit(FATAL_START(errno));
	}
	else if (targetv)
	{   if (targetv!=velocity)
		fprintf (stderr,":-( Failed to change write speed: %d->%d\n",
				(int)velocity,targetv),
		exit (FATAL_START(EINVAL));
	    break;
	}
    } while (targetv);

  return targetv;
}

static int plusminus_pages_setup (Scsi_Command &cmd,int profile)
{ unsigned int   plen,hlen,dvddash;
  unsigned char  header[16],p32;
  int            err;
  class autofree page2A;

    switch (profile)
    {	case 0x1A:	// DVD+RW
	    p32 = 0;
	    dvddash = 0;
	    break;
	case 0x1B:	// DVD+R
	    //
	    // Even though MMC-4 draft explicitely exempts DVD+RW/+R media
	    // from those being affected by Page 05h settings, some
	    // firmwares apparently pay attention to Multi-session flag
	    // when finalizing DVD+R media. Well, we probably can't blame
	    // vendors as specification is still a work in progress, not to
	    // mention that it was published after DVD+R was introduced to
	    // the market.
	    //
	    p32 = dvd_compat?0x02:0xC0;
	    dvddash = 0;
	    break;
	case 0x13:	// DVD-RW Restricted Overwrite
	    p32 = 0xC0;	// NB! Test Write in not option here
	    dvddash = 1;
	    break;
	case 0x11:	// DVD-R Sequential
	case 0x14:	// DVD-RW Sequential
	case 0x15:	// DVD-R Dual Layer Sequential
	    p32 = 0xC0;
	    if (next_track==1) do
	    {	if (dvd_compat >= (profile==0x14?2:256))
		{   is_dao = 1,
		    fprintf (stderr,"%s: engaging DVD-%s DAO upon user request...\n",
				    ioctl_device,profile==0x14?"RW":"R");
		    break;
		}

		// Try to figure out if we have to go for DAO...
		cmd[0] = 0x46;	// GET CONFIGURATION
		cmd[1] = 2;	// ask for the only feature...
		cmd[3] = 0x21;	// the "Incremental Streaming Writable" one
		cmd[8] = 16;	// The feature should be there, right?
		cmd[9] = 0;
		if ((err=cmd.transport (READ,header,16)))
		    sperror ("GET FEATURE 21h",err),
		    exit(FATAL_START(errno));

		hlen = header[0]<<24|header[1]<<16|header[2]<<8|header[3];
		// See if Feature 21h is "current," if not, engage DAO...
		if (hlen>=12 && (header[8+2]&1)==0)
		{   is_dao = dvd_compat = 1;
		    fprintf (stderr,"%s: FEATURE 21h is not on, engaging DAO...\n",
				    ioctl_device);
		    break;
		}
	    } while (0);
	
	    if (is_dao)		p32 |=2;	// DAO

	    if (profile==0x15 || // DVD-R DL Seq has no notion of multi-session
		dvd_compat)	p32 &= 0x3F;	// Single-session

	    if (test_write)
		p32 |= 0x10;	// Test Write for debugging purposes

	    dvddash=1;
	    break;
	case 0x16:	// DVD-R Dual Layer Jump
	    p32 = 0xC0;
	    p32 |= 4;	// Jump
	    if (test_write)
		p32 |= 0x10;	// Test Write for debugging purposes

	    dvddash=1;
	    break;
	default:
	    p32 = 0;
	    dvddash = 0;
	    break;
    }

    if (dvddash || p32)	page05_setup (cmd,profile,p32);

    page2A = pull_page2A (cmd);

    plen = (page2A[0]<<8|page2A[1]) + 2;
    hlen = 8 + (page2A[6]<<8|page2A[7]);

    unsigned char * const p = page2A + hlen;

    if (plen<(hlen+14) || p[1]<(14-2))	// no "Buffer Size Supported"
	buf_size = 512;			// bogus value
    else
    	buf_size = (p[12]<<8|p[13]);
    if (buf_size<512)			// bogus value
	buf_size = 512;

    // GET PERFORMANCE/SET STREAMING are listed as mandatory, so that
    // this is actually the most likely path...
    if (set_speed_B6h (cmd,dvddash,page2A) >= 0)
	goto opc;

    if (plen<(hlen+30) || p[1]<(30-2))	// no "Current Write Speed" present
    {	if (dvddash)
	{   fprintf (stderr,":-( \"Current Write Speed\" descriptor "
			    "is not present, faking 1x...\n"),
	    velocity = ONEX;
	    speed_factor = 0.0;
	}
	else
	    velocity = 0;
    }
    else do
    {	velocity = p[28]<<8|p[29];

	// Some units, most notably NEC and derivatives, were observed
	// to report CD-R descriptors...
	if (velocity < ONEX)			// must be bogus
	{   velocity=0;
	    break;
	}

	if (plen<(hlen+32) || p[1]<(32-2))	// no write descriptors
	    break;

	int            n = p[30]<<8|p[31],minv=1<<16;
	unsigned char *p_= p+32;
	for (;n--;p_+=4)
	{   int v=p_[2]<<8|p_[3];
	    if (v==0) continue;		// Lite-On returns zeros here...
	    if (v<ONEX)			// must be bogus
		velocity=0;
	    if (v < minv)
		minv = v;
	}
	if (!dvddash && minv>2*ONEX)
	    // See corresponding comment in set_speed_B6h()
	    speed_factor /= 2.4;

    } while (0);

    if (speed_factor != 0.0 &&
	(velocity==0 || set_speed_BBh (cmd,page2A)<0) )
	fprintf (stderr,":-( can't control writing velocity, "
			"-speed option is ignored\n");
opc:

    if (!dvddash || test_write || no_opc) return 0;

    // See if OPC is required...
    cmd[0] = 0x51;	// READ DISC INFORMATION
    cmd[8] = 8;
    cmd[9] = 0;
    if ((err=cmd.transport (READ,header,8)))
	sperror ("READ DISC INFORMATION",err),
	exit(FATAL_START(errno));

    if ((header[0]<<8|header[1]) <= 0x20)
    {	cmd[0] = 0x54;	// SEND OPC INFORMATION
	cmd[1] = 1;	// "Perform OPC"
	cmd[9] = 0;
	cmd.timeout(120);	// NEC units can be slooo...w
	if ((err=cmd.transport())) do
	{   if (err==0x17301)	// "POWER CALIBRATION AREA ALMOST FULL"
	    {	fprintf (stderr,":-! WARNING: Power Calibration Area "
	    			    "is almost full\n");
		break;
	    }
	    sperror ("PERFORM OPC",err);
	    if ((err&0x0FF00)==0x07300)	exit(FATAL_START(errno));
	    /*
	     * Th rest of errors are ignored, most notably observed:
	     * 0x52000, "INVALID COMMAND"		Panasonic LD-F321
	     * 0x52700, "WRITE PROTECTED"		Plextor PX-712A
	     * 0x52C00, "COMMAND SEQUENCE ERROR"	Plextor PX-716UF
	     * 0x53002, "MEDIA NOT SUPPORTED"		Toshiba TS-H542A
	     */
	} while (0);
    }

    handle_events(cmd);

  return 0;
}

static int minus_rw_quickgrow (Scsi_Command &cmd,off64_t size)
{ unsigned char	format[12];
  unsigned int	lead_out,blocks,type;
  int		i,len,err;

    type = formats[4+4]&3;

    if (type==2 && size!=0)
    {	blocks = size/2048;
	blocks += 15, blocks &= ~15;

	lead_out = 0;
	lead_out |= formats[4+0], lead_out <<= 8;
	lead_out |= formats[4+1], lead_out <<= 8;
	lead_out |= formats[4+2], lead_out <<= 8;
	lead_out |= formats[4+3];

	if (blocks<=lead_out)	// no need to grow the session...
	    return 0;
    }

    // look for Quick Grow descriptor...
    for (i=8,len=formats[3];i<len;i+=8)
	if ((formats [4+i+4]>>2) == 0x13) break;

    if (i==len)			// no Quick Grow descriptor
    {	if (type != 2)
	    quickgrown=1;	// in reality quick formatted...
	return 0;
    }
    else
    {	blocks = 0;
	blocks |= formats[i+0], blocks <<= 8;
	blocks |= formats[i+1], blocks <<= 8;
	blocks |= formats[i+2], blocks <<= 8;
	blocks |= formats[i+3];
	if (type==2 && blocks==0)	// nowhere no grow...
	    return 0;
    }

    quickgrown=1;
    fprintf (stderr,"%s: \"Quick Grow\" session...\n",ioctl_device);

    memset (format,0,sizeof(format));
    format [1] = 2;		// "IMMED"
    format [3] = 8;		// "Length"
    format [8] = 0x13<<2;	// "Quick Grow"
    format [11] = 16;

    cmd[0] = 0x4;		// FORMAT UNIT
    cmd[1] = 0x11;
    cmd[5] = 0;
    if ((err=cmd.transport (WRITE,format,sizeof(format))))
    {	sperror ("QUICK GROW",err);
	return 1;
    }

  return wait_for_unit (cmd);
}

static int minus_r_reserve_track (Scsi_Command &cmd,off64_t size)
{ int          err;
  unsigned int blocks;

    blocks = size/2048;
    if (is_dao)	dao_blocks = blocks;
    else	blocks += 15, blocks &= ~15;

    fprintf (stderr,"%s: reserving %u blocks",ioctl_device,blocks);
    if (is_dao && blocks<380000)
	fprintf (stderr,"\b, warning for short DAO recording"),
	poll (NULL,0,5000);
    fprintf (stderr,"\n");
			

    cmd[0] = 0x53;		// RESERVE TRACK
    cmd[5] = blocks>>24;
    cmd[6] = blocks>>16;
    cmd[7] = blocks>>8;
    cmd[8] = blocks;
    cmd[9] = 0;
    if ((err=cmd.transport ()))
    {	sperror ("RESERVE TRACK",err);
	return 1;
    }

  return 0;
}

static void plus_r_dl_split (Scsi_Command &cmd,off64_t size)
{ int           err;
  unsigned int  blocks,split;
  unsigned char dvd_20[4+8];

    cmd[0] = 0xAD;	// READ DVD STRUCTURE
    cmd[7] = 0x20;	// "DVD+R Double Layer Boundary Information"
    cmd[9] = sizeof(dvd_20);
    cmd[11] = 0;
    if ((err=cmd.transport(READ,dvd_20,sizeof(dvd_20))))
    	sperror ("READ DVD+R DL BOUNDARY INFORMATION",err),
	exit (FATAL_START(errno));

    if ((dvd_20[0]<<8|dvd_20[1]) < 10) 
	fprintf (stderr,":-( insane DVD+R DL BI structure length\n"),
	exit (FATAL_START(EINVAL));

    if (dvd_20[4]&0x80)
    {	fprintf (stderr,":-? L0 Data Zone Capacity is set already\n");
	return;
    }

    split = dvd_20[8]<<24|dvd_20[9]<<16|dvd_20[10]<<8|dvd_20[11];

    blocks = size/2048;
    blocks += 15, blocks &= ~15;

    if (blocks <= split)
	fprintf (stderr,":-( more than 50%% of space will be *wasted*!\n"
			"    use single layer media for this recording\n"),
	exit (FATAL_START(EMEDIUMTYPE));

    blocks /= 16;
    blocks += 1;
    blocks /= 2;
    blocks *= 16;

    fprintf (stderr,"%s: splitting layers at %u blocks\n",
		    ioctl_device,blocks);

    memset (dvd_20,0,sizeof(dvd_20));
    dvd_20[1]  = sizeof(dvd_20)-2;
    dvd_20[8]  = blocks>>24;
    dvd_20[9]  = blocks>>16;
    dvd_20[10] = blocks>>8;
    dvd_20[11] = blocks;

    cmd[0] = 0xBF;	// SEND DVD STRUCTURE
    cmd[7] = 0x20;	// "DVD+R Double Layer Recording Information"
    cmd[9] = sizeof(dvd_20);
    cmd[11] = 0;
    if ((err=cmd.transport(WRITE,dvd_20,sizeof(dvd_20))))
	sperror ("SEND DVD+R DOUBLE LAYER RECORDING INFORMATION",err),
	exit (FATAL_START(errno));

}

static int flush_cache (Scsi_Command &cmd, int even_sync=1)
{ int err;

    cmd[0] = 0x35;		// FLUSH CACHE
    cmd[1] = 0x02;		// "IMMED"
    cmd[9] = 0;
    if (!(err=cmd.transport()))
	wait_for_unit (cmd);
    else
	sperror ("FLUSH CACHE",err);

    if (even_sync)		// Pioneer apparently needs this,
    {				// non-IMMED FLUSH that is...
	cmd[0] = 0x35;		// FLUSH CACHE
	cmd[9] = 0;
	if (is_dao) cmd.timeout (15*60);
	if ((err=cmd.transport()))
	{   sperror ("SYNCHRONOUS FLUSH CACHE",err);
	    return err;
	}
    }

  return 0;
}

//
// atexit/signal handlers
//
extern "C"
void no_r_finalize ()
{   while (media_written)
    { Scsi_Command cmd(ioctl_handle);

	fprintf (stderr,"%s: flushing cache\n",ioctl_device);
	if (flush_cache (cmd))	break;

	pioneer_stop (cmd);

	media_written = 0;
	errno = 0;
    }
    _exit (errno);
}

extern "C"
void plus_rw_finalize ()
{   sigs_mask(SIGS_BLOCK);
    while (media_written)
    { Scsi_Command cmd(ioctl_handle);
      int err;

	fprintf (stderr,"%s: flushing cache\n",ioctl_device);
	if (flush_cache (cmd))	break;

	if (!dvd_compat)
	{   fprintf (stderr,"%s: stopping de-icing\n",ioctl_device);
	    cmd[0] = 0x5B;	// CLOSE TRACK/SESSION
	    cmd[1] = 0x01;	// "IMMED"
	    cmd[2] = 0;		// "Stop De-Icing"
	    cmd[9] = 0;
	    if ((err=cmd.transport()))
		sperror ("STOP DE-ICING",err);

	    if (wait_for_unit (cmd)) break;
	}

	fprintf (stderr,"%s: writing lead-out\n",ioctl_device);
	cmd[0] = 0x5B;		// CLOSE TRACK/SESSION
	cmd[1] = 0x01;		// "IMMED"
	cmd[2] = 0x02;		// "Close session"
	cmd[9] = 0;
	if ((err=cmd.transport()))
	    sperror ("CLOSE SESSION",err);

	if (wait_for_unit (cmd)) break;
 
	media_written = 0;
	next_wr_addr = 0;
	errno = 0;

	if (do_reload && media_reload())
	    perror (":-( unable to reload tray");// not actually reached
    }
    _exit (errno);
}

extern "C"
void plus_r_finalize ()
{   sigs_mask(SIGS_BLOCK);
    while (media_written)
    { Scsi_Command cmd(ioctl_handle);
      int          mode,err;

	fprintf (stderr,"%s: flushing cache\n",ioctl_device);
	if (flush_cache(cmd))	break;

	fprintf (stderr,"%s: closing track\n",ioctl_device);
	cmd[0] = 0x5B;		// CLOSE TRACK/SESSION
	cmd[1] = 0x01;		// "IMMED"
	cmd[2] = 0x01;		// "Close Track"
	cmd[4] = next_track>>8;
	cmd[5] = next_track;
	cmd[9] = 0;
	if ((err=cmd.transport()))
	    sperror ("CLOSE TRACK",err);

	if (wait_for_unit (cmd)) break;

	if (dvd_compat)
	    // HP dvd520n insists on "finalize with minimum radius"
	    // with DVD+R DL ---------------vv
	    mode = ((mmc_profile&0xFFFF)==0x2B)?0x05:0x06,
	    fprintf (stderr,"%s: closing disc\n",ioctl_device);
	else
	    mode = 0x02,
	    fprintf (stderr,"%s: closing session\n",ioctl_device);

	while (1)	// Pioneer DVR-K04RA
	{   cmd[0] = 0x5B;		// CLOSE TRACK/SESSION
	    cmd[1] = 0x01;		// "IMMED"
	    cmd[2] = mode;		// "Close session"
	    cmd[9] = 0;
	    err = cmd.transport();
	    if (err == 0x20407)		// "OP IN PROGRESS"
	    {	poll (NULL,0,stdnap);
		continue;
	    }
	    else if (err)
		sperror (dvd_compat?"CLOSE DISC":"CLOSE SESSION",err);
	    break;
	}

	if (wait_for_unit (cmd)) break;
 
	media_written = 0;
	errno = 0;

	if (do_reload && media_reload())
	    perror (":-( unable to reload tray");// not actually reached
    }
    _exit (errno);
}

extern "C"
void minus_rw_finalize ()
{   sigs_mask(SIGS_BLOCK);
    while (media_written)
    { Scsi_Command cmd(ioctl_handle);
      int err;

	fprintf (stderr,"%s: flushing cache\n",ioctl_device);
	if (flush_cache(cmd))	break;

	if (quickgrown)
	{   fprintf (stderr,"%s: writing lead-out\n",ioctl_device);
	    cmd[0] = 0x5B;		// CLOSE TRACK/SESSION
	    cmd[1] = 0x01;		// "IMMED"
	    cmd[2] = 0x02;		// "Close Session"
	    cmd[9] = 0;
	    if ((err=cmd.transport()))
		sperror ("CLOSE SESSION",err);

	    if (wait_for_unit (cmd)) break;

	    quickgrown=0;
	}

	media_written = 0;
	next_wr_addr = 0;
	errno = 0;

	if (do_reload)
	{   if (media_reload())
		perror (":-( unable to reload tray");// not actually reached
	}
	else return;	// Restricted Overwrite is just ugly!
    }
    _exit (errno);
}

extern "C"
void minus_r_finalize ()
{   sigs_mask(SIGS_BLOCK);
    while (media_written)
    { Scsi_Command cmd(ioctl_handle);
      int err;

	fprintf (stderr,"%s: flushing cache\n",ioctl_device);
	if (flush_cache(cmd,!is_dao))	break;

	if (!is_dao)
	{   fprintf (stderr,"%s: updating RMA\n",ioctl_device);
	    cmd[0] = 0x5B;	// CLOSE TRACK/SESSION
	    cmd[1] = 0x01;	// "IMMED"
	    cmd[2] = 0x01;	// "Close Track"
	    cmd[4] = next_track>>8;
	    cmd[5] = next_track;
	    cmd[9] = 0;
	    if ((err=cmd.transport()))
		sperror ("CLOSE TRACK",err);

	    if (wait_for_unit (cmd)) break;

	    fprintf (stderr,"%s: closing %s\n",ioctl_device,
			    dvd_compat?"disc":"session");

	    while (1)	// Pioneer DVR-K04RA
	    {	cmd[0] = 0x5B;	// CLOSE TRACK/SESSION
		cmd[1] = 0x01;	// "IMMED"
		cmd[2] = 0x02;	// "Close Session"
		cmd[9] = 0;
		err = cmd.transport();
		if (err == 0x20407)		// "OP IN PROGRESS"
		{   poll (NULL,0,stdnap);
		    continue;
		}
		else if (err)
		    sperror (dvd_compat?"CLOSE DISC":"CLOSE SESSION",err);
		break;
	    }

	    if (wait_for_unit (cmd)) break;
	}
 
	media_written = 0;
	errno = 0;

	if (do_reload && media_reload())
	    perror (":-( unable to reload tray");// not actually reached
    }
    _exit (errno);
}

extern "C"
void ram_reload ()
{   sigs_mask(SIGS_BLOCK);
    if (media_written && do_reload) media_reload();
    _exit (errno);
}

//
// poor_mans_setup takes care of a lot of things.
// It's invoked right before first write and if necessary/applicable
// prepares Page 05, reserves track, sets up atexit and signal handlers.
//

typedef ssize_t (*pwrite64_t)(int,const void *,size_t,off64_t);

extern "C"
pwrite64_t poor_mans_setup (void *fd,off64_t leadout)
{ Scsi_Command cmd(ioctl_handle=fd);
  int err,profile=mmc_profile&0xFFFF;

    // We might have loaded media ourselves, in which case we
    // should lock the door...
    cmd[0] = 0x1E;  // PREVENT/ALLOW MEDIA REMOVAL
    cmd[4] = 1;     // "Prevent"
    cmd[5] = 0;
    if ((err=cmd.transport ()))
	sperror ("PREVENT MEDIA REMOVAL",err),
	exit (FATAL_START(errno));

    handle_events(cmd);

    switch (profile)
    {	case 0x1A:	// DVD+RW
	    switch (disc_info[7]&3)	// Background formatting
	    {	case 0:	// blank
			plus_rw_format (cmd);
			break;
		case 1:	// suspended
			plus_rw_restart_format (cmd,leadout);
			break;
		case 2:	// in progress
		case 3: // complete
			break;
	    }

	    plusminus_pages_setup (cmd,profile);
	    atexit (plus_rw_finalize);
	    atsignals (plus_rw_finalize);
	    break;
	case 0x1B:	// DVD+R
	case 0x2B:	// DVD+R Double Layer
	    plusminus_pages_setup(cmd,profile);
	    if (profile==0x2B && next_track==1 && dvd_compat && leadout)
		plus_r_dl_split (cmd,leadout);
	    atexit (plus_r_finalize);
	    if (next_wr_addr)
	    {	atsignals (no_r_finalize);
		next_wr_addr=(unsigned int)-1;
	    }
	    else atsignals (plus_r_finalize);
	    break;
	case 0x13:	// DVD-RW Restricted Overwrite
	    plusminus_pages_setup (cmd,profile);
	    //
	    // A side note. Quick Grow can't be performed earlier,
	    // as then reading is not possible.
	    //
	    minus_rw_quickgrow (cmd,leadout);
	    atexit (minus_rw_finalize);
	    atsignals (minus_rw_finalize);
	    break;
	case 0x11:	// DVD-R Sequential
	case 0x14:	// DVD-RW Sequential
	case 0x15:	// DVD-R Dual Layer Sequential
	    plusminus_pages_setup (cmd,profile);
	    if ((profile==0x15) && leadout)
	    {	unsigned char dvd_10[4+16];
		unsigned int layer_size,data_size=leadout/2048;

		cmd[0] = 0xAD;	// READ DVD STRUCTURE
		cmd[7] = 0x10;
		cmd[9] = sizeof(dvd_10);
		cmd[11] = 0;
		if ((err=cmd.transport(READ,dvd_10,sizeof(dvd_10))))
		    sperror("READ DVD STRUCTURE #10h",err),
		    exit(FATAL_START(errno));

		layer_size  = dvd_10[4+13]<<16,
		layer_size |= dvd_10[4+14]<<8,
		layer_size |= dvd_10[4+15];

		if (data_size <= layer_size)
		    fprintf (stderr,":-( more than 50%% of space will be *wasted*!\n"
				    "    use single layer media for this recording\n"),
		    exit(FATAL_START(EMEDIUMTYPE)); 
	    }
	    if (is_dao && leadout)
		minus_r_reserve_track(cmd,leadout);
	    atexit (minus_r_finalize);
	    if (next_wr_addr)
	    {	atsignals (no_r_finalize);
		next_wr_addr=(unsigned int)-1;
	    }
	    else atsignals (minus_r_finalize);
	    break;
	case 0x12:	// DVD-RAM
	    // exclusively for speed settings...
	    plusminus_pages_setup (cmd,profile);
	    atexit (ram_reload);
	    atsignals (ram_reload);
	    break;
	case 0x16:	// DVD-R Dual Layer Jump not supported for reasons
			// discussed at web-page...
	default:
	    fprintf (stderr,":-( mounted media[%X] is not supported\n",profile),
	    exit(FATAL_START(EMEDIUMTYPE));
	    break;
    }

    if (velocity)
	fprintf (stderr,"%s: \"Current Write Speed\" is %.1fx1385KBps.\n",
			ioctl_device,velocity/(double)ONEX);

    if (next_wr_addr==(unsigned int)-1) do
    { unsigned char track[32];

	next_wr_addr = 0;

	cmd[0] = 0x52;	// READ TRACK INFORMATION
	cmd[1] = 1;
	cmd[4] = next_track>>8;
	cmd[5] = next_track&0xFF;	// last track, set up earlier
	cmd[8] = sizeof(track);
	cmd[9] = 0;
	if (cmd.transport (READ,track,sizeof(track)))
	    break;

	if (track[7]&1)	// NWA_V
	{   next_wr_addr  = track[12]<<24;
	    next_wr_addr |= track[13]<<16;
	    next_wr_addr |= track[14]<<8;
	    next_wr_addr |= track[15];
	}

	if (next_wr_addr != // track start
	    (unsigned int)(track[8]<<24|track[9]<<16|track[10]<<8|track[11]))
	    fprintf (stderr,":-? resuming track#%d from LBA#%d\n",
			    next_track,next_wr_addr);

    } while (0);
    else next_wr_addr = 0;

  return poor_mans_pwrite64;
}

extern "C"
int poor_man_rewritable (void *fd, void *buf)
{ Scsi_Command	cmd(fd);
  int		err,profile=mmc_profile&0xFFFF;

    if (profile!=0x13 &&	// not DVD-RW Restricted Overwrite
	profile!=0x12 &&	// nor DVD-RAM
	profile!=0x1A &&	// nor DVD+RW
	profile!=0x2A)		// nor DVD+RW Double Layer
	return 0;

    if (profile==0x13)		// DVD-RW Restricted Overwrite
    {	// Yet another restriction of Restricted Overwrite mode?
	// Pioneer DVR-x05 can't read a bit otherwise...
	do_reload=0;
	minus_rw_finalize ();	// with do_reload==0!
	do_reload=1;
    }
    else if (profile==0x1A ||	// DVD+RW
	     profile==0x2A)	// DVD+RW Double Layer
    {	fprintf (stderr,"%s: flushing cache\n",ioctl_device);
	if (flush_cache(cmd))	exit (errno);
    }

    cmd[0] = 0x28;	// READ(10)
    cmd[5] = 16;	// LBA#16, volume descriptor set
    cmd[8] = 16;	// 32KB
    cmd[9] = 0;
    if ((err=cmd.transport (READ,buf,16*2048)))
	sperror ("READ@LBA=10h",err),
	exit(errno);

  return 1;
}
