LINUX.ORG.RU

Получение stdin/stdout дочернего процесса

 , ,


2

1

Пытаюсь используя Си сделать fork, execl и получить stdin/stdout дочернего процесса. Нужно что бы для дочернего процесса это работало без лишних телодвижений с его стороны (т.е. ему не нужно знать что он на самом деле пишет в pipe). К примеру printf(«QWERTY») — он думает что пишет в stdout, а на самом деле пишет в канал. С кодом это станет яснее =)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int fd[2];
    int pid;

    char data[] = "QWERTY";

    pipe(fd);

    if((pid = fork()) == 0) {
        close(0);
        dup(fd[0]);

        close(1);
        dup(fd[1]);

        close(fd[0]);
        close(fd[1]);
        execl("/home/<user>/test", "/home/<user>/test", NULL);

        exit(EXIT_FAILURE);
    } else {
        write(fd[1], data, 6);
        close(fd[1]);

        char buf[1024];
        read(fd[0], buf, 1024);
        close(fd[0]);

        printf("[%d] Output: \n%s\n", getpid(), buf);
    }

    return 0;
}

В ветке для дочернего процесса я закрываю stdin/out, dup берет их (уже свободные и минимальные) файловые дескрипторы и заставляет их указывать на in/out пайпа.

В ветке родителя я просто хочу сначала писать данные в stdin дочернего процесса (которые у меня fd[1]) и потом получить уже обработанные данные из stdout (который fd[0]).

Если просто читать, но не писать данные дочернему процессу, то все работает нормально. Я могу к примеру запустить «ls» и получить список директорий, не важно :). Проблемы возникают когда нужно отправить порцию данных, а потом получить эти данные уже обработанными.

Если исполнить этот код, то QWERTY попадает обратно ко мне. Т.е. я отправил, а потом я же и получил :(. Оно и не удивительно, это же pipe. Мне надо как-то это исправить, данные должны попадать к test'у.

test -> просто ещё одна программа на Си. Берет одну строку что приходит на stdin и пишет её в файл, всё.

После запуска кода должен появится файл с текстом QWERTY, а ко мне обратно должен вернуться текст «wrote» из test'а.

Я понимаю что сложно объяснил.

#include <stdio.h>
test.c
#include <string.h>

int main(int argc, char *argv[])
{
    char buf[128];
    scanf("%s", &buf);

    FILE *f = fopen("test.txt", "w");
    fwrite(buf, 1, strlen(buf), f);
    fclose(f);

    printf("wrote");

    return 0;
}
fexunexu ()

Так нужно делать два разных пайпа для stdin и stdout, а не один как у тебя - вроде же очевидно. И ещё нужно иметь в виду, что write на stdin может заблокироваться пока не прочитаешь чего-нибудь из stdout.

mashina ★★★★★ ()
Ответ на: комментарий от mashina

можно подробнее про два разных? Я просто пока что не могу в голове представить эту идею

fexunexu ()
Ответ на: комментарий от fexunexu

pipe() создаёт один пайп и отдаёт два fd в один из которых нужно записывать данные, а из другого читать. У тебя получается, что сам же пишешь в пайп через fd[1] и из него же, fd[0], читаешь.

mashina ★★★★★ ()
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stddef.h>

const char *DATA = "QWERTY\n";
const size_t NDATA = 7;

int
main()
{
    int stdout_fds[2] = {-1, -1};
    int stdin_fds[2] = {-1, -1};
    pid_t pid = -1;
    int ret = EXIT_FAILURE;

    if (pipe(stdout_fds) < 0 ||
        pipe(stdin_fds) < 0)
    {
        perror("pipe");
        goto cleanup;
    }
    if ((pid = fork()) < 0) {
        perror("fork");
        goto cleanup;
    }
    if (pid == 0) {
        if (dup2(stdin_fds[0], 0) < 0 ||
            dup2(stdout_fds[1], 1) < 0)
        {
            perror("dup2");
            goto child_fail;
        }
        // fork дублирует все дескрипторы. Нужно закрыть всё ненужное, иначе
        // read() никогда не возвратит 0 из-за того, что не все "концы" пайпа
        // закрыты.
        if (close(stdin_fds[0]) < 0 ||
            close(stdin_fds[1]) < 0 ||
            close(stdout_fds[0]) < 0 ||
            close(stdout_fds[1]) < 0)
        {
            perror("close");
            goto child_fail;
        }
        execl("/bin/cat", "/bin/cat", "-A",
              (char*)NULL); // просто NULL здесь — ошибка, см.
                            // https://ewontfix.com/11/
        perror("execl");
child_fail:
        // exit() вызывает atexit()-обработчики, чего тут делать ну никак не
        // нужно.
        _exit(127);
    }

    if (close(stdin_fds[0]) < 0 ||
        close(stdout_fds[1]) < 0)
    {
        perror("close");
        goto cleanup;
    }

    {
        ssize_t nwritten = write(stdin_fds[1], DATA, NDATA);
        if (nwritten < 0) {
            perror("write");
            goto cleanup;
        }
        if ((size_t)nwritten != NDATA) {
            fprintf(stderr, "write() has been interrupted.\n");
            goto cleanup;
        }
    }

    if (close(stdin_fds[1]) < 0) {
        perror("close");
        goto cleanup;
    }

    char buf[1024];
    // Нужно читать, пока read() не возвратит 0 (ну, или -1).
    size_t nread = 0;
    while (nread != sizeof(buf)) {
        ssize_t r = read(stdout_fds[0], buf + nread, sizeof(buf) - nread);
        if (r < 0) {
            perror("read");
            goto cleanup;
        } else if (r == 0) {
            break;
        } else {
            nread += r;
        }
    }

    printf("[%d] Output:\n%.*s\n", (int)pid, (int)nread, buf);

    ret = EXIT_SUCCESS;
cleanup:
    // close(-1) не делает ничего страшного.
    close(stdout_fds[0]);
    close(stdout_fds[1]);
    close(stdin_fds[0]);
    close(stdin_fds[1]);
    // waitpid(-1, ...) тоже.
    waitpid(pid, NULL, 0);
    return ret;
}

С форком в многопоточном приложении всё гораздо сложнее, кстати.

doushiyou ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.