Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ProcessRunner.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <sstream>
7
8#if qStroika_Foundation_Common_Platform_POSIX
9#include <fcntl.h>
10#include <signal.h>
11#include <sys/resource.h>
12#include <sys/stat.h>
13#include <sys/types.h>
14#include <sys/wait.h>
15#include <unistd.h>
16#endif
17
23#include "Stroika/Foundation/Containers/Sequence.h"
25#if qStroika_Foundation_Common_Platform_Windows
26#include "Platform/Windows/Exception.h"
27#endif
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"
35#include "Stroika/Foundation/IO/FileSystem/FileUtils.h"
37#include "Stroika/Foundation/Memory/Common.h"
40#include "Stroika/Foundation/Streams/MemoryStream.h"
41#include "Stroika/Foundation/Streams/TextToBinary.h"
42
43#include "Sleep.h"
44#include "Thread.h"
45
46#include "ProcessRunner.h"
47
48using std::byte;
49
50using namespace Stroika::Foundation;
53using namespace Stroika::Foundation::Debug;
54using namespace Stroika::Foundation::Execution;
55using namespace Stroika::Foundation::Streams;
56using namespace Stroika::Foundation::Traversal;
57
60
61// Comment this in to turn on aggressive noisy DbgTrace in this module
62// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
63
64#if USE_NOISY_TRACE_IN_THIS_MODULE_
65#include <fstream>
66#endif
67
68#if qStroika_Foundation_Common_Platform_POSIX
69namespace {
70 // no-except cuz the exception will show up in tracelog, and nothing useful to do, and could be quite bad to except cuz mostly used
71 // in cleanup, and could cause leaks
72 inline void CLOSE_ (int& fd) noexcept
73 {
74 if (fd >= 0) [[likely]] {
75 IgnoreExceptionsForCall (Handle_ErrNoResultInterruption ([fd] () -> int { return ::close (fd); }));
76 fd = -1;
77 }
78 }
79}
80#endif
81
82#if qStroika_Foundation_Common_Platform_POSIX
83namespace {
84 static const int kMaxFD_ = [] () -> int {
85 int result{};
86 constexpr bool kUseSysConf_ = true;
87#if _BSD_SOURCE || _XOPEN_SOURCE >= 500
88 [[maybe_unused]] constexpr bool kUseGetDTableSize_ = true;
89#else
90 [[maybe_unused]] constexpr bool kUseGetDTableSize_ = false;
91#endif
92 constexpr bool kUseGetRLimit_ = true;
93 if constexpr (kUseSysConf_) {
94 result = ::sysconf (_SC_OPEN_MAX);
95 Assert (result > 20); // from http://man7.org/linux/man-pages/man3/sysconf.3.html - Must not be less than _POSIX_OPEN_MAX (20).
96 }
97 else if constexpr (kUseSysConf_) {
98 result = getdtablesize ();
99 }
100 else if constexpr (kUseGetRLimit_) {
101 struct rlimit fds{};
102 if (::getrlimit (RLIMIT_NOFILE, &fds) == 0) {
103 return fds.rlim_cur;
104 }
105 else {
106 return 1024; // wag
107 }
108 }
109 /*
110 * A little crazy, but in docker containers, this max# of files can get quite large (I've seen it over 1024*1024).
111 * Probably at that point its smart to use some other technique to close all the extra file descriptors (like look at
112 * lsof() or read /proc/sys/fs/file-nr? Something like that
113 *
114 * -- LGP 2018-10-08
115 */
116 Assert (result > 5); // sanity check - no real requirement
117 Assert (result < 4 * 1024 * 1024); // "" (if too big, looping to close all costly)
118 DbgTrace ("::sysconf (_SC_OPEN_MAX) = {}"_f, result);
119 return result;
120 }();
121}
122#endif
123
124#if qStroika_Foundation_Common_Platform_POSIX
125namespace {
126 pid_t DoFork_ ()
127 {
128 // we may want to use vfork or some such. But for AIX, it appears best to use f_fork
129 // https://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.basetrf1/fork.htm
130 // -- LGP 2016-03-31
131 return ::fork ();
132 }
133}
134#endif
135
136#if qStroika_Foundation_Common_Platform_POSIX
137#include <spawn.h>
138namespace {
139 // https://www.ibm.com/support/knowledgecenter/ssw_aix_53/com.ibm.aix.basetechref/doc/basetrf1/posix_spawn.htm%23posix_spawn
140 // http://www.systutorials.com/37124/a-posix_spawn-example-in-c-to-create-child-process-on-linux/
141
142 constexpr bool kUseSpawn_ = false; // 1/2 implemented
143}
144extern char** environ;
145#endif
146
147#if qStroika_Foundation_Common_Platform_Windows
148namespace {
149 class AutoHANDLE_ {
150 public:
151 AutoHANDLE_ (HANDLE h = INVALID_HANDLE_VALUE)
152 : fHandle{h}
153 {
154 }
155 AutoHANDLE_ (const AutoHANDLE_&) = delete;
156 ~AutoHANDLE_ ()
157 {
158 Close ();
159 }
160 AutoHANDLE_& operator= (const AutoHANDLE_& rhs)
161 {
162 if (this != &rhs) {
163 Close ();
164 fHandle = rhs.fHandle;
165 }
166 return *this;
167 }
168 operator HANDLE () const
169 {
170 return fHandle;
171 }
172 HANDLE* operator& ()
173 {
174 return &fHandle;
175 }
176 void Close ()
177 {
178 if (fHandle != INVALID_HANDLE_VALUE) {
179 Verify (::CloseHandle (fHandle));
180 fHandle = INVALID_HANDLE_VALUE;
181 }
182 }
183 void ReplaceHandleAsNonInheritable ()
184 {
185 HANDLE result = INVALID_HANDLE_VALUE;
186 Verify (::DuplicateHandle (::GetCurrentProcess (), fHandle, ::GetCurrentProcess (), &result, 0, FALSE, DUPLICATE_SAME_ACCESS));
187 Verify (::CloseHandle (fHandle));
188 fHandle = result;
189 }
190
191 public:
192 HANDLE fHandle;
193 };
194 inline void SAFE_HANDLE_CLOSER_ (HANDLE* h)
195 {
196 RequireNotNull (h);
197 if (*h != INVALID_HANDLE_VALUE) {
198 Verify (::CloseHandle (*h));
199 *h = INVALID_HANDLE_VALUE;
200 }
201 }
202}
203#endif
204
205namespace {
206 template <Common::IAnyOf<char, wchar_t> CHAR_T>
207 struct String2ContigArrayCStrs_ {
208 StackBuffer<CHAR_T> fBytesBuffer;
209 StackBuffer<CHAR_T*, 10 * sizeof (void*)> fPtrsBuffer;
210 String2ContigArrayCStrs_ (const Mapping<basic_string<CHAR_T>, basic_string<CHAR_T>>& data)
211 : String2ContigArrayCStrs_{data.template Map<Iterable<basic_string<CHAR_T>>> (
212 [] (auto kvp) -> basic_string<CHAR_T> { return kvp.fKey + SDKSTR ("=") + kvp.fValue; })}
213 {
214 }
215 String2ContigArrayCStrs_ (const Iterable<basic_string<CHAR_T>>& data)
216 {
217 StackBuffer<size_t> argsIdx;
218 size_t bufferIndex = 0;
219 for (const basic_string<CHAR_T>& i : data) {
220 fBytesBuffer.push_back (span{i});
221 fBytesBuffer.push_back ('\0');
222 argsIdx.push_back (bufferIndex);
223 bufferIndex = fBytesBuffer.GetSize ();
224 }
225 fBytesBuffer.push_back ('\0'); // not sure - maybe not needed for UNIX, but needed on windows (cuz not using double fPtrsBuffer)
226 auto freeze = fBytesBuffer.begin ();
227 for (size_t i : argsIdx) {
228 fPtrsBuffer.push_back (freeze + i);
229 }
230 fPtrsBuffer.push_back (nullptr);
231 }
232 String2ContigArrayCStrs_ () = delete;
233 String2ContigArrayCStrs_ (const String2ContigArrayCStrs_&) = delete;
234 String2ContigArrayCStrs_ (String2ContigArrayCStrs_&&) = delete;
235 };
236}
237
238#if qStroika_Foundation_Common_Platform_Windows
239namespace {
240// still unsure if needed/useful - I now think the PeekNamedPipe stuff is NOT needed, but
241// I can turn it on if needed -- LGP 2009-05-07
242//#define qUsePeekNamedPipe_ 1
243#ifndef qUsePeekNamedPipe_
244#define qUsePeekNamedPipe_ 0
245#endif
246 /*
247 * This code should all work with the smaller buffer sizes, but is more efficient with larger buffers.
248 * Just set to use the smaller buffers to stress test and debug.
249 *
250 * There is some subtle but serious bug with my pipe code - and that APPEARS to just be that
251 * WaitForMultipleObjects doesn't work with PIPEs.
252 *
253 * I COULD just rewrite a lot of this code to NOT use PIPES - but actual files. That might solve the problem
254 * because they never 'fill up'.
255 *
256 * Alternatively - it might be that my switch to ASYNC mode (PIPE_NOWAIT) was a bad idea. Maybe if I got
257 * rid of that - the WAIT code could be made to work? Not sure.
258 *
259 * Anyhow - this appears to be adequate for now...
260 *
261 * -- LGP 2006-10-17
262 */
263 constexpr size_t kPipeBufSize_ = 256 * 1024;
264 constexpr size_t kReadBufSize_ = 32 * 1024;
265}
266#endif
267
268/*
269 ********************************************************************************
270 ***************** Execution::ProcessRunner::Exception **************************
271 ********************************************************************************
272 */
273String ProcessRunner::Exception::mkMsg_ (const String& errorMessage, const optional<String>& stderrSubset,
274 const optional<ExitStatusType>& wExitStatus, const optional<SignalID>& wTermSig)
275{
276 StringBuilder sb;
277 sb << errorMessage;
278 {
279 StringBuilder extraMsg;
280 if (wExitStatus) {
281 extraMsg << "exit status {}"_f(int (*wExitStatus));
282 }
283 if (wTermSig) {
284 if (not extraMsg.empty ()) {
285 extraMsg << ", "sv;
286 }
287 extraMsg << "terminated by signal {}"_f(int (*wTermSig));
288 }
289 if (not extraMsg.empty ()) {
290 sb << ": "sv << extraMsg;
291 }
292 }
293 if (stderrSubset) {
294 sb << " (captured stderr: "sv
295 << stderrSubset->ReplaceAll ("\\s+"_RegEx, " "sv).LimitLength (100, StringShorteningPreference::ePreferKeepRight) << ")"sv;
296 }
297 return sb;
298}
299
300/*
301 ********************************************************************************
302 **************** Execution::ProcessRunner::ProcessResultType *******************
303 ********************************************************************************
304 */
305void ProcessRunner::ProcessResultType::ThrowIfFailed ()
306{
307 if (fExitStatus and *fExitStatus != 0) {
308 Throw (Exception{"Child process failed", nullopt, *fExitStatus});
309 }
310 if (fTerminatedByUncaughtSignalNumber and *fTerminatedByUncaughtSignalNumber != 0) {
311 Throw (Exception{"Child process failed", nullopt, nullopt, *fTerminatedByUncaughtSignalNumber});
312 }
313}
314
315/*
316 ********************************************************************************
317 **************** Execution::ProcessRunner::BackgroundProcess *******************
318 ********************************************************************************
319 */
320ProcessRunner::BackgroundProcess::BackgroundProcess ()
321 : fRep_{make_shared<Rep_> ()}
322{
323}
324
326{
327 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
328 Thread::Ptr t{fRep_->fProcessRunner};
329 t.ThrowIfDoneWithException ();
330 if (auto o = GetProcessResult ()) {
331 if (o->fExitStatus and o->fExitStatus != ExitStatusType{}) {
332 AssertNotReached (); // I don't think this can happen since it should have resulted in a propagated exception
333 }
334 if (o->fTerminatedByUncaughtSignalNumber) {
335 AssertNotReached (); // I don't think this can happen since it should have resulted in a propagated exception
336 }
337 }
338}
339
341{
342 // tmphack impl
344 DurationSeconds remaining = timeout;
345 while (remaining > DurationSeconds{0}) {
346 if (auto pr = GetChildProcessID ()) {
347 return;
348 }
349 Sleep (1);
350 remaining -= DurationSeconds{1};
351 }
352}
354{
355 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
356 Thread::Ptr t{fRep_->fProcessRunner};
357 t.WaitForDone (timeout);
358}
359
361{
362 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
363 Thread::Ptr t{fRep_->fProcessRunner};
364 t.Join (timeout);
365 // if he asserts in PropagateIfException () are wrong, I may need to call that here!
366}
367
369{
370 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
371 Thread::Ptr t{fRep_->fProcessRunner};
372 t.JoinUntil (timeoutAt);
373 // if he asserts in PropagateIfException () are wrong, I may need to call that here!
374}
375
377{
378 TraceContextBumper ctx{"ProcessRunner::BackgroundProcess::Terminate"};
379 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
380 // @todo? set thread to null when done -
381 //
382 // @todo - Note - UNTESTED, and probably not 100% right (esp error checking!!!
383 //
384 if (optional<pid_t> o = fRep_->fPID) {
385#if qStroika_Foundation_Common_Platform_POSIX
386 ::kill (SIGTERM, *o);
387#elif qStroika_Foundation_Common_Platform_Windows
388 // @todo - if this OpenProcess gives us any trouble, we can return the handle directory from the 'CreateRunnable' where we invoke the process
389 HANDLE processHandle = ::OpenProcess (PROCESS_TERMINATE, false, *o);
390 if (processHandle != nullptr) {
391 ::TerminateProcess (processHandle, 1);
392 ::CloseHandle (processHandle);
393 }
394 else {
395 DbgTrace ("::OpenProcess returned null: GetLastError () = {}"_f, GetLastError ());
396 }
397#else
399#endif
400 }
401}
402
403/*
404 ********************************************************************************
405 ************************** Execution::ProcessRunner ****************************
406 ********************************************************************************
407 */
408
409namespace {
411 {
412 return kRawEnvironment ();
413 }
415 {
416 return r;
417 }
419 {
421 for (auto i : env) {
422 r.Add (i.fKey.AsSDKString (), i.fValue.AsSDKString ());
423 }
424 return r;
425 }
427 {
428 Mapping<SDKString, SDKString> r = getEnv_ ();
429#if qStroika_Foundation_Common_Platform_POSIX
430 SDKString path = replacePath.Join<SDKString> ([] (const filesystem::path& p) -> SDKString { return p; }, SDKString{":"sv});
431#elif qStroika_Foundation_Common_Platform_Windows
432 SDKString path = replacePath.Join<SDKString> ([] (const filesystem::path& p) -> SDKString { return p; }, SDKString{L";"sv});
433#endif
434 r.Add (SDKSTR ("PATH"), path);
435 return r;
436 }
437}
438
439ProcessRunner::ProcessRunner (const String& commandLine, const Options& o)
440 : ProcessRunner{commandLine.ContainsAny ({'\'', '\"', '<', '>', '|', '$', '{', '}'}) ? CommandLine{kDefaultShell, commandLine} : CommandLine{commandLine}, o}
441{
442}
443
446{
447 TraceContextBumper ctx{"ProcessRunner::Run"};
448 auto activity = LazyEvalActivity ([this] () -> String { return "running '{}'"_f(this->GetCommandLine ()); });
449 DeclareActivity currentActivity{&activity};
450 if (timeout == Time::kInfinity) {
451 fStdIn_ = in;
452 fStdOut_ = out;
453 fStdErr_ = error;
455 CreateRunnable_ (&pr, nullptr, progress) ();
456 pr->value_or (ProcessResultType{}).ThrowIfFailed ();
457 }
458 else {
459 // Use 'BackgroundProcess' to get a thread we can interrupt when time is up, for timeout
460 BackgroundProcess bp = RunInBackground (in, out, error, progress);
461 [[maybe_unused]] auto&& cleanup = Finally ([&] () noexcept { bp.Terminate (); });
462 bp.Join (timeout);
463 bp.PropagateIfException ();
464 // If we didn't timeout, then the process must have completed, so we must have a process result
465 bp.GetProcessResult ().value_or (ProcessResultType{}).ThrowIfFailed ();
466 }
467}
468void ProcessRunner::Run (optional<ProcessResultType>* processResult, ProgressMonitor::Updater progress, Time::DurationSeconds timeout)
469{
470 TraceContextBumper ctx{"ProcessRunner::Run"}; //DEPREACTED API.... LOSE
471 if (timeout == Time::kInfinity) {
472 if (processResult == nullptr) {
473 CreateRunnable_ (nullptr, nullptr, progress) ();
474 }
475 else {
477 [[maybe_unused]] auto&& cleanup = Finally ([&] () noexcept { *processResult = pr.load (); });
478 CreateRunnable_ (&pr, nullptr, progress) ();
479 }
480 }
481 else {
482 if (processResult == nullptr) {
483 Thread::Ptr t = Thread::New (CreateRunnable_ (nullptr, nullptr, progress), Thread::eAutoStart, "ProcessRunner thread"_k);
484 t.Join (timeout);
485 }
486 else {
488 [[maybe_unused]] auto&& cleanup = Finally ([&] () noexcept { *processResult = pr.load (); });
489 Thread::Ptr t = Thread::New (CreateRunnable_ (&pr, nullptr, progress), Thread::eAutoStart, "ProcessRunner thread"_k);
490 t.Join (timeout);
491 }
492 }
493}
494
495auto ProcessRunner::Run (const String& cmdStdInValue, const StringOptions& stringOpts, ProgressMonitor::Updater progress,
496 Time::DurationSeconds timeout) -> tuple<String, String>
497{
498 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
499 MemoryStream::Ptr<byte> useStdIn = MemoryStream::New<byte> ();
500 MemoryStream::Ptr<byte> useStdOut = MemoryStream::New<byte> ();
501 MemoryStream::Ptr<byte> useStdErr = MemoryStream::New<byte> ();
502
503 auto mkReadStream = [&] (const InputStream::Ptr<byte>& readFromBinStrm) {
504 return stringOpts.fInputCodeCvt ? BinaryToText::Reader::New (readFromBinStrm, *stringOpts.fInputCodeCvt)
505 : BinaryToText::Reader::New (readFromBinStrm);
506 };
507 try {
508 // Prefill stream
509 if (not cmdStdInValue.empty ()) {
510 auto outStream = stringOpts.fOutputCodeCvt ? TextToBinary::Writer::New (useStdIn, *stringOpts.fOutputCodeCvt)
511 : TextToBinary::Writer::New (useStdIn);
512 outStream.Write (cmdStdInValue);
513 }
514 Assert (useStdIn.GetReadOffset () == 0);
515
516 Run (useStdIn, useStdOut, useStdErr, progress, timeout);
517
518 // get and return results from 'useStdOut' etc
519 Assert (useStdOut.GetReadOffset () == 0);
520 Assert (useStdErr.GetReadOffset () == 0);
521 return make_tuple (mkReadStream (useStdOut).ReadAll (), mkReadStream (useStdErr).ReadAll ());
522 }
523 catch (const Exception& e) {
524 String out = mkReadStream (useStdOut).ReadAll ();
525 String err = mkReadStream (useStdErr).ReadAll ();
526#if qStroika_Foundation_Debug_DefaultTracingOn
527 DbgTrace ("Captured stdout: {}"_f, out);
528 DbgTrace ("Captured stderr: {}"_f, err);
529#endif
530 Throw (Exception{e.fFailureMessage, err, e.fExitStatus, e.fTermSignal});
531 Throw (Exception{this->fArgs_.As<String> (), "{}: output: {}, stderr: {}"_f(e.As<String> (), out, err)});
532 }
533 catch (...) {
534 String out = mkReadStream (useStdOut).ReadAll ();
535 String err = mkReadStream (useStdErr).ReadAll ();
536#if qStroika_Foundation_Debug_DefaultTracingOn
537 DbgTrace ("Captured stdout: {}"_f, out);
538 DbgTrace ("Captured stderr: {}"_f, err);
539#endif
540 exception_ptr e = current_exception ();
541 Throw (NestedException{"{} (stderr: {})"_f(e, err), e});
542 }
543}
544
547{
548 TraceContextBumper ctx{"ProcessRunner::RunInBackground"};
549 this->fStdIn_ = in;
550 this->fStdOut_ = out;
551 this->fStdErr_ = error;
552 BackgroundProcess result;
553 result.fRep_->fProcessRunner =
554 Thread::New (CreateRunnable_ (&result.fRep_->fResult, nullptr, progress), Thread::eAutoStart, "ProcessRunner background thread"sv);
555 return result;
556}
557
559{
560 TraceContextBumper ctx{"ProcessRunner::RunInBackground"}; // DEPRECATED OVERLOAD
561 BackgroundProcess result;
562 result.fRep_->fProcessRunner =
563 Thread::New (CreateRunnable_ (&result.fRep_->fResult, nullptr, progress), Thread::eAutoStart, "ProcessRunner background thread"sv);
564 return result;
565}
566
567#if qStroika_Foundation_Common_Platform_POSIX
568namespace {
569 // @todo Good Candidate for REWRITE - this is a MESS!
570 void Process_Runner_POSIX_ (Synchronized<optional<ProcessRunner::ProcessResultType>>* processResult, Synchronized<optional<pid_t>>* runningPID,
571 ProgressMonitor::Updater progress, [[maybe_unused]] const optional<filesystem::path>& executable,
572 const CommandLine& cmdLine, const ProcessRunner::Options& options, const InputStream::Ptr<byte>& in,
574 {
575 optional<mode_t> umask = options.fChildUMask;
576 filesystem::path useCWD = options.fWorkingDirectory.value_or (IO::FileSystem::WellKnownLocations::GetTemporary ());
578 "...,cmdLine='{}',currentDir='{}',..."_f, cmdLine,
579 String{useCWD}.LimitLength (50, StringShorteningPreference::ePreferKeepRight))};
580
581 // track the last few bytes of stderr to include in possible exception messages
582 char trailingStderrBuf[256];
583 char* trailingStderrBufNextByte2WriteAt = begin (trailingStderrBuf);
584 size_t trailingStderrBufNWritten{};
585
586 /*
587 * NOTE:
588 * From http://linux.die.net/man/2/pipe
589 * "The array pipefd is used to return two file descriptors referring to the ends
590 * of the pipe. pipefd[0] refers to the read end of the pipe. pipefd[1] refers to
591 * the write end of the pipe"
592 */
593 int jStdin[2]{-1, -1};
594 int jStdout[2]{-1, -1};
595 int jStderr[2]{-1, -1};
596 [[maybe_unused]] auto&& cleanup = Finally ([&] () noexcept {
597 ::CLOSE_ (jStdin[0]);
598 ::CLOSE_ (jStdin[1]);
599 ::CLOSE_ (jStdout[0]);
600 ::CLOSE_ (jStdout[1]);
601 ::CLOSE_ (jStderr[0]);
602 ::CLOSE_ (jStderr[1]);
603 });
604 if (in) {
605 Handle_ErrNoResultInterruption ([&jStdin] () -> int { return ::pipe (jStdin); });
606 }
607 else {
608 jStdin[0] = ::open ("/dev/null", O_RDONLY);
609 }
610 if (out) {
611 Handle_ErrNoResultInterruption ([&jStdout] () -> int { return ::pipe (jStdout); });
612 }
613 else {
614 jStdout[1] = ::open ("/dev/null", O_WRONLY);
615 }
616 if (err) {
617 Handle_ErrNoResultInterruption ([&jStderr] () -> int { return ::pipe (jStderr); });
618 }
619 else {
620 jStderr[1] = ::open ("/dev/null", O_WRONLY);
621 }
622 // assert cuz code below needs to be more careful if these can overlap 0..2
623 Assert (in == nullptr or (jStdin[0] >= 3 and jStdin[1] >= 3));
624 Assert (out == nullptr or (jStdout[0] >= 3 and jStdout[1] >= 3));
625 Assert (err == nullptr or (jStderr[0] >= 3 and jStderr[1] >= 3));
626 DbgTrace ("jStdout[0-CHILD] = {} and jStdout[1-PARENT] = {}"_f, jStdout[0], jStdout[1]);
627
628 /*
629 * Note: Important to do all this code before the fork, because once we fork, we, lose other threads
630 * but share copy of RAM, so they COULD have mutexes locked! And we could deadlock waiting on them, so after
631 * fork, we are VERY limited as to what we can safely do.
632 */
633 const char* thisEXEPath_cstr = nullptr;
634 char** thisEXECArgv = nullptr;
635
636 String2ContigArrayCStrs_<char> execDataArgs{
637 cmdLine.GetArguments ().Map<Iterable<string>> ([] (auto si) { return si.AsNarrowSDKString (); })};
638 thisEXEPath_cstr = execDataArgs.fBytesBuffer.data ();
639 thisEXECArgv = execDataArgs.fPtrsBuffer.data ();
640
641 /*
642 * If the file is not accessible, and using fork/exec, we wont find that out til the execvp,
643 * and then there wont be a good way to propagate the error back to the caller.
644 *
645 * @todo for now - this code only checks access for absolute/full path, and we should also check using
646 * PATH and https://linux.die.net/man/3/execvp confstr(_CS_PATH)
647 */
648 if (not kUseSpawn_ and thisEXEPath_cstr[0] == '/' and ::access (thisEXEPath_cstr, R_OK | X_OK) < 0) {
649 errno_t e = errno; // save in case overwritten
650#if USE_NOISY_TRACE_IN_THIS_MODULE_
651 DbgTrace ("failed to access exe path so throwing: exe path='{}'"_f, String::FromNarrowSDKString (thisEXEPath_cstr));
652#endif
653 ThrowPOSIXErrNo (e);
654 }
655
656 pid_t childPID{};
657 if (kUseSpawn_) {
658 posix_spawn_file_actions_t file_actions{};
659 /// @see http://stackoverflow.com/questions/13893085/posix-spawnp-and-piping-child-output-to-a-string
660 // not quite right - maybe not that close
661 /*
662 * move arg stdin/out/err to 0/1/2 file-descriptors. Don't bother with variants that can handle errors/exceptions cuz we cannot really here...
663 */
664 {
665 posix_spawn_file_actions_init (&file_actions);
666 posix_spawn_file_actions_addclose (&file_actions, jStdin[0]);
667 posix_spawn_file_actions_addclose (&file_actions, jStdin[0]);
668 posix_spawn_file_actions_adddup2 (&file_actions, jStdout[1], 1);
669 posix_spawn_file_actions_addclose (&file_actions, jStdout[0]);
670 posix_spawn_file_actions_adddup2 (&file_actions, jStderr[1], 2);
671 posix_spawn_file_actions_addclose (&file_actions, jStderr[1]);
672 }
673 posix_spawnattr_t* attr = nullptr;
674 int status = ::posix_spawnp (&childPID, thisEXEPath_cstr, &file_actions, attr, thisEXECArgv, environ);
675 if (status != 0) {
676 ThrowPOSIXErrNo (status);
677 }
678 }
679 else {
680 childPID = DoFork_ ();
681 ThrowPOSIXErrNoIfNegative (childPID);
682 if (childPID == 0) {
683 if (umask) {
684 (void)::umask (*umask);
685 }
686 try {
687 /*
688 * In child process. Don't DBGTRACE here, or do anything that could raise an exception. In the child process
689 * this would be bad...
690 */
691 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wunused-result\"")
692 (void)::chdir (useCWD.c_str ());
693 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wunused-result\"")
694 if (options.fDetached.value_or (false)) {
695 /*
696 * See http://pubs.opengroup.org/onlinepubs/007904875/functions/setsid.html
697 * This is similar to setpgrp () but makes doing setpgrp unnecessary.
698 * This is also similar to setpgid (0, 0) - but makes doing that unneeded.
699 *
700 * Avoid signals like SIGHUP when the terminal session ends as well as potentially SIGTTIN and SIGTTOU
701 *
702 * @see http://stackoverflow.com/questions/8777602/why-must-detach-from-tty-when-writing-a-linux-daemon
703 *
704 * Tried using
705 * #if defined _DEFAULT_SOURCE
706 * daemon (0, 0);
707 * #endif
708 * to workaround systemd defaulting to KillMode=control-group
709 */
710 (void)::setsid ();
711 }
712 {
713 /*
714 * move arg stdin/out/err to 0/1/2 file-descriptors. Don't bother with variants that can handle errors/exceptions cuz we cannot really here...
715 */
716 int useSTDIN = jStdin[0];
717 int useSTDOUT = jStdout[1];
718 int useSTDERR = jStderr[1];
719 Assert (useSTDIN >= 0 and useSTDOUT >= 0 and useSTDERR >= 0); // parent can have -1 FDs, but child always has legit FDs
720 ::close (0);
721 ::close (1);
722 ::close (2);
723 ::dup2 (useSTDIN, 0);
724 ::dup2 (useSTDOUT, 1);
725 ::dup2 (useSTDERR, 2);
726 ::close (jStdin[0]);
727 ::close (jStdin[1]);
728 ::close (jStdout[0]);
729 ::close (jStdout[1]);
730 ::close (jStderr[0]);
731 ::close (jStderr[1]);
732 }
733 constexpr bool kCloseAllExtraneousFDsInChild_ = true;
734 if (kCloseAllExtraneousFDsInChild_) {
735 // close all but stdin, stdout, and stderr in child fork
736 for (int i = 3; i < kMaxFD_; ++i) {
737 ::close (i);
738 }
739 }
740 [[maybe_unused]] int r = ::execvp (thisEXEPath_cstr, thisEXECArgv);
741#if USE_NOISY_TRACE_IN_THIS_MODULE_
742 {
743 ofstream myfile;
744 myfile.open ("/tmp/Stroika-ProcessRunner-Exec-Failed-Debug-File.txt");
745 myfile << "thisEXEPath_cstr = " << thisEXEPath_cstr << endl;
746 myfile << "r = " << r << " and errno = " << errno << endl;
747 }
748#endif
749 ::_exit (EXIT_FAILURE);
750 }
751 catch (...) {
752 ::_exit (EXIT_FAILURE);
753 }
754 }
755 }
756 // we got here, the spawn succeeded, or the fork succeeded, and we are the parent process
757 Assert (childPID > 0);
758 {
759 constexpr size_t kStackBufReadAtATimeSize_ = 10 * 1024;
760
761#if USE_NOISY_TRACE_IN_THIS_MODULE_
762 DbgTrace ("In Parent Fork: child process PID={}"_f, childPID);
763#endif
764 if (runningPID != nullptr) {
765 runningPID->store (childPID);
766 }
767 /*
768 * WE ARE PARENT
769 */
770 int& useSTDIN = jStdin[1];
771 int& useSTDOUT = jStdout[0];
772 int& useSTDERR = jStderr[0];
773 {
774 CLOSE_ (jStdin[0]);
775 CLOSE_ (jStdout[1]);
776 CLOSE_ (jStderr[1]);
777 }
778
779 // To incrementally read from stderr and stderr as we write to stdin, we must assure
780 // our pipes are non-blocking
781 if (useSTDIN != -1) {
782 ThrowPOSIXErrNoIfNegative (::fcntl (useSTDIN, F_SETFL, fcntl (useSTDIN, F_GETFL, 0) | O_NONBLOCK));
783 }
784 if (useSTDOUT != -1) {
785 ThrowPOSIXErrNoIfNegative (::fcntl (useSTDOUT, F_SETFL, fcntl (useSTDOUT, F_GETFL, 0) | O_NONBLOCK));
786 }
787 if (useSTDERR != -1) {
788 ThrowPOSIXErrNoIfNegative (::fcntl (useSTDERR, F_SETFL, fcntl (useSTDERR, F_GETFL, 0) | O_NONBLOCK));
789 }
790
791 // Throw if any errors except EINTR (which is ignored) or EAGAIN (would block)
792 auto readALittleFromProcess = [&] (int fd, const OutputStream::Ptr<byte>& stream, bool write2StdErrCache, bool* eof = nullptr,
793 bool* maybeMoreData = nullptr) -> void {
794 if (fd == -1) {
795 if (maybeMoreData != nullptr) {
796 *maybeMoreData = false;
797 }
798 if (eof != nullptr) {
799 *eof = true;
800 }
801 return;
802 }
803 uint8_t buf[kStackBufReadAtATimeSize_];
804 int nBytesRead = 0; // int cuz we must allow for errno = EAGAIN error result = -1,
805#if USE_NOISY_TRACE_IN_THIS_MODULE_
806 int skipedThisMany{};
807#endif
808 while ((nBytesRead = ::read (fd, buf, sizeof (buf))) > 0) {
809 Assert (nBytesRead <= sizeof (buf));
810 if (stream != nullptr) {
811 stream.Write (span{buf, static_cast<size_t> (nBytesRead)});
812 }
813 if (write2StdErrCache) {
814 for (size_t i = 0; i < nBytesRead; ++i) {
815 Assert (&trailingStderrBuf[0] <= trailingStderrBufNextByte2WriteAt and
816 trailingStderrBufNextByte2WriteAt < end (trailingStderrBuf));
817 *trailingStderrBufNextByte2WriteAt = buf[i];
818 ++trailingStderrBufNWritten;
819 ++trailingStderrBufNextByte2WriteAt;
820 if (trailingStderrBufNextByte2WriteAt == end (trailingStderrBuf)) {
821 trailingStderrBufNextByte2WriteAt = begin (trailingStderrBuf);
822 }
823 Assert (&trailingStderrBuf[0] <= trailingStderrBufNextByte2WriteAt and
824 trailingStderrBufNextByte2WriteAt < end (trailingStderrBuf));
825 }
826 }
827#if USE_NOISY_TRACE_IN_THIS_MODULE_
828 if (errno == EAGAIN) {
829 // If we get lots of EAGAINS, just skip logging them to avoid spamming the tracelog
830 if (skipedThisMany++ < 100) {
831 continue;
832 }
833 else {
834 DbgTrace ("skipped {} spamming EAGAINs"_f, skipedThisMany);
835 skipedThisMany = 0;
836 }
837 }
838 buf[(nBytesRead == Memory::NEltsOf (buf)) ? (Memory::NEltsOf (buf) - 1) : nBytesRead] = '\0';
839 DbgTrace ("read from process (fd={}) nBytesRead = {}: {}"_f, fd, nBytesRead,
840 String::FromNarrowSDKString (reinterpret_cast<const char*> (buf)));
841#endif
842 }
843#if USE_NOISY_TRACE_IN_THIS_MODULE_
844 DbgTrace ("from (fd={}) nBytesRead = {}, errno={}"_f, fd, nBytesRead, errno);
845#endif
846 if (nBytesRead < 0) {
847 if (errno != EINTR and errno != EAGAIN) {
848 ThrowPOSIXErrNo (errno);
849 }
850 }
851 if (eof != nullptr) {
852 *eof = (nBytesRead == 0);
853 }
854 if (maybeMoreData != nullptr) {
855 *maybeMoreData = (nBytesRead > 0) or (nBytesRead < 0 and errno == EINTR);
856 }
857 };
858 auto readSoNotBlocking = [&] (int fd, const OutputStream::Ptr<byte>& stream, bool write2StdErrCache) {
859 bool maybeMoreData = true;
860 while (maybeMoreData) {
861 readALittleFromProcess (fd, stream, write2StdErrCache, nullptr, &maybeMoreData);
862 }
863 };
864 auto readTilEOF = [&] (int fd, const OutputStream::Ptr<byte>& stream, bool write2StdErrCache) {
865 if (fd == -1) {
866 return;
867 }
868 WaitForIOReady waiter{fd};
869 bool eof = false;
870 while (not eof) {
871 (void)waiter.WaitQuietly (1s);
872 readALittleFromProcess (fd, stream, write2StdErrCache, &eof);
873 }
874 };
875
876 if (in != nullptr) {
877 byte stdinBuf[kStackBufReadAtATimeSize_];
878 // read to 'in' til it reaches EOF (returns 0). But don't fully block, cuz we want to at least trickle in the stdout/stderr data
879 // even if no input is ready to send to child.
880 while (true) {
881 if (optional<span<byte>> bytesReadFromStdIn = in.ReadNonBlocking (span{stdinBuf})) {
882 Assert (bytesReadFromStdIn->size () <= Memory::NEltsOf (stdinBuf));
883 if (bytesReadFromStdIn->empty ()) {
884 break;
885 }
886 else {
887 const byte* e = bytesReadFromStdIn->data () + bytesReadFromStdIn->size ();
888 for (const byte* i = bytesReadFromStdIn->data (); i != e;) {
889 // read stuff from stdout, stderr while pushing to stdin, so that we don't get the PIPE buf too full
890 readSoNotBlocking (useSTDOUT, out, false);
891 readSoNotBlocking (useSTDERR, err, true);
892 int bytesWritten = ThrowPOSIXErrNoIfNegative (Handle_ErrNoResultInterruption ([useSTDIN, i, e] () {
893 int tmp = ::write (useSTDIN, i, e - i);
894 // NOTE: https://linux.die.net/man/2/write appears to indicate on pipe full, write could return 0, or < 0 with errno = EAGAIN, or EWOULDBLOCK
895 if (tmp < 0 and (errno == EAGAIN or errno == EWOULDBLOCK)) {
896 tmp = 0;
897 }
898 return tmp;
899 }));
900 Assert (bytesWritten >= 0);
901 Assert (bytesWritten <= (e - i));
902 i += bytesWritten;
903 if (bytesWritten == 0) {
904 // don't busy wait, but not clear how long to wait? Maybe should only sleep if readSoNotBlocking above returns no change
905 //
906 // OK - this is clearly wrong - @see http://stroika-bugs.sophists.com/browse/STK-589 - Fix performance of ProcessRunner - use select / poll instead of sleep when write to pipe returns 0
907 //
908 Sleep (1ms);
909 }
910 }
911 }
912 }
913 else {
914 // nothing on input stream, so pull from stdout, stderr, and wait a little to avoid busy-waiting
915 readSoNotBlocking (useSTDOUT, out, false);
916 readSoNotBlocking (useSTDERR, err, true);
917 Sleep (100ms);
918 }
919 }
920 }
921 // in case child process reads from its STDIN to EOF
922 CLOSE_ (useSTDIN);
923
924 readTilEOF (useSTDOUT, out, false);
925 readTilEOF (useSTDERR, err, true);
926
927 // not sure we need?
928 int status = 0;
929 int flags = 0; // FOR NOW - HACK - but really must handle sig-interruptions...
930 // Wait for child
931 int result =
932 Handle_ErrNoResultInterruption ([childPID, &status, flags] () -> int { return ::waitpid (childPID, &status, flags); });
933 // throw / warn if result other than child exited normally
934 if (processResult != nullptr) {
935 // not sure what it means if result != childPID??? - I think cannot happen cuz we pass in childPID, less result=-1
936 processResult->store (ProcessRunner::ProcessResultType{WIFEXITED (status) ? WEXITSTATUS (status) : optional<int> (),
937 WIFSIGNALED (status) ? WTERMSIG (status) : optional<int> ()});
938 }
939 else if (result != childPID or not WIFEXITED (status) or WEXITSTATUS (status) != 0) {
940 // @todo fix this message
941 DbgTrace ("childPID={}, result={}, status={}, WIFEXITED={}, WEXITSTATUS={}, WIFSIGNALED={}"_f, static_cast<int> (childPID),
942 result, status, WIFEXITED (status), WEXITSTATUS (status), WIFSIGNALED (status));
943 if (processResult == nullptr) {
944 StringBuilder stderrMsg;
945 if (trailingStderrBufNWritten > Memory::NEltsOf (trailingStderrBuf)) {
946 stderrMsg << "..."sv;
947 stderrMsg << String::FromLatin1 (Memory::ConstSpan (span{trailingStderrBufNextByte2WriteAt, end (trailingStderrBuf)}));
948 }
949 stderrMsg << String::FromLatin1 (Memory::ConstSpan (span{begin (trailingStderrBuf), trailingStderrBufNextByte2WriteAt}));
950 Throw (ProcessRunner::Exception{"Spawned program"sv, stderrMsg.str (),
951 WIFEXITED (status) ? WEXITSTATUS (status) : optional<uint8_t>{},
952 WIFSIGNALED (status) ? WTERMSIG (status) : optional<uint8_t>{}});
953 }
954 }
955 }
956 }
957}
958#endif
959
960#if qStroika_Foundation_Common_Platform_Windows
961namespace {
962 void Process_Runner_Windows_ (Synchronized<optional<ProcessRunner::ProcessResultType>>* processResult,
963 Synchronized<optional<pid_t>>* runningPID, ProgressMonitor::Updater progress,
964 const optional<filesystem::path>& executable, const CommandLine& cmdLine, const ProcessRunner::Options& options,
966 {
967 filesystem::path useCWD = options.fWorkingDirectory.value_or (IO::FileSystem::WellKnownLocations::GetTemporary ());
968 TraceContextBumper ctx{"{}::Process_Runner_Windows_", Stroika_Foundation_Debug_OptionalizeTraceArgs (
969 "...,cmdLine='{}',currentDir={},..."_f, cmdLine,
970 String{useCWD}.LimitLength (50, StringShorteningPreference::ePreferKeepRight))};
971
972 /*
973 * o Build directory into which we can copy the JAR file plugin,
974 * o create STDIN/STDOUT file handles to send/grab results
975 * o Run the process, waiting for it to finish.
976 * o Grab results from STDOUT file.
977 * o Cleanup created directory.
978 */
979
980 // use AutoHANDLE so these are automatically closed at the end of the procedure, whether it ends normally or via
981 // exception.
982 AutoHANDLE_ jStdin[2]{INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
983 AutoHANDLE_ jStdout[2]{INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
984 AutoHANDLE_ jStderr[2]{INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
985
986 PROCESS_INFORMATION processInfo{};
987 processInfo.hProcess = INVALID_HANDLE_VALUE;
988 processInfo.hThread = INVALID_HANDLE_VALUE;
989
990 try {
991 {
992 SECURITY_DESCRIPTOR sd{};
993 Verify (::InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION));
994 Verify (::SetSecurityDescriptorDacl (&sd, true, 0, false));
995 SECURITY_ATTRIBUTES sa = {sizeof (SECURITY_ATTRIBUTES), &sd, true};
996 if (in) {
997 Verify (::CreatePipe (&jStdin[1], &jStdin[0], &sa, kPipeBufSize_));
998 }
999 if (out) {
1000 Verify (::CreatePipe (&jStdout[1], &jStdout[0], &sa, kPipeBufSize_));
1001 }
1002 if (err) {
1003 Verify (::CreatePipe (&jStderr[1], &jStderr[0], &sa, kPipeBufSize_));
1004 }
1005 /*
1006 * Make sure the ends of the pipe WE hang onto are not inheritable, because otherwise the READ
1007 * wont return EOF (until the last one is closed).
1008 */
1009 if (in) {
1010 jStdin[0].ReplaceHandleAsNonInheritable ();
1011 }
1012 if (out) {
1013 jStdout[1].ReplaceHandleAsNonInheritable ();
1014 }
1015 if (err) {
1016 jStderr[1].ReplaceHandleAsNonInheritable ();
1017 }
1018 }
1019
1020 STARTUPINFO startInfo{};
1021 startInfo.cb = sizeof (startInfo);
1022 startInfo.hStdInput = jStdin[1];
1023 startInfo.hStdOutput = jStdout[0];
1024 startInfo.hStdError = jStderr[0];
1025 startInfo.dwFlags |= STARTF_USESTDHANDLES;
1026
1027 DWORD createProcFlags{NORMAL_PRIORITY_CLASS};
1028 if (options.fCreateNoWindow) {
1029 createProcFlags |= CREATE_NO_WINDOW;
1030 }
1031 else if (options.fDetached.value_or (false)) {
1032 // DETACHED_PROCESS ignored if CREATE_NO_WINDOW
1033 createProcFlags |= DETACHED_PROCESS;
1034 }
1035
1036 {
1037 // UNCLEAR; visual studio system() impl uses true; docs not clear
1038 // BUT - when I use false I get "unknown file: error: C++ exception with description "Spawned program 'echo hi mom' failed: error: 1" thrown in the test body." for some tests
1039 bool bInheritHandles = true;
1040
1041 TCHAR cmdLineBuf[32768]; // crazy MSFT definition! - why this should need to be non-const!
1042 Characters::CString::Copy (cmdLineBuf, Memory::NEltsOf (cmdLineBuf), cmdLine.As<String> ().AsSDKString ().c_str ());
1043
1044 optional<filesystem::path> useEXEPath = executable;
1045
1046 // WARN if EXE not in path...
1047#if qStroika_Foundation_Debug_AssertionsChecked
1048 if (useEXEPath) {
1049 if (!FindExecutableInPath (*useEXEPath)) {
1050 DbgTrace ("Warning: Cannot find exe '{}' in PATH ({})"_f, useEXEPath, kPath ());
1051 }
1052 }
1053 else {
1054 // not sure we want to do this? - since first thing could be magic interpreted by shell, like set
1055 auto cmdArgs = cmdLine.GetArguments ();
1056 if (cmdArgs.size () >= 1) {
1057 filesystem::path exe2Find = cmdArgs[0].As<filesystem::path> ();
1058 if (!FindExecutableInPath (exe2Find)) {
1059 DbgTrace ("Warning: Cannot find exe '{}' in PATH ({})"_f, exe2Find, kPath ());
1060 }
1061 }
1062 }
1063#endif
1064
1065 unique_ptr<String2ContigArrayCStrs_<SDKChar>> envBuffer;
1066 LPVOID lpEnvironment = nullptr;
1067 if (options.fEnvironment) {
1068 if (auto oep = get_if<Sequence<filesystem::path>> (&options.fEnvironment.value ())) {
1069 envBuffer = make_unique<String2ContigArrayCStrs_<SDKChar>> (getEnv_ (*oep));
1070 }
1071 else if (auto om = get_if<Mapping<String, String>> (&options.fEnvironment.value ())) {
1072 envBuffer = make_unique<String2ContigArrayCStrs_<SDKChar>> (getEnv_ (*om));
1073 }
1074 else if (auto oms = get_if<Mapping<SDKString, SDKString>> (&options.fEnvironment.value ())) {
1075 envBuffer = make_unique<String2ContigArrayCStrs_<SDKChar>> (getEnv_ (*oms));
1076 }
1077 AssertNotNull (envBuffer);
1078 lpEnvironment = envBuffer->fBytesBuffer.data (); // need to adjust createProcFlags for type used...
1079 if constexpr (same_as<SDKChar, wchar_t>) {
1080 createProcFlags |= CREATE_UNICODE_ENVIRONMENT;
1081 }
1082 }
1083
1084 // see https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
1085 // for complex rules for interpreting nullptr in appname (first) arg, and cmdLineBuf... But mostly - the idea - is
1086 // it runs the search path algorithm and tries to do the right thing
1088 ::CreateProcess (useEXEPath == nullopt ? nullptr : useEXEPath->c_str (), cmdLineBuf, nullptr, nullptr, bInheritHandles,
1089 createProcFlags, lpEnvironment, useCWD.c_str (), &startInfo, &processInfo));
1090 }
1091
1092 if (runningPID != nullptr) {
1093 runningPID->store (processInfo.dwProcessId);
1094 }
1095
1096 {
1097 /*
1098 * Remove our copy of the stdin/stdout/stderr which belong to the child (so EOF will work properly).
1099 */
1100 jStdin[1].Close ();
1101 jStdout[0].Close ();
1102 jStderr[0].Close ();
1103 }
1104
1105 AutoHANDLE_& useSTDIN = jStdin[0];
1106 Assert (jStdin[1] == INVALID_HANDLE_VALUE);
1107 AutoHANDLE_& useSTDOUT = jStdout[1];
1108 Assert (jStdout[0] == INVALID_HANDLE_VALUE);
1109 AutoHANDLE_& useSTDERR = jStderr[1];
1110 Assert (jStderr[0] == INVALID_HANDLE_VALUE);
1111
1112 constexpr size_t kStackBufReadAtATimeSize_ = 10 * 1024;
1113
1114 auto readAnyAvailableAndCopy2StreamWithoutBlocking = [] (HANDLE p, const OutputStream::Ptr<byte>& o) {
1115 if (p == INVALID_HANDLE_VALUE) {
1116 return;
1117 }
1118 byte buf[kReadBufSize_];
1119#if qUsePeekNamedPipe_
1120 DWORD nBytesAvail{};
1121#endif
1122 DWORD nBytesRead{};
1123 // Read normally blocks, we don't want to because we may need to write more before it can output
1124 // and we may need to timeout
1125 while (
1126#if qUsePeekNamedPipe_
1127 ::PeekNamedPipe (p, nullptr, nullptr, nullptr, &nBytesAvail, nullptr) and nBytesAvail != 0 and
1128#endif
1129 ::ReadFile (p, buf, sizeof (buf), &nBytesRead, nullptr) and nBytesRead > 0) {
1130 if (o != nullptr) {
1131 o.Write (span{buf, nBytesRead});
1132 }
1133#if USE_NOISY_TRACE_IN_THIS_MODULE_
1134 buf[(nBytesRead == Memory::NEltsOf (buf)) ? (Memory::NEltsOf (buf) - 1) : nBytesRead] = byte{'\0'};
1135 DbgTrace ("read from process (fd={}) nBytesRead = {}: {}"_f, p, nBytesRead, buf);
1136#endif
1137 }
1138 };
1139
1140 if (processInfo.hProcess != INVALID_HANDLE_VALUE) {
1141 {
1142 {
1143 /*
1144 * Set the pipe endpoints to non-blocking mode.
1145 */
1146 auto mkPipeNoWait_ = [] (HANDLE ioHandle) -> void {
1147 if (ioHandle != INVALID_HANDLE_VALUE) {
1148 DWORD stdinMode = 0;
1149 Verify (::GetNamedPipeHandleState (ioHandle, &stdinMode, nullptr, nullptr, nullptr, nullptr, 0));
1150 stdinMode |= PIPE_NOWAIT;
1151 Verify (::SetNamedPipeHandleState (ioHandle, &stdinMode, nullptr, nullptr));
1152 }
1153 };
1154 mkPipeNoWait_ (useSTDIN);
1155 mkPipeNoWait_ (useSTDOUT);
1156 mkPipeNoWait_ (useSTDERR);
1157 }
1158
1159 /*
1160 * Fill child-process' stdin with the source document.
1161 */
1162 if (in != nullptr) {
1163 byte stdinBuf[kStackBufReadAtATimeSize_];
1164 // blocking read to 'in' til it reaches EOF (returns 0)
1165 while (size_t nbytes = in.ReadBlocking (span{stdinBuf}).size ()) {
1166 Assert (nbytes <= Memory::NEltsOf (stdinBuf));
1167 const byte* p = begin (stdinBuf);
1168 const byte* e = p + nbytes;
1169 while (p < e) {
1170 DWORD written = 0;
1171 if (::WriteFile (useSTDIN, p, Math::PinToMaxForType<DWORD> (e - p), &written, nullptr) == 0) {
1172 DWORD lastErr = ::GetLastError ();
1173 // sometimes we fail because the target process hasn't read enough and the pipe is full.
1174 // Unfortunately - MSFT doesn't seem to have a single clear error message nor any clear
1175 // documentation about what WriteFile () returns in this case... So there maybe other errors
1176 // that are innocuous that may cause is to prematurely terminate our 'RunExternalProcess'.
1177 // -- LGP 2009-05-07
1178 if (lastErr != ERROR_SUCCESS and lastErr != ERROR_NO_MORE_FILES and lastErr != ERROR_PIPE_BUSY and
1179 lastErr != ERROR_NO_DATA) {
1180 DbgTrace ("in RunExternalProcess_ - throwing {} while fill in stdin"_f, lastErr);
1181 ThrowSystemErrNo (lastErr);
1182 }
1183 }
1184 Assert (written <= static_cast<size_t> (e - p));
1185 p += written;
1186 // in case we are failing to write to the stdIn because of blocked output on an outgoing pipe
1187 if (p < e) {
1188 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDOUT, out);
1189 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDERR, err);
1190 }
1191 if (p < e and written == 0) {
1192 // if we have more to write, but that the target process hasn't consumed it yet - don't spin trying to
1193 // send it data - back off a little
1194 Sleep (100ms);
1195 }
1196#if 0
1197 // Do timeout handling at a higher level
1198 if (Time::GetTickCount () > timeoutAt) {
1199 DbgTrace (_T ("process timed out (writing initial data) - so throwing up!"));
1200 // then we've timed out - kill the process and DON'T return the partial result!
1201 (void)::TerminateProcess (processInfo.hProcess, -1); // if it exceeded the timeout - kill it (could already be done by now - in which case - this will be ignored - fine...
1202 Throw (Execution::Platform::Windows::Exception (ERROR_TIMEOUT));
1203 }
1204#endif
1205 }
1206 }
1207 }
1208
1209 // in case invoked sub-process is reading, and waiting for EOF before processing...
1210 useSTDIN.Close ();
1211 }
1212
1213 /*
1214 * Must keep reading while waiting - in case the child emits so much information that it
1215 * fills the OS PIPE buffer.
1216 */
1217 int timesWaited = 0;
1218 while (true) {
1219 /*
1220 * It would be nice to be able to WAIT on the PIPEs - but that doesn't appear to work when they
1221 * are in ASYNCRONOUS mode.
1222 *
1223 * So - instead - just wait a very short period, and then retry polling the pipes for more data.
1224 * -- LGP 2006-10-17
1225 */
1226 HANDLE events[1] = {processInfo.hProcess};
1227
1228 // We don't want to busy wait too much, but if its fast (with java, that's rare ;-)) don't want to wait
1229 // too long needlessly...
1230 //
1231 // Also - its not exactly a busy-wait. Its just a wait between reading stuff to avoid buffers filling. If the
1232 // process actually finishes, it will change state and the wait should return immediately.
1233 double remainingTimeout = (timesWaited <= 5) ? 0.1 : 0.5;
1234 DWORD waitResult = ::WaitForMultipleObjects (static_cast<DWORD> (Memory::NEltsOf (events)), events, false,
1235 static_cast<int> (remainingTimeout * 1000));
1236 ++timesWaited;
1237
1238 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDOUT, out);
1239 readAnyAvailableAndCopy2StreamWithoutBlocking (useSTDERR, err);
1240 switch (waitResult) {
1241 case WAIT_OBJECT_0: {
1242 DbgTrace ("external process finished (DONE)"_f);
1243 // timeoutAt = -1.0f; // force out of loop
1244 goto DoneWithProcess;
1245 } break;
1246 case WAIT_TIMEOUT: {
1247 DbgTrace ("still waiting for external process output (WAIT_TIMEOUT)"_f);
1248 }
1249 }
1250 }
1251
1252 DoneWithProcess:
1253 DWORD processExitCode{};
1254 Verify (::GetExitCodeProcess (processInfo.hProcess, &processExitCode));
1255
1256 SAFE_HANDLE_CLOSER_ (&processInfo.hProcess);
1257 SAFE_HANDLE_CLOSER_ (&processInfo.hThread);
1258
1259 if (useSTDOUT != INVALID_HANDLE_VALUE) {
1260 DWORD stdoutMode = 0;
1261 Verify (::GetNamedPipeHandleState (useSTDOUT, &stdoutMode, nullptr, nullptr, nullptr, nullptr, 0));
1262 stdoutMode &= ~PIPE_NOWAIT;
1263 Verify (::SetNamedPipeHandleState (useSTDOUT, &stdoutMode, nullptr, nullptr));
1264
1265 /*
1266 * Read whatever is left...and blocking here is fine, since at this point - the subprocess should be closed/terminated.
1267 */
1268 if (out != nullptr) {
1269 byte buf[kReadBufSize_];
1270 DWORD nBytesRead = 0;
1271 while (::ReadFile (useSTDOUT, buf, sizeof (buf), &nBytesRead, nullptr)) {
1272 out.Write (span{buf, nBytesRead});
1273 }
1274 }
1275 }
1276
1277 if (processResult == nullptr) {
1278 if (processExitCode != 0) {
1279 Throw (ProcessRunner::Exception{"Child process failed"sv, nullopt, processExitCode});
1280 }
1281 }
1282 else {
1283 processResult->store (ProcessRunner::ProcessResultType{static_cast<int> (processExitCode)});
1284 }
1285 }
1286
1287 // @todo MAYBE need to copy STDERRR TOO!!!
1288 }
1289 catch (...) {
1290 // sadly and confusingly, CreateProcess() appears to set processInfo.hProcess and processInfo.hThread to nullptr - at least on some failures
1291 if (processInfo.hProcess != INVALID_HANDLE_VALUE and processInfo.hProcess != nullptr) {
1292 (void)::TerminateProcess (processInfo.hProcess, static_cast<UINT> (-1)); // if it exceeded the timeout - kill it
1293 SAFE_HANDLE_CLOSER_ (&processInfo.hProcess);
1294 SAFE_HANDLE_CLOSER_ (&processInfo.hThread);
1295 }
1296 ReThrow ();
1297 }
1298 }
1299}
1300#endif
1301
1302function<void ()> ProcessRunner::CreateRunnable_ (Synchronized<optional<ProcessResultType>>* processResult,
1303 Synchronized<optional<pid_t>>* runningPID, ProgressMonitor::Updater progress)
1304{
1305#if USE_NOISY_TRACE_IN_THIS_MODULE_
1306 TraceContextBumper ctx{"ProcessRunner::CreateRunnable_"};
1307#endif
1308 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
1309 return [processResult, runningPID, progress, exe = this->fExecutable_, cmdLine = this->fArgs_, options = fOptions_, in = fStdIn_,
1310 out = fStdOut_, err = fStdErr_] () {
1311#if USE_NOISY_TRACE_IN_THIS_MODULE_
1312 TraceContextBumper ctx{"ProcessRunner::CreateRunnable_::{}::Runner..."};
1313#endif
1314 auto activity = LazyEvalActivity{[&] () { return "executing '{}'"_f(cmdLine); }};
1315 DeclareActivity currentActivity{&activity};
1316#if qStroika_Foundation_Common_Platform_POSIX
1317 Process_Runner_POSIX_ (processResult, runningPID, progress, exe, cmdLine, options, in, out, err);
1318#elif qStroika_Foundation_Common_Platform_Windows
1319 Process_Runner_Windows_ (processResult, runningPID, progress, exe, cmdLine, options, in, out, err);
1320#endif
1321 };
1322}
#define AssertNotNull(p)
Definition Assertions.h:333
#define AssertNotImplemented()
Definition Assertions.h:401
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertNotReached()
Definition Assertions.h:355
#define Verify(c)
Definition Assertions.h:419
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
InlineBuffer< T, BUF_SIZE > StackBuffer
Store variable sized (BUF_SIZE elements) array on the stack (.
Definition StackBuffer.h:59
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
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...
Definition String.inl:741
static String FromNarrowSDKString(const char *from)
Definition String.inl:470
nonvirtual SDKString AsSDKString() const
Definition String.inl:802
static String FromLatin1(const CHAR_T *cString)
Definition String.inl:355
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:190
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
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...
Definition Exceptions.h:157
NestedException contains a new higher level error message (typically based on argument basedOnExcepti...
Definition Exceptions.h:209
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 optional< ProcessResultType > GetProcessResult() 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).
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, ProgressMonitor::Updater progress=nullptr, Time::DurationSeconds timeout=Time::kInfinity)
Run the given external command/process (set by constructor) - with the given arguments,...
nonvirtual BackgroundProcess RunInBackground(const Streams::InputStream::Ptr< byte > &in=nullptr, const Streams::OutputStream::Ptr< byte > &out=nullptr, const Streams::OutputStream::Ptr< byte > &error=nullptr, ProgressMonitor::Updater progress=nullptr)
Run the given external command/process (set by constructor) - with the given arguments in the backgro...
ProcessRunner()=delete
Construct ProcessRunner with a CommandLine to run (doesn't actually RUN til you call Run or RunInBack...
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:334
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...
Definition Thread.inl:276
nonvirtual void WaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
Definition Thread.inl:286
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...
Definition Thread.inl:280
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual optional< ElementType > ReadBlocking() const
ReadBlocking () reads either a single element, or fills in argument intoBuffer - but never blocks (no...
nonvirtual optional< span< ElementType > > ReadNonBlocking(span< ElementType > intoBuffer) const
read into intoBuffer - returning nullopt if would block, and else returning subspan of input with rea...
OutputStream<>::Ptr is Smart pointer to a stream-based sink of data.
nonvirtual void Write(span< ELEMENT_TYPE2, EXTENT_2 > elts) const
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
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
Definition SDKString.h:38
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:955
void Sleep(Time::Duration seconds2Wait)
Definition Sleep.cpp:18
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
const LazyInitialized< Containers::Sequence< filesystem::path > > kPath
Definition Module.cpp:144
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 >
Definition Finally.inl:31
const LazyInitialized< Containers::Mapping< Characters::SDKString, Characters::SDKString > > kRawEnvironment
convert getenv() to a Mapping of SDKString (in case some issue with charactor set conversion)
Definition Module.cpp:180
optional< filesystem::path > FindExecutableInPath(const filesystem::path &fn)
If fn refers to an executable - return it (using kPATH, and kPathEXT as appropriate)
Definition Module.cpp:245
int pid_t
TODO - maybe move this to configuraiotn module???
Definition Module.h:34
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)
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 ...