/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */

/* Copyright (c) 2004 - 2006 Derek Foreman, Ben Jansens
   Copyright (c) 2006 - 2021 Thomas Schmitt <scdbackup@gmx.net>
   Provided under GPL version 2 or later.
*/


#include "../config.h"

#include <unistd.h>
#include <signal.h>

/* ts A61009 */
/* #include <a ssert.h> */


/* ts A61106 : Deliberate defect provocation macros
               DO NOT DEFINE THESE IF YOU WANT SUCCESSFUL TAO !
#define Libburn_experimental_no_close_tracK 1
#define Libburn_experimental_no_close_sessioN 1
*/

/* ts A61114 : Highly experimental : try to achieve SAO on appendables
               THIS DOES NOT WORK YET !
#define Libburn_sao_can_appenD 1
*/

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/time.h>

/* ts B41126 : O_BINARY is needed for Cygwin but undefined elsewhere */
#ifndef O_BINARY
#define O_BINARY 0
#endif

#include "error.h"
#include "sector.h"
#include "libburn.h"
#include "drive.h"
#include "transport.h"
#include "debug.h"
#include "init.h"
#include "toc.h"
#include "util.h"
#include "sg.h"
#include "write.h"
#include "options.h"
#include "structure.h"
#include "source.h"
#include "mmc.h"
#include "spc.h"

#include "libdax_msgs.h"
extern struct libdax_msgs *libdax_messenger;


/* ts A91120 : <<< experimental */
#ifdef Libburn_mmap_write_buffeR
#include <sys/mman.h>
#endif


/* The maximum output size to be used with CD media. This is also curbed
   by BURN_OS_TRANSPORT_BUFFER_SIZE. The smaller number gets into effect.
*/ 
#define Libburn_cd_obS (32 * 1024)

/* The size to be used with DVD media.
*/
#define Libburn_dvd_obS (32 * 1024)

/* The size to be used with BD-RE media in normal, not streamed mode.
*/
#define Libburn_bd_re_obS (64 * 1024)

/* The size to be used with BD-R media in normal, not streamed mode.
*/
#define Libburn_bd_r_obS (64 * 1024)

/* The size to be used with BD-RE and BD-R media in streamed mode.
*/
#define Libburn_bd_streamed_obS (64 * 1024)

/* The number of retries if write(2) returns a short, non-negative write count.
*/
#define Libburn_stdio_write_retrieS 16


static int type_to_ctrl(int mode)
{
	int ctrl = 0;

	int data = BURN_MODE2 | BURN_MODE1 | BURN_MODE0;

	if (mode & data) {
		ctrl |= 4;
	} else if (mode & BURN_AUDIO) {
		if (mode & BURN_4CH)
			ctrl |= 8;
		if (mode & BURN_PREEMPHASIS)
			ctrl |= 1;
	} else
		/* ts A61008 */
		/* a ssert(0); */
		return -1;

	if (mode & BURN_COPY)
		ctrl |= 2;

	return ctrl;
}

/* only the ctrl nibble is set here (not adr) */
/* ts A61009 : removed "static" , reacted on type_to_ctrl() == -1
               preserved ignorance towards unknown modes (for now) */
void type_to_form(int mode, unsigned char *ctladr, int *form)
{
	int ret;

	ret = type_to_ctrl(mode) << 4;
	if (ret == -1) {
		*ctladr = 0xff;
		*form = -1;
		return;
	}
	*ctladr = ret;

	if (mode & BURN_AUDIO)
		*form = 0;
	if (mode & BURN_MODE0) {

		/* ts A61009 */
		/* a ssert(0); */
		*form = -1;
		return;
	}

	if (mode & BURN_MODE1)
		*form = 0x10;
	if (mode & BURN_MODE2) {

		/* ts A61009 */
		/* a ssert(0); */ /* XXX someone's gonna want this sometime */
		*form = -1;
		return;
	}

	if (mode & BURN_MODE_RAW)
		*form = 0;
	if (mode & BURN_SUBCODE_P16)	/* must be expanded to R96 */
		*form |= 0x40;
	if (mode & BURN_SUBCODE_P96)
		*form |= 0xC0;
	if (mode & BURN_SUBCODE_R96)
		*form |= 0x40;
}


/* ts A71002 : outsourced from burn_write_flush() : no sync cache here */
int burn_write_flush_buffer(struct burn_write_opts *o,struct burn_track *track)
{
	struct burn_drive *d = o->drive;

	if (d->buffer->bytes && !d->cancel) {
		int err;
		err = d->write(d, d->nwa, d->buffer);
		if (err == BE_CANCELLED)
			return 0;
		/* A61101 */
		if(track != NULL) {
			track->writecount += d->buffer->bytes;
			track->written_sectors += d->buffer->sectors;
		}
		/* ts A61119 */
		d->progress.buffered_bytes += d->buffer->bytes;

		d->nwa += d->buffer->sectors;
		d->buffer->bytes = 0;
		d->buffer->sectors = 0;
	}
	return 1;
}


int burn_write_flush(struct burn_write_opts *o, struct burn_track *track)
{
	int ret;
	struct burn_drive *d = o->drive;

	ret = burn_write_flush_buffer(o, track);
	if (ret <= 0)
		return ret;
	d->sync_cache(d);
	return 1;
}


/* ts A71002 : outsourced from burn_write_close_track() */
int burn_write_track_minsize(struct burn_write_opts *o, struct burn_session *s,
				int tnum)
{
	char msg[81];
	struct burn_drive *d;
	struct burn_track *t;
	int todo, step, cancelled, seclen;

	d = o->drive;
	t = s->track[tnum];

	/* ts A61103 : pad up track to minimum size of 600 sectors */
	if (t->written_sectors < 300) {
		todo = 300 - t->written_sectors;
		sprintf(msg,"Padding up track to minimum size (+ %d sectors)",
			todo);
		libdax_msgs_submit(libdax_messenger, o->drive->global_index,
			0x0002011a,
			LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH, msg,0,0);
		step = BUFFER_SIZE / 4096; /* shall fit any sector size */
		seclen = burn_sector_length(t->mode);
		if (seclen <= 0)
			seclen = 2048;
		memset(d->buffer, 0, sizeof(struct buffer));
		cancelled = d->cancel;
		for (; todo > 0; todo -= step) {
			if (step > todo)
				step = todo;
			d->buffer->bytes = step*seclen;
			d->buffer->sectors = step;
			d->cancel = 0;
			d->write(d, d->nwa, d->buffer);
			d->nwa += d->buffer->sectors;
			t->writecount += d->buffer->bytes;
			t->written_sectors += d->buffer->sectors;
			d->progress.buffered_bytes += d->buffer->bytes;
		}
		d->cancel = cancelled;
	}
	return 1;
}


/* ts A61030 */
int burn_write_close_track(struct burn_write_opts *o, struct burn_session *s,
				int tnum)
{
	char msg[81];
	struct burn_drive *d;

	/* ts A61106 */
#ifdef Libburn_experimental_no_close_tracK
	return 1;
#endif

	d = o->drive;

	d->busy = BURN_DRIVE_CLOSING_TRACK;

	sprintf(msg, "Closing track %2.2d", tnum+1);
	libdax_msgs_submit(libdax_messenger, o->drive->global_index,0x00020119,
			LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH, msg,0,0);

	/* MMC-1 mentions track number 0xFF for "the incomplete track",
	   MMC-3 does not. I tried both. 0xFF was in effect when other
	   bugs finally gave up and made way for readable tracks. */
	/* ts A70129 
	   Probably the right value for appendables is d->last_track_no
	*/
	d->close_track_session(o->drive, 0, 0xff);

	d->busy = BURN_DRIVE_WRITING;

	return 1;
}


/* ts A61030 */
int burn_write_close_session(struct burn_write_opts *o)
{

	/* ts A61106 */
#ifdef Libburn_experimental_no_close_sessioN
	return 1;
#endif

	libdax_msgs_submit(libdax_messenger, o->drive->global_index,0x00020119,
			LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH,
			"Closing session", 0, 0);

	/* ts A61102 */
	o->drive->busy = BURN_DRIVE_CLOSING_SESSION;

	o->drive->close_track_session(o->drive, 1, 0);

	/* ts A61102 */
	o->drive->busy = BURN_DRIVE_WRITING;

	return 1;
}


/* ts A60819, B20101:
   This is useful only when changes about CD SAO get tested.
 # define Libburn_write_with_function_print_cuE yes
*/

#ifdef Libburn_write_with_function_print_cuE


static char cue_printify(char c)
{
	if (c >= 32 && c < 127)
		return c;
	return '#';
}


static void print_cue(struct cue_sheet *sheet)
{
	int i;
	unsigned char *unit;

	printf("\n");
	printf("ctladr|trno|indx|form|scms|   msf    |  text\n");
	printf("------+----+----+----+----+----------+--------\n");
	for (i = 0; i < sheet->count; i++) {
		unit = sheet->data + 8 * i;
		if ((unit[0] & 0xf) == 2) {
			printf(
		" %1X  %1X |    |    |    |    |          | %c%c%c%c%c%c%c\n",
				(unit[0] & 0xf0) >> 4, unit[0] & 0xf,
				cue_printify(unit[1]), cue_printify(unit[2]), 
				cue_printify(unit[3]), cue_printify(unit[4]), 
				cue_printify(unit[5]), cue_printify(unit[6]), 
				unit[7] == 0 ? ' ' : cue_printify(unit[7]));
		} else if ((unit[0] & 0xf) == 3) {
			printf(
		" %1X  %1X | %2d |    |    |    |          | %c%c%c%c%c%c\n",
				(unit[0] & 0xf0) >> 4, unit[0] & 0xf,
				unit[1], cue_printify(unit[2]), 
				cue_printify(unit[3]), cue_printify(unit[4]), 
				cue_printify(unit[5]), cue_printify(unit[6]), 
				cue_printify(unit[7]));
		} else if (unit[1] > 99) {
			printf(" %1X  %1X |0x%02X| %2d | %02X | %02X |",
				(unit[0] & 0xf0) >> 4, unit[0] & 0xf,
				unit[1], unit[2], unit[3], unit[4]);
			printf(" %02d:%02d:%02d |\n",
				unit[5], unit[6], unit[7]);
		} else {
			printf(" %1X  %1X | %2d | %2d | %02X | %02X |",
				(unit[0] & 0xf0) >> 4, unit[0] & 0xf,
				unit[1], unit[2], unit[3], unit[4]);
			printf(" %02d:%02d:%02d |\n",
				unit[5], unit[6], unit[7]);
		}
	}
	fflush(stdout);
}

#endif /* Libburn_write_with_print_cuE */


/* ts B11226 */
static int new_cue(struct cue_sheet *sheet, int number, int flag)
{
	unsigned char *ptr;

	ptr = realloc(sheet->data, (sheet->count + number) * 8);
	if (ptr == NULL) {
		libdax_msgs_submit(libdax_messenger, -1, 0x00020111,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
		"Could not allocate new auxiliary object (cue_sheet->data)",
			0, 0);
		return -1;
	}
	sheet->data = ptr;
	sheet->count += number;
	return 1;
}


/* ts B11226 : outsourced new_cue() */
/** @return 1 = success , <=0 failure */
static int add_cue(struct cue_sheet *sheet, unsigned char ctladr,
		    unsigned char tno, unsigned char indx,
		    unsigned char form, unsigned char scms, int lba)
{
	unsigned char *unit;
	int m, s, f, ret;

	burn_lba_to_msf(lba, &m, &s, &f);

	ret = new_cue(sheet, 1, 0);
	if (ret <= 0)
		return -1;
	unit = sheet->data + (sheet->count - 1) * 8;
	unit[0] = ctladr;
	unit[1] = tno;
	unit[2] = indx;
	unit[3] = form;
	unit[4] = scms;
	unit[5] = m;
	unit[6] = s;
	unit[7] = f;
	return 1;
}


/* ts B11226 */
static int add_catalog_cue(struct cue_sheet *sheet, unsigned char catalog[13])
{
	unsigned char *unit;
	int i, ret;

	ret = new_cue(sheet, 2, 0);
	if (ret <= 0)
		return -1;
	unit = sheet->data + (sheet->count - 2) * 8;
	unit[0] = unit[8] = 0x02;
	for (i = 0; i < 13; i++)
		unit[1 + (i >= 7) * 8 + (i % 7)] = catalog[i];
	unit[15] = 0x00;
	return 1;
}


/* ts B11226 */
static int add_isrc_cue(struct cue_sheet *sheet, unsigned char ctladr, int tno,
			struct isrc *isrc)
{
	unsigned char *unit;
	int i, ret;
	char text[8 + 21]; /* should suffice for 64 bit oversize */

	ret = new_cue(sheet, 2, 0);
	if (ret <= 0)
		return -1;
	unit = sheet->data + (sheet->count - 2) * 8;
	unit[0] = unit[8] = (ctladr & 0xf0) | 0x03;
	unit[1] = unit[9] = tno;
	unit[2] = isrc->country[0];
	unit[3] = isrc->country[1];
	unit[4] = isrc->owner[0];
	unit[5] = isrc->owner[1];
	unit[6] = isrc->owner[2];
	sprintf(text, "%-2.2u%-5.5u", (unsigned int) isrc->year, isrc->serial);
	sprintf(text, "%-2.2u", (unsigned int) isrc->year);
	sprintf(text + 2, "%-5.5u", isrc->serial);
	text[7] = 0;
	unit[7] = text[0];
	for (i = 1; i < 7; i++)
		unit[9 + i] = text[i];
	return 1;
}


/* ts A61114: added parameter nwa */
struct cue_sheet *burn_create_toc_entries(struct burn_write_opts *o,
					  struct burn_session *session,
					  int nwa)
{
	int i, m, s, f, form, runtime = -150, ret, track_length;
	int leadin_form, leadin_start, pregap = 150, postgap;
	unsigned char ctladr, scms;
	struct burn_drive *d;
	struct burn_toc_entry *e;
	struct cue_sheet *sheet;
	struct burn_track **tar = session->track;
	int ntr = session->tracks;
	int rem = 0;

#define Libburn_track_multi_indeX yes

#ifdef Libburn_track_multi_indeX
	int j;
#else
	int pform;
#endif

	if (ntr < 1) {
		libdax_msgs_submit(libdax_messenger, -1, 0x0002019c,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			"Session has no defined tracks", 0, 0);
		return NULL;
	}

	d = o->drive;

#ifdef Libburn_sao_can_appenD
	if (d->status == BURN_DISC_APPENDABLE)
 		runtime = nwa-150;
#endif

	sheet = calloc(1, sizeof(struct cue_sheet));

	/* ts A61009 : react on failures of calloc(), add_cue_sheet()
	               type_to_form() */
	if (sheet == NULL) {
		libdax_msgs_submit(libdax_messenger, -1, 0x00020111,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"Could not allocate new auxiliary object (cue_sheet)",
			0, 0);
		return NULL;
	}

	sheet->data = NULL;
	sheet->count = 0;
	type_to_form(tar[0]->mode, &ctladr, &form);
	if (form == -1) {
		libdax_msgs_submit(libdax_messenger, -1, 0x00020116,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"Track mode has unusable value", 0, 0);
		goto failed;
	}
	if (tar[0]->mode & BURN_AUDIO)
		leadin_form = 0x01;
	else
		leadin_form = 0x14;
	if (o->num_text_packs > 0) {
		leadin_form |= 0x40;
	} else {
		/* Check for CD-TEXT in session. Not the final creation,
		   because the cue sheet content might be needed for CD-TEXT
		   pack type 0x88 "TOC".
		 */
		if (o->text_packs == NULL) {
			ret = burn_cdtext_from_session(session, NULL, NULL, 1);
			if (ret < 0)
				goto failed;
			else if (ret > 0)
				leadin_form |= 0x40;
		}
	}

	if (o->has_mediacatalog)
		ret = add_catalog_cue(sheet, o->mediacatalog);
	else if (session->mediacatalog[0])
		ret = add_catalog_cue(sheet, session->mediacatalog);
	else
		ret = 1;
	if (ret <= 0)
		goto failed;

	/* ts B11225 
	   MMC-5 6.33.3.15 Data Form of Sub-channel
	   seems to indicate that for leadin_form 0x41 one should announce
	   d->start_lba as start of the leadin (e.g. -12490) and that data
	   block type should 2 or 3 with mode page 05h. But my drives refuse
	   on that.
           It works with LBA -150 and data block type 0. Shrug.
	*/
	leadin_start = runtime;
	ret = add_cue(sheet, (ctladr & 64) | 1, 0, 0, leadin_form, 0,
								leadin_start);
	if (ret <= 0)
		goto failed;

	d->toc_entries = ntr + 3;

	/* ts A61009 */
	/* a ssert(d->toc_entry == NULL); */
	if (d->toc_entry != NULL) {

		/* ts A61109 : this happens with appendable CDs 
			>>> Open question: is the existing TOC needed ? */

		/* ts A61109 : for non-SAO, this sheet is thrown away later */
		free((char *) d->toc_entry);

		/*
		libdax_msgs_submit(libdax_messenger,
			d->global_index, 0x00020117,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"toc_entry of drive is already in use", 0, 0);
		goto failed;
		*/
	}
	if (session->firsttrack + ntr - 1 > 99) {
		libdax_msgs_submit(libdax_messenger, -1, 0x0002019b,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			"CD track number exceeds 99", 0, 0);
		goto failed;
	}
	session->lasttrack = session->firsttrack + ntr - 1;

	d->toc_entry = calloc(d->toc_entries, sizeof(struct burn_toc_entry));
	e = d->toc_entry;
	e[0].point = 0xA0;
	if (tar[0]->mode & BURN_AUDIO)
		e[0].control = TOC_CONTROL_AUDIO;
	else
		e[0].control = TOC_CONTROL_DATA;
	e[0].pmin = session->firsttrack;
	e[0].psec = o->format;
	e[0].adr = 1;
	e[1].point = 0xA1;
	e[1].pmin = session->lasttrack;
	e[1].adr = 1;
	if (tar[ntr - 1]->mode & BURN_AUDIO)
		e[1].control = TOC_CONTROL_AUDIO;
	else
		e[1].control = TOC_CONTROL_DATA;
	e[2].point = 0xA2;
	e[2].control = e[1].control;
	e[2].adr = 1;

	tar[0]->pregap2 = 1;
	if (tar[0]->pregap2_size < 150)
		tar[0]->pregap2_size = 150;

#ifndef Libburn_track_multi_indeX
	pform = form;
#endif

	for (i = 0; i < ntr; i++) {

		/* ts A70125 : 
		   Still not understanding the sense behind linking tracks,
		   i decided to at least enforce the MMC specs' minimum
		   track length.
		*/ 
		track_length = burn_track_get_sectors_2(tar[i], 1);
		if (track_length < 0)
			goto failed;
		if (track_length < 300 && !burn_track_is_open_ended(tar[i])) {
			track_length = 300;
			if (!tar[i]->pad)
				tar[i]->pad = 1;
			burn_track_set_sectors(tar[i], track_length);
		}

		type_to_form(tar[i]->mode, &ctladr, &form);
		if (tar[i]->mode & BURN_SCMS)
			scms = 0x80;
		else
			scms = 0; 

		if (tar[i]->isrc.has_isrc) {
			ret = add_isrc_cue(sheet, ctladr,
				i + session->firsttrack, &(tar[i]->isrc));
			if (ret <= 0)
				goto failed;
		}
		pregap = 0;
		if (tar[i]->pregap2)
			pregap = tar[i]->pregap2_size;
		postgap = 0;
		if (tar[i]->postgap) {
			if (tar[i]->indices >= 99) {
				libdax_msgs_submit(libdax_messenger, -1,
					0x0002019a, LIBDAX_MSGS_SEV_SORRY,
					LIBDAX_MSGS_PRIO_HIGH,
					"Post-gap index number exceeds 99",
					0, 0);
				goto failed;
			}
			if (tar[i]->indices < 2)
				tar[i]->indices = 2;
			tar[i]->index[tar[i]->indices] = track_length;
			postgap = tar[i]->postgap_size;
		}

#ifdef Libburn_track_multi_indeX

		for(j = 0; j < (tar[i]->indices + !!tar[i]->postgap) || j < 2;
									j++) {
			if(tar[i]->index[j] == 0x7fffffff) {
				if (j > 1)
		break;
				if (j == 0 && pregap <= 0)
		continue;
				/* force existence of mandatory index */
				tar[i]->index[j] = 0;
			} else if (j == 0) {
				tar[i]->index[j] = 0;
			} else if (j == 1 && tar[i]->index[0] == 0x7fffffff) {
				tar[i]->index[j] = 0;
			}

			if (j == 1) {
				tar[i]->entry = &e[3 + i];
				e[3 + i].point = i + session->firsttrack;
				burn_lba_to_msf(runtime, &m, &s, &f);
				e[3 + i].pmin = m;
				e[3 + i].psec = s;
				e[3 + i].pframe = f;
				e[3 + i].adr = 1;
				e[3 + i].control = type_to_ctrl(tar[i]->mode);
			}

			/* >>> ??? else if j == 0 && mode change to -data :
							Extended pregap */;

			/* >>> check index with track size */;

			tar[i]->index[j] += runtime;
			ret = add_cue(sheet, ctladr | 1,
					i + session->firsttrack, j, form, scms,
					tar[i]->index[j]);
			if (ret <= 0)
				goto failed;
			runtime += pregap;
			pregap = 0;
		}

		runtime += track_length + postgap;

#else /* Libburn_track_multi_indeX */

		if (i == 0) {
			ret = add_cue(sheet, ctladr | 1, session->firsttrack,
					0, form, 0, runtime);
			if (ret <= 0)
				goto failed;
			runtime += 150;
		} else if (pform != form) {

		/* ts A70121 : This seems to be the wrong test. Correct would
		   be to compare tar[]->mode or bit2 of ctladr.
		*/ 

			ret = add_cue(sheet, ctladr | 1,
					i + session->firsttrack, 0, form, scms,
					runtime);
			if (ret <= 0)
				goto failed;

			runtime += 150;
/* XXX fix pregap interval 1 for data tracks */
/* ts A60813 silence righteous compiler warning about C++ style comments
   This is possibly not a comment but rather a trace of Derek Foreman
   experiments. Thus not to be beautified - but to be preserved rectified.
/ /                      if (!(form & BURN_AUDIO))
/ /                              tar[i]->pregap1 = 1;
*/
/* ts A70121 : it is unclear why (form & BURN_AUDIO) should prevent pregap1.
   I believe, correct would be:
			runtime += 75;
			tar[i]->pregap1 = 1;

   The test for pform != form is wrong anyway. 

   Next one has to care for Post-gap: table 555 in mmc5r03c.pdf does not
   show any although 6.33.3.19 would prescribe some.
   ts B20111: Table 1 of MMC-1 shows two post-gaps. The first matches the
              precriptions with SEND CUE SHEET. The second one is riddling.
              Both are part of a track and occupy the range of the last index
              of the track. Length is 2 seconds for each.

   Nobody seems to have ever tested this situation, up to now.
   It is banned for now in burn_disc_write().
   Warning have been placed in libburn.h .
*/

			tar[i]->pregap2 = 1;
		}
/* XXX HERE IS WHERE WE DO INDICES IN THE CUE SHEET */
/* XXX and we should make sure the gaps conform to ecma-130... */
		tar[i]->entry = &e[3 + i];
		e[3 + i].point = i + session->firsttrack;
		burn_lba_to_msf(runtime, &m, &s, &f);
		e[3 + i].pmin = m;
		e[3 + i].psec = s;
		e[3 + i].pframe = f;
		e[3 + i].adr = 1;
		e[3 + i].control = type_to_ctrl(tar[i]->mode);

		ret = add_cue(sheet, ctladr | 1, i + session->firsttrack,
				1, form, scms, runtime);
		if (ret <= 0)
			goto failed;

		runtime += track_length;

#endif /* ! Libburn_track_multi_indeX */


/* if we're padding, we'll clear any current shortage.
   if we're not, we'll slip toc entries by a sector every time our
   shortage is more than a sector
XXX this is untested :)
*/
		if (!tar[i]->pad) {
			rem += burn_track_get_shortage(tar[i]);

			/* ts A61101 : I doubt that linking would yield a
					desirable effect. With TAO it is
					counterproductive in any way.
			*/
			if (o->write_type == BURN_WRITE_TAO)
				tar[i]->source->next = NULL;
			else

				if (i +1 != ntr)
					tar[i]->source->next = tar[i+1]->source;
		} else if (rem) {
			rem = 0;
			runtime++;
		}
		if (rem > burn_sector_length(tar[i]->mode)) {
			rem -= burn_sector_length(tar[i]->mode);
			runtime--;
		}

#ifndef Libburn_track_multi_indeX
		pform = form;
#endif

	}
	burn_lba_to_msf(runtime, &m, &s, &f);
	e[2].pmin = m;
	e[2].psec = s;
	e[2].pframe = f;

	ret = add_cue(sheet, ctladr | 1, 0xAA, 1, leadin_form & 0x3f,
                      0, runtime);
	if (ret <= 0)
		goto failed;
	return sheet;

failed:;
	if (sheet != NULL)
		free((char *) sheet);
	return NULL;
}

int burn_sector_length(int tracktype)
{
	if (tracktype & BURN_AUDIO)
		return 2352;
	if (tracktype & BURN_MODE_RAW)
		return 2352;
	if (tracktype & BURN_MODE1)
		return 2048;
	/* ts A61009 */
	/* a ssert(0); */
	return -1;
}

int burn_subcode_length(int tracktype)
{
	if (tracktype & BURN_SUBCODE_P16)
		return 16;
	if ((tracktype & BURN_SUBCODE_P96) || (tracktype & BURN_SUBCODE_R96))
		return 96;
	return 0;
}

int burn_write_leadin(struct burn_write_opts *o,
		       struct burn_session *s, int first)
{
	struct burn_drive *d = o->drive;
	int count;

	d->busy = BURN_DRIVE_WRITING_LEADIN;

	if (first)
		count = 0 - d->alba - 150;
	else
		count = 4500;

	d->progress.start_sector = d->alba;
	d->progress.sectors = count;
	d->progress.sector = 0;

	while (count != 0) {
		if (!sector_toc(o, s->track[0]->mode))
			return 0;
		count--;
		d->progress.sector++;
	}
	d->busy = BURN_DRIVE_WRITING;
	return 1;
}

int burn_write_leadout(struct burn_write_opts *o,
			int first, unsigned char control, int mode)
{
	struct burn_drive *d = o->drive;
	int count;

	d->busy = BURN_DRIVE_WRITING_LEADOUT;

	d->rlba = -150;
	if (first)
		count = 6750;
	else
		count = 2250;
	d->progress.start_sector = d->alba;
	d->progress.sectors = count;
	d->progress.sector = 0;

	while (count != 0) {
		if (!sector_lout(o, control, mode))
			return 0;
		count--;
		d->progress.sector++;
	}
	d->busy = BURN_DRIVE_WRITING;
	return 1;
}


static int burn_create_text_packs(struct burn_write_opts *o,
					struct burn_session *s,
					int flag)
{
	int ret, num_packs = 0;
	unsigned char *text_packs = NULL;

	ret = burn_cdtext_from_session(s, &text_packs, &num_packs, 0);
	if (ret > 0) {
		if (o->text_packs != NULL)
			free(o->text_packs);
		o->text_packs = text_packs;
		o->num_text_packs = num_packs;
	}
	return(ret);
}


static int burn_write_leadin_cdtext(struct burn_write_opts *o,
					 struct burn_session *s, int flag)
{
	int ret, i, j, si, lba, sub_cursor = 0, err, write_lba, sectors = 0;
	int self_made_text_packs = 0;
	unsigned char *subdata = NULL;
	struct burn_drive *d = o->drive;
	struct buffer *buf = NULL;
	enum burn_drive_status was_busy = o->drive->busy;
#ifdef Libburn_debug_cd_texT
	unsigned char *packs;
#endif

	if (o->num_text_packs <= 0) {
		if (o->text_packs != NULL)
			{ret = 1; goto ex;}
		/* Try to create CD-TEXT from .cdtext_* of session and track */
		ret = burn_create_text_packs(o, s, 0);
		self_made_text_packs = 1;
		if (ret <= 0)
			goto ex;
		if (o->num_text_packs <= 0)
			{ret = 1; goto ex;}
	}

	if (!o->no_text_pack_crc_check) {
		ret = burn_cdtext_crc_mismatches(o->text_packs,
				 o->num_text_packs, 0);
		if (ret != 0) {
			libdax_msgs_submit(libdax_messenger, -1, 0x0002018f,
				LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
				"Program error: CD-TEXT pack CRC mismatch",
				0, 0);
			{ ret = 0; goto ex; }
		}
	}

	d->busy = BURN_DRIVE_WRITING_LEADIN;

#ifdef Libburn_debug_cd_texT
	packs = o->text_packs;
	fprintf(stderr,
		"libburn_DEBUG: 8 bit CD-TEXT packs to be transmitted:\n"); 
	for (i = 0; i < 18 * o->num_text_packs; i += 18) {
		fprintf(stderr, "%4d :", i / 18);
		for (j = 0; j < 18; j++) {
			if (j >= 4 && j <= 15 && packs[i + j] >= 32 &&
			    packs[i + j] <= 126 && packs[i] != 0x88 &&
			    packs[i] != 0x89 && packs[i] != 0x8f)
				fprintf(stderr, "  %c", packs[i + j]);
			else
				fprintf(stderr, " %2.2X", packs[i + j]);
		}
		fprintf(stderr, "\n");
	}
#endif /* Libburn_debug_cd_texT */

	/* Chop from 8 bit text pack to 6 bit subchannel */
	BURN_ALLOC_MEM(subdata, unsigned char, o->num_text_packs * 24);
	for (i = 0; i < 18 * o->num_text_packs; i += 3) {
		si = i / 3 * 4;
		subdata[si + 0] =  (o->text_packs[i + 0] >> 2) & 0x3f;
		subdata[si + 1] =  (o->text_packs[i + 0] << 4) & 0x30;
		subdata[si + 1] |= (o->text_packs[i + 1] >> 4) & 0x0f;
		subdata[si + 2] =  (o->text_packs[i + 1] << 2) & 0x3c;
		subdata[si + 2] |= (o->text_packs[i + 2] >> 6) & 0x03;
		subdata[si + 3] =  (o->text_packs[i + 2] >> 0) & 0x3f;
	}

	/* Start at Lead-in address of ATIP and write blocks up to -150 */
	BURN_ALLOC_MEM(buf, struct buffer, 1);
	write_lba = d->start_lba;
	for (lba = d->start_lba; lba < -150; lba++) {
		/* Collect subdata in buf */
		for (j = 0; j < 4; j++) {
			memcpy(buf->data + buf->bytes,
				subdata + sub_cursor * 24, 24);
			sub_cursor = (sub_cursor + 1) % o->num_text_packs;
			buf->bytes += 24;
		}
		buf->sectors++;
		sectors++;

		/* When full or last sector : perform WRITE */
		if (buf->bytes + 96 >= 32768 || lba == -151) {

#ifdef Libburn_debug_cd_texT
			fprintf(stderr,
			    "libburn_DEBUG: 6 bit data to be transmitted:\n"); 
			for (i = 0; i < buf->bytes; i += 24) {
				fprintf(stderr, "%4d :", i / 24);
				for (j = 0; j < 24; j++)
					fprintf(stderr, " %2.2X",
						buf->data[i + j]);
				fprintf(stderr, "\n");
			}
#endif /* Libburn_debug_cd_texT */
	
			err = d->write(d, write_lba, buf);
			if (err == BE_CANCELLED)
				{ ret = 0; goto ex; }
			write_lba += sectors;
			sectors = buf->sectors = buf->bytes = 0;
		}
	}
	ret = 1;
ex:;
	if (self_made_text_packs) {
		if (o->text_packs != NULL)
			free(o->text_packs);
		o->text_packs = NULL;
		o->num_text_packs = 0;
	}
	BURN_FREE_MEM(subdata);
	BURN_FREE_MEM(buf);
	d->busy = was_busy;
	return ret;
}


int burn_write_session(struct burn_write_opts *o, struct burn_session *s)
{
	struct burn_drive *d = o->drive;
	int i, ret;

	if (o->write_type == BURN_WRITE_SAO) {
		ret = burn_write_leadin_cdtext(o, s, 0);
		if (ret <= 0)
			goto ex;
	}
	d->rlba = 0;
	for (i = 0; i < s->tracks; i++) {
		if (!burn_write_track(o, s, i))
			{ ret = 0; goto ex; }
	}

	/* ts A61103 */
	ret = 1;
ex:;
	if (o->write_type == BURN_WRITE_TAO)
		burn_write_close_session(o);
	return ret;
}


/* ts A61218 : outsourced from burn_write_track() */
int burn_disc_init_track_status(struct burn_write_opts *o,
				struct burn_session *s, struct burn_track *t,
				int tnum, int sectors)
{
	struct burn_drive *d = o->drive;

	/* Update progress */

	d->progress.start_sector = d->nwa;

	d->progress.sectors = sectors;
	d->progress.sector = 0;

	/* ts A60831: added tnum-line, extended print message on proposal
           by bonfire-app@wanadoo.fr in http://libburn.pykix.org/ticket/58 */
        d->progress.track = tnum;

	/* ts B20113 */
	d->progress.indices = t->indices;
	d->progress.index = 0;
	if (d->progress.indices > 1)
		if (t->index[0] == 0x7fffffff)
			d->progress.index = 1;

	/* ts A61102 */
	d->busy = BURN_DRIVE_WRITING;

	return 1;
}


int burn_write_track(struct burn_write_opts *o, struct burn_session *s,
		      int tnum)
{
	struct burn_track *t = s->track[tnum];
	struct burn_drive *d = o->drive;
	int i, tmp = 0, open_ended = 0, ret= 0, nwa, lba;
	int sectors;
	char msg[160];

	d->rlba = -150;

/* XXX for tao, we don't want the pregaps  but still want post? */
	if (o->write_type != BURN_WRITE_TAO) {

		/* ts A61102 */
		d->busy = BURN_DRIVE_WRITING_PREGAP;

		if (t->pregap1)
			d->rlba += 75;
		if (t->pregap2)
			d->rlba += t->pregap2_size;

		if (t->pregap1) {

			struct burn_track *pt;
			/* ts A70121 : Removed pseudo suicidal initializer 
				 = s->track[tnum - 1];
			*/

			if (tnum == 0) {

				/* ts A70121 : This is not possible because
				   track 1 cannot have a pregap at all.
				   MMC-5 6.33.3.2 precribes a mandatory pause
				   prior to any track 1. Pre-gap is prescribed
				   for mode changes like audio-to-data.
				   To set burn_track.pregap1 for track 1 is
				   kindof a dirty hack.
				*/

				printf("first track should not have a pregap1\n");
				pt = t;
			} else
				pt = s->track[tnum - 1]; /* ts A70121 */
			for (i = 0; i < 75; i++)
				if (!sector_pregap(o, t->entry->point,
					           pt->entry->control, pt->mode))
					{ ret = 0; goto ex; }
		}
		if (t->pregap2)
			for (i = 0; i < t->pregap2_size; i++)
				if (!sector_pregap(o, t->entry->point,
					           t->entry->control, t->mode))
					{ ret = 0; goto ex; }

		/* ts B20113 : Flush buffer to avoid influence pregap
		               on track counter */
		ret = sector_write_buffer(d, NULL, 0);
		if (ret <= 0)
			goto ex;

	} else {
		o->control = t->entry->control;
		d->send_write_parameters(d, s, tnum, o);

		/* ts A61103 */
		ret = d->get_nwa(d, -1, &lba, &nwa);

		/* ts A70213: CD-TAO: eventually expand size of track to max */
		burn_track_apply_fillup(t, d->media_capacity_remaining, 0);

		/* <<< */
		sprintf(msg, 
	"TAO pre-track %2.2d : get_nwa(%d)=%d, d=%d , demand=%.f , cap=%.f\n",
			tnum+1, nwa, ret, d->nwa,
			(double) burn_track_get_sectors_2(t, 1) * 2048.0,
			(double) d->media_capacity_remaining);
		libdax_msgs_submit(libdax_messenger, d->global_index,
				 0x00000002,
				LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO,
				msg, 0, 0);

		/* ts A91003 */
		if (nwa < d->nwa) {
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020173,
				LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
			   "Drive tells NWA smaller than last written address",
				0, 0);
			d->sync_cache(d);
			return 0;
		}
		d->nwa = nwa;

	}

/* user data */

	sectors = burn_track_get_sectors_2(t, 1);
	if (sectors < 0)
		{ ret = 0; goto ex; }
	open_ended = burn_track_is_open_ended(t);

	burn_disc_init_track_status(o, s, t, tnum, sectors);

	/* ts A61030 : this cannot happen. tnum is always < s->tracks */
	if (tnum == s->tracks)
		tmp = sectors > 150 ? 150 : sectors;

	for (i = 0; open_ended || i < sectors - tmp; i++) {

		/* ts A61023 : http://libburn.pykix.org/ticket/14
                               From time to time inquire drive buffer */
		if ((i%64)==0)
			d->read_buffer_capacity(d);

		if (!sector_data(o, t, 0))
			{ ret = 0; goto ex; }

		/* ts A61031 */
		if (open_ended) {
			d->progress.sectors = sectors = i;
                        if (burn_track_is_data_done(t)) 
	break;
		}

		/* update current progress */
		d->progress.sector++;
	}
	for (; i < sectors; i++) {

		/* ts A61030: program execution never gets to this point */
		fprintf(stderr,"LIBBURN_DEBUG: TNUM=%d  TRACKS=%d  TMP=%d\n",
			tnum, s->tracks, tmp);

		/* ts A61023 */
		if ((i%64)==0)
			d->read_buffer_capacity(d);

		if (!sector_data(o, t, 1))
			{ ret = 0; goto ex; }

		/* update progress */
		d->progress.sector++;
	}

	/* ts B20113 : Flush buffer to get buffered bytes assigned to the
	               track counter */
	ret = sector_write_buffer(d, t, 0);
	if (ret <= 0)
		goto ex;

	if (t->postgap && o->write_type != BURN_WRITE_TAO) {
		for (i = 0; i < t->postgap_size; i++)
			if (!sector_postgap(o, t->entry->point,
						 t->entry->control, t->mode))
				{ ret = 0; goto ex; }
		ret = sector_write_buffer(d, NULL, 0);
		if (ret <= 0)
			goto ex;
	}

	/* ts A61103 */
	ret = 1;
ex:;
	if (d->cancel)
		burn_source_cancel(t->source);
	if (o->write_type == BURN_WRITE_TAO) {

		/* ts A71002 */
		if (!burn_write_flush_buffer(o, t))
			ret = 0;

		/* Ensure that at least 600 kB get written */
		burn_write_track_minsize(o, s, tnum);
		d->sync_cache(d);

		/* ts A61030 */
		/* ts A91003 :
		   At least in simulation mode this causes NWA=0 for the
		   next track. cdrecord does not use CLOSE TRACK at all but
		   ends the tracks by SYNCHRONIZE CACHE alone.
		*/
		/* ts A91202 :
		   Peng Shao reports that his LG GH22LS30 issues an SCSI error
		   on CLOSE TRACK even in non-dummy mode. So i better give up
		   this gesture which seems not be needed by any drive.
			if (!o->simulate)
				if (burn_write_close_track(o, s, tnum) <= 0)
					ret = 0;
		*/
	}
	return ret;
}

/* ts A61009 */
/* @param flag bit1 = do not libdax_msgs_submit() */
int burn_disc_write_is_ok(struct burn_write_opts *o, struct burn_disc *disc,
			int flag)
{
	int i, t;
	char msg[80];

	for (i = 0; i < disc->sessions; i++)
		for (t = 0; t < disc->session[i]->tracks; t++)
			if (sector_headers_is_ok(
				o, disc->session[i]->track[t]->mode) != 1)
				goto bad_track_mode_found;
	return 1;
bad_track_mode_found:;
	sprintf(msg, "Unsuitable track mode 0x%x in track %d of session %d",
		disc->session[i]->track[t]->mode, i+1, t+1);
	if (!(flag & 2))
		libdax_msgs_submit(libdax_messenger, -1, 0x0002010a,
				LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
	return 0;
}


/* ts A61218 : outsourced from burn_disc_write_sync() */
int burn_disc_init_write_status(struct burn_write_opts *o,
				struct burn_disc *disc)
{
	struct burn_drive *d = o->drive;
	struct burn_track *t = NULL;
	int sx, tx, ret;

	d->cancel = 0;

#ifdef Libburn_reset_progress_asynC
	/* <<< This is now done in async.c */
	/* init progress before showing the state */
	d->progress.session = 0;
	d->progress.sessions = disc->sessions;
	d->progress.track = 0;
	d->progress.tracks = disc->session[0]->tracks;
	/* TODO: handle indices */
	d->progress.index = 0;
	d->progress.indices = disc->session[0]->track[0]->indices;
	/* TODO: handle multissession discs */
	/* XXX: sectors are only set during write track */
	d->progress.start_sector = 0;
	d->progress.sectors = 0;
	d->progress.sector = 0;
	d->progress.track = 0;
#endif /* Libburn_reset_progress_asynC */

	/* ts A61023 */
	d->progress.buffer_capacity = 0;
	d->progress.buffer_available = 0;
	d->progress.buffered_bytes = 0;
	d->progress.buffer_min_fill = 0xffffffff;

	/* ts A70711 */
	d->pessimistic_buffer_free = 0;
	d->pbf_altered = 0;
	d->pessimistic_writes = 0;
	d->waited_writes = 0;
	d->waited_tries = 0;
	d->waited_usec = 0;

	/* Set eventual media fill up for last track only */
	for (sx = 0; sx < disc->sessions; sx++)
		for (tx = 0 ; tx < disc->session[sx]->tracks; tx++) {
			t = disc->session[sx]->track[tx];
			burn_track_set_fillup(t, 0);
		}
	if (o->fill_up_media && t != NULL)
		burn_track_set_fillup(t, 1);

	d->was_feat21h_failure = 0;
	if(d->write_opts != NULL)
		burn_write_opts_free(d->write_opts);
	ret = burn_write_opts_clone(o, &(d->write_opts), 0);
	if (ret <= 0)
		return ret;
	d->write_retry_count = 0;

	d->busy = BURN_DRIVE_WRITING;

	return 1;
}


static int precheck_write_is_audio(struct burn_disc *disc, int flag)
{
	struct burn_session **sessions;
	int num_sessions, i, j;

	sessions = burn_disc_get_sessions(disc, &num_sessions);
	for (i = 0; i < num_sessions; i++)
		for (j = 0; j < sessions[i]->tracks; j++)
			if (!(sessions[i]->track[j]->mode & BURN_AUDIO))
				return 0;
	return 1;
}


static int precheck_disc_has_cdtext(struct burn_disc *disc, int flag)
{
	struct burn_session **sessions;
	int num_sessions, i, ret;

	sessions = burn_disc_get_sessions(disc, &num_sessions);
	for (i = 0; i < num_sessions; i++) {
		ret = burn_cdtext_from_session(sessions[i], NULL, NULL, 1);
		if (ret > 0)
			return 1;
	}
	return 0;
}


/* ts A70219 : API */
int burn_precheck_write(struct burn_write_opts *o, struct burn_disc *disc,
				 char reasons[BURN_REASONS_LEN], int silent)
{
	enum burn_write_types wt;
	struct burn_drive *d = o->drive;
	char *msg = NULL, *reason_pt;
	int no_media = 0, ret, has_cdtext, is_bd_pow = 0;

	reason_pt= reasons;
	reasons[0] = 0;

	if (d->drive_role == 0 || d->drive_role == 4) {
		if (d->drive_role == 0)
			sprintf(reasons,
			       "DRIVE: is a virtual placeholder (null-drive)");
		else
			sprintf(reasons, "DRIVE: read-only pseudo drive");
		no_media = 1;
		goto ex;
	}

	/* check write mode against write job */
	wt = burn_write_opts_auto_write_type(o, disc, reasons, 1);
	if (wt == BURN_WRITE_NONE) {
		if (strncmp(reasons, "MEDIA: ", 7)==0)
			no_media = 1;
		goto ex;
	}

	sprintf(reasons, "%s: ", d->current_profile_text);
	reason_pt= reasons + strlen(reasons);
	if (d->status == BURN_DISC_UNSUITABLE)
		goto unsuitable_profile;
	if (o->num_text_packs > 0) {
		has_cdtext = 1;
	} else {
		has_cdtext = precheck_disc_has_cdtext(disc, 0);
	}
	if (has_cdtext > 0) {
		if (d->current_profile == 0x09 || d->current_profile == 0x0a) {
			ret = precheck_write_is_audio(disc, 0);
			if (ret <= 0)
				strcat(reasons,
			"CD-TEXT supported only with pure audio CD media, ");
		} else {
			strcat(reasons,
				"CD-TEXT supported only with CD media, ");
		}
	}
	if (d->drive_role == 2 || d->drive_role == 5 ||
		d->current_profile == 0x1a || d->current_profile == 0x12 ||
		d->current_profile == 0x43) { 
		/* DVD+RW , DVD-RAM , BD-RE, emulated drive on stdio file */
		if (o->start_byte >= 0 && (o->start_byte % 2048))
			strcat(reasons,
			 "write start address not properly aligned to 2048, ");
	} else if (d->current_profile == 0x09 || d->current_profile == 0x0a) {
		/* CD-R , CD-RW */
		if (!burn_disc_write_is_ok(o, disc, (!!silent) << 1))
			strcat(reasons, "unsuitable track mode found, ");
		if (o->start_byte >= 0)
			strcat(reasons, "write start address not supported, ");
		if (o->num_text_packs > 0) {
			if (o->write_type != BURN_WRITE_SAO)
				strcat(reasons,
				"CD-TEXT supported only with write type SAO, ");
			if (d->start_lba == -2000000000)
				strcat(reasons,
				"No Lead-in start address known with CD-TEXT, ");
		}
	} else if (d->current_profile == 0x13) {
		/* DVD-RW Restricted Overwrite */
		if (o->start_byte >= 0 && (o->start_byte % 32768))
			strcat(reasons,
			  "write start address not properly aligned to 32k, ");
	} else if (d->drive_role == 3 ||
		   d->current_profile == 0x11 || d->current_profile == 0x14 ||
	           d->current_profile == 0x15 ||
	           d->current_profile == 0x1b || d->current_profile == 0x2b ||
		   d->current_profile == 0x41) {
		/* DVD-R* Sequential , DVD+R[/DL] , BD-R,
		   sequential stdio "drive" */
		if (o->start_byte >= 0)
			strcat(reasons, "write start address not supported, ");

		is_bd_pow = burn_drive_get_bd_r_pow(d);
		if (is_bd_pow && !silent) 
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x0002011e,
				LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			   "Unsuitable media detected: BD-R formatted to POW.",
				0, 0);
		if (is_bd_pow) {
			strcat(reasons,
				"unsuitable media formatting POW detected, ");
			return 0;
		}

	} else {
unsuitable_profile:;
		msg = calloc(1, 160);
		if (msg != NULL && !silent) {
			sprintf(msg,
			    "Unsuitable media detected. Profile %4.4Xh  %s",
			    d->current_profile, d->current_profile_text);
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x0002011e,
				LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
		}
		if (msg != NULL)
			free(msg);
		strcat(reasons, "no suitable media profile detected, ");
		return 0;
	}
ex:;
	if (reason_pt[0]) {
		if (no_media) {
			if (!silent)
				libdax_msgs_submit(libdax_messenger,
				  d->global_index, 0x0002013a,
				  LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				  "No suitable media detected", 0, 0);
			return -1;
		}
		if (!silent)
			libdax_msgs_submit(libdax_messenger,
				  d->global_index, 0x00020139,
				  LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
				  "Write job parameters are unsuitable", 0, 0);
		return 0;
	}
	return 1;
}


/* ts A70129 : learned much from dvd+rw-tools-7.0/growisofs_mmc.cpp */
int burn_disc_open_track_dvd_minus_r(struct burn_write_opts *o,
					struct burn_session *s, int tnum)
{
	struct burn_drive *d = o->drive;
	char *msg = NULL;
	int ret, lba, nwa;
	off_t size;

	BURN_ALLOC_MEM(msg, char, 160);
	d->send_write_parameters(d, NULL, -1, o);
	ret = d->get_nwa(d, -1, &lba, &nwa);
	sprintf(msg, 
		"DVD pre-track %2.2d : get_nwa(%d), ret= %d , d->nwa= %d",
		tnum+1, nwa, ret, d->nwa);
	libdax_msgs_submit(libdax_messenger, d->global_index, 0x00000002,
			LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO, msg,0,0);
	if (nwa > d->nwa)
		d->nwa = nwa;
	/* ts A70214 : eventually adjust already expanded size of track */
	burn_track_apply_fillup(s->track[tnum], d->media_capacity_remaining,1);

#ifdef Libburn_pioneer_dvr_216d_with_opC
	fprintf(stderr, "libburn_DEBUG: Libburn_pioneer_dvr_216d_with_opC : num_opc_tables = %d\n", d->num_opc_tables);
	if (d->num_opc_tables <= 0 && !o->simulate) {
		fprintf(stderr, "libburn_DEBUG: Libburn_pioneer_dvr_216d_with_opC : performing OPC\n");
		d->perform_opc(d);
		fprintf(stderr, "libburn_DEBUG: Libburn_pioneer_dvr_216d_with_opC : done\n");
	}
#endif

#ifdef Libburn_pioneer_dvr_216d_get_evenT
	mmc_get_event(d);
#endif

	if (o->write_type == BURN_WRITE_SAO) { /* DAO */
		size = ((off_t) burn_track_get_sectors_2(s->track[tnum], 1))
			* (off_t) 2048;
		if (size < 0)
			{ret = 0; goto ex;}

 		/* Eventually round track size up to write chunk */
		if (o->obs_pad && (size % o->obs))
			size += (off_t) (o->obs - (size % o->obs));

		ret = d->reserve_track(d, size);
		if (ret <= 0) {
			sprintf(msg, "Cannot reserve track of %.f bytes",
				(double) size);
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020138,
				LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
			{ret = 0; goto ex;}
		}
	}
	ret = 1;
ex:;
	BURN_FREE_MEM(msg);
	return ret;
}


/* ts A70226 */
int burn_disc_open_track_dvd_plus_r(struct burn_write_opts *o,
					struct burn_session *s, int tnum)
{
	struct burn_drive *d = o->drive;
	char *msg = NULL;
	int ret, lba, nwa;
	off_t size;

	BURN_ALLOC_MEM(msg, char, 160);
	ret = d->get_nwa(d, -1, &lba, &nwa);
	sprintf(msg, 
		"DVD+R pre-track %2.2d : get_nwa(%d), ret= %d , d->nwa= %d",
		tnum+1, nwa, ret, d->nwa);
	libdax_msgs_submit(libdax_messenger, d->global_index, 0x00000002,
			LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO, msg,0,0);
	if (nwa > d->nwa)
		d->nwa = nwa;
	/* ts A70214 : eventually adjust already expanded size of track */
	burn_track_apply_fillup(s->track[tnum], d->media_capacity_remaining,1);

	if (o->write_type == BURN_WRITE_SAO &&
	    ! burn_track_is_open_ended(s->track[tnum])) {
 		/* Reserve track */
		size = ((off_t) burn_track_get_sectors_2(s->track[tnum], 1))
			* (off_t) 2048;
		if (size < 0)
			{ret = 0; goto ex;}
		if (o->obs_pad) {
	 		/* Round track size up to write chunk size */
			/* o->obs should be 32k or 64k already. But 32k
			   alignment was once performed in d->reserve_track()*/
			if (o->obs % 32768)
				o->obs += 32768 - (o->obs % 32768);
			if (size % o->obs)
				size += (off_t) (o->obs - (size % o->obs));
		}

		ret = d->reserve_track(d, size);
		if (ret <= 0) {
			sprintf(msg, "Cannot reserve track of %.f bytes",
				(double) size);
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020138,
				LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
			{ret = 0; goto ex;}
		}
	}
	ret = 1;
ex:;
	BURN_FREE_MEM(msg);
	return ret;
}


/* ts A70129 */
int burn_disc_close_track_dvd_minus_r(struct burn_write_opts *o, int tnum)
{
	struct burn_drive *d = o->drive;
	char msg[80];

	/* only with Incremental writing */
	if (o->write_type != BURN_WRITE_TAO)
		return 2;

	sprintf(msg, "Closing track %2.2d  (absolute track number %d)",
		tnum + 1, d->last_track_no);
	libdax_msgs_submit(libdax_messenger, o->drive->global_index,0x00020119,
			LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH, msg,0,0);

	d->busy = BURN_DRIVE_CLOSING_SESSION;
	/* Ignoring tnum here and hoping that d->last_track_no is correct */
	d->close_track_session(d, 0, d->last_track_no); /* CLOSE TRACK, 001b */
	d->busy = BURN_DRIVE_WRITING;
	d->last_track_no++;
	return 1;
}


/* ts A70229 */
int burn_disc_finalize_dvd_plus_r(struct burn_write_opts *o)
{
	struct burn_drive *d = o->drive;
	char msg[40 + 80]; /* filltext + profile */

	sprintf(msg, "Finalizing %s ...",
		d->current_profile_text);
	libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00000002,
			LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO,
			msg, 0, 0);

	if(d->current_profile == 0x41) { /* BD-R */
		/* CLOSE SESSION, 110b, Finalize Disc */
		d->close_track_session(d, 3, 0);  /* (3<<1)|0 = 6 */
	} else {
		/* CLOSE SESSION, 101b, Finalize with minimal radius */
		d->close_track_session(d, 2, 1);  /* (2<<1)|1 = 5 */
	}

	sprintf(msg, "... finalizing %s done               ",
		d->current_profile_text);
	libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00000002,
			LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO,
			msg, 0, 0);

	return 1;
}


/* ts A70226 */
int burn_disc_close_track_dvd_plus_r(struct burn_write_opts *o,
			int tnum, int is_last_track)
{
	struct burn_drive *d = o->drive;
	char msg[80];

	sprintf(msg,
		"Closing track %2.2d  (absolute track and session number %d)",
		tnum + 1, d->last_track_no);
	libdax_msgs_submit(libdax_messenger, o->drive->global_index,0x00020119,
			LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH, msg,0,0);

	d->busy = BURN_DRIVE_CLOSING_SESSION;
	d->close_track_session(d, 0, d->last_track_no); /* CLOSE TRACK, 001b */

	/* Each session becomes a single logical track. So to distinguish them,
	   it is mandatory to close the session together with each track. */

	if (is_last_track && !o->multi) 
		burn_disc_finalize_dvd_plus_r(o);
	else
 		d->close_track_session(d, 1, 0); /* CLOSE SESSION, 010b */
	d->busy = BURN_DRIVE_WRITING;
	d->last_track_no++;
	return 1;
}


/* <<<
#define Libburn_simplified_dvd_chunk_transactioN 1
*/

#ifdef Libburn_simplified_dvd_chunk_transactioN

/* ts A91114 : EXPERIMENTAL, NOT COMPLETELY IMPLEMENTED

   Simplified data transmission for DVD. libburn via GNU/Linux USB is 30 %
   slower than growisofs or cdrecord when transmitting 32 KB chunks.
   With 64 KB chunks it is 20% faster than the competitors.
   No heavy CPU load is visible but there might be subtle race conditions in
   the USB driver which work better with shorter time gaps between WRITE
   commands.

   Insight: It is actually about the interference of track source reading
            with SCSI writing via USB. growisofs reads with O_DIRECT into a
            mmap()ed buffer. When doing the same, libburn with 32 KB chunks
            reaches similar write speed.
            On the other hand, 64 KB chunks are 20% faster than that and
            are not improved by reading O_DIRECT.

            O_DIRECT is a property of the input fd of struct burn_source.
            It can only be done with properly aligned memory and with aligned
            read size. Alignment size is file system system specific.
            System call
                mmap(NULL, (size_t) buffer_size, PROT_READ | PROT_WRITE,
                     MAP_SHARED | MAP_ANONYMOUS, -1, (off_t) 0);
            is supposed to allocate a properly aligned buffer.
            64 KB is supposed to be a safe size.
            Actually mmap() seems to be the main cause for a positive effect
            of O_DIRECT.

   This simplified transmission function did not bring visible benefit.
   So for now it is not worth to teach it all applicable details of old
   CD sector oriented transmission.

   @return 1= ok, go on , 2= no input with track->open_ended = nothing written
           <= 0 = error
*/
static int transact_dvd_chunk(struct burn_write_opts *opts,
				 struct burn_track *track)
{
	int curr = 0, valid, err;
	struct burn_drive *d = opts->drive;
        struct buffer *out = d->buffer;
	unsigned char *data = out->data;

#ifdef Libburn_log_in_and_out_streaM
	/* ts A61031 */
	static int tee_fd= -1;
	if(tee_fd==-1)
		tee_fd= open("/tmp/libburn_sg_readin",
				O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
				S_IRUSR | S_IWUSR);
#endif /* Libburn_log_in_and_out_streaM */


	/* Read a chunk full of data */

	/* ??? Do we have offset padding ? >>> First produce offset padding */;

	/*  <<<< */
	if (0 && !track->eos) {
		for (curr = 0; curr < opts->obs; curr += 2048) {
			if (track->source->read != NULL)
				valid = track->source->read(track->source,
						data + curr, 2048);
			else
				valid = track->source->read_xt(track->source,
						data + curr, 2048);
			if (valid <= 0) {
				track->eos = 1;
		break;
			}
			track->sourcecount += valid;

#ifdef Libburn_log_in_and_out_streaM
			if(tee_fd!=-1 && valid>0) {
				write(tee_fd, data + curr, valid);
			}
#endif /* Libburn_log_in_and_out_streaM */

		}
	} else if (!track->eos){
		valid = track->source->read(track->source, data, opts->obs);
		if (valid <= 0) {
			track->eos = 1;
		} else {
			track->sourcecount += valid;
			curr = valid;

#ifdef Libburn_log_in_and_out_streaM
			if(tee_fd!=-1 && valid>0) {
				write(tee_fd, data, valid);
			}
#endif /* Libburn_log_in_and_out_streaM */
		}
	}
	if (curr == 0 && track->open_ended) {

		/* >>> allow tail padding */;

		return 2;
	}
	if (curr < opts->obs)
		memset(data + curr , 0, opts->obs - curr);

	/* Write chunk */
	out->bytes = opts->obs;
	out->sectors = out->bytes / 2048;
	err = d->write(d, d->nwa, out);
	if (err == BE_CANCELLED)
		return 0;
	track->writecount += out->bytes;
	track->written_sectors += out->sectors;
	d->progress.buffered_bytes += out->bytes;
	d->nwa += out->sectors;
	out->bytes = 0;
	out->sectors = 0;

	return 1;
}

#endif /* Libburn_simplified_dvd_chunk_transactioN */


/* ts A61218 - A81208 */
int burn_dvd_write_track(struct burn_write_opts *o,
			struct burn_session *s, int tnum, int is_last_track)
{
	struct burn_track *t = s->track[tnum];
	struct burn_drive *d = o->drive;
	struct buffer *out = d->buffer;
	int sectors;
	int i, open_ended = 0, ret= 0, is_flushed = 0, track_open = 0;
	int first_buf_cap = 0, further_cap = 0, buf_cap_step = 1024;

	/* ts A70213 : eventually expand size of track to max */
	burn_track_apply_fillup(t, d->media_capacity_remaining, 0);

	/* ts C00806 : Consider the track state changed by try to open */
	d->medium_state_changed = 1;

	if (d->current_profile == 0x11 || d->current_profile == 0x14 ||
	    d->current_profile == 0x15) {
		/* DVD-R, DVD-RW Sequential, DVD-R/DL Sequential */
		ret = burn_disc_open_track_dvd_minus_r(o, s, tnum);
		if (ret <= 0)
			goto ex;
		/* Pioneer DVR-216D rev 1.09 hates multiple buffer inquiries
		   before the drive buffer is full.
		*/
		first_buf_cap = 0;
		further_cap = -1;
	} else if (d->current_profile == 0x1b || d->current_profile == 0x2b) {
		/* DVD+R , DVD+R/DL */
		ret = burn_disc_open_track_dvd_plus_r(o, s, tnum);
		if (ret <= 0)
			goto ex;
	} else if (d->current_profile == 0x41) {
		/* BD-R SRM */
		ret = burn_disc_open_track_dvd_plus_r(o, s, tnum);
		if (ret <= 0)
			goto ex;
	}
	track_open = 1;

	sectors = burn_track_get_sectors_2(t, 1);
	if (sectors < 0)
		{ret = 0; goto ex;}
	open_ended = burn_track_is_open_ended(t);

	/* (offset padding is done within sector_data()) */

	burn_disc_init_track_status(o, s, t, tnum, sectors);
	for (i = 0; open_ended || i < sectors; i++) {

		/* From time to time inquire drive buffer */
		/* ts A91110: Eventually avoid to do this more than once
		              before the drive buffer is full. See above DVD-
		*/
		if (i == first_buf_cap ||
		   ((i % buf_cap_step) == 0 &&
		    (i >= further_cap || further_cap < 0))) {
			d->read_buffer_capacity(d);
			if (further_cap < 0)
				further_cap =
			 	    d->progress.buffer_capacity / 2048 + 128;
		}

#ifdef Libburn_simplified_dvd_chunk_transactioN

		ret = transact_dvd_chunk(o, t);
		if (ret <= 0)
			{ret = 0; goto ex;}
		i += o->obs / 2048 - 1;
		d->progress.sector += o->obs / 2048 - 1;
#else
		/* transact a (CD sized) sector */
		if (!sector_data(o, t, 0))
			{ ret = 0; goto ex; }
#endif

		if (open_ended) {
			d->progress.sectors = sectors = i;
                        if (burn_track_is_data_done(t)) 
	break;
		}

		/* update current progress */
		d->progress.sector++;
	}
	
	/* (tail padding is done in sector_data()) */

	/* Pad up buffer to next full o->obs (usually 32 kB) */
	if (o->obs_pad && out->bytes > 0 && out->bytes < o->obs) {
		memset(out->data + out->bytes, 0, o->obs - out->bytes);
		out->sectors += (o->obs - out->bytes) / 2048;
		out->bytes = o->obs;
	}
	ret = burn_write_flush(o, t);
	if (ret <= 0)
		goto ex;
	is_flushed = 1;

	/* Eventually finalize track */
	if (d->current_profile == 0x11 || d->current_profile == 0x14 ||
	    d->current_profile == 0x15) {
		/* DVD-R, DVD-RW Sequential, DVD-R/DL Sequential */
		ret = burn_disc_close_track_dvd_minus_r(o, tnum);
		if (ret <= 0)
			goto ex;
	} else if (d->current_profile == 0x1b || d->current_profile == 0x2b) {
		/* DVD+R , DVD+R/DL */
		ret = burn_disc_close_track_dvd_plus_r(o, tnum,
							 is_last_track);
		if (ret <= 0)
			goto ex;
	} else if (d->current_profile == 0x41) {
		/* BD-R SRM */
		ret = burn_disc_close_track_dvd_plus_r(o, tnum,
							 is_last_track);
		if (ret <= 0)
			goto ex;
	}
	ret = 1;
ex:;
	if (d->cancel)
		burn_source_cancel(t->source);
	if (track_open && !is_flushed)
		d->sync_cache(d); /* burn_write_flush() was not called */
	return ret;
}


/* ts A61219 */
int burn_disc_close_session_dvd_plus_rw(struct burn_write_opts *o,
					struct burn_session *s)
{
	struct burn_drive *d = o->drive;

	d->busy = BURN_DRIVE_CLOSING_SESSION;
	/* This seems to be a quick end : "if (!dvd_compat)" */
	/* >>> Stop de-icing (ongoing background format) quickly
	       by mmc_close() (but with opcode[2]=0).
	       Wait for unit to get ready.
	       return 1;
	*/
	/* Else: end eventual background format in a "DVD-RO" compatible way */
	d->close_track_session(d, 1, 0); /* same as CLOSE SESSION for CD */
	d->busy = BURN_DRIVE_WRITING;
	return 1;
}


/* ts A61228 */
int burn_disc_close_session_dvd_minus_rw(struct burn_write_opts *o,
					struct burn_session *s)
{
	struct burn_drive *d = o->drive;

	d->busy = BURN_DRIVE_CLOSING_SESSION;
	if (d->current_profile == 0x13) {
		d->close_track_session(d, 1, 0); /* CLOSE SESSION, 010b */

		/* ??? under what circumstances to use close functiom 011b 
		       "Finalize disc" ? */

	}
	d->busy = BURN_DRIVE_WRITING;
	return 1;
}


/* ts A70129 : for profile 0x11 DVD-R, 0x14 DVD-RW Seq, 0x15 DVD-R/DL Seq */
int burn_disc_close_session_dvd_minus_r(struct burn_write_opts *o)
{
	struct burn_drive *d = o->drive;

	/* only for Incremental writing */
	if (o->write_type != BURN_WRITE_TAO)
		return 2;

#ifdef Libburn_dvd_r_dl_multi_no_close_sessioN
	if (d->current_profile == 0x15 && o->multi)
		return 2;
#endif

	libdax_msgs_submit(libdax_messenger, o->drive->global_index,0x00020119,
			LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH,
			"Closing session", 0, 0);

	d->busy = BURN_DRIVE_CLOSING_SESSION;
	d->close_track_session(d, 1, 0); /* CLOSE SESSION, 010b */
	d->busy = BURN_DRIVE_WRITING;
	return 1;
}


/* ts A61218 */
int burn_dvd_write_session(struct burn_write_opts *o,
				struct burn_session *s, int is_last_session)
{
	int i, ret, multi_mem;
        struct burn_drive *d = o->drive;

	/* ts A90108 */
	if (d->current_profile == 0x41 && d->status == BURN_DISC_APPENDABLE &&
	    d->state_of_last_session == 1) {
		/* last session on BD-R is still open */;

		/* BR-R were not closed by libburn-0.6.0.pl00 if o->multi==0.
		   This leads to an unreadable, but recoverable) media state.
		   Technically they are appendable although the last session
		   is not readable.

		   By default the open session gets closed here before the new
		   session is written. E.g. after writing a small dummy session
		   number 2 one can read session 1 and write session 3 which
		   points to data of session 1.

		   For the case that no media with 3 sessions is desired it is
		   possible to activate the following coarse single-session
		   closing code:
		   No new session will be written but calling programs will
		   report success. Quite misleading.
		   Activate only if really needed by
		   # define Libburn_bug_A90108_close_disC yes
		*/


#ifdef Libburn_bug_A90108_close_disC

		/* Close open session and media. 
		   That was the goal of the failed run which led to the
		   unreadable (but recoverable) media state.

		   It is not easy to implement a general close function for
		   all media types. Therefore this pseudo write code is under
		   control of #ifdef.
		*/
		libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020171,
				LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH,
				"Closing BD-R with accidentally open session",
				0, 0);
 		d->close_track_session(d, 3, 0); /* CLOSE SESSION, 110b */
		d->state_of_last_session = 3; /* mark as complete session */
		d->status = BURN_DISC_FULL;
		sleep(3); /* The caller might need time to arrange itself */
		return 1;

#else /* Libburn_bug_A90108_close_disC */

		/* This is the default mode.
		*/
		libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020170,
				LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH,
				"Closing open session before writing new one",
				0, 0);
 		d->close_track_session(d, 1, 0); /* CLOSE SESSION, 010b */
		d->state_of_last_session = 3; /* mark as complete session */

#endif /* ! Libburn_bug_A90108_close_disC */

	}

	for (i = 0; i < s->tracks; i++) {
		ret = burn_dvd_write_track(o, s, i,
			is_last_session && i == (s->tracks - 1));
		if (ret <= 0)
	break;
	}
	if (d->current_profile == 0x11 || d->current_profile == 0x14 ||
	    d->current_profile == 0x15) {
		/* DVD-R , DVD-RW Sequential, DVD-R/DL Sequential */
		/* If feature 21h failed on write 0: do not close session */
		if (d->was_feat21h_failure != 2) {
			multi_mem = o->multi;
			if (!is_last_session)
				o->multi = 1;
			ret = burn_disc_close_session_dvd_minus_r(o);
			o->multi = multi_mem;
			if (ret <= 0)
				return 0;
		}
	} else if (d->current_profile == 0x12 || d->current_profile == 0x43) {
		/* DVD-RAM , BD-RE */
		/* ??? any finalization needed ? */;
	} else if (d->current_profile == 0x13) {
		/* DVD-RW restricted overwrite */
		if (d->needs_close_session) {
			ret = burn_disc_close_session_dvd_minus_rw(o, s);
			if (ret <= 0)
				return 0;
		}
	} else if (d->current_profile == 0x1a) {
		/* DVD+RW */
		if (d->needs_close_session) {
			ret = burn_disc_close_session_dvd_plus_rw(o, s);
			if (ret <= 0)
				return 0;
		}
	} else if (d->current_profile == 0x1b || d->current_profile == 0x2b) {
		/* DVD+R , DVD+R/DL do each track as an own session */;
	} else if (d->current_profile == 0x41) {
		/* BD-R SRM do each track as an own session */;
	}
	return 1;
}


/* ts A61218 : learned much from dvd+rw-tools-7.0/growisofs_mmc.cpp */
int burn_disc_setup_dvd_plus_rw(struct burn_write_opts *o,
				struct burn_disc *disc)
{
	struct burn_drive *d = o->drive;
	int ret;

	if (d->bg_format_status==0 || d->bg_format_status==1) {
		d->busy = BURN_DRIVE_FORMATTING;
		/* start or re-start dvd_plus_rw formatting */
		ret = d->format_unit(d, (off_t) 0, 0);
		if (ret <= 0)
			return 0;
		d->busy = BURN_DRIVE_WRITING;
		d->needs_close_session = 1;
	}

	/* >>> perform OPC if needed */;

	/* >>> ? what else ? */;

	return 1;
}


/* ts A61228 : learned much from dvd+rw-tools-7.0/growisofs_mmc.cpp */
int burn_disc_setup_dvd_minus_rw(struct burn_write_opts *o,
				struct burn_disc *disc)
{
	struct burn_drive *d = o->drive;
	char msg[60];
	int ret;

	d->nwa = 0;
	if (o->start_byte >= 0) {
		d->nwa = o->start_byte / 32768; /* align to 32 kB */

		sprintf(msg, "Write start address is  %d * 32768", d->nwa);
		libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020127,
				LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);

		d->nwa *= 16; /* convert to 2048 block units */
	}


	/* ??? mmc5r03c.pdf 7.5.2 :
	"For DVD-RW media ... If a medium is in Restricted overwrite
	 mode, this mode page shall not be used."

	But growisofs composes a page 5 and sends it.
	mmc5r03c.pdf 5.3.16 , table 127 specifies that mode page 5
	shall be supported with feature 0026h Restricted Overwrite.
	5.3.22 describes a feature 002Ch Rigid Restrictive Overwrite
	which seems to apply to DVD-RW and does not mention page 5.

	5.4.14 finally states that profile 0013h includes feature
	002Ch rather than 0026h.
		
		d->send_write_parameters(d, NULL, -1, o);
	*/

	d->busy = BURN_DRIVE_FORMATTING;

	/* "quick grow" to at least byte equivalent of d->nwa */
	ret = d->format_unit(d, (off_t) d->nwa * (off_t) 2048,
			     (d->nwa > 0) << 3);
	if (ret <= 0)
		return 0;

	d->busy = BURN_DRIVE_WRITING;

	/* >>> perform OPC if needed */;

	return 1;
}


/* ts A70129 : for DVD-R[W] Sequential Recoding */
int burn_disc_setup_dvd_minus_r(struct burn_write_opts *o,
				struct burn_disc *disc)
{
	struct burn_drive *d = o->drive;

	/* most setup is in burn_disc_setup_track_dvd_minus_r() */;

	d->nwa = 0;
	return 1;
}


/* ts A70226 : for DVD+R , DVD+R/DL */
int burn_disc_setup_dvd_plus_r(struct burn_write_opts *o,
				struct burn_disc *disc)
{
	struct burn_drive *d = o->drive;

	/* most setup is in burn_disc_setup_track_dvd_plus_r() */;

	d->nwa = 0;
	return 1;
}


/* ts A61218 - A70415 */
int burn_dvd_write_sync(struct burn_write_opts *o,
				 struct burn_disc *disc)
{
	int i, ret, o_end;
	off_t default_size = 0;
	struct burn_drive *d = o->drive;
	struct burn_track *t;
	char *msg = NULL;

	BURN_ALLOC_MEM(msg, char, 160);
	d->needs_close_session = 0;

	/* buffer flush trigger for sector.c:get_sector() */
	o->obs = Libburn_dvd_obS;

	if (d->current_profile == 0x1a || d->current_profile == 0x12 ||
	    d->current_profile == 0x43) { 
		/* DVD+RW , DVD-RAM , BD-RE */
		ret = 1;
		if (d->current_profile == 0x1a)
			ret = burn_disc_setup_dvd_plus_rw(o, disc);
		if (ret <= 0) {
			sprintf(msg,
			  "Write preparation setup failed for DVD+RW");
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020121,
				LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
			goto early_failure;
		}
		d->nwa = 0;
		if (o->start_byte >= 0) {
			d->nwa = o->start_byte / 2048;
			sprintf(msg, "Write start address is  %d * 2048",
				d->nwa);
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020127,
				LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
		}
		if (o->obs_pad < 2)
			o->obs_pad = 0; /* no filling-up of last 32k buffer */
		if (d->current_profile == 0x43) /* BD-RE */
			o->obs = Libburn_bd_re_obS;
		if (d->do_stream_recording) {
			if (o->obs_pad < 2)
				o->obs_pad = 1;
			if (d->current_profile == 0x43) /* BD-RE */
				o->obs = Libburn_bd_streamed_obS;
		}

	} else if (d->current_profile == 0x13) {
		 /* DVD-RW Restricted Overwrite */
		ret = burn_disc_setup_dvd_minus_rw(o, disc);
		if (ret <= 0) {
			sprintf(msg,
			  "Write preparation setup failed for DVD-RW");
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020121,
				LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
			goto early_failure;
		}

		/* _Rigid_ Restricted Overwrite demands this */
		o->obs_pad = 1; /* fill-up track's last 32k buffer */

	} else if (d->current_profile == 0x11 || d->current_profile == 0x14 ||
			d->current_profile == 0x15) {
		/* DVD-R , DVD-RW Sequential , DVD-R/DL Sequential */
		t = disc->session[0]->track[0];
		o_end = ( burn_track_is_open_ended(t) && !o->fill_up_media );
		default_size = burn_track_get_default_size(t);
		if (o->write_type == BURN_WRITE_SAO && o_end) {
			sprintf(msg, "Activated track default size %.f",
				(double) default_size);
			libdax_msgs_submit(libdax_messenger,
				  d->global_index, 0x0002012e,
				  LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH,
				  msg, 0, 0);
			burn_track_set_size(t, default_size);
		}
		/* Whether to fill-up last 32k buffer of track. */
		if (o->obs_pad < 2)
			o->obs_pad = (o->write_type != BURN_WRITE_SAO);
		ret = burn_disc_setup_dvd_minus_r(o, disc);
		if (ret <= 0) {
			sprintf(msg,
			  "Write preparation setup failed for DVD-R[W]");
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020121,
				LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
			goto early_failure;
		}
		
	} else if (d->current_profile == 0x1b || d->current_profile == 0x2b ||
		   d->current_profile == 0x41) {
		/* DVD+R , DVD+R/DL , BD-R SRM */

		/* >>> ts A81208 : with BD-R set o->obs to 64 kB ? */

		t = disc->session[0]->track[0];
		o_end = ( burn_track_is_open_ended(t) && !o->fill_up_media );
		default_size = burn_track_get_default_size(t);
		if (o->write_type == BURN_WRITE_SAO && o_end) {
			sprintf(msg, "Activated track default size %.f",
				(double) default_size);
			libdax_msgs_submit(libdax_messenger,
				  d->global_index, 0x0002012e,
				  LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH,
				  msg, 0, 0);
			burn_track_set_size(t, default_size);
		}
		ret = burn_disc_setup_dvd_plus_r(o, disc);
		if (ret <= 0) {
			sprintf(msg, "Write preparation setup failed for %s",
			   	d->current_profile == 0x41 ? "BD-R" : "DVD+R");
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020121,
				LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
			goto early_failure;
		}
		/* Unaligned BD-R track end works with various drives and
		   produces exact READ CAPACITY results.
		   Nevertheless stream recording hates unaligned WRITE.
		   With DVD+R it seems that obs_pad is silently applied by the
		   drive if a non-aligned final WRITE is received.
		*/
		if (o->obs_pad < 2 &&
		    !(d->current_profile == 0x41 && !d->do_stream_recording &&
		      o->bdr_obs_exempt))
			o->obs_pad = 1; /* fill-up track's last obs buffer */

		if (d->current_profile == 0x41) /* BD-R */
			o->obs = Libburn_bd_r_obS;
		if (d->do_stream_recording) {
			if (d->current_profile == 0x41) /* BD-R */
				o->obs = Libburn_bd_streamed_obS;
		}
	}

#ifdef Libburn_dvd_obs_default_64K
	o->obs = 64 * 1024;
#endif

	/* <<< test only : Does this increase effective speed with USB ?
		ts A90801 : 64kB: speed with 16x DVD-R is 12 rather than 8
		            128kB: glibc complains about double free
		                   With BURN_OS_TRANSPORT_BUFFER_SIZE
		                   enlarged to 128 MB, the first WRITE fails
		                   with an i/o error.
	o->obs = 64 * 1024;
	*/

	if (o->dvd_obs_override >= 32 * 1024)
		o->obs = o->dvd_obs_override;

	if (o->obs > BUFFER_SIZE) {
		sprintf(msg, "Chosen write chunk size %d exceeds system dependent buffer size", o->obs);
		libdax_msgs_submit(libdax_messenger, d->global_index,
				 0x00000002, LIBDAX_MSGS_SEV_DEBUG,
				 LIBDAX_MSGS_PRIO_ZERO, msg, 0, 0);
		o->obs = 32 * 1024; /* This size is required to work */
	}

	if (d->do_stream_recording &&
		(d->current_profile == 0x43 || d->current_profile == 0x41) &&
		o->obs < Libburn_bd_streamed_obS) {
		/* LG GGW-H20 writes junk with stream recording and obs=32k */
		sprintf(msg,
		   "Stream recording disabled because of small output buffer");
		libdax_msgs_submit(libdax_messenger, d->global_index,
			 0x00020176, LIBDAX_MSGS_SEV_NOTE,
			 LIBDAX_MSGS_PRIO_HIGH, msg, 0, 0);
		d->do_stream_recording = 0;
	}

	sprintf(msg, "dvd/bd Profile= %2.2Xh , obs= %d , obs_pad= %d",
		d->current_profile, o->obs, o->obs_pad);
	libdax_msgs_submit(libdax_messenger, d->global_index, 0x00000002,
		LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO, msg, 0, 0);

	for (i = 0; i < disc->sessions; i++) {
		/* update progress */
		d->progress.session = i;
		d->progress.tracks = disc->session[i]->tracks;

		ret = burn_dvd_write_session(o, disc->session[i],
					i == (disc->sessions - 1));
		if (ret <= 0)
			goto ex;

		/* XXX: currently signs an end of session */
		d->progress.sector = 0;
		d->progress.start_sector = 0;
		d->progress.sectors = 0;
	}
	ret = 1;
ex:;

	/* >>> eventual emergency finalization measures */

	/* update media state records */
	burn_drive_mark_unready(d, 0);
	burn_drive_inquire_media(d);

	if (d->current_profile == 0x41 && d->complete_sessions >= 300) {
		sprintf(msg, "Sequential BD-R media now contains %d sessions. It is likely to soon fail writing.", d->complete_sessions);
		libdax_msgs_submit(libdax_messenger, d->global_index,
				0x0002017b, LIBDAX_MSGS_SEV_WARNING,
				LIBDAX_MSGS_PRIO_ZERO, msg, 0, 0);
	}
	BURN_FREE_MEM(msg);
	return ret;
early_failure:;
	BURN_FREE_MEM(msg);
	return 0;
}


/* ts A70904 */
int burn_stdio_open_write(struct burn_drive *d, off_t start_byte,
			 int sector_size, int flag)
{

/* We normally need _LARGEFILE64_SOURCE defined by the build system.
   Nevertheless the system might use large address integers by default.
*/
#ifndef O_LARGEFILE
#define O_LARGEFILE 0
#endif

	int fd = -1;
	int mode = O_RDWR | O_CREAT | O_LARGEFILE;
	char msg[60];
	off_t lseek_res;

	if(d->drive_role == 4) {
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020181,
			LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
			"Pseudo-drive is a read-only file. Cannot write.",
			0, 0);
		return 0;
	}
	if (d->drive_role == 5 || d->drive_role == 3)
		mode = O_WRONLY | O_CREAT | O_LARGEFILE;
	if (d->devname[0] == 0) /* null drives should not come here */
		return -1;
	fd = burn_drive__fd_from_special_adr(d->devname);
	if (fd >= 0)
		fd = dup(fd); /* check validity and make closeable */
	else
		fd = open(d->devname, mode | O_BINARY,
                    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
	if (fd == -1) {
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020005,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			"Failed to open device (a pseudo-drive)", errno, 0);
		d->cancel = 1;
		return -1;
	} 
	if (start_byte < 0)
		start_byte = 0;
	if (d->drive_role == 2 || d->drive_role == 5) {
		lseek_res = lseek(fd, start_byte, SEEK_SET);
		if (lseek_res == -1) {
			sprintf(msg, "Cannot address start byte %.f",
				(double) start_byte);
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020147,
				LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
				msg, errno, 0);
			close(fd);
			d->cancel = 1;
			fd = -1;
		}
	}
	d->nwa = start_byte / sector_size;
	return fd;
}


/* ts A70904 */
int burn_stdio_read_source(struct burn_source *source, char *buf, int bufsize,
			 	struct burn_write_opts *o, int flag)
{
	int count= 0, todo;

	for(todo = bufsize; todo > 0; todo -= count) {
		if(source->read!=NULL)
			count = source->read(source,
			    (unsigned char *) (buf + (bufsize - todo)), todo);
		else
			count = source->read_xt(source,
			    (unsigned char *) (buf + (bufsize - todo)), todo);
		if (count <= 0)
	break;
	}
	return (bufsize - todo);
}


/* ts A70904 */
int burn_stdio_write(int fd, char *buf, int count, struct burn_drive *d, 
			 int flag)
{
	int ret = 0;
	char *msg = NULL;
	int todo, done, retries;

	if (d->cancel || count <= 0)
		return 0;
	if(d->do_simulate)
		return 1;

	todo = count;
	done = 0;
	for (retries = 0; todo > 0 && retries <= Libburn_stdio_write_retrieS;
             retries++) {
/*
fprintf(stderr, "libburn_DEBUG: write(%d, %lX, %d)\n",
                fd, (unsigned long) buf, count);
*/
		ret = write(fd, buf + done, todo);
		if (ret < 0)
	break;
		done += ret;
		todo -= ret;
	}
	if (done != count) {
		BURN_ALLOC_MEM(msg, char, 160);

		sprintf(msg, "Cannot write desired amount of %d bytes.", count);
		if (retries > 1)
			sprintf(msg + strlen(msg), " Did %d retries. Last",
			        retries - 1);
		sprintf(msg + strlen(msg), " write(2) returned %d.", ret);
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020148,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			msg, errno, 0);
		d->cancel = 1;
		ret = 0; goto ex;
	}
	ret = 1;
ex:;
	BURN_FREE_MEM(msg);
	return ret;
}


/* ts A70910 : to be used as burn_drive.write(), emulating mmc_write() */
int burn_stdio_mmc_write(struct burn_drive *d, int start, struct buffer *buf)
{
	int ret;
	off_t start_byte;

	if (d->cancel)
		return BE_CANCELLED;
	if (d->stdio_fd < 0) {
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x0002017d,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"Invalid file descriptor with stdio pseudo-drive",
			0, 0);
		d->cancel = 1;
		return BE_CANCELLED;
	}
	if (start != d->nwa) {
		char msg[80];

		start_byte = ((off_t) start) * 
				(off_t) (buf->bytes / buf->sectors);
		if (lseek(d->stdio_fd, start_byte, SEEK_SET)==-1) {
			sprintf(msg, "Cannot address start byte %.f",
			 	(double) start_byte);
			libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00020147,
				LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
				msg, errno, 0);
			d->cancel = 1;
			return BE_CANCELLED;
		}
		d->nwa = start;
	}
	ret = burn_stdio_write(d->stdio_fd,(char *)buf->data, buf->bytes, d,0);
	if (ret <= 0)
		return BE_CANCELLED;
	d->nwa += buf->sectors;
	return 0;
}


/* ts A70910 : to be used as burn_drive.write(),
               emulating mmc_write() with simulated writing. */
int burn_stdio_mmc_dummy_write(struct burn_drive *d, int start,
							struct buffer *buf)
{
	if (d->cancel)
		return BE_CANCELLED;
	d->nwa = start + buf->sectors;
	return 0;
}


/* ts A70911 */
/* Flush stdio system buffer to physical device.
   @param flag bit0= do not report debug message (intermediate sync)
               bit1= do fsync(2) unconditionally
*/
int burn_stdio_sync_cache(int fd, struct burn_drive *d, int flag)
{
	int ret, do_fsync;
	char *msg = NULL;

	if (fd < 0) {
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x0002017d,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"Invalid file descriptor with stdio pseudo-drive",
			0, 0);
		d->cancel = 1;
		ret = 0; goto ex;
	}
	d->needs_sync_cache = 0;
	do_fsync = 0;
	if (flag & 2)
		do_fsync = 1;
	else if (d->write_opts != NULL)
		do_fsync = (d->write_opts->stdio_fsync_size >= 0);
	if (do_fsync) {
		if (!(flag & 1))
			libdax_msgs_submit(libdax_messenger, -1, 0x00000002,
				LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO,
				"syncing cache (stdio fsync)", 0, 0);
		ret = fsync(fd);
	} else {
		ret = 0;
	}
	if (ret != 0 && errno == EIO) {
		BURN_ALLOC_MEM(msg, char, 160);

		sprintf(msg,
		  "Cannot write desired amount of data. fsync(2) returned %d.",
		  ret);
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020148,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			msg, errno, 0);
		d->cancel = 1;
		ret = 0; goto ex;
	}
	ret = 1;
ex:;
	BURN_FREE_MEM(msg);
	return ret;
}


/* ts A70911 : to be used as burn_drive.sync_cache(),
               emulating mmc_sync_cache() */
void burn_stdio_mmc_sync_cache(struct burn_drive *d)
{
	burn_stdio_sync_cache(d->stdio_fd, d, 0);
}


/* ts C00824 : API */
/* Enforces nominal write speed */
int burn_nominal_slowdown(int kb_per_second, int max_corr,
			  struct timeval *prev_time,
			  int *us_corr, off_t b_since_prev, int flag)
{
	struct timeval tnow;
	double to_wait, goal, corr;
	int abs_max_corr;

	if (flag & 1) {
		gettimeofday(prev_time, NULL);
		*us_corr = 0;
		return 1;
	}
	if (kb_per_second <= 0)
		return 2;

	if (max_corr < -1.0e9 || max_corr > 1.0e9)
		abs_max_corr = 1000000000;
	else
		abs_max_corr = abs(max_corr);
	gettimeofday(&tnow, NULL);
	goal = ((double) b_since_prev) / 1000.0 / ((double) kb_per_second) +
		((double) prev_time->tv_sec) +
		((double) prev_time->tv_usec) / 1.0e6 +
		((double) *us_corr) / 1.0e6 ;
	to_wait = goal - ((double) tnow.tv_sec) -
		  ((double) tnow.tv_usec) / 1.0e6;

	/* usleep might be restricted to 999999 microseconds */
	while (to_wait > 0.0) {
		if (to_wait >= 0.5) {
			usleep(500000);
			to_wait -= 0.5;
		} else if (to_wait >= 0.00001) {
			usleep((int) (to_wait * 1000000.0));
			to_wait = 0.0;
		} else {
			to_wait = 0.0;
		}	
	}
	gettimeofday(prev_time, NULL);
	corr = (goal - ((double) prev_time->tv_sec) -
		((double) prev_time->tv_usec) / 1.0e6) * 1.0e6;
	if (corr > abs_max_corr)
		*us_corr = abs_max_corr;
	else if (corr < -abs_max_corr)
		*us_corr = -abs_max_corr;
	else
		*us_corr = corr;
	return 1;
}


/* ts A70904 */
int burn_stdio_write_track(struct burn_write_opts *o, struct burn_session *s,
				int tnum, int flag)
{
	int open_ended, bufsize = 16 * 2048, ret, sectors;
	struct burn_track *t = s->track[tnum];
	struct burn_drive *d = o->drive;
	char *buf = NULL;
	int i, prev_sync_sector = 0, us_corr = 0, max_corr = 250000;
	struct buffer *out = d->buffer;
	struct timeval prev_time;

	BURN_ALLOC_MEM(buf, char, bufsize);

	sectors = burn_track_get_sectors_2(t, 1);
	if (sectors < 0)
		{ret = 0; goto ex;}
	burn_disc_init_track_status(o, s, t, tnum, sectors);
	open_ended = burn_track_is_open_ended(t);

	t->end_on_premature_eoi = (o->write_type == BURN_WRITE_TAO);

	/* attach stdio emulators for mmc_*() functions */
	if (o->simulate)
		d->write = burn_stdio_mmc_dummy_write;
	else
		d->write = burn_stdio_mmc_write;
	d->do_simulate = o->simulate;
	d->sync_cache = burn_stdio_mmc_sync_cache;

	/* initialize */
	burn_nominal_slowdown(d->nominal_write_speed, max_corr,
				&prev_time, &us_corr, (off_t) 0, 1);

	for (i = 0; open_ended || i < sectors; i++) {
		/* transact a (CD sized) sector */
		if (!sector_data(o, t, 0))
			{ret= 0; goto ex;}
		if (open_ended)
			d->progress.sectors = sectors = d->progress.sector;
		if (open_ended || t->end_on_premature_eoi) {
			if (burn_track_is_data_done(t))
	break;
		}
		d->progress.sector++;
		/* Flush to disk from time to time */
		if (o->stdio_fsync_size > 0) {
			if (d->progress.sector - prev_sync_sector >=
			    o->stdio_fsync_size) {
				if (!o->simulate)
					burn_stdio_sync_cache(d->stdio_fd, d,
									 1);
				burn_nominal_slowdown(
					d->nominal_write_speed, max_corr,
					&prev_time, &us_corr,
					(off_t) (d->progress.sector -
						 prev_sync_sector) *
						 (off_t) 2048,
					0);
				prev_sync_sector = d->progress.sector;
			}
		} else if ((d->progress.sector % 512) == 0) {
			burn_nominal_slowdown(d->nominal_write_speed, max_corr,
				&prev_time, &us_corr, (off_t) (512 * 2048), 0);
		}
	}

	/* Pad up buffer to next full o->obs (usually 32 kB) */
	if (o->obs_pad && out->bytes > 0 && out->bytes < o->obs) {
		memset(out->data + out->bytes, 0, o->obs - out->bytes);
		out->sectors += (o->obs - out->bytes) / 2048;
		out->bytes = o->obs;
	}
	ret = burn_write_flush(o, t);
	ret= 1;
ex:;
	if (d->cancel)
		burn_source_cancel(t->source);
	if (t->end_on_premature_eoi == 2)
		d->cancel = 1;
	BURN_FREE_MEM(buf);
	return ret;
}


/* ts A70904 */
int burn_stdio_write_sync(struct burn_write_opts *o,
				 struct burn_disc *disc)
{
	int ret;
	struct burn_drive *d = o->drive;

	d->needs_close_session = 0;
	if (o->obs_pad < 2)
		o->obs_pad = 0; /* no filling-up of track's last 32k buffer */
	o->obs = 32*1024; /* buffer size */

	if (disc->sessions != 1)
		{ret= 0 ; goto ex;}
	if (disc->session[0]->tracks != 1)
		{ret= 0 ; goto ex;}
	
	/* update progress */
	d->progress.session = 0;
	d->progress.tracks = 1;

	/* >>> adjust sector size (2048) to eventual audio or even raw */

	/* >>> ??? ts B11004 : Why this eagerness to close and open ? */

	/* open target file */
	if (d->stdio_fd >= 0)
		close(d->stdio_fd);
	if (d->drive_role == 5 && d->status == BURN_DISC_APPENDABLE &&
            o->start_byte < 0)
		o->start_byte = d->role_5_nwa * 2048;
	d->stdio_fd = burn_stdio_open_write(d, o->start_byte, 2048, 0);
	if (d->stdio_fd == -1)
		{ret = 0; goto ex;}

	ret = burn_stdio_write_track(o, disc->session[0], 0, 0);
	if (ret <= 0)
		goto ex;

	/* XXX: currently signs an end of session */
	d->progress.sector = 0;
	d->progress.start_sector = 0;
	d->progress.sectors = 0;
	ret = 1;
ex:;

	/* >>> ??? ts B11004 : Why this eagerness to close ? */

	if (d->stdio_fd >= 0)
		close(d->stdio_fd);
	d->stdio_fd = -1;

	/* update pseudo-media state records by re-grabbing */
	burn_drive_mark_unready(d, 8);
	burn_drive_grab_stdio(d, 1);

	return ret;
}


void burn_disc_write_sync(struct burn_write_opts *o, struct burn_disc *disc)
{
	struct cue_sheet *sheet;
	struct burn_drive *d = o->drive;
	struct buffer *buffer_mem = o->drive->buffer;
	struct burn_session *s;
	struct burn_track *lt, *t;
	int first = 1, i, ret, lba, nwa = 0, multi_mem, stream_recording_start;
	off_t default_size;
	char msg[80];


/* ts A60924 : libburn/message.c gets obsoleted
	burn_message_clear_queue();
*/

	/* ts A61224 */
	burn_disc_init_write_status(o, disc); /* must be done very early */

	/* ts A80412 , A90227 , B90411 */
	if (o->do_stream_recording >= 16)
		stream_recording_start = o->do_stream_recording;
	else
		stream_recording_start = 0;
	burn_drive_set_stream_recording(d, !!o->do_stream_recording,
					stream_recording_start, 0);

	/* ts A91122 : Get buffer suitable for sources made by
	               burn_os_open_track_src() */
	d->buffer = burn_os_alloc_buffer(sizeof(struct buffer), 0);
	if (d->buffer == NULL)
		goto fail_wo_sync;

/* >>> ts A90321

	memset(d->buffer, 0, sizeof(struct buffer));

fprintf(stderr, "libburn_DEBUG: d->buffer = %lX , size = %d\n",
                (unsigned long) d->buffer, (int) sizeof(struct buffer));

calloc() seems not to have the desired effect. valgrind warns:
==18251== Syscall param write(buf) points to uninitialised byte(s)
==18251==    at 0x5071DEB: (within /lib64/libpthread-2.5.so)
==18251==    by 0x4723FA: burn_stdio_write (write.c:1850)
==18251==    by 0x4725DC: burn_stdio_mmc_write (write.c:1894)
==18251==    by 0x483B7A: get_sector (sector.c:229)
==18251==    by 0x484F11: sector_data (sector.c:639)
==18251==    by 0x4729FE: burn_stdio_write_track (write.c:2012)
==18251==    by 0x472CF4: burn_stdio_write_sync (write.c:2072)
==18251==    by 0x472E8D: burn_disc_write_sync (write.c:2125) <<< we are here
==18251==    by 0x460254: write_disc_worker_func (async.c:514)
==18251==    by 0x506B09D: start_thread (in /lib64/libpthread-2.5.so)
==18251==    by 0x55484CC: clone (in /lib64/libc-2.5.so)
*/

	d->rlba = -150;
	d->toc_temp = 9;

	if(d->drive_role == 4) {
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020181,
			LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
			"Pseudo-drive is a read-only file. Cannot write.",
			0, 0);
		goto fail_wo_sync;
	}
	/* ts A70904 */
	if (d->drive_role != 1) {
		ret = burn_stdio_write_sync(o, disc);
		if (ret <= 0)
			goto fail_wo_sync;
		goto ex;
	}
	/* ts A61218 */
	if (! d->current_is_cd_profile) {
		ret = burn_dvd_write_sync(o, disc);
		if (ret <= 0)
			goto fail_wo_sync;
		goto ex;
	}

	/* ts A70521 : GNU/Linux 2.4 USB audio fails with 64 kiB */
	/* ts A80414 : might need 64 kiB for BD-RE streaming */
        /* buffer flush trigger for sector.c:get_sector() */
	o->obs = Libburn_cd_obS;

	sprintf(msg, "cd Profile= %2.2Xh , obs= %d , obs_pad= %d",
		d->current_profile, o->obs, o->obs_pad);
	libdax_msgs_submit(libdax_messenger, d->global_index, 0x00000002,
		LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO, msg, 0, 0);

	/* ts A70218 */
	if (o->write_type == BURN_WRITE_SAO) {
		for (i = 0 ; i < disc->session[0]->tracks; i++) {
			t = disc->session[0]->track[i];
			if (burn_track_is_open_ended(t)) {
				default_size = burn_track_get_default_size(t);
				sprintf(msg,
					"Activated track default size %.f",
					(double) default_size);
				libdax_msgs_submit(libdax_messenger,
					d->global_index, 0x0002012e,
					LIBDAX_MSGS_SEV_NOTE,
					LIBDAX_MSGS_PRIO_HIGH, msg, 0, 0);
				burn_track_set_size(t, default_size);
			}
		}
	}

/* Apparently some drives require this command to be sent, and a few drives
return crap.  so we send the command, then ignore the result.
*/
	/* ts A61107 : moved up send_write_parameters because LG GSA-4082B
			 seems to dislike get_nwa() in advance */
	d->alba = d->start_lba; /* ts A61114: this looks senseless */
	d->nwa = d->alba;
	if (o->write_type == BURN_WRITE_TAO) {
		nwa = 0; /* get_nwa() will be called in burn_track() */
	} else {
		if (disc->sessions > 0)
			s = disc->session[0];
		else
			s = NULL;
		d->send_write_parameters(d, s, -1, o);

		ret = d->get_nwa(d, -1, &lba, &nwa);
		sprintf(msg,
			"SAO|RAW: Inquired nwa: %d , ret= %d , cap=%.f\n",
			nwa, ret, (double) d->media_capacity_remaining);
		libdax_msgs_submit(libdax_messenger, d->global_index,
				0x00000002,
				LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO,
				msg,0, 0);

		/* >>> ts A70212 : CD-DAO/SAO : eventually expand size of last track to maximum */;

	}

	for (i = 0; i < disc->sessions; i++) {
		/* update progress */
		d->progress.session = i;
		d->progress.tracks = disc->session[i]->tracks;

		/* ts A61114: added parameter nwa */
		sheet = burn_create_toc_entries(o, disc->session[i], nwa);

		/* ts A61009 */
		if (sheet == NULL)
			goto fail_wo_sync;

#ifdef Libburn_write_with_function_print_cuE
		print_cue(sheet);
		/* goto fail_wo_sync; */
#endif /* Libburn_write_with_function_print_cuE */

		d->medium_state_changed = 1;
		ret = 1;
		if (o->write_type == BURN_WRITE_SAO)
			ret = d->send_cue_sheet(d, sheet);
		if (sheet->data != NULL)
			free(sheet->data);
		free(sheet);
		if (ret <= 0)
			goto fail_wo_sync;

		/* --- From here on, final sync is needed. --- */

		if (o->write_type == BURN_WRITE_RAW) {
			if (!burn_write_leadin(o, disc->session[i], first))
				goto fail;
		} else {
			if (first) {

				/* ts A61030 : 0 made the burner take data. */
				/* ts A61103 : Meanwhile d->nwa is updated in
						burn_write_track()  */
				if(o->write_type == BURN_WRITE_TAO) {
					d->nwa= d->alba = 0;
				} else {

#ifdef Libburn_sao_can_appenD
					/* ts A61114: address for d->write() */
					if (d->status == BURN_DISC_APPENDABLE
					  && o->write_type == BURN_WRITE_SAO) {
						d->nwa = d->alba = nwa-150;

						sprintf(msg, 
				"SAO appendable d->nwa= %d\n", d->nwa);
						libdax_msgs_submit(
				libdax_messenger, d->global_index, 0x00000002,
				LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO,
				msg, 0, 0);

					} else {
						d->nwa = -150;
						d->alba = -150;
					}
#else
					d->nwa = -150;
					d->alba = -150;
#endif /* ! Libburn_sao_can_appenD */


				}

			} else {
				d->nwa += 4500;
				d->alba += 4500;
			}
		}
		multi_mem = o->multi;
		if(i < disc->sessions - 1)
			o->multi = 1;
		ret = burn_write_session(o, disc->session[i]);
		o->multi = multi_mem;
		if (!ret)
			goto fail;

		lt = disc->session[i]->track[disc->session[i]->tracks - 1];
		if (o->write_type == BURN_WRITE_RAW) {
			if (!burn_write_leadout(o, first, lt->entry->control,
			                        lt->mode))
				goto fail;
		} else {

			/* ts A61030 */
			if (o->write_type != BURN_WRITE_TAO)

				if (!burn_write_flush(o, NULL))
					goto fail;

			d->nwa += first ? 6750 : 2250;
			d->alba += first ? 6750 : 2250;
		}
		if (first)
			first = 0;

		/* XXX: currently signs an end of session */
		d->progress.sector = 0;
		d->progress.start_sector = 0;
		d->progress.sectors = 0;
	}

	/* ts A61030: extended skipping of flush to TAO: session is closed */
	if (o->write_type != BURN_WRITE_SAO && o->write_type != BURN_WRITE_TAO)
		if (!burn_write_flush(o, NULL))
			goto fail;

	sleep(1);

	/* ts A61125 : update media state records */
	burn_drive_mark_unready(d, 0);
	burn_drive_inquire_media(d);

	/* ts A61012 : This return was traditionally missing. I suspect this
			to have caused Cdrskin_eject() failures */
	goto ex;

fail:
	d->sync_cache(d);
fail_wo_sync:;
	usleep(500001); /* ts A61222: to avoid a warning from remove_worker()*/
	libdax_msgs_submit(libdax_messenger, d->global_index, 0x0002010b,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"Burn run failed", 0, 0);
	d->cancel = 1;
	/* <<< d->busy = BURN_DRIVE_IDLE; */
ex:;
	d->do_stream_recording = 0;
	if (d->buffer != NULL)
		burn_os_free_buffer((char *) d->buffer,
					sizeof(struct buffer), 0);
	d->buffer = buffer_mem;
	if (d->write_opts != NULL) {
		burn_write_opts_free(d->write_opts);
		d->write_opts = NULL;
	}
	if (d->write_retry_count > 0) {
		sprintf(msg, "WRITE command repetition happened %u times",
			d->write_retry_count);
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x000201ad,
			LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH,
			msg, 0, 0);
	}
	return;
}

/* ts A70811 : API function */
int burn_random_access_write(struct burn_drive *d, off_t byte_address,
				char *data, off_t data_count, int flag)
{
	int alignment = 0, start, upto, chunksize, err, fd = -1, ret;
	int do_close = 0, getfl_ret;
	char msg[81], *rpt;
	struct buffer *buf = NULL, *buffer_mem = d->buffer;

	BURN_ALLOC_MEM(buf, struct buffer, 1);
	if (d->released) {
		libdax_msgs_submit(libdax_messenger,
			d->global_index, 0x00020142,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"Drive is not grabbed on random access write", 0, 0);
		{ret = 0; goto ex;}
	}
	if(d->drive_role == 0) {
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020146,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"Drive is a virtual placeholder (null-drive)", 0, 0);
		{ret = 0; goto ex;}
	}
	if(d->drive_role == 4) {
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020181,
			LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
			"Pseudo-drive is a read-only file. Cannot write.",
			0, 0);
		{ret = 0; goto ex;}
	}

	if(d->drive_role == 2 || d->drive_role == 5)
		alignment = 2 * 1024;
	if (d->current_profile == 0x12) /* DVD-RAM */
		alignment = 2 * 1024;
        if (d->current_profile == 0x13) /* DVD-RW restricted overwrite */
		alignment = 32 * 1024;
	if (d->current_profile == 0x1a) /* DVD+RW */
		alignment = 2 * 1024;
	if (d->current_profile == 0x43) /* BD-RE */
		alignment = 2 * 1024;
	if (alignment == 0) {
		sprintf(msg, "Write start address not supported");
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020125,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			"Write start address not supported", 0, 0);
		{ret = 0; goto ex;}
	}
	if ((byte_address % alignment) != 0) {
		sprintf(msg,
			"Write start address not properly aligned (%d bytes)",
			alignment);
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020126,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			msg, 0, 0);
		{ret = 0; goto ex;}
	}
	if ((data_count % alignment) != 0) {
		sprintf(msg,
			"Write data count not properly aligned (%ld bytes)",
			(long) alignment);
		libdax_msgs_submit(libdax_messenger, d->global_index,
			0x00020141,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			msg, 0, 0);
		{ret = 0; goto ex;}
	}
	if (d->busy != BURN_DRIVE_IDLE) {
		libdax_msgs_submit(libdax_messenger,
			d->global_index, 0x00020140,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
			"Drive is busy on attempt to write random access",0,0);
		{ret = 0; goto ex;}
	}
	if (d->drive_role != 1) {
		if (d->stdio_fd >= 0) {
			/* Avoid to have a read-only fd open */
			getfl_ret = fcntl(d->stdio_fd, F_GETFL);
			if (((O_RDWR | O_WRONLY | O_RDONLY) & getfl_ret) ==
			    O_RDONLY) {
				close(d->stdio_fd);
				d->stdio_fd = -1;
			}
		}
		if (d->stdio_fd >= 0) {
			/* Avoid to have two fds open */
			fd = d->stdio_fd;
		} else {
			fd = burn_stdio_open_write(d, byte_address, 2048, 0);
			if (fd == -1)
				{ret = 0; goto ex;}
			do_close = 1;
		}
	}
	d->cancel = 0;
	d->busy = BURN_DRIVE_WRITING_SYNC;
	d->buffer = buf;

	start = byte_address / 2048;
	upto = start + data_count / 2048;
	rpt = data;
	for (; start < upto; start += 16) {
		chunksize = upto - start;
		if (chunksize > 16)
			chunksize = 16;
		d->buffer->bytes = chunksize * 2048;
		memcpy(d->buffer->data, rpt, d->buffer->bytes);
		rpt += d->buffer->bytes;
		d->buffer->sectors = chunksize;
		d->nwa = start;
		if(d->do_simulate) {
			err = 0;
		} else if(d->drive_role == 1) {
			err = d->write(d, d->nwa, d->buffer);
		} else {
			ret = burn_stdio_write(fd, (char *) d->buffer->data,
						d->buffer->bytes, d, 0);
			err = 0;
			if (ret <= 0)
				err = BE_CANCELLED;
		}
		if (err == BE_CANCELLED) {
			d->busy = BURN_DRIVE_IDLE;
			if(fd >= 0 && do_close)
				close(fd);
			{ret = -(start * 2048 - byte_address); goto ex;}
		}
	}

	if(d->drive_role == 1)
		d->needs_sync_cache = 1;
	if(flag & 1) {
		if(d->do_simulate) {
			;
		} else if(d->drive_role == 1)
			d->sync_cache(d);
		else
			burn_stdio_sync_cache(fd, d, 2);
		d->needs_sync_cache = 0;
	}
		
	if(fd >= 0 && do_close)
		close(fd);
	d->buffer = buffer_mem;
	d->busy = BURN_DRIVE_IDLE;
	ret = 1;
ex:
	BURN_FREE_MEM(buf);
	return ret;
}


/* ts B10527 */
/* @param bit0= force close, even if no damage was seen
*/
int burn_disc_close_damaged(struct burn_write_opts *o, int flag)
{
	struct burn_drive *d;
	int ret;
	enum burn_drive_status busy;

	d = o->drive;
	busy = d->busy;

	if (busy != BURN_DRIVE_IDLE) {
		libdax_msgs_submit(libdax_messenger,
			d->global_index, 0x00020106,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
			"Drive is busy on attempt to close damaged session",
			0, 0);
		{ret = 0; goto ex;}
	}
	if (!((d->next_track_damaged & 1) || (flag & 1))) {
		libdax_msgs_submit(libdax_messenger,
			d->global_index, 0x00020187,
			LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH,
			"Track not marked as damaged. No action taken.",
			0, 0);
		{ret = 0; goto ex;}
	}
	d->busy = BURN_DRIVE_WRITING;

	if (d->current_profile == 0x09 || d->current_profile == 0x0a) {
		/* Close CD track and session */
		o->write_type = BURN_WRITE_TAO; /* no action without TAO */

		/* Send mode page 5 */;
		d->send_write_parameters(d, NULL, -1, o);

		ret = burn_write_close_session(o);
		if (ret <= 0)
			goto ex;

	} else if(d->current_profile == 0x11 || d->current_profile == 0x14) {
		/* Close DVD-R[W] track and session */
		o->write_type = BURN_WRITE_TAO; /* no action without TAO */

		/* Send mode page 5 */;
		d->send_write_parameters(d, NULL, -1, o);

		ret = burn_disc_close_track_dvd_minus_r(o, 0);
		if (ret <= 0)
			goto ex;
		ret = burn_disc_close_session_dvd_minus_r(o);
		if (ret <= 0)
			goto ex;

	} else if(d->current_profile == 0x1b || d->current_profile == 0x2b) {
		/* Close DVD+R track and session */
		ret = burn_disc_close_track_dvd_plus_r(o, d->last_track_no, 1);
		if (ret <= 0)
			goto ex;

	} else if(d->current_profile == 0x41) {
		/* Close BD-R track and session */
		ret = burn_disc_close_track_dvd_plus_r(o, d->last_track_no, 1);
		if (ret <= 0)
			goto ex;

	} else {
		libdax_msgs_submit(libdax_messenger,
			d->global_index, 0x00020188,
			LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
			"Cannot close damaged track on given media type",
			0, 0);
		{ret = 0; goto ex;}

	}
	ret = 1;
ex:;
	d->busy = busy;
	/* Record with drive that repair was attempted */
	d->next_track_damaged &= ~1;
	return ret;
}


