1e2b896aaSYi Wu //===-- runtime/execute.cpp -----------------------------------------------===//
2e2b896aaSYi Wu //
3e2b896aaSYi Wu // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e2b896aaSYi Wu // See https://llvm.org/LICENSE.txt for license information.
5e2b896aaSYi Wu // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e2b896aaSYi Wu //
7e2b896aaSYi Wu //===----------------------------------------------------------------------===//
8e2b896aaSYi Wu
9e2b896aaSYi Wu #include "flang/Runtime/execute.h"
10e2b896aaSYi Wu #include "environment.h"
11e2b896aaSYi Wu #include "stat.h"
12e2b896aaSYi Wu #include "terminator.h"
13e2b896aaSYi Wu #include "tools.h"
14e2b896aaSYi Wu #include "flang/Runtime/descriptor.h"
15e2b896aaSYi Wu #include <cstdlib>
16*dfd2711fSYi Wu #include <errno.h>
17e2b896aaSYi Wu #include <future>
18e2b896aaSYi Wu #include <limits>
19*dfd2711fSYi Wu
20e2b896aaSYi Wu #ifdef _WIN32
21864d2531SSlava Zakharin #include "flang/Common/windows-include.h"
22e2b896aaSYi Wu #else
23e2b896aaSYi Wu #include <signal.h>
24a762cc21SDan McGregor #include <sys/wait.h>
25e2b896aaSYi Wu #include <unistd.h>
26e2b896aaSYi Wu #endif
27e2b896aaSYi Wu
28e2b896aaSYi Wu namespace Fortran::runtime {
29e2b896aaSYi Wu
30e2b896aaSYi Wu // cmdstat specified in 16.9.73
31e2b896aaSYi Wu // −1 if the processor does not support command line execution,
32e2b896aaSYi Wu // a processor-dependent positive value if an error condition occurs
33e2b896aaSYi Wu // −2 if no error condition occurs but WAIT is present with the value false
34e2b896aaSYi Wu // and the processor does not support asynchronous execution. Otherwise it is
35e2b896aaSYi Wu // assigned the value 0
36e2b896aaSYi Wu enum CMD_STAT {
37*dfd2711fSYi Wu ASYNC_NO_SUPPORT_ERR = -2, // system returns -1 with ENOENT
38*dfd2711fSYi Wu NO_SUPPORT_ERR = -1, // Linux setsid() returns -1
39*dfd2711fSYi Wu CMD_EXECUTED = 0, // command executed with no error
40*dfd2711fSYi Wu FORK_ERR = 1, // Linux fork() returns < 0
41*dfd2711fSYi Wu EXECL_ERR = 2, // system returns -1 with other errno
42*dfd2711fSYi Wu COMMAND_EXECUTION_ERR = 3, // exit code 1
43*dfd2711fSYi Wu COMMAND_CANNOT_EXECUTE_ERR = 4, // Linux exit code 126
44*dfd2711fSYi Wu COMMAND_NOT_FOUND_ERR = 5, // Linux exit code 127
45*dfd2711fSYi Wu INVALID_CL_ERR = 6, // cover all other non-zero exit code
46*dfd2711fSYi Wu SIGNAL_ERR = 7
47e2b896aaSYi Wu };
48e2b896aaSYi Wu
49e2b896aaSYi Wu // Override CopyCharsToDescriptor in tools.h, pass string directly
CopyCharsToDescriptor(const Descriptor & value,const char * rawValue)50e2b896aaSYi Wu void CopyCharsToDescriptor(const Descriptor &value, const char *rawValue) {
51e2b896aaSYi Wu CopyCharsToDescriptor(value, rawValue, std::strlen(rawValue));
52e2b896aaSYi Wu }
53e2b896aaSYi Wu
CheckAndCopyCharsToDescriptor(const Descriptor * value,const char * rawValue)54e2b896aaSYi Wu void CheckAndCopyCharsToDescriptor(
55e2b896aaSYi Wu const Descriptor *value, const char *rawValue) {
56e2b896aaSYi Wu if (value) {
57e2b896aaSYi Wu CopyCharsToDescriptor(*value, rawValue);
58e2b896aaSYi Wu }
59e2b896aaSYi Wu }
60e2b896aaSYi Wu
CheckAndStoreIntToDescriptor(const Descriptor * intVal,std::int64_t value,Terminator & terminator)61e2b896aaSYi Wu void CheckAndStoreIntToDescriptor(
62e2b896aaSYi Wu const Descriptor *intVal, std::int64_t value, Terminator &terminator) {
63e2b896aaSYi Wu if (intVal) {
64e2b896aaSYi Wu StoreIntToDescriptor(intVal, value, terminator);
65e2b896aaSYi Wu }
66e2b896aaSYi Wu }
67e2b896aaSYi Wu
68e2b896aaSYi Wu // If a condition occurs that would assign a nonzero value to CMDSTAT but
69e2b896aaSYi Wu // the CMDSTAT variable is not present, error termination is initiated.
TerminationCheck(std::int64_t status,const Descriptor * cmdstat,const Descriptor * cmdmsg,Terminator & terminator)70*dfd2711fSYi Wu std::int64_t TerminationCheck(std::int64_t status, const Descriptor *cmdstat,
71e2b896aaSYi Wu const Descriptor *cmdmsg, Terminator &terminator) {
72*dfd2711fSYi Wu // On both Windows and Linux, errno is set when system returns -1.
73e2b896aaSYi Wu if (status == -1) {
74*dfd2711fSYi Wu // On Windows, ENOENT means the command interpreter can't be found.
75*dfd2711fSYi Wu // On Linux, system calls execl with filepath "/bin/sh", ENOENT means the
76*dfd2711fSYi Wu // file pathname does not exist.
77*dfd2711fSYi Wu if (errno == ENOENT) {
78e2b896aaSYi Wu if (!cmdstat) {
79*dfd2711fSYi Wu terminator.Crash("Command line execution is not supported, system "
80*dfd2711fSYi Wu "returns -1 with errno ENOENT.");
81*dfd2711fSYi Wu } else {
82*dfd2711fSYi Wu StoreIntToDescriptor(cmdstat, NO_SUPPORT_ERR, terminator);
83*dfd2711fSYi Wu CheckAndCopyCharsToDescriptor(cmdmsg,
84*dfd2711fSYi Wu "Command line execution is not supported, system returns -1 with "
85*dfd2711fSYi Wu "errno ENOENT.");
86*dfd2711fSYi Wu }
87*dfd2711fSYi Wu } else {
88*dfd2711fSYi Wu char err_buffer[30];
89*dfd2711fSYi Wu char msg[]{"Execution error with system status code: -1, errno: "};
90*dfd2711fSYi Wu #ifdef _WIN32
91*dfd2711fSYi Wu if (strerror_s(err_buffer, sizeof(err_buffer), errno) != 0)
92*dfd2711fSYi Wu #else
93*dfd2711fSYi Wu if (strerror_r(errno, err_buffer, sizeof(err_buffer)) != 0)
94*dfd2711fSYi Wu #endif
95*dfd2711fSYi Wu terminator.Crash("errno to char msg failed.");
96*dfd2711fSYi Wu char *newMsg{static_cast<char *>(AllocateMemoryOrCrash(
97*dfd2711fSYi Wu terminator, std::strlen(msg) + std::strlen(err_buffer) + 1))};
98*dfd2711fSYi Wu std::strcat(newMsg, err_buffer);
99*dfd2711fSYi Wu
100*dfd2711fSYi Wu if (!cmdstat) {
101*dfd2711fSYi Wu terminator.Crash(newMsg);
102e2b896aaSYi Wu } else {
103a9309e4aSSiHuaN StoreIntToDescriptor(cmdstat, EXECL_ERR, terminator);
104*dfd2711fSYi Wu CheckAndCopyCharsToDescriptor(cmdmsg, newMsg);
105*dfd2711fSYi Wu }
106*dfd2711fSYi Wu FreeMemory(newMsg);
107e2b896aaSYi Wu }
108e2b896aaSYi Wu }
109*dfd2711fSYi Wu
110e2b896aaSYi Wu #ifdef _WIN32
111e2b896aaSYi Wu // On WIN32 API std::system returns exit status directly
112*dfd2711fSYi Wu std::int64_t exitStatusVal{status};
113*dfd2711fSYi Wu if (exitStatusVal != 0) {
1144232dd58SYi Wu if (!cmdstat) {
1154232dd58SYi Wu terminator.Crash(
1164232dd58SYi Wu "Invalid command quit with exit status code: %d", exitStatusVal);
1174232dd58SYi Wu } else {
1184232dd58SYi Wu StoreIntToDescriptor(cmdstat, INVALID_CL_ERR, terminator);
1194232dd58SYi Wu CheckAndCopyCharsToDescriptor(cmdmsg, "Invalid command line");
120e2b896aaSYi Wu }
121e2b896aaSYi Wu }
122*dfd2711fSYi Wu #else
123*dfd2711fSYi Wu std::int64_t exitStatusVal{WEXITSTATUS(status)};
124*dfd2711fSYi Wu if (exitStatusVal == 1) {
1254232dd58SYi Wu if (!cmdstat) {
126*dfd2711fSYi Wu terminator.Crash("Command line execution failed with exit code: 1.");
1274232dd58SYi Wu } else {
128*dfd2711fSYi Wu StoreIntToDescriptor(cmdstat, COMMAND_EXECUTION_ERR, terminator);
129*dfd2711fSYi Wu CheckAndCopyCharsToDescriptor(
130*dfd2711fSYi Wu cmdmsg, "Command line execution failed with exit code: 1.");
131*dfd2711fSYi Wu }
132*dfd2711fSYi Wu } else if (exitStatusVal == 126) {
133*dfd2711fSYi Wu if (!cmdstat) {
134*dfd2711fSYi Wu terminator.Crash("Command cannot be executed with exit code: 126.");
135*dfd2711fSYi Wu } else {
136*dfd2711fSYi Wu StoreIntToDescriptor(cmdstat, COMMAND_CANNOT_EXECUTE_ERR, terminator);
137*dfd2711fSYi Wu CheckAndCopyCharsToDescriptor(
138*dfd2711fSYi Wu cmdmsg, "Command cannot be executed with exit code: 126.");
139*dfd2711fSYi Wu }
140*dfd2711fSYi Wu } else if (exitStatusVal == 127) {
141*dfd2711fSYi Wu if (!cmdstat) {
142*dfd2711fSYi Wu terminator.Crash("Command not found with exit code: 127.");
143*dfd2711fSYi Wu } else {
144*dfd2711fSYi Wu StoreIntToDescriptor(cmdstat, COMMAND_NOT_FOUND_ERR, terminator);
145*dfd2711fSYi Wu CheckAndCopyCharsToDescriptor(
146*dfd2711fSYi Wu cmdmsg, "Command not found with exit code: 127.");
147*dfd2711fSYi Wu }
148*dfd2711fSYi Wu // capture all other nonzero exit code
149*dfd2711fSYi Wu } else if (exitStatusVal != 0) {
150*dfd2711fSYi Wu if (!cmdstat) {
151*dfd2711fSYi Wu terminator.Crash(
152*dfd2711fSYi Wu "Invalid command quit with exit status code: %d", exitStatusVal);
153*dfd2711fSYi Wu } else {
154*dfd2711fSYi Wu StoreIntToDescriptor(cmdstat, INVALID_CL_ERR, terminator);
155*dfd2711fSYi Wu CheckAndCopyCharsToDescriptor(cmdmsg, "Invalid command line");
1564232dd58SYi Wu }
1574232dd58SYi Wu }
1584232dd58SYi Wu #endif
159*dfd2711fSYi Wu
160*dfd2711fSYi Wu #if defined(WIFSIGNALED) && defined(WTERMSIG)
161*dfd2711fSYi Wu if (WIFSIGNALED(status)) {
162*dfd2711fSYi Wu if (!cmdstat) {
163*dfd2711fSYi Wu terminator.Crash("Killed by signal: %d", WTERMSIG(status));
164*dfd2711fSYi Wu } else {
165*dfd2711fSYi Wu StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
166*dfd2711fSYi Wu CheckAndCopyCharsToDescriptor(cmdmsg, "Killed by signal");
167*dfd2711fSYi Wu }
168*dfd2711fSYi Wu }
169*dfd2711fSYi Wu #endif
170*dfd2711fSYi Wu
171e2b896aaSYi Wu #if defined(WIFSTOPPED) && defined(WSTOPSIG)
172e2b896aaSYi Wu if (WIFSTOPPED(status)) {
173e2b896aaSYi Wu if (!cmdstat) {
174*dfd2711fSYi Wu terminator.Crash("Stopped by signal: %d", WSTOPSIG(status));
175e2b896aaSYi Wu } else {
176a9309e4aSSiHuaN StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
177*dfd2711fSYi Wu CheckAndCopyCharsToDescriptor(cmdmsg, "Stopped by signal");
178e2b896aaSYi Wu }
179e2b896aaSYi Wu }
180e2b896aaSYi Wu #endif
181e2b896aaSYi Wu return exitStatusVal;
182e2b896aaSYi Wu }
183e2b896aaSYi Wu
RTNAME(ExecuteCommandLine)184e2b896aaSYi Wu void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait,
185e2b896aaSYi Wu const Descriptor *exitstat, const Descriptor *cmdstat,
186e2b896aaSYi Wu const Descriptor *cmdmsg, const char *sourceFile, int line) {
187e2b896aaSYi Wu Terminator terminator{sourceFile, line};
1887dd4d28eSYi Wu char *newCmd{EnsureNullTerminated(
189e2b896aaSYi Wu command.OffsetElement(), command.ElementBytes(), terminator)};
190e2b896aaSYi Wu
191e2b896aaSYi Wu if (exitstat) {
192e2b896aaSYi Wu RUNTIME_CHECK(terminator, IsValidIntDescriptor(exitstat));
193e2b896aaSYi Wu }
194e2b896aaSYi Wu
195e2b896aaSYi Wu if (cmdstat) {
196e2b896aaSYi Wu RUNTIME_CHECK(terminator, IsValidIntDescriptor(cmdstat));
197e2b896aaSYi Wu // Assigned 0 as specifed in standard, if error then overwrite
198e2b896aaSYi Wu StoreIntToDescriptor(cmdstat, CMD_EXECUTED, terminator);
199e2b896aaSYi Wu }
200e2b896aaSYi Wu
201e2b896aaSYi Wu if (cmdmsg) {
202e2b896aaSYi Wu RUNTIME_CHECK(terminator, IsValidCharDescriptor(cmdmsg));
203e2b896aaSYi Wu }
204e2b896aaSYi Wu
205e2b896aaSYi Wu if (wait) {
206e2b896aaSYi Wu // either wait is not specified or wait is true: synchronous mode
207*dfd2711fSYi Wu std::int64_t status{std::system(newCmd)};
208*dfd2711fSYi Wu std::int64_t exitStatusVal{
209*dfd2711fSYi Wu TerminationCheck(status, cmdstat, cmdmsg, terminator)};
210e2b896aaSYi Wu // If sync, assigned processor-dependent exit status. Otherwise unchanged
211e2b896aaSYi Wu CheckAndStoreIntToDescriptor(exitstat, exitStatusVal, terminator);
212e2b896aaSYi Wu } else {
213e2b896aaSYi Wu // Asynchronous mode
214e2b896aaSYi Wu #ifdef _WIN32
215e2b896aaSYi Wu STARTUPINFO si;
216e2b896aaSYi Wu PROCESS_INFORMATION pi;
217e2b896aaSYi Wu ZeroMemory(&si, sizeof(si));
218e2b896aaSYi Wu si.cb = sizeof(si);
219e2b896aaSYi Wu ZeroMemory(&pi, sizeof(pi));
220e2b896aaSYi Wu
221e2b896aaSYi Wu // add "cmd.exe /c " to the beginning of command
222e2b896aaSYi Wu const char *prefix{"cmd.exe /c "};
2234aa04245SPeter Klausler char *newCmdWin{static_cast<char *>(AllocateMemoryOrCrash(
2244aa04245SPeter Klausler terminator, std::strlen(prefix) + std::strlen(newCmd) + 1))};
225e2b896aaSYi Wu std::strcpy(newCmdWin, prefix);
226e2b896aaSYi Wu std::strcat(newCmdWin, newCmd);
227e2b896aaSYi Wu
228e2b896aaSYi Wu // Convert the char to wide char
229e2b896aaSYi Wu const size_t sizeNeeded{mbstowcs(NULL, newCmdWin, 0) + 1};
2304aa04245SPeter Klausler wchar_t *wcmd{static_cast<wchar_t *>(
2314aa04245SPeter Klausler AllocateMemoryOrCrash(terminator, sizeNeeded * sizeof(wchar_t)))};
232e2b896aaSYi Wu if (std::mbstowcs(wcmd, newCmdWin, sizeNeeded) == static_cast<size_t>(-1)) {
233e2b896aaSYi Wu terminator.Crash("Char to wide char failed for newCmd");
234e2b896aaSYi Wu }
2354aa04245SPeter Klausler FreeMemory(newCmdWin);
236e2b896aaSYi Wu
237e2b896aaSYi Wu if (CreateProcess(nullptr, wcmd, nullptr, nullptr, FALSE, 0, nullptr,
238e2b896aaSYi Wu nullptr, &si, &pi)) {
239e2b896aaSYi Wu // Close handles so it will be removed when terminated
240e2b896aaSYi Wu CloseHandle(pi.hProcess);
241e2b896aaSYi Wu CloseHandle(pi.hThread);
242e2b896aaSYi Wu } else {
243e2b896aaSYi Wu if (!cmdstat) {
244e2b896aaSYi Wu terminator.Crash(
245e2b896aaSYi Wu "CreateProcess failed with error code: %lu.", GetLastError());
246e2b896aaSYi Wu } else {
247*dfd2711fSYi Wu StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator);
248e2b896aaSYi Wu CheckAndCopyCharsToDescriptor(cmdmsg, "CreateProcess failed.");
249e2b896aaSYi Wu }
250e2b896aaSYi Wu }
2514aa04245SPeter Klausler FreeMemory(wcmd);
252e2b896aaSYi Wu #else
253e2b896aaSYi Wu pid_t pid{fork()};
254e2b896aaSYi Wu if (pid < 0) {
255e2b896aaSYi Wu if (!cmdstat) {
256e2b896aaSYi Wu terminator.Crash("Fork failed with pid: %d.", pid);
257e2b896aaSYi Wu } else {
258e2b896aaSYi Wu StoreIntToDescriptor(cmdstat, FORK_ERR, terminator);
259e2b896aaSYi Wu CheckAndCopyCharsToDescriptor(cmdmsg, "Fork failed");
260e2b896aaSYi Wu }
261e2b896aaSYi Wu } else if (pid == 0) {
2625a7f9a5aSYi Wu // Create a new session, let init process take care of zombie child
2635a7f9a5aSYi Wu if (setsid() == -1) {
2645a7f9a5aSYi Wu if (!cmdstat) {
2655a7f9a5aSYi Wu terminator.Crash("setsid() failed with errno: %d, asynchronous "
2665a7f9a5aSYi Wu "process initiation failed.",
2675a7f9a5aSYi Wu errno);
2685a7f9a5aSYi Wu } else {
2695a7f9a5aSYi Wu StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator);
2705a7f9a5aSYi Wu CheckAndCopyCharsToDescriptor(cmdmsg,
2715a7f9a5aSYi Wu "setsid() failed, asynchronous process initiation failed.");
2725a7f9a5aSYi Wu }
2735a7f9a5aSYi Wu exit(EXIT_FAILURE);
2745a7f9a5aSYi Wu }
275*dfd2711fSYi Wu std::int64_t status{std::system(newCmd)};
276e2b896aaSYi Wu TerminationCheck(status, cmdstat, cmdmsg, terminator);
277e2b896aaSYi Wu exit(status);
278e2b896aaSYi Wu }
279e2b896aaSYi Wu #endif
280e2b896aaSYi Wu }
281e2b896aaSYi Wu // Deallocate memory if EnsureNullTerminated dynamically allocated memory
282e2b896aaSYi Wu if (newCmd != command.OffsetElement()) {
2834aa04245SPeter Klausler FreeMemory(newCmd);
284e2b896aaSYi Wu }
285e2b896aaSYi Wu }
286e2b896aaSYi Wu
287e2b896aaSYi Wu } // namespace Fortran::runtime
288