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 | //===----------------------------------------------------------------------===// |
8 | |
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 <future> |
17 | #include <limits> |
18 | #ifdef _WIN32 |
19 | #include "flang/Common/windows-include.h" |
20 | #else |
21 | #include <signal.h> |
22 | #include <sys/wait.h> |
23 | #include <unistd.h> |
24 | #endif |
25 | |
26 | namespace Fortran::runtime { |
27 | |
28 | // cmdstat specified in 16.9.73 |
29 | // −1 if the processor does not support command line execution, |
30 | // a processor-dependent positive value if an error condition occurs |
31 | // −2 if no error condition occurs but WAIT is present with the value false |
32 | // and the processor does not support asynchronous execution. Otherwise it is |
33 | // assigned the value 0 |
34 | enum CMD_STAT { |
35 | ASYNC_NO_SUPPORT_ERR = -2, |
36 | NO_SUPPORT_ERR = -1, |
37 | CMD_EXECUTED = 0, |
38 | FORK_ERR = 1, |
39 | EXECL_ERR = 2, |
40 | INVALID_CL_ERR = 3, |
41 | SIGNAL_ERR = 4 |
42 | }; |
43 | |
44 | // Override CopyCharsToDescriptor in tools.h, pass string directly |
45 | void CopyCharsToDescriptor(const Descriptor &value, const char *rawValue) { |
46 | CopyCharsToDescriptor(value, rawValue, std::strlen(s: rawValue)); |
47 | } |
48 | |
49 | void CheckAndCopyCharsToDescriptor( |
50 | const Descriptor *value, const char *rawValue) { |
51 | if (value) { |
52 | CopyCharsToDescriptor(value: *value, rawValue); |
53 | } |
54 | } |
55 | |
56 | void CheckAndStoreIntToDescriptor( |
57 | const Descriptor *intVal, std::int64_t value, Terminator &terminator) { |
58 | if (intVal) { |
59 | StoreIntToDescriptor(intVal, value, terminator); |
60 | } |
61 | } |
62 | |
63 | // If a condition occurs that would assign a nonzero value to CMDSTAT but |
64 | // the CMDSTAT variable is not present, error termination is initiated. |
65 | int TerminationCheck(int status, const Descriptor *cmdstat, |
66 | const Descriptor *cmdmsg, Terminator &terminator) { |
67 | if (status == -1) { |
68 | if (!cmdstat) { |
69 | terminator.Crash("Execution error with system status code: %d" , status); |
70 | } else { |
71 | StoreIntToDescriptor(length: cmdstat, value: EXECL_ERR, terminator); |
72 | CheckAndCopyCharsToDescriptor(value: cmdmsg, rawValue: "Execution error" ); |
73 | } |
74 | } |
75 | #ifdef _WIN32 |
76 | // On WIN32 API std::system returns exit status directly |
77 | int exitStatusVal{status}; |
78 | if (exitStatusVal == 1) { |
79 | #else |
80 | int exitStatusVal{WEXITSTATUS(status)}; |
81 | if (exitStatusVal == 127 || exitStatusVal == 126) { |
82 | #endif |
83 | if (!cmdstat) { |
84 | terminator.Crash( |
85 | "Invalid command quit with exit status code: %d" , exitStatusVal); |
86 | } else { |
87 | StoreIntToDescriptor(length: cmdstat, value: INVALID_CL_ERR, terminator); |
88 | CheckAndCopyCharsToDescriptor(value: cmdmsg, rawValue: "Invalid command line" ); |
89 | } |
90 | } |
91 | #if defined(WIFSIGNALED) && defined(WTERMSIG) |
92 | if (WIFSIGNALED(status)) { |
93 | if (!cmdstat) { |
94 | terminator.Crash("killed by signal: %d" , WTERMSIG(status)); |
95 | } else { |
96 | StoreIntToDescriptor(length: cmdstat, value: SIGNAL_ERR, terminator); |
97 | CheckAndCopyCharsToDescriptor(value: cmdmsg, rawValue: "killed by signal" ); |
98 | } |
99 | } |
100 | #endif |
101 | #if defined(WIFSTOPPED) && defined(WSTOPSIG) |
102 | if (WIFSTOPPED(status)) { |
103 | if (!cmdstat) { |
104 | terminator.Crash("stopped by signal: %d" , WSTOPSIG(status)); |
105 | } else { |
106 | StoreIntToDescriptor(length: cmdstat, value: SIGNAL_ERR, terminator); |
107 | CheckAndCopyCharsToDescriptor(value: cmdmsg, rawValue: "stopped by signal" ); |
108 | } |
109 | } |
110 | #endif |
111 | return exitStatusVal; |
112 | } |
113 | |
114 | void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait, |
115 | const Descriptor *exitstat, const Descriptor *cmdstat, |
116 | const Descriptor *cmdmsg, const char *sourceFile, int line) { |
117 | Terminator terminator{sourceFile, line}; |
118 | char *newCmd{EnsureNullTerminated( |
119 | command.OffsetElement(), command.ElementBytes(), terminator)}; |
120 | |
121 | if (exitstat) { |
122 | RUNTIME_CHECK(terminator, IsValidIntDescriptor(exitstat)); |
123 | } |
124 | |
125 | if (cmdstat) { |
126 | RUNTIME_CHECK(terminator, IsValidIntDescriptor(cmdstat)); |
127 | // Assigned 0 as specifed in standard, if error then overwrite |
128 | StoreIntToDescriptor(cmdstat, CMD_EXECUTED, terminator); |
129 | } |
130 | |
131 | if (cmdmsg) { |
132 | RUNTIME_CHECK(terminator, IsValidCharDescriptor(cmdmsg)); |
133 | } |
134 | |
135 | if (wait) { |
136 | // either wait is not specified or wait is true: synchronous mode |
137 | int status{std::system(command: newCmd)}; |
138 | int exitStatusVal{TerminationCheck(status, cmdstat, cmdmsg, terminator)}; |
139 | // If sync, assigned processor-dependent exit status. Otherwise unchanged |
140 | CheckAndStoreIntToDescriptor(exitstat, exitStatusVal, terminator); |
141 | } else { |
142 | // Asynchronous mode |
143 | #ifdef _WIN32 |
144 | STARTUPINFO si; |
145 | PROCESS_INFORMATION pi; |
146 | ZeroMemory(&si, sizeof(si)); |
147 | si.cb = sizeof(si); |
148 | ZeroMemory(&pi, sizeof(pi)); |
149 | |
150 | // add "cmd.exe /c " to the beginning of command |
151 | const char *prefix{"cmd.exe /c " }; |
152 | char *newCmdWin{static_cast<char *>(AllocateMemoryOrCrash( |
153 | terminator, std::strlen(prefix) + std::strlen(newCmd) + 1))}; |
154 | std::strcpy(newCmdWin, prefix); |
155 | std::strcat(newCmdWin, newCmd); |
156 | |
157 | // Convert the char to wide char |
158 | const size_t sizeNeeded{mbstowcs(NULL, newCmdWin, 0) + 1}; |
159 | wchar_t *wcmd{static_cast<wchar_t *>( |
160 | AllocateMemoryOrCrash(terminator, sizeNeeded * sizeof(wchar_t)))}; |
161 | if (std::mbstowcs(wcmd, newCmdWin, sizeNeeded) == static_cast<size_t>(-1)) { |
162 | terminator.Crash("Char to wide char failed for newCmd" ); |
163 | } |
164 | FreeMemory(newCmdWin); |
165 | |
166 | if (CreateProcess(nullptr, wcmd, nullptr, nullptr, FALSE, 0, nullptr, |
167 | nullptr, &si, &pi)) { |
168 | // Close handles so it will be removed when terminated |
169 | CloseHandle(pi.hProcess); |
170 | CloseHandle(pi.hThread); |
171 | } else { |
172 | if (!cmdstat) { |
173 | terminator.Crash( |
174 | "CreateProcess failed with error code: %lu." , GetLastError()); |
175 | } else { |
176 | StoreIntToDescriptor(cmdstat, (uint32_t)GetLastError(), terminator); |
177 | CheckAndCopyCharsToDescriptor(cmdmsg, "CreateProcess failed." ); |
178 | } |
179 | } |
180 | FreeMemory(wcmd); |
181 | #else |
182 | pid_t pid{fork()}; |
183 | if (pid < 0) { |
184 | if (!cmdstat) { |
185 | terminator.Crash("Fork failed with pid: %d." , pid); |
186 | } else { |
187 | StoreIntToDescriptor(cmdstat, FORK_ERR, terminator); |
188 | CheckAndCopyCharsToDescriptor(cmdmsg, "Fork failed" ); |
189 | } |
190 | } else if (pid == 0) { |
191 | // Create a new session, let init process take care of zombie child |
192 | if (setsid() == -1) { |
193 | if (!cmdstat) { |
194 | terminator.Crash("setsid() failed with errno: %d, asynchronous " |
195 | "process initiation failed." , |
196 | errno); |
197 | } else { |
198 | StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator); |
199 | CheckAndCopyCharsToDescriptor(cmdmsg, |
200 | "setsid() failed, asynchronous process initiation failed." ); |
201 | } |
202 | exit(EXIT_FAILURE); |
203 | } |
204 | int status{std::system(command: newCmd)}; |
205 | TerminationCheck(status, cmdstat, cmdmsg, terminator); |
206 | exit(status: status); |
207 | } |
208 | #endif |
209 | } |
210 | // Deallocate memory if EnsureNullTerminated dynamically allocated memory |
211 | if (newCmd != command.OffsetElement()) { |
212 | FreeMemory(newCmd); |
213 | } |
214 | } |
215 | |
216 | } // namespace Fortran::runtime |
217 | |