2012-12-15 9 views
8

Sono in difficoltà con la creazione del processo e piping l'output del processo secondario in una stringa del processo padre. L'ho fatto funzionare su Windows (usando CreatePipe e CreateProcess e ReadFile), ma non riesco a ottenere l'esatto analogico su Unix per funzionare. Questo è il mio codice:posix_spawnp e piping output figlio a una stringa

#include <spawn.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <sys/wait.h> 
#include <iostream> 
#include <string> 
#include <vector> 
using namespace std; 

int main() 
{ 
    int exit_code; 
    int cout_pipe[2]; 
    int cerr_pipe[2]; 
    posix_spawn_file_actions_t action; 

    if(pipe(cout_pipe) || pipe(cerr_pipe)) 
    cout << "pipe returned an error.\n"; 

    posix_spawn_file_actions_init(&action); 
    posix_spawn_file_actions_addclose(&action, cout_pipe[0]); 
    posix_spawn_file_actions_addclose(&action, cerr_pipe[0]); 
    posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1); 
    posix_spawn_file_actions_adddup2(&action, cerr_pipe[1], 2); 

    posix_spawn_file_actions_addclose(&action, cout_pipe[1]); 
    posix_spawn_file_actions_addclose(&action, cerr_pipe[1]); 

    vector<string> argmem = {"bla"}; 
    vector<char*> args = {&argmem[0][0], nullptr}; // I don't want to call new. 

    pid_t pid; 
    if(posix_spawnp(&pid, "echo", &action, NULL, &args[0], NULL) != 0) 
    cout << "posix_spawnp failed with error: " << strerror(errno) << "\n"; 
    //close(cout_pipe[0]); 
    //close(cerr_pipe[0]); 

    close(cout_pipe[1]); 
    close(cerr_pipe[1]); 

    waitpid(pid,&exit_code,0); 
    cout << "exit code: " << exit_code << "\n"; 

    // Read from pipes 
    const size_t buffer_size = 1024; 
    string buffer; 
    buffer.resize(buffer_size); 
    ssize_t bytes_read = read(cout_pipe[0], &buffer[0], buffer_size); 
    while ((bytes_read = read(cout_pipe[0], &buffer[0], buffer_size)) > 0) 
    { 
    cout << "read " << bytes_read << " bytes from stdout.\n"; 
    cout << buffer.substr(0, static_cast<size_t>(bytes_read)+1) << "\n"; 
    bytes_read = read(cout_pipe[0], &buffer[0], buffer_size); 
    } 
    if(bytes_read == -1) 
    cout << "Failure reading from stdout pipe.\n"; 
    while ((bytes_read = read(cerr_pipe[0], &buffer[0], buffer_size)) > 0) 
    { 
    cout << "read " << bytes_read << " bytes from stderr.\n"; 
    cout << buffer.substr(0, static_cast<size_t>(bytes_read)+1) << "\n"; 
    bytes_read = read(cout_pipe[0], &buffer[0], buffer_size); 
    } 
    if(bytes_read == -1) 
    cout << "Failure reading from stderr pipe.\n"; 

    posix_spawn_file_actions_destroy(&action); 
} 

L'output è:

codice di uscita: 0

quindi suppongo che tutto funziona, tranne le tubazioni vero e proprio. Cosa c'è di sbagliato qui? Mi chiedo anche se ci sia un modo per leggere i byte in pipe in un ciclo waitpid, ma quando provo a farlo, il processo genitore si blocca all'infinito.

+0

Non avevo mai sentito parlare di 'posix_spawnp'. Cosa c'è che non va nel buon vecchio 'popen'? Che interessante funzione di syscall/libreria è. Ricorderà questo :) – sehe

+1

'popen' non ha la flessibilità richiesta: AFAICT non riesco a reindirizzare stderr con quello. È anche la funzione più simile all'API di CreateProcess. Heck, MSDN ha anche '_spawnvp' che assomiglia a' posix_spawnp', ma come ho detto, il mio codice 'CreateProcess' sta praticamente funzionando bene. È il lato Unix che al momento non collabora :( – rubenvb

+2

Ho aggiunto il tag C per suscitare interesse Sebbene il codice sia tecnicamente C++, i bit importanti (ovvero il piping e lo spawning) sono puri C. – rubenvb

risposta

13

posix_spawn è interessante e utile, il che rende questa domanda degna di essere negata - anche se non è più rilevante per l'OP.

Ci sono alcuni bug significativi nel codice come postati. Ho il sospetto che alcuni di questi erano il risultato di incisione nella disperazione, ma non so che era il bug originale:

  1. Il args matrice non include il argv[0] che rappresenterebbe il nome del file eseguibile. Ciò si traduce nel programma echo che non vede mai l'intestazione argv[1] ("bla").
  2. La funzione read() viene chiamata da posizioni diverse in un modo che non ha senso. Un modo corretto per farlo sarebbe chiamare solo read come parte dell'espressione di controllo per i loop while.
  3. waitpid() viene chiamato prima della lettura dai tubi. Ciò impedisce il completamento dell'I/O (almeno in casi non banali).
  4. Un problema più sottile con questo codice è che tenta di leggere tutto il stdout del bambino prima di leggere qualsiasi cosa da stderr. In linea di principio, questo potrebbe causare il blocco del bambino durante il tentativo di scrivere su stderr, impedendo così il completamento del programma. Creare una soluzione efficiente a questo è più complicato in quanto richiede la lettura da qualsiasi pipe abbia i dati disponibili. Ho usato poll() per questo. Un altro approccio sarebbe utilizzare più thread.

Inoltre, ho usato sh (shell di comando, cioè bash) come processo figlio. Ciò offre una grande flessibilità aggiuntiva, come l'esecuzione di una pipeline invece di un singolo eseguibile. In particolare, tuttavia, l'utilizzo di sh offre la semplice comodità di non dover gestire l'analisi della riga di comando.

/*BINFMTCXX: -std=c++11 -Wall -Werror 
*/ 

#include <spawn.h> // see manpages-posix-dev 
#include <poll.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <sys/wait.h> 
#include <iostream> 
#include <string> 
#include <vector> 
using namespace std; 

int main() 
{ 
    int exit_code; 
    int cout_pipe[2]; 
    int cerr_pipe[2]; 
    posix_spawn_file_actions_t action; 

    if(pipe(cout_pipe) || pipe(cerr_pipe)) 
    cout << "pipe returned an error.\n"; 

    posix_spawn_file_actions_init(&action); 
    posix_spawn_file_actions_addclose(&action, cout_pipe[0]); 
    posix_spawn_file_actions_addclose(&action, cerr_pipe[0]); 
    posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1); 
    posix_spawn_file_actions_adddup2(&action, cerr_pipe[1], 2); 

    posix_spawn_file_actions_addclose(&action, cout_pipe[1]); 
    posix_spawn_file_actions_addclose(&action, cerr_pipe[1]); 

//string command = "echo bla"; // example #1 
    string command = "pgmcrater -width 64 -height 9 |pgmtopbm |pnmtoplainpnm"; 
    string argsmem[] = {"sh","-c"}; // allows non-const access to literals 
    char * args[] = {&argsmem[0][0],&argsmem[1][0],&command[0],nullptr}; 

    pid_t pid; 
    if(posix_spawnp(&pid, args[0], &action, NULL, &args[0], NULL) != 0) 
    cout << "posix_spawnp failed with error: " << strerror(errno) << "\n"; 

    close(cout_pipe[1]), close(cerr_pipe[1]); // close child-side of pipes 

    // Read from pipes 
    string buffer(1024,' '); 
    std::vector<pollfd> plist = { {cout_pipe[0],POLLIN}, {cerr_pipe[0],POLLIN} }; 
    for (int rval; (rval=poll(&plist[0],plist.size(),/*timeout*/-1))>0;) { 
    if (plist[0].revents&POLLIN) { 
     int bytes_read = read(cout_pipe[0], &buffer[0], buffer.length()); 
     cout << "read " << bytes_read << " bytes from stdout.\n"; 
     cout << buffer.substr(0, static_cast<size_t>(bytes_read)) << "\n"; 
    } 
    else if (plist[1].revents&POLLIN) { 
     int bytes_read = read(cerr_pipe[0], &buffer[0], buffer.length()); 
     cout << "read " << bytes_read << " bytes from stderr.\n"; 
     cout << buffer.substr(0, static_cast<size_t>(bytes_read)) << "\n"; 
    } 
    else break; // nothing left to read 
    } 

    waitpid(pid,&exit_code,0); 
    cout << "exit code: " << exit_code << "\n"; 

    posix_spawn_file_actions_destroy(&action); 
} 
+0

Non l'ho premuto a fondo .C'è molto spazio per un migliore controllo degli errori e pulizia delle risorse. – nobar

+1

Perché non hai inserito solo args ma hai fatto & args [0] al quinto argomento di posix_spawn()? – Dula

+1

@Dula: il modulo che ho usato è più generale e più chiaro per me. Funziona per i puntatori, ma anche per i gestori di array basati sulla classe come 'std :: vector' e' std :: string'. Io uso quella forma in diversi punti, tra cui '& buffer [0]', che non avrebbe funzionato come solo 'buffer'. – nobar