• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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