1 //===-- runtime/execute.cpp -----------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "flang/Runtime/execute.h"
10 #include "environment.h"
12 #include "terminator.h"
14 #include "flang/Runtime/descriptor.h"
21 #include "flang/Common/windows-include.h"
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
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
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
) {
57 CopyCharsToDescriptor(*value
, rawValue
);
61 void CheckAndStoreIntToDescriptor(
62 const Descriptor
*intVal
, std::int64_t value
, Terminator
&terminator
) {
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.
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
) {
79 terminator
.Crash("Command line execution is not supported, system "
80 "returns -1 with errno ENOENT.");
82 StoreIntToDescriptor(cmdstat
, NO_SUPPORT_ERR
, terminator
);
83 CheckAndCopyCharsToDescriptor(cmdmsg
,
84 "Command line execution is not supported, system returns -1 with "
89 char msg
[]{"Execution error with system status code: -1, errno: "};
91 if (strerror_s(err_buffer
, sizeof(err_buffer
), errno
) != 0)
93 if (strerror_r(errno
, err_buffer
, sizeof(err_buffer
)) != 0)
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
);
101 terminator
.Crash(newMsg
);
103 StoreIntToDescriptor(cmdstat
, EXECL_ERR
, terminator
);
104 CheckAndCopyCharsToDescriptor(cmdmsg
, newMsg
);
111 // On WIN32 API std::system returns exit status directly
112 std::int64_t exitStatusVal
{status
};
113 if (exitStatusVal
!= 0) {
116 "Invalid command quit with exit status code: %d", exitStatusVal
);
118 StoreIntToDescriptor(cmdstat
, INVALID_CL_ERR
, terminator
);
119 CheckAndCopyCharsToDescriptor(cmdmsg
, "Invalid command line");
123 std::int64_t exitStatusVal
{WEXITSTATUS(status
)};
124 if (exitStatusVal
== 1) {
126 terminator
.Crash("Command line execution failed with exit code: 1.");
128 StoreIntToDescriptor(cmdstat
, COMMAND_EXECUTION_ERR
, terminator
);
129 CheckAndCopyCharsToDescriptor(
130 cmdmsg
, "Command line execution failed with exit code: 1.");
132 } else if (exitStatusVal
== 126) {
134 terminator
.Crash("Command cannot be executed with exit code: 126.");
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) {
142 terminator
.Crash("Command not found with exit code: 127.");
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) {
152 "Invalid command quit with exit status code: %d", exitStatusVal
);
154 StoreIntToDescriptor(cmdstat
, INVALID_CL_ERR
, terminator
);
155 CheckAndCopyCharsToDescriptor(cmdmsg
, "Invalid command line");
160 #if defined(WIFSIGNALED) && defined(WTERMSIG)
161 if (WIFSIGNALED(status
)) {
163 terminator
.Crash("Killed by signal: %d", WTERMSIG(status
));
165 StoreIntToDescriptor(cmdstat
, SIGNAL_ERR
, terminator
);
166 CheckAndCopyCharsToDescriptor(cmdmsg
, "Killed by signal");
171 #if defined(WIFSTOPPED) && defined(WSTOPSIG)
172 if (WIFSTOPPED(status
)) {
174 terminator
.Crash("Stopped by signal: %d", WSTOPSIG(status
));
176 StoreIntToDescriptor(cmdstat
, SIGNAL_ERR
, terminator
);
177 CheckAndCopyCharsToDescriptor(cmdmsg
, "Stopped by signal");
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
)};
192 RUNTIME_CHECK(terminator
, IsValidIntDescriptor(exitstat
));
196 RUNTIME_CHECK(terminator
, IsValidIntDescriptor(cmdstat
));
197 // Assigned 0 as specifed in standard, if error then overwrite
198 StoreIntToDescriptor(cmdstat
, CMD_EXECUTED
, terminator
);
202 RUNTIME_CHECK(terminator
, IsValidCharDescriptor(cmdmsg
));
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
);
216 PROCESS_INFORMATION pi
;
217 ZeroMemory(&si
, 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
);
245 "CreateProcess failed with error code: %lu.", GetLastError());
247 StoreIntToDescriptor(cmdstat
, ASYNC_NO_SUPPORT_ERR
, terminator
);
248 CheckAndCopyCharsToDescriptor(cmdmsg
, "CreateProcess failed.");
256 terminator
.Crash("Fork failed with pid: %d.", pid
);
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) {
265 terminator
.Crash("setsid() failed with errno: %d, asynchronous "
266 "process initiation failed.",
269 StoreIntToDescriptor(cmdstat
, ASYNC_NO_SUPPORT_ERR
, terminator
);
270 CheckAndCopyCharsToDescriptor(cmdmsg
,
271 "setsid() failed, asynchronous process initiation failed.");
275 std::int64_t status
{std::system(newCmd
)};
276 TerminationCheck(status
, cmdstat
, cmdmsg
, terminator
);
281 // Deallocate memory if EnsureNullTerminated dynamically allocated memory
282 if (newCmd
!= command
.OffsetElement()) {
287 } // namespace Fortran::runtime