Подтвердить что ты не робот

Может ли popen() создавать двунаправленные трубы, например pipe() + fork()?

Я реализую конвейер на смоделированной файловой системе в С++ (в основном с C). Он должен запускать команды в оболочке хоста, но выполнять сам трубопровод в имитируемой файловой системе.

Я мог бы добиться этого с помощью системных вызовов pipe(), fork() и system(), но я бы предпочел использовать popen() (который обрабатывает создание канала, форматирование процесса и передачу команды оболочка). Это может быть невозможно, потому что (я думаю) мне нужно иметь возможность писать из родительского процесса в трубе, читать на конце дочернего процесса, записывать выходные данные из дочернего элемента и, наконец, читать этот вывод из родителя. Страница руководства для popen() в моей системе говорит, что возможна двунаправленная трубка, но мой код нужно запускать в системе со старой версией, поддерживающей только однонаправленные каналы.

С помощью отдельных вызовов выше я могу открыть/закрыть трубки, чтобы достичь этого. Возможно ли это с помощью popen()?

Для тривиального примера для запуска ls -l | grep .txt | grep cmds мне нужно:

  • Откройте канал и процесс для запуска ls -l на хосте; прочитайте его вывод назад
  • Произведите вывод ls -l на мой симулятор
  • Откройте трубу и процесс для запуска grep .txt на хосте на выходном канале ls -l
  • Произведите вывод этой обратной связи на симулятор (застрял здесь)
  • Откройте трубку и процесс для запуска grep cmds на хосте на выходном канале grep .txt
  • Произведите вывод этой задней части на симулятор и распечатайте его

man popen

Из Mac OS X:

Функция popen() "открывает" a процесса путем создания двунаправленного трубы, разветвления и вызова оболочки. Любые потоки, открытые предыдущим popen()вызовы в родительском процессе закрыты в новом дочернем процессе. Исторически сложилось, что popen()с однонаправленной трубой; следовательно, существует множество реализаций popen()разрешить аргумент режима чтение или письмо, а не оба. Потому как popen() теперь реализуется с использованием двунаправленная труба, аргумент режима может запросить двунаправленный поток данных. Аргумент mode является указателем на нулевая строка, которая должна быть 'r' для чтения, 'w' для записи или "r +" для чтения и записи.

4b9b3361

Ответ 1

Кажется, ты ответил на свой вопрос. Если ваш код должен работать на более старой системе, которая не поддерживает popen открытие двунаправленных каналов, то вы не сможете использовать popen (по крайней мере, не тот, который был предоставлен).

Реальный вопрос будет касаться точных возможностей более старых систем, о которых идет речь. В частности, поддерживает ли их pipe создание двунаправленных труб? Если у них есть pipe, который может создать двунаправленный канал, но popen, который этого не делает, тогда я напишу основной поток кода, чтобы использовать popen с двунаправленным каналом, и поставьте реализацию popen, который может использовать двунаправленный канал, который скомпилируется в используемом при необходимости.

Если вам нужно поддерживать системы, достаточно старые, чтобы pipe поддерживал только однонаправленные каналы, вы практически застряли в использовании pipe, fork, dup2 и т.д. самостоятельно. Я бы, вероятно, все еще обернул это функцией, которая работает почти как современная версия popen, но вместо того, чтобы возвращать один дескриптор файла, заполняет небольшую структуру двумя файловыми дескрипторами, один для дочернего stdin, другое для ребенка stdout.

Ответ 2

Я бы посоветовал написать свою собственную функцию, чтобы сделать для вас трубопровод /forking/system -ing. Вы могли бы заставить эту функцию запускать процесс и возвращать дескрипторы файла чтения/записи, как в...

typedef void pfunc_t (int rfd, int wfd);

pid_t pcreate(int fds[2], pfunc_t pfunc) {
    /* Spawn a process from pfunc, returning it 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; /* ? */
}

Вы можете добавить любую функциональность, в которой вы нуждаетесь.

Ответ 3

POSIX предусматривает, что вызов popen() не предназначен для обеспечения двунаправленной связи:

Аргумент mode для popen() - это строка, определяющая режим ввода/вывода:

  • Если режим равен r, когда дочерний процесс запущен, его файловый дескриптор STDOUT_FILENO должен быть записываемым концом канала и файловым дескриптором fileno (stream) в вызывающем процессе, где stream - указатель потока, возвращаемый popen(), должен быть читаемым концом трубы.
  • Если режим w, когда дочерний процесс запущен, его файловый дескриптор STDIN_FILENO должен быть читаемым концом канала и файловым дескриптором fileno (stream) в вызывающем процессе, где stream - указатель потока, возвращенный popen (), должен быть записываемый конец трубы.
  • Если режим - любое другое значение, результат не указан.

Любой переносимый код не будет делать никаких предположений. BSD popen() похож на ваш вопрос.

Кроме того, трубы отличаются от сокетов, и каждый дескриптор файла канала является однонаправленным. Вам нужно будет создать два канала, каждый из которых настроен для каждого направления.

Ответ 4

Не нужно создавать две трубы и отсылать filedescriptor в каждом процессе. Просто используйте сокет вместо этого. fooobar.com/questions/199010/...

Ответ 5

В одном из netresolve backends Я разговариваю с script, поэтому мне нужно написать его stdin и читать из stdout. Следующая функция выполняет команду с stdin и stdout, перенаправленными на канал. Вы можете использовать его и адаптировать по своему вкусу.

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

(Я использовал один и тот же код в одновременном чтении и записи по почте)