yum-mirror/slang

Making it easier to work with shaders

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

ncelikNVFix UnixPipeStream::read() not handling EOF (#8626)d30ae275e

master
17.2 KiB682 linesraw
1// slang-unix-process.cpp
2#include "../slang-common.h"
3#include "../slang-memory-arena.h"
4#include "../slang-process.h"
5#include "../slang-string-escape-util.h"
6#include "../slang-string-util.h"
7
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12// #include <dirent.h>
13#include <errno.h>
14#include <fcntl.h>
15#include <poll.h>
16#include <sys/stat.h>
17#include <sys/types.h>
18#include <sys/wait.h>
19#include <unistd.h>
20
21#if SLANG_OSX
22#include <signal.h>
23#endif
24
25#include <time.h>
26
27namespace Slang
28{
29
30class UnixProcess : public Process
31{
32public:
33    // Process
34    virtual bool isTerminated() SLANG_OVERRIDE;
35    virtual bool waitForTermination(Int timeInMs) SLANG_OVERRIDE;
36    virtual void terminate(int32_t returnValue) SLANG_OVERRIDE;
37    virtual void kill(int32_t returnValue) SLANG_OVERRIDE;
38
39    UnixProcess(pid_t pid, Stream* const* streams);
40
41protected:
42    /// Returns true if terminated
43    bool _updateTerminationState(int options);
44
45    bool m_isTerminated = false; ///< True if ths process is terminated
46    pid_t m_pid;                 ///< The process id
47};
48
49class UnixPipeStream : public Stream
50{
51public:
52    typedef UnixPipeStream ThisType;
53
54    // Stream
55    virtual Int64 getPosition() SLANG_OVERRIDE { return 0; }
56    virtual SlangResult seek(SeekOrigin origin, Int64 offset) SLANG_OVERRIDE
57    {
58        SLANG_UNUSED(origin);
59        SLANG_UNUSED(offset);
60        return SLANG_E_NOT_AVAILABLE;
61    }
62    virtual SlangResult read(void* buffer, size_t length, size_t& outReadBytes) SLANG_OVERRIDE;
63    virtual SlangResult write(const void* buffer, size_t length) SLANG_OVERRIDE;
64    virtual bool isEnd() SLANG_OVERRIDE { return m_isClosed; }
65    virtual bool canRead() SLANG_OVERRIDE { return _has(FileAccess::Read) && !m_isClosed; }
66    virtual bool canWrite() SLANG_OVERRIDE { return _has(FileAccess::Write) && !m_isClosed; }
67    virtual void close() SLANG_OVERRIDE;
68    virtual SlangResult flush() SLANG_OVERRIDE;
69
70    UnixPipeStream(int fd, FileAccess access, bool isOwned)
71        : m_fd(fd), m_access(access), m_isOwned(isOwned), m_isClosed(false)
72    {
73    }
74
75protected:
76    /// This read file descriptor non blocking. Doing so will change the behavior of
77    /// read - it can fail and return an error indicating there is no data, instead of blocking.
78    /// Currently this mechanism isn't used, as checking via poll seemed to work.
79    void _setReadNonBlocking()
80    {
81        // Makes non blocking
82        if (_has(FileAccess::Read))
83        {
84            // Make non blocking, for read
85            fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) | O_NONBLOCK);
86        }
87    }
88    bool _has(FileAccess access) const { return (Index(access) & Index(m_access)) != 0; }
89
90    bool m_isClosed;     ///< If true this stream has been closed (ie cannot read/write to anymore)
91    bool m_isOwned;      ///< True if m_fd is owned by this object.
92    FileAccess m_access; ///< Access allowed to this stream - either Read or Write
93    int m_fd;            /// The 'file descriptor' for the pipe
94};
95
96/* !!!!!!!!!!!!!!!!!!!!!! UnixProcess !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
97
98UnixProcess::UnixProcess(pid_t pid, Stream* const* streams)
99    : m_pid(pid)
100{
101    // Set to an 'odd value'
102    m_returnValue = -1;
103
104    for (Index i = 0; i < SLANG_COUNT_OF(m_streams); ++i)
105    {
106        m_streams[i] = streams[i];
107    }
108}
109
110bool UnixProcess::_updateTerminationState(int options)
111{
112    if (!m_isTerminated)
113    {
114        int childStatus;
115        const pid_t terminatedPid = waitpid(m_pid, &childStatus, options);
116        if (terminatedPid == -1)
117        {
118            // Guess we should just mark as terminated
119            m_isTerminated = true;
120
121            fprintf(stderr, "error: `waitpid` failed\n");
122        }
123        else if (terminatedPid == m_pid)
124        {
125            if (WIFEXITED(childStatus))
126            {
127                m_returnValue = (int)(int8_t)WEXITSTATUS(childStatus);
128            }
129            m_isTerminated = true;
130        }
131    }
132    return m_isTerminated;
133}
134
135bool UnixProcess::isTerminated()
136{
137    if (m_isTerminated)
138    {
139        return true;
140    }
141    return _updateTerminationState(WNOHANG);
142}
143
144bool UnixProcess::waitForTermination(Int timeInMs)
145{
146    // If < 0 we will wait blocking until terminated
147    if (timeInMs < 0)
148    {
149        while (!_updateTerminationState(0))
150            ;
151        return true;
152    }
153
154    // Note that the amount of time waiting is very approximate (we are relying on sleeps time and
155    // don't take into account time outside of sleeping)
156
157    // How often to test
158    const Int checkRateMs = 100; /// Check every 0.1 seconds
159
160    while (timeInMs > 0)
161    {
162        if (_updateTerminationState(WNOHANG))
163        {
164            return true;
165        }
166
167        // Work out how long to sleep for
168        const Int sleepMs = (timeInMs >= checkRateMs) ? checkRateMs : timeInMs;
169
170        // Sleep
171        sleepCurrentThread(sleepMs);
172
173        timeInMs -= sleepMs;
174    }
175
176    return _updateTerminationState(WNOHANG);
177}
178
179void UnixProcess::terminate(int32_t returnValue)
180{
181    // Using this mechanism, we can't set a returnValue so just ignore
182    SLANG_UNUSED(returnValue);
183
184    if (!isTerminated())
185    {
186        // Request the process terminates
187        ::kill(m_pid, SIGTERM);
188    }
189}
190
191void UnixProcess::kill(int32_t returnValue)
192{
193    if (!isTerminated())
194    {
195        // We waited, lets just terminate with kill
196        ::kill(m_pid, SIGKILL);
197
198        // Set the return value
199        m_returnValue = returnValue;
200        // Mark as terminated
201        m_isTerminated = true;
202    }
203}
204
205/* !!!!!!!!!!!!!!!!!!!!!! UnixPipeStream !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
206
207void UnixPipeStream::close()
208{
209    if (!m_isClosed)
210    {
211        if (m_isOwned)
212        {
213            ::close(m_fd);
214        }
215
216        m_isClosed = true;
217        // Make something hopefully invalid
218        m_fd = -1;
219    }
220}
221
222SlangResult UnixPipeStream::flush()
223{
224#if 0
225    // https://stackoverflow.com/questions/43184035/flushing-pipe-without-closing-in-c
226    // Makes the case that flushing is not applicable with pipes.
227    if (canWrite())
228    {
229        // We might want to use
230        ::fsync(m_fd);
231    }
232#endif
233    return SLANG_OK;
234}
235
236SlangResult UnixPipeStream::read(void* buffer, size_t length, size_t& outReadBytes)
237{
238    outReadBytes = 0;
239
240    if (!_has(FileAccess::Read))
241    {
242        return SLANG_E_NOT_AVAILABLE;
243    }
244    if (m_isClosed)
245    {
246        return SLANG_OK;
247    }
248
249    // Check if it's hung up.
250    pollfd pollInfo;
251
252    pollInfo.fd = m_fd;
253    pollInfo.events = POLLIN | POLLHUP;
254    pollInfo.revents = 0;
255
256    // https://linux.die.net/man/2/poll
257
258    // Return immediately
259    const int pollTimeout = 0;
260
261    const int pollResult = ::poll(&pollInfo, 1, pollTimeout);
262    if (pollResult < 0)
263    {
264        return SLANG_FAIL;
265    }
266
267    // If there are no poll events, we are done
268    if (pollResult == 0)
269    {
270        return SLANG_OK;
271    }
272
273    // If there is data read that first
274    if (pollInfo.revents & POLLIN)
275    {
276        auto count = ::read(m_fd, buffer, length);
277
278        // If it's -1 it seems like an error
279        if (count == -1)
280        {
281            const int err = errno;
282
283            // On non blocking pipe these indicate there could be more to come
284            if (err == EAGAIN || err == EWOULDBLOCK)
285            {
286                return SLANG_OK;
287            }
288            // Okay - guess we have an error then
289            return SLANG_FAIL;
290        }
291
292        outReadBytes = size_t(count);
293
294        // If no bytes were wanted, then there could still be bytes in the pipe
295        // before a HUP. So don't fall through to check for HUP.
296        //
297        // If some bytes *were* wanted and none were read, we can allow fall through to
298        // handle HUP.
299        if (length == 0 || count > 0)
300        {
301            return SLANG_OK;
302        }
303
304        // End of file.
305        if (count == 0)
306        {
307            close();
308        }
309    }
310
311    if (pollInfo.revents & POLLHUP)
312    {
313        close();
314    }
315
316    if (pollInfo.revents & POLLERR || pollInfo.revents & POLLNVAL)
317    {
318        return SLANG_FAIL;
319    }
320
321    return SLANG_OK;
322}
323
324SlangResult UnixPipeStream::write(const void* buffer, size_t length)
325{
326    if (!_has(FileAccess::Write))
327    {
328        return SLANG_E_NOT_AVAILABLE;
329    }
330    if (m_isClosed)
331    {
332        // The pipe is closed
333        return SLANG_FAIL;
334    }
335
336    pollfd pollInfo;
337
338    pollInfo.fd = m_fd;
339    pollInfo.events = POLLHUP;
340    pollInfo.revents = 0;
341
342    // https://linux.die.net/man/2/poll
343
344    // Return immediately
345    const int pollTimeout = 0;
346
347    int pollResult = ::poll(&pollInfo, 1, pollTimeout);
348    if (pollResult < 0)
349    {
350        return SLANG_FAIL;
351    }
352
353    if (pollInfo.revents & POLLHUP)
354    {
355        close();
356        return SLANG_FAIL;
357    }
358
359    const ssize_t writeResult = ::write(m_fd, buffer, length);
360
361    if (writeResult < 0 || size_t(writeResult) != length)
362    {
363        return SLANG_FAIL;
364    }
365
366    return SLANG_OK;
367}
368
369/* !!!!!!!!!!!!!!!!!!!!!! Process !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
370
371/* static */ UnownedStringSlice Process::getExecutableSuffix()
372{
373#if __CYGWIN__
374    return UnownedStringSlice::fromLiteral(".exe");
375#else
376    return UnownedStringSlice::fromLiteral("");
377#endif
378}
379
380/* static */ StringEscapeHandler* Process::getEscapeHandler()
381{
382    return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space);
383}
384
385static const int kCannotExecute = 126;
386
387static int pipeCLOEXEC(int pipefd[2])
388{
389#if SLANG_APPLE_FAMILY
390    // without pipe2 on macOS, there's an unavoidable race here where
391    // another process could fork and execv with execWatchPipe before we
392    // can set CLOEXEC on it...
393    if (pipe(pipefd) == -1 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) == -1 ||
394        fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) == -1)
395    {
396        return -1;
397    }
398    return 0;
399#else
400    return pipe2(pipefd, O_CLOEXEC);
401#endif
402}
403
404/* static */ SlangResult Process::create(
405    const CommandLine& commandLine,
406    Process::Flags,
407    RefPtr<Process>& outProcess)
408{
409    const char* whatFailed = nullptr;
410    pid_t childPid;
411
412    //
413    // Set up command line
414    //
415    List<char const*> argPtrs;
416
417    const auto& exe = commandLine.m_executableLocation;
418
419    // Add the command
420    argPtrs.add(exe.m_pathOrName.getBuffer());
421
422    // Add all the args - they don't need any explicit escaping
423    for (auto arg : commandLine.m_args)
424    {
425        // All args for this target must be unescaped (as they are in CommandLine)
426        argPtrs.add(arg.getBuffer());
427    }
428
429    // Terminate with a null
430    argPtrs.add(nullptr);
431
432    //
433    // Set up pipes
434    //
435    int stdinPipe[2] = {-1, -1};
436    int stdoutPipe[2] = {-1, -1};
437    int stderrPipe[2] = {-1, -1};
438
439    // We will create this pipe with O_CLOEXEC, so that it gets closed
440    // automatically if the child's exec succeeds
441    int execWatchPipe[2] = {-1, -1};
442
443    if (pipe(stdinPipe) == -1 || pipe(stdoutPipe) == -1 || pipe(stderrPipe) == -1 ||
444        pipeCLOEXEC(execWatchPipe) == -1)
445    {
446        whatFailed = "pipe";
447        goto reportErr;
448    }
449
450    // Make sure that none of our pipes are going to be clobbered by dup2 to
451    // 0,1,2 in the child.
452    whatFailed = "fcntl";
453    int next;
454    if (stdinPipe[0] < 3)
455    {
456        if (-1 == (next = fcntl(stdinPipe[0], F_DUPFD, 3)))
457        {
458            goto reportErr;
459        }
460        close(stdinPipe[0]);
461        stdinPipe[0] = next;
462    }
463    if (stdoutPipe[1] < 3)
464    {
465        if (-1 == (next = fcntl(stdoutPipe[1], F_DUPFD, 3)))
466        {
467            goto reportErr;
468        }
469        close(stdoutPipe[1]);
470        stdoutPipe[1] = next;
471    }
472    if (stderrPipe[1] < 3)
473    {
474        if (-1 == (next = fcntl(stderrPipe[1], F_DUPFD, 3)))
475        {
476            goto reportErr;
477        }
478        close(stderrPipe[1]);
479        stderrPipe[1] = next;
480    }
481    if (execWatchPipe[1] < 3)
482    {
483        if (-1 == (next = fcntl(execWatchPipe[1], F_DUPFD_CLOEXEC, 3)))
484        {
485            goto reportErr;
486        }
487        close(execWatchPipe[1]);
488        execWatchPipe[1] = next;
489    }
490    whatFailed = nullptr;
491
492    childPid = fork();
493    if (childPid == -1)
494    {
495        whatFailed = "fork";
496        goto reportErr;
497    }
498
499    if (childPid == 0)
500    {
501        // We are the child process.
502
503        // Close unused fds and duplicate into standard handles
504
505        ::close(execWatchPipe[0]);
506        ::close(stdinPipe[1]);
507        ::close(stdoutPipe[0]);
508        ::close(stderrPipe[0]);
509
510        dup2(stdinPipe[0], STDIN_FILENO);
511        ::close(stdinPipe[0]);
512        dup2(stdoutPipe[1], STDOUT_FILENO);
513        ::close(stdoutPipe[1]);
514        dup2(stderrPipe[1], STDERR_FILENO);
515        ::close(stderrPipe[1]);
516
517        // Reset locale to ensure the output can be parsed regardless of user's
518        // locale.
519        setenv("LC_ALL", "C", 1);
520
521        if (exe.m_type == ExecutableLocation::Type::Path)
522        {
523            // Use the specified path (ie don't search)
524            ::execv(argPtrs[0], (char* const*)&argPtrs[0]);
525        }
526        else
527        {
528            // Search for the executable
529            ::execvp(argPtrs[0], (char* const*)&argPtrs[0]);
530        }
531
532        // If we get here, then `exec` failed
533
534        // Signal the failure to our parent
535        int execErr = errno;
536        if (::write(execWatchPipe[1], &execErr, sizeof(execErr)))
537            fprintf(stderr, "error: `exec` watch pipe write failed\n");
538
539        // NOTE! Because we have dup2 into STDERR_FILENO, this error will *not* generally appear on
540        // the terminal but in the stderrPipe.
541        fprintf(stderr, "error: `exec` failed\n");
542
543        // Terminate with failure.
544        // Call _exit() rather than exit() so we don't run anything registered with atexit()
545        ::_exit(kCannotExecute);
546    }
547    else
548    {
549        // We are the parent process
550        ::close(execWatchPipe[1]);
551        ::close(stdinPipe[0]);
552        ::close(stdoutPipe[1]);
553        ::close(stderrPipe[1]);
554
555        RefPtr<Stream> streams[Index(StdStreamType::CountOf)];
556
557        // Previously code didn't need to close, so we'll make stream now own the handles
558        streams[Index(StdStreamType::Out)] =
559            new UnixPipeStream(stdoutPipe[0], FileAccess::Read, true);
560        stdoutPipe[0] = -1;
561        streams[Index(StdStreamType::ErrorOut)] =
562            new UnixPipeStream(stderrPipe[0], FileAccess::Read, true);
563        stderrPipe[0] = -1;
564        streams[Index(StdStreamType::In)] =
565            new UnixPipeStream(stdinPipe[1], FileAccess::Write, true);
566        stdinPipe[1] = -1;
567
568        // Check that the exec actually succeeded
569        int execErrCode;
570        // Our success is if we read zero bytes, indicating that the pipe was
571        // closed by the child's exec and O_CLOEXEC. (and us just above)
572        const int readRes = ::read(execWatchPipe[0], &execErrCode, sizeof(execErrCode));
573        if (readRes < 0)
574        {
575            whatFailed = "read from forked process";
576            goto reportErr;
577        }
578        else if (readRes > 0)
579        {
580            // exec failed, and the child reported back to us
581            // don't print messages by default, as we do some speculative
582            // execution of processes to see if they exist and it gets noisy
583            const bool verbose = false;
584            if (verbose)
585            {
586                fprintf(
587                    stderr,
588                    "error: exec for \"%s\" failed: %s\n",
589                    argPtrs[0],
590                    ::strerror(execErrCode));
591            }
592            whatFailed = "exec";
593            // Don't report the exec as we expect some of them to fail
594            goto closePipes;
595        }
596
597        outProcess = new UnixProcess(childPid, streams[0].readRef());
598    }
599
600    goto closePipes;
601
602    // Report any error and then cleanup
603reportErr:
604    fprintf(stderr, "error: `%s` failed (%s)\n", whatFailed, strerror(errno));
605closePipes:
606    ::close(execWatchPipe[0]);
607    ::close(execWatchPipe[1]);
608    ::close(stdinPipe[0]);
609    ::close(stdinPipe[1]);
610    ::close(stderrPipe[0]);
611    ::close(stderrPipe[1]);
612    ::close(stdoutPipe[0]);
613    ::close(stdoutPipe[1]);
614
615    return whatFailed ? SLANG_FAIL : SLANG_OK;
616}
617
618/* static */ uint64_t Process::getClockFrequency()
619{
620    return 1000000000;
621}
622
623/* static */ uint64_t Process::getClockTick()
624{
625    struct timespec now;
626    clock_gettime(CLOCK_MONOTONIC, &now);
627    return uint64_t(now.tv_sec) * 1000000000 + now.tv_nsec;
628}
629
630/* static */ void Process::sleepCurrentThread(Int timeInMs)
631{
632    struct timespec timeSpec;
633
634    if (timeInMs >= 1000)
635    {
636        timeSpec.tv_sec = timeInMs / 1000;
637        timeSpec.tv_nsec = (timeInMs % 1000) * 1000 * 1000;
638    }
639    else if (timeInMs > 0)
640    {
641        timeSpec.tv_sec = 0;
642        timeSpec.tv_nsec = timeInMs * 1000 * 1000;
643    }
644    else
645    {
646        timeSpec.tv_sec = 0;
647        timeSpec.tv_nsec = 0;
648    }
649    nanosleep(&timeSpec, nullptr);
650}
651
652/* static */ SlangResult Process::getStdStream(StdStreamType type, RefPtr<Stream>& out)
653{
654    switch (type)
655    {
656    case StdStreamType::In:
657        {
658            out = new UnixPipeStream(STDIN_FILENO, FileAccess::Read, false);
659            break;
660        }
661    case StdStreamType::Out:
662        {
663            out = new UnixPipeStream(STDOUT_FILENO, FileAccess::Write, false);
664            break;
665        }
666    case StdStreamType::ErrorOut:
667        {
668            out = new UnixPipeStream(STDERR_FILENO, FileAccess::Write, false);
669            break;
670        }
671    default:
672        return SLANG_FAIL;
673    }
674    return SLANG_OK;
675}
676
677uint32_t Process::getId()
678{
679    return getpid();
680}
681
682} // namespace Slang