xref: /aosp_15_r20/art/runtime/exec_utils.cc (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright (C) 2011 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 "exec_utils.h"
18 
19 #include <poll.h>
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <unistd.h>
23 
24 #include <chrono>
25 #include <climits>
26 #include <condition_variable>
27 #include <cstdint>
28 #include <cstring>
29 #include <ctime>
30 #include <mutex>
31 #include <optional>
32 #include <string>
33 #include <string_view>
34 #include <thread>
35 #include <vector>
36 
37 #include "android-base/file.h"
38 #include "android-base/parseint.h"
39 #include "android-base/scopeguard.h"
40 #include "android-base/stringprintf.h"
41 #include "android-base/strings.h"
42 #include "android-base/unique_fd.h"
43 #include "base/macros.h"
44 #include "base/pidfd.h"
45 #include "base/utils.h"
46 #include "runtime.h"
47 
48 namespace art HIDDEN {
49 
50 namespace {
51 
52 using ::android::base::ParseInt;
53 using ::android::base::ReadFileToString;
54 using ::android::base::StringPrintf;
55 using ::android::base::unique_fd;
56 
ToCommandLine(const std::vector<std::string> & args)57 std::string ToCommandLine(const std::vector<std::string>& args) {
58   return android::base::Join(args, ' ');
59 }
60 
61 // Fork and execute a command specified in a subprocess.
62 // If there is a runtime (Runtime::Current != nullptr) then the subprocess is created with the
63 // same environment that existed when the runtime was started.
64 // Returns the process id of the child process on success, -1 otherwise.
ExecWithoutWait(const std::vector<std::string> & arg_vector,bool new_process_group,std::string * error_msg)65 pid_t ExecWithoutWait(const std::vector<std::string>& arg_vector,
66                       bool new_process_group,
67                       std::string* error_msg) {
68   // Convert the args to char pointers.
69   const char* program = arg_vector[0].c_str();
70   std::vector<char*> args;
71   args.reserve(arg_vector.size() + 1);
72   for (const auto& arg : arg_vector) {
73     args.push_back(const_cast<char*>(arg.c_str()));
74   }
75   args.push_back(nullptr);
76 
77   // fork and exec
78   pid_t pid = fork();
79   if (pid == 0) {
80     // no allocation allowed between fork and exec
81 
82     if (new_process_group) {
83       setpgid(0, 0);
84     }
85 
86     // (b/30160149): protect subprocesses from modifications to LD_LIBRARY_PATH, etc.
87     // Use the snapshot of the environment from the time the runtime was created.
88     char** envp = (Runtime::Current() == nullptr) ? nullptr : Runtime::Current()->GetEnvSnapshot();
89     if (envp == nullptr) {
90       execv(program, &args[0]);
91     } else {
92       execve(program, &args[0], envp);
93     }
94     // This should be regarded as a crash rather than a normal return.
95     PLOG(FATAL) << "Failed to execute (" << ToCommandLine(arg_vector) << ")";
96     UNREACHABLE();
97   } else if (pid == -1) {
98     *error_msg = StringPrintf("Failed to execute (%s) because fork failed: %s",
99                               ToCommandLine(arg_vector).c_str(),
100                               strerror(errno));
101     return -1;
102   } else {
103     return pid;
104   }
105 }
106 
WaitChild(pid_t pid,const std::vector<std::string> & arg_vector,bool no_wait,std::string * error_msg)107 ExecResult WaitChild(pid_t pid,
108                      const std::vector<std::string>& arg_vector,
109                      bool no_wait,
110                      std::string* error_msg) {
111   siginfo_t info;
112   // WNOWAIT leaves the child in a waitable state. The call is still blocking.
113   int options = WEXITED | (no_wait ? WNOWAIT : 0);
114   if (TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, options)) != 0) {
115     *error_msg = StringPrintf("waitid failed for (%s) pid %d: %s",
116                               ToCommandLine(arg_vector).c_str(),
117                               pid,
118                               strerror(errno));
119     return {.status = ExecResult::kUnknown};
120   }
121   if (info.si_pid != pid) {
122     *error_msg = StringPrintf("waitid failed for (%s): wanted pid %d, got %d: %s",
123                               ToCommandLine(arg_vector).c_str(),
124                               pid,
125                               info.si_pid,
126                               strerror(errno));
127     return {.status = ExecResult::kUnknown};
128   }
129   if (info.si_code != CLD_EXITED) {
130     *error_msg =
131         StringPrintf("Failed to execute (%s) because the child process is terminated by signal %d",
132                      ToCommandLine(arg_vector).c_str(),
133                      info.si_status);
134     return {.status = ExecResult::kSignaled, .signal = info.si_status};
135   }
136   return {.status = ExecResult::kExited, .exit_code = info.si_status};
137 }
138 
139 // A fallback implementation of `WaitChildWithTimeout` that creates a thread to wait instead of
140 // relying on `pidfd_open`.
WaitChildWithTimeoutFallback(pid_t pid,const std::vector<std::string> & arg_vector,int timeout_ms,std::string * error_msg)141 ExecResult WaitChildWithTimeoutFallback(pid_t pid,
142                                         const std::vector<std::string>& arg_vector,
143                                         int timeout_ms,
144                                         std::string* error_msg) {
145   bool child_exited = false;
146   bool timed_out = false;
147   std::condition_variable cv;
148   std::mutex m;
149 
150   std::thread wait_thread([&]() {
151     std::unique_lock<std::mutex> lock(m);
152     if (!cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [&] { return child_exited; })) {
153       timed_out = true;
154       kill(pid, SIGKILL);
155     }
156   });
157 
158   ExecResult result = WaitChild(pid, arg_vector, /*no_wait=*/true, error_msg);
159 
160   {
161     std::unique_lock<std::mutex> lock(m);
162     child_exited = true;
163   }
164   cv.notify_all();
165   wait_thread.join();
166 
167   // The timeout error should have a higher priority than any other error.
168   if (timed_out) {
169     *error_msg =
170         StringPrintf("Failed to execute (%s) because the child process timed out after %dms",
171                      ToCommandLine(arg_vector).c_str(),
172                      timeout_ms);
173     return ExecResult{.status = ExecResult::kTimedOut};
174   }
175 
176   return result;
177 }
178 
179 // Waits for the child process to finish and leaves the child in a waitable state.
WaitChildWithTimeout(pid_t pid,unique_fd pidfd,const std::vector<std::string> & arg_vector,int timeout_ms,std::string * error_msg)180 ExecResult WaitChildWithTimeout(pid_t pid,
181                                 unique_fd pidfd,
182                                 const std::vector<std::string>& arg_vector,
183                                 int timeout_ms,
184                                 std::string* error_msg) {
185   auto cleanup = android::base::make_scope_guard([&]() {
186     kill(pid, SIGKILL);
187     std::string ignored_error_msg;
188     WaitChild(pid, arg_vector, /*no_wait=*/true, &ignored_error_msg);
189   });
190 
191   struct pollfd pfd;
192   pfd.fd = pidfd.get();
193   pfd.events = POLLIN;
194   int poll_ret = TEMP_FAILURE_RETRY(poll(&pfd, /*nfds=*/1, timeout_ms));
195 
196   pidfd.reset();
197 
198   if (poll_ret < 0) {
199     *error_msg = StringPrintf("poll failed for pid %d: %s", pid, strerror(errno));
200     return {.status = ExecResult::kUnknown};
201   }
202   if (poll_ret == 0) {
203     *error_msg =
204         StringPrintf("Failed to execute (%s) because the child process timed out after %dms",
205                      ToCommandLine(arg_vector).c_str(),
206                      timeout_ms);
207     return {.status = ExecResult::kTimedOut};
208   }
209 
210   cleanup.Disable();
211   return WaitChild(pid, arg_vector, /*no_wait=*/true, error_msg);
212 }
213 
ParseProcStat(const std::string & stat_content,int64_t ticks_per_sec,int64_t * cpu_time_ms)214 bool ParseProcStat(const std::string& stat_content,
215                    int64_t ticks_per_sec,
216                    /*out*/ int64_t* cpu_time_ms) {
217   size_t pos = stat_content.rfind(") ");
218   if (pos == std::string::npos) {
219     return false;
220   }
221   std::vector<std::string> stat_fields;
222   // Skip the first two fields. The second field is the parenthesized process filename, which can
223   // contain anything, including spaces.
224   Split(std::string_view(stat_content).substr(pos + 2), ' ', &stat_fields);
225   constexpr int kSkippedFields = 2;
226   int64_t utime, stime, cutime, cstime;
227   if (stat_fields.size() < 22 - kSkippedFields ||
228       !ParseInt(stat_fields[13 - kSkippedFields], &utime) ||
229       !ParseInt(stat_fields[14 - kSkippedFields], &stime) ||
230       !ParseInt(stat_fields[15 - kSkippedFields], &cutime) ||
231       !ParseInt(stat_fields[16 - kSkippedFields], &cstime)) {
232     return false;
233   }
234   *cpu_time_ms = (utime + stime + cutime + cstime) * 1000 / ticks_per_sec;
235   return true;
236 }
237 
238 }  // namespace
239 
ExecAndReturnCode(const std::vector<std::string> & arg_vector,std::string * error_msg) const240 int ExecUtils::ExecAndReturnCode(const std::vector<std::string>& arg_vector,
241                                  std::string* error_msg) const {
242   return ExecAndReturnResult(arg_vector, /*timeout_sec=*/-1, error_msg).exit_code;
243 }
244 
ExecAndReturnResult(const std::vector<std::string> & arg_vector,int timeout_sec,std::string * error_msg) const245 ExecResult ExecUtils::ExecAndReturnResult(const std::vector<std::string>& arg_vector,
246                                           int timeout_sec,
247                                           std::string* error_msg) const {
248   return ExecAndReturnResult(arg_vector,
249                              timeout_sec,
250                              ExecCallbacks(),
251                              /*new_process_group=*/false,
252                              /*stat=*/nullptr,
253                              error_msg);
254 }
255 
ExecAndReturnResult(const std::vector<std::string> & arg_vector,int timeout_sec,const ExecCallbacks & callbacks,bool new_process_group,ProcessStat * stat,std::string * error_msg) const256 ExecResult ExecUtils::ExecAndReturnResult(const std::vector<std::string>& arg_vector,
257                                           int timeout_sec,
258                                           const ExecCallbacks& callbacks,
259                                           bool new_process_group,
260                                           /*out*/ ProcessStat* stat,
261                                           /*out*/ std::string* error_msg) const {
262   if (timeout_sec > INT_MAX / 1000) {
263     *error_msg = "Timeout too large";
264     return {.status = ExecResult::kStartFailed};
265   }
266 
267   // Start subprocess.
268   pid_t pid = ExecWithoutWait(arg_vector, new_process_group, error_msg);
269   if (pid == -1) {
270     return {.status = ExecResult::kStartFailed};
271   }
272 
273   std::string stat_error_msg;
274   std::optional<int64_t> start_time = GetUptimeMs(&stat_error_msg);
275   callbacks.on_start(pid);
276 
277   // Wait for subprocess to finish.
278   ExecResult result;
279   if (timeout_sec >= 0) {
280     unique_fd pidfd = PidfdOpen(pid);
281     if (pidfd.get() >= 0) {
282       result =
283           WaitChildWithTimeout(pid, std::move(pidfd), arg_vector, timeout_sec * 1000, error_msg);
284     } else {
285       LOG(DEBUG) << StringPrintf(
286           "pidfd_open failed for pid %d: %s, falling back", pid, strerror(errno));
287       result = WaitChildWithTimeoutFallback(pid, arg_vector, timeout_sec * 1000, error_msg);
288     }
289   } else {
290     result = WaitChild(pid, arg_vector, /*no_wait=*/true, error_msg);
291   }
292 
293   if (stat != nullptr) {
294     if (!start_time.has_value() || !GetStat(pid, start_time.value(), stat, &stat_error_msg)) {
295       LOG(ERROR) << "Failed to get process stat: " << stat_error_msg;
296     }
297   }
298 
299   callbacks.on_end(pid);
300 
301   std::string local_error_msg;
302   // TODO(jiakaiz): Use better logic to detect waitid failure.
303   if (WaitChild(pid, arg_vector, /*no_wait=*/false, &local_error_msg).status ==
304       ExecResult::kUnknown) {
305     LOG(ERROR) << "Failed to clean up child process '" << arg_vector[0] << "': " << local_error_msg;
306   }
307 
308   return result;
309 }
310 
Exec(const std::vector<std::string> & arg_vector,std::string * error_msg) const311 bool ExecUtils::Exec(const std::vector<std::string>& arg_vector, std::string* error_msg) const {
312   int status = ExecAndReturnCode(arg_vector, error_msg);
313   if (status < 0) {
314     // Internal error. The error message is already set.
315     return false;
316   }
317   if (status > 0) {
318     *error_msg =
319         StringPrintf("Failed to execute (%s) because the child process returns non-zero exit code",
320                      ToCommandLine(arg_vector).c_str());
321     return false;
322   }
323   return true;
324 }
325 
PidfdOpen(pid_t pid) const326 unique_fd ExecUtils::PidfdOpen(pid_t pid) const { return art::PidfdOpen(pid, /*flags=*/0); }
327 
GetProcStat(pid_t pid) const328 std::string ExecUtils::GetProcStat(pid_t pid) const {
329   std::string stat_content;
330   if (!ReadFileToString(StringPrintf("/proc/%d/stat", pid), &stat_content)) {
331     stat_content = "";
332   }
333   return stat_content;
334 }
335 
GetUptimeMs(std::string * error_msg) const336 std::optional<int64_t> ExecUtils::GetUptimeMs(std::string* error_msg) const {
337   timespec t;
338   if (clock_gettime(CLOCK_MONOTONIC, &t) != 0) {
339     *error_msg = ART_FORMAT("Failed to get uptime: {}", strerror(errno));
340     return std::nullopt;
341   }
342   return t.tv_sec * 1000 + t.tv_nsec / 1000000;
343 }
344 
GetTicksPerSec() const345 int64_t ExecUtils::GetTicksPerSec() const { return sysconf(_SC_CLK_TCK); }
346 
GetStat(pid_t pid,int64_t start_time,ProcessStat * stat,std::string * error_msg) const347 bool ExecUtils::GetStat(pid_t pid,
348                         int64_t start_time,
349                         /*out*/ ProcessStat* stat,
350                         /*out*/ std::string* error_msg) const {
351   std::optional<int64_t> uptime_ms = GetUptimeMs(error_msg);
352   if (!uptime_ms.has_value()) {
353     return false;
354   }
355   std::string stat_content = GetProcStat(pid);
356   if (stat_content.empty()) {
357     *error_msg = StringPrintf("Failed to read /proc/%d/stat: %s", pid, strerror(errno));
358     return false;
359   }
360   int64_t ticks_per_sec = GetTicksPerSec();
361   if (!ParseProcStat(stat_content, ticks_per_sec, &stat->cpu_time_ms)) {
362     *error_msg = StringPrintf("Failed to parse /proc/%d/stat '%s'", pid, stat_content.c_str());
363     return false;
364   }
365   stat->wall_time_ms = uptime_ms.value() - start_time;
366   return true;
367 }
368 
369 }  // namespace art
370