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