在孩子死后,如何防止父进程丢失控制台输入?

时间:2017-07-01 10:34:38

标签: c++ input terminal pipe fork

我正在尝试围绕交互式程序创建一个包装器。为此,我使用了pipedup2poll的组合。一切似乎都顺利,直到孩子终止。在此步骤中,父进程似乎丢失了stdin,这是我似乎无法理解的原因。

以下是代码:

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <fcntl.h>
#include <signal.h>

#include <vector>
#include <string>
#include <iostream>

struct SystemFunctionFailure
{
    std::string what;
    SystemFunctionFailure(std::string const& what) : what(what) {}
};

template<typename T,size_t N> constexpr size_t countof(const T(&)[N]) { return N; }

void readAndPrint(std::string const& what, int fd)
{
    std::cerr << "Reading "+what+"\n";
    std::vector<char> buffer(1024);
    const auto bytesRead=read(fd,buffer.data(),buffer.size());
    if(bytesRead==-1)
    {
        if(errno!=EAGAIN)
            throw SystemFunctionFailure("read "+what);
    }
    else if(bytesRead==0)
    {
        std::cerr << "EOF reached on "+what+"\n";
        exit(0);
    }
    else
        std::cerr << "CONTENTS OF "+what+": "+std::string(buffer.data(),buffer.size())+"\n";
}

int main()
{
    try
    {
        int pipeChildOut[2];
        if(pipe(pipeChildOut)==-1) throw SystemFunctionFailure("pipe for child stdout");

        int pipeChildErr[2];
        if(pipe(pipeChildErr)==-1) throw SystemFunctionFailure("pipe for child stderr");

        int pipeChildIn[2];
        if(pipe(pipeChildIn)==-1) throw SystemFunctionFailure("pipe for child stdin");

        const auto child=fork();
        if(child==-1) throw SystemFunctionFailure("fork");

        if(child)
        {
            dup2(pipeChildOut[1],STDOUT_FILENO);
            close(pipeChildOut[0]);
            dup2(pipeChildErr[1],STDERR_FILENO);
            close(pipeChildErr[0]);
            dup2(pipeChildIn[0],STDIN_FILENO);
            close(pipeChildIn[1]);
            execlp("sh","sh","-c","sleep 1; echo Test ; sleep 1; echo Child is exiting... >&2",nullptr);
            throw SystemFunctionFailure("execlp returned");
        }
        else
        {
            const int childStdErr=pipeChildErr[0];
            const int childStdOut=pipeChildOut[0];
            dup2(pipeChildIn[1],STDOUT_FILENO);
            fcntl(childStdErr,F_SETFL,O_NONBLOCK);
            fcntl(childStdOut,F_SETFL,O_NONBLOCK);
            fcntl(STDIN_FILENO,F_SETFL,O_NONBLOCK);

            while(true)
            {
                std::cerr << "New iteration of IO loop\n";
                pollfd pollfds[]={ // making the indices coincide with .._FILENO
                                  {STDIN_FILENO,POLLIN},
                                  {childStdOut,POLLIN},
                                  {childStdErr,POLLIN},
                                 };
                if(poll(pollfds,countof(pollfds),{-1})==-1)
                    throw SystemFunctionFailure("poll");
                std::cerr << "poll returned\n";

                for(unsigned i=0;i<countof(pollfds);++i)
                    std::cerr <<" pollfds["<<i<<"].revents: " << pollfds[i].revents << "\n";

                if(pollfds[ STDIN_FILENO].revents&POLLIN) readAndPrint("stdin" ,pollfds[ STDIN_FILENO].fd);
                if(pollfds[STDOUT_FILENO].revents&POLLIN) readAndPrint("stdout",pollfds[STDOUT_FILENO].fd);
                if(pollfds[STDERR_FILENO].revents&POLLIN) readAndPrint("stderr",pollfds[STDERR_FILENO].fd);
            }
        }
    }
    catch(SystemFunctionFailure& ex)
    {
        perror(ex.what.c_str());
        exit(EXIT_FAILURE);
    }
}

此处,子项通过stdin隐式关闭​​其原始dup2,因此它似乎不会影响父级对控制台输入的任何访问。但出于某种原因,我得到的是输出:

$ g++ test.cpp -o test -std=c++14 && ./test
New iteration of IO loop
poll returned
 pollfds[0].revents: 0
 pollfds[1].revents: 1
 pollfds[2].revents: 0
Reading stdout
CONTENTS OF stdout: Test

New iteration of IO loop
poll returned
 pollfds[0].revents: 0
 pollfds[1].revents: 0
 pollfds[2].revents: 1
Reading stderr
CONTENTS OF stderr: Child is exiting...

New iteration of IO loop
$

即。我得到shell提示符,因此父级不再位于前台。在此之后,如果我等待几秒钟并输入一个字母,我会得到这个输出:

poll returned
 pollfds[0].revents: 1
 pollfds[1].revents: 0
 pollfds[2].revents: 0
Reading stdin
read stdin: Input/output error

我想至少让父进程在子进程死后保留对其控制台输入的访问权限。阅读an answer to another question之后,我认为我的问题是相关的,但答案并没有回答我的问题:“如何做到这一点?”。

1 个答案:

答案 0 :(得分:1)

我认为if(child)应为if(child == 0)

来自https://linux.die.net/man/2/fork

  

成功时,子进程的PID在父进程中返回,   并在孩子中返回 0。失败时,返回-1   parent,没有创建子进程,并且正确设置了errno。