diff options
Diffstat (limited to 'runtime/exec_utils.cc')
| -rw-r--r-- | runtime/exec_utils.cc | 347 |
1 files changed, 268 insertions, 79 deletions
diff --git a/runtime/exec_utils.cc b/runtime/exec_utils.cc index 463d4580cf..1021429460 100644 --- a/runtime/exec_utils.cc +++ b/runtime/exec_utils.cc @@ -16,22 +16,46 @@ #include "exec_utils.h" +#include <poll.h> #include <sys/types.h> #include <sys/wait.h> +#include <unistd.h> + +#include <ctime> +#include <string_view> + +#ifdef __BIONIC__ +#include <sys/pidfd.h> +#endif + +#include <chrono> +#include <climits> +#include <condition_variable> +#include <cstdint> +#include <mutex> #include <string> +#include <thread> #include <vector> +#include "android-base/file.h" +#include "android-base/parseint.h" +#include "android-base/scopeguard.h" #include "android-base/stringprintf.h" #include "android-base/strings.h" - +#include "android-base/unique_fd.h" +#include "base/macros.h" +#include "base/utils.h" #include "runtime.h" namespace art { -using android::base::StringPrintf; - namespace { +using ::android::base::ParseInt; +using ::android::base::ReadFileToString; +using ::android::base::StringPrintf; +using ::android::base::unique_fd; + std::string ToCommandLine(const std::vector<std::string>& args) { return android::base::Join(args, ' '); } @@ -40,10 +64,11 @@ std::string ToCommandLine(const std::vector<std::string>& args) { // If there is a runtime (Runtime::Current != nullptr) then the subprocess is created with the // same environment that existed when the runtime was started. // Returns the process id of the child process on success, -1 otherwise. -pid_t ExecWithoutWait(std::vector<std::string>& arg_vector) { +pid_t ExecWithoutWait(const std::vector<std::string>& arg_vector, std::string* error_msg) { // Convert the args to char pointers. const char* program = arg_vector[0].c_str(); std::vector<char*> args; + args.reserve(arg_vector.size() + 1); for (const auto& arg : arg_vector) { args.push_back(const_cast<char*>(arg.c_str())); } @@ -65,110 +90,274 @@ pid_t ExecWithoutWait(std::vector<std::string>& arg_vector) { } else { execve(program, &args[0], envp); } - PLOG(ERROR) << "Failed to execve(" << ToCommandLine(arg_vector) << ")"; - // _exit to avoid atexit handlers in child. - _exit(1); + // This should be regarded as a crash rather than a normal return. + PLOG(FATAL) << "Failed to execute (" << ToCommandLine(arg_vector) << ")"; + UNREACHABLE(); + } else if (pid == -1) { + *error_msg = StringPrintf("Failed to execute (%s) because fork failed: %s", + ToCommandLine(arg_vector).c_str(), + strerror(errno)); + return -1; } else { return pid; } } -} // namespace - -int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg) { - pid_t pid = ExecWithoutWait(arg_vector); - if (pid == -1) { - *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s", - ToCommandLine(arg_vector).c_str(), strerror(errno)); - return -1; +ExecResult WaitChild(pid_t pid, + const std::vector<std::string>& arg_vector, + bool no_wait, + std::string* error_msg) { + siginfo_t info; + // WNOWAIT leaves the child in a waitable state. The call is still blocking. + int options = WEXITED | (no_wait ? WNOWAIT : 0); + if (TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, options)) != 0) { + *error_msg = StringPrintf("waitid failed for (%s) pid %d: %s", + ToCommandLine(arg_vector).c_str(), + pid, + strerror(errno)); + return {.status = ExecResult::kUnknown}; + } + if (info.si_pid != pid) { + *error_msg = StringPrintf("waitid failed for (%s): wanted pid %d, got %d: %s", + ToCommandLine(arg_vector).c_str(), + pid, + info.si_pid, + strerror(errno)); + return {.status = ExecResult::kUnknown}; } + if (info.si_code != CLD_EXITED) { + *error_msg = + StringPrintf("Failed to execute (%s) because the child process is terminated by signal %d", + ToCommandLine(arg_vector).c_str(), + info.si_status); + return {.status = ExecResult::kSignaled, .signal = info.si_status}; + } + return {.status = ExecResult::kExited, .exit_code = info.si_status}; +} - // wait for subprocess to finish - int status = -1; - pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)); - if (got_pid != pid) { - *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: " - "wanted %d, got %d: %s", - ToCommandLine(arg_vector).c_str(), pid, got_pid, strerror(errno)); - return -1; +// A fallback implementation of `WaitChildWithTimeout` that creates a thread to wait instead of +// relying on `pidfd_open`. +ExecResult WaitChildWithTimeoutFallback(pid_t pid, + const std::vector<std::string>& arg_vector, + int timeout_ms, + std::string* error_msg) { + bool child_exited = false; + bool timed_out = false; + std::condition_variable cv; + std::mutex m; + + std::thread wait_thread([&]() { + std::unique_lock<std::mutex> lock(m); + if (!cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [&] { return child_exited; })) { + timed_out = true; + kill(pid, SIGKILL); + } + }); + + ExecResult result = WaitChild(pid, arg_vector, /*no_wait=*/true, error_msg); + + { + std::unique_lock<std::mutex> lock(m); + child_exited = true; } - if (WIFEXITED(status)) { - return WEXITSTATUS(status); + cv.notify_all(); + wait_thread.join(); + + // The timeout error should have a higher priority than any other error. + if (timed_out) { + *error_msg = + StringPrintf("Failed to execute (%s) because the child process timed out after %dms", + ToCommandLine(arg_vector).c_str(), + timeout_ms); + return ExecResult{.status = ExecResult::kTimedOut}; } - return -1; + + return result; } -int ExecAndReturnCode(std::vector<std::string>& arg_vector, - time_t timeout_secs, - bool* timed_out, - std::string* error_msg) { - *timed_out = false; +// Waits for the child process to finish and leaves the child in a waitable state. +ExecResult WaitChildWithTimeout(pid_t pid, + unique_fd pidfd, + const std::vector<std::string>& arg_vector, + int timeout_ms, + std::string* error_msg) { + auto cleanup = android::base::make_scope_guard([&]() { + kill(pid, SIGKILL); + std::string ignored_error_msg; + WaitChild(pid, arg_vector, /*no_wait=*/true, &ignored_error_msg); + }); - // Start subprocess. - pid_t pid = ExecWithoutWait(arg_vector); - if (pid == -1) { - *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s", - ToCommandLine(arg_vector).c_str(), strerror(errno)); - return -1; + struct pollfd pfd; + pfd.fd = pidfd.get(); + pfd.events = POLLIN; + int poll_ret = TEMP_FAILURE_RETRY(poll(&pfd, /*nfds=*/1, timeout_ms)); + + pidfd.reset(); + + if (poll_ret < 0) { + *error_msg = StringPrintf("poll failed for pid %d: %s", pid, strerror(errno)); + return {.status = ExecResult::kUnknown}; + } + if (poll_ret == 0) { + *error_msg = + StringPrintf("Failed to execute (%s) because the child process timed out after %dms", + ToCommandLine(arg_vector).c_str(), + timeout_ms); + return {.status = ExecResult::kTimedOut}; } - // Add SIGCHLD to the signal set. - sigset_t child_mask, original_mask; - sigemptyset(&child_mask); - sigaddset(&child_mask, SIGCHLD); - if (sigprocmask(SIG_BLOCK, &child_mask, &original_mask) == -1) { - *error_msg = StringPrintf("Failed to set sigprocmask(): %s", strerror(errno)); - return -1; + cleanup.Disable(); + return WaitChild(pid, arg_vector, /*no_wait=*/true, error_msg); +} + +bool ParseProcStat(const std::string& stat_content, + int64_t uptime_ms, + int64_t ticks_per_sec, + /*out*/ ProcessStat* stat) { + size_t pos = stat_content.rfind(") "); + if (pos == std::string::npos) { + return false; } + std::vector<std::string> stat_fields; + // Skip the first two fields. The second field is the parenthesized process filename, which can + // contain anything, including spaces. + Split(std::string_view(stat_content).substr(pos + 2), ' ', &stat_fields); + constexpr int kSkippedFields = 2; + int64_t utime, stime, cutime, cstime, starttime; + if (stat_fields.size() < 22 - kSkippedFields || + !ParseInt(stat_fields[13 - kSkippedFields], &utime) || + !ParseInt(stat_fields[14 - kSkippedFields], &stime) || + !ParseInt(stat_fields[15 - kSkippedFields], &cutime) || + !ParseInt(stat_fields[16 - kSkippedFields], &cstime) || + !ParseInt(stat_fields[21 - kSkippedFields], &starttime)) { + return false; + } + stat->cpu_time_ms = (utime + stime + cutime + cstime) * 1000 / ticks_per_sec; + stat->wall_time_ms = uptime_ms - starttime * 1000 / ticks_per_sec; + return true; +} - // Wait for a SIGCHLD notification. - errno = 0; - timespec ts = {timeout_secs, 0}; - int wait_result = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts)); - int wait_errno = errno; +} // namespace - // Restore the original signal set. - if (sigprocmask(SIG_SETMASK, &original_mask, nullptr) == -1) { - *error_msg = StringPrintf("Fail to restore sigprocmask(): %s", strerror(errno)); - if (wait_result == 0) { - return -1; - } +int ExecUtils::ExecAndReturnCode(const std::vector<std::string>& arg_vector, + std::string* error_msg) const { + return ExecAndReturnResult(arg_vector, /*timeout_sec=*/-1, error_msg).exit_code; +} + +ExecResult ExecUtils::ExecAndReturnResult(const std::vector<std::string>& arg_vector, + int timeout_sec, + std::string* error_msg) const { + return ExecAndReturnResult(arg_vector, timeout_sec, ExecCallbacks(), /*stat=*/nullptr, error_msg); +} + +ExecResult ExecUtils::ExecAndReturnResult(const std::vector<std::string>& arg_vector, + int timeout_sec, + const ExecCallbacks& callbacks, + /*out*/ ProcessStat* stat, + /*out*/ std::string* error_msg) const { + if (timeout_sec > INT_MAX / 1000) { + *error_msg = "Timeout too large"; + return {.status = ExecResult::kStartFailed}; + } + + // Start subprocess. + pid_t pid = ExecWithoutWait(arg_vector, error_msg); + if (pid == -1) { + return {.status = ExecResult::kStartFailed}; } - // Having restored the signal set, see if we need to terminate the subprocess. - if (wait_result == -1) { - if (wait_errno == EAGAIN) { - *error_msg = "Timed out."; - *timed_out = true; + callbacks.on_start(pid); + + // Wait for subprocess to finish. + ExecResult result; + if (timeout_sec >= 0) { + unique_fd pidfd = PidfdOpen(pid); + if (pidfd.get() >= 0) { + result = + WaitChildWithTimeout(pid, std::move(pidfd), arg_vector, timeout_sec * 1000, error_msg); } else { - *error_msg = StringPrintf("Failed to sigtimedwait(): %s", strerror(errno)); + LOG(DEBUG) << StringPrintf( + "pidfd_open failed for pid %d: %s, falling back", pid, strerror(errno)); + result = WaitChildWithTimeoutFallback(pid, arg_vector, timeout_sec * 1000, error_msg); } - if (kill(pid, SIGKILL) != 0) { - PLOG(ERROR) << "Failed to kill() subprocess: "; + } else { + result = WaitChild(pid, arg_vector, /*no_wait=*/true, error_msg); + } + + if (stat != nullptr) { + std::string local_error_msg; + if (!GetStat(pid, stat, &local_error_msg)) { + LOG(ERROR) << "Failed to get process stat: " << local_error_msg; } } - // Wait for subprocess to finish. - int status = -1; - pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)); - if (got_pid != pid) { - *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: " - "wanted %d, got %d: %s", - ToCommandLine(arg_vector).c_str(), pid, got_pid, strerror(errno)); - return -1; + 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).status == + ExecResult::kUnknown) { + LOG(ERROR) << "Failed to clean up child process '" << arg_vector[0] << "': " << local_error_msg; } - if (WIFEXITED(status)) { - return WEXITSTATUS(status); + + return result; +} + +bool ExecUtils::Exec(const std::vector<std::string>& arg_vector, std::string* error_msg) const { + int status = ExecAndReturnCode(arg_vector, error_msg); + if (status < 0) { + // Internal error. The error message is already set. + return false; } - return -1; + if (status > 0) { + *error_msg = + StringPrintf("Failed to execute (%s) because the child process returns non-zero exit code", + ToCommandLine(arg_vector).c_str()); + return false; + } + return true; } +unique_fd ExecUtils::PidfdOpen(pid_t pid) const { +#ifdef __BIONIC__ + return unique_fd(pidfd_open(pid, /*flags=*/0)); +#else + // There is no glibc wrapper for pidfd_open. +#ifndef SYS_pidfd_open + constexpr int SYS_pidfd_open = 434; +#endif + return unique_fd(syscall(SYS_pidfd_open, pid, /*flags=*/0)); +#endif +} -bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) { - int status = ExecAndReturnCode(arg_vector, error_msg); - if (status != 0) { - *error_msg = StringPrintf("Failed execv(%s) because non-0 exit status", - ToCommandLine(arg_vector).c_str()); +std::string ExecUtils::GetProcStat(pid_t pid) const { + std::string stat_content; + if (!ReadFileToString(StringPrintf("/proc/%d/stat", pid), &stat_content)) { + stat_content = ""; + } + return stat_content; +} + +int64_t ExecUtils::GetUptimeMs() const { + timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return t.tv_sec * 1000 + t.tv_nsec / 1000000; +} + +int64_t ExecUtils::GetTicksPerSec() const { return sysconf(_SC_CLK_TCK); } + +bool ExecUtils::GetStat(pid_t pid, + /*out*/ ProcessStat* stat, + /*out*/ std::string* error_msg) const { + int64_t uptime_ms = GetUptimeMs(); + std::string stat_content = GetProcStat(pid); + if (stat_content.empty()) { + *error_msg = StringPrintf("Failed to read /proc/%d/stat: %s", pid, strerror(errno)); + return false; + } + int64_t ticks_per_sec = GetTicksPerSec(); + if (!ParseProcStat(stat_content, uptime_ms, ticks_per_sec, stat)) { + *error_msg = StringPrintf("Failed to parse /proc/%d/stat '%s'", pid, stat_content.c_str()); return false; } return true; |