diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2021-11-10 17:33:22 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-11-10 17:33:22 -0500 |
| commit | 8a9e518371df03b3f382e0fe869da83751fdda0b (patch) | |
| tree | 749f9c1c79acd375ec3ee97e45a10007dd6632fa /source/core/windows/slang-win-process-util.cpp | |
| parent | 95e82acc0b32c81a9c6ac39708d18a423d8c7b1e (diff) | |
Interprocess communication via pipes (#2009)
* #include an absolute path didn't work - because paths were taken to always be relative.
* Use 'Process' to communicate with an command line tool.
* Remove slang-win-stream
* Tidy up windows ProcessUtil.
* First version of BufferedReadStream.
* Windows working IPC for steams.
* Test proxy count option.
* Split Process/ProcessUtil. Process is platform dependant. ProcessUtil are functions that are platform independent.
* First implementation of Unix Process interface.
* Unix process compiles on cygwin.
* Fix typo in unix process.
* Separate unix pipe stream error of invalid access, from pipe availability.
* Fix in standard line extraction.
* Make fd non blocking.
* Fix issues with Windows Process streams.
* Added UnixPipe.
* Some fixes around UnixPipeStream.
* Make a unix stream closed explicit.
* Hack to debug linux process/stream.
* Revert to old linux pipe handling.
* Pass executable path for unit tests.
Split out CommandLine into own source.
* Small improvements in process/command line.
* Check process behavior with crash.
* Make stderr and stdout unbuffered for crash testing.
* Only turn disable buffering in crash test.
* Disable crash test on CI.
* Fix crash on clang/linux.
* Enable crash test.
Remove _appendBuffer as can use StreamUtil functionality.
Diffstat (limited to 'source/core/windows/slang-win-process-util.cpp')
| -rw-r--r-- | source/core/windows/slang-win-process-util.cpp | 333 |
1 files changed, 0 insertions, 333 deletions
diff --git a/source/core/windows/slang-win-process-util.cpp b/source/core/windows/slang-win-process-util.cpp deleted file mode 100644 index 4039361a4..000000000 --- a/source/core/windows/slang-win-process-util.cpp +++ /dev/null @@ -1,333 +0,0 @@ -// slang-win-process-util.cpp -#include "../slang-process-util.h" - -#include "../slang-string.h" -#include "../slang-string-escape-util.h" - -#ifdef _WIN32 -// Include Windows header in a way that minimized namespace pollution. -// TODO: We could try to avoid including this at all, but it would -// mean trying to hide certain struct layouts, which would add -// more dynamic allocation. -# define WIN32_LEAN_AND_MEAN -# define NOMINMAX -# include <Windows.h> -# undef WIN32_LEAN_AND_MEAN -# undef NOMINMAX -#endif - -#include <stdio.h> -#include <stdlib.h> - -namespace Slang { - -namespace { // anonymous - -struct ThreadInfo -{ - HANDLE file; - String output; -}; - -// Has behavior very similar to unique_ptr - assignment is a move. -class WinHandle -{ -public: - /// Detach the encapsulated handle. Returns the handle (which now must be externally handled) - HANDLE detach() { HANDLE handle = m_handle; m_handle = nullptr; return handle; } - - /// Return as a handle - operator HANDLE() const { return m_handle; } - - /// Assign - void operator=(HANDLE handle) { setNull(); m_handle = handle; } - void operator=(WinHandle&& rhs) { HANDLE handle = m_handle; m_handle = rhs.m_handle; rhs.m_handle = handle; } - - /// Get ready for writing - SLANG_FORCE_INLINE HANDLE* writeRef() { setNull(); return &m_handle; } - /// Get for read access - SLANG_FORCE_INLINE const HANDLE* readRef() const { return &m_handle; } - - void setNull() - { - if (m_handle) - { - CloseHandle(m_handle); - m_handle = nullptr; - } - } - - /// Ctor - WinHandle(HANDLE handle = nullptr):m_handle(handle) {} - WinHandle(WinHandle&& rhs):m_handle(rhs.m_handle) { rhs.m_handle = nullptr; } - - /// Dtor - ~WinHandle() { setNull(); } - -private: - - WinHandle(const WinHandle&) = delete; - void operator=(const WinHandle& rhs) = delete; - - HANDLE m_handle; -}; - -} // anonymous - -static DWORD WINAPI _readerThreadProc(LPVOID threadParam) -{ - ThreadInfo* info = (ThreadInfo*)threadParam; - HANDLE file = info->file; - - static const int kChunkSize = 1024; - char buffer[kChunkSize]; - - StringBuilder outputBuilder; - - // We need to re-write the output to deal with line - // endings, so we check for paired '\r' and '\n' - // characters, which may span chunks. - int prevChar = -1; - - for (;;) - { - DWORD bytesRead = 0; - BOOL readResult = ReadFile(file, buffer, kChunkSize, &bytesRead, nullptr); - - const DWORD lastError = GetLastError(); - if (lastError == ERROR_BROKEN_PIPE) - { - break; - } - - if (!readResult) - { - break; - } - - - // walk the buffer and rewrite to eliminate '\r' '\n' pairs - char* readCursor = buffer; - char const* end = buffer + bytesRead; - char* writeCursor = buffer; - - while (readCursor != end) - { - int p = prevChar; - int c = *readCursor++; - prevChar = c; - switch (c) - { - case '\r': case '\n': - // swallow input if '\r' and '\n' appear in sequence - if ((p ^ c) == ('\r' ^ '\n')) - { - // but don't swallow the next byte - prevChar = -1; - continue; - } - // always replace '\r' with '\n' - c = '\n'; - break; - - default: - break; - } - - *writeCursor++ = (char)c; - } - bytesRead = (DWORD)(writeCursor - buffer); - - // Note: Current "core" implementation gives no way to know - // the length of the buffer, so we ultimately have - // to just assume null termination... - outputBuilder.Append(buffer, bytesRead); - } - - info->output = outputBuilder.ProduceString(); - - return 0; -} - -/* static */StringEscapeHandler* ProcessUtil::getEscapeHandler() -{ - return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space); -} - -/* static */UnownedStringSlice ProcessUtil::getExecutableSuffix() -{ - return UnownedStringSlice::fromLiteral(".exe"); -} - -/* static */String ProcessUtil::getCommandLineString(const CommandLine& commandLine) -{ - auto escapeHandler = getEscapeHandler(); - - StringBuilder cmd; - StringEscapeUtil::appendMaybeQuoted(escapeHandler, commandLine.m_executable.getUnownedSlice(), cmd); - - for (const auto& arg : commandLine.m_args) - { - cmd << " "; - StringEscapeUtil::appendMaybeQuoted(escapeHandler, arg.getUnownedSlice(), cmd); - } - return cmd.ToString(); -} - -#define SLANG_RETURN_FAIL_ON_FALSE(x) if (!(x)) return SLANG_FAIL; - -/* static */SlangResult ProcessUtil::execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult) -{ - outExecuteResult.init(); - - SECURITY_ATTRIBUTES securityAttributes; - securityAttributes.nLength = sizeof(securityAttributes); - securityAttributes.lpSecurityDescriptor = nullptr; - securityAttributes.bInheritHandle = true; - - WinHandle childStdOutRead; - WinHandle childStdErrRead; - WinHandle childStdInWrite; - - // Now we can actually get around to starting a process - PROCESS_INFORMATION processInfo; - ZeroMemory(&processInfo, sizeof(processInfo)); - { - WinHandle childStdOutWrite; - WinHandle childStdErrWrite; - WinHandle childStdInRead; - - { - WinHandle childStdOutReadTmp; - WinHandle childStdErrReadTmp; - WinHandle childStdInWriteTmp; - // create stdout pipe for child process - SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdOutReadTmp.writeRef(), childStdOutWrite.writeRef(), &securityAttributes, 0)); - // create stderr pipe for child process - SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdErrReadTmp.writeRef(), childStdErrWrite.writeRef(), &securityAttributes, 0)); - // create stdin pipe for child process - SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdInRead.writeRef(), childStdInWriteTmp.writeRef(), &securityAttributes, 0)); - - HANDLE currentProcess = GetCurrentProcess(); - - // create a non-inheritable duplicate of the stdout reader - SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdOutReadTmp, currentProcess, childStdOutRead.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS)); - // create a non-inheritable duplicate of the stderr reader - SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdErrReadTmp, currentProcess, childStdErrRead.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS)); - // create a non-inheritable duplicate of the stdin writer - SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdInWriteTmp, currentProcess, childStdInWrite.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS)); - } - - - // TODO: switch to proper wide-character versions of these... - STARTUPINFOW startupInfo; - ZeroMemory(&startupInfo, sizeof(startupInfo)); - startupInfo.cb = sizeof(startupInfo); - startupInfo.hStdError = childStdErrWrite; - startupInfo.hStdOutput = childStdOutWrite; - startupInfo.hStdInput = childStdInRead; - startupInfo.dwFlags = STARTF_USESTDHANDLES; - - OSString pathBuffer; - LPCWSTR path = nullptr; - - if (commandLine.m_executableType == CommandLine::ExecutableType::Path) - { - StringBuilder cmd; - StringEscapeUtil::appendMaybeQuoted(getEscapeHandler(), commandLine.m_executable.getUnownedSlice(), cmd); - - pathBuffer = cmd.toWString(); - path = pathBuffer.begin(); - } - - // Produce the command line string - String cmdString = getCommandLineString(commandLine); - OSString cmdStringBuffer = cmdString.toWString(); - - // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa - // `CreateProcess` requires write access to this, for some reason... - BOOL success = CreateProcessW( - path, - (LPWSTR)cmdStringBuffer.begin(), - nullptr, - nullptr, - true, - CREATE_NO_WINDOW, - nullptr, // TODO: allow specifying environment variables? - nullptr, - &startupInfo, - &processInfo); - - if (!success) - { - DWORD err = GetLastError(); - SLANG_UNUSED(err); - - return SLANG_FAIL; - } - - // close handles we are now done with - CloseHandle(processInfo.hThread); - } - - // Create a thread to read from the child's stdout. - ThreadInfo stdOutThreadInfo; - stdOutThreadInfo.file = childStdOutRead; - WinHandle stdOutThread = CreateThread(nullptr, 0, &_readerThreadProc, (LPVOID)&stdOutThreadInfo, 0, nullptr); - - // Create a thread to read from the child's stderr. - ThreadInfo stdErrThreadInfo; - stdErrThreadInfo.file = childStdErrRead; - WinHandle stdErrThread = CreateThread(nullptr, 0, &_readerThreadProc, (LPVOID)&stdErrThreadInfo, 0, nullptr); - - // wait for the process to exit - // TODO: set a timeout as a safety measure... - WaitForSingleObject(processInfo.hProcess, INFINITE); - - // get exit code for process - // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess - - DWORD childExitCode = 0; - if (!GetExitCodeProcess(processInfo.hProcess, &childExitCode)) - { - // TODO(JS): Do we want to close here? It seems plausible because just because reading the exit code failed, doesn't mean the handle is closed - CloseHandle(processInfo.hProcess); - - return SLANG_FAIL; - } - - // wait for the reader threads - WaitForSingleObject(stdOutThread, INFINITE); - WaitForSingleObject(stdErrThread, INFINITE); - - CloseHandle(processInfo.hProcess); - - outExecuteResult.standardOutput = stdOutThreadInfo.output; - outExecuteResult.standardError = stdErrThreadInfo.output; - outExecuteResult.resultCode = childExitCode; - - return SLANG_OK; -} - -static uint64_t _getClockFrequency() -{ - LARGE_INTEGER timerFrequency; - QueryPerformanceFrequency(&timerFrequency); - return timerFrequency.QuadPart; -} - -static const uint64_t g_frequency = _getClockFrequency(); - -/* static */uint64_t ProcessUtil::getClockFrequency() -{ - return g_frequency; -} - -/* static */uint64_t ProcessUtil::getClockTick() -{ - LARGE_INTEGER counter; - QueryPerformanceCounter(&counter); - return counter.QuadPart; -} - -} |
