#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>
#include <signal.h>


static int           media_written=0,next_track=1,velocity=0,
		     is_dao=0,quickgrown=0,do_reload=1;

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

static unsigned int  next_wr_addr=1; // it starts as boolean
static unsigned char formats[260];

extern int	dvd_compat,test_write,no_reload,mmc_profile,_argc;
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);

	while (1)	// Pioneer DVR-x06 needs this...
	{   cmd[0] = 0x1B;	// START/STOP UNIT
	    cmd[1] = 0x1;	// "IMMED"
	    cmd[4] = 0;		// "Stop"
	    cmd[5] = 0;
	    if (cmd.transport() == 0x20407)	// "OP IN PROGRESS"
	    {	poll (NULL,0,333);
		continue;
	    }
	    break;
	}

#if defined(RELOAD_NEVER_NEEDED)
	return (errno=0);
#else
	if (no_reload)	return (errno=0);

	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,"%d",ioctl_fd);
	execlp(_argv[0],"-reload",str,ioctl_device,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;
	    }
	    else return 0;	// m-m-m-m! patched kernel:-)
	}
	{ 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];
  int profile=-1,once=1;
  unsigned int len;

    do {
	handle_events (cmd);

	cmd[0] = 0x46;
	cmd[8] = sizeof(buf);
	cmd[9] = 0;
	if (cmd.transport(READ,buf,sizeof(buf)))
	    perror (":-( unable to GET CONFIGURATION, non-MMC unit?"),
	    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 (cmd.transport ())
	    perror (":-( unable to LOAD TRAY"),
	    exit (FATAL_START(errno));

    } while (once--);

    // should I check for profile==0 here?

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

    cmd[0] = 0x23;	// READ FORMAT CAPACITIES
    cmd[8] = 12;
    cmd[9] = 0;
    if (cmd.transport (READ,formats,12))
	perror (":-( unable to READ FORMAT CAPACITIES"),
	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 (cmd.transport (READ,formats,4+len))
	perror (":-( unable to READ FORMAT CAPACITIES"),
	exit (FATAL_START(errno));

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

  return profile;
}

extern "C"
off64_t get_capacity (void *fd)
{ Scsi_Command	cmd(fd);
  unsigned char	buf[32];
  off64_t	ret=0;
  unsigned int	nwa,free_blocks;
  int		i,obligatory,len;

#if 0
    struct stat64	sb;

    if (mmc_profile==-1 || fstat64 (fd,&sb) || S_ISREG(sb.st_mode))
	return 0;
#endif

    obligatory=0x00;
    switch (mmc_profile)
    {	case 0x1A:
	    obligatory=0x26;
	case 0x13:
	    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];
	    ret *= formats[4+5]<<16|formats[4+6]<<8|formats[4+7];
	    break;

	case 0x1B:
	case 0x11:
	case 0x14:
	    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 (cmd.transport (READ,buf,sizeof(buf)))
	    {	perror (":-( unable to READ TRACK INFORMATION");
		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;
	    ret *= 2048;
	    break;

	default:
	    break;
    }

  return ret;
}

static
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 -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;

    while (1)
    {	cmd[0] = 0x2A;			// WRITE(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;
	//
	// 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=128;	// bogus values...
	    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
		bsize /= 2;			// drain 1/2 of the buffer
	    }
	    if (bsize < 128)	bsize = 128;
	    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;
	    }

	    if (retries > 128)
		fprintf (stderr,":-[ the LUN appears to be stuck "
				"writing LBA %xh, retry in %dms ]\n",
				lba,msecs),
		retries=0, msecs--;

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

	    lba |= 0x80000000;		// signal insane bfree...
	}
	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,
		    poll (NULL,0,5000);
		}
		else if (errcode==0x52C00)
		    fprintf (stderr,":-! \"COMMAND SEQUENCE ERROR(%x)\" at %xh. "
				    "Is media being read?\n",errcode,lba);
		continue;
	    }
	    lba |= 0x40000000;	// signal "can't SYNC CACHE"
	}
	fprintf (stderr,":-[ LBA=%xh, SENSE KEY=%Xh/ASC=%02Xh/ASCQ=%02Xh ]\n",
			lba,SK(errcode),ASC(errcode),ASCQ(errcode));
	if (lba==0)
	{   if (ASC(errcode)==0x30)
		fprintf (stderr,":-[ media is not formatted or unsupported ]\n");
	    else if (mmc_profile==0x14 /*&& ASC(errcode)==0x64*/)
		fprintf (stderr,":-[ attempt -blank=full or re-run with "
				"-dvd-compat -dvd-compat to engage DAO ]\n");
	}
	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 errcode,i,len;
  unsigned char descr[12];

    if ((formats[4+4]&3) == 1)	// Unformatted media
    {	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));

	fprintf (stderr,"%s: pre-formatting blank DVD+RW...\n",ioctl_device);

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

	cmd[0] = 0x04;		// FORMAT UNIT
	cmd[1] = 0x11;		// "FmtData" and "Format Code"
	cmd[5] = 0;
	if ((errcode=cmd.transport(WRITE,descr,sizeof(descr))))
	{   fprintf (stderr,":-( unable to FORMAT UNIT (%Xh/%02Xh/%02Xh): ",
			    SK(errcode),ASC(errcode),ASCQ(errcode)),
	    perror (NULL), exit(FATAL_START(errno));
	}

	wait_for_unit (cmd);

	cmd[0] = 0x35;		// FLUSH CACHE
	cmd[9] = 0;
	if ((errcode=cmd.transport()))
	{   fprintf (stderr,":-( unable to FLUSH CACHE (%Xh/%02Xh/%02Xh): ",
			    SK(errcode),ASC(errcode),ASCQ(errcode)),
	    perror (NULL), exit(FATAL_START(errno));
	}
    }
}

static int plus_rw_restart_format (Scsi_Command &cmd)
{ unsigned char descr[12];
  int errcode;

    memset (descr,0,sizeof(descr));
    descr[1]=0x02;		// "IMMED" flag
    descr[3]=0x08;		// "Descriptor Length" (LSB)
    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 ((errcode=cmd.transport(WRITE,descr,sizeof(descr))))
    {	fprintf (stderr,":-( unable to RESTART FORMAT (%Xh/%02Xh/%02Xh): ",
			SK(errcode),ASC(errcode),ASCQ(errcode)),
	perror (NULL);
	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,errcode;
  Scsi_Command cmd(fd);
  unsigned char buf[36];

    cmd[0] = 0x51;		// READ DISC INFORMATION
    cmd[8] = 32;
    cmd[9] = 0;
    if (cmd.transport (READ,buf,32))
	perror (":-( unable to READ DISC INFORMATION"),
	exit(FATAL_START(errno));

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

    if (((buf[2]>>2)&3) != 0)
	fprintf (stderr,":-( %s: last session is not empty\n",ioctl_device),
	exit(FATAL_START(EMEDIUMTYPE));

    next_track = buf[5]|buf[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 ((errcode=cmd.transport (READ,buf,sizeof(buf))))
    {	perror (":-( unable to READ TRACK INFORMATION");
	if (ASC(errcode)==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,":-( %s: Next Writable Address is invalid\n",
			ioctl_device), 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 (cmd.transport (READ,buf,12))
	perror (":-( unable to READ SESSION INFO"),
	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            errcode;

    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 (cmd.transport(READ,header,sizeof(header)))
	perror (":-( unable to MODE SENSE"), 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 ((errcode=cmd.transport(READ,page2A,len)))
	fprintf (stderr,":-[ MODE SENSE failed with "
			"SK=%Xh/ASC=%02Xh/ASCQ=%02Xh\n",
			SK(errcode),ASC(errcode),ASCQ(errcode)),
	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 void plus_writing_speed (Scsi_Command &cmd)
{ unsigned int   len,hlen;
  unsigned char  *p;
  class autofree {
    private:
	unsigned char *ptr;
    public:
	autofree()			{ ptr=NULL; }
	~autofree()			{ if (ptr) free(ptr); }
	unsigned char *operator=(unsigned char *str)
					{ return ptr=str; }
	operator unsigned char *()	{ return ptr; }
  } page2A;

    page2A = pull_page2A(cmd);

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

    p = page2A + hlen;

    if (len<(hlen+30) || p[1]<(30-2))	// no "Current Write Speed" present
	return;

    velocity = p[28]<<8|p[29];

    if (velocity < 1385)		// must be bogus
    {	velocity = 0; return;	}

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

    int i = p[30]<<8|p[31];
    for (p+=32;i;i--,p+=4)
	if ((p[2]<<8|p[3]) < 1385)	// must be bogus
	{   velocity = 0; return;   }
}

static int minus_rw_setup (Scsi_Command &cmd,int profile)
{ unsigned int   len,hlen;
  unsigned char  header[16],*p,p32=0xC0;
  int            errcode;
  class autofree {
    private:
	unsigned char *ptr;
    public:
	autofree()			{ ptr=NULL; }
	~autofree()			{ if (ptr) free(ptr); }
	unsigned char *operator=(unsigned char *str)
					{ return ptr=str; }
	operator unsigned char *()	{ return ptr; }
  } page2A;

    if (next_track==1 && (profile==0x14 || profile==0x11)) do
    {	// 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 (cmd.transport (READ,header,16))
	    perror (":-( unable to GET FEATURE 21h"),
	    exit(FATAL_START(errno));

	len = header[0]<<24|header[1]<<16|header[2]<<8|header[3];
	// See if Feature 21h is "current," if not, engage DAO...
	if (len>=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;
	}

	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");

    } while (0);

    if (is_dao)		p32 |=2;	// DAO
    if (dvd_compat)	p32 &= 0x3F;	// Single-session

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

    page05_setup (cmd,profile,p32);

    // WRITING VELOCITY SETUP //
    page2A = pull_page2A (cmd);

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

    p = page2A + hlen;

    if (len<(hlen+30) || p[1]<(30-2))	// no "Current Write Speed" present
	fprintf (stderr,":-[ \"Current Write Speed\" is not present.\n"),
	exit(FATAL_START(EMEDIUMTYPE));

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

	if (speed_factor != 0.0)
	{ int i,j,v,v0,minv,mini;
	  unsigned char *vdp=p+32;

	   if (len<(hlen+32) || p[1]<(32-2))
		fprintf (stderr,":-[ Can't control writing velocity, "
				"skip -speed command line option.\n"),
		exit(FATAL_START(EINVAL));

	    j=(p[30]<<8|p[31])*4;
	    for (minv=0x7fffffff,i=0;i<j;i+=4)
	    {	v=vdp[i+2]<<8|vdp[i+3];
		if (v<minv) minv=v;
	    } // minv will be treated as 1x
	    v0=(int)(speed_factor*minv + 0.5);
	    for (mini=0,minv=0x7fffffff,i=0;i<j;i+=4)
	    {	v=abs(v0-(vdp[i+2]<<8|vdp[i+3]));
		if (v<minv) minv=v, mini=i;
	    } // mini is index of descriptor with *closest* velocity

	    velocity0=vdp[mini+2]<<8|vdp[mini+3];
	    speed_factor=0.0;

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

	    cmd[0]=0xBB;	// SET CD SPEED
	    cmd[1]=vdp[mini+1];	// Rotation Control
	    cmd[2]=cmd[3]=0xff;	// Read Speed
	    cmd[4]=vdp[mini+2];	// Write Speed
	    cmd[5]=vdp[mini+3];
	    cmd[11]=0;
	    if ((errcode=cmd.transport()))
		fprintf (stderr,":-[ SET CD SPEED failed with "
				"SK=%Xh/ASC=%02Xh/ASCQ=%02Xh\n",
				SK(errcode),ASC(errcode),ASCQ(errcode)),
		exit(FATAL_START(errno));

	    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 ((errcode=cmd.transport(READ,page2A,len)))
		fprintf (stderr,":-[ MODE SENSE failed with "
				"SK=%Xh/ASC=%02Xh/ASCQ=%02Xh\n",
				SK(errcode),ASC(errcode),ASCQ(errcode)),
		exit(FATAL_START(errno));
	}
	else if (velocity0)
	{   if (velocity0!=velocity)
		fprintf (stderr,":-[ Failed to change write speed: %d->%d\n",
				velocity,velocity0),
		exit(FATAL_START(EINVAL));
	    break;
	}
    } while (velocity0);

#if 0
    velocity *= 1024;
#endif
    // END OF WRITING VELOCITY SETUP //

    // See if OPC is required...
    cmd[0] = 0x51;	// READ DISC INFORMATION
    cmd[8] = 8;
    cmd[9] = 0;
    if (cmd.transport (READ,header,8))
	perror (":-( unable to READ DISC INFORMATION"),
	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 ((errcode=cmd.transport()))
	    fprintf (stderr,":-[ PERFORM OPC failed with "
			    "SK=%Xh/ASC=%02Xh/ASCQ=%02Xh\n",
			    SK(errcode),ASC(errcode),ASCQ(errcode)),
	    exit(FATAL_START(errno));
    }

    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,errcode;

    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 ((errcode=cmd.transport (WRITE,format,sizeof(format))))
    {	fprintf (stderr,":-[ unable to QUICK GROW (%Xh/%02Xh/%02Xh)",
			SK(errcode),ASC(errcode),ASCQ(errcode));
	return 1;
    }

  return wait_for_unit (cmd);
}

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

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

    fprintf(stderr,"%s: reserving %u blocks\n",ioctl_device,blocks);

    cmd[0] = 0x53;		// RESERVE TRACK
    cmd[5] = blocks>>24;
    cmd[6] = blocks>>16;
    cmd[7] = blocks>>8;
    cmd[8] = blocks;
    cmd[9] = 0;
    if ((errcode=cmd.transport ()))
    {	fprintf (stderr,":-[ unable to RESERVE TRACK (%Xh/%02Xh/%02Xh)]\n",
			SK(errcode),ASC(errcode),ASCQ(errcode));
	return 1;
    }

  return 0;
}

static int flush_cache (Scsi_Command &cmd)
{ int err;

    cmd[0] = 0x35;		// FLUSH CACHE
    cmd[1] = 0x02;		// "IMMED"
    cmd[9] = 0;
    if (!(err=cmd.transport()))
	wait_for_unit (cmd);
    else
	perror (":-( unable to FLUSH CACHE");

#if 1	// 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()))
    {	perror (":-( unable to FLUSH CACHE synchronously");
	return err;
    }
#endif

  return 0;
}

//
// atexit/signal handlers
//
#define block_sigs()	do {	\
    sigset_t mask;		\
				\
    sigemptyset (&mask);	\
    sigaddset (&mask,SIGHUP),	\
    sigaddset (&mask,SIGINT),	\
    sigaddset (&mask,SIGTERM),	\
    sigaddset (&mask,SIGPIPE);	\
				\
    sigprocmask (SIG_BLOCK,	\
	&mask,NULL);		\
} while (0)

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;

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

extern "C"
void plus_rw_finalize ()
{   block_sigs();
    while (media_written)
    { Scsi_Command cmd(ioctl_handle);
      int errcode;

	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 ((errcode=cmd.transport()))
		fprintf (stderr,":-( unable to CLOSE SESSION (%Xh/%02Xh/%02Xh): ",
				SK(errcode),ASC(errcode),ASCQ(errcode)),
		perror (NULL);

	    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 ((errcode=cmd.transport()))
	    fprintf (stderr,":-( unable to CLOSE SESSION (%Xh/%02Xh/%02Xh): ",
			    SK(errcode),ASC(errcode),ASCQ(errcode)),
	    perror (NULL);

	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 ()
{   block_sigs();
    while (media_written)
    { Scsi_Command cmd(ioctl_handle);
      int          mode,errcode;

	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 ((errcode=cmd.transport()))
	    fprintf (stderr,":-( unable to CLOSE TRACK (%Xh/%02Xh/%02Xh): ",
			    SK(errcode),ASC(errcode),ASCQ(errcode)),
	    perror (NULL);

	if (wait_for_unit (cmd)) break;

	if (dvd_compat)
	    mode=0x06, fprintf (stderr,"%s: closing disc\n",ioctl_device);
	else
	    mode=0x02, fprintf (stderr,"%s: closing session\n",ioctl_device);

	cmd[0] = 0x5B;		// CLOSE TRACK/SESSION
	cmd[1] = 0x01;		// "IMMED"
	cmd[2] = mode;		// "Close session"
	cmd[9] = 0;
	if ((errcode=cmd.transport()))
	    fprintf (stderr,":-( unable to CLOSE SESSION (%Xh/%02Xh/%02Xh): ",
			    SK(errcode),ASC(errcode),ASCQ(errcode)),
	    perror (NULL);

	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 ()
{   block_sigs();
    while (media_written)
    { Scsi_Command cmd(ioctl_handle);
      int errcode;

	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 ((errcode=cmd.transport()))
		fprintf (stderr,":-( unable to CLOSE SESSION (%Xh/%02Xh/%02Xh): ",
				SK(errcode),ASC(errcode),ASCQ(errcode)),
		perror (NULL);

	    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 ()
{   block_sigs();
    while (media_written)
    { Scsi_Command cmd(ioctl_handle);
      int errcode;

	fprintf (stderr,"%s: flushing cache\n",ioctl_device);
	if (flush_cache(cmd))	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 ((errcode=cmd.transport()))
		fprintf (stderr,":-( unable to CLOSE TRACK (%Xh/%02Xh/%02Xh): ",
				SK(errcode),ASC(errcode),ASCQ(errcode)),
		perror (NULL);

	    if (wait_for_unit (cmd)) break;

	    fprintf (stderr,"%s: closing %s\n",ioctl_device,
			    dvd_compat?"disc":"session");
	    cmd[0] = 0x5B;	// CLOSE TRACK/SESSION
	    cmd[1] = 0x01;	// "IMMED"
	    cmd[2] = 0x02;	// "Close Session"
	    cmd[9] = 0;
	    if ((errcode=cmd.transport()))
		fprintf (stderr,":-( unable to CLOSE %s (%Xh/%02Xh/%02Xh): ",
				dvd_compat?"DISC":"SESSION",
				SK(errcode),ASC(errcode),ASCQ(errcode)),
		perror (NULL);

	    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);
}

//
// 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.
//
#define sigit(SIG,f)	do {	\
    sigaction (SIG,NULL,&sa);	\
    sa.sa_mask = mask;		\
    sa.sa_handler = f;		\
    sa.sa_flags &= ~SA_NODEFER;	\
    sa.sa_flags |= SA_RESETHAND;\
    sigaction (SIG,&sa,NULL);	\
} while (0)
static void atsignals(void(*func)())
{ void           (*f)(int)=(void(*)(int))func;
  sigset_t         mask;
  struct sigaction sa;

    sigemptyset (&mask);
    sigaddset (&mask,SIGHUP),	sigaddset (&mask,SIGINT),
    sigaddset (&mask,SIGTERM),	sigaddset (&mask,SIGPIPE);

    sigit (SIGHUP,f);	sigit (SIGINT,f);
    sigit (SIGTERM,f);	sigit (SIGPIPE,f);
}
#undef sigit

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

extern "C"
pwrite64_t poor_mans_setup (void *fd,int profile,off64_t leadout)
{ Scsi_Command cmd(ioctl_handle=fd);
  unsigned char dinfo[8];
  int errcode;

    handle_events(cmd);

    switch (profile)
    {	case 0x1A:	// DVD+RW
	    cmd[0] = 0x51;	// READ DISC INFORMATION
	    cmd[8] = sizeof(dinfo);
	    cmd[9] = 0;
	    if ((errcode=cmd.transport(READ,dinfo,sizeof(dinfo))))
		fprintf (stderr,":-( unable to READ DISC INFORMATION "
				"(%Xh/%02Xh/%02Xh): ",
				SK(errcode),ASC(errcode),ASCQ(errcode)),
		perror (NULL), exit (FATAL_START(errno));

	    switch (dinfo[7]&3)	// Background formatting
	    {	case 0:	// blank
			plus_rw_format (cmd);
			break;
		case 1:	// suspended
			if (dvd_compat) plus_rw_restart_format (cmd);
			break;
		case 2:	// in progress
		case 3: // complete
			break;
	    }

	    plus_writing_speed (cmd);
	    atexit (plus_rw_finalize);
	    atsignals (plus_rw_finalize);
	    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.
	    //
	    page05_setup(cmd,profile,dvd_compat?0x02:0xC0);
	    plus_writing_speed (cmd);
	    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
	    if (minus_rw_setup (cmd,profile))
		perror (":-( unable to setup the Write Page"),
		exit (FATAL_START(errno));
	    //
	    // 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
	    if (minus_rw_setup (cmd,profile))
		perror (":-( unable to setup the Write Page"),
		exit (FATAL_START(errno));
	    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;
	default:
	    fprintf (stderr,":-( %s: mounted media[%X] is not supported\n",
	    		    ioctl_device,profile),
	    exit(FATAL_START(EMEDIUMTYPE));
	    break;
    }

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

    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;

    if (mmc_profile!=0x13 &&	// not DVD-RW Restricted Overwrite
	mmc_profile!=0x1A)	// nor DVD+RW
	return 0;

    if (mmc_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 (mmc_profile==0x1A)	// DVD+RW
    {	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)))
	fprintf (stderr,":-( %s: unable to READ (%Xh/%02Xh/%02Xh): ",
			ioctl_device,SK(err),ASC(err),ASCQ(err)),
	perror (NULL), exit(errno);

  return 1;
}
