Forum Programmation.c++ popen / pclose

Posté par  .
Étiquettes : aucune
0
1
juin
2007
Bonjour,

Je cherche à faire la chose suivante en C++/C :
- executer une commande depuis mon code
- recuperer le code de retour
- recuperer stderr d'un coté
- recuperer stdout d'un autre coté

Pour le moment j'ai trouvé une solution avec popen+close qui me permet de recuperer le code de retour, en revanche, pour stderr et stdout, je ne vois pas comment les recuperer separement.

PS : on oubli le cat dans 2 fichiers, question d'optimisation
PS : via l'API java, on peut récuperer code de retour, stdout et stderr separement, donc c'est forcement possible en C, mais comment ... ?

Voila "en gros" mon code pour le moment :


FILE* pipe = popen(command.c_str(), "r");
if ( 0 == pipe ) {
// popen failed, log the error
return -1;
}

// Read pipe until it is closed by the child process
// store data in the output variable
char tmp[1024];
string output = "";

while ( 0 != fgets(tmp, 1024, pipe) )
{
output.append(tmp);
}

// wait for the end of the child process
int status = pclose(pipe);

// check status
std::cout << "status: " << status << std::endl;
if ( (-1 == status) and (ECHILD == errno) )
{
std::cout << "pclose failled " << std::endl;
return -1;
} else if ( false == WIFEXITED(status)) {
std::cout << "command exit with error " << std::endl;
return -1;
} else {
std::cout << "command exit ok" << std::endl;
return -1;
}


Je ne suis pas du tout sur pour le "WIFEXITED(status) == false".
Peut etre qu'il faut seulement mettre "status == -1" ?

Merci
  • # .

    Posté par  . Évalué à 5.

    Pour le moment j'ai trouvé une solution avec popen+close qui me permet de recuperer le code de retour, en revanche, pour stderr et stdout, je ne vois pas comment les recuperer separement.

    Je viens d'écrire rapidement (comprendre pas très propre) un code sur lequel tu peux te baser, si tu as des questions n'hésite pas !


    #include <iostream>
    #include <string>

    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>

    #define STDOUT 1
    #define STDERR 2

    using namespace std;

    void WPipe(int Fd[2])
    {
        if (pipe(Fd) < 0)
        {
            perror("Erreur pipe");
            exit(EXIT_FAILURE);
        }
        return;
    }

    void WDup(int oldfd, int newfd)
    {
        if (dup2(oldfd, newfd) < 0)
        {
            perror("Erreur dup2");
            exit(EXIT_FAILURE);
        }
    }

    int main()
    {
        int status;
        int FdCOut[2];
        int FdCErr[2];
        pid_t Pid;

        WPipe(FdCErr);
        WPipe(FdCOut);
        Pid = fork();

        if (Pid == -1)
        {
            perror("Erreur fork");
            exit(EXIT_FAILURE);
        }
        else if (Pid == 0)  // Child
        {
            close(FdCOut[0]);
            close(FdCErr[0]);
            WDup(FdCOut[1], STDOUT);
            WDup(FdCErr[1], STDERR);

            execl("./test.run", "a", (char *) NULL);

            perror("Erreur execl");
            exit(EXIT_FAILURE);
        }

        close(FdCOut[1]);
        close(FdCErr[1]);

        // Ici mettre la lecture depuis FdCOut[0] et FdCErr[0]
        char tmp[1024];
        string output = "";
        while (read(FdCOut[0], tmp, 1024) > 0)
        {
            output.append(tmp);
        }
        std::cout << output << endl;

        waitpid(Pid, &status, 0);

        if (!WIFEXITED(status))
        {
            std::cerr << "Erreur fils\n";
            exit(EXIT_FAILURE);
        }

        cout << "OK, code sortie: "
              << WEXITSTATUS(status)
              << "\n";

        return 0;
    }
    • [^] # Re: .

      Posté par  . Évalué à 1.

      Hum, c'est parfait, merci beaucoup !
      J'espérais pouvoir éviter les fork/execlp/dup2/waitpid.
      Mais manifestement je ne peux pas faire ce que je veux juste avec system ou popen.
      Donc je vais faire comme tu dis, encore merci.
      • [^] # Re: .

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

        En fait il y a deux solution à ce problème:

        #include <sys/types.h>
        #include <sys/wait.h>
        #include <unistd.h>
        #include <stdlib.h>

        main () {

        int status, old_stdout, old_stderr, c;
        int pipe_stdout[2], pipe_stderr[2];
        char buffer[1024];

        /* creation pipes de communication */
        pipe(pipe_stdout);
        pipe(pipe_stderr);

        #if 1
        if (fork()) {
        close (pipe_stdout[0]);
        close (pipe_stderr[0]);
        /* redirect to pipe */
        dup2(pipe_stdout[1], STDOUT_FILENO);
        dup2(pipe_stderr[1], STDERR_FILENO);
        execl("/bin/ls", "ls", "-la", "/bin", (char*)NULL);
        }

        wait(NULL);
        #else
        /* Backup old i/o */
        dup2(STDOUT_FILENO, old_stdout);
        dup2(STDERR_FILENO, old_stderr);

        dup2(pipe_stdout[1], STDOUT_FILENO);
        dup2(pipe_stderr[1], STDERR_FILENO);
        system("/bin/ls -la --color /bin");

        /* restore old i/o */
        dup2 (old_stdout, STDOUT_FILENO);
        dup2 (old_stderr, STDERR_FILENO);
        #endif

        /* close (pipe_stdout[1]);
        close (pipe_stderr[1]);*/

        memset(buffer, 0, 1024);
        /* Lecture pipes */
        printf("Lecture stdout:\n");
        while (c=read(pipe_stdout[0], buffer, 1024)>0) {
        printf("%s", buffer);
        memset(buffer, 0, 1024);
        }

        memset(buffer, 0, 1024);
        printf("Lecture stderr:\n");
        while (c=read(pipe_stderr[0], buffer, 1024)>0) {
        printf("%s", buffer);
        memset(buffer, 0, 1024);
        }
        }
        • [^] # Re: .

          Posté par  . Évalué à 1.

          ok, je n'ai pas tout compris dans la 2eme version.
          Mais ce n'est pas grave, je vais prendre la premiere, son fonctionnement est clair.

          Une autre petite question :
          Si la commande que je lance dans mon execl() pose une question (password par exemple) et qu'on ne s'y attendait pas, le processus ne finira jamais et on reste bloqué sur le waitpid dans le pere ... N'y a t'il pas une solution pour que le processus fils ne se bloque pas en attente d'une interaction sur stdin ? Par exemple, ne peut on pas "fermer" stdin de ce process ?

          merci pour votre aide
          • [^] # Re: .

            Posté par  . Évalué à 1.

            Par exemple, ne peut on pas "fermer" stdin de ce process ?


            Bien sûr ! Il suffit de mettre dans le fils, avant le execl() :

            close(STDIN_FILENO);
            • [^] # Re: .

              Posté par  . Évalué à 1.

              Je viens de tester, mais ca ne change rien... il reste tjrs en attente d'interaction avec STDIN , par exemple pour lire un mot de passe...


              if (close(STDIN_FILENO) != 0) {exit(-1);}


              Je l'ai bien mis juste avant mon execl dans le code du child...

              Une idée ?
              • [^] # Re: .

                Posté par  . Évalué à 1.

                Une application en particulier ? Je viens de tester avec passwd, ça marche parfaitement chez moi !


                else if (Pid == 0) // Child
                {
                close(FdCOut[0]);
                close(FdCErr[0]);
                WDup(FdCOut[1], STDOUT);
                WDup(FdCErr[1], STDERR);
                if (close(STDIN_FILENO) != 0)
                exit(EXIT_FAILURE);

                execl("/usr/bin/passwd", "passwd", (char *) NULL);

                perror("Erreur execl");
                exit(EXIT_FAILURE);
                }



                $ ./a.out
                OUT
                Changing password for vince


                ERR
                (current) UNIX password: passwd : Conversation error
                passwd: password unchanged


                OK, code sortie: 10
                $
                • [^] # Re: .

                  Posté par  . Évalué à 1.

                  Interessant, il n'y a pas les meme comportements suivant la commande que l'on fait :

                  close(stdin) marche bien effectivement pour la commande "/usr/bin/passwrd"

                  Par contre, si on fait "ssh localhost ls" (et que cette commande demande un mot de passe), ben ca marche pas, le process child demande le mot de passe dans la console qui a lancé le pere ...

                  Original .... Une idée du pourquoi il y a cette difference et comment faire pour le cas où ca marche pas ?
                  • [^] # Re: .

                    Posté par  . Évalué à 1.

                    C'est parce que ssh lit directement sur tty.


                    open("/dev/tty", O_RDWR) = 6
                    write(6, "vince@darkangel\'s password: ", 32vince@darkangel's password: ) = 32
                    read(6, <unfinished ...>
                    • [^] # Re: .

                      Posté par  . Évalué à 1.

                      c'est null
                      donc je peux pas faire grand chose ?
                      • [^] # Re: .

                        Posté par  . Évalué à 1.

                        donc je peux pas faire grand chose ?

                        Si, lire le man de SSH.

                        -> utilise une authentification SSH par clé, soit sans passphrase (c'est pas bien), soit avec une passphrase (et dans ce cas là tu peux utiliser ssh-agent pour la taper une seule fois, et en dehors du programme)

                        -> regarde du côté des paramètres -f et -n de SSH :

                        -f
                        Requests ssh to go to background just before command execution. This is useful if ssh is going to ask for passwords or passphrases, but the user wants it in the background. This implies -n. The recommended way to start X11 programs at a remote site is with something like ssh -f host xterm.
                        -n
                        Redirects stdin from /dev/null (actually, prevents reading from stdin). This must be used when ssh is run in the background. A common trick is to use this to run X11 programs on a remote machine. For example, ssh -n shadows.cs.hut.fi emacs & will start an emacs on shadows.cs.hut.fi, and the X11 connection will be automatically forwarded over an encrypted channel. The ssh program will be put in the background. (This does not work if ssh needs to ask for a password or passphrase; see also the -f option.)
                        • [^] # Re: .

                          Posté par  . Évalué à 1.

                          oki, vraiment merci beaucoup !
                        • [^] # Re: .

                          Posté par  . Évalué à 1.

                          heu en fait, ca ne m'avance pas trop :

                          Le process demande tjrs le mot de passe au lieu de retourner "autantification failed".

                          De plus je ne veux pas lancer la commande en arriere plan (l'appel doit etre bloquant).
                          • [^] # Re: .

                            Posté par  . Évalué à 1.

                            utilise une authentification SSH par clé, soit sans passphrase (c'est pas bien), soit avec une passphrase (et dans ce cas là tu peux utiliser ssh-agent pour la taper une seule fois, et en dehors du programme)

                            Et là, pas de problème de mot de passe.
                            • [^] # Re: .

                              Posté par  . Évalué à 1.

                              Oui tout a fait, c'est ce que j'ai prévue.
                              Mais si la machine est mal configurée, par exemple le .ssh est supprimé...
                              La la commande SSH va à nouveau demander le mot de passe.
                              Et la il faudrait que ma fonction sorte en cas d'erreur plutot que de rester freeze sur un read() ... et ca je vois pas comment le faire.

Suivre le flux des commentaires

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