xref: /llvm-project/flang/runtime/execute.cpp (revision dfd2711f8f70fca45d2ddbca0eede7ad957ec307)
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