проблема получения разветвленного процесса для чтения из STDIN с использованием каналов - PullRequest
1 голос
/ 23 сентября 2019

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

Основная функция, которая обрабатывает этот механизм (пожалуйста, не обращайте внимания на мелкие ошибки проверки)

минимальный рабочий пример

#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdarg.h>

struct exec_cmd_t {
    exec_cmd_t(std::vector<std::string> args) : args(args), has_executed(false), cpid(-1) { }
    exec_cmd_t(const exec_cmd_t &) = delete;
    exec_cmd_t(exec_cmd_t &&) = delete;
    exec_cmd_t & operator=(const exec_cmd_t &) = delete;
    exec_cmd_t & operator=(exec_cmd_t &&) = delete;

    std::string operator()();

    std::string pipe_cmd(const std::string & input);
    std::string pipe_cmd();
    ~exec_cmd_t();

    private:
    std::vector<std::string> args;
    bool has_executed;
    int cpid;
    std::stringstream in_stream;
    std::stringstream out_stream;

    friend std::string operator | (exec_cmd_t & first, exec_cmd_t & second);
    friend std::string operator | (exec_cmd_t && first, exec_cmd_t && second);
    friend std::string operator | (std::string, exec_cmd_t & second);
    friend std::string operator | (std::string, exec_cmd_t && second);
};

std::string exec_cmd_t::pipe_cmd(const std::string & input) {
    this->has_executed = true;
    const int read_end = 0;
    const int write_end = 1;

    int read_pipe[2];
    int write_pipe[2];

    if (pipe(read_pipe) < 0 || pipe(write_pipe) < 0) {
        this->has_executed = false;
        return std::string{};
    }

    this->in_stream << input;
    std::string line;
    while(getline(this->in_stream, line)) {
        if (line.size() == 0) {
            continue;
        }
       int wr_sz = write(write_pipe[write_end], line.c_str(), line.size());
       if (wr_sz <= 0) {
           break;
       }
       write(write_pipe[write_end], "\n", 1);
    }
    close(write_pipe[write_end]);

    this->cpid = fork();
    if (this->cpid == 0) {
        dup2(write_pipe[read_end], STDIN_FILENO);
        dup2(read_pipe[write_end], STDOUT_FILENO);
        close(read_pipe[read_end]);
        close(write_pipe[write_end]);
        close(read_pipe[write_end]);
        close(write_pipe[read_end]);
        prctl(PR_SET_PDEATHSIG, SIGTERM);
        char * params[args.size()];
        const char * image_path = args[0].c_str();
        for(int i = 1; i < args.size(); i++) {
            params[i-1] = const_cast<char *>(args[i].c_str());
        }
        params[args.size()] = nullptr;
        execv(image_path, params);
        exit(1);
    }

    close(read_pipe[write_end]);
    close(write_pipe[read_end]);

    char buff[256];
    int rd_sz = -1;
    int flags = fcntl(read_pipe[0], F_GETFL, 0);
    fcntl(read_pipe[read_end], F_SETFL, flags | O_NONBLOCK);
    int status = 0;
    waitpid(this->cpid, &status, 0);
    this->has_executed = false;
    int error_code = 0;
    while((rd_sz = read(read_pipe[read_end], buff, sizeof(buff))) > 0) {
        buff[rd_sz] = '\0';
        this->out_stream << std::string{buff};
    }
    close(read_pipe[read_end]);
    return this->out_stream.str();
}


std::string exec_cmd_t::pipe_cmd() {
    static std::string empty_str{};
    return pipe_cmd(empty_str);
}

std::string exec_cmd_t::operator()() {
    return pipe_cmd();
}

exec_cmd_t::~exec_cmd_t() {
    if (this->has_executed) {
        int status;
        waitpid(this->cpid, &status, WNOHANG);
        if (!WIFEXITED(status)) {
            kill(this->cpid, SIGKILL);
            waitpid(this->cpid, &status, 0);
        }
    }
}

std::string operator | (exec_cmd_t & first, exec_cmd_t & second) {
    return second.pipe_cmd(first());
}

std::string operator | (exec_cmd_t && first, exec_cmd_t && second) {
    return second.pipe_cmd(first());
}

std::string operator | (std::string output, exec_cmd_t & second) {
    return second.pipe_cmd(output);
}

std::string operator | (std::string output, exec_cmd_t && second) {
    return second.pipe_cmd(output);
}

int main() {
    auto str = exec_cmd_t{ {"/bin/echo", "echo", "hello\nworld\nor\nnot"} } | exec_cmd_t{ {"/bin/grep", "grep", "world", "-"} };
    std::cout << str << std::endl;
    return 0;
}

дает мне

grep: =V: No such file or directory                                                                                                                                                   
(standard input):world 

Кажетсянапример, grep выполняется дважды: один завершается с ошибкой без такого файла или каталога, а другой - с успехом.Любое предложение будет очень полезно :-).Заранее спасибо.

1 Ответ

1 голос
/ 25 сентября 2019

У вас на востоке есть одна причина для неопределенного поведения, которое может заставить вашу программу делать то, что она делает.Вы объявляете и используете VLA вне диапазона, как это:

char* params[args.size()];
...
params[args.size()] = nullptr;
execv(image_path, params);

Это оставляет окончание char* в вашем params неинициализированным, так что оно может указывать куда угодно.grep считает, что оно указывает на имя файла, пытается открыть его и не удается.

Поскольку VLA: s не соответствует стандарту C ++, рассмотрите возможность его изменения на:

std::vector<char*> params(args.size());
...
params[args.size() - 1] = nullptr;
execv(image_path, params.data());

Другоепричина для беспокойства заключается в том, что вы используете int s там, где вы должны были использовать ssize_t s, даже если очень маловероятно, что вы прочитали или написали больше, чем int мог бы обработать.

После того, как я сделалте изменения, это начало работать и напечатало ожидаемое world.Я даже добавил третью команду, чтобы убедиться, что она справится.Предлагаемые изменения:


14,15c14,15
<     exec_cmd_t(std::vector<std::string> args) :
<         args(args), has_executed(false), cpid(-1) {}
---
>     exec_cmd_t(std::vector<std::string> Args) :
>         args(Args), has_executed(false), cpid(-1), in_stream{}, out_stream{} {}
59c59
<         int wr_sz = write(write_pipe[write_end], line.c_str(), line.size());
---
>         ssize_t wr_sz = write(write_pipe[write_end], line.c_str(), line.size());
76c76
<         char* params[args.size()];
---
>         std::vector<char*> params(args.size());
78c78
<         for(int i = 1; i < args.size(); i++) {
---
>         for(decltype(args.size()) i = 1; i < args.size(); i++) {
81,82c81,82
<         params[args.size()] = nullptr;
<         execv(image_path, params);
---
>         params[args.size() - 1] = nullptr;
>         execv(image_path, params.data());
90c90
<     int rd_sz = -1;
---
>     ssize_t rd_sz = -1;
96c96
<     int error_code = 0;
---
>     // int error_code = 0; // unused
106,107c106
<     static std::string empty_str{};
<     return pipe_cmd(empty_str);
---
>     return pipe_cmd({});
143c142,143
<                exec_cmd_t{{"/bin/grep", "grep", "world", "-"}};
---
>                exec_cmd_t{{"/bin/grep", "grep", "-A1", "hello"}} |
>                exec_cmd_t{{"/bin/grep", "grep", "world"}};

Я также понял, что ваша программа действует как прокси между переданными командами, считывая все из одной команды и записывая ее в следующую.

Вы можете запустить все программы одновременно и настроить каналы между запущенными программами за один раз.Для трех команд вам понадобятся три канала:

                     cmd1  cmd2  cmd3
                     |  w--r  w--r  |
                 stdin              read output into program
or fed by your program

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

...