2010-10-07 17 views
18

Estoy implementando tuberías en un sistema de archivos simulado en C++ (principalmente con C). Necesita ejecutar comandos en el shell del host pero realizar la propia tubería en el sistema de archivos simulado.¿Puede popen() hacer tubos bidireccionales como pipe() + fork()?

que podía lograr esto con los pipe(), fork() y system() llamadas al sistema, pero prefiero usar popen() (que se ocupa de la creación de una tubería, un proceso que se bifurcan, y pasando un comando de la cáscara). Esto puede no ser posible porque (creo) necesito poder escribir desde el proceso principal de la tubería, leer en el extremo del proceso secundario, escribir la salida desde el elemento secundario y, finalmente, leer esa salida del elemento primario. La página de manual para popen() en mi sistema dice que es posible un conducto bidireccional, pero mi código debe ejecutarse en un sistema con una versión anterior que solo soporte tubos unidireccionales.

Con las llamadas separadas anteriores, puedo abrir/cerrar tuberías para lograr esto. ¿Es posible con popen()?

Para un ejemplo trivial, para ejecutar ls -l | grep .txt | grep cmds necesito:

  • abierto de una tubería y el proceso para ejecutar ls -l en el host; leer su salida trasera
  • canalizar la salida del ls -l de nuevo a mi simulador de
  • Abrir un tubo y el proceso para ejecutar grep .txt en el host en la salida del hilo de ls -l
  • Pipe la salida de esta vuelta al simulador (pegado aquí)
  • Abrir un tubo y el proceso para ejecutar grep cmds en el host en la salida del hilo grep .txt
  • pipe la salida de esta vuelta al simulador e imprimirlo

hombre Popen

desde Mac OS X:

La función popen() 'abre' un proceso mediante la creación de una tubería bidireccional, se bifurcan, e invocando la cáscara. Cualquier secuencia abierta por popen() llamadas anteriores en el proceso principal están cerradas en el nuevo proceso hijo. Históricamente, popen() se implementó con una tubería unidireccional; por lo tanto, muchas implementaciones de popen() solo permiten que el argumento mode especifique lectura o escritura, no ambas. Como popen() ahora se implementa utilizando un conducto bidireccional , el argumento de modo puede solicitar un flujo de datos bidireccional. El argumento modo es un puntero a una cadena terminada en cero que debe ser 'r' para lectura, 'w' para escritura o 'r +' para lectura y escritura.

+3

ignorar esta mala manual, que está haciendo que parezca que unidireccionalidad de las tuberías es un comportamiento heredado. El comportamiento de su implementación no es estándar y es probable que nunca sea compatible con otros entornos. Simplemente configure las tuberías y el tenedor y ejecútese, y todo estará feliz. –

+2

Todo este hilo está hecho de win, todas las respuestas y la pregunta merecen +1. –

+0

Acabo de hacer ** man popen ** en Ubuntu 13.04 y dice: 'Dado que un conducto es por definición unidireccional, el argumento tipo puede especificar solo lectura o escritura, no ambos; la secuencia resultante es correspondientemente de solo lectura o de solo escritura. "Estoy sorprendido de que este sea el popen más antiguo ... – sager89

Respuesta

10

Parece que has respondido tu propia pregunta. Si su código necesita funcionar en un sistema anterior que no admite popen abriendo tuberías bidireccionales, entonces no podrá usar popen (al menos no el que se suministra).

La verdadera pregunta sería acerca de las capacidades exactas de los sistemas anteriores en cuestión. En particular, ¿su pipe admite la creación de conductos bidireccionales? Si tienen un pipe que puede crear una tubería bidireccional, pero popen que no, entonces escribiría la secuencia principal del código para usar popen con una tubería bidireccional, y suministraría una implementación de popen que puede usar una tubería bidireccional que se compila en un usado donde sea necesario.

Si necesita para apoyar los sistemas suficiente como para que pipe sólo es compatible con tuberías viejas unidireccionales, entonces estás bastante atrapado con el uso de pipe, fork, dup2, etc., por su cuenta. Probablemente envuelva esto en una función que funcione casi como una versión moderna de popen, pero en lugar de devolver un identificador de archivo, rellena una pequeña estructura con dos identificadores de archivo, uno para el stdin del niño, el otro para el niño stdout.

+0

Creo que me has ayudado a darme cuenta de que mi pregunta era realmente:" ¿Es la capacidad bidireccional? común '? ", y parece que no lo es. Gracias por la gran respuesta. –

+0

+1 para el popen para una dirección + tubería() para la otra. Me evita reescribir la mayoría de las declaraciones de cout, y solo tiene que cambiar cin en su lugar. – Cardin

8

POSIX estipula que la llamada popen() no está diseñado para proporcionar una comunicación bidireccional:

El argumento de modo en popen() es una cadena que especifica el modo I/O:

  1. Si el modo es r, cuando se inicia el proceso secundario, su descriptor de archivo STDOUT_FILENO será el final escribible del conducto y el descriptor de archivo fileno (flujo) en el proceso de llamada, donde stream es el puntero de flujo devuelto por popen() , será el final legible de tubo.
  2. Si el modo es w, cuando se inicia el proceso secundario, el descriptor de archivo STDIN_FILENO será el final legible de la tubería y el descriptor de archivo fileno (flujo) en el proceso de llamada, donde stream es el puntero de flujo devuelto por popen (), será el final grabable de la tubería.
  3. Si el modo tiene algún otro valor, el resultado no se especifica.

Cualquier código portátil no hará suposiciones más allá de eso. El BSD popen() es similar a lo que describe su pregunta.

Además, las tuberías son diferentes de las tomas y cada descriptor de archivo de tubería es unidireccional. Tendría que crear dos tuberías, una configurada para cada dirección.

19

Le sugiero que escriba su propia función para hacer la tubería/bifurcación/sistema para usted. Usted podría tener la función de generar un proceso y volver descriptores de archivos de lectura/escritura, como en ...

typedef void pfunc_t (int rfd, int wfd); 

pid_t pcreate(int fds[2], pfunc_t pfunc) { 
    /* Spawn a process from pfunc, returning it's pid. The fds array passed will 
    * be filled with two descriptors: fds[0] will read from the child process, 
    * and fds[1] will write to it. 
    * Similarly, the child process will receive a reading/writing fd set (in 
    * that same order) as arguments. 
    */ 
    pid_t pid; 
    int pipes[4]; 

    /* Warning: I'm not handling possible errors in pipe/fork */ 

    pipe(&pipes[0]); /* Parent read/child write pipe */ 
    pipe(&pipes[2]); /* Child read/parent write pipe */ 

    if ((pid = fork()) > 0) { 
     /* Parent process */ 
     fds[0] = pipes[0]; 
     fds[1] = pipes[3]; 

     close(pipes[1]); 
     close(pipes[2]); 

     return pid; 

    } else { 
     close(pipes[0]); 
     close(pipes[3]); 

     pfunc(pipes[2], pipes[1]); 

     exit(0); 
    } 

    return -1; /* ? */ 
} 

usted puede añadir cualquier funcionalidad que necesita en ese país.

+0

¡Eso es maravilloso! Gracias GRAN ESTILO! –

1

No es necesario crear dos tuberías y perder un descriptor de archivo en cada proceso. Solo use un socket en su lugar.https://stackoverflow.com/a/25177958/894520

+1

¿Puede agregar más detalles de por qué un descriptor de archivo para cada proceso sería un "desperdicio"? Quiero decir, ¿cuánto cuesta? ¿Qué es más barato con el socket de dominio? –

+0

Tampoco estoy convencido de que se requiera socket en este caso. Pero si de hecho es mejor que las tuberías, me gustaría saber cómo y por qué. –

0

En uno de netresolve backends Estoy hablando con un guión y por lo tanto tengo que escribir a su stdin y leer desde su stdout. La siguiente función ejecuta un comando con stdin y stdout redirigidos a un conducto. Puedes usarlo y adaptarlo a tu gusto.

static bool 
start_subprocess(char *const command[], int *pid, int *infd, int *outfd) 
{ 
    int p1[2], p2[2]; 

    if (!pid || !infd || !outfd) 
     return false; 

    if (pipe(p1) == -1) 
     goto err_pipe1; 
    if (pipe(p2) == -1) 
     goto err_pipe2; 
    if ((*pid = fork()) == -1) 
     goto err_fork; 

    if (*pid) { 
     /* Parent process. */ 
     *infd = p1[1]; 
     *outfd = p2[0]; 
     close(p1[0]); 
     close(p2[1]); 
     return true; 
    } else { 
     /* Child process. */ 
     dup2(p1[0], 0); 
     dup2(p2[1], 1); 
     close(p1[0]); 
     close(p1[1]); 
     close(p2[0]); 
     close(p2[1]); 
     execvp(*command, command); 
     /* Error occured. */ 
     fprintf(stderr, "error running %s: %s", *command, strerror(errno)); 
     abort(); 
    } 

err_fork: 
    close(p2[1]); 
    close(p2[0]); 
err_pipe2: 
    close(p1[1]); 
    close(p1[0]); 
err_pipe1: 
    return false; 
} 

https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46

(que utiliza el mismo código en popen simultaneous read and write)