Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
Process.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4
5#include "../Common/CompilerMacrosExport.h"
6#ifndef SC_EXPORT_LIBRARY_PROCESS
7#define SC_EXPORT_LIBRARY_PROCESS 0
8#endif
9#define SC_PROCESS_EXPORT SC_COMPILER_LIBRARY_EXPORT(SC_EXPORT_LIBRARY_PROCESS)
10
11#include "../Common/AlignedStorage.h"
12#include "../Common/Assert.h"
13#include "../Common/IGrowableBufferSpan.h"
14#include "../Common/IGrowableBufferStringPath.h"
15#include "../File/File.h"
16
17namespace SC
18{
19SC_DECLARE_ASSERT_PROVIDER(ProcessAssert, SC_PROCESS_EXPORT);
20
21#define SC_PROCESS_ASSERT_RELEASE(e) SC_ASSERT_PROVIDER_RELEASE(SC::ProcessAssert, e)
22#define SC_PROCESS_ASSERT_DEBUG(e) SC_ASSERT_PROVIDER_DEBUG(SC::ProcessAssert, e)
23#define SC_PROCESS_TRUST_RESULT(expression) SC_PROCESS_ASSERT_RELEASE(expression)
24
25struct SC_PROCESS_EXPORT ProcessChain;
26
27struct SC_PROCESS_EXPORT ProcessDescriptor
28{
29 using Handle = detail::FileDescriptorDefinition::Handle;
30 static constexpr auto Invalid = detail::FileDescriptorDefinition::Invalid;
31};
32
35{
36 int32_t status = -1;
37};
38
41
44
47{
48 int32_t pid = 0;
49};
50
83
84struct SC_PROCESS_EXPORT Process
85{
86 static constexpr size_t InlineCommandStorageCapacity = StringPath::MaxPath + 1024;
87
88 struct SC_PROCESS_EXPORT Options
89 {
91 Options();
92 };
93
94 struct StdStream
95 {
97 StdStream(IGrowableBuffer& destination)
98 {
99 growableBuffer = &destination;
100 operation = Operation::GrowableBuffer;
101 }
102
104 StdStream(GrowableBuffer<FileDescriptor>& file)
105 {
106 operation = Operation::FileDescriptor;
107 (void)file.content.get(fileDescriptor, Result::Error("Invalid redirection file descriptor"));
108 file.content.detach();
109 }
110
111 StdStream(GrowableBuffer<PipeDescriptor>& pipe)
112 {
113 operation = Operation::ExternalPipe;
114 pipeDescriptor = &pipe.content;
115 }
116
117 StdStream(const StdStream&) = delete;
118 StdStream(StdStream&&) = delete;
119 StdStream& operator=(const StdStream&) = delete;
120 StdStream& operator=(StdStream&&) = delete;
121
122 protected:
124 {
125 };
126 StdStream() = default;
127 StdStream(AlreadySetup) { operation = Operation::AlreadySetup; }
128 friend struct Process;
129 friend struct ProcessChain;
130
131 enum class Operation
132 {
133 AlreadySetup,
134 Inherit,
135 Ignore,
136 ExternalPipe,
138 GrowableBuffer,
139 WritableSpan,
140 ReadableSpan
141 };
142 Operation operation = Operation::Inherit;
143
144 Span<const char> readableSpan;
145 Span<char>* writableSpan = nullptr;
146
147 IGrowableBuffer* growableBuffer = nullptr;
148
149 FileDescriptor::Handle fileDescriptor;
150
151 PipeDescriptor* pipeDescriptor;
152 };
153
154 struct StdOut : public StdStream
155 {
156 // clang-format off
157 struct Ignore{};
158 struct Inherit{};
159
160
162 StdOut(GrowableBuffer<StdOut::Ignore>) { operation = Operation::Ignore; }
163
165 StdOut(GrowableBuffer<StdOut::Inherit>) { operation = Operation::Inherit; }
166 StdOut(GrowableBuffer<StdOut>) { operation = Operation::Inherit; }
167 StdOut() { operation = Operation::Inherit; }
168
170 StdOut(GrowableBuffer<Span<char>>& span) { operation = Operation::WritableSpan; writableSpan = &span.content; span.setContentInDestructor = false; }
171
172 using StdStream::StdStream;
173 friend struct ProcessChain;
174 // clang-format on
175 };
176
177 using StdErr = StdOut;
178
179 struct StdIn : public StdStream
180 {
181 // clang-format off
182 struct Inherit{};
183
185 StdIn(GrowableBuffer<Inherit>) { operation = Operation::Inherit; }
186 StdIn(GrowableBuffer<StdIn>) { operation = Operation::Inherit; }
187 StdIn() { operation = Operation::Inherit; }
188
190 template <int N> StdIn(GrowableBuffer<const char [N]>& item) { operation = Operation::ReadableSpan; readableSpan = {item.content, N - 1}; }
191
193 StdIn(GrowableBuffer<StringSpan> string) { operation = Operation::ReadableSpan; readableSpan = string.content.toCharSpan();}
194
196 StdIn(GrowableBuffer<Span<const char>> span) { operation = Operation::ReadableSpan; readableSpan = span.content;}
197
198 using StdStream::StdStream;
199 friend struct ProcessChain;
200 // clang-format on
201 };
202
205
206 ProcessDescriptor::Handle handle = ProcessDescriptor::Invalid;
207
210
218 template <typename Out = StdOut, typename In = StdIn, typename Err = StdErr>
219 Result launch(Span<const StringSpan> cmd, Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
220 {
221 SC_TRY(formatArguments(cmd));
222 GrowableBuffer<typename TypeTraits::RemoveReference<Out>::type> gbOut = {stdOut};
223 GrowableBuffer<typename TypeTraits::RemoveReference<In>::type> gbIn = {stdIn};
224 GrowableBuffer<typename TypeTraits::RemoveReference<Err>::type> gbErr = {stdErr};
225 return launch(StdOut(gbOut), StdIn(gbIn), StdErr(gbErr));
226 }
227
235 template <typename Out = StdOut, typename In = StdIn, typename Err = StdErr>
236 Result exec(Span<const StringSpan> cmd, Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
237 {
238 SC_TRY(launch(cmd, stdOut, stdIn, stdErr));
239 return waitForExitSync();
240 }
241
243 int32_t getExitStatus() const { return exitStatus.status; }
244
246 Result setWorkingDirectory(StringSpan processWorkingDirectory);
247
249 void inheritParentEnvironmentVariables(bool inherit) { inheritEnv = inherit; }
250
252 Result setEnvironment(StringSpan environmentVariable, StringSpan value);
253
255 [[nodiscard]] static size_t getNumberOfProcessors();
256
258 [[nodiscard]] static bool isWindowsConsoleSubsystem();
259
261 [[nodiscard]] static bool isWindowsEmulatedProcess();
262
266 Process(Span<native_char_t> commandMemory = {}, Span<native_char_t> environmentMemory = {})
267 : command({commandMemory}), environment({environmentMemory})
268 {
269 if (commandMemory.empty())
270 command = {commandStorage};
271 if (environmentMemory.empty())
272 environment = {environmentStorage};
273 }
274
275 private:
276 ProcessExitStatus exitStatus;
277
278 FileDescriptor stdInFd;
279 FileDescriptor stdOutFd;
280 FileDescriptor stdErrFd;
281
282 Result launch(const StdOut& stdOutput, const StdIn& stdInput, const StdErr& stdError);
283
284 Result formatArguments(Span<const StringSpan> cmd);
285
286 StringPath currentDirectory;
287#if SC_PLATFORM_WINDOWS
288 StringPath executablePathForLaunch;
289 bool executablePathLooksLikeFile = false;
290#endif
291
292 // On Windows command holds the concatenation of executable and arguments.
293 // On Posix command holds the concatenation of executable and arguments SEPARATED BY null-terminators (\0).
294 // This is done so that in this single buffer with no allocation (under 255) or a single allocation (above 255)
295 // we can track all arguments to be passed to execve.
296 native_char_t commandStorage[InlineCommandStorageCapacity];
297 StringSpan::NativeWritable command;
298#if !SC_PLATFORM_WINDOWS // On Posix we need to track the "sub-strings" hidden in command
299 static constexpr size_t MAX_NUM_ARGUMENTS = 64;
300 size_t commandArgumentsByteOffset[MAX_NUM_ARGUMENTS]; // Tracking length of each argument in the command string
301 size_t commandArgumentsNumber = 0; // Counts number of arguments (including executable name)
302#endif
303
304 native_char_t environmentStorage[4096 * 8];
305 StringSpan::NativeWritable environment;
306
307 static constexpr size_t MAX_NUM_ENVIRONMENT = 256;
308
309 size_t environmentByteOffset[MAX_NUM_ENVIRONMENT]; // Tracking length of each environment variable
310 size_t environmentNumber = 0; // Counts number of environment variable
311
312 bool inheritEnv = true;
313
314 friend struct ProcessChain;
315 ProcessChain* parent = nullptr;
316
317 Process* next = nullptr;
318 Process* prev = nullptr;
319 struct Internal;
320 struct InternalFork;
321 friend struct ProcessFork;
322 Result launchImplementation();
323 Result launchForkChild(PipeDescriptor& pipe);
324 Result launchForkParent(PipeDescriptor& pipe, const void* previousSignals);
325};
326
343struct SC_PROCESS_EXPORT ProcessChain
344{
345 Process::Options options;
350 Result pipe(Process& process, const Span<const StringSpan> cmd);
351
355 template <typename Out = Process::StdOut, typename In = Process::StdIn, typename Err = Process::StdErr>
356 Result launch(Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
357 {
358 GrowableBuffer<typename TypeTraits::RemoveReference<Out>::type> gbOut = {stdOut};
359 GrowableBuffer<typename TypeTraits::RemoveReference<In>::type> gbIn = {stdIn};
360 GrowableBuffer<typename TypeTraits::RemoveReference<Err>::type> gbErr = {stdErr};
361 return internalLaunch(gbOut, gbIn, gbErr);
362 }
363
367
370 template <typename Out = Process::StdOut, typename In = Process::StdIn, typename Err = Process::StdErr>
371 Result exec(Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
372 {
373 SC_TRY(launch(stdOut, stdIn, stdErr));
374 return waitForExitSync();
375 }
376
377 private:
378 Result internalLaunch(const Process::StdOut& stdOut, const Process::StdIn& stdIn, const Process::StdErr& stdErr);
379 // Trimmed duplicate of IntrusiveDoubleLinkedList<T>
380 struct ProcessLinkedList
381 {
382 Process* back = nullptr; // has no next
383 Process* front = nullptr; // has no prev
384
385 [[nodiscard]] bool isEmpty() const { return front == nullptr; }
386
387 void clear();
388 void queueBack(Process& process);
389 };
390 ProcessLinkedList processes;
391};
392
398{
401
402 ProcessEnvironment(const ProcessEnvironment&) = delete;
404 ProcessEnvironment& operator=(const ProcessEnvironment&) = delete;
405 ProcessEnvironment& operator=(ProcessEnvironment&&) = delete;
406
408 [[nodiscard]] size_t size() const { return numberOfEnvironment; }
409
414 [[nodiscard]] bool get(size_t index, StringSpan& name, StringSpan& value) const;
415
420 [[nodiscard]] bool contains(StringSpan variableName, size_t* index = nullptr) const;
421
426 [[nodiscard]] bool get(StringSpan variableName, StringSpan& value) const;
427
428 private:
429 size_t numberOfEnvironment = 0;
430#if SC_PLATFORM_WINDOWS
431 static constexpr size_t MAX_ENVIRONMENTS = 256;
432
433 StringSpan envStrings[MAX_ENVIRONMENTS];
434 wchar_t* environment = nullptr;
435#else
436 char** environment = nullptr;
437#endif
438};
439
465struct SC_PROCESS_EXPORT ProcessFork
466{
467 ProcessFork();
468 ~ProcessFork();
469 ProcessFork(const ProcessFork&) = delete;
470 ProcessFork* operator=(const ProcessFork&) = delete;
471
472 enum Side
473 {
476 };
477
479 [[nodiscard]] Side getSide() const { return side; }
480
481 enum State
482 {
485 };
486
488 Result fork(State state);
489
492
494 Result waitForChild();
495
497 int32_t getExitStatus() const { return exitStatus.status; }
498
501
504
505 private:
506 Side side = ForkParent;
507#if SC_PLATFORM_WINDOWS
508 ProcessDescriptor::Handle processHandle = ProcessDescriptor::Invalid;
509 ProcessDescriptor::Handle threadHandle = ProcessDescriptor::Invalid;
510#else
511 ProcessID processID;
512#endif
513 ProcessExitStatus exitStatus;
514
515 PipeDescriptor parentToFork;
516 PipeDescriptor forkToParent;
517};
519
520} // namespace SC
[UniqueHandleDeclaration2Snippet]
Definition File.h:130
Read / Write pipe (Process stdin/stdout and IPC communication)
Definition File.h:302
Execute multiple child processes chaining input / output between them.
Definition Process.h:344
Result launch(Out &&stdOut=Out(), In &&stdIn=In(), Err &&stdErr=Err())
Launch the entire chain of processes.
Definition Process.h:356
Result pipe(Process &process, const Span< const StringSpan > cmd)
Add a process to the chain, with given arguments.
Result exec(Out &&stdOut=Out(), In &&stdIn=In(), Err &&stdErr=Err())
Launch the entire chain of processes and waits for the results (calling ProcessChain::waitForExitSync...
Definition Process.h:371
Result waitForExitSync()
Waits (blocking) for entire process chain to exit.
Definition Process.h:28
Reads current process environment variables.
Definition Process.h:398
bool get(size_t index, StringSpan &name, StringSpan &value) const
Get the environment variable at given index, returning its name and value.
bool get(StringSpan variableName, StringSpan &value) const
Gets the value of an environment variable from current process.
size_t size() const
Returns the total number of environment variables for current process.
Definition Process.h:408
bool contains(StringSpan variableName, size_t *index=nullptr) const
Checks if an environment variable exists in current process.
Wraps the code returned by a process that has exited.
Definition Process.h:35
Forks current process exiting child at end of process A fork duplicates a parent process execution st...
Definition Process.h:466
Result waitForChild()
Waits for child fork to finish execution.
State
Definition Process.h:482
@ Suspended
Start the forked process suspended (resume it with ProcessFork::resumeChildFork)
Definition Process.h:483
@ Immediate
Start the forked process immediately.
Definition Process.h:484
Side getSide() const
Obtain process parent / fork side.
Definition Process.h:479
FileDescriptor & getWritePipe()
Gets the descriptor to "write" something to the other side.
Side
Definition Process.h:473
@ ForkParent
Parent side of the fork.
Definition Process.h:474
@ ForkChild
Child side of the fork.
Definition Process.h:475
Result resumeChildFork()
Sends 1 byte on parentToFork to resume State::Paused child fork.
FileDescriptor & getReadPipe()
Gets the descriptor to "read" something from the other side.
Result fork(State state)
Forks current process (use ForkProcess::getType to know the side)
int32_t getExitStatus() const
Gets the return code from the exited child fork.
Definition Process.h:497
Native os handle to a process identifier.
Definition Process.h:47
Definition Process.h:89
bool windowsHide
[Windows] Hides child process window (default == Process::isWindowsConsoleSubsystem)
Definition Process.h:90
Definition Process.h:182
Definition Process.h:180
StdIn(GrowableBuffer< Span< const char > > span)
Fills standard input with content of a Span.
Definition Process.h:196
StdIn(GrowableBuffer< Inherit >)
Inherits child process Input from parent process.
Definition Process.h:185
StdIn(GrowableBuffer< const char[N]> &item)
Fills standard input with content of a C-String.
Definition Process.h:190
StdIn(GrowableBuffer< StringSpan > string)
Fills standard input with content of a StringSpan.
Definition Process.h:193
Definition Process.h:157
Definition Process.h:158
Definition Process.h:155
StdOut(GrowableBuffer< Span< char > > &span)
Read the process standard output/error into the given Span.
Definition Process.h:170
StdOut(GrowableBuffer< StdOut::Inherit >)
Inherits child process standard output/error (child process will print into parent process console)
Definition Process.h:165
StdOut(GrowableBuffer< StdOut::Ignore >)
Ignores child process standard output/error (child process output will be silenced)
Definition Process.h:162
Definition Process.h:124
Definition Process.h:95
StdStream(GrowableBuffer< FileDescriptor > &file)
Redirects child process standard output/error to a given file descriptor.
Definition Process.h:104
StdStream(IGrowableBuffer &destination)
Read the process standard output/error into the given String / Buffer.
Definition Process.h:97
Execute a child process with standard file descriptors redirection.
Definition Process.h:85
int32_t getExitStatus() const
gets the return code from the exited child process (valid only after exec or waitForExitSync)
Definition Process.h:243
Result exec(Span< const StringSpan > cmd, Out &&stdOut=Out(), In &&stdIn=In(), Err &&stdErr=Err())
Executes a child process with the given arguments, waiting (blocking) until it's fully finished.
Definition Process.h:236
Result setEnvironment(StringSpan environmentVariable, StringSpan value)
Sets the environment variable for the newly spawned child process.
Process(Span< native_char_t > commandMemory={}, Span< native_char_t > environmentMemory={})
Constructs a Process object passing (optional) memory storage for command and environment variables.
Definition Process.h:266
Options options
Options for the child process (hide console window etc.)
Definition Process.h:204
static bool isWindowsEmulatedProcess()
Returns true if we're emulating x64 on ARM64 or the inverse on Windows.
void inheritParentEnvironmentVariables(bool inherit)
Controls if the newly spawned child process will inherit parent process environment variables.
Definition Process.h:249
ProcessID processID
ID of the process (can be the same as handle on Posix)
Definition Process.h:203
static bool isWindowsConsoleSubsystem()
Returns true only under Windows if executable is compiled with /SUBSYSTEM:Console
Result launch(Span< const StringSpan > cmd, Out &&stdOut=Out(), In &&stdIn=In(), Err &&stdErr=Err())
Launch child process with the given arguments.
Definition Process.h:219
static size_t getNumberOfProcessors()
Returns number of (virtual) processors available.
Result setWorkingDirectory(StringSpan processWorkingDirectory)
Sets the starting working directory of the process that will be launched / executed.
Result waitForExitSync()
Waits (blocking) for process to exit after launch. It can only be called if Process::launch succeeded...