/* fichier filtre_thread.c
créé par Yann GUIDON (whygee@f-cpu.org)
version Fri Aug  5 18:32:37 CEST 2005

 fonction :
   prend un fichier, tourne en rond puis le réécrit
   mais plus sans temps mort.

 compilation :
   gcc -D_REENTRANT -lpthread \
       -o filtre_thread filtre_thread.c
 options:
      -DBUFFER_SIZE=taille_en_octets
      -DCHARGE=un_nombre

 invocation :
   filtre_thread nom_du_fichier_src nom_dest charge
*/

#define _FILE_OFFSET_BITS 64
#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>  /* open() */
#include <errno.h>  /* perror() */
#include <stdio.h>  /* printf() */
#include <stdlib.h> /* malloc(), strtol() */
#include <unistd.h> /* exit(), read() */
#include <signal.h> /* signal() */
#include <pthread.h>   /* threads */
#include <semaphore.h> /* semaphores */

/* permet de moduler la charge CPU */
#ifndef CHARGE
#define CHARGE (4)
#endif

#ifndef BLOCK_COUNT
#define BLOCK_COUNT (2048)
#endif

#ifndef BLOCK_SIZE
#define BLOCK_SIZE (16*1024)
#endif

#define BUFFER_SIZE (BLOCK_COUNT*BLOCK_SIZE)

char *buffer;
int charge = CHARGE, fd_in, fd_out;
ssize_t t = 0;
ssize_t byte_count[BLOCK_COUNT];
sem_t sem_lu, sem_ecrit;
pthread_t thr_lecture;


/* Cette partie du code se charge de remplir
  *buffer avec des blocs, tant qu'il y reste
  de la place et des octets à lire : */

void * thread_lecture (void * arg) {
  ssize_t t;
  int index=0, index_block=0;

  do {
    sem_wait(&sem_ecrit);
    t = read(fd_in, &buffer[index_block], BLOCK_SIZE);
    byte_count[index] = t;
    if (t < 0)
      perror("Erreur de lecture");
    else {
      /* renvoie le résultat et incrémente les compteurs locaux */
      index_block += BLOCK_SIZE;
      if (++index >= BLOCK_COUNT)
        index = index_block = 0;
    }
    sem_post(&sem_lu);
  } while (t > 0);

  /* mort naturelle sans conséquence : */
  return NULL;
}


/* En plus d'effectuer toute l'initialisation,
 le main() contient l'autre partie du programme qui
 effectue le "vrai travail" et écrit le résultat : */

int main (int argc, char *argv[]) {
  unsigned long int i;
  int index=0, index_block=0, j;

  if (argc < 3) {
    printf("Erreur d'argument\n\
 Spécifier un nom de fichier source et un\
 nom de fichier destination.\n");
    exit(EXIT_FAILURE);
  }

  fd_in = open(argv[1], O_RDONLY);
  if (fd_in == -1) {
    perror("Erreur à l'ouverture de la source");
    exit(EXIT_FAILURE);
  }

  fd_out = open(argv[2], O_WRONLY|O_CREAT);
  if (fd_out == -1) {
    perror("Erreur à l'ouverture du destinataire");
    exit(EXIT_FAILURE);
  }

  if (argc > 3) {
    charge=strtol(argv[3], NULL,10);
    if (charge < 0) {
      printf("Erreur d'argument : charge invalide ?\n");
      exit(EXIT_FAILURE);
    }
  }

  buffer = malloc(BUFFER_SIZE);
  if (buffer == NULL) {
    perror("Erreur de malloc() ");
    exit(EXIT_FAILURE);
  }

  /* C'est ici que ça devient intéressant */

  /* initialisation des sémaphores */
  sem_init(&sem_ecrit, 0, BLOCK_COUNT);
  sem_init(&sem_lu, 0, 0);

  /* création du thread de lecture */
  if (pthread_create (&thr_lecture, NULL, thread_lecture, NULL)) {
    perror("Erreur à la création du thread de lecture");
    exit(EXIT_FAILURE);
  }

  /* boucle d'attente-xoriture-écriture */
  while(1) {
    sem_wait(&sem_lu);
    t = byte_count[index];
    if (t > 0) {

      /* charge le CPU */
      for (j = 0; j < charge; j++)
        for (i = 0; i < t; i++)
          buffer[i+index_block] ^= j;

      write(fd_out, &buffer[index_block], t);

      index_block += BLOCK_SIZE;
      if (++index >= BLOCK_COUNT)
        index = index_block = 0;
      
      sem_post(&sem_ecrit);
    }
    else {
      pthread_join(thr_lecture, NULL);
      exit(EXIT_SUCCESS);
    }
  }
}
