summaryrefslogtreecommitdiffstats
path: root/source/core/windows/slang-win-process.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/core/windows/slang-win-process.cpp')
-rw-r--r--source/core/windows/slang-win-process.cpp495
1 files changed, 495 insertions, 0 deletions
diff --git a/source/core/windows/slang-win-process.cpp b/source/core/windows/slang-win-process.cpp
new file mode 100644
index 000000000..57eb209c3
--- /dev/null
+++ b/source/core/windows/slang-win-process.cpp
@@ -0,0 +1,495 @@
+// slang-win-process-util.cpp
+#include "../slang-process.h"
+
+#include "../slang-string.h"
+#include "../slang-string-escape-util.h"
+#include "../slang-string-util.h"
+#include "../slang-process-util.h"
+
+#include "../../../slang-com-helper.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>
+
+#ifndef SLANG_RETURN_FAIL_ON_FALSE
+# define SLANG_RETURN_FAIL_ON_FALSE(x) if (!(x)) return SLANG_FAIL;
+#endif
+
+namespace Slang {
+
+// 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;
+ }
+ }
+ bool isNull() const { return 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;
+};
+
+/* A simple Stream implementation of a File HANDLE (or Pipe). Note that currently does not allow getPosition/seek/atEnd */
+class WinPipeStream : public Stream
+{
+public:
+ typedef WinPipeStream ThisType;
+
+ // Stream
+ virtual Int64 getPosition() SLANG_OVERRIDE { return 0; }
+ virtual SlangResult seek(SeekOrigin origin, Int64 offset) SLANG_OVERRIDE { SLANG_UNUSED(origin); SLANG_UNUSED(offset); return SLANG_E_NOT_AVAILABLE; }
+ virtual SlangResult read(void* buffer, size_t length, size_t& outReadBytes) SLANG_OVERRIDE;
+ virtual SlangResult write(const void* buffer, size_t length) SLANG_OVERRIDE;
+ virtual bool isEnd() SLANG_OVERRIDE { return m_streamHandle.isNull(); }
+ virtual bool canRead() SLANG_OVERRIDE { return _has(FileAccess::Read) && !m_streamHandle.isNull(); }
+ virtual bool canWrite() SLANG_OVERRIDE { return _has(FileAccess::Write) && !m_streamHandle.isNull(); }
+ virtual void close() SLANG_OVERRIDE;
+ virtual SlangResult flush() SLANG_OVERRIDE;
+
+ WinPipeStream(HANDLE handle, FileAccess access, bool isOwned = true);
+
+ ~WinPipeStream() { close(); }
+
+protected:
+
+ bool _has(FileAccess access) const { return (Index(access) & Index(m_access)) != 0; }
+
+ SlangResult _updateState(BOOL res);
+
+ FileAccess m_access = FileAccess::None;
+ WinHandle m_streamHandle;
+ bool m_isOwned;
+};
+
+
+class WinProcess : public Process
+{
+public:
+
+ virtual bool isTerminated() SLANG_OVERRIDE;
+ virtual void waitForTermination() SLANG_OVERRIDE;
+
+ WinProcess(HANDLE handle, Stream* const* streams) :
+ m_processHandle(handle)
+ {
+ for (Index i = 0; i < Index(Process::StreamType::CountOf); ++i)
+ {
+ m_streams[i] = streams[i];
+ }
+ }
+
+protected:
+
+ void _hasTerminated();
+ WinHandle m_processHandle; ///< If not set the process has terminated
+};
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!! WinPipeStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+WinPipeStream::WinPipeStream(HANDLE handle, FileAccess access, bool isOwned) :
+ m_streamHandle(handle),
+ m_access(access),
+ m_isOwned(isOwned)
+{
+ // It might be handy to get information about the handle
+ // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-getnamedpipeinfo
+
+ {
+ DWORD flags, outBufferSize, inBufferSize, maxInstances;
+ // It appears that by default windows pipe buffer size is 4k.
+ if (GetNamedPipeInfo(handle, &flags, &outBufferSize, &inBufferSize, &maxInstances))
+ {
+ }
+ }
+}
+
+SlangResult WinPipeStream::_updateState(BOOL res)
+{
+ if (res)
+ {
+ return SLANG_OK;
+ }
+ else
+ {
+ const auto err = GetLastError();
+
+ if (err == ERROR_BROKEN_PIPE)
+ {
+ m_streamHandle.setNull();
+ return SLANG_OK;
+ }
+
+ SLANG_UNUSED(err);
+ return SLANG_FAIL;
+ }
+}
+
+SlangResult WinPipeStream::read(void* buffer, size_t length, size_t& outReadBytes)
+{
+ outReadBytes = 0;
+ if (!_has(FileAccess::Read))
+ {
+ return SLANG_E_NOT_AVAILABLE;
+ }
+
+ if (m_streamHandle.isNull())
+ {
+ return SLANG_OK;
+ }
+
+ // Check if there is any data, so won't block
+ {
+ DWORD bytesRead = 0;
+ DWORD totalBytes = 0;
+ DWORD remainingBytes = 0;
+
+ // Works on anonymous pipes too
+ // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe
+
+ SLANG_RETURN_ON_FAIL(_updateState(PeekNamedPipe(m_streamHandle, nullptr, DWORD(0), &bytesRead, &totalBytes, &remainingBytes)));
+ // If there is nothing to read we are done
+ // If we don't do this ReadFile will *block* if there is nothing available
+ if (totalBytes == 0)
+ {
+ return SLANG_OK;
+ }
+ }
+
+ {
+ DWORD bytesRead = 0;
+ SLANG_RETURN_ON_FAIL(_updateState(ReadFile(m_streamHandle, buffer, DWORD(length), &bytesRead, nullptr)));
+
+ outReadBytes = size_t(bytesRead);
+ }
+
+ return SLANG_OK;
+}
+
+SlangResult WinPipeStream::write(const void* buffer, size_t length)
+{
+ if (!_has(FileAccess::Write))
+ {
+ return SLANG_E_NOT_AVAILABLE;
+ }
+
+ if (m_streamHandle.isNull())
+ {
+ // Writing to closed stream
+ return SLANG_FAIL;
+ }
+
+ DWORD numWritten = 0;
+ BOOL writeResult = WriteFile(m_streamHandle, buffer, DWORD(length), &numWritten, nullptr);
+
+ if (!writeResult || numWritten != length)
+ {
+ return SLANG_FAIL;
+ }
+
+ return SLANG_OK;
+}
+
+void WinPipeStream::close()
+{
+ if (!m_isOwned)
+ {
+ // If we don't own it just detach it
+ m_streamHandle.detach();
+ }
+ m_streamHandle.setNull();
+}
+
+SlangResult WinPipeStream::flush()
+{
+ if ((Index(m_access) & Index(FileAccess::Write)) == 0 || m_streamHandle.isNull())
+ {
+ return SLANG_E_NOT_AVAILABLE;
+ }
+
+ // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers
+
+ if (!FlushFileBuffers(m_streamHandle))
+ {
+ auto err = GetLastError();
+ SLANG_UNUSED(err);
+ }
+ return SLANG_OK;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!! WinProcess !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+void WinProcess::_hasTerminated()
+{
+ if (!m_processHandle.isNull())
+ {
+ // get exit code for process
+ // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
+
+ DWORD childExitCode = 0;
+ if (GetExitCodeProcess(m_processHandle, &childExitCode))
+ {
+ m_returnValue = int32_t(childExitCode);
+ }
+ m_processHandle.setNull();
+ }
+}
+
+void WinProcess::waitForTermination()
+{
+ if (m_processHandle.isNull())
+ {
+ return;
+ }
+
+ // wait for the process to exit
+ // TODO: set a timeout as a safety measure...
+ WaitForSingleObject(m_processHandle, INFINITE);
+
+ _hasTerminated();
+}
+
+bool WinProcess::isTerminated()
+{
+ if (m_processHandle.isNull())
+ {
+ return true;
+ }
+
+ auto res = WaitForSingleObject(m_processHandle, 0);
+
+ if (res == WAIT_TIMEOUT)
+ {
+ return false;
+ }
+ _hasTerminated();
+ return true;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+/* static */StringEscapeHandler* Process::getEscapeHandler()
+{
+ return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space);
+}
+
+/* static */UnownedStringSlice Process::getExecutableSuffix()
+{
+ return UnownedStringSlice::fromLiteral(".exe");
+}
+
+/* static */SlangResult Process::getStdStream(StreamType type, RefPtr<Stream>& out)
+{
+ switch (type)
+ {
+ case StreamType::StdIn:
+ {
+ const HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
+ out = new WinPipeStream(stdinHandle, FileAccess::Read, false);
+ return SLANG_OK;
+ }
+ }
+
+ return SLANG_FAIL;
+}
+
+/* static */SlangResult Process::create(const CommandLine& commandLine, Process::Flags flags, RefPtr<Process>& outProcess)
+{
+ WinHandle childStdOutRead;
+ WinHandle childStdErrRead;
+ WinHandle childStdInWrite;
+
+ WinHandle processHandle;
+ {
+ WinHandle childStdOutWrite;
+ WinHandle childStdErrWrite;
+ WinHandle childStdInRead;
+
+ SECURITY_ATTRIBUTES securityAttributes;
+ securityAttributes.nLength = sizeof(securityAttributes);
+ securityAttributes.lpSecurityDescriptor = nullptr;
+ securityAttributes.bInheritHandle = true;
+
+ // 0 means use the 'system default'
+ //const DWORD bufferSize = 64 * 1024;
+ const DWORD bufferSize = 0;
+
+ {
+ WinHandle childStdOutReadTmp;
+ WinHandle childStdErrReadTmp;
+ WinHandle childStdInWriteTmp;
+ // create stdout pipe for child process
+ SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdOutReadTmp.writeRef(), childStdOutWrite.writeRef(), &securityAttributes, bufferSize));
+ // create stderr pipe for child process
+ SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdErrReadTmp.writeRef(), childStdErrWrite.writeRef(), &securityAttributes, bufferSize));
+ // create stdin pipe for child process
+ SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdInRead.writeRef(), childStdInWriteTmp.writeRef(), &securityAttributes, bufferSize));
+
+ const HANDLE currentProcess = GetCurrentProcess();
+
+ // https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
+
+ // 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 = commandLine.toString();
+ OSString cmdStringBuffer = cmdString.toWString();
+
+ // Now we can actually get around to starting a process
+ PROCESS_INFORMATION processInfo;
+ ZeroMemory(&processInfo, sizeof(processInfo));
+
+ // https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
+
+ DWORD createFlags = CREATE_NO_WINDOW;
+
+ if (flags & Process::Flag::AttachDebugger)
+ {
+ createFlags |= CREATE_SUSPENDED;
+ }
+
+ // 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,
+ createFlags,
+ nullptr, // TODO: allow specifying environment variables?
+ nullptr,
+ &startupInfo,
+ &processInfo);
+
+ if (!success)
+ {
+ DWORD err = GetLastError();
+ SLANG_UNUSED(err);
+ return SLANG_FAIL;
+ }
+
+ if (flags & Process::Flag::AttachDebugger)
+ {
+ // Lets see if we can set up to debug
+ // https://docs.microsoft.com/en-us/windows/win32/debug/debugging-a-running-process
+
+ //DebugActiveProcess(processInfo.dwProcessId);
+
+ // Resume the thread
+ ResumeThread(processInfo.hThread);
+ }
+
+ // close handles we are now done with
+ CloseHandle(processInfo.hThread);
+
+ // Save the process handle
+ processHandle = processInfo.hProcess;
+ }
+
+ RefPtr<Stream> streams[Index(Process::StreamType::CountOf)];
+ streams[Index(Process::StreamType::ErrorOut)] = new WinPipeStream(childStdErrRead.detach(), FileAccess::Read);
+ streams[Index(Process::StreamType::StdOut)] = new WinPipeStream(childStdOutRead.detach(), FileAccess::Read);
+ streams[Index(Process::StreamType::StdIn)] = new WinPipeStream(childStdInWrite.detach(), FileAccess::Write);
+
+ outProcess = new WinProcess(processHandle.detach(), streams[0].readRef());
+ return SLANG_OK;
+}
+
+/* static */void Process::sleepCurrentThread(Index timeInMs)
+{
+ ::Sleep(DWORD(timeInMs));
+}
+
+static uint64_t _getClockFrequency()
+{
+ LARGE_INTEGER timerFrequency;
+ QueryPerformanceFrequency(&timerFrequency);
+ return timerFrequency.QuadPart;
+}
+
+static const uint64_t g_frequency = _getClockFrequency();
+
+/* static */uint64_t Process::getClockFrequency()
+{
+ return g_frequency;
+}
+
+/* static */uint64_t Process::getClockTick()
+{
+ LARGE_INTEGER counter;
+ QueryPerformanceCounter(&counter);
+ return counter.QuadPart;
+}
+
+} // namespace Slang