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

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


#include "../config.h"


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

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

#include "source.h"
#include "libburn.h"
#include "file.h"
#include "async.h"
#include "init.h"
#include "util.h"

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


/* main channel data can be padded on read, but 0 padding the subs will make
an unreadable disc */


/* This is a generic OS oriented function wrapper which compensates
   shortcomings of read() in respect to a guaranteed amount of return data.
   See  man 2 read , paragraph "RETURN VALUE".
*/
static int read_full_buffer(int fd, unsigned char *buffer, int size)
{
	int ret,summed_ret = 0;

	/* make safe against partial buffer returns */
	while (1) {
		ret = read(fd, buffer + summed_ret, size - summed_ret);
		if (ret <= 0)
	break;
		summed_ret += ret;
		if (summed_ret >= size)
	break;
	}
	if (ret < 0)		 /* error encountered. abort immediately */
		return ret;
	return summed_ret;
}


static int file_read(struct burn_source *source,
		     unsigned char *buffer,
		     int size)
{
	struct burn_source_file *fs = source->data;

	return read_full_buffer(fs->datafd, buffer, size);
}

static int file_read_sub(struct burn_source *source,
			 unsigned char *buffer,
			 int size)
{
	struct burn_source_file *fs = source->data;

	return read_full_buffer(fs->subfd, buffer, size);
}

static void file_free(struct burn_source *source)
{
	struct burn_source_file *fs = source->data;

	close(fs->datafd);
	if (source->read_sub)
		close(fs->subfd);
	free(fs);
}

static off_t file_size(struct burn_source *source)
{
	struct stat buf;
	struct burn_source_file *fs = source->data;

	if (fs->fixed_size > 0)
		return fs->fixed_size;
	if (fstat(fs->datafd, &buf) != 0)
		return (off_t) 0;
	if ((buf.st_mode & S_IFMT) != S_IFREG)
		return (off_t) 0;
	return (off_t) buf.st_size;
}


/* ts A70125 */
static int file_set_size(struct burn_source *source, off_t size)
{
	struct burn_source_file *fs = source->data;

	fs->fixed_size = size;
	return 1;
}


struct burn_source *burn_file_source_new(const char *path, const char *subpath)
{
	struct burn_source_file *fs;
	struct burn_source *src;
	int fd1 = -1, fd2 = -1;

	if (!path)
		return NULL;
	fd1 = open(path, O_RDONLY | O_BINARY);
	if (fd1 == -1)
		return NULL;
	if (subpath != NULL) {
		fd2 = open(subpath, O_RDONLY | O_BINARY);
		if (fd2 == -1) {
			close(fd1);
			return NULL;
		}
	}
	fs = calloc(1, sizeof(struct burn_source_file));

	/* ts A70825 */
	if (fs == NULL) {
failure:;
		close(fd1);
		if (fd2 >= 0)
			close(fd2);
		return NULL;
	}

	fs->datafd = fd1;
	fs->subfd = fd2;

	/* ts A70125 */
	fs->fixed_size = 0;

	src = burn_source_new();

	/* ts A70825 */
	if (src == NULL) {
		free((char *) fs);
		goto failure;
	}

	src->read = file_read;
	if (subpath)
		src->read_sub = file_read_sub;

	src->get_size = file_size;
	src->set_size = file_set_size;
	src->free_data = file_free;
	src->data = fs;
	return src;
}


/* ts A70126 : removed class burn_source_fd in favor of burn_source_file */

struct burn_source *burn_fd_source_new(int datafd, int subfd, off_t size)
{
	struct burn_source_file *fs;
	struct burn_source *src;

	if (datafd == -1)
		return NULL;
	fs = burn_alloc_mem(sizeof(struct burn_source_file), 1, 0);
	if (fs == NULL) /* ts A70825 */
		return NULL;
	fs->datafd = datafd;
	fs->subfd = subfd;
	fs->fixed_size = size;

	src = burn_source_new();

	/* ts A70825 */
	if (src == NULL) {
		free((char *) fs);
		return NULL;
	}

	src->read = file_read;
	if(subfd != -1)
		src->read_sub = file_read_sub;
	src->get_size = file_size;
	src->set_size = file_set_size;
	src->free_data = file_free;
	src->data = fs;
	return src;
}


/* ts A71003 */
/* ------------------------------ fifo --------------------------- */

/* The fifo mechanism consists of a burn_source proxy which is here,
   a thread management team which is located in async.c,
   and a synchronous shoveller which is here.
*/

static int fifo_sleep(int flag)
{
	static unsigned long sleeptime = 50000; /* 50 ms */

	usleep(sleeptime);
	return 0;
}


static int fifo_read(struct burn_source *source,
		     unsigned char *buffer,
		     int size)
{
	struct burn_source_fifo *fs = source->data;
	int ret, todo, rpos, bufsize, diff, counted = 0;

	if (fs->end_of_consumption) {
		/* ??? msg: reading has been ended already */;
		return 0;
	}
        if (fs->is_started == 0) {
		ret = burn_fifo_start(source, 0);
		if (ret <= 0) {
			libdax_msgs_submit(libdax_messenger, -1, 0x00020152,
				 LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				"Cannot start fifo thread", 0, 0);
			fs->end_of_consumption = 1;
			return -1;
		}
		fs->is_started = 1;
	}
	if (size == 0)
		return 0;

	/* Reading from the ring buffer */

	/* This needs no mutex because each volatile variable has one thread
	   which may write and the other which only reads and is aware of
	   volatility.
	   The feeder of the ringbuffer is in burn_fifo_source_shoveller().
	*/
	todo = size;
	bufsize = fs->chunksize * fs->chunks;
	while (todo > 0) {
		/* readpos is not volatile here , writepos is volatile */
		rpos = fs->buf_readpos;
		while (rpos == fs->buf_writepos) {
			if (fs->end_of_input)
		break;
			if (fs->input_error) {
				if (todo < size) /* deliver partial buffer */
		break;
				fs->end_of_consumption = 1;
				libdax_msgs_submit(libdax_messenger, -1,
				   0x00020154,
				   LIBDAX_MSGS_SEV_NOTE, LIBDAX_MSGS_PRIO_HIGH,
				   "Forwarded input error ends output", 0, 0);
				return -1;
			}
			if (!counted)
				fs->empty_counter++;
			counted = 1;
			fifo_sleep(0);
		}
		diff = fs->buf_writepos - rpos; /* read volatile only once */
		if (diff == 0)
	break;
		if (diff > 0)
			/* diff bytes are available */;
		else 
			/* at least (bufsize - rpos) bytes are available */
			diff =  bufsize - rpos;
		if (diff > todo)
			diff = todo;
		memcpy(buffer, fs->buf+(size-todo)+rpos, diff);
		fs->buf_readpos += diff;
		if (fs->buf_readpos >= bufsize)
			fs->buf_readpos = 0;
		todo -= diff;
	}
	if (size - todo <= 0)
		fs->end_of_consumption = 1;
	else
		fs->out_counter += size - todo;

/*
	fprintf(stderr,
		"libburn_EXPERIMENTAL: read= %d , pos= %d , out_count= %.f\n",
		(size - todo), fs->buf_readpos, (double) fs->out_counter);
*/

	fs->get_counter++;
	return (size - todo);
}


static off_t fifo_get_size(struct burn_source *source)
{
	struct burn_source_fifo *fs = source->data;

	return fs->inp->get_size(fs->inp);
}


static int fifo_set_size(struct burn_source *source, off_t size)
{
	struct burn_source_fifo *fs = source->data;

	return fs->inp->set_size(fs->inp, size);
}


static void fifo_free(struct burn_source *source)
{
	struct burn_source_fifo *fs = source->data;
	int wait_count;
	static int wait_max = 30, wait_usleep = 100000;

	burn_fifo_abort(fs, 0);
	for (wait_count = 0; wait_count <= wait_max; wait_count++) {
		if (fs->thread_is_valid <= 0)
	break;
		if (wait_count < wait_max)
			usleep(wait_usleep);
	}
	if (wait_count > wait_max) {
		/* The shoveler thread might still be active. If so, it would
		   use invalid or inappropriate memory if the fifo would be
		   disposed now. A memory and resource leak is the better
		   option here.
		*/
		libdax_msgs_submit(libdax_messenger, -1,
				   0x000201ab,
				   LIBDAX_MSGS_SEV_WARNING,
				   LIBDAX_MSGS_PRIO_HIGH,
				   "Leaving burn_source_fifo object undisposed because it is possibly stuck but alive",
				   0, 0);
		return;
	}

	if (fs->inp != NULL)
		burn_source_free(fs->inp);

	if (fs->buf != NULL)
		burn_os_free_buffer(fs->buf,
			((size_t) fs->chunksize) * (size_t) fs->chunks, 0);
	free((char *) fs);
}


int burn_fifo_source_shoveller(struct burn_source *source, int flag)
{
	struct burn_source_fifo *fs = source->data;
	int ret, bufsize, diff, wpos, rpos, trans_end, free_bytes, fill;
	int counted;
	char *bufpt;
	pthread_t thread_handle_storage;

	fs->thread_handle= &thread_handle_storage;
	*((pthread_t *) fs->thread_handle)= pthread_self();
	fs->thread_pid = getpid();
	fs->thread_is_valid = 1;

	/* Lock was obtained by async.c:add_worker() */
	burn_async_manage_lock(BURN_ASYNC_LOCK_RELEASE);

	bufsize = fs->chunksize * fs->chunks;
	while (!fs->end_of_consumption) {
		if (fs->do_abort)
			goto emergency_exit;

		/* wait for enough buffer space available */
		wpos = fs->buf_writepos;
		counted = 0;
		while (1) {
			if (fs->do_abort)
				goto emergency_exit;
			rpos = fs->buf_readpos;
			diff = rpos - wpos;
			trans_end = 0;
			if (diff == 0)
				free_bytes = bufsize - 1;
			else if (diff > 0)
				free_bytes = diff - 1;
			else {
				free_bytes = (bufsize - wpos) + rpos - 1;
				if (bufsize - wpos < fs->inp_read_size)
					trans_end = 1;
			}
			if (free_bytes >= fs->inp_read_size)
		break;
			if (!counted)
				fs->full_counter++;
			counted = 1;
			fifo_sleep(0);
		}

		fill = bufsize - free_bytes - 1;
		if (fill < fs->total_min_fill)
			fs->total_min_fill = fill;
		if (fill < fs->interval_min_fill)
			fs->interval_min_fill = fill;

		/* prepare the receiving memory */
		bufpt = fs->buf + wpos;
		if (trans_end) {
			bufpt = burn_os_alloc_buffer(
					(size_t) fs->inp_read_size, 0);
			if (bufpt == NULL) {
				libdax_msgs_submit(libdax_messenger, -1,
				  0x00000003,
				  LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
				  "Out of virtual memory", 0, 0);
				fs->input_error = ENOMEM;
	break;
			}
		}

		/* Obtain next chunk */
		if (fs->do_abort)
			goto emergency_exit;
		if (fs->inp->read != NULL)
			ret = fs->inp->read(fs->inp,
				 (unsigned char *) bufpt, fs->inp_read_size);
		else
			ret = fs->inp->read_xt( fs->inp,
				 (unsigned char *) bufpt, fs->inp_read_size);
		if (ret == 0) {

			/* >>> ??? ts B00326 */
			/* >>> report EOF of fifo input and fs->in_counter */;

	break; /* EOF */
		} else if (ret < 0) {
			libdax_msgs_submit(libdax_messenger, -1, 0x00020153,
				 LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
				"Read error on fifo input", errno, 0);
			fs->input_error = errno;
			if(errno == 0)
				fs->input_error = EIO;
	break;
		}
		fs->in_counter += ret;
		fs->put_counter++;

		/* activate read chunk */
		if (fs->do_abort)
			goto emergency_exit;
		if (ret > fs->inp_read_size)
					/* beware of ill custom burn_source */
			ret = fs->inp_read_size;
		if (trans_end) {
			/* copy to end of buffer */
			memcpy(fs->buf + wpos, bufpt, bufsize - wpos);
			/* copy to start of buffer */
			memcpy(fs->buf, bufpt + (bufsize - wpos),
				fs->inp_read_size - (bufsize - wpos));
			burn_os_free_buffer(bufpt, (size_t) fs->inp_read_size,
						0);
			if (ret >= bufsize - wpos)
				fs->buf_writepos = ret - (bufsize - wpos);
			else
				fs->buf_writepos += ret;
		} else if (fs->buf_writepos + ret == bufsize)
			fs->buf_writepos = 0;
		else
			fs->buf_writepos += ret;

/*
		fprintf(stderr, "[%2.2d%%] ",
			 (int) (100.0 - 100.0 * ((double) free_bytes) /
						 (double) bufsize));
		fprintf(stderr,
			"libburn_EXPERIMENTAL: writepos= %d ,in_count = %.f\n",
			fs->buf_writepos, (double) fs->in_counter);
*/
	}
	if (!fs->end_of_consumption)
		fs->end_of_input = 1;

	/* wait for end of reading by consumer */;
	while (fs->buf_readpos != fs->buf_writepos && !fs->end_of_consumption) {
		if (fs->do_abort)
			goto emergency_exit;
		fifo_sleep(0);
	}

	/* destroy ring buffer */;
	if (!fs->end_of_consumption)
		fs->end_of_consumption = 2; /* Claim stop of consumption */

	/* This is not prone to race conditions because either the consumer
	   indicated hangup by fs->end_of_consumption = 1 or the consumer set
	   fs->buf_readpos to a value indicating the buffer is empty.
	   So in both cases the consumer is aware that reading is futile
	   or even fatal.
	*/
	if(fs->buf != NULL)
		burn_os_free_buffer(fs->buf,
			((size_t) fs->chunksize) * (size_t) fs->chunks, 0);
	fs->buf = NULL;

emergency_exit:;
	burn_async_manage_lock(BURN_ASYNC_LOCK_OBTAIN);
	fs->thread_handle= NULL;
	fs->thread_is_valid = 0;
	burn_async_manage_lock(BURN_ASYNC_LOCK_RELEASE);
	return (fs->input_error == 0);
}


int burn_fifo_cancel(struct burn_source *source)
{
	int ret;
	struct burn_source_fifo *fs = source->data;

	ret = burn_source_cancel(fs->inp);
	return ret;
}

/*
   @param flag bit0= allow larger read chunks
*/
struct burn_source *burn_fifo_source_new(struct burn_source *inp,
		 		int chunksize, int chunks, int flag)
{
	struct burn_source_fifo *fs;
	struct burn_source *src;

	if (((double) chunksize) * ((double) chunks) > 1024.0*1024.0*1024.0) {
		libdax_msgs_submit(libdax_messenger, -1, 0x00020155,
				 LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
				"Desired fifo buffer too large (> 1GB)", 0, 0);
		return NULL;
	}
	if (chunksize < 1 || chunks < 2) {
		libdax_msgs_submit(libdax_messenger, -1, 0x00020156,
				 LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
				"Desired fifo buffer too small", 0, 0);
		return NULL;
	}
	fs = burn_alloc_mem(sizeof(struct burn_source_fifo), 1, 0);
	if (fs == NULL)
		return NULL;
	fs->is_started = 0;
	fs->thread_handle = NULL;
	fs->thread_pid = 0;
	fs->thread_is_valid = 0;
	fs->do_abort = 0;
	fs->inp = NULL; /* set later */
	if (flag & 1)
		fs->inp_read_size = 32 * 1024;
	else
		fs->inp_read_size = chunksize;
	fs->chunksize = chunksize;
	fs->chunks = chunks;
	fs->buf = NULL;
	fs->buf_writepos = fs->buf_readpos = 0;
	fs->end_of_input = 0;
	fs->input_error = 0;
	fs->end_of_consumption = 0;
	fs->in_counter = fs->out_counter = 0;
	fs->total_min_fill = fs->interval_min_fill = 0;
	fs->put_counter = fs->get_counter = 0;
	fs->empty_counter = fs->full_counter = 0;

	src = burn_source_new();
	if (src == NULL) {
		free((char *) fs);
		return NULL;
	}
	src->read = NULL;
	src->read_sub = NULL;
	src->get_size = fifo_get_size;
	src->set_size = fifo_set_size;
	src->free_data = fifo_free;
	src->data = fs;
	src->version= 1;
	src->read_xt = fifo_read;
	src->cancel= burn_fifo_cancel;
	fs->inp = inp;
	inp->refcount++; /* make sure inp lives longer than src */

	return src;
}


/* ts A71003 : API */
int burn_fifo_inquire_status(struct burn_source *source,
		 int *size, int *free_bytes, char **status_text)
{
	struct burn_source_fifo *fs = source->data;
	int ret = 0, diff, wpos, rpos;
	static char *(states[8]) = {
			"standby", "active", "ending", "failing",
			"unused", "abandoned", "ended", "aborted"};

	*status_text = NULL;
	*size = 0;

	if (source->free_data != fifo_free) {
		libdax_msgs_submit(libdax_messenger, -1, 0x00020157,
				 LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
		  "burn_source is not a fifo object", 0, 0);
		return -1;
	}
	*size = fs->chunksize * fs->chunks;
	rpos = fs->buf_readpos;
	wpos = fs->buf_writepos;
	diff = rpos - wpos;
	if (diff == 0)
		*free_bytes = *size - 1;
	else if (diff > 0)
		*free_bytes = diff - 1;
	else
		*free_bytes = (*size - wpos) + rpos - 1;
	ret = 0;
	if (fs->end_of_consumption > 0)
		ret |= 4;
	if (fs->input_error)
		ret |= 3;
	else if (fs->end_of_input)
		ret |= 2;
	else if(fs->buf != NULL)
		ret |= 1;
	*status_text = states[ret];
	return ret;
}


/* ts A91125 : API */
void burn_fifo_get_statistics(struct burn_source *source,
                             int *total_min_fill, int *interval_min_fill,
                             int *put_counter, int *get_counter,
                             int *empty_counter, int *full_counter)
{
	struct burn_source_fifo *fs = source->data;

	*total_min_fill = fs->total_min_fill;
	*interval_min_fill = fs->interval_min_fill;
	*put_counter = fs->put_counter;
	*get_counter = fs->get_counter;
	*empty_counter = fs->empty_counter;
	*full_counter = fs->full_counter;
}


/* ts A91125 : API */
void burn_fifo_next_interval(struct burn_source *source,
                            int *interval_min_fill)
{ 
	struct burn_source_fifo *fs = source->data;
	int size, free_bytes, ret;
	char *status_text;

	*interval_min_fill = fs->interval_min_fill;
	ret = burn_fifo_inquire_status(source,
					 &size, &free_bytes, &status_text);
	if (ret < 0)
		return;
	fs->interval_min_fill = size - free_bytes - 1;
}


/* @param flag bit0= do not copy to buf but only wait until the fifo has read
                     bufsize or input ended.
                     The same happens if buf is NULL.
               bit1= fill to max fifo size
*/
int burn_fifo_fill_data(struct burn_source *source, char *buf, int bufsize,
                        int flag)
{
	int size, free_bytes, ret, wait_count= 0;
	char *status_text;
	struct burn_source_fifo *fs = source->data;

	if (buf == NULL)
		flag |= 1;

	/* Eventually start fifo thread by reading 0 bytes */
	ret = fifo_read(source, (unsigned char *) NULL, 0);
	if (ret<0)
		{ret = 0; goto ex;}

	/* wait for at least bufsize bytes being ready */
	while (1) {
		ret= burn_fifo_inquire_status(source,
					 &size, &free_bytes, &status_text);
		if (flag & 2) {
			bufsize = size - (size % fs->inp_read_size) -
					fs->inp_read_size;
			if (bufsize <= 0)
				{ret = 0; goto ex;}
		}
		if (size - fs->inp_read_size < bufsize) {
			if (flag & 1) {
				bufsize = size - (size % fs->inp_read_size) -
						fs->inp_read_size;
				if (bufsize <= 0)
					{ret = 0; goto ex;}
			} else {
				libdax_msgs_submit(libdax_messenger, -1,
					0x0002015c, LIBDAX_MSGS_SEV_FAILURE,
					 LIBDAX_MSGS_PRIO_HIGH,
			 	"Fifo size too small for desired peek buffer",
					 0, 0);
				{ret = -1; goto ex;}
			}
		}
		if (fs->out_counter > 0 || (ret & 4) || fs->buf == NULL) {
			libdax_msgs_submit(libdax_messenger, -1, 0x0002015e,
			LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
	 	"Fifo is already under consumption when peeking is desired",
			0, 0);
			{ret = -1; goto ex;}
		}
		if(size - free_bytes >= bufsize) {

			/* <<<
			fprintf(stderr,
		"libburn_DEBUG: after waiting cycle %d : fifo %s , %d bytes\n",
			 wait_count, status_text, size - free_bytes);
			*/
			if(!(flag & 1))
				memcpy(buf, fs->buf, bufsize);
			{ret = 1; goto ex;}
		}

		if (ret & 2) {
			/* input has ended, not enough data arrived */
			if (flag & 1)
				{ret = 0; goto ex;}
			libdax_msgs_submit(libdax_messenger, -1, 0x0002015d,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
		 	"Fifo input ended short of desired peek buffer size",
			0, 0);
			{ret = 0; goto ex;}
		}

		if (free_bytes < fs->inp_read_size) {
			/* Usable fifo size filled, not enough data arrived */
			if (flag & 1)
				{ret = 0; goto ex;}
			libdax_msgs_submit(libdax_messenger, -1, 0x00020174,
			LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
		 	"Fifo alignment does not allow desired read size",
			0, 0);
			{ret = 0; goto ex;}
		}

		usleep(100000);
		wait_count++;

		/* <<<
		if(wait_count%10==0)
			fprintf(stderr,
		 "libburn_DEBUG: waiting cycle %d : fifo %s , %d bytes\n",
			 wait_count, status_text, size - free_bytes);
		*/

	}
	ret = 0;
ex:;
	fs->total_min_fill = fs->interval_min_fill = fs->buf_writepos;
	return(ret);
}


/* ts A80713 : API */
int burn_fifo_peek_data(struct burn_source *source, char *buf, int bufsize,
                        int flag)
{
	return burn_fifo_fill_data(source, buf, bufsize, 0);
}


/* ts A91125 : API */
int burn_fifo_fill(struct burn_source *source, int bufsize, int flag)
{
	return burn_fifo_fill_data(source, NULL, bufsize,
					1 | ((flag & 1) << 1));
}


/* ----------------------------- Offset source ----------------------------- */
/* ts B00922 */

static void offst_free(struct burn_source *source);

/* @param flag bit0 = do not check for burn_source_offst, do not return NULL
*/
static struct burn_source_offst *offst_auth(struct burn_source *source,
								int flag)
{
	if (source->free_data != offst_free && !(flag & 1)) {
		libdax_msgs_submit(libdax_messenger, -1, 0x0002017a,
			LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
 			"Expected offset source object as parameter",
			0, 0);
		return NULL;
	}
	return (struct burn_source_offst *) source->data;
}

static off_t offst_get_size(struct burn_source *source)
{
        struct burn_source_offst *fs;

	if ((fs = offst_auth(source, 0)) == NULL)
		return (off_t) 0;
        return fs->nominal_size;
}

static int offst_set_size(struct burn_source *source, off_t size)
{
	struct burn_source_offst *fs;

	if ((fs = offst_auth(source, 0)) == NULL)
		return 0;

	fs->nominal_size = size;
	if (fs->size <= 0 || fs->size_adjustable)
		fs->size = size;
	return 1;
}

static void offst_free(struct burn_source *source)
{
	struct burn_source_offst *fs;

	if ((fs = offst_auth(source, 0)) == NULL)
		return;
	if (fs->prev != NULL)
		offst_auth(fs->prev, 1)->next = fs->next;
	if (fs->next != NULL)
		offst_auth(fs->next, 1)->prev = fs->prev;
	if (fs->inp != NULL)
		burn_source_free(fs->inp); /* i.e. decrement refcount */
	free(source->data);
}

static int offst_read(struct burn_source *source, unsigned char *buffer,
			int size)
{
	int ret, to_read, todo;
	struct burn_source_offst *fs;

	if ((fs = offst_auth(source, 0)) == NULL)
		return -1;

	/* Eventually skip bytes up to start position */;
	if (!fs->running) {
		if (fs->prev != NULL)
			fs->pos = offst_auth(fs->prev, 1)->pos;
		fs->running= 1;
	}
	if(fs->pos < fs->start) {
		todo = fs->start - fs->pos;
		while (todo > 0) {
			to_read = todo;
			if (to_read > size)
				to_read = size;
			ret = burn_source_read(fs->inp, buffer, to_read);
			if (ret <= 0)
				return ret;
			todo -= ret;
			fs->pos += ret;
		}
	}

	/* Produce EOF if source size is exhausted.
	   burn_source delivers no incomplete sector buffers.
	*/
	if (fs->pos + size > fs->start + fs->size)
		return 0;

	/* Read payload */
	ret = burn_source_read(fs->inp, buffer, size);
	if (ret > 0)
		fs->pos += ret;
	return ret;
}

static int offst_cancel(struct burn_source *source)
{
	int ret;
	struct burn_source_offst *fs;

	if ((fs = offst_auth(source, 0)) == NULL)
		return -1;
	ret = burn_source_cancel(fs->inp);
	return ret;
}

struct burn_source *burn_offst_source_new(
		struct burn_source *inp, struct burn_source *prev,
		off_t start, off_t size, int flag)
{
	struct burn_source *src;
	struct burn_source_offst *fs, *prev_fs = NULL;

	if (prev != NULL)
		if ((prev_fs = offst_auth(prev, 0)) == NULL)
			return NULL; /* Not type burn_source_offst */

	fs = calloc(1, sizeof(struct burn_source_offst));
	if (fs == NULL)
		return NULL;
        src = burn_source_new();
        if (src == NULL) {
                free((char *) fs);
                return NULL;
        }
        src->read = NULL;
        src->read_sub = NULL;
        src->get_size = offst_get_size;
        src->set_size = offst_set_size;
        src->free_data = offst_free;
        src->data = fs;
        src->version= 1;
        src->read_xt = offst_read;
        src->cancel= offst_cancel;
        fs->inp = inp;
	fs->prev = prev;
	fs->next = NULL;
	if (prev != NULL) {
		if (prev_fs->next != NULL) {
			offst_auth(prev_fs->next, 1)->prev = src;
			fs->next = prev_fs->next;
		}
		prev_fs->next = src;
		if (prev_fs->start + prev_fs->size > start) {
			libdax_msgs_submit(libdax_messenger, -1, 0x00020179,
			LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
 		"Offset source start address is before end of previous source",
			0, 0);
			return NULL;
		}
	}
	fs->start = start;
	fs->size = size;
	fs->size_adjustable = !(flag & 1);
	fs->nominal_size = size;
	fs->running = 0;
	fs->pos = 0;
        inp->refcount++; /* make sure inp lives longer than src */

        return src;
}


/* -------------------- WAVE file extractor ------------------- */


/* ts B30522 */
/* API
    @param flag         Bitfield for control purposes:
                        bit0= Report about progress by UPDATE message
                        bit3= Enable DAP : "flaw obscuring mechanisms like
                                            audio data mute and interpolate"

*/
int burn_drive_extract_audio(struct burn_drive *drive,
                             int start_sector, int sector_count,
                             char *target_path, int flag)
{
	int fd = -1, ret, todo, sector_no, val, min, sec, fr;
	int sectors_done= 0;
	off_t data_size, data_count = 0;
	time_t last_pacified = 0, now;
	char *msg = NULL, *buf = NULL;

	BURN_ALLOC_MEM(msg, char, 4096);
	BURN_ALLOC_MEM(buf, char, 24 * 2352);

	fd = open(target_path, O_WRONLY | O_CREAT | O_BINARY,
                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
	if (fd == -1) {
		sprintf(msg, "Cannot open disk file for writing: %.4000s",
				target_path);
		libdax_msgs_submit(libdax_messenger, -1, 0x000201a1,
				LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
				msg, errno, 0);
		ret = 0; goto ex;
	}

	/* WAV header */
	strcpy(buf, "RIFF");
	val = 4 + 8 + 16 + 8 + sector_count * 2352; /* ChunkSize */
	burn_int_to_lsb(val, buf + 4);
	strcpy(buf + 8, "WAVE");
	strcpy(buf + 12, "fmt ");
	burn_int_to_lsb(16, buf + 16);     /* Subchunk1Size */
	buf[20] = 1;                       /* AudioFormat */
	buf[21] = 0;
	buf[22] = 2;                       /* NumChannels */
	buf[23] = 0;
	burn_int_to_lsb(44100, buf + 24);  /* SampleRate */
	burn_int_to_lsb(176400, buf + 28); /* ByteRate */
	buf[32] = 4;                       /* BlockAlign */
	buf[33] = 0;
	buf[34] = 16;                      /* BitsPerSample */
	buf[35] = 0;
	strcpy(buf + 36, "data");
	burn_int_to_lsb(sector_count * 2352, buf + 40); /* Subchunk2Size */

	ret = write(fd, buf, 44);
	if (ret == -1)
		goto write_error;

	/* Audio data */
	todo = sector_count;
	sector_no = start_sector;
	while (todo > 0) {
		if (todo > 24)
			data_size = 24 * 2352;
		else
			data_size = todo * 2352;
		ret = burn_read_audio(drive, sector_no, buf, data_size,
						&data_count, flag & 8);
		if (ret <= 0) {
			sprintf(msg, "Failure to read audio sectors");
			libdax_msgs_submit(libdax_messenger, -1, 0x000201a4,
				LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
				msg, 0, 0);
			goto ex;
		}
		ret = write(fd, buf, data_count);
		if (ret == -1) {
write_error:;
			sprintf(msg,
				"Error while writing to disk file: %.4000s",
				target_path);
			libdax_msgs_submit(libdax_messenger, -1, 0x000201a2,
						LIBDAX_MSGS_SEV_FAILURE,
						LIBDAX_MSGS_PRIO_HIGH,
						msg, errno, 0);
			ret = 0; goto ex;
		}
		todo -= data_count / 2352;
		sectors_done += data_count / 2352;
		sector_no += data_count / 2352;
		if ((flag & 1) && (now = time(NULL)) - last_pacified >= 1) {
			last_pacified = now;
			burn_lba_to_msf(sectors_done, &min, &sec, &fr);
			sprintf(msg,
		   "Minutes:seconds of audio data read: %2d:%2.2d  (%6.2f MB)",
			   min, sec,
			   ((double) sectors_done) * 2352.0 / 1048576.0);
			libdax_msgs_submit(libdax_messenger, -1, 0x000201a3,
						LIBDAX_MSGS_SEV_UPDATE,
						LIBDAX_MSGS_PRIO_HIGH,
						msg, 0, 1);
		}
	}
	if ((flag & 1)) {
		burn_lba_to_msf(sectors_done, &min, &sec, &fr);
		sprintf(msg,
		  "Minutes:seconds of audio data read: %2d:%2.2d  (%6.2f MB)",
		   min, sec, ((double) sectors_done) * 2352.0 / 1048576.0);
		libdax_msgs_submit(libdax_messenger, -1, 0x000201a3,
					LIBDAX_MSGS_SEV_UPDATE,
					LIBDAX_MSGS_PRIO_HIGH,
					msg, 0, 0);
	}
	ret = 1;
ex:;
	BURN_FREE_MEM(buf);
	BURN_FREE_MEM(msg);
	if (fd != -1)
		close(fd);
	return ret;
}


/* ts B30522 */
/* API
    @param flag         Bitfield for control purposes:
                        bit0= Report about progress by UPDATE message
                        bit3= Enable DAP : "flaw obscuring mechanisms like
                                            audio data mute and interpolate"
*/
int burn_drive_extract_audio_track(struct burn_drive *drive,
                                   struct burn_track *track,
                                   char *target_path, int flag)
{
	int ret;
	struct burn_toc_entry toc_entry;

	burn_track_get_entry(track, &toc_entry);
	if (!(toc_entry.extensions_valid & 1)) {
		/* Can only happen if burn_disc_cd_toc_extensions() is skipped
		   in mmc_read_toc_al().
		*/
		libdax_msgs_submit(libdax_messenger, -1, 0x00000004,
				LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH,
	  "Internal libburn error: Outdated burn_toc_entry format encountered",
				errno, 0);
		return -1;
	}
	ret = burn_drive_extract_audio(drive, toc_entry.start_lba,
					toc_entry.track_blocks,
					target_path, flag & (1 | 8));
	return ret;
}

