YZY
YZY

Reputation: 57

`boost::process::child` will hang if the result of the command is long

I have a simple C++ program which use boost::process:child to receive a terminal command and execute it, and then print out the result. But then if I execute a command like ps aux, the whole program will just no responding. I'm not sure how to fix. I've tried to debug it, and the debugger will just stop at cp.wait().

try{
        child cp(cmd+" "+param, (std_out & std_err) > pipe);
        cp.wait();
        return_code = cp.exit_code();
}catch (process_error e){
        return_code = -1;
}

Upvotes: 1

Views: 895

Answers (1)

sehe
sehe

Reputation: 393507

You need to consume the pipe. What is the pipe connected to (in other words, who is consuming it?).

If, as seems to be the case, you're not interested in the output at all, simply redirect to the null device:

As a side note, I'd strongly discourage using namespace boost::process as it looks like you're doing. There are many names in that namespace that are very prone to clash with C/standard library names. For example, is pipe a local variable? Is it the type boost::process::pipe? Is it the POSIX system library call ::pipe?

Fix

Live On Coliru

#include <boost/process.hpp>
#include <iostream>

int main()
{
    namespace bp    = boost::process;
    std::string cmd = "cat", param = "/etc/dictionaries-common/words";
    //bp::pipe pipe;

    int return_code = -1;
    try {
        bp::child cp(cmd + " " + param, (bp::std_out & bp::std_err) > bp::null);
        cp.wait();
        return_code = cp.exit_code();
    } catch (bp::process_error const& e) {
        return_code = -1;
    }

    std::cout << "return_code: " << return_code << "\n";
    return return_code;
}

Note that it returns 1 on Coliru because that dictionary doesn't exist. On my system it correctly returns 0 (without blocking.

Security

Note that piecing together commands as strings can be very error-prone and invites security vulnerability (what if the filename passed in is "foot.txt; rm -rf *"?).

I'd usggest using the argument vector style:

    std::string              cmd = "cat";
    std::vector<std::string> params{"-n", "--",
                                    "/etc/dictionaries-common/words"};

    bp::child cp(bp::search_path(cmd), params,
                 (bp::std_out & bp::std_err) > bp::null);

Even safer is not using search_path but it requires the exe to be a fully specified path (like /bin/cat or ./bin/myexe).

Note that now it's safe enough to accept file names from external input:

    std::vector<std::string> params{"-n", "--"};
    params.insert(end(params), argv + 1, argv + argc);

As a finishing touch, let's close stdin for the child process (so we get no privacy leak there and also no hang if there are no files listed on the command line...):

Live On Coliru

#include <boost/process.hpp>
#include <iostream>

int main(int argc, char** argv)
{
    namespace bp    = boost::process;
    int return_code = -1;

    try {
        std::string              cmd = "/bin/cat";
        std::vector<std::string> params{"-n", "--"};
        params.insert(end(params), argv + 1, argv + argc);

        bp::child cp(cmd, params,        //
                     bp::std_in.close(), //
                     (bp::std_out & bp::std_err) > bp::null);

        cp.wait();
        return_code = cp.exit_code();
    } catch (bp::process_error const& e) {
        return_code = -1;
    }

    std::cout << "return_code: " << return_code << "\n";
    return return_code;
}

Now it prints return_code: 0 because the files actually exist on Coliru.

Upvotes: 2

Related Questions