Skip to content

[flang] Fix execute_command_line cmdstat is not set when error occurs #93023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 21, 2024
19 changes: 10 additions & 9 deletions flang/docs/Intrinsics.md
Original file line number Diff line number Diff line change
Expand Up @@ -893,16 +893,17 @@ used in constant expressions have currently no folding support at all.
##### `CMDSTAT`:

- Synchronous execution:
- -2: No error condition occurs, but `WAIT` is present with the value `false`, and the processor does not support asynchronous execution.
- -1: The processor does not support command line execution.
- -2: `ASYNC_NO_SUPPORT_ERR` - No error condition occurs, but `WAIT` is present with the value `false`, and the processor does not support asynchronous execution.
- -1: `NO_SUPPORT_ERR` - The processor does not support command line execution. (system returns -1 with errno `ENOENT`)
- 0: `CMD_EXECUTED` - Command executed with no error.
- \+ (positive value): An error condition occurs.
- 1: Fork Error (occurs only on POSIX-compatible systems).
- 2: Execution Error (command exits with status -1).
- 3: Invalid Command Error (determined by the exit code depending on the system).
- On Windows: exit code is 1.
- On POSIX-compatible systems: exit code is 127 or 126.
- 4: Signal error (either stopped or killed by signal, occurs only on POSIX-compatible systems).
- 0: Otherwise.
- 1: `FORK_ERR` - Fork Error (occurs only on POSIX-compatible systems).
- 2: `EXECL_ERR` - Execution Error (system returns -1 with other errno).
- 3: `COMMAND_EXECUTION_ERR` - Invalid Command Error (exit code 1).
- 4: `COMMAND_CANNOT_EXECUTE_ERR` - Command Cannot Execute Error (Linux exit code 126).
- 5: `COMMAND_NOT_FOUND_ERR` - Command Not Found Error (Linux exit code 127).
- 6: `INVALID_CL_ERR` - Invalid Command Line Error (covers all other non-zero exit codes).
- 7: `SIGNAL_ERR` - Signal error (either stopped or killed by signal, occurs only on POSIX-compatible systems).
- Asynchronous execution:
- 0 will always be assigned.

Expand Down
121 changes: 96 additions & 25 deletions flang/runtime/execute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
#include "tools.h"
#include "flang/Runtime/descriptor.h"
#include <cstdlib>
#include <errno.h>
#include <future>
#include <limits>

#ifdef _WIN32
#include "flang/Common/windows-include.h"
#else
Expand All @@ -32,13 +34,16 @@ namespace Fortran::runtime {
// 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
ASYNC_NO_SUPPORT_ERR = -2, // system returns -1 with ENOENT
NO_SUPPORT_ERR = -1, // Linux setsid() returns -1
CMD_EXECUTED = 0, // command executed with no error
FORK_ERR = 1, // Linux fork() returns < 0
EXECL_ERR = 2, // system returns -1 with other errno
COMMAND_EXECUTION_ERR = 3, // exit code 1
COMMAND_CANNOT_EXECUTE_ERR = 4, // Linux exit code 126
COMMAND_NOT_FOUND_ERR = 5, // Linux exit code 127
INVALID_CL_ERR = 6, // cover all other non-zero exit code
SIGNAL_ERR = 7
};

// Override CopyCharsToDescriptor in tools.h, pass string directly
Expand All @@ -62,24 +67,86 @@ void CheckAndStoreIntToDescriptor(

// 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,
std::int64_t TerminationCheck(std::int64_t status, const Descriptor *cmdstat,
const Descriptor *cmdmsg, Terminator &terminator) {
// On both Windows and Linux, errno is set when system returns -1.
if (status == -1) {
if (!cmdstat) {
terminator.Crash("Execution error with system status code: %d", status);
// On Windows, ENOENT means the command interpreter can't be found.
// On Linux, system calls execl with filepath "/bin/sh", ENOENT means the
// file pathname does not exist.
if (errno == ENOENT) {
if (!cmdstat) {
terminator.Crash("Command line execution is not supported, system "
"returns -1 with errno ENOENT.");
} else {
StoreIntToDescriptor(cmdstat, NO_SUPPORT_ERR, terminator);
CheckAndCopyCharsToDescriptor(cmdmsg,
"Command line execution is not supported, system returns -1 with "
"errno ENOENT.");
}
} else {
StoreIntToDescriptor(cmdstat, EXECL_ERR, terminator);
CheckAndCopyCharsToDescriptor(cmdmsg, "Execution error");
char err_buffer[30];
char msg[]{"Execution error with system status code: -1, errno: "};
#ifdef _WIN32
if (strerror_s(err_buffer, sizeof(err_buffer), errno) != 0)
#else
if (strerror_r(errno, err_buffer, sizeof(err_buffer)) != 0)
#endif
terminator.Crash("errno to char msg failed.");
char *newMsg{static_cast<char *>(AllocateMemoryOrCrash(
terminator, std::strlen(msg) + std::strlen(err_buffer) + 1))};
std::strcat(newMsg, err_buffer);

if (!cmdstat) {
terminator.Crash(newMsg);
} else {
StoreIntToDescriptor(cmdstat, EXECL_ERR, terminator);
CheckAndCopyCharsToDescriptor(cmdmsg, newMsg);
}
FreeMemory(newMsg);
}
}

#ifdef _WIN32
// On WIN32 API std::system returns exit status directly
int exitStatusVal{status};
if (exitStatusVal == 1) {
std::int64_t exitStatusVal{status};
if (exitStatusVal != 0) {
if (!cmdstat) {
terminator.Crash(
"Invalid command quit with exit status code: %d", exitStatusVal);
} else {
StoreIntToDescriptor(cmdstat, INVALID_CL_ERR, terminator);
CheckAndCopyCharsToDescriptor(cmdmsg, "Invalid command line");
}
}
#else
int exitStatusVal{WEXITSTATUS(status)};
if (exitStatusVal == 127 || exitStatusVal == 126) {
#endif
std::int64_t exitStatusVal{WEXITSTATUS(status)};
if (exitStatusVal == 1) {
if (!cmdstat) {
terminator.Crash("Command line execution failed with exit code: 1.");
} else {
StoreIntToDescriptor(cmdstat, COMMAND_EXECUTION_ERR, terminator);
CheckAndCopyCharsToDescriptor(
cmdmsg, "Command line execution failed with exit code: 1.");
}
} else if (exitStatusVal == 126) {
if (!cmdstat) {
terminator.Crash("Command cannot be executed with exit code: 126.");
} else {
StoreIntToDescriptor(cmdstat, COMMAND_CANNOT_EXECUTE_ERR, terminator);
CheckAndCopyCharsToDescriptor(
cmdmsg, "Command cannot be executed with exit code: 126.");
}
} else if (exitStatusVal == 127) {
if (!cmdstat) {
terminator.Crash("Command not found with exit code: 127.");
} else {
StoreIntToDescriptor(cmdstat, COMMAND_NOT_FOUND_ERR, terminator);
CheckAndCopyCharsToDescriptor(
cmdmsg, "Command not found with exit code: 127.");
}
// capture all other nonzero exit code
} else if (exitStatusVal != 0) {
if (!cmdstat) {
terminator.Crash(
"Invalid command quit with exit status code: %d", exitStatusVal);
Expand All @@ -88,23 +155,26 @@ int TerminationCheck(int status, const Descriptor *cmdstat,
CheckAndCopyCharsToDescriptor(cmdmsg, "Invalid command line");
}
}
#endif

#if defined(WIFSIGNALED) && defined(WTERMSIG)
if (WIFSIGNALED(status)) {
if (!cmdstat) {
terminator.Crash("killed by signal: %d", WTERMSIG(status));
terminator.Crash("Killed by signal: %d", WTERMSIG(status));
} else {
StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
CheckAndCopyCharsToDescriptor(cmdmsg, "killed by signal");
CheckAndCopyCharsToDescriptor(cmdmsg, "Killed by signal");
}
}
#endif

#if defined(WIFSTOPPED) && defined(WSTOPSIG)
if (WIFSTOPPED(status)) {
if (!cmdstat) {
terminator.Crash("stopped by signal: %d", WSTOPSIG(status));
terminator.Crash("Stopped by signal: %d", WSTOPSIG(status));
} else {
StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
CheckAndCopyCharsToDescriptor(cmdmsg, "stopped by signal");
CheckAndCopyCharsToDescriptor(cmdmsg, "Stopped by signal");
}
}
#endif
Expand Down Expand Up @@ -134,8 +204,9 @@ void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait,

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)};
std::int64_t status{std::system(newCmd)};
std::int64_t exitStatusVal{
TerminationCheck(status, cmdstat, cmdmsg, terminator)};
// If sync, assigned processor-dependent exit status. Otherwise unchanged
CheckAndStoreIntToDescriptor(exitstat, exitStatusVal, terminator);
} else {
Expand Down Expand Up @@ -173,7 +244,7 @@ void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait,
terminator.Crash(
"CreateProcess failed with error code: %lu.", GetLastError());
} else {
StoreIntToDescriptor(cmdstat, (uint32_t)GetLastError(), terminator);
StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator);
CheckAndCopyCharsToDescriptor(cmdmsg, "CreateProcess failed.");
}
}
Expand Down Expand Up @@ -201,7 +272,7 @@ void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait,
}
exit(EXIT_FAILURE);
}
int status{std::system(newCmd)};
std::int64_t status{std::system(newCmd)};
TerminationCheck(status, cmdstat, cmdmsg, terminator);
exit(status);
}
Expand Down
82 changes: 65 additions & 17 deletions flang/unittests/Runtime/CommandTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ TEST_F(ZeroArguments, GetCommand) { CheckCommandValue(commandOnlyArgv, 1); }
TEST_F(ZeroArguments, ECLValidCommandAndPadSync) {
OwningPtr<Descriptor> command{CharDescriptor("echo hi")};
bool wait{true};
OwningPtr<Descriptor> exitStat{EmptyIntDescriptor()};
OwningPtr<Descriptor> cmdStat{EmptyIntDescriptor()};
OwningPtr<Descriptor> exitStat{IntDescriptor(404)};
OwningPtr<Descriptor> cmdStat{IntDescriptor(202)};
OwningPtr<Descriptor> cmdMsg{CharDescriptor("No change")};

RTNAME(ExecuteCommandLine)
Expand All @@ -339,40 +339,91 @@ TEST_F(ZeroArguments, ECLValidCommandStatusSetSync) {
CheckDescriptorEqStr(cmdMsg.get(), "No change");
}

TEST_F(ZeroArguments, ECLInvalidCommandErrorSync) {
OwningPtr<Descriptor> command{CharDescriptor("InvalidCommand")};
TEST_F(ZeroArguments, ECLGeneralErrorCommandErrorSync) {
OwningPtr<Descriptor> command{CharDescriptor("cat GeneralErrorCommand")};
bool wait{true};
OwningPtr<Descriptor> exitStat{IntDescriptor(404)};
OwningPtr<Descriptor> cmdStat{IntDescriptor(202)};
OwningPtr<Descriptor> cmdMsg{CharDescriptor("cmd msg buffer XXXXXXXXXXXXXX")};

RTNAME(ExecuteCommandLine)
(*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get());
#ifdef _WIN32
CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 1);
CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 6);
CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXXXXXXXX");
#else
CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 1);
CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 3);
CheckDescriptorEqStr(cmdMsg.get(), "Command line execution failed");
#endif
}

TEST_F(ZeroArguments, ECLNotExecutedCommandErrorSync) {
OwningPtr<Descriptor> command{CharDescriptor(
"touch NotExecutedCommandFile && chmod -x NotExecutedCommandFile && "
"./NotExecutedCommandFile")};
bool wait{true};
OwningPtr<Descriptor> exitStat{IntDescriptor(404)};
OwningPtr<Descriptor> cmdStat{IntDescriptor(202)};
OwningPtr<Descriptor> cmdMsg{CharDescriptor("cmd msg buffer XXXXXXXX")};

RTNAME(ExecuteCommandLine)
(*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get());
#ifdef _WIN32
CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 1);
CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 6);
CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXX");
#else
CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 126);
CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 4);
CheckDescriptorEqStr(cmdMsg.get(), "Command cannot be execu");
// removing the file only on Linux (file is not created on Win)
OwningPtr<Descriptor> commandClean{
CharDescriptor("rm -f NotExecutedCommandFile")};
OwningPtr<Descriptor> cmdMsgNoErr{CharDescriptor("No Error")};
RTNAME(ExecuteCommandLine)
(*commandClean.get(), wait, exitStat.get(), cmdStat.get(), cmdMsgNoErr.get());
CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 0);
CheckDescriptorEqStr(cmdMsgNoErr.get(), "No Error");
CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 0);
#endif
}

TEST_F(ZeroArguments, ECLNotFoundCommandErrorSync) {
OwningPtr<Descriptor> command{CharDescriptor("NotFoundCommand")};
bool wait{true};
OwningPtr<Descriptor> exitStat{IntDescriptor(404)};
OwningPtr<Descriptor> cmdStat{IntDescriptor(202)};
OwningPtr<Descriptor> cmdMsg{CharDescriptor("Message ChangedXXXXXXXXX")};
OwningPtr<Descriptor> cmdMsg{CharDescriptor("unmodified buffer XXXXXXXXX")};

RTNAME(ExecuteCommandLine)
(*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get());
#ifdef _WIN32
CheckDescriptorEqInt(exitStat.get(), 1);
CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 1);
CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 6);
CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXXXXXX");
#else
CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 127);
CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 5);
CheckDescriptorEqStr(cmdMsg.get(), "Command not found with exit");
#endif
CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 3);
CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXXX");
}

TEST_F(ZeroArguments, ECLInvalidCommandTerminatedSync) {
OwningPtr<Descriptor> command{CharDescriptor("InvalidCommand")};
bool wait{true};
OwningPtr<Descriptor> exitStat{IntDescriptor(404)};
OwningPtr<Descriptor> cmdMsg{CharDescriptor("No Change")};

#ifdef _WIN32
EXPECT_DEATH(RTNAME(ExecuteCommandLine)(
*command.get(), wait, exitStat.get(), nullptr, cmdMsg.get()),
*command.get(), wait, nullptr, nullptr, cmdMsg.get()),
"Invalid command quit with exit status code: 1");
#else
EXPECT_DEATH(RTNAME(ExecuteCommandLine)(
*command.get(), wait, exitStat.get(), nullptr, cmdMsg.get()),
"Invalid command quit with exit status code: 127");
*command.get(), wait, nullptr, nullptr, cmdMsg.get()),
"Command not found with exit code: 127.");
#endif
CheckDescriptorEqInt(exitStat.get(), 404);
CheckDescriptorEqStr(cmdMsg.get(), "No Change");
}

Expand All @@ -394,13 +445,10 @@ TEST_F(ZeroArguments, ECLValidCommandAndExitStatNoChangeAndCMDStatusSetAsync) {
TEST_F(ZeroArguments, ECLInvalidCommandParentNotTerminatedAsync) {
OwningPtr<Descriptor> command{CharDescriptor("InvalidCommand")};
bool wait{false};
OwningPtr<Descriptor> exitStat{IntDescriptor(404)};
OwningPtr<Descriptor> cmdMsg{CharDescriptor("No change")};

EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)(
*command.get(), wait, exitStat.get(), nullptr, cmdMsg.get()));

CheckDescriptorEqInt(exitStat.get(), 404);
*command.get(), wait, nullptr, nullptr, cmdMsg.get()));
CheckDescriptorEqStr(cmdMsg.get(), "No change");
}

Expand Down
Loading