summaryrefslogtreecommitdiffstats
path: root/source/core/unix/slang-unix-process.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/core/unix/slang-unix-process.cpp')
-rw-r--r--source/core/unix/slang-unix-process.cpp430
1 files changed, 430 insertions, 0 deletions
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