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 "Subprocess.h"
18
19 #include <dlfcn.h>
20 #include <poll.h>
21 #include <string.h>
22 #include <sys/prctl.h>
23 #include <sys/wait.h>
24 #include <unistd.h>
25
26 #include <condition_variable>
27 #include <mutex>
28 #include <thread>
29
30 namespace gfxstream {
31 namespace {
32
33 template <typename F>
34 class ScopedCloser {
35 public:
ScopedCloser(F && func)36 constexpr ScopedCloser(F&& func) : mFunc(std::forward<F>(func)), mEnabled(true) {}
37
~ScopedCloser()38 ~ScopedCloser() {
39 if (mEnabled) {
40 mFunc();
41 }
42 }
43
Disable()44 void Disable() { mEnabled = false; }
45
46 private:
47 F mFunc;
48 bool mEnabled = false;
49 };
50
51
PidfdOpen(pid_t pid)52 int PidfdOpen(pid_t pid) {
53 // There is no glibc wrapper for pidfd_open.
54 #ifndef SYS_pidfd_open
55 constexpr int SYS_pidfd_open = 434;
56 #endif
57 return syscall(SYS_pidfd_open, pid, /*flags=*/0);
58 }
59
WaitForChild(pid_t pid)60 gfxstream::expected<Ok, std::string> WaitForChild(pid_t pid) {
61 siginfo_t info;
62 if (TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT)) != 0) {
63 return gfxstream::unexpected("Error from waitid(): " +
64 std::string(strerror(errno)));
65 }
66 if (info.si_pid != pid) {
67 return gfxstream::unexpected("Error from waitid(): returned different pid.");
68 }
69 if (info.si_code != CLD_EXITED) {
70 return gfxstream::unexpected("Failed to wait for subprocess: terminated by signal " +
71 std::to_string(info.si_status));
72 }
73 return Ok{};
74 }
75
76 // When `pidfd_open` is not available, fallback to using a second
77 // thread to kill the child process after the given timeout.
WaitForChildWithTimeoutFallback(pid_t pid,std::chrono::milliseconds timeout)78 gfxstream::expected<Ok, std::string> WaitForChildWithTimeoutFallback(
79 pid_t pid, std::chrono::milliseconds timeout) {
80 bool childExited = false;
81 bool childTimedOut = false;
82 std::condition_variable cv;
83 std::mutex m;
84
85 std::thread wait_thread([&]() {
86 std::unique_lock<std::mutex> lock(m);
87 if (!cv.wait_for(lock, timeout, [&] { return childExited; })) {
88 childTimedOut = true;
89 kill(pid, SIGKILL);
90 }
91 });
92
93 auto result = WaitForChild(pid);
94 {
95 std::unique_lock<std::mutex> lock(m);
96 childExited = true;
97 }
98 cv.notify_all();
99 wait_thread.join();
100
101 if (childTimedOut) {
102 return gfxstream::unexpected("Failed to wait for subprocess: timed out.");
103 }
104 return result;
105 }
106
WaitForChildWithTimeout(pid_t pid,int pidfd,std::chrono::milliseconds timeout)107 gfxstream::expected<Ok, std::string> WaitForChildWithTimeout(
108 pid_t pid,
109 int pidfd,
110 std::chrono::milliseconds timeout) {
111 ScopedCloser cleanup([&]() {
112 kill(pid, SIGKILL);
113 WaitForChild(pid);
114 });
115
116 struct pollfd poll_info = {
117 .fd = pidfd,
118 .events = POLLIN,
119 };
120 int ret = TEMP_FAILURE_RETRY(poll(&poll_info, 1, timeout.count()));
121 close(pidfd);
122
123 if (ret < 0) {
124 return gfxstream::unexpected("Failed to wait for subprocess: poll() returned " +
125 std::to_string(ret));
126 }
127 if (ret == 0) {
128 return gfxstream::unexpected("Failed to wait for subprocess: subprocess did not "
129 "finished within " + std::to_string(timeout.count()) +
130 "ms.");
131 }
132
133 cleanup.Disable();
134 return WaitForChild(pid);
135 }
136
137 } // namespace
138
DoWithSubprocessCheck(const std::function<gfxstream::expected<Ok,std::string> ()> & function,std::chrono::milliseconds timeout)139 gfxstream::expected<Ok, std::string> DoWithSubprocessCheck(
140 const std::function<gfxstream::expected<Ok, std::string>()>& function,
141 std::chrono::milliseconds timeout) {
142 pid_t pid = fork();
143 if (pid == 0) {
144 function();
145 _exit(0);
146 }
147
148 int pidfd = PidfdOpen(pid);
149 if (pidfd >= 0) {
150 GFXSTREAM_EXPECT(WaitForChildWithTimeout(pid, pidfd, timeout));
151 } else {
152 GFXSTREAM_EXPECT(WaitForChildWithTimeoutFallback(pid, timeout));
153 }
154
155 return function();
156 }
157
158 } // namespace gfxstream