Forum Programmation.c Communication entre processus avec pipe() et dup2().

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
1
28
fév.
2020

Bonjour,

Je souhaite écrire un programme en C, qui permet d'enregistrer toutes les entrées/sorties d'un bash.
Mon idée est de créer un nouveau processus "enfant" qui va lancé le bash.
Le processus "parent" va s'occuper d'enregistrer les entrées/sorties du processus "enfant".
Pour l'instant j'essaye seulement de connecter STDIN du père a STDIN du fils et STDOUT du fils a STDOUT du père.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define BUFF_SIZE 1024
#define NB_PIPE 2

int main(int argc, char **argv){
  char *buff;
  int pid,i;
  int fd[NB_PIPE][2];
  /* Création des pipes */
  for(i=0;i<NB_PIPE;i++){
    if(pipe(fd[i]) == -1){
       perror("pipe failed\n");
       return 1;
    }
  }
  /* Création du processus fils */
  if((pid = fork()) == -1){
     perror("fork failed\n");
     return 2;
  }
  /* Processus fils */
  if(pid == 0){
    /* Connecte STDIN a la sortie du pipe 1 */
    close(fd[0][1]);
    dup2(fd[0][0],0);
    /* Connecte STDOUT a l'entrée du pipe 2 */
    close(fd[1][0]);
    dup2(fd[1][1],1);
    /* Lance le bash */
    execve("/bin/bash",NULL,NULL);
  }
  /* Processus père */
  else{
    close(fd[0][0]);
    close(fd[1][1]);
    while(1){
      // Allocation
      buff = malloc(BUFF_SIZE*sizeof(char));
      // Lecture sur STDIN (clavier utilisateur) dans buff
      read(0,buff,BUFF_SIZE);
      // Ecriture sur le pipe 1 de buff
      write(fd[0][1],buff,BUFF_SIZE);
      // Lecture sur le pipe 2 dans buff
      read(fd[1][0],buff,BUFF_SIZE);
      //Ecriture sur STDOUT de buff
      write(1,buff,BUFF_SIZE);
      // Libère la mémoire
      free(buff);
    }
  }
  return 0;
}

Voici un exemple d'exécution :

toto@ThinkPad:~/lab$ ./toto 
ls
main.c
toto
toto.c
ls
main.c
toto
toto.c
bash: line 3: oto: command not found
bash: line 4: toto.c: command not found

Le programme ne fonctionne pas bien :

1) Le prompt du bash ne s'affiche pas.
2) Après la deuxième commande une erreur se produit.

J'ai aussi essayé de changer le code du processus père par :

  /* Processus père */
  else{
    /* Connecte STDIN a l'entrée du pipe 1 */
    close(fd[0][0]);
    dup2(fd[0][1],0);
    /* Connecte STDOUT a la sortie du pipe 2 */
    close(fd[1][1]);
    dup2(fd[1][0],1);
    /* Attend la fin du processus fils. */
    wait(0);
    printf("Fin du processus fils.\n");
  }

Mais le programme ne fonctionne pas du tout avec cette version.

Est-ce la bonne manière de faire ?
Si oui quel est le problème dans mon code ?
Sinon comment auriez-vous fait ?

Merci.

  • # Impossible

    Posté par  . Évalué à 3. Dernière modification le 28/02/20 à 19:54.

    /* Connecte STDIN a l'entrée du pipe 1 */

    Tu connectes une entrée à une entrée …

    /* Connecte STDOUT a la sortie du pipe 2 */

    puis une sortie à une sortie.

    ça peut pas marcher.

    Une sortie envoie ses données vers une entrée, et une entrée ne lit que depuis une sortie

    Sinon:
    https://linux.die.net/man/2/read

    On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number is smaller than the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal. On error, -1 is returned, and errno is set appropriately. In this case it is left unspecified whether the file position (if any) changes.

    Tu ne testes pas la valeur de retour de ton write.

    Enfin, d'après le man de malloc:

    Description
    The malloc() function allocates size bytes and returns a pointer to the allocated memory. The memory is not initialized. If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().

    Ce doit être ça la cause de ton problème: pour ne pas l'avoir, tu devrais tester le nombre de caractères récupérés sur stdin ou depuis ton pipe, et n'écrire que le contenu de ton buffer. Tu éviterais le problème de l'erreur lors de la deuxième exécution (tu utilises une zone mémoire réallouée après avoir été libérée, et comme elle est pas initialisée, elle contient des résidus de tes précédentes écritures/lectures).

    • [^] # Re: Impossible

      Posté par  . Évalué à 2.

      Pour le problème de prompt, essaie d'utiliser bash -il

      Extrait de bash:

             -i        If the -i option is present, the shell is interactive.
             -l        Make bash act as if it had been invoked as a login shell (see
                       INVOCATION below).
      

      Une autre option qui pourrait t'intéresser:

      
             -s        If the -s option is present, or if no arguments remain after
                       option processing, then commands are read from the standard
                       input.  This option allows the positional parameters to be
                       set when invoking an interactive shell or when reading input
                       through a pipe.
      
    • [^] # Re: Impossible

      Posté par  . Évalué à 1.

      Merci pour ta réponse, j'ai corrigé.
      Voila ou j'en suis :

      #include <stdio.h>
      #include <unistd.h>
      #include <stdlib.h>
      #include <sys/wait.h>
      #include <sys/select.h>
      #include <sys/time.h>
      #include <sys/types.h>
      
      #define BUFF_SIZE 256
      #define NB_PIPE 2
      
      int main(int argc, char **argv){
        char *buff;                           /* Déclaration des variables */
        int pid,i,fd_stdin,fd_stdout,ret,nb_read;
        int fd[NB_PIPE][2];
        FILE *f = NULL;
        fd_set readfds;
        struct timeval timeout;
      
        for(i=0;i<NB_PIPE;i++){               /* Création des pipes */
          if(pipe(fd[i]) == -1){
             perror("pipe failed\n");
             return 1;
          }
        }
      
        fd_stdin = 0;                         /* Initialisation des variables */
        fd_stdout = 1;
        buff = malloc(BUFF_SIZE*sizeof(char));
        f = fopen("tmp_log","wba+");
      
        if((pid = fork()) == -1){             /* Création du processus fils */
           perror("fork failed\n");
           return 2;
        }
      
        if(pid == 0){                         /* Processus fils */
          close(fd[0][1]);                    /* Ferme l'entrée de pipe 1 */
          close(fd[1][0]);                    /* Ferme la sortie de pipe 2 */
          dup2(fd[0][0],0);                   /* Duplique la sortie de pipe 1 sur STDIN */
          dup2(fd[1][1],1);                   /* Duplique l'entrée de pipe 2 sur STDOUT */
          execve("/bin/bash",NULL,NULL);      /* Lance l'execution d'un bash */
        }
        else{                                  /* Processus père */
          close(fd[0][0]);                    /* Ferme la sortie de pipe 1 */
          close(fd[1][1]);                    /* Ferme l'entrée de pipe 2 */
      
          while(1){
      
              timeout.tv_sec = 5;                                 /* Initialisation timeout et des file descriptor a écouter */
              timeout.tv_usec = 0;
              FD_ZERO(&readfds);
              FD_SET(fd_stdin,&readfds);
              FD_SET(fd[1][0],&readfds);
      
              select(FD_SETSIZE,&readfds,NULL,NULL,&timeout);     /* On ecoute les descripteurs de fichier contenu dans readfds */
      
                                                                  /* Activité detecté */
              if(FD_ISSET(fd_stdin,&readfds)){                    /* Sur STDIN */
                  nb_read = read(fd_stdin,buff,BUFF_SIZE);        /* Lecture sur STDIN* */
                  write(fd[0][1],buff,nb_read);                   /* Ecriture sur l'entrée de pipe 1 */
                  fseek(f, 0, SEEK_END);                          /* Se déplace a la fin du fichier */
                  for(i=0;i<nb_read;i++){
                    fputc(buff[i],f);                             /* Ecrit un caractère a la fin du fichier */
                  }
              }
      
              if(FD_ISSET(fd[1][0],&readfds)){                    /* Sur la sortie du pipe 2 */
                  nb_read = read(fd[1][0],buff,BUFF_SIZE);        /* Lecture sur la sortie de pipe 2 */
                  write(fd_stdout,buff,nb_read);                  /* Ecriture sur STDOUT*/
                  fseek(f, 0, SEEK_END);                          /* Se déplace a la fin du fichier */
                  for(i=0;i<nb_read;i++){
                    fputc(buff[i],f);                             /* Ecrit un caractère a la fin du fichier */
                  }
              }
          }
        }
        return 0;
      }

      Exemple :

      toto@ThinkPad:~/lab$ ./main 
      ls
      Makefile
      main
      main.c
      main.o
      tmp_log
      toto
      toto.c
      toto.o
      env
      PWD=/home/toto/lab
      SHLVL=1
      _=/usr/bin/env
      ^C
      

      Le programme fonctionne quasiment.
      Le soucis maintenant c'est l'affichage, et toujours pas de prompt.
      J'ai essayé les options de bash sans succès.
      Je pense que l'erreur vient d'ailleurs car quand j'exécute ce programme :

      #include <stdio.h>
      #include <unistd.h>
      #include <stdlib.h>
      
      #define NB_PIPE 2
      
      int main(int argc, char **argv){
        int pid;
        /* Création du processus fils */
        if((pid = fork()) == -1){
           perror("fork failed\n");
           return 2;
        }
        /* Processus fils */
        if(pid == 0){
          execve("/bin/bash",NULL,NULL);
        }
        /* Processus père */
        else{
          wait(0);
        }
        return 0;
      }

      J'ai ça :

      toto@ThinkPad:~/lab$ ./toto 
      To run a command as administrator (user "root"), use "sudo <command>".
      See "man sudo_root" for details.
      
      toto@ThinkPad:/home/toto/lab$ ls
      Makefile  main  main.c  main.o  tmp_log  toto  toto.c  toto.o
      toto@ThinkPad:/home/toto/lab$ env
      LS_COLORS=
      M2_HOME=/opt/maven
      LESSCLOSE=/usr/bin/lesspipe %s %s
      PWD=/home/toto/lab
      SHLVL=1
      PATH=/opt/maven/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/var/lib/snapd/snap/bin
      LESSOPEN=| /usr/bin/lesspipe %s
      _=/usr/bin/env
      

      Aussi je ne comprend pas pourquoi le résultat de la commande env est différent.

      • [^] # Re: Impossible

        Posté par  . Évalué à 2. Dernière modification le 29/02/20 à 17:33.

        Ti as quoi quand tupasses les options i et l a ta commande bash ?

        Peux tu stp montrer le source et le résultat ?

        • [^] # Re: Impossible

          Posté par  . Évalué à 1.

          Source :

          argv[0] = "-li";
          argv[1] = NULL;
          execve("/bin/bash",argv,NULL);

          Résultat :

          toto@ThinkPad:~/lab$ ./main 
          ls
          Makefile
          main
          main.c
          main.o
          tmp_log
          toto
          toto.c
          toto.o
          ls
          Makefile
          main
          main.c
          main.o
          tmp_log
          toto
          toto.c
          toto.o
          
          
          • [^] # Re: Impossible

            Posté par  . Évalué à 2. Dernière modification le 29/02/20 à 18:34.

            Si tu regardes la manpage de execve, tu verras que argv[0] doit correspondre au nom du fichier lancé a l'exécution et les paramètres doivent commencer a argv[1].

            Regarde la page man il y a un exemple et essaie de t'en inspirer: il me semble que dans l'exemple, argv[0] est a null.

            Fais un retour si c ok, ce que tu fais m'intéresse.

            • [^] # Re: Impossible

              Posté par  . Évalué à 2. Dernière modification le 01/03/20 à 09:27.

              Pour info, chez moi ça marche (prompt affiché) sous freebsd:

              #include <stdio.h>
              #include <unistd.h>
              #include <stdlib.h>
              
              #include <sys/types.h>
              #include <sys/wait.h>
              
              #define NB_PIPE 2
              
              int main(int argc, char **argv){
                int pid;
                char *argv2[]={ NULL, NULL };
                /* Création du processus fils */
                if((pid = fork()) == -1){
                   perror("fork failed\n");
                   return 2;
                }
              
                /* Processus fils */
                if(pid == 0){
                  execve("/bin/bash",argv2 ,NULL);
                }
                /* Processus père */
                else{
                  wait(0);
                }
                return 0;
              }
              • [^] # Re: Impossible

                Posté par  . Évalué à 1. Dernière modification le 01/03/20 à 11:09.

                Alors quand je fait sans pipe() et dup2() ça marche avec le prompt :

                #include <stdio.h>
                #include <unistd.h>
                #include <stdlib.h>
                
                #define NB_PIPE 2
                
                int main(int argc, char **argv){
                  int pid;
                  /* Création du processus fils */
                  if((pid = fork()) == -1){
                     perror("fork failed\n");
                     return 2;
                  }
                  /* Processus fils */
                  if(pid == 0){
                    execve("/bin/bash",NULL,NULL);
                  }
                  /* Processus père */
                  else{
                    wait(0);
                  }
                  return 0;
                }

                Quand je fait :

                #include <stdio.h>
                #include <unistd.h>
                #include <stdlib.h>
                #include <sys/wait.h>
                #include <sys/select.h>
                #include <sys/time.h>
                #include <sys/types.h>
                #include <errno.h>
                
                #define BUFF_SIZE 4096                  /* the capacity of a pipe is 4096 bytes */
                #define NB_PIPE 2
                
                int main(int argc, char **argv){
                  char *buff;                           /* Déclaration des variables */
                  int pid,i,fd_stdin,fd_stdout,ret,nb_read;
                  int fd[NB_PIPE][2];
                  FILE *f = NULL;
                  fd_set readfds;
                  struct timeval timeout;
                
                  for(i=0;i<NB_PIPE;i++){               /* Création des pipes */
                    if(pipe(fd[i]) == -1){
                       perror("pipe failed\n");
                       return 1;
                    }
                  }
                
                  fd_stdin = 0;                         /* Initialisation des variables */
                  fd_stdout = 1;
                  f = fopen("tmp_log","wba+");
                
                  if((pid = fork()) == -1){             /* Création du processus fils */
                     perror("fork failed\n");
                     return 2;
                  }
                
                  if(pid == 0){                         /* Processus fils */
                    close(fd[0][1]);                    /* Ferme l'entrée de pipe 1 */
                    close(fd[1][0]);                    /* Ferme la sortie de pipe 2 */
                    dup2(fd[0][0],0);                   /* Duplique la sortie de pipe 1 sur STDIN */
                    dup2(fd[1][1],1);                   /* Duplique l'entrée de pipe 2 sur STDOUT */
                    argv[0] = "/bin/bash";
                    argv[1] = "-li";
                    argv[2] = NULL;
                
                    execve("/bin/bash",argv,NULL);      /* Lance l'execution d'un bash */
                  }
                  else{                                  /* Processus père */
                    close(fd[0][0]);                    /* Ferme la sortie de pipe 1 */
                    close(fd[1][1]);                    /* Ferme l'entrée de pipe 2 */
                
                    while(1){
                
                        timeout.tv_sec = 5;                                 /* Initialisation timeout et des file descriptor a écouter */
                        timeout.tv_usec = 0;
                        FD_ZERO(&readfds);
                        FD_SET(fd_stdin,&readfds);
                        FD_SET(fd[1][0],&readfds);
                
                        select(FD_SETSIZE,&readfds,NULL,NULL,&timeout);     /* On ecoute les descripteurs de fichier contenu dans readfds */
                
                                                                            /* Activité detectée */
                        if(FD_ISSET(fd_stdin,&readfds)){                    /* Sur STDIN */
                            printf("toto\n");
                            nb_read = read(fd_stdin,buff,BUFF_SIZE);        /* Lecture sur STDIN* */
                            printf("toto\n");
                            write(fd[0][1],buff,nb_read);                   /* Ecriture sur l'entrée de pipe 1 */
                            fseek(f, 0, SEEK_END);                          /* Se déplace a la fin du fichier */
                            for(i=0;i<nb_read;i++){
                              fputc(buff[i],f);                             /* Ecrit un caractère a la fin du fichier */
                            }
                        }
                
                        if(FD_ISSET(fd[1][0],&readfds)){                    /* Sur la sortie du pipe 2 */
                            nb_read = read(fd[1][0],buff,BUFF_SIZE);        /* Lecture sur la sortie de pipe 2 */
                            write(fd_stdout,buff,nb_read);                  /* Ecriture sur STDOUT*/
                            fseek(f, 0, SEEK_END);                          /* Se déplace a la fin du fichier */
                            for(i=0;i<nb_read;i++){
                              fputc(buff[i],f);                             /* Ecrit un caractère a la fin du fichier */
                            }
                        }
                    }
                  }
                  return 0;
                }

                L'exécution donne :

                toto@ThinkPad:~/lab$ ./main 
                toto@ThinkPad:/home/toto/lab$ ls
                toto
                
                [1]+  Arrêté                ./main
                toto@ThinkPad:~/lab$ ls
                main  main.c  main.o  Makefile  tmp_log  toto  toto.c  toto.o
                toto@ThinkPad:~/lab$ 
                

                Le prompt s'affiche mais quand j'exécute "ls" le bash se ferme
                et exécute "ls" dans le bash qui appelle main.

                L'erreur semble venir du read de STDIN dans le père.

                • [^] # Re: Impossible

                  Posté par  . Évalué à 2.

                  Je viens d'essayer. Le bash ne se ferme pas mais il se met en arrière plan. Quand tu fais fg et que tu passes une commanbde ça marche. Je pense que c'est parce que bash n'est pas invoqué depuis un terminal.

                • [^] # Re: Impossible

                  Posté par  . Évalué à 2.

                  L'erreur semble venir du read de STDIN dans le père.

                  Possible aussi …

                  J'ai plus trop le temps de regarder maintenant. Fais un retour si tu arrives à quelque chose.

                • [^] # Re: Impossible

                  Posté par  . Évalué à 3.

                  Plop!

                  Une piste ?

                  • [^] # Re: Impossible

                    Posté par  . Évalué à 1. Dernière modification le 01/03/20 à 17:23.

                    Je suis entrain de regarder pselect et le paramètre sigmask.
                    Je vais regarder ton lien.

                • [^] # Re: Impossible

                  Posté par  (site Web personnel) . Évalué à 2.

                  Euh … vous utilisez le même argv que celui passé à main là.

                  char* const basharg[]={"-i",NULL};
                  execve("/usr/local/bin/bash",basharg,NULL);
                  perror("Execve failed\n");
                  exit(EXIT_FAILURE);
                  
                  // pas besoin de else
                • [^] # Re: Impossible

                  Posté par  (site Web personnel) . Évalué à 3. Dernière modification le 01/03/20 à 18:11.

                  Au passage, utilisez les constantes STDIN_FILENO et STDOUT_FILENO à la place de 0 et 1.

                  vous devriez de plus vérifier que le fils ne s'est pas terminé avec waitpid, après chaque timeout sur select par exemple.

                  J'ai aussi l'impression que vous allez lancer deux bash avec votre execve.

                  Et votre buffer n'est pas initialisé:

                  char buff[MAX_BUF_SIZE];   
                  memset(buff,0,sizeof buff);
                  
                  • [^] # Re: Impossible

                    Posté par  . Évalué à 1.

                    Pour execve si j'utilise :

                    basharg[0] = "-i";
                    basharg[1] = NULL;
                    

                    Le prompt ne s'affiche pas.

                    Je pense avoir corrigé le reste .

                    #include <stdio.h>
                    #include <unistd.h>
                    #include <stdlib.h>
                    #include <sys/wait.h>
                    #include <sys/select.h>
                    #include <sys/time.h>
                    #include <sys/types.h>
                    #include <errno.h>
                    #include <string.h>
                    
                    
                    #define BUFF_SIZE 4096              /* the capacity of a pipe is 4096 bytes */
                    #define NB_PIPE 2
                    
                    int main(int argc, char **argv){
                      char buff[BUFF_SIZE];             /* Déclaration des variables */
                      char* basharg[3];
                      int fd[NB_PIPE][2];
                      FILE *f = NULL;
                      fd_set readfds;
                      struct timeval timeout;
                      int pid,i,nb_read;
                    
                      memset(buff,0,sizeof buff);
                      f = fopen("tmp_log","wba+");
                    
                      for(i=0;i<NB_PIPE;i++){               /* Création des pipes */
                        if(pipe(fd[i]) == -1){
                           perror("pipe failed\n");
                           return 1;
                        }
                      }
                    
                      if((pid = fork()) == -1){             /* Création du processus fils */
                         perror("fork failed\n");
                         return 2;
                      }
                    
                      if(pid == 0){                         /* Processus fils */
                        close(fd[0][1]);                    /* Ferme l'entrée de pipe 1 */
                        close(fd[1][0]);                    /* Ferme la sortie de pipe 2 */
                        dup2(fd[0][0],0);                   /* Duplique la sortie de pipe 1 sur STDIN */
                        dup2(fd[1][1],1);                   /* Duplique l'entrée de pipe 2 sur STDOUT */
                    
                        basharg[0] = "/bin/bash";
                        basharg[1] = "-li";
                        basharg[2] = NULL;
                    
                        execve("/bin/bash",basharg,NULL);
                        perror("Execve failed\n");
                        exit(EXIT_FAILURE);
                      }
                      else{                                  /* Processus père */
                        close(fd[0][0]);                    /* Ferme la sortie de pipe 1 */
                        close(fd[1][1]);                    /* Ferme l'entrée de pipe 2 */
                    
                    
                        while(waitpid (pid, NULL, WNOHANG) != pid){
                    
                            timeout.tv_sec = 5;                                 /* Initialisation timeout et des file descriptor a écouter */
                            timeout.tv_usec = 0;
                            FD_ZERO(&readfds);
                            FD_SET(STDIN_FILENO,&readfds);
                            FD_SET(fd[1][0],&readfds);
                    
                            select(FD_SETSIZE,&readfds,NULL,NULL,&timeout);     /* On ecoute les descripteurs de fichier contenu dans readfds */
                    
                                                                                /* Activité detectée */
                            if(FD_ISSET(STDIN_FILENO,&readfds)){                /* Sur STDIN */
                                nb_read = read(STDIN_FILENO,buff,BUFF_SIZE);    /* Lecture sur STDIN* */
                                write(fd[0][1],buff,nb_read);                   /* Ecriture sur l'entrée de pipe 1 */
                                fseek(f, 0, SEEK_END);                          /* Se déplace a la fin du fichier */
                                for(i=0;i<nb_read;i++){
                                  fputc(buff[i],f);                             /* Ecrit un caractère a la fin du fichier */
                                }
                            }
                    
                            if(FD_ISSET(fd[1][0],&readfds)){                    /* Sur la sortie du pipe 2 */
                                nb_read = read(fd[1][0],buff,BUFF_SIZE);        /* Lecture sur la sortie de pipe 2 */
                                write(STDOUT_FILENO,buff,nb_read);                  /* Ecriture sur STDOUT*/
                                fseek(f, 0, SEEK_END);                          /* Se déplace a la fin du fichier */
                                for(i=0;i<nb_read;i++){
                                  fputc(buff[i],f);                             /* Ecrit un caractère a la fin du fichier */
                                }
                            }
                        }
                      }
                      return 0;
                    }
                    • [^] # Re: Impossible

                      Posté par  (site Web personnel) . Évalué à 2.

                      Je pense avoir corrigé le reste .

                      Vous n'avez pas correctement alloué basharg.

                      Le prompt ne s'affiche pas.

                      Je ne sais pas pourquoi, mais là, j'ai la flemme de faire le tri dans tous ces pipes .

                      Utilisez un pty comme suggéré. C'est la manière canonique de faire ce genre de chose.

                      • [^] # Re: Impossible

                        Posté par  . Évalué à 1.

                        Bonjour, ça fonctionne avec un PTY.
                        Merci pour votre aide.

                        #define _XOPEN_SOURCE 600
                        #include <stdlib.h>
                        #include <fcntl.h>
                        #include <errno.h>
                        #include <unistd.h>
                        #include <stdio.h>
                        #define __USE_BSD
                        #include <termios.h>
                        #include <sys/select.h>
                        #include <sys/ioctl.h>
                        #include <string.h>
                        
                        #define BUFF_SIZE 4096
                        #define OPERATION_FAIL -1
                        
                        
                        int main(int ac, char *av[]){
                        
                            int fd_master, fd_slave, pid, rc, i, nb_read;
                            FILE *f = NULL;
                            char *buffer;
                            char* const basharg[]={"/bin/bash",NULL};
                            fd_set readfd_slave;
                            struct timeval timeout;
                        
                            buffer = malloc(BUFF_SIZE*sizeof(char));
                            f = fopen("tmp_log","wba+");
                            memset(buffer,0,BUFF_SIZE);
                        
                            /* Open PTY in read and write mode */
                            fd_master = posix_openpt(O_RDWR);
                            if (fd_master == OPERATION_FAIL){
                              fprintf(stderr, "Error %d on posix_openpt()\n", errno);
                              exit(EXIT_FAILURE);
                            }
                        
                            /* Change acces rigth of slave side and set group */
                            rc = grantpt(fd_master);
                            if (rc == OPERATION_FAIL){
                              fprintf(stderr, "Error %d on grantpt()\n", errno);
                              exit(EXIT_FAILURE);
                            }
                        
                            /* Unlock slave side */
                            rc = unlockpt(fd_master);
                            if (rc == OPERATION_FAIL){
                              fprintf(stderr, "Error %d on unlockpt()\n", errno);
                              exit(EXIT_FAILURE);
                            }
                        
                            /* Get file descriptor of slave */
                            fd_slave = open(ptsname(fd_master), O_RDWR);
                            if (rc == OPERATION_FAIL){
                              fprintf(stderr, "Error %d on open()\n", errno);
                              exit(EXIT_FAILURE);
                            }
                        
                            /* Create son process */
                            if((pid = fork()) == OPERATION_FAIL){
                              fprintf(stderr, "Error %d on fork()\n", errno);
                              exit(EXIT_FAILURE);
                            }
                        
                            /* Soon process */
                            if(pid==0){
                        
                              struct termios slave_orig_term_settings; // Saved terminal settings
                              struct termios new_term_settings; // Current terminal settings
                              // Close the master side of the PTY
                              close(fd_master);
                              // Save the default parameters of the slave side of the PTY
                              rc = tcgetattr(fd_slave, &slave_orig_term_settings);
                              // Set raw mode on the slave side of the PTY
                              new_term_settings = slave_orig_term_settings;
                              cfmakeraw (&new_term_settings);
                              tcsetattr (fd_slave, TCSANOW, &new_term_settings);
                              // The slave side of the PTY becomes the standard input and outputs of the child process
                              close(0); // Close standard input (current terminal)
                              close(1); // Close standard output (current terminal)
                              close(2); // Close standard error (current terminal)
                        
                              dup(fd_slave); // PTY becomes standard input (0)
                              dup(fd_slave); // PTY becomes standard output (1)
                              dup(fd_slave); // PTY becomes standard error (2)
                        
                              // Now the original file descriptor is useless
                              close(fd_slave);
                        
                              // Make the current process a new session leader
                              setsid();
                        
                              // As the child is a session leader, set the controlling terminal to be the slave side of the PTY
                              // (Mandatory for programs like the shell to make them manage correctly their outputs)
                              ioctl(0, TIOCSCTTY, 1);
                        
                              // Execution of the program
                              {
                                      execve("/bin/bash",basharg ,NULL);
                              }
                        
                        
                            }
                            /* Father process */
                            else{
                        
                              /* Close the slave side of the PTY */
                              close(fd_slave);
                        
                              /* While soon process is not terminated */
                              while(waitpid (pid, NULL, WNOHANG) != pid){
                        
                                  /* Set timeout for select() */
                                  timeout.tv_sec = 5;
                                  timeout.tv_usec = 0;
                        
                                  /* Set on wich file descriptor listen*/
                                  FD_ZERO(&readfd_slave);
                                  FD_SET(STDIN_FILENO,&readfd_slave);
                                  FD_SET(fd_master,&readfd_slave);
                        
                                  /* Listen file descriptor */
                                  rc = select(FD_SETSIZE,&readfd_slave, NULL, NULL,&timeout);
                        
                                  if (rc == OPERATION_FAIL){
                                    fprintf(stderr, "Error %d on select()\n", errno);
                                    exit(EXIT_FAILURE);
                                  }
                        
                                  /* Data on STDIN */
                                  if(FD_ISSET(STDIN_FILENO,&readfd_slave)){
                                    nb_read = read(STDIN_FILENO,buffer,BUFF_SIZE);
                                    if(nb_read == OPERATION_FAIL){
                                      fprintf(stderr, "Error %d on read standard input\n", errno);
                                      exit(EXIT_FAILURE);
                                    }
                                    else{
                                      write(fd_master,buffer,nb_read);
                                      for(i=0;i<nb_read;i++){
                                        fputc(buffer[i],f);
                                      }
                                    }
                                  }
                        
                                  /* Data on fd_slave */
                                  if(FD_ISSET(fd_master,&readfd_slave)){
                                    nb_read = read(fd_master,buffer,BUFF_SIZE);
                                    if(nb_read == OPERATION_FAIL){
                                      fprintf(stderr, "Error %d on read master PTY\n", errno);
                                      exit(EXIT_FAILURE);
                                    }
                                    else{
                                      write(STDOUT_FILENO ,buffer,nb_read);
                                      for(i=0;i<nb_read;i++){
                                        fputc(buffer[i],f);
                                      }
                                    }
                                  }
                        
                              }
                              close(f);
                              free(buffer);
                            }
                        
                          exit(EXIT_SUCCESS);
                        }
                        • [^] # Re: Impossible

                          Posté par  . Évalué à 2.

                          Bonjour, ça fonctionne avec un PTY.
                          Merci pour votre aide.

                          Cool, merci pour ton retour ;)

                        • [^] # Re: Impossible

                          Posté par  (site Web personnel) . Évalué à 4.

                          Bonjour, ça fonctionne avec un PTY.

                          C'est étudié pour ;)

                          Plusieurs remarques,

                          • Si vous utilisez fopen, répondez par fclose, il est peu probable que vos close(f) soient valides. Un stream et un descripteur de fichier sont deux choses différentes.

                          • Puisque vous allouez dynamiquement le buffer, faites le uniquement dans le processus père. Sinon, le processus fils va hériter d'une copie.

                          • De même pour les bashargs, dans le fils.

                          • Pour moi, vous lancez toujours un bash … qui lance un autre bash (vérifiez avec ps).

                          • Sortez du while tout ce qui ne doit être fait qu'une fois (le timeout).

                          • Pas besoin du else, soit execve remplace complètement le processus, soit il échoue:

                           execve(..);
                           perror("marche po\n");
                           exit(EXIT_FAILURE);
                          • si vous voulez voir quelque chose dans votre fichier,
                            faites des fflush.

                          • Maintenant que vous avez tout écrit à la main, vous pouvez lire le manuel de forkpty
                            voire les sources.

                          • et utiliser un poll ou un epoll.


                          C'est con tous ces #define _XOPEN_SOURCE et autres, hein.
                          Pour éviter ce bordel, passez sous FreeBSD.

                          • [^] # Re: Impossible

                            Posté par  . Évalué à 2.

                            Pour moi, vous lancez toujours un bash … qui lance un autre bash (vérifiez avec ps).

                            Hello. Ce détail m'a échappé, et je ne l'ai pas constaté dans mes essais précédents. Qu'est-ce qui vous fait dire ça ?

                            • [^] # Re: Impossible

                              Posté par  (site Web personnel) . Évalué à 2.

                              Qu'est-ce qui vous fait dire ça ?

                              En fait, vous avez raison, j'ai confondu avec la famille des exec().

                              Le premier argument de execve est par convention le nom du programme.

                              J'ai exhumé ceci:

                                /* Child side.  */
                                # SHELL_NAME vaut "sh"
                                # SHELL_PATH vaut "/bin/sh"
                                const char *new_argv[1];
                                new_argv[0] = SHELL_NAME;
                                new_argv[1] = NULL;
                                [...] 
                                /* Exec the shell.  */
                                execve (SHELL_PATH, (char *const*) new_argv, __environ);
                                /* NOT FOUND */
                                exit (127);

                              Je me demande si le fonctionnement n'est pas différent sous FreeBSD.

                              • [^] # Re: Impossible

                                Posté par  (site Web personnel) . Évalué à 3.

                                Le premier argument de execve est par convention le nom du programme.

                                Pour compléter, ceci sert lorsque un programme est un lien vers un autre, plus générique.
                                Et que le comportement de ce dernier dépend du nom sous lequel on l'a appelé, comme les utilitaires busybox par exemple.

                              • [^] # Re: Impossible

                                Posté par  . Évalué à 2.

                                J'ai essayé sous FreeBSD, de mémoire, le comportement est identique.

                                D'après la page man:

                                     The argument argv is a pointer to a null-terminated array of character
                                     pointers to null-terminated character strings.  These strings construct
                                     the argument list to be made available to the new process.  At least one
                                     argument must be present in the array; by custom, the first element
                                     should be the name of the executed program (for example, the last compo-
                                     nent of path).
                                
                                

                                Cordialement.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n’en sommes pas responsables.