diff --git a/flang-rt/lib/runtime/execute.cpp b/flang-rt/lib/runtime/execute.cpp index 8da7069f5700..b843a0c7f463 100644 --- a/flang-rt/lib/runtime/execute.cpp +++ b/flang-rt/lib/runtime/execute.cpp @@ -111,7 +111,17 @@ std::int64_t TerminationCheck(std::int64_t status, const Descriptor *cmdstat, // On WIN32 API std::system() returns exit status directly. On other OS'es, // special status codes are handled below. std::int64_t exitStatusVal{status}; -#ifndef _WIN32 +#ifdef _WIN32 + if (status == 9009) { + // cmd.exe returns status code 9009 for "command not found" error + if (!cmdstat) { + terminator.Crash("Command not found."); + } else { + StoreIntToDescriptor(cmdstat, COMMAND_NOT_FOUND_ERR, terminator); + CheckAndCopyCharsToDescriptor(cmdmsg, "Command not found."); + } + } +#else #if defined(WIFSIGNALED) && defined(WTERMSIG) if (WIFSIGNALED(status)) { @@ -195,9 +205,36 @@ void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait, RUNTIME_CHECK(terminator, IsValidCharDescriptor(cmdmsg)); } + const char *cmd{newCmd}; +#ifdef _WIN32 + // Construct a string that looks like + // "cmd.exe /v:on /c \"mycommand & exit /b !ERRORLEVEL!\"" + // Explanantion: + // /v:on - turns delayed environment variable expansion on, so + // variables written as !VAR! are expanded at execution time + // instead of at parse time. This is required for !ERRORLEVEL! + // to reflect the current error code at the moment exit runs. + // exit /b !ERRORLEVEL! - exits the current cmd instance (/b) and + // sets its process exit code to the current ERRORLEVEL value. + // Because delayed expansion is on, !ERRORLEVEL! is evaluated at + // execution time, so this cmd instance returns the same error + // code as mycommand. + // This allows cmd.exe to either return the exit code of mycommand, or + // to return its own exit code to the caller. The code 9009 is used + // by cmd.exe to indicate "not found" condition. + const char prefix[]{"cmd.exe /v:on /c \""}; + const char suffix[]{" & exit /b !ERRORLEVEL!\""}; + const size_t newCmdWinLen{ + (sizeof(prefix) - 1) + std::strlen(newCmd) + (sizeof(suffix) - 1) + 1}; + char *newCmdWin{ + static_cast(AllocateMemoryOrCrash(terminator, newCmdWinLen))}; + std::snprintf(newCmdWin, newCmdWinLen, "%s%s%s", prefix, newCmd, suffix); + cmd = newCmdWin; +#endif + if (wait) { // either wait is not specified or wait is true: synchronous mode - std::int64_t status{std::system(newCmd)}; + std::int64_t status{std::system(cmd)}; std::int64_t exitStatusVal{ TerminationCheck(status, cmdstat, cmdmsg, terminator)}; // If sync, assigned processor-dependent exit status. Otherwise unchanged @@ -211,13 +248,6 @@ void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait, 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{static_cast(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{static_cast( @@ -225,7 +255,6 @@ void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait, if (std::mbstowcs(wcmd, newCmdWin, sizeNeeded) == static_cast(-1)) { terminator.Crash("Char to wide char failed for newCmd"); } - FreeMemory(newCmdWin); if (CreateProcessW(nullptr, wcmd, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { @@ -278,6 +307,11 @@ void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait, } #endif } + +#ifdef _WIN32 + FreeMemory(newCmdWin); +#endif + // Deallocate memory if EnsureNullTerminated dynamically allocated memory if (newCmd != command.OffsetElement()) { FreeMemory(newCmd); diff --git a/flang-rt/unittests/Runtime/CommandTest.cpp b/flang-rt/unittests/Runtime/CommandTest.cpp index 4509c9a34c79..3bed8acafc59 100644 --- a/flang-rt/unittests/Runtime/CommandTest.cpp +++ b/flang-rt/unittests/Runtime/CommandTest.cpp @@ -365,9 +365,9 @@ TEST_F(ZeroArguments, ECLNotExecutedCommandErrorSync) { RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); #ifdef _WIN32 - CheckDescriptorEqInt(exitStat.get(), 1); - CheckDescriptorEqInt(cmdStat.get(), 0); - CheckDescriptorEqStr(cmdMsg.get(), "cmd msg buffer XXXXXXXX"); + CheckDescriptorEqInt(exitStat.get(), 9009); + CheckDescriptorEqInt(cmdStat.get(), 5); + CheckDescriptorEqStr(cmdMsg.get(), "Command not found."); #else CheckDescriptorEqInt(exitStat.get(), 126); CheckDescriptorEqInt(cmdStat.get(), 4); @@ -394,9 +394,9 @@ TEST_F(ZeroArguments, ECLNotFoundCommandErrorSync) { RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); #ifdef _WIN32 - CheckDescriptorEqInt(exitStat.get(), 1); - CheckDescriptorEqInt(cmdStat.get(), 0); - CheckDescriptorEqStr(cmdMsg.get(), "unmodified buffer XXXXXXXXX"); + CheckDescriptorEqInt(exitStat.get(), 9009); + CheckDescriptorEqInt(cmdStat.get(), 5); + CheckDescriptorEqStr(cmdMsg.get(), "Command not found."); #else CheckDescriptorEqInt(exitStat.get(), 127); CheckDescriptorEqInt(cmdStat.get(), 5); @@ -412,7 +412,7 @@ TEST_F(ZeroArguments, ECLInvalidCommandTerminatedSync) { #ifdef _WIN32 EXPECT_DEATH(RTNAME(ExecuteCommandLine)( *command.get(), wait, nullptr, nullptr, cmdMsg.get()), - "Invalid command quit with exit status code: 1"); + "Command not found."); #else EXPECT_DEATH(RTNAME(ExecuteCommandLine)( *command.get(), wait, nullptr, nullptr, cmdMsg.get()), @@ -490,7 +490,7 @@ TEST_F(ZeroArguments, SystemInvalidCommandExitStat) { RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), nullptr); #ifdef _WIN32 - CheckDescriptorEqInt(exitStat.get(), 1); + CheckDescriptorEqInt(exitStat.get(), 9009); #else CheckDescriptorEqInt(exitStat.get(), 127); #endif