1 | //===-- ExecuteFunction implementation for Unix-like Systems --------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "ExecuteFunction.h" |
10 | #include "src/__support/macros/config.h" |
11 | #include "test/UnitTest/ExecuteFunction.h" // FunctionCaller |
12 | #include <assert.h> |
13 | #include <poll.h> |
14 | #include <signal.h> |
15 | #include <stdio.h> |
16 | #include <stdlib.h> |
17 | #include <string.h> |
18 | #include <sys/wait.h> |
19 | #include <unistd.h> |
20 | |
21 | namespace LIBC_NAMESPACE_DECL { |
22 | namespace testutils { |
23 | |
24 | bool ProcessStatus::exited_normally() { return WIFEXITED(platform_defined); } |
25 | |
26 | int ProcessStatus::get_exit_code() { |
27 | assert(exited_normally() && "Abnormal termination, no exit code" ); |
28 | return WEXITSTATUS(platform_defined); |
29 | } |
30 | |
31 | int ProcessStatus::get_fatal_signal() { |
32 | if (exited_normally()) |
33 | return 0; |
34 | return WTERMSIG(platform_defined); |
35 | } |
36 | |
37 | ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) { |
38 | int pipe_fds[2]; |
39 | if (::pipe(pipedes: pipe_fds) == -1) { |
40 | delete func; |
41 | return ProcessStatus::error(error: "pipe(2) failed" ); |
42 | } |
43 | |
44 | // Don't copy the buffers into the child process and print twice. |
45 | ::fflush(stderr); |
46 | ::fflush(stdout); |
47 | pid_t pid = ::fork(); |
48 | if (pid == -1) { |
49 | delete func; |
50 | return ProcessStatus::error(error: "fork(2) failed" ); |
51 | } |
52 | |
53 | if (!pid) { |
54 | (*func)(); |
55 | delete func; |
56 | ::exit(status: 0); |
57 | } |
58 | ::close(fd: pipe_fds[1]); |
59 | |
60 | struct pollfd poll_fd { |
61 | .fd: pipe_fds[0], .events: 0, .revents: 0 |
62 | }; |
63 | // No events requested so this call will only return after the timeout or if |
64 | // the pipes peer was closed, signaling the process exited. |
65 | if (::poll(fds: &poll_fd, nfds: 1, timeout: timeout_ms) == -1) { |
66 | delete func; |
67 | return ProcessStatus::error(error: "poll(2) failed" ); |
68 | } |
69 | // If the pipe wasn't closed by the child yet then timeout has expired. |
70 | if (!(poll_fd.revents & POLLHUP)) { |
71 | ::kill(pid: pid, SIGKILL); |
72 | delete func; |
73 | return ProcessStatus::timed_out_ps(); |
74 | } |
75 | |
76 | int wstatus = 0; |
77 | // Wait on the pid of the subprocess here so it gets collected by the system |
78 | // and doesn't turn into a zombie. |
79 | pid_t status = ::waitpid(pid: pid, stat_loc: &wstatus, options: 0); |
80 | if (status == -1) { |
81 | delete func; |
82 | return ProcessStatus::error(error: "waitpid(2) failed" ); |
83 | } |
84 | assert(status == pid); |
85 | delete func; |
86 | return {.platform_defined: wstatus}; |
87 | } |
88 | |
89 | const char *signal_as_string(int signum) { return ::strsignal(sig: signum); } |
90 | |
91 | } // namespace testutils |
92 | } // namespace LIBC_NAMESPACE_DECL |
93 | |