//
// Copyright (c) 1997 Jean-Marc Zucconi
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer
//    in this position and unchanged.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. The name of the author may not be used to endorse or promote products
//    derived from this software without specific prior written permission
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <sys/types.h>
#ifdef USE_SHM
#include <sys/shm.h>
#else
#include <sys/mman.h>
#endif
#include <signal.h>

#include "cd-write.h"

#ifdef USE_SHM
static char *shmptr;
#else
static caddr_t shmptr;
#endif
static int shmid, shm_size, buf_size;
char **p_bufs = 0, **c_bufs = 0;
static int i_file, size, ibuf;
static FD fd;
static int track_last, block_size;
static int got_sig;
static cuesheet *cuelist;

static struct {
    char *name;
    int size;
    int fd;
    int type;
} files[200];

static char *device;
static struct worm_op *ops;
static int speed, dummy, cd_type, disk_at_once;
static char *lbuf = 0;

// BUFFER_SIZE must be a multiple of the max block size (2352 bytes)
// it must also be a multiple of the length of data written in a single
// scsi call: BUFFER_SIZE = OUTPUT_DIV * scsireq.len
const int OUTPUT_DIV = 8;
const int BUFFER_SIZE = 301056; // 8*(2352*16)
const int NBUFF = 6; // number of buffers. Memory required = NBUFF*BUFFER_SIZE
const int NFRAMES = 27;

#ifdef REALLY_DUMMY
#ifdef VERIFY
FILE *wfile;
#endif
#endif

static int output (char *, int);
static void output_init ();
static void
sigc (int x) 
{
    got_sig = SIGCHLD;
}
static void
sigp (int x) 
{
    got_sig = SIGPIPE;
}

void
set_params (char *dev, int s, int d, struct worm_op *o, int t, int dao, cuesheet *c)
{
    speed = s;
    dummy = d;
    ops = o;
    device = dev;
    cd_type = t;
    disk_at_once = dao;
    cuelist = c;
}
void 
add_file (int n, char *name, char *dir, int size, int type)
{
    if (files[n].name)
	free (files[n].name);
    if (files[n+1].name)
	free (files[n+1].name);
    files[n+1].name = 0;
    if ((type & T_PREGAP) == 0) {
	files[n].name = (char *) malloc (strlen (name)+strlen (dir)+2);
	strcpy (files[n].name, dir);
	strcat (files[n].name, "/");
	strcat (files[n].name, name);
    } else
	files[n].name =(char *) malloc (2);
    files[n].size = size;
    files[n].type = type;
    files[n].fd = -1;
    files[n+1].fd = -1;
}
static char *
alloc_shm ()
{

    shm_size = NBUFF * BUFFER_SIZE;

#ifdef USE_SHM
    shmid = shmget (IPC_PRIVATE, shm_size, IPC_CREAT | 0660);
    if (shmid < 0) 
	return strerror (errno);

    shmptr = (char *) shmat (shmid, 0, 0);
    if ((int)shmptr < 0)
	return strerror (errno);
#else
    shmptr = (char *)mmap (0, shm_size, PROT_READ|PROT_WRITE, 
		   MAP_ANON|MAP_SHARED, -1, 0);
    if (shmptr == MAP_FAILED)
	return strerror (errno);
#if 0 // I always get ``Resource temporarily unavailable'' :-(
    if (mlock (shmptr, shm_size) < 0) {
	Info (strerror (errno));
	Info ("(can't lock pages in memory)");
    }
#endif
#endif

    if (p_bufs) {
	free (p_bufs);
	free (c_bufs);
    }
    p_bufs = (char **) malloc (NBUFF*sizeof (char *));
    c_bufs = (char **) malloc (NBUFF*sizeof (char *));
    for (int i = 0; i < NBUFF; i++)
	p_bufs[i] = shmptr + i*BUFFER_SIZE;
    i_file = 0;
    lbuf = new char[2352*NFRAMES];
    bzero (lbuf, 2352*NFRAMES);
    ibuf = 0;
    return 0;
}
static void 
free_shm ()
{
#ifdef USE_SHM
    shmdt (shmptr);
    shmctl (shmid, IPC_RMID, 0);
#else
    munmap (shmptr, shm_size);
#endif
    delete [] lbuf;
}
static char *
fill_buff ()
{
    int l;

    if (files[i_file].fd < 0) {
	if (!files[i_file].name)
	    return 0;
	if ((files[i_file].type & T_PREGAP) == 0) {
	    files[i_file].fd = open (files[i_file].name, O_RDONLY);
	    if (files[i_file].fd < 0)
		return strerror (errno);
	} else
	    files[i_file].fd = 1;
	size = files[i_file].size;
	// if block size < 2352, reduce the buffer size
	if (files[i_file].type == T_DATA)
	    buf_size = 2048*(BUFFER_SIZE/2352);
	else
	    buf_size = BUFFER_SIZE;
    }
    if (size) {
	if (size < buf_size) {
	    l = size;
	    bzero (c_bufs[ibuf]+l, buf_size-l);
	} else
	    l = buf_size;
	if ((files[i_file].type & T_PREGAP) == 0) {
	    if (read (files[i_file].fd, c_bufs[ibuf], l) != l)
		return strerror (errno);
	} else
	    bzero (c_bufs[ibuf], l);
	size -= l;
	if (ops->swap && files[i_file].type != T_DATA &&
	    (files[i_file].type & T_PREGAP) == 0)
	    swab (c_bufs[ibuf], c_bufs[ibuf], l);
    }
    if (!size) {
	if ((files[i_file].type & T_PREGAP) == 0)
	    close (files[i_file].fd);
	files[i_file].fd = 0;
	i_file++;
    }
    ibuf++;
    if (ibuf == NBUFF)
	ibuf = 0;
    return 0;
}

char *
worm ()
{
    int i, c;
    char *p;
    static char str[1024];
    int pid;
    int p1[2], p2[2];
    int n, s;
    int leadin, leadout;

    p = alloc_shm ();
    if (p) 
	return p;

    if (pipe (p1) < 0 || pipe (p2) < 0) {
	free_shm ();
	return strerror (errno);
    }
    got_sig = 0;
    pid = fork ();
    if (pid < 0) {
	free_shm ();
	return strerror (errno);
    }
    if (pid == 0) {
#ifdef USE_SHM
	char *ptr = (char *) shmat (shmid, 0, 0);
#else
	char *ptr = shmptr;
#endif
	for (i = 0; i < NBUFF; i++)
	    c_bufs[i] = ptr + i*BUFFER_SIZE;
	close (p1[1]);
	close (p2[0]);
	while (1) {
	    read (p1[0], &c, 1);
	    c &= 0xff;
            if (c == 'Q')
		exit (0);
	    p = fill_buff ();
	    if (p) {
		strcpy (ptr, p);
		exit (1);
	    }
	    write (p2[1], &c, 1);
	}
    }

    close (p1[0]);
    close (p2[1]);
    signal (SIGCHLD, sigc);
    signal (SIGPIPE, sigp);
    // fill all buffers 
    c = 'R';
    for (i = 0; i < NBUFF; i++)
	write (p1[1], &c, 1);
    int jbuf = 0, k;
    do {
	sleep (1);
	ioctl (p2[0], FIONREAD, &k);
	if (got_sig) {
	    strncpy (str, shmptr, sizeof (str));
	    free_shm ();
	    if (got_sig == SIGCHLD)
		return str;
	    else
		return "child process died";
	}
    } while (k != NBUFF);

    
#ifndef REALLY_DUMMY
    fd =  cam_open_device(device, O_RDWR);
    if (!fd)
	return "Can't open device";
    start_unit (fd);
    Info ("Initializing disk");
    s = ops->disk_init (fd, speed, dummy);
#else
    printf("disk init fd=%d speed=%d dummy=%d toc-type=%d\n", 
	   fd, speed, dummy, cd_type);
    s = 0;
#endif
    if (s) {
	c = 'Q';
	write (p1[1], &c, 1);
	free_shm ();
	CLOSE (fd);
	return "Can't initialize disk";
    }
    if (disk_at_once) {
#ifndef REALLY_DUMMY
	ops->track_start (fd, T_DAO, BUFFER_SIZE / OUTPUT_DIV);
	s = ops->send_cuesheet (fd, cuelist, &leadin, &leadout);
	if (s)  {
	    c = 'Q';
	    write (p1[1], &c, 1);
	    free_shm ();
	    CLOSE (fd);
	    return "Can't initialize session";
	}
	sprintf (str, "Writing lead in\n");
	copy_progress_start (leadin);
	Info (str);
	n = 0;
	while (leadin) {
	    s = NFRAMES > leadin ? leadin : NFRAMES;
	    if (ops->write (fd, lbuf, s*2352)) {
		ops->track_end (fd);
		CLOSE (fd);
		return "Error";
	    }
	    leadin -= s;
	    if (++n == 5) {
		n = 0;
		copy_progress_msg (leadin);
	    }
	}
	copy_progress_msg (0);
#else
	printf ("lead in\n");
#endif
    }

    n = 1;
    for (i = 0; i < 200; i++) {
	if (!files[i].name)
	    break;
	int size = files[i].size;
	if (files[i].type == T_DATA)
	    block_size = 2048;
	else
	    block_size = 2352;
	buf_size = block_size * (BUFFER_SIZE / 2352);
	s = files[i].size/buf_size;
	track_last = files[i].size - s*buf_size;
	if (!disk_at_once)
	    sleep (3);
	if ((files[i].type & T_PREGAP) == 0) {
	    sprintf (str, "Writing track %d\n", n++);
	    copy_progress_start (size);
	    Info (str);
	}
#ifndef REALLY_DUMMY
	if (!disk_at_once)
	    if (ops->track_start (fd, files[i].type, buf_size / OUTPUT_DIV)) {
		c = 'Q';
		write (p1[1], &c, 1);
		free_shm ();
		CLOSE (fd);
		return "Can't initialize track";
	    }
#else
	printf ("track %d (%c): blocksize=%d  last_track=%d\n",
		i+1, files[i].type, block_size, track_last);
#ifdef VERIFY
	sprintf (str, "TRACK_%02d", i+1);
	wfile = fopen (str, "w");
	if (!wfile) {
	    perror (str);
	    exit (1);
	}
#endif
#endif
	while (size >= 0) {
	    if (got_sig) {
		strncpy (str, shmptr, sizeof (str));
		free_shm ();
		CLOSE (fd);
		if (got_sig == SIGCHLD)
		    return str;
		else
		    return "child process died";
	    }
	    if ((files[i].type & T_PREGAP) == 0)
		copy_progress_msg (size);
	    read (p2[0], &k, 1);
	    if (size)
		k = output (p_bufs[jbuf], size > buf_size ? buf_size : size);
	    else
		k = 0;
	    if (k) {
		if (disk_at_once) {
		    c = 'Q';
		    write (p1[1], &c, 1);
		    free_shm ();
		    CLOSE (fd);
		    return "Error";
		}
		if (k == WERR_UNRECOV) {
		    c = 'Q';
		    write (p1[1], &c, 1);
		    free_shm ();
		    if (!dummy) {
			Info ("trying to fixate the disk");
			if (ops->disk_end (fd, cd_type)) {
			    CLOSE (fd);
			    return "Can't fixate the disk";
			}
		    }
		    CLOSE (fd);
		    return "Error";
		} else {
		    size -= buf_size;
		    write (p1[1], &c, 1);
		    while (size > 0) {
			read (p2[0], &k, 1);
			write (p1[1], &c, 1);
			size -= buf_size;
			jbuf++;
			if (jbuf == NBUFF)
			    jbuf = 0;
		    }
		    ops->track_end (fd);
		    goto new_track;
		}
	    }
	    write (p1[1], &c, 1);
	    size -= buf_size;
	    jbuf++;
	    if (jbuf == NBUFF)
		jbuf = 0;
	    if (!size)
		size = -1;
	}
#ifndef REALLY_DUMMY
	if (!disk_at_once)
	    if (ops->track_end (fd)) {
		c = 'Q';
		write (p1[1], &c, 1);
		free_shm ();
		CLOSE (fd);
		return "Can't close track";
	    }
	if ((files[i].type & T_PREGAP) == 0)
	    copy_progress_msg (0);
#else
	printf ("close track\n");
#ifdef VERIFY
	fclose (wfile);
#endif
#endif
    new_track:
	;
    }
#ifndef REALLY_DUMMY
    if (disk_at_once) {
	sprintf (str, "Writing lead out\n");
	copy_progress_start (leadout);
	Info (str);
	n = 0;
	while (leadout) {
	    s = NFRAMES > leadout ? leadout : NFRAMES;
	    if (ops->write (fd, lbuf, s*2352)) {
		ops->track_end (fd);
		CLOSE (fd);
		return "Error";
	    }
	    leadout -= s;
	    if (++n == 5) {
		n = 0;
		copy_progress_msg (leadout);
	    }
	}
	copy_progress_msg (0);
	ops->track_end (fd);
    }
#else
    printf ("lead out\n");
#endif
    c = 'Q';
    write (p1[1], &c, 1);
    close (p1[1]);
    close (p2[0]);
    free_shm ();
    if (!dummy && !disk_at_once) {
	Info ("Fixating the disk (be patient)");
	if (ops->disk_end (fd, cd_type)) {
	    CLOSE (fd);
	    return "Can't fixate the disk";
	}
    }
    CLOSE (fd);
    return 0;
}

static int 
output (char *buf, int size)
{
    int i, n, len;

    len = buf_size / OUTPUT_DIV;

    for (i = 0; i < OUTPUT_DIV; i++) {
	if (size < len) {
	    n = size/block_size;
	    if (size % block_size)
		n++;
	    len = block_size * n;
	}
#ifndef REALLY_DUMMY
        n = ops->write (fd, buf, len);
	if (n)
	    return n;
#else
	printf ("W "); fflush (stdout);
#ifdef VERIFY
	if (fwrite (buf, 1, len, wfile) != len) {
	    perror ("write");
	    exit (1);
	}
#endif
#endif
	size -= len;
	if (size <= 0)
	    break;
	buf += len;
    }
    return 0;
}

