0

I'd like to write a function that would take a command array, one input stream and two output streams and execute that command using the given streams as input, output and error output respectively. (Returns the return value of the command or -1). This should be implemented using unix system calls.

int exec_with_streams (const std::vector<std::string>& args, std::istream& in, std::ostream& out, std::ostream& err)

Question: how can I implement it?

As for the fork, exec and the outputs it is more or less obvious what to do but the problem comes with the input since we don't know when and how much the process wants to read. So here is a not-working example, where we need to adjust the reading part. (The code is adapted from https://github.com/ericcurtin/execxx/blob/master/execxx.hpp)

 int exec_with_streams (const std::vector<std::string>& args, 
                              std::istream& in, 
                              std::ostream& out, 
                              std::ostream& err)
{
  int in_fds[2];
  pipe(in_fds);

  int out_fds[2];
  pipe(out_fds);

  int err_fds[2];
  pipe(err_fds);

  const pid_t pid = fork();
  if (pid == 0) {
    close(in_fds[1]);
    close(out_fds[0]);
    close(err_fds[0]);

    dup2(in_fds[0], 0);
    dup2(out_fds[1], 1);
    dup2(err_fds[1], 2);

    std::vector<char*> vc(args.size() + 1, nullptr);
    for (size_t i = 0; i < args.size(); ++i) {
      vc[i] = const_cast<char*>(args[i].c_str());
    }

    int ret = execvp(vc[0], &vc[0]);
    exit(ret);
  }

  
  close(in_fds[0]);
  close(out_fds[1]);
  close(err_fds[1]);
  
  const int buf_size = 4096;
  char buffer[buf_size];
  char inbuf[buf_size];
  
  ssize_t in_b = 0;
  ssize_t out_b = 0;
  ssize_t err_b = 0;
  do {
    out_b = read(out_fds[0], buffer, buf_size);
    if (out_b > 0) {
      out.write(buffer, out_b);
    }
    
    err_b = read(err_fds[0], buffer, buf_size);
    if (err_b > 0) {
      out.write(buffer, err_b);
    }
    
    in_b = in.read(inbuf, buf_size).gcount();
    if (in_b > 0) {
      in_b = write(in_fds[1], inbuf, in_b);
    }
  } while (in_b != 0 || out_b != 0 || err_b !=0);

  close(in_fds[1]);
  close(out_fds[0]);
  close(err_fds[0]);

  int r, status;
  do {
    r = waitpid(pid, &status, 0);
  } while (r == -1);

  return status;
}

Thanks a lot in advance

Update - some more clarification: I need a way to check if the child process created by fork() is paused and waits for input. If so the parent could in turn extract characters from the input stream and feed it into the child via the pipe.

  • Does this answer your question? [Is it possible to use a C++ stream class to buffer reads from a pipe?](https://stackoverflow.com/questions/12114156/is-it-possible-to-use-a-c-stream-class-to-buffer-reads-from-a-pipe) – Sorin Jan 10 '22 at 21:52
  • @Sorin Possibly, but I don't see how, since that question focuses on how to implement such a class. However I ask: if you give me an istream, how do I know when and how much to pull from it. – Johnie Walker Jan 11 '22 at 07:50
  • Then maybe this is what you are looking for: https://stackoverflow.com/questions/1704791/is-my-process-waiting-for-input – Sorin Jan 12 '22 at 09:02
  • Yes indeed this would be the solution, but I'm not sure how to implement it in c++ using unix system calls (and the standard library). Also that question/answer focuses on windows, but I would like to have a POSIX or portable solution. As far as I understand it, windows processes and threads don't exactly translate to processes and threads in POSIX systems. Anyway tanks for pointing out. – Johnie Walker Jan 12 '22 at 13:20

0 Answers0