запись во внешнюю программу через канал (C) - PullRequest
1 голос
/ 05 января 2020

Я хотел использовать внешнюю программу для обработки данных в памяти. Как внешний компрессор, кодировщик, что угодно, чтобы обработать мои данные и получить результат. Я много читал о трубах, и это все еще не работает. Таким образом, я получил простую программу, которая пытается записать во внешнюю программу по каналу, как это, позволяя ей печатать на стандартный вывод:

                                                     stdout
             (w) pipeA (r)          $prog            +---+
             +-----------+       /~~~~~~~~~~~\       |{1}|
             |[1]     [0]| ----> |{0}     {1}| ----> |   |
        +~~> +-----------+       \~~~~~~~~~~~/       |   |
        |                                            +---+
        |
       +-+
 write() |
       +-+

И я все еще ничего не получил. Мой код выглядит так:

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


int main(void)
{
    int pipA[2];
    int pid;
    char buf_IN[32] = "Hello pipe!\n";
    ssize_t n_written;

    if ((pipe(pipA) == -1))    {
        perror("pipe failed");
        exit(1);
    }
    if ((pid = fork()) < 0)    {
        perror("fork failed");
        exit(2);
    }
    /*****************************/

    if (pid == 0)
        { /* in child */
        dup2(0, pipA[0]);   // pipA[read(0)-end]->$prog[write{0}-end]
        close(pipA[1]);   // $prog won't write to this pipe(A)
        // external ``$prog''ram
        execlp("wc", "wc", (char *) 0);   // out should be: '      1       2      12'
        //execlp("base64", "base64", (char *) 0);   // out should be: 'SGVsbG8gcGlwZSEK'

        ;///if we're here something went wrong
        perror("execlp() @child failed");
        exit(3);
        }

    else
        { /* in parent */
        //dup2(pipA[1], 0);  // STDIN -> pipA // that supposed to connect STDIN->pipA; just in case I needed it
        close(pipA[0]);  // we won't read it, let $prog write to stdout
        //perror("execlp() @parent failed");
        //exit(4);
        n_written = write(pipA[1], buf_IN, strlen(buf_IN));
        close(pipA[1]);  // I guess this will close the pipe and send child EOF

        // base64: read error: Input/output error
        // wc: 'standard input': Input/output error
        //      0       0       0
        }

return 0;
}

Комментарии показывают, что я делаю. Я должен признать, что я не получаю эти dup () в каналах, и это то, что я думаю, вызывает проблему здесь, но не знаю. Вы можете помочь с этой, казалось бы, простой проблемой? Любая помощь приветствуется.

Ответы [ 2 ]

4 голосов
/ 05 января 2020

Диагноз

У вас есть аргументы для dup2() задом наперед. Вам необходимо:

dup2(pipA[0], 0);

Закрытие файловых дескрипторов

Вы недостаточно закрываете файловые дескрипторы у дочернего элемента:


Правило большого пальца : Если вы dup2() на одном конце канала подключены к стандартному вводу или стандартному выводу, закройте оба дескриптора исходного файла, возвращенных pipe(), как можно скорее. В частности, вы должны закрыть их перед использованием любого из семейства функций exec*().

Правило также применяется, если вы дублируете дескрипторы с dup() или fcntl() с F_DUPFD


Prescription

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

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

int main(void)
{
    int pipA[2];
    int pid;
    char buf_IN[32] = "Hello pipe!\n";
    ssize_t n_written;

    if ((pipe(pipA) == -1))
    {
        perror("pipe failed");
        exit(1);
    }
    if ((pid = fork()) < 0)
    {
        perror("fork failed");
        exit(2);
    }

    if (pid == 0)
    {
        /* Child: Connect pipA[0] (read) to standard input 0 */
        dup2(pipA[0], 0);
        close(pipA[1]);  /* Close write end of pipe */
        close(pipA[0]);  /* Close read  end of pipe */
        execlp("wc", "wc", (char *)0);
        perror("execlp() @child failed");
        exit(3);
    }
    else
    {
        close(pipA[0]);  /* Close read end of pipe */
        n_written = write(pipA[1], buf_IN, strlen(buf_IN));
        if (n_written != (ssize_t)strlen(buf_IN))
        {
            perror("short write");
            exit(4);
        }
        close(pipA[1]);  /* Close write end of pipe — EOF for child */
    }

    /* Optionally wait for child to die before exiting */
    // #include <sys/wait.h>  // With other #include lines
    // int corpse;
    // int status;
    // while ((corpse = wait(&status)) > 0)
    //     printf("Child %d exited with status 0x%.4X\n", corpse, status);

    return 0;
}

При запуске это выдает:

       1       2      12

Это выглядит правильно.

Без wait() l oop возможно, что вы увидите вывод из wc после запроса от оболочки (поэтому он может выглядеть как если программа ожидает ввода от вас, но на самом деле это будет оболочка, ожидающая ввода); с ожидающим l oop вы получите правильное разделение вывода из приглашения оболочки. Вам не нужно ничего печатать в теле l oop, но это обнадеживает.

0 голосов
/ 06 января 2020

Чтобы не делать дубликатов и не играть экспертом в незнакомой области, я выкладываю это как ответ.

Я закончил задачу

                 (w) pipeA (r)            child            (w) pipeB (r)
                 +-----------+       /~~~~~~~~~~~~~\       +-----------+
            +~~~>|[1]     [0]| ----> |{0} $prog {1}| ----> |[1]     [0]| ~~~+
            |    +-----------+       \~~~~~~~~~~~~~/       +-----------+    |
            |                                                               \/
           +-+                                                             +--+
     write() |                                                             |  read()
           +-+                                                             +--+

                dup(pA[0],0)                                dup(pB[1],1)
                close(pA[1])                                close(pA[0])
                close(pA[0])                                close(pA[1])


со вторым каналом, который можно прочитать. Все по аналогии. Если есть что-то серьезное, что не так с этим или что-то, о чем я должен знать, скажите, пожалуйста (Извините за отступы в стиле python, надеюсь, вы не возражаете)

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

ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte);

int main(void)
    {
    int pipA[2], pipB[2];
    int pid;
    char buf_IN[32] = "Hello pipe!\n";
    char buf_OUT[1024];
    char *bptr;
    ssize_t n_written, n_read = 0, a_read = 0, to_read = sizeof(buf_OUT);

    if ((pipe(pipA) == -1) || (pipe(pipB) == -1))
        {
        perror("pipe failed");
        exit(1);
        }
    if ((pid = fork()) < 0)
        {
        perror("fork failed");
        exit(2);
        }

    if (pid == 0)
        {
        /* in child */
        dup2(pipA[0], 0);  // connect pipe A (read end/exit) to stdin (write end/input)
        close(pipA[1]);  // close unused pipe A end
        close(pipA[0]);  // close  - " - //
        ;
        dup2(pipB[1], 1);  // connect stdout (read end/output) to pipe B (write end/entry)
        close(pipB[0]);  // close unused pipe B ends
        close(pipB[1]);  // close  - " - //
        execlp("lzip", "lzip", "-c", (char *)0);
        ;
        perror("execlp() @child failed");
        exit(3);
        }
    else
        {
        /* in parent */
        close(pipA[0]);  // close pipe A read end - will only write to this one
        n_written = write(pipA[1], buf_IN, strlen(buf_IN));
        if (n_written < 0)
            perror("error: read_whole_file(pipA[1], ...) failed miserably\n");
        close(pipA[1]);  // close write end which subsequently signals EOF to child
        ;
        close(pipB[1]);  // close pipe B write end - will only read form this one
        a_read = read_whole_file(pipB[0], buf_OUT, sizeof(buf_OUT));
        if (a_read < 0)
            perror("error: read_whole_file(pipB[0], ...) failed miserably\n");
        close(pipB[0]);  // close read end after reading
        ;
        write(STDOUT_FILENO, buf_OUT, a_read);  // dump it to parent's stdout - equivalent of processing data received from external program/plugin
        }
    return 0;
    }

ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte)  // read whole file
    {
    ssize_t n_read, a_read = 0, to_read = nbyte;
    char *bptr = (char*)buf;
    size_t BUF_SIZE = 4096;
    do
        {
        if(to_read < BUF_SIZE)
            BUF_SIZE = to_read;
        n_read = read(fildes, bptr, BUF_SIZE);
        if (n_read < 0)  // recover from temporarily failed read (got it from git wrapper)
            {
            if ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK))
                continue;
            }
        bptr += n_read;
        to_read -= n_read;
        a_read += n_read;
        } while ((n_read>0) && (to_read>0));
    ;
    if (n_read < 0)
        a_read = n_read;
    return a_read;
    }

Заметим, что это не так очевидно - у меня до сих пор не получается close(pipA[0]); и close(pipB[1]); у ребенка. Почему они больше не используются? Также dup2(pipB[1], 1);, я думал, что все будет наоборот, но это не сработало, поэтому из-за ошибки завершения пробега я прихожу с этим.

...