================ @@ -0,0 +1,206 @@ +//===-- runtime/execute.cpp -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "flang/Runtime/execute.h" +#include "environment.h" +#include "stat.h" +#include "terminator.h" +#include "tools.h" +#include "flang/Runtime/descriptor.h" +#include <cstdlib> +#include <future> +#include <limits> +#ifdef _WIN32 +#define LEAN_AND_MEAN +#define NOMINMAX +#include <windows.h> +#else +#include <signal.h> +#include <unistd.h> +#endif + +namespace Fortran::runtime { + +// cmdstat specified in 16.9.73 +// −1 if the processor does not support command line execution, +// a processor-dependent positive value if an error condition occurs +// −2 if no error condition occurs but WAIT is present with the value false +// and the processor does not support asynchronous execution. Otherwise it is +// assigned the value 0 +enum CMD_STAT { + ASYNC_NO_SUPPORT_ERR = -2, + NO_SUPPORT_ERR = -1, + CMD_EXECUTED = 0, + FORK_ERR = 1, + EXECL_ERR = 2, + INVALID_CL_ERR = 3, + SIGNAL_ERR = 4 +}; + +// Override CopyCharsToDescriptor in tools.h, pass string directly +void CopyCharsToDescriptor(const Descriptor &value, const char *rawValue) { + CopyCharsToDescriptor(value, rawValue, std::strlen(rawValue)); +} + +void CheckAndCopyCharsToDescriptor( + const Descriptor *value, const char *rawValue) { + if (value) { + CopyCharsToDescriptor(*value, rawValue); + } +} + +void CheckAndStoreIntToDescriptor( + const Descriptor *intVal, std::int64_t value, Terminator &terminator) { + if (intVal) { + StoreIntToDescriptor(intVal, value, terminator); + } +} + +// If a condition occurs that would assign a nonzero value to CMDSTAT but +// the CMDSTAT variable is not present, error termination is initiated. +int TerminationCheck(int status, const Descriptor *cmdstat, + const Descriptor *cmdmsg, Terminator &terminator) { + if (status == -1) { + if (!cmdstat) { + terminator.Crash("Execution error with system status code: %d", status); + } else { + CheckAndStoreIntToDescriptor(cmdstat, EXECL_ERR, terminator); + CopyCharsToDescriptor(*cmdmsg, "Execution error"); + } + } +#ifdef _WIN32 + // On WIN32 API std::system returns exit status directly + int exitStatusVal{status}; + if (exitStatusVal == 1) { +#else + int exitStatusVal{WEXITSTATUS(status)}; + if (exitStatusVal == 127 || exitStatusVal == 126) { +#endif + if (!cmdstat) { + terminator.Crash( + "Invalid command quit with exit status code: %d", exitStatusVal); + } else { + CheckAndStoreIntToDescriptor(cmdstat, INVALID_CL_ERR, terminator); + CopyCharsToDescriptor(*cmdmsg, "Invalid command line"); + } + } +#if defined(WIFSIGNALED) && defined(WTERMSIG) + if (WIFSIGNALED(status)) { + if (!cmdstat) { + terminator.Crash("killed by signal: %d", WTERMSIG(status)); + } else { + CheckAndStoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator); + CopyCharsToDescriptor(*cmdmsg, "killed by signal"); + } + } +#endif +#if defined(WIFSTOPPED) && defined(WSTOPSIG) + if (WIFSTOPPED(status)) { + if (!cmdstat) { + terminator.Crash("stopped by signal: %d", WSTOPSIG(status)); + } else { + CheckAndStoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator); + CopyCharsToDescriptor(*cmdmsg, "stopped by signal"); + } + } +#endif + return exitStatusVal; +} + +void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait, + const Descriptor *exitstat, const Descriptor *cmdstat, + const Descriptor *cmdmsg, const char *sourceFile, int line) { + Terminator terminator{sourceFile, line}; + const char *newCmd{EnsureNullTerminated( + command.OffsetElement(), command.ElementBytes(), terminator)}; + + if (exitstat) { + RUNTIME_CHECK(terminator, IsValidIntDescriptor(exitstat)); + } + + if (cmdstat) { + RUNTIME_CHECK(terminator, IsValidIntDescriptor(cmdstat)); + // Assigned 0 as specifed in standard, if error then overwrite + StoreIntToDescriptor(cmdstat, CMD_EXECUTED, terminator); + } + + if (cmdmsg) { + RUNTIME_CHECK(terminator, IsValidCharDescriptor(cmdmsg)); + } + + if (wait) { + // either wait is not specified or wait is true: synchronous mode + int status{std::system(newCmd)}; + int exitStatusVal{TerminationCheck(status, cmdstat, cmdmsg, terminator)}; + // If sync, assigned processor-dependent exit status. Otherwise unchanged + CheckAndStoreIntToDescriptor(exitstat, exitStatusVal, terminator); + } else { +// Asynchronous mode +#ifdef _WIN32 + STARTUPINFO si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + // add "cmd.exe /c " to the beginning of command + const char *prefix{"cmd.exe /c "}; + char *newCmdWin{(char *)AllocateMemoryOrCrash( + terminator, std::strlen(prefix) + std::strlen(newCmd) + 1)}; + std::strcpy(newCmdWin, prefix); + std::strcat(newCmdWin, newCmd); + + // Convert the char to wide char + const size_t sizeNeeded{mbstowcs(NULL, newCmdWin, 0) + 1}; + wchar_t *wcmd{(wchar_t *)AllocateMemoryOrCrash( + terminator, sizeNeeded * sizeof(wchar_t))}; + if (std::mbstowcs(wcmd, newCmdWin, sizeNeeded) == static_cast<size_t>(-1)) { + terminator.Crash("Char to wide char failed for newCmd"); + } + FreeMemory((void *)newCmdWin); + + if (CreateProcess(nullptr, wcmd, nullptr, nullptr, FALSE, 0, nullptr, + nullptr, &si, &pi)) { + // Close handles so it will be removed when terminated + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } else { + if (!cmdstat) { + terminator.Crash( + "CreateProcess failed with error code: %lu.", GetLastError()); + } else { + StoreIntToDescriptor(cmdstat, (uint32_t)GetLastError(), terminator); + CheckAndCopyCharsToDescriptor(cmdmsg, "CreateProcess failed."); + } + } + FreeMemory((void *)wcmd); +#else + // terminated children do not become zombies + signal(SIGCHLD, SIG_IGN); ---------------- yi-wu-arm wrote:
or an alternativelydo what `gfortran` is doing: do not change the value of `exitstatus`, `cmdmsg`, and set `cmdstatus` to 0, no mater what happened. ```fortran program test_associated integer :: exitstatus, cmdstatus character(len=35) :: cmdmsg exitstatus=404 cmdstatus=303 cmdmsg="Does async execution change cmdmsg?" call execute_command_line ("InvalidCommand", .false., exitstatus, cmdstatus, cmdmsg) call sleep(1) print *, "Exist status was: ", exitstatus print *, "Command status was: ", cmdstatus print *, "Error message is: ", trim(cmdmsg) end ``` console output when `isWait=false` (async) ``` sh: 1: InvalidCommand: not found Exist status was: 404 Command status was: 0 Error message is: Does async execution change cmdmsg? ``` console output when `isWait=true` (sync) ``` sh: 1: InvalidCommand: not found Exist status was: 127 Command status was: 3 Error message is: Invalid command line ``` Note that `call sleep()` is not available on flang-new, so on llvm-test-suite https://github.com/llvm/llvm-test-suite/blob/9ca97f5027150f7e507e5ab4c56f38a29fb3c696/Fortran/gfortran/regression/execute_command_line_1.f90#L16, it would fail to compile as well, https://github.com/llvm/llvm-project/pull/74077 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits