4#include "Stroika/Foundation/StroikaPreComp.h"
8#if qStroika_Foundation_Common_Platform_POSIX
11#include <sys/resource.h>
23#include "Stroika/Foundation/Containers/Sequence.h"
25#if qStroika_Foundation_Common_Platform_Windows
26#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
28#include "Stroika/Foundation/Execution/Activity.h"
29#include "Stroika/Foundation/Execution/CommandLine.h"
30#include "Stroika/Foundation/Execution/Exceptions.h"
32#include "Stroika/Foundation/Execution/Module.h"
37#include "Stroika/Foundation/IO/FileSystem/FileUtils.h"
40#include "Stroika/Foundation/Memory/Common.h"
43#include "Stroika/Foundation/Streams/MemoryStream.h"
44#include "Stroika/Foundation/Streams/TextToBinary.h"
46#include "ProcessRunner.h"
53using namespace Stroika::Foundation::Debug;
55using namespace Stroika::Foundation::Streams;
56using namespace Stroika::Foundation::Traversal;
59using Memory::MakeSharedPtr;
65#if USE_NOISY_TRACE_IN_THIS_MODULE_
69#if qStroika_Foundation_Common_Platform_POSIX
73 inline void CLOSE_ (
int& fd)
noexcept
75 if (fd >= 0) [[likely]] {
83#if qStroika_Foundation_Common_Platform_POSIX
85 static const int kMaxFD_ = [] () ->
int {
87 constexpr bool kUseSysConf_ =
true;
88#if _BSD_SOURCE || _XOPEN_SOURCE >= 500
89 [[maybe_unused]]
constexpr bool kUseGetDTableSize_ =
true;
91 [[maybe_unused]]
constexpr bool kUseGetDTableSize_ =
false;
93 constexpr bool kUseGetRLimit_ =
true;
94 if constexpr (kUseSysConf_) {
95 result = ::sysconf (_SC_OPEN_MAX);
98 else if constexpr (kUseSysConf_) {
99 result = getdtablesize ();
101 else if constexpr (kUseGetRLimit_) {
103 if (::getrlimit (RLIMIT_NOFILE, &fds) == 0) {
118 Assert (result < 4 * 1024 * 1024);
119 DbgTrace (
"::sysconf (_SC_OPEN_MAX) = {}"_f, result);
125#if qStroika_Foundation_Common_Platform_POSIX
137#if qStroika_Foundation_Common_Platform_POSIX
143 constexpr bool kUseSpawn_ =
false;
145extern char** environ;
148#if qStroika_Foundation_Common_Platform_Windows
152 AutoHANDLE_ (HANDLE h = INVALID_HANDLE_VALUE)
156 AutoHANDLE_ (
const AutoHANDLE_&) =
delete;
161 AutoHANDLE_& operator= (
const AutoHANDLE_& rhs)
165 fHandle = rhs.fHandle;
169 operator HANDLE ()
const
179 if (fHandle != INVALID_HANDLE_VALUE) {
180 Verify (::CloseHandle (fHandle));
181 fHandle = INVALID_HANDLE_VALUE;
184 void ReplaceHandleAsNonInheritable ()
186 HANDLE result = INVALID_HANDLE_VALUE;
187 Verify (::DuplicateHandle (::GetCurrentProcess (), fHandle, ::GetCurrentProcess (), &result, 0, FALSE, DUPLICATE_SAME_ACCESS));
188 Verify (::CloseHandle (fHandle));
195 inline void SAFE_HANDLE_CLOSER_ (HANDLE* h)
198 if (*h != INVALID_HANDLE_VALUE) {
199 Verify (::CloseHandle (*h));
200 *h = INVALID_HANDLE_VALUE;
207 template <Common::IAnyOf<
char,
wchar_t> CHAR_T>
208 struct String2ContigArrayCStrs_ {
209 StackBuffer<CHAR_T> fBytesBuffer;
210 StackBuffer<CHAR_T*, 10 *
sizeof (
void*)> fPtrsBuffer;
211 String2ContigArrayCStrs_ (
const Mapping<basic_string<CHAR_T>, basic_string<CHAR_T>>& data)
212 : String2ContigArrayCStrs_{data.template Map<
Iterable<basic_string<CHAR_T>>> (
213 [] (auto kvp) -> basic_string<CHAR_T> {
return kvp.fKey + SDKSTR (
"=") + kvp.fValue; })}
216 String2ContigArrayCStrs_ (
const Iterable<basic_string<CHAR_T>>& data)
218 StackBuffer<size_t> argsIdx;
219 size_t bufferIndex = 0;
220 for (
const basic_string<CHAR_T>& i : data) {
221 fBytesBuffer.push_back (span{i});
222 fBytesBuffer.push_back (
'\0');
223 argsIdx.push_back (bufferIndex);
224 bufferIndex = fBytesBuffer.GetSize ();
226 fBytesBuffer.push_back (
'\0');
227 auto freeze = fBytesBuffer.
begin ();
228 for (
size_t i : argsIdx) {
229 fPtrsBuffer.push_back (freeze + i);
231 fPtrsBuffer.push_back (
nullptr);
233 String2ContigArrayCStrs_ () =
delete;
234 String2ContigArrayCStrs_ (
const String2ContigArrayCStrs_&) =
delete;
235 String2ContigArrayCStrs_ (String2ContigArrayCStrs_&&) =
delete;
239#if qStroika_Foundation_Common_Platform_Windows
244#ifndef qUsePeekNamedPipe_
245#define qUsePeekNamedPipe_ 0
264 constexpr size_t kPipeBufSize_ = 256 * 1024;
265 constexpr size_t kReadBufSize_ = 32 * 1024;
274String ProcessRunner::Exception::mkMsg_ (
const String& errorMessage,
const optional<String>& stderrSubset,
275 const optional<ExitStatusType>& wExitStatus,
const optional<SignalID>& wTermSig)
282 extraMsg <<
"exit status {}"_f(
int (*wExitStatus));
285 if (not extraMsg.
empty ()) {
288 extraMsg <<
"terminated by signal {}"_f(
int (*wTermSig));
290 if (not extraMsg.
empty ()) {
291 sb <<
": "sv << extraMsg;
295 sb <<
" (captured stderr: "sv
296 << stderrSubset->ReplaceAll (
"\\s+"_RegEx,
" "sv).LimitLength (100, StringShorteningPreference::ePreferKeepRight) <<
")"sv;
306void ProcessRunner::ProcessResultType::ThrowIfFailed ()
308 if (fExitStatus and *fExitStatus != 0) {
309 Throw (
Exception{
"Child process failed"sv, nullopt, *fExitStatus});
311 if (fTerminatedByUncaughtSignalNumber and *fTerminatedByUncaughtSignalNumber != 0) {
312 Throw (
Exception{
"Child process failed"sv, nullopt, nullopt, *fTerminatedByUncaughtSignalNumber});
321 sb <<
"exitStatus: "sv << fExitStatus;
323 if (fTerminatedByUncaughtSignalNumber) {
327 sb <<
"terminatedByUncaughtSignalNumber: "sv << fTerminatedByUncaughtSignalNumber;
338ProcessRunner::BackgroundProcess::BackgroundProcess ()
339 : fRep_{MakeSharedPtr<Rep_> ()}
348 if (
auto o = GetProcessResult ()) {
349 if (o->fExitStatus and o->fExitStatus != ExitStatusType{}) {
352 if (o->fTerminatedByUncaughtSignalNumber) {
363 if (
auto pr = GetChildProcessID ()) {
367 }
while (runUntil > Time::GetTickCount ());
401 if (optional<pid_t> o = fRep_->fDetailedRunnableRep_->fRunningPID.load ()) {
402#if qStroika_Foundation_Common_Platform_POSIX
403 ::kill (SIGTERM, *o);
404#elif qStroika_Foundation_Common_Platform_Windows
406 HANDLE processHandle = ::OpenProcess (PROCESS_TERMINATE,
false, *o);
407 if (processHandle !=
nullptr) {
408 ::TerminateProcess (processHandle, 1);
409 ::CloseHandle (processHandle);
412 DbgTrace (
"::OpenProcess returned null: GetLastError () = {}"_f, GetLastError ());
423 if (fRep_ and fRep_->fDetailedRunnableRep_) {
424 sb <<
"processID: "sv << fRep_->fDetailedRunnableRep_->fRunningPID.load ();
425 sb <<
", processResult: "sv << fRep_->fDetailedRunnableRep_->fProcessResult.load ();
450 r.
Add (i.fKey.AsSDKString (), i.fValue.AsSDKString ());
457#if qStroika_Foundation_Common_Platform_POSIX
459#elif qStroika_Foundation_Common_Platform_Windows
462 r.
Add (SDKSTR (
"PATH"), path);
476 Require (not fOptions_.fDetached);
477 auto activity =
LazyEvalActivity ([
this] () ->
String {
return "running '{}'"_f(this->GetCommandLine ()); });
479 if (timeout == Time::kInfinity) {
483 auto [runable, results] = CreateDetailedRunnable_ ();
485 results->fProcessResult.load ().value_or (ProcessResultType{}).
ThrowIfFailed ();
490 [[maybe_unused]]
auto&& cleanup =
Finally ([&] ()
noexcept { bp.Terminate (); });
492 bp.PropagateIfException ();
494 bp.GetProcessResult ().value_or (ProcessResultType{}).
ThrowIfFailed ();
501 if (timeout == Time::kInfinity) {
502 if (processResult ==
nullptr) {
503 CreateSimpleRunnable_ () ();
506 auto [runnable, prDetails] = CreateDetailedRunnable_ ();
507#if qCompilerAndStdLib_NamedAutoLocalBindingNotCapturable_Buggy
508 auto pd2 = prDetails;
509 [[maybe_unused]]
auto&& cleanup =
Finally ([&] ()
noexcept { *processResult = pd2->fProcessResult.load (); });
511 [[maybe_unused]]
auto&& cleanup =
Finally ([&] ()
noexcept { *processResult = prDetails->fProcessResult.load (); });
517 if (processResult ==
nullptr) {
522 auto [runnable, prDetails] = CreateDetailedRunnable_ ();
523#if qCompilerAndStdLib_NamedAutoLocalBindingNotCapturable_Buggy
524 auto pd2 = prDetails;
525 [[maybe_unused]]
auto&& cleanup =
Finally ([&] ()
noexcept { *processResult = pd2->fProcessResult.load (); });
527 [[maybe_unused]]
auto&& cleanup =
Finally ([&] ()
noexcept { *processResult = prDetails->fProcessResult.load (); });
544 : BinaryToText::Reader::New (readFromBinStrm);
548 if (not cmdStdInValue.empty ()) {
550 : TextToBinary::Writer::New (useStdIn);
551 outStream.Write (cmdStdInValue);
553 Assert (useStdIn.GetReadOffset () == 0);
555 Run (useStdIn, useStdOut, useStdErr, timeout);
558 Assert (useStdOut.GetReadOffset () == 0);
559 Assert (useStdErr.GetReadOffset () == 0);
560 return make_tuple (mkReadStream (useStdOut).ReadAll (), mkReadStream (useStdErr).ReadAll ());
563 String out = mkReadStream (useStdOut).ReadAll ();
564 String err = mkReadStream (useStdErr).ReadAll ();
565#if qStroika_Foundation_Debug_DefaultTracingOn
566 DbgTrace (
"Captured stdout: {}"_f, out);
567 DbgTrace (
"Captured stderr: {}"_f, err);
569 Throw (
Exception{e.fFailureMessage, err, e.fExitStatus, e.fTermSignal});
573 String out = mkReadStream (useStdOut).ReadAll ();
574 String err = mkReadStream (useStdErr).ReadAll ();
575#if qStroika_Foundation_Debug_DefaultTracingOn
576 DbgTrace (
"Captured stdout: {}"_f, out);
577 DbgTrace (
"Captured stderr: {}"_f, err);
579 exception_ptr e = current_exception ();
588 if (fOptions_.fDetached) {
589 Require (in ==
nullptr and out ==
nullptr and error ==
nullptr);
592 this->fStdOut_ = out;
593 this->fStdErr_ = error;
594 BackgroundProcess result;
595 auto [runnable, prDetails] = CreateDetailedRunnable_ ();
596 result.fRep_->fDetailedRunnableRep_ = prDetails;
597 if (fOptions_.fDetached) {
601 result.fRep_->fProcessRunner =
Thread::New (runnable, Thread::eAutoStart,
"ProcessRunner background thread"sv);
609 BackgroundProcess result;
610 auto [runnable, prDetails] = CreateDetailedRunnable_ ();
611 result.fRep_->fDetailedRunnableRep_ = prDetails;
612 if (fOptions_.fDetached) {
616 result.fRep_->fProcessRunner =
Thread::New (runnable, Thread::eAutoStart,
"ProcessRunner background thread"sv);
636 return Run (in, out, error, timeout);
638[[deprecated (
"Since Stroika v3.0d23d")]] tuple<Characters::String, Characters::String>
643 return Run (cmdStdInValue, stringOpts, timeout);
646#if qStroika_Foundation_Common_Platform_POSIX
648void ProcessRunner::Process_Runner_POSIX_ (
const shared_ptr<DetailedRunnableRep_>& runneeDetails,
649 [[maybe_unused]]
const optional<filesystem::path>& executable,
const CommandLine& cmdLine,
653 optional<mode_t> umask = options.fChildUMask;
656 "...,cmdLine='{}',currentDir='{}',..."_f, cmdLine,
660 char trailingStderrBuf[256];
661 char* trailingStderrBufNextByte2WriteAt = begin (trailingStderrBuf);
662 size_t trailingStderrBufNWritten{};
671 int jStdin[2]{-1, -1};
672 int jStdout[2]{-1, -1};
673 int jStderr[2]{-1, -1};
674 [[maybe_unused]]
auto&& cleanup =
Finally ([&] ()
noexcept {
675 ::CLOSE_ (jStdin[0]);
676 ::CLOSE_ (jStdin[1]);
677 ::CLOSE_ (jStdout[0]);
678 ::CLOSE_ (jStdout[1]);
679 ::CLOSE_ (jStderr[0]);
680 ::CLOSE_ (jStderr[1]);
686 jStdin[0] = ::open (
"/dev/null", O_RDONLY);
692 jStdout[1] = ::open (
"/dev/null", O_WRONLY);
698 jStderr[1] = ::open (
"/dev/null", O_WRONLY);
701 Assert (in ==
nullptr or (jStdin[0] >= 3 and jStdin[1] >= 3));
702 Assert (out ==
nullptr or (jStdout[0] >= 3 and jStdout[1] >= 3));
703 Assert (err ==
nullptr or (jStderr[0] >= 3 and jStderr[1] >= 3));
704 DbgTrace (
"jStdout[0-CHILD] = {} and jStdout[1-PARENT] = {}"_f, jStdout[0], jStdout[1]);
711 const char* thisEXEPath_cstr =
nullptr;
712 char** thisEXECArgv =
nullptr;
714 String2ContigArrayCStrs_<char> execDataArgs{
716 thisEXEPath_cstr = execDataArgs.fBytesBuffer.data ();
717 thisEXECArgv = execDataArgs.fPtrsBuffer.data ();
726 if (not kUseSpawn_ and thisEXEPath_cstr[0] ==
'/' and ::access (thisEXEPath_cstr, R_OK | X_OK) < 0) {
728#if USE_NOISY_TRACE_IN_THIS_MODULE_
736 posix_spawn_file_actions_t file_actions{};
743 posix_spawn_file_actions_init (&file_actions);
744 posix_spawn_file_actions_addclose (&file_actions, jStdin[0]);
745 posix_spawn_file_actions_addclose (&file_actions, jStdin[0]);
746 posix_spawn_file_actions_adddup2 (&file_actions, jStdout[1], 1);
747 posix_spawn_file_actions_addclose (&file_actions, jStdout[0]);
748 posix_spawn_file_actions_adddup2 (&file_actions, jStderr[1], 2);
749 posix_spawn_file_actions_addclose (&file_actions, jStderr[1]);
751 posix_spawnattr_t* attr =
nullptr;
752 int status = ::posix_spawnp (&childPID, thisEXEPath_cstr, &file_actions, attr, thisEXECArgv, environ);
758 childPID = DoFork_ ();
762 (void)::umask (*umask);
769 DISABLE_COMPILER_GCC_WARNING_START (
"GCC diagnostic ignored \"-Wunused-result\"")
770 (void)::chdir (useCWD.c_str ());
771 DISABLE_COMPILER_GCC_WARNING_END (
"GCC diagnostic ignored \"-Wunused-result\"")
772 if (options.fDetached) {
794 int useSTDIN = jStdin[0];
795 int useSTDOUT = jStdout[1];
796 int useSTDERR = jStderr[1];
797 Assert (useSTDIN >= 0 and useSTDOUT >= 0 and useSTDERR >= 0);
801 ::dup2 (useSTDIN, 0);
802 ::dup2 (useSTDOUT, 1);
803 ::dup2 (useSTDERR, 2);
806 ::close (jStdout[0]);
807 ::close (jStdout[1]);
808 ::close (jStderr[0]);
809 ::close (jStderr[1]);
811 constexpr bool kCloseAllExtraneousFDsInChild_ =
true;
812 if (kCloseAllExtraneousFDsInChild_) {
814 for (
int i = 3; i < kMaxFD_; ++i) {
818 [[maybe_unused]]
int r = ::execvp (thisEXEPath_cstr, thisEXECArgv);
819#if USE_NOISY_TRACE_IN_THIS_MODULE_
822 myfile.open (
"/tmp/Stroika-ProcessRunner-Exec-Failed-Debug-File.txt");
823 myfile <<
"thisEXEPath_cstr = " << thisEXEPath_cstr << endl;
824 myfile <<
"r = " << r <<
" and errno = " << errno << endl;
827 ::_exit (EXIT_FAILURE);
830 ::_exit (EXIT_FAILURE);
835 Assert (childPID > 0);
837 constexpr size_t kStackBufReadAtATimeSize_ = 10 * 1024;
839#if USE_NOISY_TRACE_IN_THIS_MODULE_
840 DbgTrace (
"In Parent Fork: child process PID={}"_f, childPID);
842 if (runneeDetails !=
nullptr) {
843 runneeDetails->fRunningPID.store (childPID);
848 int& useSTDIN = jStdin[1];
849 int& useSTDOUT = jStdout[0];
850 int& useSTDERR = jStderr[0];
859 if (useSTDIN != -1) {
862 if (useSTDOUT != -1) {
865 if (useSTDERR != -1) {
870 auto readALittleFromProcess = [&] (
int fd,
const OutputStream::Ptr<byte>& stream,
bool write2StdErrCache,
bool* eof =
nullptr,
871 bool* maybeMoreData =
nullptr) ->
void {
873 if (maybeMoreData !=
nullptr) {
874 *maybeMoreData =
false;
876 if (eof !=
nullptr) {
881 uint8_t buf[kStackBufReadAtATimeSize_];
883#if USE_NOISY_TRACE_IN_THIS_MODULE_
884 int skipedThisMany{};
886 while ((nBytesRead = ::read (fd, buf,
sizeof (buf))) > 0) {
887 Assert (nBytesRead <=
sizeof (buf));
888 if (stream !=
nullptr) {
889 stream.Write (span{buf,
static_cast<size_t> (nBytesRead)});
891 if (write2StdErrCache) {
892 for (
size_t i = 0; i < nBytesRead; ++i) {
893 Assert (&trailingStderrBuf[0] <= trailingStderrBufNextByte2WriteAt and trailingStderrBufNextByte2WriteAt < end (trailingStderrBuf));
894 *trailingStderrBufNextByte2WriteAt = buf[i];
895 ++trailingStderrBufNWritten;
896 ++trailingStderrBufNextByte2WriteAt;
897 if (trailingStderrBufNextByte2WriteAt == end (trailingStderrBuf)) {
898 trailingStderrBufNextByte2WriteAt = begin (trailingStderrBuf);
900 Assert (&trailingStderrBuf[0] <= trailingStderrBufNextByte2WriteAt and trailingStderrBufNextByte2WriteAt < end (trailingStderrBuf));
903#if USE_NOISY_TRACE_IN_THIS_MODULE_
904 if (errno == EAGAIN) {
906 if (skipedThisMany++ < 100) {
910 DbgTrace (
"skipped {} spamming EAGAINs"_f, skipedThisMany);
914 buf[(nBytesRead == std::size (buf)) ? (std::size (buf) - 1) : nBytesRead] =
'\0';
915 DbgTrace (
"read from process (fd={}) nBytesRead = {}: {}"_f, fd, nBytesRead,
919#if USE_NOISY_TRACE_IN_THIS_MODULE_
920 DbgTrace (
"from (fd={}) nBytesRead = {}, errno={}"_f, fd, nBytesRead, errno);
922 if (nBytesRead < 0) {
923 if (errno != EINTR and errno != EAGAIN) {
927 if (eof !=
nullptr) {
928 *eof = (nBytesRead == 0);
930 if (maybeMoreData !=
nullptr) {
931 *maybeMoreData = (nBytesRead > 0) or (nBytesRead < 0 and errno == EINTR);
935 bool maybeMoreData =
true;
936 while (maybeMoreData) {
937 readALittleFromProcess (fd, stream, write2StdErrCache,
nullptr, &maybeMoreData);
947 (void)waiter.WaitQuietly (1s);
948 readALittleFromProcess (fd, stream, write2StdErrCache, &eof);
952 if (options.fDetached) {
953 Assert (in ==
nullptr and useSTDOUT == -1 and useSTDERR == -1);
957 byte stdinBuf[kStackBufReadAtATimeSize_];
961 if (optional<span<byte>> bytesReadFromStdIn = in.
ReadNonBlocking (span{stdinBuf})) {
962 Assert (bytesReadFromStdIn->size () <= std::size (stdinBuf));
963 if (bytesReadFromStdIn->empty ()) {
967 const byte* e = bytesReadFromStdIn->data () + bytesReadFromStdIn->size ();
968 for (
const byte* i = bytesReadFromStdIn->data (); i != e;) {
970 readSoNotBlocking (useSTDOUT, out,
false);
971 readSoNotBlocking (useSTDERR, err,
true);
973 int tmp = ::write (useSTDIN, i, e - i);
975 if (tmp < 0 and (errno == EAGAIN or errno == EWOULDBLOCK)) {
980 Assert (bytesWritten >= 0);
981 Assert (bytesWritten <= (e - i));
983 if (bytesWritten == 0) {
995 readSoNotBlocking (useSTDOUT, out,
false);
996 readSoNotBlocking (useSTDERR, err,
true);
1004 readTilEOF (useSTDOUT, out,
false);
1005 readTilEOF (useSTDERR, err,
true);
1008 if (not options.fDetached) {
1015 if (runneeDetails !=
nullptr) {
1018 WIFEXITED (status) ? WEXITSTATUS (status) : optional<int>{}, WIFSIGNALED (status) ? WTERMSIG (status) : optional<int>{}});
1020 else if (result != childPID or not WIFEXITED (status) or WEXITSTATUS (status) != 0) {
1022 DbgTrace (
"childPID={}, result={}, status={}, WIFEXITED={}, WEXITSTATUS={}, WIFSIGNALED={}"_f,
static_cast<int> (childPID),
1023 result, status, WIFEXITED (status), WEXITSTATUS (status), WIFSIGNALED (status));
1025 if (trailingStderrBufNWritten > std::size (trailingStderrBuf)) {
1026 stderrMsg <<
"..."sv;
1027 stderrMsg <<
String::FromLatin1 (Memory::ConstSpan (span{trailingStderrBufNextByte2WriteAt, end (trailingStderrBuf)}));
1029 stderrMsg <<
String::FromLatin1 (Memory::ConstSpan (span{begin (trailingStderrBuf), trailingStderrBufNextByte2WriteAt}));
1031 WIFSIGNALED (status) ? WTERMSIG (status) : optional<uint8_t>{}});
1038#if qStroika_Foundation_Common_Platform_Windows
1039void ProcessRunner::Process_Runner_Windows_ (
const shared_ptr<DetailedRunnableRep_>& runneeDetails,
const optional<filesystem::path>& executable,
1045 "...,cmdLine='{}',currentDir={},..."_f, cmdLine,
1046 String{useCWD}.
LimitLength (50, StringShorteningPreference::ePreferKeepRight))};
1058 AutoHANDLE_ jStdin[2]{INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
1059 AutoHANDLE_ jStdout[2]{INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
1060 AutoHANDLE_ jStderr[2]{INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
1062 PROCESS_INFORMATION processInfo{};
1063 processInfo.hProcess = INVALID_HANDLE_VALUE;
1064 processInfo.hThread = INVALID_HANDLE_VALUE;
1068 SECURITY_DESCRIPTOR sd{};
1069 Verify (::InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION));
1070 Verify (::SetSecurityDescriptorDacl (&sd,
true, 0,
false));
1071 SECURITY_ATTRIBUTES sa = {
sizeof (SECURITY_ATTRIBUTES), &sd,
true};
1073 Verify (::CreatePipe (&jStdin[1], &jStdin[0], &sa, kPipeBufSize_));
1076 Verify (::CreatePipe (&jStdout[1], &jStdout[0], &sa, kPipeBufSize_));
1079 Verify (::CreatePipe (&jStderr[1], &jStderr[0], &sa, kPipeBufSize_));
1086 jStdin[0].ReplaceHandleAsNonInheritable ();
1089 jStdout[1].ReplaceHandleAsNonInheritable ();
1092 jStderr[1].ReplaceHandleAsNonInheritable ();
1096 STARTUPINFO startInfo{
1097 .cb =
sizeof (startInfo), .dwFlags = STARTF_USESTDHANDLES, .hStdInput = jStdin[1], .hStdOutput = jStdout[0], .hStdError = jStderr[0]};
1099 DWORD createProcFlags{NORMAL_PRIORITY_CLASS};
1100 if (options.fCreateNoWindow) {
1101 createProcFlags |= CREATE_NO_WINDOW;
1103 else if (options.fDetached) {
1105 createProcFlags |= DETACHED_PROCESS;
1111 bool bInheritHandles =
true;
1113 TCHAR cmdLineBuf[32768];
1114 Characters::CString::Copy (cmdLineBuf, std::size (cmdLineBuf), cmdLine.
As<
String> ().
AsSDKString ().c_str ());
1116 optional<filesystem::path> useEXEPath = executable;
1119#if qStroika_Foundation_Debug_AssertionsChecked
1122 DbgTrace (
"Warning: Cannot find exe '{}' in PATH ({})"_f, useEXEPath,
kPath ());
1128 if (cmdArgs.size () >= 1) {
1129 filesystem::path exe2Find = cmdArgs[0].As<filesystem::path> ();
1131 DbgTrace (
"Warning: Cannot find exe '{}' in PATH ({})"_f, exe2Find,
kPath ());
1137 unique_ptr<String2ContigArrayCStrs_<SDKChar>> envBuffer;
1138 LPVOID lpEnvironment =
nullptr;
1139 if (options.fEnvironment) {
1141 envBuffer = make_unique<String2ContigArrayCStrs_<SDKChar>> (getEnv_ (*oep));
1144 envBuffer = make_unique<String2ContigArrayCStrs_<SDKChar>> (getEnv_ (*om));
1147 envBuffer = make_unique<String2ContigArrayCStrs_<SDKChar>> (getEnv_ (*oms));
1150 lpEnvironment = envBuffer->fBytesBuffer.data ();
1151 if constexpr (same_as<SDKChar, wchar_t>) {
1152 createProcFlags |= CREATE_UNICODE_ENVIRONMENT;
1160 ::CreateProcess (useEXEPath == nullopt ?
nullptr : useEXEPath->c_str (), cmdLineBuf, nullptr, nullptr, bInheritHandles,
1161 createProcFlags, lpEnvironment, useCWD.c_str (), &startInfo, &processInfo));
1164 if (runneeDetails !=
nullptr) {
1165 runneeDetails->fRunningPID.store (processInfo.dwProcessId);
1173 jStdout[0].Close ();
1174 jStderr[0].Close ();
1177 AutoHANDLE_& useSTDIN = jStdin[0];
1178 Assert (jStdin[1] == INVALID_HANDLE_VALUE);
1179 AutoHANDLE_& useSTDOUT = jStdout[1];
1180 Assert (jStdout[0] == INVALID_HANDLE_VALUE);
1181 AutoHANDLE_& useSTDERR = jStderr[1];
1182 Assert (jStderr[0] == INVALID_HANDLE_VALUE);
1184 constexpr size_t kStackBufReadAtATimeSize_ = 10 * 1024;
1187 if (p == INVALID_HANDLE_VALUE) {
1190 byte buf[kReadBufSize_];
1191#if qUsePeekNamedPipe_
1192 DWORD nBytesAvail{};
1198#
if qUsePeekNamedPipe_
1199 ::PeekNamedPipe (p,
nullptr,
nullptr,
nullptr, &nBytesAvail,
nullptr) and nBytesAvail != 0 and
1201 ::ReadFile (p, buf,
sizeof (buf), &nBytesRead,
nullptr) and nBytesRead > 0) {
1203 o.Write (span{buf, nBytesRead});
1205#if USE_NOISY_TRACE_IN_THIS_MODULE_
1206 buf[(nBytesRead == std::size (buf)) ? (std::size (buf) - 1) : nBytesRead] = byte{
'\0'};
1207 DbgTrace (
"read from process (fd={}) nBytesRead = {}: {}"_f, p, nBytesRead, buf);
1212 if (options.fDetached) {
1213 Assert (useSTDIN == INVALID_HANDLE_VALUE and useSTDOUT == INVALID_HANDLE_VALUE and useSTDERR == INVALID_HANDLE_VALUE);
1214 SAFE_HANDLE_CLOSER_ (&processInfo.hProcess);
1215 SAFE_HANDLE_CLOSER_ (&processInfo.hThread);
1219 Assert (processInfo.hProcess != INVALID_HANDLE_VALUE);
1220 if (processInfo.hProcess != INVALID_HANDLE_VALUE) {
1226 auto mkPipeNoWait_ = [] (HANDLE ioHandle) ->
void {
1227 if (ioHandle != INVALID_HANDLE_VALUE) {
1229 Verify (::GetNamedPipeHandleState (ioHandle, &mode,
nullptr,
nullptr,
nullptr,
nullptr, 0));
1230 mode |= PIPE_NOWAIT;
1231 Verify (::SetNamedPipeHandleState (ioHandle, &mode,
nullptr,
nullptr));
1234 mkPipeNoWait_ (useSTDIN);
1235 mkPipeNoWait_ (useSTDOUT);
1236 mkPipeNoWait_ (useSTDERR);
1242 if (in !=
nullptr) {
1243 byte stdinBuf[kStackBufReadAtATimeSize_];
1245 while (
size_t nbytes = in.
ReadBlocking (span{stdinBuf}).size ()) {
1246 Assert (nbytes <= std::size (stdinBuf));
1247 const byte* p = begin (stdinBuf);
1248 const byte* e = p + nbytes;
1251 if (::WriteFile (useSTDIN, p, Math::PinToMaxForType<DWORD> (e - p), &written,
nullptr) == 0) {
1252 DWORD lastErr = ::GetLastError ();
1258 if (lastErr != ERROR_SUCCESS and lastErr != ERROR_NO_MORE_FILES and lastErr != ERROR_PIPE_BUSY and lastErr != ERROR_NO_DATA) {
1259 DbgTrace (
"in RunExternalProcess_ - throwing {} while fill in stdin"_f, lastErr);
1260 ThrowSystemErrNo (lastErr);
1263 Assert (written <=
static_cast<size_t> (e - p));
1267 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDOUT, out);
1268 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDERR, err);
1270 if (p < e and written == 0) {
1277 if (Time::GetTickCount () > timeoutAt) {
1278 DbgTrace (_T (
"process timed out (writing initial data) - so throwing up!"));
1280 (void)::TerminateProcess (processInfo.hProcess, -1);
1281 Throw (Execution::Platform::Windows::Exception (ERROR_TIMEOUT));
1296 int timesWaited = 0;
1305 HANDLE events[1] = {processInfo.hProcess};
1312 double remainingTimeout = (timesWaited <= 5) ? 0.1 : 0.5;
1314 ::WaitForMultipleObjects (
static_cast<DWORD
> (std::size (events)), events,
false,
static_cast<int> (remainingTimeout * 1000));
1317 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDOUT, out);
1318 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDERR, err);
1319 switch (waitResult) {
1320 case WAIT_OBJECT_0: {
1321#if USE_NOISY_TRACE_IN_THIS_MODULE_
1322 DbgTrace (
"external process finished (DONE)"_f);
1325 goto DoneWithProcess;
1327 case WAIT_TIMEOUT: {
1328 DbgTrace (
"still waiting for external process output (WAIT_TIMEOUT)"_f);
1334 DWORD processExitCode{};
1335 Verify (::GetExitCodeProcess (processInfo.hProcess, &processExitCode));
1337 SAFE_HANDLE_CLOSER_ (&processInfo.hProcess);
1338 SAFE_HANDLE_CLOSER_ (&processInfo.hThread);
1340 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDOUT, out);
1341 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDERR, err);
1343 if (runneeDetails ==
nullptr) {
1344 if (processExitCode != 0) {
1349#if USE_NOISY_TRACE_IN_THIS_MODULE_
1350 DbgTrace (
"storing process status (ExitCode): {}"_f,
static_cast<int> (processExitCode));
1352 if (processExitCode != 0) {
1353 DbgTrace (
"storing process status (ExitCode): {}"_f,
static_cast<int> (processExitCode));
1362 if (processInfo.hProcess != INVALID_HANDLE_VALUE and processInfo.hProcess !=
nullptr) {
1363 (void)::TerminateProcess (processInfo.hProcess,
static_cast<UINT
> (-1));
1364 SAFE_HANDLE_CLOSER_ (&processInfo.hProcess);
1365 SAFE_HANDLE_CLOSER_ (&processInfo.hThread);
1372tuple<function<void ()>, shared_ptr<ProcessRunner::DetailedRunnableRep_>> ProcessRunner::CreateDetailedRunnable_ ()
1374#if USE_NOISY_TRACE_IN_THIS_MODULE_
1378 auto resultDetails = MakeSharedPtr<DetailedRunnableRep_> ();
1380 [resultDetails, exe = this->fExecutable_, cmdLine = this->fArgs_, options = fOptions_, in = fStdIn_, out = fStdOut_, err = fStdErr_] () {
1381#if USE_NOISY_TRACE_IN_THIS_MODULE_
1384 auto activity =
LazyEvalActivity{[&] () {
return "executing '{}'"_f(cmdLine); }};
1386#if qStroika_Foundation_Common_Platform_POSIX
1387 Process_Runner_POSIX_ (resultDetails, exe, cmdLine, options, in, out, err);
1388#elif qStroika_Foundation_Common_Platform_Windows
1389 Process_Runner_Windows_ (resultDetails, exe, cmdLine, options, in, out, err);
1395function<void ()> ProcessRunner::CreateSimpleRunnable_ ()
1398 Assert (not fOptions_.fDetached);
1400 return [exe = this->fExecutable_, cmdLine = this->fArgs_, options = fOptions_, in = fStdIn_, out = fStdOut_, err = fStdErr_] () {
1401#if USE_NOISY_TRACE_IN_THIS_MODULE_
1404 auto activity =
LazyEvalActivity{[&] () {
return "executing '{}'"_f(cmdLine); }};
1406#if qStroika_Foundation_Common_Platform_POSIX
1407 Process_Runner_POSIX_ (
nullptr, exe, cmdLine, options, in, out, err);
1408#elif qStroika_Foundation_Common_Platform_Windows
1409 Process_Runner_Windows_ (
nullptr, exe, cmdLine, options, in, out, err);
#define AssertNotImplemented()
#define RequireNotNull(p)
#define AssertNotReached()
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
InlineBuffer< T, BUF_SIZE > StackBuffer
Store variable sized (BUF_SIZE elements) array on the stack (.
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual bool empty() const noexcept
nonvirtual String str() const
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual String LimitLength(size_t maxLen, StringShorteningPreference keepPref=StringShorteningPreference::ePreferKeepLeft) const
return the first maxLen (or fewer if string shorter) characters of this string (adding ellipsis if tr...
static String FromNarrowSDKString(const char *from)
nonvirtual SDKString AsSDKString() const
static String FromLatin1(const CHAR_T *cString)
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
A generalization of a vector: a container whose elements are keyed by the natural numbers.
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
nonvirtual Sequence< String > GetArguments() const
nonvirtual T As(ARGS... args) const
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
NestedException contains a new higher level error message (typically based on argument basedOnExcepti...
nonvirtual void WaitForStarted(Time::DurationSeconds timeout=Time::kInfinity) const
wait until GetChildProcessID () returns a valid answer, or until the process failed to start (in whic...
nonvirtual String ToString() const
nonvirtual void PropagateIfException() const
nonvirtual void WaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
nonvirtual void Join(Time::DurationSeconds timeout=Time::kInfinity) const
Join () does WaitForDone () and throw exception if there was any error (see PropagateIfException).
nonvirtual void JoinUntil(Time::TimePointSeconds timeoutAt) const
WaitForDoneUntil () and throw exception if there was any error (see PropagateIfException).
nonvirtual void Terminate()
Run the given command, and optionally support stdin/stdout/stderr as streams (either sync with Run,...
nonvirtual void Run(const Streams::InputStream::Ptr< byte > &in, const Streams::OutputStream::Ptr< byte > &out=nullptr, const Streams::OutputStream::Ptr< byte > &error=nullptr, Time::DurationSeconds timeout=Time::kInfinity)
Run the given external command/process (set by constructor) - with the given arguments,...
ProcessRunner()=delete
Construct ProcessRunner with a CommandLine to run (doesn't actually RUN til you call Run or RunInBack...
nonvirtual BackgroundProcess RunInBackground(const Streams::InputStream::Ptr< byte > &in=nullptr, const Streams::OutputStream::Ptr< byte > &out=nullptr, const Streams::OutputStream::Ptr< byte > &error=nullptr)
Run the given external command/process (set by constructor) - with the given arguments in the backgro...
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
nonvirtual void Join(Time::DurationSeconds timeout=Time::kInfinity) const
Wait for the pointed-to thread to be done. If the thread completed with an exception (other than thre...
nonvirtual void WaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
nonvirtual void JoinUntil(Time::TimePointSeconds timeoutAt) const
Wait for the pointed-to thread to be done. If the thread completed with an exception (other than thre...
nonvirtual void ThrowIfDoneWithException() const
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
OutputStream<>::Ptr is Smart pointer to a stream-based sink of data.
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
nonvirtual RESULT_T Join(const CONVERT_TO_RESULT &convertToResult=kDefaultToStringConverter<>, const COMBINER &combiner=Characters::kDefaultStringCombiner) const
ape the JavaScript/python 'join' function - take the parts of 'this' iterable and combine them into a...
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
basic_string< SDKChar > SDKString
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
void Sleep(Time::Duration seconds2Wait)
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
const LazyInitialized< Containers::Sequence< filesystem::path > > kPath
EXPECTED::value_type ThrowIfFailed(const EXPECTED &e)
void ThrowPOSIXErrNo(errno_t errNo=errno)
treats errNo as a POSIX errno value, and throws a SystemError (subclass of @std::system_error) except...
auto Handle_ErrNoResultInterruption(CALL call) -> decltype(call())
Handle UNIX EINTR system call behavior - fairly transparently - just effectively removes them from th...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
const LazyInitialized< Containers::Mapping< Characters::SDKString, Characters::SDKString > > kRawEnvironment
convert getenv() to a Mapping of SDKString (in case some issue with charactor set conversion)
optional< filesystem::path > FindExecutableInPath(const filesystem::path &fn)
If fn refers to an executable - return it (using kPATH, and kPathEXT as appropriate)
int pid_t
TODO - maybe move this to configuraiotn module???
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)
filesystem::path GetTemporary()
Ptr New(const InputStream::Ptr< byte > &src, optional< AutomaticCodeCvtFlags > codeCvtFlags={}, optional< SeekableFlag > seekable={}, ReadAhead readAhead=eReadAheadAllowed)
Create an InputStream::Ptr<Character> from the arguments (usually binary source) - which can be used ...
Ptr New(const Streams::OutputStream::Ptr< byte > &src, const Characters::CodeCvt<> &char2OutputConverter)
nonvirtual String ToString() const