/****************************************
 * Gestion de l'interface avec le shell
 * init_pipe() démarre le shell et crée les deux pipes
 * envoieshell() envoie une commande à sh (sans '\n' au bout)
 * recoitshell() reçoit une ligne de la réponse (ou NULL après la dernière)
 * execshell() exécute une commande shell sans attendre la réponse
 * erreur() provoque une erreur générale
 * *************************************/

#include "prehtml.h"

/* fichiers inclus uniquement dans cette partie, pour cette raison
 * ils ne sont pas dans prehtml.h */
#include <signal.h>
#include <wait.h>

FILE *pin=NULL,*pout=NULL;
static int result_fork;
static char envoyee[LONGUEUR_LIGNE];

void erreur(char *format, ...){
  va_list ap;
  va_start(ap,format);
  dit_ou();
  fprintf(stderr,"prehtml: ");
  vfprintf(stderr,format,ap);
  putc('\n',stderr);
  exit(1);
}

void warning(char *format, ...){
  va_list ap;
  va_start(ap,format);
  dit_ou();
  fprintf(stderr,"prehtml: attention: ");
  vfprintf(stderr,format,ap);
  putc('\n',stderr);
}

typedef struct _listetemp{
  char *nom;
  struct _listetemp *suivant;
} listetemp;
static listetemp premiertemp, *tempactu=NULL;

void creetemp(char *nom){ /* signale la création d'un fichier temporaire,
			   * pour permettre le nettoyage si nécessaire */
  if (!tempactu) tempactu=&premiertemp;
  tempactu->nom=strdup(nom);
  tempactu->suivant=malloc(sizeof(listetemp));
  tempactu=tempactu->suivant;
  tempactu->nom=NULL;
}

void fin_urgente(int numsignal){
  char nom[10];
  switch(numsignal){
   case SIGALRM: strcpy(nom,"SIGALRM");break;
   case SIGHUP: strcpy(nom,"SIGHUP");break;
   case SIGINT: strcpy(nom,"SIGINT");break;
   case SIGQUIT: strcpy(nom,"SIGQUIT");break;
   case SIGABRT: strcpy(nom,"SIGABT");break;
   default: sprintf(nom,"n°%-5d",numsignal);
  }
  if (numsignal)
    warning("Interruption reçue: %s\n"
	  "commande éventuellement en cours de traitement: \n"
	  "   <%s %s>\n"
	  "Nettoyage des fichiers temporaires",
	  nom,(fonction?fonction:"[aucune]"),(chaine?chaine:""));
  else fprintf(stderr,"Nettoyage des fichiers temporaires\n\n");
  /* la fonction suivante est une extension GNU !!!
   elle sert à fermer les fichiers avant de les refermer;
   il semble que ça marche quand même si on ne la met pas...*/
  fcloseall();
  if (tempactu){ /* on a créé des fichiers temporaires*/
    tempactu=&premiertemp;
    while (tempactu->nom){
      unlink(tempactu->nom);
      tempactu=tempactu->suivant;
    }
  }
  if (numsignal){
    /* je remets le comportement pas défaut, et je relance le signal */
    signal(numsignal, SIG_DFL);
    raise(numsignal);
  }
}
  

static int alarmstop=0;
void alarme(int numsignal){
  if (alarmstop){
    fprintf(stderr,"shell: la commande:\n     \"%s\"\n"
	 " ne répond pas: peut-être ceci vient-il de la balise:\n"
	 "     <%s %s>\n"
	 "Vérifiez les guillemets incomplets, crochets, etc...\n\n",
	 envoyee,fonction,chaine);
    fin_urgente(0);
    exit(1);
  }
  else
    warning("shell:  la commande:\n     \"%s\"\n"
	    " semble durer un peu longtemps: est-ce cette balise?\n"
	    "     <%s %s>",
	 envoyee,fonction,chaine);
}

/*void init_pipe_old(void){
  char nompipein[]="/tmp/preHTMLinXXXXXX",
    nompipeout[]="/tmp/preHTMLoutXXXXXX";
  char commande[120];
  mktemp(nompipein);mktemp(nompipeout);
  creetemp(nompipein); // fichier à effacer si arrêt d'urgence
  creetemp(nompipeout); // fichier à effacer si arrêt d'urgence
  if (mkfifo(nompipein,0666|O_CREAT)==-1 || 
      mkfifo(nompipeout,0666|O_CREAT)==-1)
    erreur("Pas pu créer les fichiers temporaires FIFO");
  result_fork=fork();
  if (result_fork==-1) erreur("Le fork n'a pas marché");
  if (result_fork==0){ // process fils
    // attention à l'ordre des 2 fichiers qui suivent... 
    freopen(nompipeout,"w",stdout);
    freopen(nompipein,"r",stdin);
    snprintf(commande,120,"exec sh %s >%s",nompipein,nompipeout);
    execlp("sh","sh","-c",commande,NULL);
    erreur("exec sh n'a pas marché");
  }
  else{
    // ... car si on ne les ouvre pas dans le même ordre ici, ça bloque
    pin=fopen(nompipeout,"rb");
    pout=fopen(nompipein,"wb");
    // on vérifie que ça fonctionne
    fputs("echo\n",pout);
    fflush(pout);
    while(fgetc(pin)!='\n');
    // puis on efface les fichiers FIFO (mais les pipes restent)
    unlink(nompipein);
    unlink(nompipeout);
  }
  if (signal (SIGALRM,alarme)==SIG_ERR ||
      signal(SIGHUP,fin_urgente)==SIG_ERR ||
      signal(SIGINT,fin_urgente)==SIG_ERR ||
      signal(SIGQUIT,fin_urgente)==SIG_ERR ||
      signal(SIGABRT,fin_urgente)==SIG_ERR )
    fprintf(stderr,
	    "prehtml: attention: des interruptions n'ont pas pu être détournées\n");
}*/

void init_pipe(void){
  int fd[4];
  pipe(fd);
  pipe(fd+2);
  result_fork=fork();
  if (result_fork==-1) erreur("Le fork n'a pas marché");
  if (result_fork==0){ // process fils
    dup2(fd[0],0); // stdin
    dup2(fd[3],1); // stdout
    setlinebuf(stdin);
    setlinebuf(stdout);
    execlp("sh","sh",NULL);
    erreur("exec sh n'a pas marché");
  }
  else{
    /* ... car si on ne les ouvre pas dans le même ordre ici, ça bloque */
    pin=fdopen(fd[2],"rb");
    pout=fdopen(fd[1],"wb");
    setlinebuf(pin);
    setlinebuf(pout);
    // on vérifie que ça fonctionne
    fputs("echo\n",pout);
    fflush(pout);
    while(fgetc(pin)!='\n');
  }
  if (signal (SIGALRM,alarme)==SIG_ERR ||
      signal(SIGHUP,fin_urgente)==SIG_ERR ||
      signal(SIGINT,fin_urgente)==SIG_ERR ||
      signal(SIGQUIT,fin_urgente)==SIG_ERR ||
      signal(SIGABRT,fin_urgente)==SIG_ERR )
    fprintf(stderr,
	    "prehtml: attention: des interruptions n'ont pas pu être détournées\n");
}

void fin_pipe(void){
  if (!pout) erreur("fin_pipe(): pout déjà fermé");
  fclose(pout);
  if (!pin) erreur("fin_pipe(): pin déjà fermé");
  fclose(pin);
  pout=pin=NULL;
  kill(result_fork,SIGTERM);
  wait(NULL);
}
  

void envoieshell(char *s){  // envoie une commande à sh
  char s2[LONGUEUR_LIGNE];
  if (!pout) erreur("envoieshell: pipe out non ouvert!");
  memcpy(envoyee,s,LONGUEUR_LIGNE);
  fprintf(pout,"echo COMMANDE_TEST;%s\necho %s\n",s,CODE_FIN_SHELL);
  fflush(pout);
  alarmstop=1; /* si timeout ici, c'est la fin: le shell ne se lance pas */
  alarm(3); // alarme dans 3 secondes !
  fgets(s2,LONGUEUR_LIGNE,pin);
  alarm(30); // OK, j'ai reçu quelques chose
  alarmstop=0; /* si timeout ici, c'est peut-être que la commande shell est
		* simplement très longue: simple warning */
  if (strcmp(s2,"COMMANDE_TEST\n"))
    erreur("shell: Avant même d'envoyer \"%s\",\n"
	   "  je reçois \"%s\". Erreur interne ?",
	   s,s2);
}


char *recoitshell(char *s,int retireEOL){ // récupère la réponse de sh et met une ligne
                            // dans *s; renvoie NULL si c'est fini
  static int fin_ligne_otee=0;
  int fin_prec;
  int i;
  
  if (retireEOL) fin_ligne_otee=0;
  s[0]='\n';
  fgets(s+fin_ligne_otee,LONGUEUR_LIGNE-fin_ligne_otee,pin);
  fin_prec=fin_ligne_otee;
  i=strlen(s);
  if (i) if (s[i-1]=='\n') {s[i-1]='\0';fin_ligne_otee=1;}
  if (!strcmp(s+fin_prec,CODE_FIN_SHELL)) {
    fin_ligne_otee=0; // le dernier \n doit bien être ôté
    alarm(0); // ma commande shell est finie, donc le timeout aussi
    return NULL;
  }
  return s;
}

void execshell(char *s){ // envoie une commande à sh mais néglige la réponse
  char bidon[LONGUEUR_LIGNE];
  envoieshell(s);
  while (recoitshell(bidon,0));
}
