[libc++][Android] Allow testing libc++ with clang-r536225 (#116149)
[llvm-project.git] / flang / runtime / execute.cpp
blobc7f8f386d81f4fe84038441d7116c47766426706
1 //===-- runtime/execute.cpp -----------------------------------------------===//
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 //===----------------------------------------------------------------------===//
9 #include "flang/Runtime/execute.h"
10 #include "environment.h"
11 #include "stat.h"
12 #include "terminator.h"
13 #include "tools.h"
14 #include "flang/Runtime/descriptor.h"
15 #include <cstdlib>
16 #include <errno.h>
17 #include <future>
18 #include <limits>
20 #ifdef _WIN32
21 #include "flang/Common/windows-include.h"
22 #else
23 #include <signal.h>
24 #include <sys/wait.h>
25 #include <unistd.h>
26 #endif
28 namespace Fortran::runtime {
30 // cmdstat specified in 16.9.73
31 // −1 if the processor does not support command line execution,
32 // a processor-dependent positive value if an error condition occurs
33 // −2 if no error condition occurs but WAIT is present with the value false
34 // and the processor does not support asynchronous execution. Otherwise it is
35 // assigned the value 0
36 enum CMD_STAT {
37 ASYNC_NO_SUPPORT_ERR = -2, // system returns -1 with ENOENT
38 NO_SUPPORT_ERR = -1, // Linux setsid() returns -1
39 CMD_EXECUTED = 0, // command executed with no error
40 FORK_ERR = 1, // Linux fork() returns < 0
41 EXECL_ERR = 2, // system returns -1 with other errno
42 COMMAND_EXECUTION_ERR = 3, // exit code 1
43 COMMAND_CANNOT_EXECUTE_ERR = 4, // Linux exit code 126
44 COMMAND_NOT_FOUND_ERR = 5, // Linux exit code 127
45 INVALID_CL_ERR = 6, // cover all other non-zero exit code
46 SIGNAL_ERR = 7
49 // Override CopyCharsToDescriptor in tools.h, pass string directly
50 void CopyCharsToDescriptor(const Descriptor &value, const char *rawValue) {
51 CopyCharsToDescriptor(value, rawValue, std::strlen(rawValue));
54 void CheckAndCopyCharsToDescriptor(
55 const Descriptor *value, const char *rawValue) {
56 if (value) {
57 CopyCharsToDescriptor(*value, rawValue);
61 void CheckAndStoreIntToDescriptor(
62 const Descriptor *intVal, std::int64_t value, Terminator &terminator) {
63 if (intVal) {
64 StoreIntToDescriptor(intVal, value, terminator);
68 // If a condition occurs that would assign a nonzero value to CMDSTAT but
69 // the CMDSTAT variable is not present, error termination is initiated.
70 std::int64_t TerminationCheck(std::int64_t status, const Descriptor *cmdstat,
71 const Descriptor *cmdmsg, Terminator &terminator) {
72 // On both Windows and Linux, errno is set when system returns -1.
73 if (status == -1) {
74 // On Windows, ENOENT means the command interpreter can't be found.
75 // On Linux, system calls execl with filepath "/bin/sh", ENOENT means the
76 // file pathname does not exist.
77 if (errno == ENOENT) {
78 if (!cmdstat) {
79 terminator.Crash("Command line execution is not supported, system "
80 "returns -1 with errno ENOENT.");
81 } else {
82 StoreIntToDescriptor(cmdstat, NO_SUPPORT_ERR, terminator);
83 CheckAndCopyCharsToDescriptor(cmdmsg,
84 "Command line execution is not supported, system returns -1 with "
85 "errno ENOENT.");
87 } else {
88 char err_buffer[30];
89 char msg[]{"Execution error with system status code: -1, errno: "};
90 #ifdef _WIN32
91 if (strerror_s(err_buffer, sizeof(err_buffer), errno) != 0)
92 #else
93 if (strerror_r(errno, err_buffer, sizeof(err_buffer)) != 0)
94 #endif
95 terminator.Crash("errno to char msg failed.");
96 char *newMsg{static_cast<char *>(AllocateMemoryOrCrash(
97 terminator, std::strlen(msg) + std::strlen(err_buffer) + 1))};
98 std::strcat(newMsg, err_buffer);
100 if (!cmdstat) {
101 terminator.Crash(newMsg);
102 } else {
103 StoreIntToDescriptor(cmdstat, EXECL_ERR, terminator);
104 CheckAndCopyCharsToDescriptor(cmdmsg, newMsg);
106 FreeMemory(newMsg);
110 #ifdef _WIN32
111 // On WIN32 API std::system returns exit status directly
112 std::int64_t exitStatusVal{status};
113 if (exitStatusVal != 0) {
114 if (!cmdstat) {
115 terminator.Crash(
116 "Invalid command quit with exit status code: %d", exitStatusVal);
117 } else {
118 StoreIntToDescriptor(cmdstat, INVALID_CL_ERR, terminator);
119 CheckAndCopyCharsToDescriptor(cmdmsg, "Invalid command line");
122 #else
123 std::int64_t exitStatusVal{WEXITSTATUS(status)};
124 if (exitStatusVal == 1) {
125 if (!cmdstat) {
126 terminator.Crash("Command line execution failed with exit code: 1.");
127 } else {
128 StoreIntToDescriptor(cmdstat, COMMAND_EXECUTION_ERR, terminator);
129 CheckAndCopyCharsToDescriptor(
130 cmdmsg, "Command line execution failed with exit code: 1.");
132 } else if (exitStatusVal == 126) {
133 if (!cmdstat) {
134 terminator.Crash("Command cannot be executed with exit code: 126.");
135 } else {
136 StoreIntToDescriptor(cmdstat, COMMAND_CANNOT_EXECUTE_ERR, terminator);
137 CheckAndCopyCharsToDescriptor(
138 cmdmsg, "Command cannot be executed with exit code: 126.");
140 } else if (exitStatusVal == 127) {
141 if (!cmdstat) {
142 terminator.Crash("Command not found with exit code: 127.");
143 } else {
144 StoreIntToDescriptor(cmdstat, COMMAND_NOT_FOUND_ERR, terminator);
145 CheckAndCopyCharsToDescriptor(
146 cmdmsg, "Command not found with exit code: 127.");
148 // capture all other nonzero exit code
149 } else if (exitStatusVal != 0) {
150 if (!cmdstat) {
151 terminator.Crash(
152 "Invalid command quit with exit status code: %d", exitStatusVal);
153 } else {
154 StoreIntToDescriptor(cmdstat, INVALID_CL_ERR, terminator);
155 CheckAndCopyCharsToDescriptor(cmdmsg, "Invalid command line");
158 #endif
160 #if defined(WIFSIGNALED) && defined(WTERMSIG)
161 if (WIFSIGNALED(status)) {
162 if (!cmdstat) {
163 terminator.Crash("Killed by signal: %d", WTERMSIG(status));
164 } else {
165 StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
166 CheckAndCopyCharsToDescriptor(cmdmsg, "Killed by signal");
169 #endif
171 #if defined(WIFSTOPPED) && defined(WSTOPSIG)
172 if (WIFSTOPPED(status)) {
173 if (!cmdstat) {
174 terminator.Crash("Stopped by signal: %d", WSTOPSIG(status));
175 } else {
176 StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
177 CheckAndCopyCharsToDescriptor(cmdmsg, "Stopped by signal");
180 #endif
181 return exitStatusVal;
184 void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait,
185 const Descriptor *exitstat, const Descriptor *cmdstat,
186 const Descriptor *cmdmsg, const char *sourceFile, int line) {
187 Terminator terminator{sourceFile, line};
188 char *newCmd{EnsureNullTerminated(
189 command.OffsetElement(), command.ElementBytes(), terminator)};
191 if (exitstat) {
192 RUNTIME_CHECK(terminator, IsValidIntDescriptor(exitstat));
195 if (cmdstat) {
196 RUNTIME_CHECK(terminator, IsValidIntDescriptor(cmdstat));
197 // Assigned 0 as specifed in standard, if error then overwrite
198 StoreIntToDescriptor(cmdstat, CMD_EXECUTED, terminator);
201 if (cmdmsg) {
202 RUNTIME_CHECK(terminator, IsValidCharDescriptor(cmdmsg));
205 if (wait) {
206 // either wait is not specified or wait is true: synchronous mode
207 std::int64_t status{std::system(newCmd)};
208 std::int64_t exitStatusVal{
209 TerminationCheck(status, cmdstat, cmdmsg, terminator)};
210 // If sync, assigned processor-dependent exit status. Otherwise unchanged
211 CheckAndStoreIntToDescriptor(exitstat, exitStatusVal, terminator);
212 } else {
213 // Asynchronous mode
214 #ifdef _WIN32
215 STARTUPINFO si;
216 PROCESS_INFORMATION pi;
217 ZeroMemory(&si, sizeof(si));
218 si.cb = sizeof(si);
219 ZeroMemory(&pi, sizeof(pi));
221 // add "cmd.exe /c " to the beginning of command
222 const char *prefix{"cmd.exe /c "};
223 char *newCmdWin{static_cast<char *>(AllocateMemoryOrCrash(
224 terminator, std::strlen(prefix) + std::strlen(newCmd) + 1))};
225 std::strcpy(newCmdWin, prefix);
226 std::strcat(newCmdWin, newCmd);
228 // Convert the char to wide char
229 const size_t sizeNeeded{mbstowcs(NULL, newCmdWin, 0) + 1};
230 wchar_t *wcmd{static_cast<wchar_t *>(
231 AllocateMemoryOrCrash(terminator, sizeNeeded * sizeof(wchar_t)))};
232 if (std::mbstowcs(wcmd, newCmdWin, sizeNeeded) == static_cast<size_t>(-1)) {
233 terminator.Crash("Char to wide char failed for newCmd");
235 FreeMemory(newCmdWin);
237 if (CreateProcess(nullptr, wcmd, nullptr, nullptr, FALSE, 0, nullptr,
238 nullptr, &si, &pi)) {
239 // Close handles so it will be removed when terminated
240 CloseHandle(pi.hProcess);
241 CloseHandle(pi.hThread);
242 } else {
243 if (!cmdstat) {
244 terminator.Crash(
245 "CreateProcess failed with error code: %lu.", GetLastError());
246 } else {
247 StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator);
248 CheckAndCopyCharsToDescriptor(cmdmsg, "CreateProcess failed.");
251 FreeMemory(wcmd);
252 #else
253 pid_t pid{fork()};
254 if (pid < 0) {
255 if (!cmdstat) {
256 terminator.Crash("Fork failed with pid: %d.", pid);
257 } else {
258 StoreIntToDescriptor(cmdstat, FORK_ERR, terminator);
259 CheckAndCopyCharsToDescriptor(cmdmsg, "Fork failed");
261 } else if (pid == 0) {
262 // Create a new session, let init process take care of zombie child
263 if (setsid() == -1) {
264 if (!cmdstat) {
265 terminator.Crash("setsid() failed with errno: %d, asynchronous "
266 "process initiation failed.",
267 errno);
268 } else {
269 StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator);
270 CheckAndCopyCharsToDescriptor(cmdmsg,
271 "setsid() failed, asynchronous process initiation failed.");
273 exit(EXIT_FAILURE);
275 std::int64_t status{std::system(newCmd)};
276 TerminationCheck(status, cmdstat, cmdmsg, terminator);
277 exit(status);
279 #endif
281 // Deallocate memory if EnsureNullTerminated dynamically allocated memory
282 if (newCmd != command.OffsetElement()) {
283 FreeMemory(newCmd);
287 } // namespace Fortran::runtime