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 {
92 Options();
93 };
94
95 struct StdStream
96 {
98 StdStream(IGrowableBuffer& destination)
99 {
100 growableBuffer = &destination;
101 operation = Operation::GrowableBuffer;
102 }
103
105 StdStream(GrowableBuffer<FileDescriptor>& file)
106 {
107 operation = Operation::FileDescriptor;
108 (void)file.content.get(fileDescriptor, Result::Error("Invalid redirection file descriptor"));
109 file.content.detach();
110 }
111
112 StdStream(GrowableBuffer<PipeDescriptor>& pipe)
113 {
114 operation = Operation::ExternalPipe;
115 pipeDescriptor = &pipe.content;
116 }
117
118 StdStream(const StdStream&) = delete;
119 StdStream(StdStream&&) = delete;
120 StdStream& operator=(const StdStream&) = delete;
121 StdStream& operator=(StdStream&&) = delete;
122
123 protected:
125 {
126 };
127 StdStream() = default;
128 StdStream(AlreadySetup) { operation = Operation::AlreadySetup; }
129 friend struct Process;
130 friend struct ProcessChain;
131
132 enum class Operation
133 {
134 AlreadySetup,
135 Inherit,
136 Ignore,
137 ExternalPipe,
139 GrowableBuffer,
140 WritableSpan,
141 ReadableSpan
142 };
143 Operation operation = Operation::Inherit;
144
145 Span<const char> readableSpan;
146 Span<char>* writableSpan = nullptr;
147
148 IGrowableBuffer* growableBuffer = nullptr;
149
150 FileDescriptor::Handle fileDescriptor;
151
152 PipeDescriptor* pipeDescriptor;
153 };
154
155 struct StdOut : public StdStream
156 {
157 // clang-format off
158 struct Ignore{};
159 struct Inherit{};
160
161
163 StdOut(GrowableBuffer<StdOut::Ignore>) { operation = Operation::Ignore; }
164
166 StdOut(GrowableBuffer<StdOut::Inherit>) { operation = Operation::Inherit; }
167 StdOut(GrowableBuffer<StdOut>) { operation = Operation::Inherit; }
168 StdOut() { operation = Operation::Inherit; }
169
171 StdOut(GrowableBuffer<Span<char>>& span) { operation = Operation::WritableSpan; writableSpan = &span.content; span.setContentInDestructor = false; }
172
173 using StdStream::StdStream;
174 friend struct ProcessChain;
175 // clang-format on
176 };
177
178 using StdErr = StdOut;
179
180 struct StdIn : public StdStream
181 {
182 // clang-format off
183 struct Inherit{};
184
186 StdIn(GrowableBuffer<Inherit>) { operation = Operation::Inherit; }
187 StdIn(GrowableBuffer<StdIn>) { operation = Operation::Inherit; }
188 StdIn() { operation = Operation::Inherit; }
189
191 template <int N> StdIn(GrowableBuffer<const char [N]>& item) { operation = Operation::ReadableSpan; readableSpan = {item.content, N - 1}; }
192
194 StdIn(GrowableBuffer<StringSpan> string) { operation = Operation::ReadableSpan; readableSpan = string.content.toCharSpan();}
195
197 StdIn(GrowableBuffer<Span<const char>> span) { operation = Operation::ReadableSpan; readableSpan = span.content;}
198
199 using StdStream::StdStream;
200 friend struct ProcessChain;
201 // clang-format on
202 };
203
206
207 ProcessDescriptor::Handle handle = ProcessDescriptor::Invalid;
208
211
219 template <typename Out = StdOut, typename In = StdIn, typename Err = StdErr>
220 Result launch(Span<const StringSpan> cmd, Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
221 {
222 SC_TRY(formatArguments(cmd));
223 GrowableBuffer<typename TypeTraits::RemoveReference<Out>::type> gbOut = {stdOut};
224 GrowableBuffer<typename TypeTraits::RemoveReference<In>::type> gbIn = {stdIn};
225 GrowableBuffer<typename TypeTraits::RemoveReference<Err>::type> gbErr = {stdErr};
226 return launch(StdOut(gbOut), StdIn(gbIn), StdErr(gbErr));
227 }
228
236 template <typename Out = StdOut, typename In = StdIn, typename Err = StdErr>
237 Result exec(Span<const StringSpan> cmd, Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
238 {
239 SC_TRY(launch(cmd, stdOut, stdIn, stdErr));
240 return waitForExitSync();
241 }
242
244 int32_t getExitStatus() const { return exitStatus.status; }
245
247 Result setWorkingDirectory(StringSpan processWorkingDirectory);
248
250 void inheritParentEnvironmentVariables(bool inherit) { inheritEnv = inherit; }
251
253 Result setEnvironment(StringSpan environmentVariable, StringSpan value);
254
256 [[nodiscard]] static size_t getNumberOfProcessors();
257
259 [[nodiscard]] static bool isWindowsConsoleSubsystem();
260
262 [[nodiscard]] static bool isWindowsEmulatedProcess();
263
267 Process(Span<native_char_t> commandMemory = {}, Span<native_char_t> environmentMemory = {})
268 : command({commandMemory}), environment({environmentMemory})
269 {
270 if (commandMemory.empty())
271 command = {commandStorage};
272 if (environmentMemory.empty())
273 environment = {environmentStorage};
274 }
275
276 private:
277 ProcessExitStatus exitStatus;
278
279 FileDescriptor stdInFd;
280 FileDescriptor stdOutFd;
281 FileDescriptor stdErrFd;
282
283 Result launch(const StdOut& stdOutput, const StdIn& stdInput, const StdErr& stdError);
284
285 Result formatArguments(Span<const StringSpan> cmd);
286
287 StringPath currentDirectory;
288#if SC_PLATFORM_WINDOWS
289 StringPath executablePathForLaunch;
290 bool executablePathLooksLikeFile = false;
291#endif
292
293 // On Windows command holds the concatenation of executable and arguments.
294 // On Posix command holds the concatenation of executable and arguments SEPARATED BY null-terminators (\0).
295 // This is done so that in this single buffer with no allocation (under 255) or a single allocation (above 255)
296 // we can track all arguments to be passed to execve.
297 native_char_t commandStorage[InlineCommandStorageCapacity];
298 StringSpan::NativeWritable command;
299#if !SC_PLATFORM_WINDOWS // On Posix we need to track the "sub-strings" hidden in command
300 static constexpr size_t MAX_NUM_ARGUMENTS = 64;
301 size_t commandArgumentsByteOffset[MAX_NUM_ARGUMENTS]; // Tracking length of each argument in the command string
302 size_t commandArgumentsNumber = 0; // Counts number of arguments (including executable name)
303#endif
304
305 native_char_t environmentStorage[4096 * 8];
306 StringSpan::NativeWritable environment;
307
308 static constexpr size_t MAX_NUM_ENVIRONMENT = 256;
309
310 size_t environmentByteOffset[MAX_NUM_ENVIRONMENT]; // Tracking length of each environment variable
311 size_t environmentNumber = 0; // Counts number of environment variable
312
313 bool inheritEnv = true;
314
315 friend struct ProcessChain;
316 ProcessChain* parent = nullptr;
317
318 Process* next = nullptr;
319 Process* prev = nullptr;
320 struct Internal;
321 struct InternalFork;
322 friend struct ProcessFork;
323 Result launchImplementation();
324 Result launchForkChild(PipeDescriptor& pipe);
325 Result launchForkParent(PipeDescriptor& pipe, const void* previousSignals);
326};
327
344struct SC_PROCESS_EXPORT ProcessChain
345{
346 Process::Options options;
351 Result pipe(Process& process, const Span<const StringSpan> cmd);
352
356 template <typename Out = Process::StdOut, typename In = Process::StdIn, typename Err = Process::StdErr>
357 Result launch(Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
358 {
359 GrowableBuffer<typename TypeTraits::RemoveReference<Out>::type> gbOut = {stdOut};
360 GrowableBuffer<typename TypeTraits::RemoveReference<In>::type> gbIn = {stdIn};
361 GrowableBuffer<typename TypeTraits::RemoveReference<Err>::type> gbErr = {stdErr};
362 return internalLaunch(gbOut, gbIn, gbErr);
363 }
364
368
371 template <typename Out = Process::StdOut, typename In = Process::StdIn, typename Err = Process::StdErr>
372 Result exec(Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
373 {
374 SC_TRY(launch(stdOut, stdIn, stdErr));
375 return waitForExitSync();
376 }
377
378 private:
379 Result internalLaunch(const Process::StdOut& stdOut, const Process::StdIn& stdIn, const Process::StdErr& stdErr);
380 // Trimmed duplicate of IntrusiveDoubleLinkedList<T>
381 struct ProcessLinkedList
382 {
383 Process* back = nullptr; // has no next
384 Process* front = nullptr; // has no prev
385
386 [[nodiscard]] bool isEmpty() const { return front == nullptr; }
387
388 void clear();
389 void queueBack(Process& process);
390 };
391 ProcessLinkedList processes;
392};
393
399{
402
403 ProcessEnvironment(const ProcessEnvironment&) = delete;
405 ProcessEnvironment& operator=(const ProcessEnvironment&) = delete;
406 ProcessEnvironment& operator=(ProcessEnvironment&&) = delete;
407
409 [[nodiscard]] size_t size() const { return numberOfEnvironment; }
410
415 [[nodiscard]] bool get(size_t index, StringSpan& name, StringSpan& value) const;
416
421 [[nodiscard]] bool contains(StringSpan variableName, size_t* index = nullptr) const;
422
427 [[nodiscard]] bool get(StringSpan variableName, StringSpan& value) const;
428
429 private:
430 size_t numberOfEnvironment = 0;
431#if SC_PLATFORM_WINDOWS
432 static constexpr size_t MAX_ENVIRONMENTS = 256;
433
434 StringSpan envStrings[MAX_ENVIRONMENTS];
435 wchar_t* environment = nullptr;
436#else
437 char** environment = nullptr;
438#endif
439};
440
466struct SC_PROCESS_EXPORT ProcessFork
467{
468 ProcessFork();
469 ~ProcessFork();
470 ProcessFork(const ProcessFork&) = delete;
471 ProcessFork* operator=(const ProcessFork&) = delete;
472
473 enum Side
474 {
477 };
478
480 [[nodiscard]] Side getSide() const { return side; }
481
482 enum State
483 {
486 };
487
489 Result fork(State state);
490
493
495 Result waitForChild();
496
498 int32_t getExitStatus() const { return exitStatus.status; }
499
502
505
506 private:
507 Side side = ForkParent;
508#if SC_PLATFORM_WINDOWS
509 ProcessDescriptor::Handle processHandle = ProcessDescriptor::Invalid;
510 ProcessDescriptor::Handle threadHandle = ProcessDescriptor::Invalid;
511#else
512 ProcessID processID;
513#endif
514 ProcessExitStatus exitStatus;
515
516 PipeDescriptor parentToFork;
517 PipeDescriptor forkToParent;
518};
520
521} // 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:345
Result launch(Out &&stdOut=Out(), In &&stdIn=In(), Err &&stdErr=Err())
Launch the entire chain of processes.
Definition Process.h:357
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:372
Result waitForExitSync()
Waits (blocking) for entire process chain to exit.
Definition Process.h:28
Reads current process environment variables.
Definition Process.h:399
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:409
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:467
Result waitForChild()
Waits for child fork to finish execution.
State
Definition Process.h:483
@ Suspended
Start the forked process suspended (resume it with ProcessFork::resumeChildFork)
Definition Process.h:484
@ Immediate
Start the forked process immediately.
Definition Process.h:485
Side getSide() const
Obtain process parent / fork side.
Definition Process.h:480
FileDescriptor & getWritePipe()
Gets the descriptor to "write" something to the other side.
Side
Definition Process.h:474
@ ForkParent
Parent side of the fork.
Definition Process.h:475
@ ForkChild
Child side of the fork.
Definition Process.h:476
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:498
Native os handle to a process identifier.
Definition Process.h:47
Definition Process.h:89
bool windowsHide
[Windows] Hides child process window
Definition Process.h:90
bool windowsCreateNewProcessGroup
[Windows] Creates a console process group rooted at the child
Definition Process.h:91
Definition Process.h:183
Definition Process.h:181
StdIn(GrowableBuffer< Span< const char > > span)
Fills standard input with content of a Span.
Definition Process.h:197
StdIn(GrowableBuffer< Inherit >)
Inherits child process Input from parent process.
Definition Process.h:186
StdIn(GrowableBuffer< const char[N]> &item)
Fills standard input with content of a C-String.
Definition Process.h:191
StdIn(GrowableBuffer< StringSpan > string)
Fills standard input with content of a StringSpan.
Definition Process.h:194
Definition Process.h:158
Definition Process.h:159
Definition Process.h:156
StdOut(GrowableBuffer< Span< char > > &span)
Read the process standard output/error into the given Span.
Definition Process.h:171
StdOut(GrowableBuffer< StdOut::Inherit >)
Inherits child process standard output/error (child process will print into parent process console)
Definition Process.h:166
StdOut(GrowableBuffer< StdOut::Ignore >)
Ignores child process standard output/error (child process output will be silenced)
Definition Process.h:163
Definition Process.h:125
Definition Process.h:96
StdStream(GrowableBuffer< FileDescriptor > &file)
Redirects child process standard output/error to a given file descriptor.
Definition Process.h:105
StdStream(IGrowableBuffer &destination)
Read the process standard output/error into the given String / Buffer.
Definition Process.h:98
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:244
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:237
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:267
Options options
Options for the child process (hide console window etc.)
Definition Process.h:205
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:250
ProcessID processID
ID of the process (can be the same as handle on Posix)
Definition Process.h:204
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:220
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...