yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Ellie HermaszewskaAdd missing header for _getpid() (#5852)96a8781c7

master
18.0 KiB652 linesraw
1// slang-win-process-util.cpp
2#include "../slang-process-util.h"
3#include "../slang-process.h"
4#include "../slang-string-escape-util.h"
5#include "../slang-string-util.h"
6#include "../slang-string.h"
7#include "slang-com-helper.h"
8
9#ifdef _WIN32
10// TODO: We could try to avoid including this at all, but it would
11// mean trying to hide certain struct layouts, which would add
12// more dynamic allocation.
13#include <windows.h>
14#endif
15
16#include <process.h>
17#include <stdio.h>
18#include <stdlib.h>
19
20#ifndef SLANG_RETURN_FAIL_ON_FALSE
21#define SLANG_RETURN_FAIL_ON_FALSE(x) \
22    if (!(x))                         \
23        return SLANG_FAIL;
24#endif
25
26namespace Slang
27{
28
29// Has behavior very similar to unique_ptr - assignment is a move.
30class WinHandle
31{
32public:
33    /// Detach the encapsulated handle. Returns the handle (which now must be externally handled)
34    HANDLE detach()
35    {
36        HANDLE handle = m_handle;
37        m_handle = nullptr;
38        return handle;
39    }
40
41    /// Return as a handle
42    operator HANDLE() const { return m_handle; }
43
44    /// Assign
45    void operator=(HANDLE handle)
46    {
47        setNull();
48        m_handle = handle;
49    }
50    void operator=(WinHandle&& rhs)
51    {
52        HANDLE handle = m_handle;
53        m_handle = rhs.m_handle;
54        rhs.m_handle = handle;
55    }
56
57    /// Get ready for writing
58    SLANG_FORCE_INLINE HANDLE* writeRef()
59    {
60        setNull();
61        return &m_handle;
62    }
63    /// Get for read access
64    SLANG_FORCE_INLINE const HANDLE* readRef() const { return &m_handle; }
65
66    void setNull()
67    {
68        if (m_handle)
69        {
70            CloseHandle(m_handle);
71            m_handle = nullptr;
72        }
73    }
74    bool isNull() const { return m_handle == nullptr; }
75
76    /// Ctor
77    WinHandle(HANDLE handle = nullptr)
78        : m_handle(handle)
79    {
80    }
81    WinHandle(WinHandle&& rhs)
82        : m_handle(rhs.m_handle)
83    {
84        rhs.m_handle = nullptr;
85    }
86
87    /// Dtor
88    ~WinHandle() { setNull(); }
89
90private:
91    WinHandle(const WinHandle&) = delete;
92    void operator=(const WinHandle& rhs) = delete;
93
94    HANDLE m_handle;
95};
96
97/* A simple Stream implementation of a File HANDLE (or Pipe). Note that currently does not allow
98 * getPosition/seek/atEnd */
99class WinPipeStream : public Stream
100{
101public:
102    typedef WinPipeStream ThisType;
103
104    // Stream
105    virtual Int64 getPosition() SLANG_OVERRIDE { return 0; }
106    virtual SlangResult seek(SeekOrigin origin, Int64 offset) SLANG_OVERRIDE
107    {
108        SLANG_UNUSED(origin);
109        SLANG_UNUSED(offset);
110        return SLANG_E_NOT_AVAILABLE;
111    }
112    virtual SlangResult read(void* buffer, size_t length, size_t& outReadBytes) SLANG_OVERRIDE;
113    virtual SlangResult write(const void* buffer, size_t length) SLANG_OVERRIDE;
114    virtual bool isEnd() SLANG_OVERRIDE { return m_streamHandle.isNull(); }
115    virtual bool canRead() SLANG_OVERRIDE
116    {
117        return _has(FileAccess::Read) && !m_streamHandle.isNull();
118    }
119    virtual bool canWrite() SLANG_OVERRIDE
120    {
121        return _has(FileAccess::Write) && !m_streamHandle.isNull();
122    }
123    virtual void close() SLANG_OVERRIDE;
124    virtual SlangResult flush() SLANG_OVERRIDE;
125
126    WinPipeStream(HANDLE handle, FileAccess access, bool isOwned = true);
127
128    ~WinPipeStream() { close(); }
129
130protected:
131    bool _has(FileAccess access) const { return (Index(access) & Index(m_access)) != 0; }
132
133    SlangResult _updateState(BOOL res);
134
135    FileAccess m_access = FileAccess::None;
136    WinHandle m_streamHandle;
137    bool m_isOwned;
138    bool m_isPipe;
139};
140
141class WinProcess : public Process
142{
143public:
144    // Process
145    virtual bool isTerminated() SLANG_OVERRIDE;
146    virtual bool waitForTermination(Int timeInMs) SLANG_OVERRIDE;
147    virtual void terminate(int32_t returnCode) SLANG_OVERRIDE;
148    virtual void kill(int32_t returnCode) SLANG_OVERRIDE;
149
150    WinProcess(HANDLE handle, Stream* const* streams)
151        : m_processHandle(handle)
152    {
153        for (Index i = 0; i < Index(StdStreamType::CountOf); ++i)
154        {
155            m_streams[i] = streams[i];
156        }
157    }
158
159protected:
160    void _hasTerminated();
161    WinHandle m_processHandle; ///< If not set the process has terminated
162};
163
164/* !!!!!!!!!!!!!!!!!!!!!!!!!!! WinPipeStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
165
166WinPipeStream::WinPipeStream(HANDLE handle, FileAccess access, bool isOwned)
167    : m_streamHandle(handle), m_access(access), m_isOwned(isOwned)
168{
169
170    // On Win32 a HANDLE has to be handled differently if it's a PIPE or FILE, so first determine
171    // if it really is a pipe.
172    // http://msdn.microsoft.com/en-us/library/aa364960(VS.85).aspx
173    m_isPipe = ::GetFileType(handle) == FILE_TYPE_PIPE;
174
175    if (m_isPipe)
176    {
177        // It might be handy to get information about the handle
178        // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-getnamedpipeinfo
179
180        DWORD flags, outBufferSize, inBufferSize, maxInstances;
181        // It appears that by default windows pipe buffer size is 4k.
182        if (GetNamedPipeInfo(handle, &flags, &outBufferSize, &inBufferSize, &maxInstances))
183        {
184        }
185    }
186}
187
188SlangResult WinPipeStream::_updateState(BOOL res)
189{
190    if (res)
191    {
192        return SLANG_OK;
193    }
194    else
195    {
196        const auto err = GetLastError();
197
198        if (err == ERROR_BROKEN_PIPE)
199        {
200            m_streamHandle.setNull();
201            return SLANG_OK;
202        }
203
204        SLANG_UNUSED(err);
205        return SLANG_FAIL;
206    }
207}
208
209SlangResult WinPipeStream::read(void* buffer, size_t length, size_t& outReadBytes)
210{
211    outReadBytes = 0;
212    if (!_has(FileAccess::Read))
213    {
214        return SLANG_E_NOT_AVAILABLE;
215    }
216
217    if (m_streamHandle.isNull())
218    {
219        return SLANG_OK;
220    }
221
222    DWORD bytesRead = 0;
223
224    // Check if there is any data, so won't block
225    if (m_isPipe)
226    {
227        DWORD pipeBytesRead = 0;
228        DWORD pipeTotalBytesAvailable = 0;
229        DWORD pipeRemainingBytes = 0;
230
231        // Works on anonymous pipes too
232        // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe
233
234        SLANG_RETURN_ON_FAIL(_updateState(::PeekNamedPipe(
235            m_streamHandle,
236            nullptr,
237            DWORD(0),
238            &pipeBytesRead,
239            &pipeTotalBytesAvailable,
240            &pipeRemainingBytes)));
241        // If there is nothing to read we are done
242        // If we don't do this ReadFile will *block* if there is nothing available
243        if (pipeTotalBytesAvailable == 0)
244        {
245            return SLANG_OK;
246        }
247
248        SLANG_RETURN_ON_FAIL(
249            _updateState(::ReadFile(m_streamHandle, buffer, DWORD(length), &bytesRead, nullptr)));
250    }
251    else
252    {
253        SLANG_RETURN_ON_FAIL(
254            _updateState(::ReadFile(m_streamHandle, buffer, DWORD(length), &bytesRead, nullptr)));
255
256        // If it's not a pipe, and there is nothing left, then we are done.
257        if (length > 0 && bytesRead == 0)
258        {
259            close();
260        }
261    }
262
263    outReadBytes = size_t(bytesRead);
264    return SLANG_OK;
265}
266
267SlangResult WinPipeStream::write(const void* buffer, size_t length)
268{
269    if (!_has(FileAccess::Write))
270    {
271        return SLANG_E_NOT_AVAILABLE;
272    }
273
274    if (m_streamHandle.isNull())
275    {
276        // Writing to closed stream
277        return SLANG_FAIL;
278    }
279
280    DWORD numWritten = 0;
281    BOOL writeResult = ::WriteFile(m_streamHandle, buffer, DWORD(length), &numWritten, nullptr);
282
283    if (!writeResult)
284    {
285        auto err = ::GetLastError();
286
287        if (err == ERROR_BROKEN_PIPE)
288        {
289            close();
290            return SLANG_FAIL;
291        }
292
293        SLANG_UNUSED(err);
294        return SLANG_FAIL;
295    }
296
297    if (numWritten != length)
298    {
299        return SLANG_FAIL;
300    }
301
302    return SLANG_OK;
303}
304
305void WinPipeStream::close()
306{
307    if (!m_isOwned)
308    {
309        // If we don't own it just detach it
310        m_streamHandle.detach();
311    }
312    m_streamHandle.setNull();
313}
314
315SlangResult WinPipeStream::flush()
316{
317    if ((Index(m_access) & Index(FileAccess::Write)) == 0 || m_streamHandle.isNull())
318    {
319        return SLANG_E_NOT_AVAILABLE;
320    }
321
322    // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers
323    if (!::FlushFileBuffers(m_streamHandle))
324    {
325        auto err = GetLastError();
326        SLANG_UNUSED(err);
327    }
328    return SLANG_OK;
329}
330
331/* !!!!!!!!!!!!!!!!!!!!!!!!!!! WinProcess !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
332
333void WinProcess::_hasTerminated()
334{
335    if (!m_processHandle.isNull())
336    {
337        // get exit code for process
338        // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
339
340        DWORD childExitCode = 0;
341        if (::GetExitCodeProcess(m_processHandle, &childExitCode))
342        {
343            m_returnValue = int32_t(childExitCode);
344        }
345        m_processHandle.setNull();
346    }
347}
348
349bool WinProcess::waitForTermination(Int timeInMs)
350{
351    if (m_processHandle.isNull())
352    {
353        return true;
354    }
355
356    const DWORD timeOutTime = (timeInMs < 0) ? INFINITE : DWORD(timeInMs);
357
358    // wait for the process to exit
359    // TODO: set a timeout as a safety measure...
360    auto res = ::WaitForSingleObject(m_processHandle, timeOutTime);
361
362    if (res == WAIT_TIMEOUT)
363    {
364        return false;
365    }
366
367    _hasTerminated();
368    return true;
369}
370
371bool WinProcess::isTerminated()
372{
373    return waitForTermination(0);
374}
375
376void WinProcess::terminate(int32_t returnCode)
377{
378    if (!isTerminated())
379    {
380        // If it's not terminated, try terminating.
381        // Might take time, so use isTerminated to check
382        ::TerminateProcess(m_processHandle, UINT32(returnCode));
383    }
384}
385
386void WinProcess::kill(int32_t returnCode)
387{
388    if (!isTerminated())
389    {
390        // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess
391        ::TerminateProcess(m_processHandle, UINT32(returnCode));
392
393        // Just assume it's done and set the return code
394        m_returnValue = returnCode;
395        m_processHandle.setNull();
396    }
397}
398
399/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
400
401/* static */ StringEscapeHandler* Process::getEscapeHandler()
402{
403    return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space);
404}
405
406/* static */ UnownedStringSlice Process::getExecutableSuffix()
407{
408    return UnownedStringSlice::fromLiteral(".exe");
409}
410
411/* static */ SlangResult Process::getStdStream(StdStreamType type, RefPtr<Stream>& out)
412{
413    switch (type)
414    {
415    case StdStreamType::In:
416        {
417            out = new WinPipeStream(GetStdHandle(STD_INPUT_HANDLE), FileAccess::Read, false);
418            return SLANG_OK;
419        }
420    case StdStreamType::Out:
421        {
422            out = new WinPipeStream(GetStdHandle(STD_OUTPUT_HANDLE), FileAccess::Write, false);
423            return SLANG_OK;
424        }
425    case StdStreamType::ErrorOut:
426        {
427            out = new WinPipeStream(GetStdHandle(STD_ERROR_HANDLE), FileAccess::Write, false);
428            return SLANG_OK;
429        }
430    }
431
432    return SLANG_FAIL;
433}
434
435/* static */ SlangResult Process::create(
436    const CommandLine& commandLine,
437    Process::Flags flags,
438    RefPtr<Process>& outProcess)
439{
440    WinHandle childStdOutRead;
441    WinHandle childStdErrRead;
442    WinHandle childStdInWrite;
443
444    WinHandle processHandle;
445    {
446        WinHandle childStdOutWrite;
447        WinHandle childStdErrWrite;
448        WinHandle childStdInRead;
449
450        SECURITY_ATTRIBUTES securityAttributes;
451        securityAttributes.nLength = sizeof(securityAttributes);
452        securityAttributes.lpSecurityDescriptor = nullptr;
453        securityAttributes.bInheritHandle = true;
454
455        // 0 means use the 'system default'
456        // const DWORD bufferSize = 64 * 1024;
457        const DWORD bufferSize = 0;
458
459        {
460            WinHandle childStdOutReadTmp;
461            WinHandle childStdErrReadTmp;
462            WinHandle childStdInWriteTmp;
463            // create stdout pipe for child process
464            SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(
465                childStdOutReadTmp.writeRef(),
466                childStdOutWrite.writeRef(),
467                &securityAttributes,
468                bufferSize));
469            if ((flags & Process::Flag::DisableStdErrRedirection) == 0)
470            {
471                // create stderr pipe for child process
472                SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(
473                    childStdErrReadTmp.writeRef(),
474                    childStdErrWrite.writeRef(),
475                    &securityAttributes,
476                    bufferSize));
477            }
478            // create stdin pipe for child process
479            SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(
480                childStdInRead.writeRef(),
481                childStdInWriteTmp.writeRef(),
482                &securityAttributes,
483                bufferSize));
484
485            const HANDLE currentProcess = GetCurrentProcess();
486
487            // https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
488
489            // create a non-inheritable duplicate of the stdout reader
490            SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(
491                currentProcess,
492                childStdOutReadTmp,
493                currentProcess,
494                childStdOutRead.writeRef(),
495                0,
496                FALSE,
497                DUPLICATE_SAME_ACCESS));
498            // create a non-inheritable duplicate of the stderr reader
499            if (childStdErrReadTmp)
500                SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(
501                    currentProcess,
502                    childStdErrReadTmp,
503                    currentProcess,
504                    childStdErrRead.writeRef(),
505                    0,
506                    FALSE,
507                    DUPLICATE_SAME_ACCESS));
508            // create a non-inheritable duplicate of the stdin writer
509            SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(
510                currentProcess,
511                childStdInWriteTmp,
512                currentProcess,
513                childStdInWrite.writeRef(),
514                0,
515                FALSE,
516                DUPLICATE_SAME_ACCESS));
517        }
518
519        // TODO: switch to proper wide-character versions of these...
520        STARTUPINFOW startupInfo;
521        ZeroMemory(&startupInfo, sizeof(startupInfo));
522        startupInfo.cb = sizeof(startupInfo);
523        startupInfo.hStdError = childStdErrWrite;
524        startupInfo.hStdOutput = childStdOutWrite;
525        startupInfo.hStdInput = childStdInRead;
526        startupInfo.dwFlags = STARTF_USESTDHANDLES;
527
528        OSString pathBuffer;
529        LPCWSTR path = nullptr;
530
531        const auto& exe = commandLine.m_executableLocation;
532        if (exe.m_type == ExecutableLocation::Type::Path)
533        {
534            // If it 'Path' specified we pass in as the lpApplicationName to limit
535            // searching.
536            pathBuffer = exe.m_pathOrName.toWString();
537            path = pathBuffer.begin();
538        }
539
540        // Produce the command line string
541        String cmdString = commandLine.toString();
542        OSString cmdStringBuffer = cmdString.toWString();
543
544        // Now we can actually get around to starting a process
545        PROCESS_INFORMATION processInfo;
546        ZeroMemory(&processInfo, sizeof(processInfo));
547
548        // https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
549
550        DWORD createFlags = CREATE_NO_WINDOW;
551
552        if (flags & Process::Flag::AttachDebugger)
553        {
554            createFlags |= CREATE_SUSPENDED;
555        }
556
557        // From docs:
558        // If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string
559        // pointed to by lpApplicationName specifies the module to execute, and the null-terminated
560        // string pointed to by lpCommandLine specifies the command line.
561
562        // JS:
563        // Somewhat confusingly this means that even if lpApplicationName is specified, it muse
564        // *ALSO* be included as the first whitespace delimited arg must *also* be the (possibly)
565        // quoted executable
566
567        // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa
568        // `CreateProcess` requires write access to this, for some reason...
569        BOOL success = CreateProcessW(
570            path,
571            (LPWSTR)cmdStringBuffer.begin(),
572            nullptr,
573            nullptr,
574            true,
575            createFlags,
576            nullptr, // TODO: allow specifying environment variables?
577            nullptr,
578            &startupInfo,
579            &processInfo);
580
581        if (!success)
582        {
583            DWORD err = GetLastError();
584            SLANG_UNUSED(err);
585            return SLANG_FAIL;
586        }
587
588        if (flags & Process::Flag::AttachDebugger)
589        {
590            // Lets see if we can set up to debug
591            // https://docs.microsoft.com/en-us/windows/win32/debug/debugging-a-running-process
592
593            // DebugActiveProcess(processInfo.dwProcessId);
594
595            // Resume the thread
596            ResumeThread(processInfo.hThread);
597        }
598
599        // close handles we are now done with
600        CloseHandle(processInfo.hThread);
601
602        // Save the process handle
603        processHandle = processInfo.hProcess;
604    }
605
606    RefPtr<Stream> streams[Index(StdStreamType::CountOf)];
607
608    if (childStdErrRead)
609        streams[Index(StdStreamType::ErrorOut)] =
610            new WinPipeStream(childStdErrRead.detach(), FileAccess::Read);
611    streams[Index(StdStreamType::Out)] =
612        new WinPipeStream(childStdOutRead.detach(), FileAccess::Read);
613    streams[Index(StdStreamType::In)] =
614        new WinPipeStream(childStdInWrite.detach(), FileAccess::Write);
615    outProcess = new WinProcess(processHandle.detach(), streams[0].readRef());
616
617    return SLANG_OK;
618}
619
620/* static */ void Process::sleepCurrentThread(Int timeInMs)
621{
622    ::Sleep(DWORD(timeInMs));
623}
624
625static uint64_t _getClockFrequency()
626{
627    LARGE_INTEGER timerFrequency;
628    QueryPerformanceFrequency(&timerFrequency);
629    return timerFrequency.QuadPart;
630}
631
632static const uint64_t g_frequency = _getClockFrequency();
633
634/* static */ uint64_t Process::getClockFrequency()
635{
636    return g_frequency;
637}
638
639/* static */ uint64_t Process::getClockTick()
640{
641    LARGE_INTEGER counter;
642    QueryPerformanceCounter(&counter);
643    return counter.QuadPart;
644}
645
646uint32_t Process::getId()
647{
648    return _getpid();
649}
650
651
652} // namespace Slang