Может ли popen () создавать двунаправленные каналы, такие как pipe () + fork ()? - PullRequest
19 голосов
/ 07 октября 2010

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

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

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

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

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

человек попен

Из Mac OS X:

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

Ответы [ 5 ]

19 голосов
/ 07 октября 2010

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

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; /* ? */
}

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

11 голосов
/ 07 октября 2010

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

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

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

8 голосов
/ 07 октября 2010

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

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

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

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

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

1 голос
/ 23 февраля 2017

В одном из netresolve я говорю о скрипте, и поэтому мне нужно записать его 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

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

1 голос
/ 07 августа 2014

Нет необходимости создавать два канала и тратить файловый дескриптор в каждом процессе.Просто используйте сокет вместо этого.https://stackoverflow.com/a/25177958/894520

...