llvm-project
287 строк · 9.9 Кб
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
28namespace 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
36enum CMD_STAT {
37ASYNC_NO_SUPPORT_ERR = -2, // system returns -1 with ENOENT
38NO_SUPPORT_ERR = -1, // Linux setsid() returns -1
39CMD_EXECUTED = 0, // command executed with no error
40FORK_ERR = 1, // Linux fork() returns < 0
41EXECL_ERR = 2, // system returns -1 with other errno
42COMMAND_EXECUTION_ERR = 3, // exit code 1
43COMMAND_CANNOT_EXECUTE_ERR = 4, // Linux exit code 126
44COMMAND_NOT_FOUND_ERR = 5, // Linux exit code 127
45INVALID_CL_ERR = 6, // cover all other non-zero exit code
46SIGNAL_ERR = 7
47};
48
49// Override CopyCharsToDescriptor in tools.h, pass string directly
50void CopyCharsToDescriptor(const Descriptor &value, const char *rawValue) {
51CopyCharsToDescriptor(value, rawValue, std::strlen(rawValue));
52}
53
54void CheckAndCopyCharsToDescriptor(
55const Descriptor *value, const char *rawValue) {
56if (value) {
57CopyCharsToDescriptor(*value, rawValue);
58}
59}
60
61void CheckAndStoreIntToDescriptor(
62const Descriptor *intVal, std::int64_t value, Terminator &terminator) {
63if (intVal) {
64StoreIntToDescriptor(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.
70std::int64_t TerminationCheck(std::int64_t status, const Descriptor *cmdstat,
71const Descriptor *cmdmsg, Terminator &terminator) {
72// On both Windows and Linux, errno is set when system returns -1.
73if (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.
77if (errno == ENOENT) {
78if (!cmdstat) {
79terminator.Crash("Command line execution is not supported, system "
80"returns -1 with errno ENOENT.");
81} else {
82StoreIntToDescriptor(cmdstat, NO_SUPPORT_ERR, terminator);
83CheckAndCopyCharsToDescriptor(cmdmsg,
84"Command line execution is not supported, system returns -1 with "
85"errno ENOENT.");
86}
87} else {
88char err_buffer[30];
89char msg[]{"Execution error with system status code: -1, errno: "};
90#ifdef _WIN32
91if (strerror_s(err_buffer, sizeof(err_buffer), errno) != 0)
92#else
93if (strerror_r(errno, err_buffer, sizeof(err_buffer)) != 0)
94#endif
95terminator.Crash("errno to char msg failed.");
96char *newMsg{static_cast<char *>(AllocateMemoryOrCrash(
97terminator, std::strlen(msg) + std::strlen(err_buffer) + 1))};
98std::strcat(newMsg, err_buffer);
99
100if (!cmdstat) {
101terminator.Crash(newMsg);
102} else {
103StoreIntToDescriptor(cmdstat, EXECL_ERR, terminator);
104CheckAndCopyCharsToDescriptor(cmdmsg, newMsg);
105}
106FreeMemory(newMsg);
107}
108}
109
110#ifdef _WIN32
111// On WIN32 API std::system returns exit status directly
112std::int64_t exitStatusVal{status};
113if (exitStatusVal != 0) {
114if (!cmdstat) {
115terminator.Crash(
116"Invalid command quit with exit status code: %d", exitStatusVal);
117} else {
118StoreIntToDescriptor(cmdstat, INVALID_CL_ERR, terminator);
119CheckAndCopyCharsToDescriptor(cmdmsg, "Invalid command line");
120}
121}
122#else
123std::int64_t exitStatusVal{WEXITSTATUS(status)};
124if (exitStatusVal == 1) {
125if (!cmdstat) {
126terminator.Crash("Command line execution failed with exit code: 1.");
127} else {
128StoreIntToDescriptor(cmdstat, COMMAND_EXECUTION_ERR, terminator);
129CheckAndCopyCharsToDescriptor(
130cmdmsg, "Command line execution failed with exit code: 1.");
131}
132} else if (exitStatusVal == 126) {
133if (!cmdstat) {
134terminator.Crash("Command cannot be executed with exit code: 126.");
135} else {
136StoreIntToDescriptor(cmdstat, COMMAND_CANNOT_EXECUTE_ERR, terminator);
137CheckAndCopyCharsToDescriptor(
138cmdmsg, "Command cannot be executed with exit code: 126.");
139}
140} else if (exitStatusVal == 127) {
141if (!cmdstat) {
142terminator.Crash("Command not found with exit code: 127.");
143} else {
144StoreIntToDescriptor(cmdstat, COMMAND_NOT_FOUND_ERR, terminator);
145CheckAndCopyCharsToDescriptor(
146cmdmsg, "Command not found with exit code: 127.");
147}
148// capture all other nonzero exit code
149} else if (exitStatusVal != 0) {
150if (!cmdstat) {
151terminator.Crash(
152"Invalid command quit with exit status code: %d", exitStatusVal);
153} else {
154StoreIntToDescriptor(cmdstat, INVALID_CL_ERR, terminator);
155CheckAndCopyCharsToDescriptor(cmdmsg, "Invalid command line");
156}
157}
158#endif
159
160#if defined(WIFSIGNALED) && defined(WTERMSIG)
161if (WIFSIGNALED(status)) {
162if (!cmdstat) {
163terminator.Crash("Killed by signal: %d", WTERMSIG(status));
164} else {
165StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
166CheckAndCopyCharsToDescriptor(cmdmsg, "Killed by signal");
167}
168}
169#endif
170
171#if defined(WIFSTOPPED) && defined(WSTOPSIG)
172if (WIFSTOPPED(status)) {
173if (!cmdstat) {
174terminator.Crash("Stopped by signal: %d", WSTOPSIG(status));
175} else {
176StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
177CheckAndCopyCharsToDescriptor(cmdmsg, "Stopped by signal");
178}
179}
180#endif
181return exitStatusVal;
182}
183
184void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait,
185const Descriptor *exitstat, const Descriptor *cmdstat,
186const Descriptor *cmdmsg, const char *sourceFile, int line) {
187Terminator terminator{sourceFile, line};
188char *newCmd{EnsureNullTerminated(
189command.OffsetElement(), command.ElementBytes(), terminator)};
190
191if (exitstat) {
192RUNTIME_CHECK(terminator, IsValidIntDescriptor(exitstat));
193}
194
195if (cmdstat) {
196RUNTIME_CHECK(terminator, IsValidIntDescriptor(cmdstat));
197// Assigned 0 as specifed in standard, if error then overwrite
198StoreIntToDescriptor(cmdstat, CMD_EXECUTED, terminator);
199}
200
201if (cmdmsg) {
202RUNTIME_CHECK(terminator, IsValidCharDescriptor(cmdmsg));
203}
204
205if (wait) {
206// either wait is not specified or wait is true: synchronous mode
207std::int64_t status{std::system(newCmd)};
208std::int64_t exitStatusVal{
209TerminationCheck(status, cmdstat, cmdmsg, terminator)};
210// If sync, assigned processor-dependent exit status. Otherwise unchanged
211CheckAndStoreIntToDescriptor(exitstat, exitStatusVal, terminator);
212} else {
213// Asynchronous mode
214#ifdef _WIN32
215STARTUPINFO si;
216PROCESS_INFORMATION pi;
217ZeroMemory(&si, sizeof(si));
218si.cb = sizeof(si);
219ZeroMemory(&pi, sizeof(pi));
220
221// add "cmd.exe /c " to the beginning of command
222const char *prefix{"cmd.exe /c "};
223char *newCmdWin{static_cast<char *>(AllocateMemoryOrCrash(
224terminator, std::strlen(prefix) + std::strlen(newCmd) + 1))};
225std::strcpy(newCmdWin, prefix);
226std::strcat(newCmdWin, newCmd);
227
228// Convert the char to wide char
229const size_t sizeNeeded{mbstowcs(NULL, newCmdWin, 0) + 1};
230wchar_t *wcmd{static_cast<wchar_t *>(
231AllocateMemoryOrCrash(terminator, sizeNeeded * sizeof(wchar_t)))};
232if (std::mbstowcs(wcmd, newCmdWin, sizeNeeded) == static_cast<size_t>(-1)) {
233terminator.Crash("Char to wide char failed for newCmd");
234}
235FreeMemory(newCmdWin);
236
237if (CreateProcess(nullptr, wcmd, nullptr, nullptr, FALSE, 0, nullptr,
238nullptr, &si, &pi)) {
239// Close handles so it will be removed when terminated
240CloseHandle(pi.hProcess);
241CloseHandle(pi.hThread);
242} else {
243if (!cmdstat) {
244terminator.Crash(
245"CreateProcess failed with error code: %lu.", GetLastError());
246} else {
247StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator);
248CheckAndCopyCharsToDescriptor(cmdmsg, "CreateProcess failed.");
249}
250}
251FreeMemory(wcmd);
252#else
253pid_t pid{fork()};
254if (pid < 0) {
255if (!cmdstat) {
256terminator.Crash("Fork failed with pid: %d.", pid);
257} else {
258StoreIntToDescriptor(cmdstat, FORK_ERR, terminator);
259CheckAndCopyCharsToDescriptor(cmdmsg, "Fork failed");
260}
261} else if (pid == 0) {
262// Create a new session, let init process take care of zombie child
263if (setsid() == -1) {
264if (!cmdstat) {
265terminator.Crash("setsid() failed with errno: %d, asynchronous "
266"process initiation failed.",
267errno);
268} else {
269StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator);
270CheckAndCopyCharsToDescriptor(cmdmsg,
271"setsid() failed, asynchronous process initiation failed.");
272}
273exit(EXIT_FAILURE);
274}
275std::int64_t status{std::system(newCmd)};
276TerminationCheck(status, cmdstat, cmdmsg, terminator);
277exit(status);
278}
279#endif
280}
281// Deallocate memory if EnsureNullTerminated dynamically allocated memory
282if (newCmd != command.OffsetElement()) {
283FreeMemory(newCmd);
284}
285}
286
287} // namespace Fortran::runtime
288