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 | |
| 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')
| -rw-r--r-- | source/core/slang-command-line.cpp | 54 | ||||
| -rw-r--r-- | source/core/slang-command-line.h | 60 | ||||
| -rw-r--r-- | source/core/slang-process-util.cpp | 78 | ||||
| -rw-r--r-- | source/core/slang-process-util.h | 79 | ||||
| -rw-r--r-- | source/core/slang-process.h | 73 | ||||
| -rw-r--r-- | source/core/slang-stream.cpp | 276 | ||||
| -rw-r--r-- | source/core/slang-stream.h | 61 | ||||
| -rw-r--r-- | source/core/slang-string-util.cpp | 54 | ||||
| -rw-r--r-- | source/core/slang-string-util.h | 3 | ||||
| -rw-r--r-- | source/core/unix/slang-unix-process-util.cpp | 246 | ||||
| -rw-r--r-- | source/core/unix/slang-unix-process.cpp | 430 | ||||
| -rw-r--r-- | source/core/windows/slang-win-process-util.cpp | 333 | ||||
| -rw-r--r-- | source/core/windows/slang-win-process.cpp | 495 |
13 files changed, 1594 insertions, 648 deletions
diff --git a/source/core/slang-command-line.cpp b/source/core/slang-command-line.cpp new file mode 100644 index 000000000..419c031ab --- /dev/null +++ b/source/core/slang-command-line.cpp @@ -0,0 +1,54 @@ +// slang-command-line.cpp +#include "slang-command-line.h" + +#include "slang-process.h" + +#include "slang-string.h" +#include "slang-string-escape-util.h" +#include "slang-string-util.h" + +#include "../../slang-com-helper.h" + +namespace Slang { + +void CommandLine::addPrefixPathArg(const char* prefix, const String& path, const char* pathPostfix) +{ + StringBuilder builder; + builder << prefix << path; + if (pathPostfix) + { + // Work out the path with the postfix + builder << pathPostfix; + } + addArg(builder.ProduceString()); +} + +void CommandLine::setExecutable(const String& dir, const String& name) +{ + StringBuilder builder; + Path::combineIntoBuilder(dir.getUnownedSlice(), name.getUnownedSlice(), builder); + builder << Process::getExecutableSuffix(); + setExecutablePath(builder.ProduceString()); +} + +void CommandLine::append(StringBuilder& out) const +{ + auto escapeHandler = Process::getEscapeHandler(); + + StringEscapeUtil::appendMaybeQuoted(escapeHandler, m_executable.getUnownedSlice(), out); + + for (const auto& arg : m_args) + { + out << " "; + StringEscapeUtil::appendMaybeQuoted(escapeHandler, arg.getUnownedSlice(), out); + } +} + +String CommandLine::toString() const +{ + StringBuilder buf; + append(buf); + return buf.ProduceString(); +} + +} // namespace Slang diff --git a/source/core/slang-command-line.h b/source/core/slang-command-line.h new file mode 100644 index 000000000..85ba1a331 --- /dev/null +++ b/source/core/slang-command-line.h @@ -0,0 +1,60 @@ +// slang-command-line.h +#ifndef SLANG_COMMAND_LINE_H +#define SLANG_COMMAND_LINE_H + +#include "slang-string.h" +#include "slang-list.h" + +namespace Slang { + +struct CommandLine +{ + enum class ExecutableType + { + Unknown, ///< The executable is not specified + Path, ///< The executable is set as a path (ie won't be searched for) + Filename, ///< The executable is set as a filename + }; + + /// Add args - assumed unescaped + void addArg(const String& in) { m_args.add(in); } + void addArgs(const String* args, Int argsCount) { for (Int i = 0; i < argsCount; ++i) addArg(args[i]); } + + /// Find the index of an arg which is exact match for slice + SLANG_INLINE Index findArgIndex(const UnownedStringSlice& slice) const { return m_args.indexOf(slice); } + + /// Set the executable path. + /// NOTE! On some targets the executable path *must* include an extension to be able to start as a process + void setExecutablePath(const String& path) { m_executableType = ExecutableType::Path; m_executable = path; } + + /// Set the executable path from a base directory and an executable name (no suffix such as '.exe' needed) + void setExecutable(const String& dir, const String& name); + + /// Set a filename (such that the path will be looked up + void setExecutableFilename(const String& filename) { m_executableType = ExecutableType::Filename; m_executable = filename; } + + /// For handling args where the switch is placed directly in front of the path + void addPrefixPathArg(const char* prefix, const String& path, const char* pathPostfix = nullptr); + + /// Get the total number of args + SLANG_FORCE_INLINE Index getArgCount() const { return m_args.getCount(); } + + /// Reset to the initial state + void reset() { *this = CommandLine(); } + + /// Append the command line to out + void append(StringBuilder& out) const; + /// convert into a string + String toString() const; + + /// Ctor + CommandLine():m_executableType(ExecutableType::Unknown) {} + + ExecutableType m_executableType; ///< How the executable is specified + String m_executable; ///< Executable to run. Note that the executable is never escaped. + List<String> m_args; ///< The arguments (Stored *unescaped*) +}; + +} + +#endif // SLANG_COMMAND_LINE_H diff --git a/source/core/slang-process-util.cpp b/source/core/slang-process-util.cpp new file mode 100644 index 000000000..b1466d150 --- /dev/null +++ b/source/core/slang-process-util.cpp @@ -0,0 +1,78 @@ +// slang-process-util.cpp + +#include "slang-process-util.h" + +#include "slang-string.h" +#include "slang-string-escape-util.h" +#include "slang-string-util.h" + +#include "../../slang-com-helper.h" + +namespace Slang { + +/* static */SlangResult ProcessUtil::execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult) +{ + RefPtr<Process> process; + SLANG_RETURN_ON_FAIL(Process::create(commandLine, 0, process)); + SLANG_RETURN_ON_FAIL(readUntilTermination(process, outExecuteResult)); + return SLANG_OK; +} + +static Index _getCount(List<Byte>* buf) +{ + return buf ? buf->getCount() : 0; +} + +// We may want something more sophisticated here, if bytes is something other than ascii/utf8 +static String _getText(const ConstArrayView<Byte>& bytes) +{ + StringBuilder buf; + StringUtil::appendStandardLines(UnownedStringSlice((const char*)bytes.begin(), (const char*)bytes.end()), buf); + return buf.ProduceString(); +} + +/* static */SlangResult ProcessUtil::readUntilTermination(Process* process, ExecuteResult& outExecuteResult) +{ + List<Byte> stdOut; + List<Byte> stdError; + + SLANG_RETURN_ON_FAIL(readUntilTermination(process, &stdOut, &stdError)); + + // Get the return code + outExecuteResult.resultCode = ExecuteResult::ResultCode(process->getReturnValue()); + + outExecuteResult.standardOutput = _getText(stdOut.getArrayView()); + outExecuteResult.standardError = _getText(stdError.getArrayView()); + + return SLANG_OK; +} + +/* static */SlangResult ProcessUtil::readUntilTermination(Process* process, List<Byte>* outStdOut, List<Byte>* outStdError) +{ + Stream* stdOutStream = process->getStream(Process::StreamType::StdOut); + Stream* stdErrorStream = process->getStream(Process::StreamType::ErrorOut); + + while (!process->isTerminated()) + { + const auto preCount = _getCount(outStdOut) + _getCount(outStdError); + + SLANG_RETURN_ON_FAIL(StreamUtil::readOrDiscard(stdOutStream, 0, outStdOut)); + SLANG_RETURN_ON_FAIL(StreamUtil::readOrDiscard(stdErrorStream, 0, outStdError)); + + const auto postCount = _getCount(outStdOut) + _getCount(outStdError); + + // If nothing was read, we can yield + if (preCount == postCount) + { + Process::sleepCurrentThread(0); + } + } + + // Read anything remaining + SLANG_RETURN_ON_FAIL(StreamUtil::readOrDiscardAll(stdOutStream, 0, outStdOut)); + SLANG_RETURN_ON_FAIL(StreamUtil::readOrDiscardAll(stdErrorStream, 0, outStdError)); + + return SLANG_OK; +} + +} // namespace Slang diff --git a/source/core/slang-process-util.h b/source/core/slang-process-util.h index af524940c..14ca208f8 100644 --- a/source/core/slang-process-util.h +++ b/source/core/slang-process-util.h @@ -2,52 +2,14 @@ #ifndef SLANG_PROCESS_UTIL_H #define SLANG_PROCESS_UTIL_H -#include "slang-string.h" -#include "slang-list.h" - -#include "slang-string-escape-util.h" +#include "slang-process.h" namespace Slang { -struct CommandLine -{ - enum class ExecutableType - { - Unknown, ///< The executable is not specified - Path, ///< The executable is set as a path - Filename, ///< The executable is set as a filename - }; - - /// Add args - assumed unescaped - void addArg(const String& in) { m_args.add(in); } - void addArgs(const String* args, Int argsCount) { for (Int i = 0; i < argsCount; ++i) addArg(args[i]); } - - /// Find the index of an arg which is exact match for slice - SLANG_INLINE Index findArgIndex(const UnownedStringSlice& slice) const { return m_args.indexOf(slice); } - - /// Set the executable path - void setExecutablePath(const String& path) { m_executableType = ExecutableType::Path; m_executable = path; } - void setExecutableFilename(const String& filename) { m_executableType = ExecutableType::Filename; m_executable = filename; } - - /// For handling args where the switch is placed directly in front of the path - SLANG_INLINE void addPrefixPathArg(const char* prefix, const String& path, const char* pathPostfix = nullptr); - - /// Get the total number of args - SLANG_FORCE_INLINE Index getArgCount() const { return m_args.getCount(); } - - /// Reset to the initial state - void reset() { *this = CommandLine(); } - - /// Ctor - CommandLine():m_executableType(ExecutableType::Unknown) {} - - ExecutableType m_executableType; ///< How the executable is specified - String m_executable; ///< Executable to run. Note that the executable is never escaped. - List<String> m_args; ///< The arguments (Stored *unescaped*) -}; - struct ExecuteResult { + typedef int ResultCode; + void init() { resultCode = 0; @@ -55,45 +17,24 @@ struct ExecuteResult standardError = String(); } - typedef int ResultCode; ResultCode resultCode; - Slang::String standardOutput; - Slang::String standardError; + String standardOutput; + String standardError; }; struct ProcessUtil { - /// The quoting style used for the command line on this target. Currently just uses Space, - /// but in future may take into account platform sec - static StringEscapeHandler* getEscapeHandler(); - - /// Get the suffix used on this platform - static UnownedStringSlice getExecutableSuffix(); - - /// Output how the command line is executed on the target (with escaping and the such like) - static String getCommandLineString(const CommandLine& commandLine); - /// Execute the command line static SlangResult execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult); - static uint64_t getClockFrequency(); + /// Read from read from streams until process terminates. + /// Passing nullptr for a stream, will just discard what's in the stream + static SlangResult readUntilTermination(Process* process, List<Byte>* outStdOut, List<Byte>* stdError); - static uint64_t getClockTick(); + /// Read streams from process. + static SlangResult readUntilTermination(Process* process, ExecuteResult& outExecuteResult); }; -// ----------------------------------------------------------------------- -SLANG_INLINE void CommandLine::addPrefixPathArg(const char* prefix, const String& path, const char* pathPostfix) -{ - StringBuilder builder; - builder << prefix << path; - if (pathPostfix) - { - // Work out the path with the postfix - builder << pathPostfix; - } - addArg(builder.ProduceString()); -} - } #endif // SLANG_PROCESS_UTIL_H diff --git a/source/core/slang-process.h b/source/core/slang-process.h new file mode 100644 index 000000000..ddbe7cbf0 --- /dev/null +++ b/source/core/slang-process.h @@ -0,0 +1,73 @@ +// slang-process.h +#ifndef SLANG_PROCESS_H +#define SLANG_PROCESS_H + +#include "slang-string.h" +#include "slang-list.h" +#include "slang-stream.h" +#include "slang-io.h" + +#include "slang-string-escape-util.h" + +#include "slang-command-line.h" + +namespace Slang { + +class Process : public RefObject +{ +public: + enum class StreamType + { + ErrorOut, + StdOut, + StdIn, + CountOf, + }; + + typedef uint32_t Flags; + struct Flag + { + enum Enum : Flags + { + AttachDebugger = 0x01, + }; + }; + + /// Get the stream for the type + Stream* getStream(StreamType type) const { return m_streams[Index(type)]; } + int32_t getReturnValue() const { return m_returnValue; } + + /// True if the process has terminated + virtual bool isTerminated() = 0; + /// Blocks until the process has completed + virtual void waitForTermination() = 0; + + + /// The quoting style used for the command line on this target. Currently just uses Space, + /// but in future may take into account platform sec + static StringEscapeHandler* getEscapeHandler(); + + /// Get the suffix used on this platform + static UnownedStringSlice getExecutableSuffix(); + + /// Create a process using the executable/args defined from the commandLine + static SlangResult create(const CommandLine& commandLine, Process::Flags flags, RefPtr<Process>& outProcess); + + /// Sleep the current thread for time specified in milliseconds. 0 indicates to OS ok to yield this thread. + static void sleepCurrentThread(Index timeInMs); + + /// Get a standard stream + static SlangResult getStdStream(StreamType type, RefPtr<Stream>& out); + + static uint64_t getClockFrequency(); + + static uint64_t getClockTick(); + +protected: + int32_t m_returnValue = 0; ///< Value returned if process terminated + RefPtr<Stream> m_streams[Index(StreamType::CountOf)]; ///< Streams to communicate with the process +}; + +} + +#endif // SLANG_PROCESS_H diff --git a/source/core/slang-stream.cpp b/source/core/slang-stream.cpp index f938eb886..042b1d898 100644 --- a/source/core/slang-stream.cpp +++ b/source/core/slang-stream.cpp @@ -250,6 +250,16 @@ SlangResult FileStream::write(const void* buffer, size_t length) return (bytesWritten == length) ? SLANG_OK : SLANG_FAIL; } +SlangResult FileStream::flush() +{ + if (m_handle && canWrite()) + { + fflush(m_handle); + return SLANG_OK; + } + return SLANG_E_NOT_AVAILABLE; +} + bool FileStream::canRead() { return ((int)m_fileAccess & (int)FileAccess::Read) != 0; @@ -362,4 +372,270 @@ SlangResult OwnedMemoryStream::write(const void * buffer, size_t length) return SLANG_OK; } +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! BufferedReadStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +void BufferedReadStream::_advanceStartIndex(Index byteCount) +{ + SLANG_ASSERT(Index(getCount()) >= byteCount && byteCount >= 0); + m_startIndex += byteCount; + if (getCount() == 0) + { + m_startIndex = 0; + } +} + +Int64 BufferedReadStream::getPosition() +{ + return m_stream ? (m_stream->getPosition() - getCount()) : 0; +} + +SlangResult BufferedReadStream::seek(SeekOrigin origin, Int64 offset) +{ + if (!m_stream) + { + return SLANG_FAIL; + } + // As it currently stands the data behind m_startIndex is the previous data. + // So we could seek backwards up to -m_startIndex. + // We don't worry about this here, for simplicity sake. + + if (origin == SeekOrigin::End || origin == SeekOrigin::Start || offset < 0 || offset >= Int64(getCount())) + { + // Empty the buffer + m_startIndex = 0; + m_buffer.setCount(0); + // Seek on underlying stream + return m_stream->seek(origin, offset); + } + + // We can just seek on the buffered data + _advanceStartIndex(Index(offset)); + return SLANG_OK; +} + +SlangResult BufferedReadStream::read(void* inBuffer, size_t length, size_t& outReadBytes) +{ + // If the buffer has no data and the read size is larger than the default read size - may as well just read directly into the output buffer + if (getCount() == 0 && length > m_defaultReadSize) + { + return m_stream->read(inBuffer, length, outReadBytes); + } + + Byte* buffer = (Byte*)inBuffer; + + size_t totalReadBytes = 0; + outReadBytes = 0; + + // Do a read to fill the buffer. + SLANG_RETURN_ON_FAIL(update()); + + while (length > 0) + { + const size_t bufferCount = size_t(getCount()); + + if (bufferCount) + { + const size_t readCount = (bufferCount < length) ? bufferCount : length; + + ::memcpy(buffer, getBuffer(), readCount); + + _advanceStartIndex(Index(readCount)); + buffer += readCount; + length -= readCount; + + totalReadBytes += readCount; + } + else + { + if (m_stream == nullptr) + { + break; + } + + // Read from underlying buffer + size_t readBytes; + SlangResult res = m_stream->read(buffer, length, readBytes); + + outReadBytes = totalReadBytes + readBytes; + return res; + } + } + + outReadBytes = totalReadBytes; + return SLANG_OK; +} + +SlangResult BufferedReadStream::write(const void* buffer, size_t length) +{ + SLANG_UNUSED(buffer); + SLANG_UNUSED(length); + + return SLANG_E_NOT_AVAILABLE; +} + +bool BufferedReadStream::canRead() +{ + return getCount() > 0 || (m_stream && m_stream->canRead()); +} + +bool BufferedReadStream::canWrite() +{ + return false; +} + +void BufferedReadStream::close() +{ + if (m_stream) + { + m_stream->close(); + m_stream.setNull(); + } +} + +bool BufferedReadStream::isEnd() +{ + return getCount() == 0 && (m_stream == nullptr || m_stream->isEnd()); +} + +SlangResult BufferedReadStream::flush() +{ + return SLANG_E_NOT_AVAILABLE; +} + +SlangResult BufferedReadStream::update() +{ + if (m_stream == nullptr) + { + // Should this return an error? + return SLANG_OK; + } + + { + // How much buffer space do we have. We need at least m_defaultReadSize + const size_t remainingCount = size_t(m_buffer.getCapacity() - m_buffer.getCount()); + + // Repeat until we have enough space + while (remainingCount < m_defaultReadSize) + { + // If there is anything in the buffer shift it all down + if (m_startIndex > 0) + { + Byte* buffer = m_buffer.getBuffer(); + const Index count = getCount(); + if (count > 0) + { + ::memmove(buffer, buffer + m_startIndex, count); + } + + m_buffer.setCount(count); + m_startIndex = 0; + } + else + { + // Make sure we have the space + const Index prevCount = m_buffer.getCount(); + m_buffer.setCount(prevCount + m_defaultReadSize); + m_buffer.setCount(prevCount); + } + } + SLANG_ASSERT(size_t(m_buffer.getCapacity() - m_buffer.getCount()) >= m_defaultReadSize); + } + + { + const Index prevCount = m_buffer.getCount(); + m_buffer.setCount(prevCount + m_defaultReadSize); + + size_t readBytes = 0; + + const SlangResult res = m_stream->read(m_buffer.getBuffer() + prevCount, m_defaultReadSize, readBytes); + + m_buffer.setCount(prevCount + Index(readBytes)); + + return res; + } +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! StreamUtil !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +/* static */SlangResult StreamUtil::readAll(Stream* stream, size_t readSize, List<Byte>& ioBytes) +{ + while (!stream->isEnd()) + { + SLANG_RETURN_ON_FAIL(read(stream, readSize, ioBytes)); + } + + return SLANG_OK; +} + +/* static */SlangResult StreamUtil::read(Stream* stream, size_t readSize, List<Byte>& ioBytes) +{ + readSize = (readSize <= 0) ? 1024 : readSize; + + while (true) + { + const Index prevCount = ioBytes.getCount(); + ioBytes.setCount(prevCount + readSize); + + size_t readBytesCount; + SLANG_RETURN_ON_FAIL(stream->read(ioBytes.getBuffer() + prevCount, readSize, readBytesCount)); + ioBytes.setCount(prevCount + Index(readBytesCount)); + + if (readBytesCount == 0) + { + return SLANG_OK; + } + } +} + +/* static */SlangResult StreamUtil::discard(Stream* stream) +{ + Byte buf[1024]; + const Index bufSize = SLANG_COUNT_OF(buf); + + while (true) + { + size_t readBytesCount; + SLANG_RETURN_ON_FAIL(stream->read(buf, bufSize, readBytesCount)); + + if (readBytesCount == 0) + { + return SLANG_OK; + } + } +} + +/* static */SlangResult StreamUtil::discardAll(Stream* stream) +{ + while (!stream->isEnd()) + { + SLANG_RETURN_ON_FAIL(discard(stream)); + } + return SLANG_OK; +} + + +/* static */SlangResult StreamUtil::readOrDiscard(Stream* stream, size_t readSize, List<Byte>* ioBytes) +{ + if (ioBytes) + { + return read(stream, readSize, *ioBytes); + } + else + { + return discard(stream); + } +} + +/* static */SlangResult StreamUtil::readOrDiscardAll(Stream* stream, size_t readSize, List<Byte>* ioBytes) +{ + if (ioBytes) + { + return readAll(stream, readSize, *ioBytes); + } + else + { + return discardAll(stream); + } +} + } // namespace Slang diff --git a/source/core/slang-stream.h b/source/core/slang-stream.h index e33e1c601..f6c9f54a3 100644 --- a/source/core/slang-stream.h +++ b/source/core/slang-stream.h @@ -43,6 +43,9 @@ public: /// Implies any pending data is flushed. virtual void close() = 0; + /// Only applicable for write streams, flushes any buffers to underlying representation (such as pipe, or file) + virtual SlangResult flush() = 0; + /// Helper function that will also *fail* if the specified amount of bytes aren't read. SlangResult readExactly(void* buffer, size_t length); }; @@ -76,6 +79,7 @@ public: virtual bool canRead() SLANG_OVERRIDE { return (int(m_access) & int(FileAccess::Read)) != 0; } virtual bool canWrite() SLANG_OVERRIDE { return (int(m_access) & int(FileAccess::Write)) != 0; } virtual void close() SLANG_OVERRIDE { m_access = FileAccess::None; } + virtual SlangResult flush() SLANG_OVERRIDE { return canWrite() ? SLANG_OK : SLANG_E_NOT_AVAILABLE; } /// Get the contents ConstArrayView<uint8_t> getContents() const { return ConstArrayView<uint8_t>(m_contents, m_contentsSize); } @@ -159,6 +163,7 @@ public: virtual bool canWrite() SLANG_OVERRIDE; virtual void close() SLANG_OVERRIDE; virtual bool isEnd() SLANG_OVERRIDE; + virtual SlangResult flush() SLANG_OVERRIDE; FileStream(); @@ -175,6 +180,62 @@ private: bool m_endReached = false; }; +/* A simple BufferedReader. The valid data is between m_startIndex and getCount(). +Can be used as a buffer to build up a result from a stream in memory using 'update' to read to the appropriate buffer size. +*/ +class BufferedReadStream : public Stream +{ +public: + typedef Stream Super; + + virtual Int64 getPosition() SLANG_OVERRIDE; + virtual SlangResult seek(SeekOrigin origin, Int64 offset) SLANG_OVERRIDE; + 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 canRead() SLANG_OVERRIDE; + virtual bool canWrite() SLANG_OVERRIDE; + virtual void close() SLANG_OVERRIDE; + virtual bool isEnd() SLANG_OVERRIDE; + virtual SlangResult flush() SLANG_OVERRIDE; + + /// Will read assuming backing stream is + SlangResult update(); + + Byte* getBuffer() { return m_buffer.getBuffer() + m_startIndex; } + const Byte* getBuffer() const { return m_buffer.getBuffer() + m_startIndex; } + + size_t getCount() const { return m_buffer.getCount() - m_startIndex; } + + ConstArrayView<Byte> getView() const { return ConstArrayView<Byte>(getBuffer(), Index(getCount())); } + ArrayView<Byte> getView() { return ArrayView<Byte>(getBuffer(), Index(getCount())); } + +protected: + void _advanceStartIndex(Index byteCount); + + size_t m_defaultReadSize = 1024; ///< When initiating a read the default read size + List<Byte> m_buffer; ///< Holds the characters + Index m_startIndex; ///< The start index + RefPtr<Stream> m_stream; ///< Stream that is being read from +}; + +struct StreamUtil +{ + /// Appends all bytes that can be read from stream into bytes + static SlangResult readAll(Stream* stream, size_t readSize, List<Byte>& ioBytes); + + /// Read as much as can be read until a 0 sized read, or an error and append onto ioBytes + /// Read size controls the size of each buffer read. Passing 0, will use the default read size. + static SlangResult read(Stream* stream, size_t readSize, List<Byte>& ioBytes); + + static SlangResult discard(Stream* stream); + + static SlangResult discardAll(Stream* stream); + + static SlangResult readOrDiscard(Stream* stream, size_t readSize, List<Byte>* ioBytes); + static SlangResult readOrDiscardAll(Stream* stream, size_t readSize, List<Byte>* ioBytes); +}; + + } // namespace Slang #endif diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp index b2886c413..5282f01a6 100644 --- a/source/core/slang-string-util.cpp +++ b/source/core/slang-string-util.cpp @@ -350,6 +350,60 @@ ComPtr<ISlangBlob> StringUtil::createStringBlob(const String& string) return (fromChar == toChar || string.indexOf(fromChar) == Index(-1)) ? string : calcCharReplaced(string.getUnownedSlice(), fromChar, toChar); } +/* static */void StringUtil::appendStandardLines(const UnownedStringSlice& text, StringBuilder& out) +{ + const char* cur = text.begin(); + const char* start = cur; + const char* const end = text.end(); + + while (cur < end) + { + const char c = *cur; + switch (c) + { + case '\n': + { + ++cur; + if (cur < end && *cur == '\r') + { + // If we have following \r, we should append with \n + // Append (including \n) + out.append(start, cur); + // Skip the \r + start = ++cur; + } + else + { + // If not, we don't need to append because just \n is 'standard', and everything remaining + // is appended at the end + } + break; + } + case '\r': + { + out.append(start, cur); + out.appendChar('\n'); + + ++cur; + // If next is \n, we want to skip that + cur += Index(cur < end && *cur == '\n'); + start = cur; + break; + } + default: + { + cur++; + break; + } + } + } + + if (start < end) + { + out.append(start, end); + } +} + /* static */bool StringUtil::extractLine(UnownedStringSlice& ioText, UnownedStringSlice& outLine) { char const*const begin = ioText.begin(); diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h index 6a0794082..03f503ed2 100644 --- a/source/core/slang-string-util.h +++ b/source/core/slang-string-util.h @@ -78,6 +78,9 @@ struct StringUtil /// Create a blob from a string static ComPtr<ISlangBlob> createStringBlob(const String& string); + /// Given input text outputs with standardized line endings. Ie \n\r -> \n + static void appendStandardLines(const UnownedStringSlice& text, StringBuilder& out); + /// Extracts a line and stores the remaining text in ioText, and the line in outLine. Returns true if has a line. /// /// As well as indicating end of text with the return value, at the end of all the text a 'special' null UnownedStringSlice with a null 'begin' diff --git a/source/core/unix/slang-unix-process-util.cpp b/source/core/unix/slang-unix-process-util.cpp deleted file mode 100644 index af49eec37..000000000 --- a/source/core/unix/slang-unix-process-util.cpp +++ /dev/null @@ -1,246 +0,0 @@ -// slang-unix-process-util.cpp -#include "../slang-process-util.h" - -#include "../slang-common.h" -#include "../slang-string-util.h" -#include "../slang-string-escape-util.h" -#include "../slang-memory-arena.h" - -#include <stdio.h> -#include <stdlib.h> - -//#include <dirent.h> -#include <errno.h> -#include <poll.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -#include <time.h> - -namespace Slang { - - -/* static */UnownedStringSlice ProcessUtil::getExecutableSuffix() -{ -#if __CYGWIN__ - return UnownedStringSlice::fromLiteral(".exe"); -#else - return UnownedStringSlice::fromLiteral(""); -#endif -} - -/* static */StringEscapeHandler* ProcessUtil::getEscapeHandler() -{ - return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space); -} - -/* static */String ProcessUtil::getCommandLineString(const CommandLine& commandLine) -{ - auto escapeHandler = getEscapeHandler(); - - // When outputting the command line we potentially need to escape the path to the - // command and args - that aren't already explicitly marked as escaped. - - 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(); -} - -/* static */SlangResult ProcessUtil::execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult) -{ - outExecuteResult.init(); - - List<char const*> argPtrs; - - // Add the command - argPtrs.add(commandLine.m_executable.getBuffer()); - - // Add all the args - they don't need any explicit escaping - for (auto arg : commandLine.m_args) - { - // All args for this target must be unescaped (as they are in CommandLine) - argPtrs.add(arg.getBuffer()); - } - - // Terminate with a null - argPtrs.add(nullptr); - - int stdoutPipe[2]; - int stderrPipe[2]; - - if (pipe(stdoutPipe) == -1) - { - fprintf(stderr, "error: `pipe` failed\n"); - return SLANG_FAIL; - } - - if (pipe(stderrPipe) == -1) - { - fprintf(stderr, "error: `pipe` failed\n"); - return SLANG_FAIL; - } - - pid_t childProcessID = fork(); - if (childProcessID == -1) - { - fprintf(stderr, "error: `fork` failed\n"); - return SLANG_FAIL; - } - - if (childProcessID == 0) - { - // We are the child process. - - dup2(stdoutPipe[1], STDOUT_FILENO); - dup2(stderrPipe[1], STDERR_FILENO); - - close(stdoutPipe[0]); - close(stdoutPipe[1]); - - close(stderrPipe[0]); - close(stderrPipe[1]); - - execvp(argPtrs[0], (char* const*)&argPtrs[0]); - - // If we get here, then `exec` failed - fprintf(stderr, "error: `exec` failed\n"); - return SLANG_FAIL; - } - else - { - // We are the parent process - - close(stdoutPipe[1]); - close(stderrPipe[1]); - - int stdoutFD = stdoutPipe[0]; - int stderrFD = stderrPipe[0]; - - pollfd pollInfos[2]; - nfds_t pollInfoCount = 2; - - pollInfos[0].fd = stdoutFD; - pollInfos[0].events = POLLIN; - pollInfos[0].revents = 0; - pollInfos[1].fd = stderrFD; - pollInfos[1].events = POLLIN; - pollInfos[1].revents = 0; - - int remainingCount = 2; - int iterations = 0; - while (remainingCount) - { - // Safeguard against infinite loop: - iterations++; - if (iterations > 10000) - { - fprintf(stderr, "poll(): %d iterations\n", iterations); - return SLANG_FAIL; - } - - // Set a timeout of 100 seconds; - // we really shouldn't wait too long... - int pollTimeout = 100000; - int pollResult = poll(pollInfos, pollInfoCount, pollTimeout); - if (pollResult <= 0) - { - // If there was a signal that got in - // the way, then retry... - if (pollResult == -1 && errno == EINTR) - continue; - - // timeout or error... - fprintf(stderr, "error: `poll` failed or timed out\n"); - return SLANG_FAIL; - } - - enum { kBufferSize = 1024 }; - char buffer[kBufferSize]; - - if (pollInfos[0].revents) - { - auto count = read(stdoutFD, buffer, kBufferSize); - if (count <= 0) - { - // end-of-file - close(stdoutFD); - pollInfos[0].fd = -1; - remainingCount--; - } - - outExecuteResult.standardOutput.append(buffer, buffer + count); - } - - if (pollInfos[1].revents) - { - auto count = read(stderrFD, buffer, kBufferSize); - if (count <= 0) - { - // end-of-file - close(stderrFD); - pollInfos[1].fd = -1; - remainingCount--; - } - - outExecuteResult.standardError.append(buffer, buffer + count); - } - } - - int childStatus = 0; - iterations = 0; - for (;;) - { - // Safeguard against infinite loop: - iterations++; - if (iterations > 10000) - { - fprintf(stderr, "waitpid(): %d iterations\n", iterations); - return SLANG_FAIL; - } - - pid_t terminatedProcessID = waitpid(childProcessID, &childStatus, 0); - if (terminatedProcessID == -1) - { - fprintf(stderr, "error: `waitpid` failed\n"); - return SLANG_FAIL; - } - - if (terminatedProcessID == childProcessID) - { - if (WIFEXITED(childStatus)) - { - outExecuteResult.resultCode = (int)(int8_t)WEXITSTATUS(childStatus); - } - else - { - outExecuteResult.resultCode = 1; - } - return SLANG_OK; - } - } - } - - return SLANG_FAIL; -} - -/* static */uint64_t ProcessUtil::getClockFrequency() -{ - return 1000000000; -} - -/* static */uint64_t ProcessUtil::getClockTick() -{ - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - return uint64_t(now.tv_sec) * 1000000000 + now.tv_nsec; -} - -} // namespace Slang diff --git a/source/core/unix/slang-unix-process.cpp b/source/core/unix/slang-unix-process.cpp new file mode 100644 index 000000000..083160f02 --- /dev/null +++ b/source/core/unix/slang-unix-process.cpp @@ -0,0 +1,430 @@ +// slang-unix-process.cpp +#include "../slang-process.h" + +#include "../slang-common.h" +#include "../slang-string-util.h" +#include "../slang-string-escape-util.h" +#include "../slang-memory-arena.h" + +#include <stdio.h> +#include <stdlib.h> + +//#include <dirent.h> +#include <errno.h> +#include <poll.h> +#include <fcntl.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <time.h> + +namespace Slang { + +class UnixProcess : public Process +{ +public: + // Process + virtual bool isTerminated() SLANG_OVERRIDE; + virtual void waitForTermination() SLANG_OVERRIDE; + + UnixProcess(pid_t pid, Stream*const* streams); + +protected: + /// Returns true if terminated + bool _updateTerminationState(int options); + + bool m_isTerminated = false; ///< True if ths process is terminated + pid_t m_pid; ///< The process id +}; + +class UnixPipeStream : public Stream +{ +public: + typedef UnixPipeStream 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_isClosed; } + virtual bool canRead() SLANG_OVERRIDE { return _has(FileAccess::Read) && !m_isClosed; } + virtual bool canWrite() SLANG_OVERRIDE { return _has(FileAccess::Write) && !m_isClosed; } + virtual void close() SLANG_OVERRIDE; + virtual SlangResult flush() SLANG_OVERRIDE; + + UnixPipeStream(int fd, FileAccess access, bool isOwned) : + m_fd(fd), + m_access(access), + m_isOwned(isOwned), + m_isClosed(false) + { + } + +protected: + /// This read file descriptor non blocking. Doing so will change the behavior of + /// read - it can fail and return an error indicating there is no data, instead of blocking. + /// Currently this mechanism isn't used, as checking via poll seemed to work. + void _setReadNonBlocking() + { + // Makes non blocking + if (_has(FileAccess::Read)) + { + // Make non blocking, for read + fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) | O_NONBLOCK); + } + } + bool _has(FileAccess access) const { return (Index(access) & Index(m_access)) != 0; } + + bool m_isClosed; ///< If true this stream has been closed (ie cannot read/write to anymore) + bool m_isOwned; ///< True if m_fd is owned by this object. + FileAccess m_access; ///< Access allowed to this stream - either Read or Write + int m_fd; /// The 'file descriptor' for the pipe +}; + +/* !!!!!!!!!!!!!!!!!!!!!! UnixProcess !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +UnixProcess::UnixProcess(pid_t pid, Stream* const* streams): + m_pid(pid) +{ + // Set to an 'odd value' + m_returnValue = -1; + + for (Index i = 0; i < SLANG_COUNT_OF(m_streams); ++i) + { + m_streams[i] = streams[i]; + } +} + +bool UnixProcess::_updateTerminationState(int options) +{ + if (!m_isTerminated) + { + int childStatus; + const pid_t terminatedPid = waitpid(m_pid, &childStatus, options); + if (terminatedPid == -1) + { + // Guess we should just mark as terminated + m_isTerminated = true; + + fprintf(stderr, "error: `waitpid` failed\n"); + } + else if (terminatedPid == m_pid) + { + if (WIFEXITED(childStatus)) + { + m_returnValue = (int)(int8_t)WEXITSTATUS(childStatus); + } + m_isTerminated = true; + } + } + return m_isTerminated; +} + +bool UnixProcess::isTerminated() +{ + if (m_isTerminated) + { + return true; + } + return _updateTerminationState(WNOHANG); +} + +void UnixProcess::waitForTermination() +{ + while (!_updateTerminationState(0)); +} + +/* !!!!!!!!!!!!!!!!!!!!!! UnixPipeStream !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +void UnixPipeStream::close() +{ + if (!m_isClosed) + { + if (m_isOwned) + { + ::close(m_fd); + } + + m_isClosed = true; + // Make something hopefully invalid + m_fd = -1; + } +} + +SlangResult UnixPipeStream::flush() +{ +#if 0 + // https://stackoverflow.com/questions/43184035/flushing-pipe-without-closing-in-c + // Makes the case that flushing is not applicable with pipes. + if (canWrite()) + { + // We might want to use + ::fsync(m_fd); + } +#endif + return SLANG_OK; +} + +SlangResult UnixPipeStream::read(void* buffer, size_t length, size_t& outReadBytes) +{ + outReadBytes = 0; + + if (!_has(FileAccess::Read)) + { + return SLANG_E_NOT_AVAILABLE; + } + if (m_isClosed) + { + return SLANG_OK; + } + + // Check if it's hung up. + pollfd pollInfo; + + pollInfo.fd = m_fd; + pollInfo.events = POLLIN | POLLHUP; + pollInfo.revents = 0; + + // https://linux.die.net/man/2/poll + + // Return immediately + const int pollTimeout = 0; + + const int pollResult = ::poll(&pollInfo, 1, pollTimeout); + if (pollResult < 0) + { + return SLANG_FAIL; + } + + // If there is data read that first + if (pollInfo.revents & POLLIN) + { + auto count = ::read(m_fd, buffer, length); + + // If it's -1 it seems like an error + if (count == -1) + { + const int err = errno; + + // On non blocking pipe these indicate there could be more to come + if (err == EAGAIN || err == EWOULDBLOCK) + { + return SLANG_OK; + } + // Okay - guess we have an error then + return SLANG_FAIL; + } + + outReadBytes = size_t(count); + return SLANG_OK; + } + + if (pollInfo.revents & POLLHUP) + { + close(); + } + + return SLANG_OK; +} + +SlangResult UnixPipeStream::write(const void* buffer, size_t length) +{ + if (!_has(FileAccess::Write)) + { + return SLANG_E_NOT_AVAILABLE; + } + if (m_isClosed) + { + // The pipe is closed + return SLANG_FAIL; + } + + pollfd pollInfo; + + pollInfo.fd = m_fd; + pollInfo.events = POLLHUP; + pollInfo.revents = 0; + + // https://linux.die.net/man/2/poll + + // Return immediately + const int pollTimeout = 0; + + int pollResult = ::poll(&pollInfo, 1, pollTimeout); + if (pollResult < 0) + { + return SLANG_FAIL; + } + + if (pollInfo.revents & POLLHUP) + { + close(); + return SLANG_FAIL; + } + + const ssize_t writeResult = ::write(m_fd, buffer, length); + + if (writeResult < 0 || writeResult != length) + { + return SLANG_FAIL; + } + + return SLANG_OK; +} + +/* !!!!!!!!!!!!!!!!!!!!!! Process !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +/* static */UnownedStringSlice Process::getExecutableSuffix() +{ +#if __CYGWIN__ + return UnownedStringSlice::fromLiteral(".exe"); +#else + return UnownedStringSlice::fromLiteral(""); +#endif +} + +/* static */StringEscapeHandler* Process::getEscapeHandler() +{ + return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space); +} + +/* static */SlangResult Process::create(const CommandLine& commandLine, Process::Flags flags, RefPtr<Process>& outProcess) +{ + List<char const*> argPtrs; + + // Add the command + argPtrs.add(commandLine.m_executable.getBuffer()); + + // Add all the args - they don't need any explicit escaping + for (auto arg : commandLine.m_args) + { + // All args for this target must be unescaped (as they are in CommandLine) + argPtrs.add(arg.getBuffer()); + } + + // Terminate with a null + argPtrs.add(nullptr); + + int stdoutPipe[2]; + int stderrPipe[2]; + int stdinPipe[2]; + + if (pipe(stdoutPipe) == -1 || pipe(stderrPipe) == -1 || pipe(stdinPipe) == -1) + { + fprintf(stderr, "error: `pipe` failed\n"); + return SLANG_FAIL; + } + + pid_t childPid = fork(); + if (childPid == -1) + { + fprintf(stderr, "error: `fork` failed\n"); + return SLANG_FAIL; + } + + if (childPid == 0) + { + // We are the child process. + + // Duplicate into standard handles + dup2(stdoutPipe[1], STDOUT_FILENO); + dup2(stderrPipe[1], STDERR_FILENO); + dup2(stdinPipe[0], STDIN_FILENO); + + // Close all of the handles + ::close(stdoutPipe[0]); + ::close(stdoutPipe[1]); + + ::close(stderrPipe[0]); + ::close(stderrPipe[1]); + + ::close(stdinPipe[0]); + ::close(stdinPipe[1]); + + ::execvp(argPtrs[0], (char* const*)&argPtrs[0]); + + // If we get here, then `exec` failed + + // NOTE! Because we have dup2 into STDERR_FILENO, this error will *not* generally appear on + // the terminal but in the stderrPipe. + fprintf(stderr, "error: `exec` failed\n"); + + return SLANG_FAIL; + } + else + { + // We are the parent process + ::close(stdoutPipe[1]); + ::close(stderrPipe[1]); + ::close(stdinPipe[0]); + + RefPtr<Stream> streams[Index(Process::StreamType::CountOf)]; + + // Previously code didn't need to close, so we'll make stream not own the handles + streams[Index(Process::StreamType::StdOut)] = new UnixPipeStream(stdoutPipe[0], FileAccess::Read, true); + streams[Index(Process::StreamType::ErrorOut)] = new UnixPipeStream(stderrPipe[0], FileAccess::Read, true); + streams[Index(Process::StreamType::StdIn)] = new UnixPipeStream(stdinPipe[1], FileAccess::Write, true); + + outProcess = new UnixProcess(childPid, streams[0].readRef()); + return SLANG_OK; + } +} + +/* static */uint64_t Process::getClockFrequency() +{ + return 1000000000; +} + +/* static */uint64_t Process::getClockTick() +{ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return uint64_t(now.tv_sec) * 1000000000 + now.tv_nsec; +} + +/* static */void Process::sleepCurrentThread(Index timeInMs) +{ + struct timespec timeSpec; + + if (timeInMs > 0) + { + timeSpec.tv_sec = timeInMs / 1000; + timeSpec.tv_nsec = (timeInMs % 1000) * 1000 * 1000; + } + else + { + timeSpec.tv_sec = 0; + timeSpec.tv_nsec = 0; + } + nanosleep(&timeSpec, nullptr); +} + +/* static */SlangResult Process::getStdStream(StreamType type, RefPtr<Stream>& out) +{ + switch (type) + { + case StreamType::StdIn: + { + out = new UnixPipeStream(STDIN_FILENO, FileAccess::Read, false); + break; + } + case StreamType::StdOut: + { + out = new UnixPipeStream(STDOUT_FILENO, FileAccess::Write, false); + break; + } + case StreamType::ErrorOut: + { + out = new UnixPipeStream(STDERR_FILENO, FileAccess::Write, false); + break; + } + default: return SLANG_FAIL; + } + return SLANG_OK; +} + +} // namespace Slang 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; -} - -} 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 |
