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 <future>
17#include <limits>
18#ifdef _WIN32
19#include "flang/Common/windows-include.h"
20#else
21#include <signal.h>
22#include <sys/wait.h>
23#include <unistd.h>
24#endif
25
26namespace Fortran::runtime {
27
28// cmdstat specified in 16.9.73
29// −1 if the processor does not support command line execution,
30// a processor-dependent positive value if an error condition occurs
31// −2 if no error condition occurs but WAIT is present with the value false
32// and the processor does not support asynchronous execution. Otherwise it is
33// assigned the value 0
34enum CMD_STAT {
35 ASYNC_NO_SUPPORT_ERR = -2,
36 NO_SUPPORT_ERR = -1,
37 CMD_EXECUTED = 0,
38 FORK_ERR = 1,
39 EXECL_ERR = 2,
40 INVALID_CL_ERR = 3,
41 SIGNAL_ERR = 4
42};
43
44// Override CopyCharsToDescriptor in tools.h, pass string directly
45void CopyCharsToDescriptor(const Descriptor &value, const char *rawValue) {
46 CopyCharsToDescriptor(value, rawValue, std::strlen(s: rawValue));
47}
48
49void CheckAndCopyCharsToDescriptor(
50 const Descriptor *value, const char *rawValue) {
51 if (value) {
52 CopyCharsToDescriptor(value: *value, rawValue);
53 }
54}
55
56void CheckAndStoreIntToDescriptor(
57 const Descriptor *intVal, std::int64_t value, Terminator &terminator) {
58 if (intVal) {
59 StoreIntToDescriptor(intVal, value, terminator);
60 }
61}
62
63// If a condition occurs that would assign a nonzero value to CMDSTAT but
64// the CMDSTAT variable is not present, error termination is initiated.
65int TerminationCheck(int status, const Descriptor *cmdstat,
66 const Descriptor *cmdmsg, Terminator &terminator) {
67 if (status == -1) {
68 if (!cmdstat) {
69 terminator.Crash("Execution error with system status code: %d", status);
70 } else {
71 StoreIntToDescriptor(length: cmdstat, value: EXECL_ERR, terminator);
72 CheckAndCopyCharsToDescriptor(value: cmdmsg, rawValue: "Execution error");
73 }
74 }
75#ifdef _WIN32
76 // On WIN32 API std::system returns exit status directly
77 int exitStatusVal{status};
78 if (exitStatusVal == 1) {
79#else
80 int exitStatusVal{WEXITSTATUS(status)};
81 if (exitStatusVal == 127 || exitStatusVal == 126) {
82#endif
83 if (!cmdstat) {
84 terminator.Crash(
85 "Invalid command quit with exit status code: %d", exitStatusVal);
86 } else {
87 StoreIntToDescriptor(length: cmdstat, value: INVALID_CL_ERR, terminator);
88 CheckAndCopyCharsToDescriptor(value: cmdmsg, rawValue: "Invalid command line");
89 }
90 }
91#if defined(WIFSIGNALED) && defined(WTERMSIG)
92 if (WIFSIGNALED(status)) {
93 if (!cmdstat) {
94 terminator.Crash("killed by signal: %d", WTERMSIG(status));
95 } else {
96 StoreIntToDescriptor(length: cmdstat, value: SIGNAL_ERR, terminator);
97 CheckAndCopyCharsToDescriptor(value: cmdmsg, rawValue: "killed by signal");
98 }
99 }
100#endif
101#if defined(WIFSTOPPED) && defined(WSTOPSIG)
102 if (WIFSTOPPED(status)) {
103 if (!cmdstat) {
104 terminator.Crash("stopped by signal: %d", WSTOPSIG(status));
105 } else {
106 StoreIntToDescriptor(length: cmdstat, value: SIGNAL_ERR, terminator);
107 CheckAndCopyCharsToDescriptor(value: cmdmsg, rawValue: "stopped by signal");
108 }
109 }
110#endif
111 return exitStatusVal;
112}
113
114void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait,
115 const Descriptor *exitstat, const Descriptor *cmdstat,
116 const Descriptor *cmdmsg, const char *sourceFile, int line) {
117 Terminator terminator{sourceFile, line};
118 char *newCmd{EnsureNullTerminated(
119 command.OffsetElement(), command.ElementBytes(), terminator)};
120
121 if (exitstat) {
122 RUNTIME_CHECK(terminator, IsValidIntDescriptor(exitstat));
123 }
124
125 if (cmdstat) {
126 RUNTIME_CHECK(terminator, IsValidIntDescriptor(cmdstat));
127 // Assigned 0 as specifed in standard, if error then overwrite
128 StoreIntToDescriptor(cmdstat, CMD_EXECUTED, terminator);
129 }
130
131 if (cmdmsg) {
132 RUNTIME_CHECK(terminator, IsValidCharDescriptor(cmdmsg));
133 }
134
135 if (wait) {
136 // either wait is not specified or wait is true: synchronous mode
137 int status{std::system(command: newCmd)};
138 int exitStatusVal{TerminationCheck(status, cmdstat, cmdmsg, terminator)};
139 // If sync, assigned processor-dependent exit status. Otherwise unchanged
140 CheckAndStoreIntToDescriptor(exitstat, exitStatusVal, terminator);
141 } else {
142// Asynchronous mode
143#ifdef _WIN32
144 STARTUPINFO si;
145 PROCESS_INFORMATION pi;
146 ZeroMemory(&si, sizeof(si));
147 si.cb = sizeof(si);
148 ZeroMemory(&pi, sizeof(pi));
149
150 // add "cmd.exe /c " to the beginning of command
151 const char *prefix{"cmd.exe /c "};
152 char *newCmdWin{static_cast<char *>(AllocateMemoryOrCrash(
153 terminator, std::strlen(prefix) + std::strlen(newCmd) + 1))};
154 std::strcpy(newCmdWin, prefix);
155 std::strcat(newCmdWin, newCmd);
156
157 // Convert the char to wide char
158 const size_t sizeNeeded{mbstowcs(NULL, newCmdWin, 0) + 1};
159 wchar_t *wcmd{static_cast<wchar_t *>(
160 AllocateMemoryOrCrash(terminator, sizeNeeded * sizeof(wchar_t)))};
161 if (std::mbstowcs(wcmd, newCmdWin, sizeNeeded) == static_cast<size_t>(-1)) {
162 terminator.Crash("Char to wide char failed for newCmd");
163 }
164 FreeMemory(newCmdWin);
165
166 if (CreateProcess(nullptr, wcmd, nullptr, nullptr, FALSE, 0, nullptr,
167 nullptr, &si, &pi)) {
168 // Close handles so it will be removed when terminated
169 CloseHandle(pi.hProcess);
170 CloseHandle(pi.hThread);
171 } else {
172 if (!cmdstat) {
173 terminator.Crash(
174 "CreateProcess failed with error code: %lu.", GetLastError());
175 } else {
176 StoreIntToDescriptor(cmdstat, (uint32_t)GetLastError(), terminator);
177 CheckAndCopyCharsToDescriptor(cmdmsg, "CreateProcess failed.");
178 }
179 }
180 FreeMemory(wcmd);
181#else
182 pid_t pid{fork()};
183 if (pid < 0) {
184 if (!cmdstat) {
185 terminator.Crash("Fork failed with pid: %d.", pid);
186 } else {
187 StoreIntToDescriptor(cmdstat, FORK_ERR, terminator);
188 CheckAndCopyCharsToDescriptor(cmdmsg, "Fork failed");
189 }
190 } else if (pid == 0) {
191 // Create a new session, let init process take care of zombie child
192 if (setsid() == -1) {
193 if (!cmdstat) {
194 terminator.Crash("setsid() failed with errno: %d, asynchronous "
195 "process initiation failed.",
196 errno);
197 } else {
198 StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator);
199 CheckAndCopyCharsToDescriptor(cmdmsg,
200 "setsid() failed, asynchronous process initiation failed.");
201 }
202 exit(EXIT_FAILURE);
203 }
204 int status{std::system(command: newCmd)};
205 TerminationCheck(status, cmdstat, cmdmsg, terminator);
206 exit(status: status);
207 }
208#endif
209 }
210 // Deallocate memory if EnsureNullTerminated dynamically allocated memory
211 if (newCmd != command.OffsetElement()) {
212 FreeMemory(newCmd);
213 }
214}
215
216} // namespace Fortran::runtime
217

source code of flang/runtime/execute.cpp