288 lines · cpp
1//===-- lib/runtime/execute.cpp ---------------------------------*- C++ -*-===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "flang/Runtime/execute.h"10#include "flang-rt/runtime/descriptor.h"11#include "flang-rt/runtime/environment.h"12#include "flang-rt/runtime/stat.h"13#include "flang-rt/runtime/terminator.h"14#include "flang-rt/runtime/tools.h"15#include <cstdlib>16#include <errno.h>17#include <future>18#include <limits>19 20#ifdef _WIN3221#include "flang/Common/windows-include.h"22#else23#include <signal.h>24#include <sys/wait.h>25#include <unistd.h>26#endif27 28namespace Fortran::runtime {29 30// cmdstat specified in 16.9.7331// −1 if the processor does not support command line execution,32// a processor-dependent positive value if an error condition occurs33// −2 if no error condition occurs but WAIT is present with the value false34// and the processor does not support asynchronous execution. Otherwise it is35// assigned the value 036enum CMD_STAT {37 ASYNC_NO_SUPPORT_ERR = -2, // system returns -1 with ENOENT38 NO_SUPPORT_ERR = -1, // Linux setsid() returns -139 CMD_EXECUTED = 0, // command executed with no error40 FORK_ERR = 1, // Linux fork() returns < 041 EXECL_ERR = 2, // system returns -1 with other errno42 COMMAND_EXECUTION_ERR = 3, // exit code 143 COMMAND_CANNOT_EXECUTE_ERR = 4, // Linux exit code 12644 COMMAND_NOT_FOUND_ERR = 5, // Linux exit code 12745 INVALID_CL_ERR = 6, // cover all other non-zero exit code46 SIGNAL_ERR = 747};48 49// Override CopyCharsToDescriptor in tools.h, pass string directly50void CopyCharsToDescriptor(const Descriptor &value, const char *rawValue) {51 CopyCharsToDescriptor(value, rawValue, std::strlen(rawValue));52}53 54void CheckAndCopyCharsToDescriptor(55 const Descriptor *value, const char *rawValue) {56 if (value) {57 CopyCharsToDescriptor(*value, rawValue);58 }59}60 61void 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 but69// the CMDSTAT variable is not present, error termination is initiated.70std::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 the76 // 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 _WIN3291 if (strerror_s(err_buffer, sizeof(err_buffer), errno) != 0)92#else93 if (strerror_r(errno, err_buffer, sizeof(err_buffer)) != 0)94#endif95 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 _WIN32111 // On WIN32 API std::system returns exit status directly112 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#else123 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 code149 } 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#endif159 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#endif170 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#endif181 return exitStatusVal;182}183 184void 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 overwrite198 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 mode207 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 unchanged211 CheckAndStoreIntToDescriptor(exitstat, exitStatusVal, terminator);212 } else {213// Asynchronous mode214#ifdef _WIN32215 STARTUPINFOW 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 command222 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 char229 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 (CreateProcessW(nullptr, wcmd, nullptr, nullptr, FALSE, 0, nullptr,238 nullptr, &si, &pi)) {239 // Close handles so it will be removed when terminated240 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#else253 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 child263 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#endif280 }281 // Deallocate memory if EnsureNullTerminated dynamically allocated memory282 if (newCmd != command.OffsetElement()) {283 FreeMemory(newCmd);284 }285}286 287} // namespace Fortran::runtime288