/* cpfile.c  -- Copy a file overlapping read and write  */

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

#define BUFFERSIZE 8192


typedef struct bufferstruct {
  char b[BUFFERSIZE];
  int count;         /* Number of characters in b */
  int last;          /* !=0 iff it is last data buffer */
  pthread_mutex_t mutex;
  pthread_cond_t condition;
} buffer;

/* Operations on buffer */
void initbuffer (buffer * p);  
int fillbuffer (buffer * p); /* Returns 0 iff no more data */
int unfillbuffer (buffer * p); /* Returns 0 iff last data */

void filler(void); /* The procedure executed by the reading thread*/

buffer buf[2]; /* The two buffers used for concurrent reading, writing*/
int infile, outfile; /* File descriptors for input and output files   */

int main (int argc, char *argv[]){
  pthread_t t;
  int ubuffer; /* It is 0 or 1, indicating buffer to be emptied*/
  int rc;   /* Return Code */
  
  if (argc < 3) {
    printf("Usage: cpfile fromfile tofile\n");
    exit(0);}
  
  if ((infile = open(argv[1], O_RDONLY)) < 0) {
    printf("Cannot open %s\n", argv[1]); exit(0);}
  if ((outfile = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 
		      S_IRUSR|S_IRGRP|S_IROTH)) < 0) {
    printf("Cannot open %s\n", argv[2]); exit(0);}
  initbuffer(&buf[0]);
  initbuffer(&buf[1]);

  if ((rc = pthread_create(&t, NULL,
			   (void *)filler, NULL))!= 0) {
    fprintf(stderr, "Cannot create thread %s\n", strerror(rc));
    exit(1);}

  ubuffer = 0;
  while (unfillbuffer(&(buf[ubuffer])))
    ubuffer = 1-ubuffer;
  close(outfile);
  pthread_exit(0);

  return 0; /*Not executed - just to shut up a compiler warning */
}

/* The thread that reads from source file */
void filler(void){
  int fbuffer = 0;   /* It is 0 or 1, undicating buffer to be filled*/

  while (fillbuffer(&(buf[fbuffer])))
    fbuffer = 1-fbuffer;
  close(infile);
  pthread_exit (0);
}

void initbuffer (buffer * p) {
  int rc;
  if((rc = pthread_mutex_init(&(p->mutex), NULL)) != 0) {
    fprintf(stderr, "pthread_mutex_init %s\n", strerror(rc));
    exit(0);}
  if((rc = pthread_cond_init(&((p->condition)), NULL))
     != 0) {
    fprintf(stderr, "Cannot create thread %s\n", strerror(rc));
    exit(0);}
  p->count = 0;
  p->last = 0;
}

/* It reads from the infile into buffer.
   Returns 0 when no more data */
int fillbuffer (buffer * p) {
  int temp;
  pthread_mutex_lock(&(p->mutex));
  while ((p->count) > 0) 
    pthread_cond_wait(&((p->condition)), &(p->mutex));
  (p->count) = read(infile, p->b, BUFFERSIZE);
  if (p->count < 0) {
    fprintf(stderr, "%s\n", strerror(p->count));
    exit(0);
  }
  p->last = (p->count == 0);
  temp = p->count;
  pthread_cond_signal(&((p->condition)));
  pthread_mutex_unlock(&(p->mutex));
  return temp;
}

/* It writes into the outfile from buffer.
   Returns 0 iff last data */
int unfillbuffer (buffer * p) {
  int nn = 0;
  char *q;
  pthread_mutex_lock(&(p->mutex));
  while ((p->count) == 0) {
    pthread_cond_wait(&((p->condition)), &(p->mutex));}
  q = p->b;
  while (p->count > 0) {
    nn = write(outfile, q, p->count);
    if (nn < 0) {
      fprintf(stderr, "%s\n", strerror(nn));
      exit(0);
    }
    p->count -= nn;
    q += nn;
  }
  
  nn = p->last;
  pthread_cond_signal(&((p->condition)));
  pthread_mutex_unlock(&(p->mutex));
  return nn;
}




