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 "../Foundation/Compiler.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 "../File/File.h"
12#include "../Foundation/AlignedStorage.h"
13#include "../Foundation/Internal/IGrowableBuffer.h"
14#include "../Foundation/StringPath.h"
15
16namespace SC
17{
18struct SC_PROCESS_EXPORT ProcessChain;
19
20struct SC_PROCESS_EXPORT ProcessDescriptor
21{
22 using Handle = detail::FileDescriptorDefinition::Handle;
23 static constexpr auto Invalid = detail::FileDescriptorDefinition::Invalid;
24};
25
28{
29 int32_t status = -1;
30};
31
34
37
40{
41 int32_t pid = 0;
42};
43
76
77struct SC_PROCESS_EXPORT Process
78{
79 static constexpr size_t InlineCommandStorageCapacity = StringPath::MaxPath + 1024;
80
81 struct SC_PROCESS_EXPORT Options
82 {
84 Options();
85 };
86
87 struct StdStream
88 {
90 StdStream(IGrowableBuffer& destination)
91 {
92 growableBuffer = &destination;
93 operation = Operation::GrowableBuffer;
94 }
95
97 StdStream(GrowableBuffer<FileDescriptor>& file)
98 {
99 operation = Operation::FileDescriptor;
100 (void)file.content.get(fileDescriptor, Result::Error("Invalid redirection file descriptor"));
101 file.content.detach();
102 }
103
104 StdStream(GrowableBuffer<PipeDescriptor>& pipe)
105 {
106 operation = Operation::ExternalPipe;
107 pipeDescriptor = &pipe.content;
108 }
109
110 StdStream(const StdStream&) = delete;
111 StdStream(StdStream&&) = delete;
112 StdStream& operator=(const StdStream&) = delete;
113 StdStream& operator=(StdStream&&) = delete;
114
115 protected:
117 {
118 };
119 StdStream() = default;
120 StdStream(AlreadySetup) { operation = Operation::AlreadySetup; }
121 friend struct Process;
122 friend struct ProcessChain;
123
124 enum class Operation
125 {
126 AlreadySetup,
127 Inherit,
128 Ignore,
129 ExternalPipe,
131 GrowableBuffer,
132 WritableSpan,
133 ReadableSpan
134 };
135 Operation operation = Operation::Inherit;
136
137 Span<const char> readableSpan;
138 Span<char>* writableSpan = nullptr;
139
140 IGrowableBuffer* growableBuffer = nullptr;
141
142 FileDescriptor::Handle fileDescriptor;
143
144 PipeDescriptor* pipeDescriptor;
145 };
146
147 struct StdOut : public StdStream
148 {
149 // clang-format off
150 struct Ignore{};
151 struct Inherit{};
152
153
155 StdOut(GrowableBuffer<StdOut::Ignore>) { operation = Operation::Ignore; }
156
158 StdOut(GrowableBuffer<StdOut::Inherit>) { operation = Operation::Inherit; }
159 StdOut(GrowableBuffer<StdOut>) { operation = Operation::Inherit; }
160 StdOut() { operation = Operation::Inherit; }
161
163 StdOut(GrowableBuffer<Span<char>>& span) { operation = Operation::WritableSpan; writableSpan = &span.content; span.setContentInDestructor = false; }
164
165 using StdStream::StdStream;
166 friend struct ProcessChain;
167 // clang-format on
168 };
169
170 using StdErr = StdOut;
171
172 struct StdIn : public StdStream
173 {
174 // clang-format off
175 struct Inherit{};
176
178 StdIn(GrowableBuffer<Inherit>) { operation = Operation::Inherit; }
179 StdIn(GrowableBuffer<StdIn>) { operation = Operation::Inherit; }
180 StdIn() { operation = Operation::Inherit; }
181
183 template <int N> StdIn(GrowableBuffer<const char [N]>& item) { operation = Operation::ReadableSpan; readableSpan = {item.content, N - 1}; }
184
186 StdIn(GrowableBuffer<StringSpan> string) { operation = Operation::ReadableSpan; readableSpan = string.content.toCharSpan();}
187
189 StdIn(GrowableBuffer<Span<const char>> span) { operation = Operation::ReadableSpan; readableSpan = span.content;}
190
191 using StdStream::StdStream;
192 friend struct ProcessChain;
193 // clang-format on
194 };
195
198
199 ProcessDescriptor::Handle handle = ProcessDescriptor::Invalid;
200
203
211 template <typename Out = StdOut, typename In = StdIn, typename Err = StdErr>
212 Result launch(Span<const StringSpan> cmd, Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
213 {
214 SC_TRY(formatArguments(cmd));
215 GrowableBuffer<typename TypeTraits::RemoveReference<Out>::type> gbOut = {stdOut};
216 GrowableBuffer<typename TypeTraits::RemoveReference<In>::type> gbIn = {stdIn};
217 GrowableBuffer<typename TypeTraits::RemoveReference<Err>::type> gbErr = {stdErr};
218 return launch(StdOut(gbOut), StdIn(gbIn), StdErr(gbErr));
219 }
220
228 template <typename Out = StdOut, typename In = StdIn, typename Err = StdErr>
229 Result exec(Span<const StringSpan> cmd, Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
230 {
231 SC_TRY(launch(cmd, stdOut, stdIn, stdErr));
232 return waitForExitSync();
233 }
234
236 int32_t getExitStatus() const { return exitStatus.status; }
237
239 Result setWorkingDirectory(StringSpan processWorkingDirectory);
240
242 void inheritParentEnvironmentVariables(bool inherit) { inheritEnv = inherit; }
243
245 Result setEnvironment(StringSpan environmentVariable, StringSpan value);
246
248 [[nodiscard]] static size_t getNumberOfProcessors();
249
251 [[nodiscard]] static bool isWindowsConsoleSubsystem();
252
254 [[nodiscard]] static bool isWindowsEmulatedProcess();
255
259 Process(Span<native_char_t> commandMemory = {}, Span<native_char_t> environmentMemory = {})
260 : command({commandMemory}), environment({environmentMemory})
261 {
262 if (commandMemory.empty())
263 command = {commandStorage};
264 if (environmentMemory.empty())
265 environment = {environmentStorage};
266 }
267
268 private:
269 ProcessExitStatus exitStatus;
270
271 FileDescriptor stdInFd;
272 FileDescriptor stdOutFd;
273 FileDescriptor stdErrFd;
274
275 Result launch(const StdOut& stdOutput, const StdIn& stdInput, const StdErr& stdError);
276
277 Result formatArguments(Span<const StringSpan> cmd);
278
279 StringPath currentDirectory;
280
281 // On Windows command holds the concatenation of executable and arguments.
282 // On Posix command holds the concatenation of executable and arguments SEPARATED BY null-terminators (\0).
283 // This is done so that in this single buffer with no allocation (under 255) or a single allocation (above 255)
284 // we can track all arguments to be passed to execve.
285 native_char_t commandStorage[InlineCommandStorageCapacity];
286 StringSpan::NativeWritable command;
287#if !SC_PLATFORM_WINDOWS // On Posix we need to track the "sub-strings" hidden in command
288 static constexpr size_t MAX_NUM_ARGUMENTS = 64;
289 size_t commandArgumentsByteOffset[MAX_NUM_ARGUMENTS]; // Tracking length of each argument in the command string
290 size_t commandArgumentsNumber = 0; // Counts number of arguments (including executable name)
291#endif
292
293 native_char_t environmentStorage[4096 * 4]; // 16K (w)chars of storage for environment variables
294 StringSpan::NativeWritable environment;
295
296 static constexpr size_t MAX_NUM_ENVIRONMENT = 256;
297
298 size_t environmentByteOffset[MAX_NUM_ENVIRONMENT]; // Tracking length of each environment variable
299 size_t environmentNumber = 0; // Counts number of environment variable
300
301 bool inheritEnv = true;
302
303 friend struct ProcessChain;
304 ProcessChain* parent = nullptr;
305
306 Process* next = nullptr;
307 Process* prev = nullptr;
308 struct Internal;
309 struct InternalFork;
310 friend struct ProcessFork;
311 Result launchImplementation();
312 Result launchForkChild(PipeDescriptor& pipe);
313 Result launchForkParent(PipeDescriptor& pipe, const void* previousSignals);
314};
315
332struct SC_PROCESS_EXPORT ProcessChain
333{
334 Process::Options options;
340
344 template <typename Out = Process::StdOut, typename In = Process::StdIn, typename Err = Process::StdErr>
345 Result launch(Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
346 {
347 GrowableBuffer<typename TypeTraits::RemoveReference<Out>::type> gbOut = {stdOut};
348 GrowableBuffer<typename TypeTraits::RemoveReference<In>::type> gbIn = {stdIn};
349 GrowableBuffer<typename TypeTraits::RemoveReference<Err>::type> gbErr = {stdErr};
350 return internalLaunch(gbOut, gbIn, gbErr);
351 }
352
356
359 template <typename Out = Process::StdOut, typename In = Process::StdIn, typename Err = Process::StdErr>
360 Result exec(Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
361 {
362 SC_TRY(launch(stdOut, stdIn, stdErr));
363 return waitForExitSync();
364 }
365
366 private:
367 Result internalLaunch(const Process::StdOut& stdOut, const Process::StdIn& stdIn, const Process::StdErr& stdErr);
368 // Trimmed duplicate of IntrusiveDoubleLinkedList<T>
369 struct ProcessLinkedList
370 {
371 Process* back = nullptr; // has no next
372 Process* front = nullptr; // has no prev
373
374 [[nodiscard]] bool isEmpty() const { return front == nullptr; }
375
376 void clear();
377 void queueBack(Process& process);
378 };
379 ProcessLinkedList processes;
380};
381
387{
390
391 ProcessEnvironment(const ProcessEnvironment&) = delete;
393 ProcessEnvironment& operator=(const ProcessEnvironment&) = delete;
394 ProcessEnvironment& operator=(ProcessEnvironment&&) = delete;
395
397 [[nodiscard]] size_t size() const { return numberOfEnvironment; }
398
403 [[nodiscard]] bool get(size_t index, StringSpan& name, StringSpan& value) const;
404
409 [[nodiscard]] bool contains(StringSpan variableName, size_t* index = nullptr) const;
410
415 [[nodiscard]] bool get(StringSpan variableName, StringSpan& value) const;
416
417 private:
418 size_t numberOfEnvironment = 0;
419#if SC_PLATFORM_WINDOWS
420 static constexpr size_t MAX_ENVIRONMENTS = 256;
421
422 StringSpan envStrings[MAX_ENVIRONMENTS];
423 wchar_t* environment = nullptr;
424#else
425 char** environment = nullptr;
426#endif
427};
428
454struct SC_PROCESS_EXPORT ProcessFork
455{
456 ProcessFork();
457 ~ProcessFork();
458 ProcessFork(const ProcessFork&) = delete;
459 ProcessFork* operator=(const ProcessFork&) = delete;
460
461 enum Side
462 {
465 };
466
468 [[nodiscard]] Side getSide() const { return side; }
469
470 enum State
471 {
474 };
475
478
481
484
486 int32_t getExitStatus() const { return exitStatus.status; }
487
490
493
494 private:
495 Side side = ForkParent;
496#if SC_PLATFORM_WINDOWS
497 ProcessDescriptor::Handle processHandle = ProcessDescriptor::Invalid;
498 ProcessDescriptor::Handle threadHandle = ProcessDescriptor::Invalid;
499#else
500 ProcessID processID;
501#endif
502 ProcessExitStatus exitStatus;
503
504 PipeDescriptor parentToFork;
505 PipeDescriptor forkToParent;
506};
508
509} // namespace SC
int int32_t
Platform independent (4) bytes signed int.
Definition PrimitiveTypes.h:37
char native_char_t
The native char for the platform (wchar_t (4 bytes) on Windows, char (1 byte) everywhere else )
Definition PrimitiveTypes.h:25
#define SC_TRY(expression)
Checks the value of the given expression and if failed, returns this value to caller.
Definition Result.h:49
[UniqueHandleDeclaration2Snippet]
Definition File.h:128
Read / Write pipe (Process stdin/stdout and IPC communication)
Definition File.h:300
Execute multiple child processes chaining input / output between them.
Definition Process.h:333
Result launch(Out &&stdOut=Out(), In &&stdIn=In(), Err &&stdErr=Err())
Launch the entire chain of processes.
Definition Process.h:345
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:360
Result waitForExitSync()
Waits (blocking) for entire process chain to exit.
Definition Process.h:21
Reads current process environment variables.
Definition Process.h:387
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:397
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:28
Forks current process exiting child at end of process A fork duplicates a parent process execution st...
Definition Process.h:455
Result waitForChild()
Waits for child fork to finish execution.
State
Definition Process.h:471
@ Suspended
Start the forked process suspended (resume it with ProcessFork::resumeChildFork)
Definition Process.h:472
@ Immediate
Start the forked process immediately.
Definition Process.h:473
Side getSide() const
Obtain process parent / fork side.
Definition Process.h:468
FileDescriptor & getWritePipe()
Gets the descriptor to "write" something to the other side.
Side
Definition Process.h:462
@ ForkParent
Parent side of the fork.
Definition Process.h:463
@ ForkChild
Child side of the fork.
Definition Process.h:464
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:486
Native os handle to a process identifier.
Definition Process.h:40
Definition Process.h:82
bool windowsHide
[Windows] Hides child process window (default == Process::isWindowsConsoleSubsystem)
Definition Process.h:83
Definition Process.h:175
Definition Process.h:173
StdIn(GrowableBuffer< Span< const char > > span)
Fills standard input with content of a Span.
Definition Process.h:189
StdIn(GrowableBuffer< Inherit >)
Inherits child process Input from parent process.
Definition Process.h:178
StdIn(GrowableBuffer< const char[N]> &item)
Fills standard input with content of a C-String.
Definition Process.h:183
StdIn(GrowableBuffer< StringSpan > string)
Fills standard input with content of a StringSpan.
Definition Process.h:186
Definition Process.h:150
Definition Process.h:151
Definition Process.h:148
StdOut(GrowableBuffer< Span< char > > &span)
Read the process standard output/error into the given Span.
Definition Process.h:163
StdOut(GrowableBuffer< StdOut::Inherit >)
Inherits child process standard output/error (child process will print into parent process console)
Definition Process.h:158
StdOut(GrowableBuffer< StdOut::Ignore >)
Ignores child process standard output/error (child process output will be silenced)
Definition Process.h:155
Definition Process.h:117
Definition Process.h:88
StdStream(GrowableBuffer< FileDescriptor > &file)
Redirects child process standard output/error to a given file descriptor.
Definition Process.h:97
StdStream(IGrowableBuffer &destination)
Read the process standard output/error into the given String / Buffer.
Definition Process.h:90
Execute a child process with standard file descriptors redirection.
Definition Process.h:78
int32_t getExitStatus() const
gets the return code from the exited child process (valid only after exec or waitForExitSync)
Definition Process.h:236
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:229
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:259
Options options
Options for the child process (hide console window etc.)
Definition Process.h:197
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:242
ProcessID processID
ID of the process (can be the same as handle on Posix)
Definition Process.h:196
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:212
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...
An ascii string used as boolean result. SC_TRY macro forwards errors to caller.
Definition Result.h:13
View over a contiguous sequence of items (pointer + size in elements).
Definition Span.h:29
An read-only view over a string (to avoid including Strings library when parsing is not needed).
Definition StringSpan.h:37