Перенаправление stdin и stdout через каналы в C работает для внешних программ, но не для рекурсивного вызова - PullRequest
0 голосов
/ 03 декабря 2018

Я пытаюсь связаться с разветвленными дочерними процессами с помощью перенаправления канала stdin и stdout в C. Мне уже удалось заставить это работать для команд оболочки (например, ls), выполняемых в дочерних процессах.Однако я не смог рекурсивно выполнить ту же программу и перенаправить вывод (напечатанный printf(), fprintf() на stdout, ...) через каналы от дочернего процесса к родительскому (в этом тестедо stdout родительского), хотя это нормально работает для ls или аналогичных команд.

Вот как я пытался приблизиться к этому:

  • Я создаю канал,конец чтения предназначен для родительского элемента, дочерний процесс должен выполнить запись в конец записи.
  • Процесс разветвляется, оба процесса закрывают неиспользуемый конец соответственно.
  • Конец записи канала перенаправляетсяна STDOUT_FILENO и закрыто
  • Дочерний процесс выполняет программу рекурсивно (она называется ./to2)

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

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netdb.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>


static void usage(void){
    fprintf(stderr,"RIP");
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]){   
    if(argc > 1){
        dprintf(STDOUT_FILENO,"Please work\n"); 
        printf("\n THIS IS A MESSAGE FROM THE CHILD \n");
        fputs("Pretty Please!\n",stdout);
        fflush(stdout);
        exit(EXIT_SUCCESS);
    }
    int p1[2];
    if(-1 == pipe(p1)) { 
        fprintf(stderr,"pipe\n");
        fprintf(stderr,"%s\n",strerror(errno));
        usage();    
    }
    int f = fork();
    if(f == 0){
        close(p1[0]);
        if(dup2(p1[1],STDOUT_FILENO) < 0){
            fprintf(stderr,"dup2\n");
            usage();
        }
        close(p1[1]);

        //I want this to work:
        //execlp("./to2", "./to2", "-e");

        //This works fine:
        execlp("ls", "ls");

        exit(EXIT_SUCCESS);
    } else if (f == -1) {
        usage();
    } else  {
        close(p1[1]);
        int w = -1;
        if(-1 == wait(&w)) usage();
        char b[12];
        memset(b,0,12); 
        read(p1[0],&b,12);

        char reading_buf[1];

        while(read(p1[0], reading_buf, 1) > 0){ 
            write(1, reading_buf, STDOUT_FILENO);
        }
        close(p1[0]);
    }
}   

В целях тестирования функция вызывается рекурсивно с дополнительными аргументами, в то время как родительская программа вызывается без дополнительных аргументов (отсюда if(argc>1)).В финальной программе бесконечной рекурсии избегают другими способами.

Я что-то понял неправильно?Я довольно смущен тем фактом, что единственное, что не работает, - это перенаправление вывода моей собственной программы ...

Заранее большое спасибо, любая помощьили идеи с благодарностью.

1 Ответ

0 голосов
/ 03 декабря 2018

Основная проблема точно так, как указано в комментариях - вы не правильно называете execlp() (или ls в альтернативе).Вы должны сделать последний аргумент этих вызовов функций явным нулевым указателем, как показано в этом коде, который представляет собой слегка отредактированную версию вопроса:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

static void usage(void)
{
    fprintf(stderr, "RIP\n");
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    if (argc > 1)
    {
        dprintf(STDOUT_FILENO, "Please work\n");
        printf("THIS IS A MESSAGE FROM THE CHILD\n");
        fputs("Pretty Please!\n", stdout);
        fflush(stdout);
        exit(EXIT_SUCCESS);
    }
    int p1[2];
    if (-1 == pipe(p1))
    {
        fprintf(stderr, "pipe: %s\n", strerror(errno));
        usage();
    }
    int f = fork();
    if (f == 0)
    {
        close(p1[0]);
        if (dup2(p1[1], STDOUT_FILENO) < 0)
        {
            fprintf(stderr, "dup2: %s\n", strerror(errno));
            usage();
        }
        close(p1[1]);
        execlp(argv[0], argv[0], "-e", (char *)0);
        fprintf(stderr, "failed to exec %s again\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    else if (f == -1)
    {
        usage();
    }
    else
    {
        close(p1[1]);
        char b[13];
        memset(b, 0, 13);
        if (read(p1[0], &b, 12) < 0)
        {
            fprintf(stderr, "Failed to read from pipe (%s)\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        int len = strcspn(b, "\n");
        printf("M1 [%.*s]\n", len, b);

        char reading_buf[1];

        while (read(p1[0], reading_buf, 1) > 0)
        {
            write(1, reading_buf, STDOUT_FILENO);
        }
        close(p1[0]);
        int w = -1;
        if (-1 == wait(&w))
            usage();
    }
    return 0;
}

Два важных изменения должны быть выделены:

  • Этот код повторяет первую строку данных - ту, которая записана dprintf() - тогда как исходный код просто прочитал ее и отбросил.
  • Вызов wait()после ввода, а не до.Если у дочернего элемента было больше данных для записи, чем набора фиксированных сообщений, он мог бы заблокировать ожидание того, чтобы родитель прочитал некоторые данные, в то время как родительский элемент заблокировал ожидание выхода дочернего элемента.Это будет тупик.

Функция usage() не имеет соответствующего имени - она ​​не сообщает, как запустить программу.Я также завершаю работу с состоянием сбоя, а не с успехом, если дочерний процесс завершается с ошибкой execlp().

При особых обстоятельствах вызов wait() может сообщать о состоянии выхода от какого-либо дочернего объекта, отличного от того, которыйбыл раздвоенКак правило, лучше использовать цикл, чтобы пожинать таких детей.Однако требуемые обстоятельства чрезвычайно необычны - процесс, который запустил родительский элемент с функцией exec*(), должен был предварительно создать некоторых дочерних элементов, которых он не ожидал, чтобы они наследовались родительским процессом (поскольку PID не 'изменить exec*() вызов).

...