/*****************************************************************************
* 	  Copyright (c) 1988 Thinking Machines Corporation, Inc.,	     *
*		of Cambridge, Mass.   All rights reserved.		     *
*									     *
*  This notice is intended as a precaution against inadvertent publication   *
*  and does not constitute an admission or acknowledgement that publication  *
*  has occurred or constitute a waiver of confidentiality.		     *
*									     *
*  Connection Machine software is the proprietary and confidential property  *
*  of Thinking Machines Corporation.					     *
*****************************************************************************/

#include <stdio.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/signal.h>
#include <sys/time.h>
#include <sys/errno.h>

#include <cm/cm_file.h>
#include <cm/cm_errno.h>

#ifdef notdef
#include "../StorageTek/stcreg.h"
#endif

#ifndef min
#define min(x, y) (((x) > (y)) ? (y) : (x))
#endif

extern int errno;
extern char *sys_errlist[];
extern char *CMFS_sys_errlist[];
extern int CMI_soft_error_print;

int virtual_width = 1;
int physical_width = 1;

int input_block_size;
int output_block_size;

int verbose = 0;

int unix_input_block_size = 32*1024;
int unix_output_block_size = 32*1024;

/*#define DEBUG 1*/
#define FREE 1
#define FULL 2
#define READING 3
#define WRITING 4

#define NBUF 2

#define SHBUFSIZE (2016*1024)	
#undef SHBUFSIZE
#define SHBUFSIZE (480*1024)

struct shbuf {
    int writer_error_flag;
    int reader_error_flag;
    int buf_status[NBUF];
    int buf_sequence[NBUF];
    struct {
	int size;
	int bytes_valid;
	char data[SHBUFSIZE];
    } buf_buf[NBUF];
};

struct shbuf *shbuf;
int reader;
int writer;
int sawsig;
int expected_sequence;
int sequence;

static void
signal_handler()
{
    sawsig = 1;
}

fast_copy(infile, outfile, flags)
char *infile, *outfile;
int flags;
{
    int shm_id;
    int i;
    void (*old_sighup)();
    struct shmid_ds sd;
    int total_bytes_read;

    CMI_soft_error_print = 1;

    shm_id = shmget(getpid(), sizeof (struct shbuf), IPC_CREAT|IPC_EXCL);
    if (shm_id == -1) {
	perror("shmget");
	return -1;
    }
    /* make it accessable by other users */
    sd.shm_perm.mode = 0644;
    sd.shm_perm.uid = getuid();
    sd.shm_perm.gid = getgid();
    if (shmctl(shm_id, IPC_SET, &sd) < 0)
	perror("shmctl set permissions");

    shbuf = (struct shbuf *)shmat(shm_id, 0, 0);
    if ((int)shbuf == -1) {
	perror("shmat");
	(void) shmctl(shm_id, IPC_RMID, 0);
	return -1;
    }
    (void) shmctl(shm_id, IPC_RMID, 0);

    /* Mark all buffers as free */
    for (i = 0; i < NBUF; i++) {
	shbuf->buf_status[i] = FREE;
	shbuf->buf_buf[i].size = SHBUFSIZE;
    }
    /* No error */
    shbuf->reader_error_flag = shbuf->writer_error_flag = 0;

    expected_sequence = sequence = 0;
    if (flags & 1)
	input_block_size = SHBUFSIZE;
    else
	input_block_size = unix_input_block_size;

    if (flags & 2)
	output_block_size = SHBUFSIZE;
    else
	output_block_size = unix_output_block_size;

    reader = getpid();
    old_sighup = signal(SIGHUP, signal_handler);

    if ((writer = fork())) {
	int infd;
	int foo=0;
	/* Parent, reader process */

	if (flags & 1) {
	    infd = CMFS_open(infile, CMFS_O_RDONLY);
	    if (infd < 0) {
		(void) kill(writer, SIGKILL);
		shbuf->reader_error_flag = CMFS_errno;
		goto done;
	    }
	} else {
	    infd = open(infile, O_RDONLY);
	    if (infd < 0) {
		perror(infile);
		(void) kill(writer, SIGKILL);
		shbuf->reader_error_flag = errno;
		goto done;
	    }
	}
	while (1) {
	    register char *p;
	    int bytes_available;
	    int buf_no;
	    int bytes_read;
	    int bytes_to_read;

	    buf_no = get_free_buf();
#ifdef DEBUG
	    printf("Reading into buf %d\n", buf_no);
	    fflush(stdout);
#endif
	    if (buf_no < 0) {
		/* Writer error */
		break;
	    }
	    p = shbuf->buf_buf[buf_no].data;
	    bytes_available = shbuf->buf_buf[buf_no].size;
	    bytes_to_read = min(bytes_available, input_block_size);
	    while (bytes_available >= input_block_size) {
		bytes_to_read = min(bytes_available, input_block_size);
		if (flags & 1)
		    bytes_read = CMFS_serial_read_file(infd, p, bytes_to_read);
		else
		    bytes_read = read(infd, p, bytes_to_read);
		if (bytes_read < 0) {
		    /* handle error */
		    shbuf->reader_error_flag = (flags & 1) ? CMFS_errno : errno;
		    break;
		}
		if (bytes_read == 0) {
		    /* handle EOF */
		    shbuf->reader_error_flag = -1; /* EOF */
		    break;
		}
		bytes_available -= bytes_read;
		p += bytes_read;
		total_bytes_read += bytes_read;
	    }
	    shbuf->buf_buf[buf_no].bytes_valid = 
		shbuf->buf_buf[buf_no].size - bytes_available;
	    mark_buffer_full(buf_no);
	    if (bytes_read <= 0)
		break;
	    if (verbose) {
		printf("%d    \r", total_bytes_read);
		fflush(stdout);
	    }
		
	}
	if (flags & 1)
	    CMFS_close(infd);
	else
	    close(infd);
    done:

#ifdef DEBUG
	printf("Reader done... waiting for writer\n");
	fflush(stdout);
#endif

	/* Wait for writer to exit */
	while (wait(0) != writer)
	    ;

	(void) signal(SIGHUP, old_sighup);

	{
	    char *reader_status, *writer_status;

	    if (shbuf->reader_error_flag == 0)
		reader_status = NULL;
	    else if (shbuf->reader_error_flag == -1)
		reader_status = NULL;
	    else if (flags&1)
		reader_status = CMFS_sys_errlist[shbuf->reader_error_flag];
	    else
		reader_status = sys_errlist[shbuf->reader_error_flag];

	    if (shbuf->writer_error_flag == 0)
		writer_status = NULL;
	    else if (shbuf->writer_error_flag == -1)
		writer_status = NULL;
	    else if (flags&2)
		writer_status = CMFS_sys_errlist[shbuf->writer_error_flag];
	    else
		writer_status = sys_errlist[shbuf->writer_error_flag];

	    if (reader_status)
		fprintf(stderr, "Error reading %s: %s\n",
			infile, reader_status);
	    if (writer_status)
		fprintf(stderr, "Error writing %s: %s\n",
			outfile, writer_status);
	    if (reader_status || writer_status)
		total_bytes_read = -1;
	}
	return (total_bytes_read);

    } else {
	int outfd;
	/* Child */
	
	/* Open output file */
	if (flags & 2) {
	    outfd = CMFS_open(outfile, 
			      CMFS_O_RECORDWISE|CMFS_O_WRONLY|CMFS_O_TRUNC|CMFS_O_CREAT, 
			      0666,
			      physical_width, virtual_width);
	    if (outfd < 0) {
		shbuf->writer_error_flag = CMFS_errno;
		exit(1);
	    }
	} else {
	    outfd = open(outfile, O_WRONLY|O_TRUNC|O_CREAT, 0666);
	    if (outfd < 0) {
		shbuf->writer_error_flag = errno;
		exit(1);
	    }
#ifdef notdef
	    if (ioctl(outfd, STCIOC_SET_BLKSIZE, &unix_output_block_size) == 0)
		output_block_size = SHBUFSIZE/unix_output_block_size 
		    * unix_output_block_size;
#endif
	}

	while (1) {
	    int buf_no;
	    char *p;
	    int bytes_written;
	    int bytes_available;
	    int bytes_to_write;

	    buf_no = get_full_buf();
	    if (buf_no == -1) {
		/* EOF or error on read */
		if (flags & 2)
		    CMFS_close(outfd);
		else
		    close(outfd);
		exit(0);
	    }
	    p = shbuf->buf_buf[buf_no].data;
#ifdef DEBUG
	    printf("Writing buf %d at 0x%x (%d bytes)\n", buf_no,
		   p,
		   shbuf->buf_buf[buf_no].bytes_valid);
	    fflush(stdout);
#endif
	    bytes_available = shbuf->buf_buf[buf_no].bytes_valid;
	    while (bytes_available > 0) {
		bytes_to_write = min(bytes_available, output_block_size);
		if (bytes_to_write < output_block_size) {
		    int zero = 0;
#ifdef notdef
		    (void) ioctl(outfd, STCIOC_SET_BLKSIZE, &zero);
#endif
		}
		    
		if (flags & 2)
		    bytes_written = CMFS_serial_write_file(outfd, p, 
							   bytes_to_write);
		else
		    bytes_written = write(outfd, p, bytes_to_write);
		if (bytes_written < 0) {
		    /* Handle write error */
		    shbuf->writer_error_flag = errno;
		    if (flags & 2)
			CMFS_close(outfd);
		    else
			close(outfd);
		    exit(1);
		}
		bytes_available -= bytes_written;
		p += bytes_written;
	    }
	    mark_buffer_free(buf_no);
	}
	/* NOTREACHED */
    }
    /* NOTREACHED */
}

/* Called only by reader (parent) */
static get_free_buf()
{
    int i;

    while (1){
	sawsig = 0;
	for (i = 0; i < NBUF; i++)
	    if (shbuf->buf_status[i] == FREE) {
		shbuf->buf_status[i] = READING;
		return (i);
	    }
	if (shbuf->writer_error_flag)
		return -1;
	if (sawsig == 0) {
	    /*printf("\r                   \rReader waiting...\r");
	    fflush(stdout);*/
	    pause();
	}
    }
}


/* called only by writer (child) */
static get_full_buf()
{
    int i;

    while (1){
	sawsig = 0;
	for (i = 0; i < NBUF; i++)
	    if (shbuf->buf_sequence[i] == expected_sequence)
		if (shbuf->buf_status[i] == FULL) {
		    expected_sequence++;
		    shbuf->buf_status[i] = WRITING;
		    return (i);
		}

	if (shbuf->reader_error_flag)
	    return -1;
	if (sawsig == 0) {
	    /*printf("\r                   \rWriter waiting...\r");
	    fflush(stdout);*/
	    pause();
	}
    }
}

/* Called only by writer (child) */
static mark_buffer_free(buf)
int buf;
{
    shbuf->buf_status[buf] = FREE;
    kill(reader, SIGHUP);
}

/* Called only by reader (parent) */
static mark_buffer_full(buf)
int buf;
{

    shbuf->buf_sequence[buf] = sequence++;
    shbuf->buf_status[buf] = FULL;
    kill(writer, SIGHUP);
}


main(ac, av)
char **av;
{
    int flags = 0;
    char *infile = NULL;
    char *outfile = NULL;
    struct timeval starttime, endtime;
    double timediff, rate;
    int nbytes;

    if (ac < 3) {
    usage:
	fprintf(stderr, "usage: %s [-todv] [-fromdv] [-v] infile outfile\n",
		av[0]);
	exit(1);
    }

    av++; ac--;
    while (infile == NULL && outfile == NULL) {
	if (strcmp(av[0], "-todv") == 0) {
	    flags |= 2;
	    av++; ac--;
	    continue;
	}
	if (strcmp(av[0], "-fromdv") == 0) {
	    flags |= 1;
	    av++; ac--;
	    continue;
	}
	if (strcmp(av[0], "-virtual") == 0) {
	    virtual_width = atoi(av[1]);
	    av++; ac--;
	    av++; ac--;
	    continue;
	}
	if (strcmp(av[0], "-physical") == 0) {
	    physical_width = atoi(av[1]);
	    av++; ac--;
	    av++; ac--;
	    continue;
	}
	if (strcmp(av[0], "-v") == 0) {
	    verbose++;
	    av++; ac--;
	    continue;
	}
	if (ac < 2)
	    goto usage;
	infile = av[0];
	outfile = av[1];
    }

    if (gettimeofday(&starttime, 0) < 0) {
	perror("gettimeofday");
    }
    nbytes = fast_copy(infile, outfile, flags);
    if (nbytes < 0)
	exit(1);

    if (gettimeofday(&endtime, 0) < 0) {
	perror("gettimeofday");
    }
    timediff = (double) tv_usec_diff(&starttime, &endtime)
	/ 1e6;

    rate = (nbytes / timediff) / 1e3;

    printf("%d bytes in %.2f seconds (%.2f kbytes/sec)  \n", 
	   nbytes, timediff, rate);
    exit(0);
}


static long
tv_usec_diff(tv0, tv1)
struct timeval *tv0, *tv1;
{
  long val;

  val = (tv1->tv_sec - tv0->tv_sec) * 1000000;
  val += tv1->tv_usec - tv0->tv_usec;
  return val;
}
