¿Puede popen() hacer tuberías bidireccionales como pipe() + fork()?

8 minutos de lectura

avatar de usuario
Taylor D Edmiston

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 canalización en el sistema de archivos simulado.

Podría lograr esto con el pipe(), fork()y system() llamadas al sistema, pero prefiero usar popen() (que maneja la creación de una tubería, la bifurcación de un proceso y el paso de un comando al shell). Esto puede no ser posible porque (creo) necesito poder escribir desde el proceso principal de la canalización, leer en el extremo del proceso secundario, escribir la salida del secundario y finalmente leer esa salida del principal. La página del manual para popen() en mi sistema dice que es posible una tubería bidireccional, pero mi código debe ejecutarse en un sistema con una versión anterior que admita solo tuberías unidireccionales.

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

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

  • Abra una tubería y procese para ejecutar ls -l en el anfitrión; leer su salida de nuevo
  • Pipe la salida de ls -l volver a mi simulador
  • Abra una tubería y procese para ejecutar grep .txt en el host en la salida canalizada de ls -l
  • Canalice la salida de esto de vuelta al simulador (atascado aquí)
  • Abra una tubería y procese para ejecutar grep cmds en el host en la salida canalizada de grep .txt
  • Canalice la salida de esto de vuelta al simulador e imprímalo

hombre papa

Desde Mac OS X:

los popen() La función ‘abre’ un proceso creando una tubería bidireccional, bifurcándola e invocando el shell. Cualquier flujo abierto por anterior popen()
las llamadas en el proceso principal se cierran en el nuevo proceso secundario. Históricamente, popen() se implementó con tubería unidireccional; por lo tanto, muchas implementaciones de popen() solo permita que el argumento de modo especifique lectura o escritura, no ambas. Porque
popen() ahora se implementa mediante una canalización bidireccional, el argumento de modo puede solicitar un flujo de datos bidireccional. El argumento de modo es un puntero a una cadena terminada en nulo que debe ser ‘r’ para leer, ‘w’ para escribir o ‘r+’ para leer y escribir.

  • Ignore este mal manual, que hace que parezca que la unidireccionalidad de las tuberías es un comportamiento heredado. El comportamiento de su implementación no es estándar y es probable que nunca se admita en otros entornos. Simplemente configure las tuberías y la horquilla y ejecútelo usted mismo, y todo estará feliz.

    – R.. GitHub DEJA DE AYUDAR A ICE

    7 oct 2010 a las 19:20

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

    – Matt Carpintero

    3 de mayo de 2011 a las 1:14


  • acabo de hacer un hombre papa en Ubuntu 13.04 y dice:Since a pipe is by definition unidirectional, the type argument may specify only reading or writing, not both; the resulting stream is correspondingly read-only or write-only. Me sorprende que este sea el papa “mayor”…

    – sabio89

    14/03/2014 a las 15:14


Sugeriría escribir su propia función para hacer la tubería/bifurcación/sistema-ing para usted. Puede hacer que la función genere un proceso y devuelva descriptores de archivo 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; /* ? */
}

Puede agregar cualquier funcionalidad que necesite allí.

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

La verdadera pregunta sería sobre las capacidades exactas de los sistemas más antiguos en cuestión. En particular, ¿su pipe apoyar la creación de tuberías bidireccionales? si tienen un pipe que puede crear una tubería bidireccional, pero popen eso no es así, entonces escribiría la secuencia principal del código para usar popen con tubería bidireccional, y suministrar una implementación de popen que puede usar una tubería bidireccional que se compila en un uso donde sea necesario.

Si necesita admitir sistemas lo suficientemente antiguos como para pipe solo admite tuberías unidireccionales, entonces está prácticamente atascado con el uso pipe, fork, dup2, etc., por su cuenta. Probablemente todavía envolvería esto en una función que funcione casi como una versión moderna de popenpero en lugar de devolver un identificador de archivo, completa una pequeña estructura con dos identificadores de archivo, uno para el niño stdinel otro para el niño stdout.

  • Creo que me ha ayudado a darme cuenta de que mi pregunta era realmente: “¿La capacidad bidireccional es ‘común’?”, Y parece que no lo es. Gracias por la gran respuesta.

    – Taylor D. Edmiston

    23 de octubre de 2010 a las 0:58

  • +1 para popen para una dirección + pipe() para la otra. Me ahorra tener que reescribir la mayoría de las declaraciones de cout y solo tengo que cambiar cin en su lugar.

    – Cardín

    23 de noviembre de 2013 a las 9:54

avatar de usuario
jonathan leffler

POSIX estipula que el popen() La llamada no está diseñada para proporcionar comunicación bidireccional:

El argumento de modo de popen() es una cadena que especifica el modo de E/S:

  1. Si el modo es r, cuando se inicia el proceso secundario, su descriptor de archivo STDOUT_FILENO será el extremo grabable de la canalización, y el descriptor de archivo fileno(stream) en el proceso de llamada, donde stream es el puntero de flujo devuelto por popen(), será el extremo legible de la tubería.
  2. Si el modo es w, cuando se inicia el proceso secundario, su descriptor de archivo STDIN_FILENO será el final legible de la canalización, y el descriptor de archivo fileno(stream) en el proceso de llamada, donde stream es el puntero de flujo devuelto por popen(), será ser el extremo grabable de la tubería.
  3. Si la moda es cualquier 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 los conectores y cada descriptor de archivo de tubería es unidireccional. Tendría que crear dos tuberías, una configurada para cada dirección.

avatar de usuario
pavel simerda

En una de resolución de red backends Estoy hablando con un script y, por lo tanto, necesito escribir en su stdin y leer de su stdout. La siguiente función ejecuta un comando con stdin y stdout redirigidos a una tubería. 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

(Usé el mismo código en popen lectura y escritura simultáneas)

Aquí está el código (C++, pero se puede convertir fácilmente a C):

#include <unistd.h>

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <utility>

// Like popen(), but returns two FILE*: child's stdin and stdout, respectively.
std::pair<FILE *, FILE *> popen2(const char *__command)
{
    // pipes[0]: parent writes, child reads (child's stdin)
    // pipes[1]: child writes, parent reads (child's stdout)
    int pipes[2][2];

    pipe(pipes[0]);
    pipe(pipes[1]);

    if (fork() > 0)
    {
        // parent
        close(pipes[0][0]);
        close(pipes[1][1]);

        return {fdopen(pipes[0][1], "w"), fdopen(pipes[1][0], "r")};
    }
    else
    {
        // child
        close(pipes[0][1]);
        close(pipes[1][0]);

        dup2(pipes[0][0], STDIN_FILENO);
        dup2(pipes[1][1], STDOUT_FILENO);

        execl("/bin/sh", "/bin/sh", "-c", __command, NULL);

        exit(1);
    }
}

Uso:

int main()
{
    auto [p_stdin, p_stdout] = popen2("cat -n");

    if (p_stdin == NULL || p_stdout == NULL)
    {
        printf("popen2() failed\n");
        return 1;
    }

    const char msg[] = "Hello there!";
    char buf[32];

    printf("I say \"%s\"\n", msg);

    fwrite(msg, 1, sizeof(msg), p_stdin);
    fclose(p_stdin);

    fread(buf, 1, sizeof(buf), p_stdout);
    fclose(p_stdout);

    printf("child says \"%s\"\n", buf);

    return 0;
}

Salida posible:

I say "Hello there!"
child says "     1      Hello there!"

avatar de usuario
Comunidad

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

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad