binder: Add execute() to host utils.

Add utility for executing commands.

This function assumes that, when a given predicate
|end| finishes, the child process does not emit any other
messages.

If this is not the case, caller to execute()
must handle these I/O in the pipes in the returned
CommandResult object. Otherwise the child program may
hang on I/O.

Test: binderUtilsTest
Bug: 190233850

Change-Id: Ib8be5db140af04a286ccbb2283cb032390aff5ac
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index b9c8d20..467b0e6 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -146,6 +146,14 @@
         darwin: {
             enabled: false,
         },
+        host: {
+            static_libs: [
+                "libbase",
+            ],
+            srcs: [
+                "UtilsHost.cpp",
+            ],
+        },
     },
 
     aidl: {
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index b58d919..59f0ba6 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -31,6 +31,9 @@
       "name": "binderStabilityTest"
     },
     {
+      "name": "binderUtilsTest"
+    },
+    {
       "name": "libbinder_ndk_unit_test"
     },
     {
diff --git a/libs/binder/UtilsHost.cpp b/libs/binder/UtilsHost.cpp
new file mode 100644
index 0000000..e524dab
--- /dev/null
+++ b/libs/binder/UtilsHost.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "UtilsHost.h"
+
+#include <poll.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <sstream>
+
+#include <log/log.h>
+
+namespace android {
+
+CommandResult::~CommandResult() {
+    if (!pid.has_value()) return;
+    if (*pid == 0) {
+        ALOGW("%s: PID is unexpectedly 0, won't kill it", __PRETTY_FUNCTION__);
+        return;
+    }
+
+    ALOGE_IF(kill(*pid, SIGKILL) != 0, "kill(%d): %s", *pid, strerror(errno));
+
+    while (pid.has_value()) {
+        int status;
+        LOG_HOST("%s: Waiting for PID %d to exit.", __PRETTY_FUNCTION__, *pid);
+        int waitres = waitpid(*pid, &status, 0);
+        if (waitres == -1) {
+            ALOGE("%s: waitpid(%d): %s", __PRETTY_FUNCTION__, *pid, strerror(errno));
+            break;
+        }
+        if (WIFEXITED(status)) {
+            LOG_HOST("%s: PID %d exited.", __PRETTY_FUNCTION__, *pid);
+            pid.reset();
+        } else if (WIFSIGNALED(status)) {
+            LOG_HOST("%s: PID %d terminated by signal %d.", __PRETTY_FUNCTION__, *pid,
+                     WTERMSIG(status));
+            pid.reset();
+        } else if (WIFSTOPPED(status)) {
+            ALOGW("%s: pid %d stopped", __PRETTY_FUNCTION__, *pid);
+        } else if (WIFCONTINUED(status)) {
+            ALOGW("%s: pid %d continued", __PRETTY_FUNCTION__, *pid);
+        }
+    }
+}
+
+std::ostream& operator<<(std::ostream& os, const CommandResult& res) {
+    if (res.exitCode) os << "code=" << *res.exitCode;
+    if (res.signal) os << "signal=" << *res.signal;
+    if (res.pid) os << ", pid=" << *res.pid;
+    return os << ", stdout=" << res.stdout << ", stderr=" << res.stderr;
+}
+
+std::string CommandResult::toString() const {
+    std::stringstream ss;
+    ss << (*this);
+    return ss.str();
+}
+
+android::base::Result<CommandResult> execute(std::vector<std::string> argStringVec,
+                                             const std::function<bool(const CommandResult&)>& end) {
+    // turn vector<string> into null-terminated char* vector.
+    std::vector<char*> argv;
+    argv.reserve(argStringVec.size() + 1);
+    for (auto& arg : argStringVec) argv.push_back(arg.data());
+    argv.push_back(nullptr);
+
+    CommandResult ret;
+    android::base::unique_fd outWrite;
+    if (!android::base::Pipe(&ret.outPipe, &outWrite))
+        return android::base::ErrnoError() << "pipe() for outPipe";
+    android::base::unique_fd errWrite;
+    if (!android::base::Pipe(&ret.errPipe, &errWrite))
+        return android::base::ErrnoError() << "pipe() for errPipe";
+
+    int pid = fork();
+    if (pid == -1) return android::base::ErrnoError() << "fork()";
+    if (pid == 0) {
+        // child
+        ret.outPipe.reset();
+        ret.errPipe.reset();
+
+        int res = TEMP_FAILURE_RETRY(dup2(outWrite.get(), STDOUT_FILENO));
+        LOG_ALWAYS_FATAL_IF(-1 == res, "dup2(outPipe): %s", strerror(errno));
+        outWrite.reset();
+
+        res = TEMP_FAILURE_RETRY(dup2(errWrite.get(), STDERR_FILENO));
+        LOG_ALWAYS_FATAL_IF(-1 == res, "dup2(errPipe): %s", strerror(errno));
+        errWrite.reset();
+
+        execvp(argv[0], argv.data());
+        LOG_ALWAYS_FATAL("execvp() returns");
+    }
+    // parent
+    outWrite.reset();
+    errWrite.reset();
+    ret.pid = pid;
+
+    auto handlePoll = [](android::base::unique_fd* fd, const pollfd& pfd, std::string* s) {
+        if (!fd->ok()) return true;
+        if (pfd.revents & POLLIN) {
+            char buf[1024];
+            ssize_t n = TEMP_FAILURE_RETRY(read(fd->get(), buf, sizeof(buf)));
+            if (n < 0) return false;
+            if (n > 0) *s += std::string_view(buf, n);
+        }
+        if (pfd.revents & POLLHUP) {
+            fd->reset();
+        }
+        return true;
+    };
+
+    // Drain both stdout and stderr. Check end() regularly until both are closed.
+    while (ret.outPipe.ok() || ret.errPipe.ok()) {
+        pollfd fds[2];
+        pollfd *outPollFd = nullptr, *errPollFd = nullptr;
+        memset(fds, 0, sizeof(fds));
+        nfds_t nfds = 0;
+        if (ret.outPipe.ok()) {
+            outPollFd = &fds[nfds++];
+            *outPollFd = {.fd = ret.outPipe.get(), .events = POLLIN};
+        }
+        if (ret.errPipe.ok()) {
+            errPollFd = &fds[nfds++];
+            *errPollFd = {.fd = ret.errPipe.get(), .events = POLLIN};
+        }
+        int pollRet = poll(fds, nfds, 1000 /* ms timeout */);
+        if (pollRet == -1) return android::base::ErrnoError() << "poll()";
+
+        if (!handlePoll(&ret.outPipe, *outPollFd, &ret.stdout))
+            return android::base::ErrnoError() << "read(stdout)";
+        if (!handlePoll(&ret.errPipe, *errPollFd, &ret.stderr))
+            return android::base::ErrnoError() << "read(stderr)";
+
+        if (end && end(ret)) return ret;
+    }
+
+    // If both stdout and stderr are closed by the subprocess, it may or may not be terminated.
+    while (ret.pid.has_value()) {
+        int status;
+        auto exitPid = waitpid(pid, &status, 0);
+        if (exitPid == -1) return android::base::ErrnoError() << "waitpid(" << pid << ")";
+        if (exitPid == pid) {
+            if (WIFEXITED(status)) {
+                ret.pid = std::nullopt;
+                ret.exitCode = WEXITSTATUS(status);
+            } else if (WIFSIGNALED(status)) {
+                ret.pid = std::nullopt;
+                ret.signal = WTERMSIG(status);
+            } else if (WIFSTOPPED(status)) {
+                ALOGW("%s: pid %d stopped", __PRETTY_FUNCTION__, *ret.pid);
+            } else if (WIFCONTINUED(status)) {
+                ALOGW("%s: pid %d continued", __PRETTY_FUNCTION__, *ret.pid);
+            }
+        }
+        // ret is not changed unless the process is terminated (where pid == nullopt). Hence there
+        // is no need to check the predicate `end(ret)`.
+    }
+
+    return ret;
+}
+} // namespace android
diff --git a/libs/binder/UtilsHost.h b/libs/binder/UtilsHost.h
new file mode 100644
index 0000000..0f29f60
--- /dev/null
+++ b/libs/binder/UtilsHost.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+#include <ostream>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include <android-base/macros.h>
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+/**
+ * Log a lot more information about host-device binder communication, when debugging issues.
+ */
+#define SHOULD_LOG_HOST false
+
+#if SHOULD_LOG_HOST
+#define LOG_HOST(...) ALOGI(__VA_ARGS__)
+#else
+#define LOG_HOST(...) ALOGV(__VA_ARGS__) // for type checking
+#endif
+
+namespace android {
+
+struct CommandResult {
+    std::optional<int32_t> exitCode;
+    std::optional<int32_t> signal;
+    std::optional<pid_t> pid;
+    std::string stdout;
+    std::string stderr;
+
+    android::base::unique_fd outPipe;
+    android::base::unique_fd errPipe;
+
+    CommandResult() = default;
+    CommandResult(CommandResult&& other) noexcept { (*this) = std::move(other); }
+    CommandResult& operator=(CommandResult&& other) noexcept {
+        std::swap(exitCode, other.exitCode);
+        std::swap(signal, other.signal);
+        std::swap(pid, other.pid);
+        std::swap(stdout, other.stdout);
+        std::swap(stderr, other.stderr);
+        return *this;
+    }
+    ~CommandResult();
+    [[nodiscard]] std::string toString() const;
+
+    [[nodiscard]] bool stdoutEndsWithNewLine() const {
+        return !stdout.empty() && stdout.back() == '\n';
+    }
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(CommandResult);
+};
+
+std::ostream& operator<<(std::ostream& os, const CommandResult& res);
+
+// Execute a command using tokens specified in @a argStringVec.
+//
+// @a end is a predicate checked periodically when the command emits any output to stdout or
+// stderr. When it is evaluated to true, the function returns immediately even though
+// the child process has not been terminated. The function also assumes that, after @a end
+// is evaluated to true, the child process does not emit any other messages.
+// If this is not the case, caller to execute() must handle these I/O in the pipes in the returned
+// CommandResult object. Otherwise the child program may hang on I/O.
+//
+// If @a end is nullptr, it is equivalent to a predicate that always returns false. In this
+// case, execute() returns after the child process is terminated.
+//
+// If @a end is evaluated to true, and execute() returns with the child process running,
+// the returned CommandResult has pid, outPipe, and errPipe set. In this case, the caller is
+// responsible for holding the returned CommandResult. When the CommandResult object is destroyed,
+// the child process is killed.
+//
+// On the other hand, execute() returns with the child process terminated, either exitCode or signal
+// is set.
+//
+// If the parent process has encountered any errors for system calls, return ExecuteError with
+// the proper errno set.
+android::base::Result<CommandResult> execute(std::vector<std::string> argStringVec,
+                                             const std::function<bool(const CommandResult&)>& end);
+} // namespace android
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index c7c899f..0aa3139 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -327,3 +327,17 @@
         "libutils",
     ],
 }
+
+cc_test_host {
+    name: "binderUtilsHostTest",
+    defaults: ["binder_test_defaults"],
+    srcs: ["binderUtilsHostTest.cpp"],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+    ],
+    static_libs: [
+        "libgmock",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/libs/binder/tests/binderUtilsHostTest.cpp b/libs/binder/tests/binderUtilsHostTest.cpp
new file mode 100644
index 0000000..fb24836
--- /dev/null
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sysexits.h>
+
+#include <chrono>
+
+#include <android-base/result-gmock.h>
+#include <android-base/strings.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "../UtilsHost.h"
+
+using android::base::testing::Ok;
+using testing::Optional;
+
+namespace android {
+
+TEST(UtilsHost, ExecuteImmediately) {
+    auto result = execute({"echo", "foo"}, nullptr);
+    ASSERT_THAT(result, Ok());
+    EXPECT_THAT(result->exitCode, Optional(EX_OK));
+    EXPECT_EQ(result->stdout, "foo\n");
+}
+
+TEST(UtilsHost, ExecuteLongRunning) {
+    auto now = std::chrono::system_clock::now();
+
+    {
+        std::vector<std::string> args{"sh", "-c",
+                                      "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 1"};
+        auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+            return android::base::EndsWith(commandResult.stdout, "\n");
+        });
+        auto elapsed = std::chrono::system_clock::now() - now;
+        auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+        EXPECT_GE(elapsedMs, 1000);
+        EXPECT_LT(elapsedMs, 2000);
+
+        ASSERT_THAT(result, Ok());
+        EXPECT_EQ(std::nullopt, result->exitCode);
+        EXPECT_EQ(result->stdout, "foo\n");
+    }
+
+    // ~CommandResult() called, child process is killed.
+    // Assert that the second sleep does not finish.
+    auto elapsed = std::chrono::system_clock::now() - now;
+    auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+    EXPECT_LT(elapsedMs, 2000);
+}
+
+TEST(UtilsHost, ExecuteLongRunning2) {
+    auto now = std::chrono::system_clock::now();
+
+    {
+        std::vector<std::string> args{"sh", "-c",
+                                      "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 2"};
+        auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+            return android::base::EndsWith(commandResult.stdout, "\n");
+        });
+        auto elapsed = std::chrono::system_clock::now() - now;
+        auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+        EXPECT_GE(elapsedMs, 4000);
+        EXPECT_LT(elapsedMs, 6000);
+
+        ASSERT_THAT(result, Ok());
+        EXPECT_EQ(std::nullopt, result->exitCode);
+        EXPECT_EQ(result->stdout, "foo\n");
+    }
+
+    // ~CommandResult() called, child process is killed.
+    // Assert that the second sleep does not finish.
+    auto elapsed = std::chrono::system_clock::now() - now;
+    auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+    EXPECT_LT(elapsedMs, 6000);
+}
+
+TEST(UtilsHost, KillWithSigKill) {
+    std::vector<std::string> args{"sh", "-c", "echo foo && sleep 10"};
+    auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+        // FOR TEST PURPOSE ONLY. DON'T DO THIS!
+        if (commandResult.pid.has_value()) {
+            (void)kill(*commandResult.pid, SIGKILL);
+        }
+        // FOR TEST PURPOSE ONLY. DON'T DO THIS!
+        return false;
+    });
+
+    ASSERT_THAT(result, Ok());
+    EXPECT_EQ(std::nullopt, result->exitCode);
+    EXPECT_THAT(result->signal, Optional(SIGKILL));
+}
+
+} // namespace android