Change ExecUtils to support callbacks.

artd needs the callbacks to get the pid in order to kill the subprocess.

Partially cherry-picked from
commit 659f49bdb1263ceb26f666052f3ef7e4732b4eb2

Bug: 244412198
Test: m test-art-host-gtest-art_runtime_tests
Merged-In: I8c40949b5ed88ff85ddedad9d86f0b9bbfddb98d
Change-Id: Ia28410bdf6314178756ee5e3a94000dc6f3ecc97
diff --git a/runtime/exec_utils.cc b/runtime/exec_utils.cc
index cc4667c..929f09f 100644
--- a/runtime/exec_utils.cc
+++ b/runtime/exec_utils.cc
@@ -250,11 +250,13 @@
                                  int timeout_sec,
                                  bool* timed_out,
                                  std::string* error_msg) const {
-  return ExecAndReturnCode(arg_vector, timeout_sec, timed_out, /*stat=*/nullptr, error_msg);
+  return ExecAndReturnCode(
+      arg_vector, timeout_sec, ExecCallbacks(), timed_out, /*stat=*/nullptr, error_msg);
 }
 
 int ExecUtils::ExecAndReturnCode(const std::vector<std::string>& arg_vector,
                                  int timeout_sec,
+                                 const ExecCallbacks& callbacks,
                                  /*out*/ bool* timed_out,
                                  /*out*/ ProcessStat* stat,
                                  /*out*/ std::string* error_msg) const {
@@ -271,6 +273,8 @@
     return -1;
   }
 
+  callbacks.on_start(pid);
+
   // Wait for subprocess to finish.
   int status;
   if (timeout_sec >= 0) {
@@ -295,6 +299,8 @@
     }
   }
 
+  callbacks.on_end(pid);
+
   std::string local_error_msg;
   // TODO(jiakaiz): Use better logic to detect waitid failure.
   if (WaitChild(pid, arg_vector, /*no_wait=*/false, &local_error_msg) < 0 &&
diff --git a/runtime/exec_utils.h b/runtime/exec_utils.h
index 751c64d..aa0d0fc 100644
--- a/runtime/exec_utils.h
+++ b/runtime/exec_utils.h
@@ -19,6 +19,7 @@
 
 #include <time.h>
 
+#include <functional>
 #include <string>
 #include <vector>
 
@@ -34,6 +35,14 @@
   int cpu_time_ms = 0;
 };
 
+struct ExecCallbacks {
+  // Called in the parent process as soon as the child process is forked.
+  std::function<void(pid_t pid)> on_start = [](pid_t) {};
+  // Called in the parent process after the child process exits while still in a waitable state, no
+  // matter the child process succeeds or not.
+  std::function<void(pid_t pid)> on_end = [](pid_t) {};
+};
+
 // Wrapper on fork/execv to run a command in a subprocess.
 // These spawn child processes using the environment as it was set when the single instance
 // of the runtime (Runtime::Current()) was started.  If no instance of the runtime was started, it
@@ -57,10 +66,11 @@
                                 /*out*/ bool* timed_out,
                                 /*out*/ std::string* error_msg) const;
 
-  // Same as above, but also collects stat of the process. The stat is collected no matter the child
-  // process succeeds or not.
+  // Same as above, but also collects stat of the process and calls callbacks. The stat is collected
+  // no matter the child process succeeds or not.
   virtual int ExecAndReturnCode(const std::vector<std::string>& arg_vector,
                                 int timeout_sec,
+                                const ExecCallbacks& callbacks,
                                 /*out*/ bool* timed_out,
                                 /*out*/ ProcessStat* stat,
                                 /*out*/ std::string* error_msg) const;
diff --git a/runtime/exec_utils_test.cc b/runtime/exec_utils_test.cc
index c09bf40..e9bbd8f 100644
--- a/runtime/exec_utils_test.cc
+++ b/runtime/exec_utils_test.cc
@@ -34,7 +34,12 @@
 namespace art {
 
 using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Gt;
 using ::testing::HasSubstr;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Ne;
 using ::testing::Return;
 
 std::string PrettyArguments(const char* signature);
@@ -201,8 +206,9 @@
   EXPECT_CALL(*exec_utils_, GetUptimeMs()).WillOnce(Return(1620344887ll));
   EXPECT_CALL(*exec_utils_, GetTicksPerSec()).WillOnce(Return(100));
 
-  ASSERT_EQ(
-      exec_utils_->ExecAndReturnCode(command, /*timeout_sec=*/-1, &timed_out, &stat, &error_msg), 0)
+  ASSERT_EQ(exec_utils_->ExecAndReturnCode(
+                command, /*timeout_sec=*/-1, ExecCallbacks(), &timed_out, &stat, &error_msg),
+            0)
       << error_msg;
 
   EXPECT_EQ(stat.cpu_time_ms, 990);
@@ -224,12 +230,40 @@
   EXPECT_CALL(*exec_utils_, GetTicksPerSec()).WillOnce(Return(100));
 
   // This will always time out.
-  exec_utils_->ExecAndReturnCode(command, /*timeout_sec=*/1, &timed_out, &stat, &error_msg);
+  exec_utils_->ExecAndReturnCode(
+      command, /*timeout_sec=*/1, ExecCallbacks(), &timed_out, &stat, &error_msg);
 
   EXPECT_EQ(stat.cpu_time_ms, 990);
   EXPECT_EQ(stat.wall_time_ms, 1007);
 }
 
+TEST_P(ExecUtilsTest, ExecCallbacks) {
+  MockFunction<void(pid_t)> on_start;
+  MockFunction<void(pid_t)> on_end;
+
+  {
+    InSequence s;
+    EXPECT_CALL(on_start, Call(AllOf(Gt(0), Ne(getpid()))));
+    EXPECT_CALL(on_end, Call(AllOf(Gt(0), Ne(getpid()))));
+  }
+
+  std::vector<std::string> command;
+  command.push_back(GetBin("id"));
+
+  std::string error_msg;
+  bool timed_out;
+
+  exec_utils_->ExecAndReturnCode(command,
+                                 /*timeout_sec=*/-1,
+                                 ExecCallbacks{
+                                     .on_start = on_start.AsStdFunction(),
+                                     .on_end = on_end.AsStdFunction(),
+                                 },
+                                 &timed_out,
+                                 /*stat=*/nullptr,
+                                 &error_msg);
+}
+
 INSTANTIATE_TEST_SUITE_P(AlwaysOrNeverFallback, ExecUtilsTest, testing::Values(true, false));
 
 }  // namespace art