1#include <Foundation/FoundationInternal.h>
2PL_FOUNDATION_INTERNAL_HEADER
4#include <Foundation/IO/OSFile.h>
5#include <Foundation/Logging/Log.h>
6#include <Foundation/System/Process.h>
7#include <Foundation/Threading/Thread.h>
8#include <Foundation/Threading/ThreadUtils.h>
10#include <Foundation/System/SystemInformation.h>
18#ifndef _PL_DEFINED_POLLFD_POD
19# define _PL_DEFINED_POLLFD_POD
20PL_DEFINE_AS_POD_TYPE(
struct pollfd);
53 void operator=(
const plFd&) =
delete;
54 void operator=(
plFd&& other)
61 void TakeOwnership(
int fd)
67 int Borrow()
const {
return m_fd; }
81 if (addFlags & O_CLOEXEC)
83 int flags = fcntl(m_fd, F_GETFD);
85 if (fcntl(m_fd, F_SETFD, flags) != 0)
87 plLog::Error(
"Failed to set flags on {}: {}", m_fd, errno);
90 addFlags &= ~O_CLOEXEC;
95 int flags = fcntl(m_fd, F_GETFL);
97 if (fcntl(m_fd, F_SETFD, flags) != 0)
99 plLog::Error(
"Failed to set flags on {}: {}", m_fd, errno);
111#if PL_ENABLED(PL_USE_LINUX_POSIX_EXTENSIONS)
112 if (pipe2((
int*)fds, flags) != 0)
117 if (pipe((
int*)fds) != 0)
121 if (flags != 0 && (fds[0].AddFlags(flags).Failed() || fds[1].AddFlags(flags).Failed()))
137 struct ProcessStartupError
139 enum class Type : plUInt32
141 FailedToChangeWorkingDirectory = 0,
158 pid_t m_childPid = -1;
159 bool m_exitCodeAvailable =
false;
160 bool m_processSuspended =
false;
170 plFd m_wakeupPipeReadEnd;
171 plFd m_wakeupPipeWriteEnd;
173 static void* StreamWatcherThread(
void* context)
180 pollfds.
PushBack({self->m_wakeupPipeReadEnd.Borrow(), POLLIN, 0});
181 for (StdStreamInfo& stream : self->m_streams)
183 pollfds.
PushBack({stream.fd.Borrow(), POLLIN, 0});
193 if (pollfds[0].revents != 0)
198 for (plUInt32 i = 1; i < pollfds.
GetCount(); ++i)
200 if (pollfds[i].revents & POLLIN)
203 StdStreamInfo& stream = self->m_streams[i - 1];
206 ssize_t numBytes = read(stream.fd.Borrow(), buffer, PL_ARRAY_SIZE(buffer));
209 if (errno == EWOULDBLOCK)
213 plLog::Error(
"Process Posix read error on {}: {}", stream.fd.Borrow(), errno);
217 const char* szCurrentPos = buffer;
218 const char* szEndPos = buffer + numBytes;
219 while (szCurrentPos < szEndPos)
227 stream.callback(
plStringView(szCurrentPos, szFound + 1));
233 stream.callback(overflowBuffer);
234 overflowBuffer.
Clear();
236 szCurrentPos = szFound + 1;
242 szCurrentPos = szEndPos;
246 if (numBytes < PL_ARRAY_SIZE(buffer))
252 pollfds[i].revents = 0;
262 for (plUInt32 i = 0; i < self->m_streams.GetCount(); ++i)
267 self->m_streams[i].callback(overflowBuffer);
268 overflowBuffer.
Clear();
271 self->m_streams[i].fd.Close();
280 if (plFd::MakePipe(wakeupPipe, O_NONBLOCK | O_CLOEXEC).Failed())
287 m_wakeupPipeReadEnd = std::move(wakeupPipe[0]);
288 m_wakeupPipeWriteEnd = std::move(wakeupPipe[1]);
291 m_streamWatcherThread = PL_DEFAULT_NEW(
plOSThread, &StreamWatcherThread,
this,
"StdStrmWtch");
292 m_streamWatcherThread->Start();
297 void StopStreamWatcher()
299 if (m_streamWatcherThread)
302 PL_IGNORE_UNUSED(write(m_wakeupPipeWriteEnd.Borrow(), &c, 1));
303 m_streamWatcherThread->Join();
304 m_streamWatcherThread =
nullptr;
306 m_wakeupPipeReadEnd.Close();
307 m_wakeupPipeWriteEnd.Close();
312 m_streams.
PushBack({std::move(fd), callback});
316 plUInt32 GetNumStreams()
const {
return m_streams.
GetCount(); }
318 static plResult StartChildProcess(
const plProcessOptions& opt, pid_t& outPid,
bool suspended,
plFd& outStdOutFd,
plFd& outStdErrFd)
322 plFd startupErrorPipe[2];
326 if (!opt.m_sProcess.IsAbsolutePath())
335 auto envPATH = getenv(
"PATH");
336 if (envPATH ==
nullptr)
338#if _POSIX_C_SOURCE >= 2 || _XOPEN_SOURCE
339 size_t confPathSize = confstr(_CS_PATH,
nullptr, 0);
340 if (confPathSize > 0)
358 path.
Split(
false, pathParts,
":");
360 for (
auto& pathPart : pathParts)
362 executablePath = pathPart;
368 executablePath.
Clear();
377 if (opt.m_onStdOut.IsValid())
379 if (plFd::MakePipe(stdoutPipe).Failed())
383 if (stdoutPipe[0].AddFlags(O_NONBLOCK).Failed())
389 if (opt.m_onStdError.IsValid())
391 if (plFd::MakePipe(stderrPipe).Failed())
395 if (stderrPipe[0].AddFlags(O_NONBLOCK).Failed())
401 if (plFd::MakePipe(startupErrorPipe, O_CLOEXEC).Failed())
406 pid_t childPid = fork();
416 if (raise(SIGSTOP) < 0)
422 if (opt.m_bHideConsoleWindow ==
true)
425 int stdinReplace = open(
"/dev/null", O_RDONLY);
426 dup2(stdinReplace, STDIN_FILENO);
429 if (!opt.m_onStdOut.IsValid())
431 int stdoutReplace = open(
"/dev/null", O_WRONLY);
432 dup2(stdoutReplace, STDOUT_FILENO);
433 close(stdoutReplace);
436 if (!opt.m_onStdError.IsValid())
438 int stderrReplace = open(
"/dev/null", O_WRONLY);
439 dup2(stderrReplace, STDERR_FILENO);
440 close(stderrReplace);
446 PL_ASSERT_NOT_IMPLEMENTED;
449 if (opt.m_onStdOut.IsValid())
451 stdoutPipe[0].Close();
452 dup2(stdoutPipe[1].Borrow(), STDOUT_FILENO);
453 stdoutPipe[1].Close();
456 if (opt.m_onStdError.IsValid())
458 stderrPipe[0].Close();
459 dup2(stderrPipe[1].Borrow(), STDERR_FILENO);
460 stderrPipe[1].Close();
463 startupErrorPipe[0].Close();
468 for (
const plString& arg : opt.m_Arguments)
470 args.
PushBack(
const_cast<char*
>(arg.GetData()));
474 if (!opt.m_sWorkingDirectory.IsEmpty())
476 if (chdir(opt.m_sWorkingDirectory.GetData()) < 0)
478 auto err = ProcessStartupError{ProcessStartupError::Type::FailedToChangeWorkingDirectory, 0};
479 PL_IGNORE_UNUSED(write(startupErrorPipe[1].Borrow(), &err,
sizeof(err)));
480 startupErrorPipe[1].Close();
485 if (execv(executablePath, args.
GetData()) < 0)
487 auto err = ProcessStartupError{ProcessStartupError::Type::FailedToExecv, errno};
488 PL_IGNORE_UNUSED(write(startupErrorPipe[1].Borrow(), &err,
sizeof(err)));
489 startupErrorPipe[1].Close();
495 startupErrorPipe[1].Close();
496 stdoutPipe[1].Close();
497 stderrPipe[1].Close();
499 ProcessStartupError err = {};
500 auto errSize = read(startupErrorPipe[0].Borrow(), &err,
sizeof(err));
501 startupErrorPipe[0].Close();
508 PL_ASSERT_DEV(errSize ==
sizeof(err),
"Child process should have written a full ProcessStartupError struct");
511 case ProcessStartupError::Type::FailedToChangeWorkingDirectory:
512 plLog::Error(
"Failed to start process '{}' because the given working directory '{}' is invalid", opt.m_sProcess, opt.m_sWorkingDirectory);
514 case ProcessStartupError::Type::FailedToExecv:
515 plLog::Error(
"Failed to exec when starting process '{}' the error code is '{}'", opt.m_sProcess, err.errorCode);
523 if (opt.m_onStdOut.IsValid())
525 outStdOutFd = std::move(stdoutPipe[0]);
528 if (opt.m_onStdError.IsValid())
530 outStdErrFd = std::move(stderrPipe[0]);
538plProcess::plProcess()
543plProcess::~plProcess()
545 if (GetState() == plProcessState::Running)
547 plLog::Dev(
"Process still running - terminating '{}'", m_sProcess);
549 Terminate().IgnoreResult();
557plResult plProcess::Execute(
const plProcessOptions& opt, plInt32* out_iExitCode )
562 if (plProcessImpl::StartChildProcess(opt, childPid,
false, stdoutFd, stderrFd).Failed())
568 if (stdoutFd.IsValid())
570 impl.AddStream(std::move(stdoutFd), opt.m_onStdOut);
573 if (stderrFd.IsValid())
575 impl.AddStream(std::move(stderrFd), opt.m_onStdError);
578 if (impl.GetNumStreams() > 0 && impl.StartStreamWatcher().Failed())
583 int childStatus = -1;
584 pid_t waitedPid = waitpid(childPid, &childStatus, 0);
589 if (out_iExitCode !=
nullptr)
591 if (WIFEXITED(childStatus))
593 *out_iExitCode = WEXITSTATUS(childStatus);
605 PL_ASSERT_DEV(m_pImpl->m_childPid == -1,
"Can not reuse an instance of plProcess");
610 if (plProcessImpl::StartChildProcess(opt, m_pImpl->m_childPid, launchFlags.
IsSet(plProcessLaunchFlags::Suspended), stdoutFd, stderrFd).Failed())
615 m_pImpl->m_exitCodeAvailable =
false;
616 m_pImpl->m_processSuspended = launchFlags.
IsSet(plProcessLaunchFlags::Suspended);
618 if (stdoutFd.IsValid())
620 m_pImpl->AddStream(std::move(stdoutFd), opt.m_onStdOut);
623 if (stderrFd.IsValid())
625 m_pImpl->AddStream(std::move(stderrFd), opt.m_onStdError);
628 if (m_pImpl->GetNumStreams() > 0)
630 if (m_pImpl->StartStreamWatcher().Failed())
636 if (launchFlags.
IsSet(plProcessLaunchFlags::Detached))
644plResult plProcess::ResumeSuspended()
646 if (m_pImpl->m_childPid < 0 || !m_pImpl->m_processSuspended)
651 if (kill(m_pImpl->m_childPid, SIGCONT) < 0)
655 m_pImpl->m_processSuspended =
false;
662 PL_SCOPE_EXIT(m_pImpl->StopStreamWatcher());
666 if (waitpid(m_pImpl->m_childPid, &childStatus, 0) < 0)
677 waitResult = waitpid(m_pImpl->m_childPid, &childStatus, WNOHANG);
687 if (timeSpent > timeout)
695 if (WIFEXITED(childStatus))
697 m_iExitCode = WEXITSTATUS(childStatus);
703 m_pImpl->m_exitCodeAvailable =
true;
710 if (m_pImpl->m_childPid == -1)
715 PL_SCOPE_EXIT(m_pImpl->StopStreamWatcher());
717 if (kill(m_pImpl->m_childPid, SIGKILL) < 0)
724 m_pImpl->m_exitCodeAvailable =
true;
730plProcessState plProcess::GetState()
const
732 if (m_pImpl->m_childPid == -1)
734 return plProcessState::NotStarted;
737 if (m_pImpl->m_exitCodeAvailable)
739 return plProcessState::Finished;
742 int childStatus = -1;
743 int waitResult = waitpid(m_pImpl->m_childPid, &childStatus, WNOHANG);
746 m_iExitCode = WEXITSTATUS(childStatus);
747 m_pImpl->m_exitCodeAvailable =
true;
749 m_pImpl->StopStreamWatcher();
751 return plProcessState::Finished;
754 return plProcessState::Running;
757void plProcess::Detach()
759 m_pImpl->m_childPid = -1;
762plOsProcessHandle plProcess::GetProcessHandle()
const
764 PL_ASSERT_DEV(
false,
"There is no process handle on posix");
768plOsProcessID plProcess::GetProcessID()
const
770 PL_ASSERT_DEV(m_pImpl->m_childPid != -1,
"No ProcessID available");
771 return m_pImpl->m_childPid;
774plOsProcessID plProcess::GetCurrentProcessID()
void PushBack(const T &value)
Pushes value at the end of the array.
Definition ArrayBase_inl.h:333
T * GetData()
Returns a pointer to the array data, or nullptr if the array is empty.
Definition ArrayBase_inl.h:423
void SetCountUninitialized(plUInt32 uiCount)
Resizes the array to have exactly uiCount elements. Extra elements might be uninitialized.
Definition ArrayBase_inl.h:155
void SetCount(plUInt32 uiCount)
Resizes the array to have exactly uiCount elements. Default constructs extra elements if the array is...
Definition ArrayBase_inl.h:106
plUInt32 GetCount() const
Returns the number of active elements in the array.
Definition ArrayBase_inl.h:172
Definition DynamicArray.h:81
Definition Process_posix.h:24
A hybrid array uses in-place storage to handle the first few elements without any allocation....
Definition HybridArray.h:12
static void Dev(plLogInterface *pInterface, const plFormatString &string)
Status information that is nice to have during development.
static void Error(plLogInterface *pInterface, const plFormatString &string)
An error that needs to be fixed as soon as possible.
Definition Log.cpp:375
static plResult GetFileStats(plStringView sFileOrFolder, plFileStats &out_stats)
Gets the stats about the given file or folder. Returns false, if the stats could not be determined.
static const plString GetCurrentWorkingDirectory()
Returns the processes current working directory (CWD).
Implementation of a thread.
Definition OSThread.h:13
plStringBuilder is a class that is meant for creating and modifying strings.
Definition StringBuilder.h:35
void Clear()
Resets this string to be empty. Does not deallocate any previously allocated data,...
Definition StringBuilder_inl.h:88
const char * GetData() const
Returns a char pointer to the internal Utf8 data.
Definition StringBuilder_inl.h:148
void Append(plUInt32 uiChar)
Appends a single Utf32 character.
Definition StringBuilder_inl.h:94
void AppendPath(plStringView sPath1, plStringView sPath2={}, plStringView sPath3={}, plStringView sPath4={})
Appends several path pieces. Makes sure they are always properly separated by a slash.
Definition StringBuilder.cpp:866
static const char * FindSubString(const char *szSource, const char *szStringToFind, const char *pSourceEnd=plUnicodeUtils::GetMaxStringEnd< char >(), const char *szStringToFindEnd=plUnicodeUtils::GetMaxStringEnd< char >())
Searches for the first occurrence of szStringToFind in szSource.
Definition StringUtils.cpp:550
plStringView represent a read-only sub-string of a larger string, as it can store a dedicated string ...
Definition StringView.h:34
void Split(bool bReturnEmptyStrings, Container &ref_output, const char *szSeparator1, const char *szSeparator2=nullptr, const char *szSeparator3=nullptr, const char *szSeparator4=nullptr, const char *szSeparator5=nullptr, const char *szSeparator6=nullptr) const
Fills the given container with plStringView's which represent each found substring....
Definition StringView_inl.h:141
static void Sleep(const plTime &duration)
Suspends the execution of the current thread for the given amount of time. (Precision may vary accord...
Definition ThreadUtils_Posix.h:29
A Unique ptr manages an object and destroys that object when it goes out of scope....
Definition UniquePtr.h:10
constexpr PL_ALWAYS_INLINE T Min(T f1, T f2)
Returns the smaller value, f1 or f2.
Definition Math_inl.h:27
The plBitflags class allows you to work with type-safe bitflags.
Definition Bitflags.h:82
PL_ALWAYS_INLINE bool IsSet(Enum flag) const
Checks if certain flags are set within the bitfield.
Definition Bitflags.h:127
A generic delegate class which supports static functions and member functions.
Definition Delegate.h:76
Holds the stats for a file.
Definition OSFile.h:34
bool m_bIsDirectory
Whether the file object is a file or folder.
Definition OSFile.h:56
Definition Process_posix.h:163
Definition Process_posix.h:152
Default enum for returning failure or success, instead of using a bool.
Definition Types.h:54
bool IsEmpty() const
Returns whether the string is an empty string.
Definition StringBase_inl.h:25
The time class encapsulates a double value storing the time in seconds.
Definition Time.h:12
PL_ALWAYS_INLINE static constexpr plTime MakeFromMilliseconds(double fMilliseconds)
Creates an instance of plTime that was initialized from milliseconds.
Definition Time.h:26
PL_ALWAYS_INLINE constexpr bool IsZero() const
Returns true if the stored time is exactly zero. That typically means the value was not changed from ...
Definition Time.h:50
static plTime Now()
Gets the current time.
Definition Time_Posix.h:12