1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "host/libs/graphics_detector/subprocess.h"
18
19 #include <dlfcn.h>
20 #include <poll.h>
21 #include <sys/prctl.h>
22 #include <sys/wait.h>
23
24 #include <condition_variable>
25 #include <mutex>
26 #include <thread>
27
28 #include <android-base/logging.h>
29 #include <android-base/scopeguard.h>
30 #include <android-base/unique_fd.h>
31
32 namespace cuttlefish {
33 namespace {
34
35 const char* const kFailedGraphicsSubprocessDisclaimer =
36 "Note: the Cuttlefish launcher runs some tests to check for the "
37 "availability of various graphics libraries and features on your "
38 "machine and failures during these tests can be expected.";
39
PidfdOpen(pid_t pid)40 int PidfdOpen(pid_t pid) {
41 // There is no glibc wrapper for pidfd_open.
42 #ifndef SYS_pidfd_open
43 constexpr int SYS_pidfd_open = 434;
44 #endif
45 return syscall(SYS_pidfd_open, pid, /*flags=*/0);
46 }
47
WaitForChild(const std::string & message,pid_t pid)48 SubprocessResult WaitForChild(const std::string& message, pid_t pid) {
49 siginfo_t info;
50
51 int options = WEXITED | WNOWAIT;
52 if (TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, options)) != 0) {
53 PLOG(VERBOSE) << "Failed to wait for subprocess " << pid << " running "
54 << message << " : waitid error. "
55 << kFailedGraphicsSubprocessDisclaimer;
56 return SubprocessResult::kFailure;
57 }
58 if (info.si_pid != pid) {
59 LOG(VERBOSE) << "Failed to wait for subprocess " << pid << " running "
60 << message << ": waitid returned different pid. "
61 << kFailedGraphicsSubprocessDisclaimer;
62 return SubprocessResult::kFailure;
63 }
64 if (info.si_code != CLD_EXITED) {
65 LOG(VERBOSE) << "Failed to wait for subprocess " << pid << " running "
66 << message << ": subprocess terminated by signal "
67 << info.si_status << ". "
68 << kFailedGraphicsSubprocessDisclaimer;
69 return SubprocessResult::kFailure;
70 }
71 return SubprocessResult::kSuccess;
72 }
73
WaitForChildWithTimeoutFallback(const std::string & message,pid_t pid,std::chrono::milliseconds timeout)74 SubprocessResult WaitForChildWithTimeoutFallback(
75 const std::string& message, pid_t pid, std::chrono::milliseconds timeout) {
76 bool child_exited = false;
77 bool child_timed_out = false;
78 std::condition_variable cv;
79 std::mutex m;
80
81 std::thread wait_thread([&]() {
82 std::unique_lock<std::mutex> lock(m);
83 if (!cv.wait_for(lock, timeout, [&] { return child_exited; })) {
84 child_timed_out = true;
85 if (kill(pid, SIGKILL) != 0) {
86 PLOG(VERBOSE) << "Failed to kill subprocess " << pid << " running "
87 << message << " after " << timeout.count()
88 << "ms timeout. " << kFailedGraphicsSubprocessDisclaimer;
89 }
90 }
91 });
92
93 SubprocessResult result = WaitForChild(message, pid);
94 {
95 std::unique_lock<std::mutex> lock(m);
96 child_exited = true;
97 }
98 cv.notify_all();
99 wait_thread.join();
100
101 if (child_timed_out) {
102 return SubprocessResult::kFailure;
103 }
104 return result;
105 }
106
107 // When `pidfd_open` is not available, fallback to using a second
108 // thread to kill the child process after the given timeout.
WaitForChildWithTimeout(const std::string & message,pid_t pid,android::base::unique_fd pidfd,std::chrono::milliseconds timeout)109 SubprocessResult WaitForChildWithTimeout(const std::string& message, pid_t pid,
110 android::base::unique_fd pidfd,
111 std::chrono::milliseconds timeout) {
112 auto cleanup = android::base::make_scope_guard([&]() {
113 kill(pid, SIGKILL);
114 WaitForChild(message, pid);
115 });
116
117 struct pollfd poll_info = {
118 .fd = pidfd.get(),
119 .events = POLLIN,
120 };
121 int ret = TEMP_FAILURE_RETRY(poll(&poll_info, 1, timeout.count()));
122 pidfd.reset();
123
124 if (ret < 0) {
125 LOG(ERROR) << "Failed to wait for subprocess " << pid << " running "
126 << message << ": poll failed with " << ret << ". "
127 << kFailedGraphicsSubprocessDisclaimer;
128 return SubprocessResult::kFailure;
129 }
130 if (ret == 0) {
131 LOG(ERROR) << "Subprocess " << pid << " running " << message
132 << " did not complete within " << timeout.count()
133 << "ms. Killing. " << kFailedGraphicsSubprocessDisclaimer;
134 return SubprocessResult::kFailure;
135 }
136
137 cleanup.Disable();
138 return WaitForChild(message, pid);
139 }
140
141 } // namespace
142
DoWithSubprocessCheck(const std::string & message,const std::function<void ()> & function,std::chrono::milliseconds timeout)143 SubprocessResult DoWithSubprocessCheck(const std::string& message,
144 const std::function<void()>& function,
145 std::chrono::milliseconds timeout) {
146 LOG(VERBOSE) << "Running " << message << " in subprocess...";
147 pid_t pid = fork();
148 if (pid == 0) {
149 prctl(PR_SET_NAME, "gfxDtctCanSegv");
150 function();
151 std::exit(0);
152 }
153
154 LOG(VERBOSE) << "Waiting for subprocess " << pid << " running " << message
155 << "...";
156
157 SubprocessResult result = SubprocessResult::kFailure;
158
159 android::base::unique_fd pidfd(PidfdOpen(pid));
160 if (pidfd.get() >= 0) {
161 result = WaitForChildWithTimeout(message, pid, std::move(pidfd), timeout);
162 } else {
163 result = WaitForChildWithTimeoutFallback(message, pid, timeout);
164 }
165
166 if (result == SubprocessResult::kSuccess) {
167 LOG(VERBOSE) << "Subprocess running " << message << " succeeded. Running "
168 << message << " in this process...";
169 function();
170 return SubprocessResult::kSuccess;
171 } else {
172 LOG(VERBOSE) << "Subprocess running " << message << " failed. Not running "
173 << message << " in this process.";
174 return SubprocessResult::kFailure;
175 }
176 }
177
178 } // namespace cuttlefish