Subprocess: Close all unused file descriptors.

This patch extends Subprocess::ExecFlags() method with a list of
file descriptors that should be kept open on the child process and
mapped to a pipe() in the parent. The remaining file descriptors
will be closed.

Bug: 27880754
TEST=Added unittests for this functionality.

Change-Id: Id96fb6a72f7805a0bfacfbc85422dc7d44750d93
diff --git a/common/subprocess.cc b/common/subprocess.cc
index 61f2d54..1bfb911 100644
--- a/common/subprocess.cc
+++ b/common/subprocess.cc
@@ -71,6 +71,7 @@
 // Process.
 bool LaunchProcess(const vector<string>& cmd,
                    uint32_t flags,
+                   const vector<int>& output_pipes,
                    brillo::Process* proc) {
   for (const string& arg : cmd)
     proc->AddArg(arg);
@@ -84,6 +85,10 @@
       env.emplace(key, value);
   }
 
+  for (const int fd : output_pipes) {
+    proc->RedirectUsingPipe(fd, false);
+  }
+  proc->SetCloseUnusedFileDescriptors(true);
   proc->RedirectUsingPipe(STDOUT_FILENO, false);
   proc->SetPreExecCallback(base::Bind(&SetupChild, env, flags));
 
@@ -143,10 +148,6 @@
   MessageLoop::current()->CancelTask(record->stdout_task_id);
   record->stdout_task_id = MessageLoop::kTaskIdNull;
 
-  // Release and close all the pipes now.
-  record->proc.Release();
-  record->proc.Reset(0);
-
   // Don't print any log if the subprocess exited with exit code 0.
   if (info.si_code != CLD_EXITED) {
     LOG(INFO) << "Subprocess terminated with si_code " << info.si_code;
@@ -160,20 +161,28 @@
   if (!record->callback.is_null()) {
     record->callback.Run(info.si_status, record->stdout);
   }
+  // Release and close all the pipes after calling the callback so our
+  // redirected pipes are still alive. Releasing the process first makes
+  // Reset(0) not attempt to kill the process, which is already a zombie at this
+  // point.
+  record->proc.Release();
+  record->proc.Reset(0);
+
   subprocess_records_.erase(pid_record);
 }
 
 pid_t Subprocess::Exec(const vector<string>& cmd,
                        const ExecCallback& callback) {
-  return ExecFlags(cmd, kRedirectStderrToStdout, callback);
+  return ExecFlags(cmd, kRedirectStderrToStdout, {}, callback);
 }
 
 pid_t Subprocess::ExecFlags(const vector<string>& cmd,
                             uint32_t flags,
+                            const vector<int>& output_pipes,
                             const ExecCallback& callback) {
   unique_ptr<SubprocessRecord> record(new SubprocessRecord(callback));
 
-  if (!LaunchProcess(cmd, flags, &record->proc)) {
+  if (!LaunchProcess(cmd, flags, output_pipes, &record->proc)) {
     LOG(ERROR) << "Failed to launch subprocess";
     return 0;
   }
@@ -215,6 +224,13 @@
   pid_record->second->proc.Release();
 }
 
+int Subprocess::GetPipeFd(pid_t pid, int fd) const {
+  auto pid_record = subprocess_records_.find(pid);
+  if (pid_record == subprocess_records_.end())
+    return -1;
+  return pid_record->second->proc.GetPipe(fd);
+}
+
 bool Subprocess::SynchronousExec(const vector<string>& cmd,
                                  int* return_code,
                                  string* stdout) {
@@ -232,7 +248,10 @@
                                       int* return_code,
                                       string* stdout) {
   brillo::ProcessImpl proc;
-  if (!LaunchProcess(cmd, flags, &proc)) {
+  // It doesn't make sense to redirect some pipes in the synchronous case
+  // because we won't be reading on our end, so we don't expose the output_pipes
+  // in this case.
+  if (!LaunchProcess(cmd, flags, {}, &proc)) {
     LOG(ERROR) << "Failed to launch subprocess";
     return false;
   }