• 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 "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