xref: /llvm-project/flang/runtime/execute.cpp (revision dfd2711f8f70fca45d2ddbca0eede7ad957ec307)
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 <errno.h>
17 #include <future>
18 #include <limits>
19 
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
27 
28 namespace Fortran::runtime {
29 
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
47 };
48 
49 // Override CopyCharsToDescriptor in tools.h, pass string directly
CopyCharsToDescriptor(const Descriptor & value,const char * rawValue)50 void CopyCharsToDescriptor(const Descriptor &value, const char *rawValue) {
51   CopyCharsToDescriptor(value, rawValue, std::strlen(rawValue));
52 }
53 
CheckAndCopyCharsToDescriptor(const Descriptor * value,const char * rawValue)54 void CheckAndCopyCharsToDescriptor(
55     const Descriptor *value, const char *rawValue) {
56   if (value) {
57     CopyCharsToDescriptor(*value, rawValue);
58   }
59 }
60 
CheckAndStoreIntToDescriptor(const Descriptor * intVal,std::int64_t value,Terminator & terminator)61 void CheckAndStoreIntToDescriptor(
62     const Descriptor *intVal, std::int64_t value, Terminator &terminator) {
63   if (intVal) {
64     StoreIntToDescriptor(intVal, value, terminator);
65   }
66 }
67 
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.
TerminationCheck(std::int64_t status,const Descriptor * cmdstat,const Descriptor * cmdmsg,Terminator & terminator)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.");
86       }
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);
99 
100       if (!cmdstat) {
101         terminator.Crash(newMsg);
102       } else {
103         StoreIntToDescriptor(cmdstat, EXECL_ERR, terminator);
104         CheckAndCopyCharsToDescriptor(cmdmsg, newMsg);
105       }
106       FreeMemory(newMsg);
107     }
108   }
109 
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");
120     }
121   }
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.");
131     }
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.");
139     }
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.");
147     }
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");
156     }
157   }
158 #endif
159 
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");
167     }
168   }
169 #endif
170 
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");
178     }
179   }
180 #endif
181   return exitStatusVal;
182 }
183 
RTNAME(ExecuteCommandLine)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)};
190 
191   if (exitstat) {
192     RUNTIME_CHECK(terminator, IsValidIntDescriptor(exitstat));
193   }
194 
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);
199   }
200 
201   if (cmdmsg) {
202     RUNTIME_CHECK(terminator, IsValidCharDescriptor(cmdmsg));
203   }
204 
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));
220 
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);
227 
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");
234     }
235     FreeMemory(newCmdWin);
236 
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.");
249       }
250     }
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");
260       }
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.");
272         }
273         exit(EXIT_FAILURE);
274       }
275       std::int64_t status{std::system(newCmd)};
276       TerminationCheck(status, cmdstat, cmdmsg, terminator);
277       exit(status);
278     }
279 #endif
280   }
281   // Deallocate memory if EnsureNullTerminated dynamically allocated memory
282   if (newCmd != command.OffsetElement()) {
283     FreeMemory(newCmd);
284   }
285 }
286 
287 } // namespace Fortran::runtime
288