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#include "ProcessError.h"
17
18namespace SC
19{
20SC_DECLARE_ASSERT_PROVIDER(ProcessAssert, SC_PROCESS_EXPORT);
21
22#define SC_PROCESS_ASSERT_RELEASE(e) SC_ASSERT_PROVIDER_RELEASE(SC::ProcessAssert, e)
23#define SC_PROCESS_ASSERT_DEBUG(e) SC_ASSERT_PROVIDER_DEBUG(SC::ProcessAssert, e)
24#define SC_PROCESS_TRUST_RESULT(expression) SC_PROCESS_ASSERT_RELEASE(expression)
25
26struct SC_PROCESS_EXPORT ProcessChain;
27
28struct SC_PROCESS_EXPORT ProcessDescriptor
29{
30 using Handle = detail::FileDescriptorDefinition::Handle;
31 static constexpr auto Invalid = detail::FileDescriptorDefinition::Invalid;
32};
33
36{
37 int32_t status = -1;
38};
39
42
45
48{
49 int32_t pid = 0;
50};
51
84
85struct SC_PROCESS_EXPORT Process
86{
87 static constexpr size_t InlineCommandStorageCapacity = StringPath::MaxPath + 1024;
88
89 struct SC_PROCESS_EXPORT Options
90 {
93 Options();
94 };
95
96 struct StdStream
97 {
99 StdStream(IGrowableBuffer& destination)
100 {
101 growableBuffer = &destination;
102 operation = Operation::GrowableBuffer;
103 }
104
106 StdStream(GrowableBuffer<FileDescriptor>& file)
107 {
108 operation = Operation::FileDescriptor;
109 (void)file.content.get(fileDescriptor,
110 Result::Error(ProcessResultCategory, ProcessError::InvalidOutputRedirection));
111 file.content.detach();
112 }
113
114 StdStream(GrowableBuffer<PipeDescriptor>& pipe)
115 {
116 operation = Operation::ExternalPipe;
117 pipeDescriptor = &pipe.content;
118 }
119
120 StdStream(const StdStream&) = delete;
121 StdStream(StdStream&&) = delete;
122 StdStream& operator=(const StdStream&) = delete;
123 StdStream& operator=(StdStream&&) = delete;
124
125 protected:
127 {
128 };
129 StdStream() = default;
130 StdStream(AlreadySetup) { operation = Operation::AlreadySetup; }
131 friend struct Process;
132 friend struct ProcessChain;
133
134 enum class Operation
135 {
136 AlreadySetup,
137 Inherit,
138 Ignore,
139 ExternalPipe,
141 GrowableBuffer,
142 WritableSpan,
143 ReadableSpan
144 };
145 Operation operation = Operation::Inherit;
146
147 Span<const char> readableSpan;
148 Span<char>* writableSpan = nullptr;
149
150 IGrowableBuffer* growableBuffer = nullptr;
151
152 FileDescriptor::Handle fileDescriptor = ProcessDescriptor::Invalid;
153
154 PipeDescriptor* pipeDescriptor;
155 };
156
157 struct StdOut : public StdStream
158 {
159 // clang-format off
160 struct Ignore{};
161 struct Inherit{};
162
163
165 StdOut(GrowableBuffer<StdOut::Ignore>) { operation = Operation::Ignore; }
166
168 StdOut(GrowableBuffer<StdOut::Inherit>) { operation = Operation::Inherit; }
169 StdOut(GrowableBuffer<StdOut>) { operation = Operation::Inherit; }
170 StdOut() { operation = Operation::Inherit; }
171
173 StdOut(GrowableBuffer<Span<char>>& span) { operation = Operation::WritableSpan; writableSpan = &span.content; span.setContentInDestructor = false; }
174
175 using StdStream::StdStream;
176 friend struct ProcessChain;
177 // clang-format on
178 };
179
180 using StdErr = StdOut;
181
182 struct StdIn : public StdStream
183 {
184 // clang-format off
185 struct Inherit{};
186
188 StdIn(GrowableBuffer<Inherit>) { operation = Operation::Inherit; }
189 StdIn(GrowableBuffer<StdIn>) { operation = Operation::Inherit; }
190 StdIn() { operation = Operation::Inherit; }
191
193 template <int N> StdIn(GrowableBuffer<const char [N]>& item) { operation = Operation::ReadableSpan; readableSpan = {item.content, N - 1}; }
194
196 StdIn(GrowableBuffer<StringSpan> string) { operation = Operation::ReadableSpan; readableSpan = string.content.toCharSpan();}
197
199 StdIn(GrowableBuffer<Span<const char>> span) { operation = Operation::ReadableSpan; readableSpan = span.content;}
200
201 using StdStream::StdStream;
202 friend struct ProcessChain;
203 // clang-format on
204 };
205
208
209 ProcessDescriptor::Handle handle = ProcessDescriptor::Invalid;
210
213
221 template <typename Out = StdOut, typename In = StdIn, typename Err = StdErr>
222 ResultProcess launch(Span<const StringSpan> cmd, Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
223 {
224 SC_TRY(formatArguments(cmd));
225 GrowableBuffer<typename TypeTraits::RemoveReference<Out>::type> gbOut = {stdOut};
226 GrowableBuffer<typename TypeTraits::RemoveReference<In>::type> gbIn = {stdIn};
227 GrowableBuffer<typename TypeTraits::RemoveReference<Err>::type> gbErr = {stdErr};
228 return launch(StdOut(gbOut), StdIn(gbIn), StdErr(gbErr));
229 }
230
238 template <typename Out = StdOut, typename In = StdIn, typename Err = StdErr>
239 ResultProcess exec(Span<const StringSpan> cmd, Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
240 {
241 SC_TRY(launch(cmd, stdOut, stdIn, stdErr));
242 return waitForExitSync();
243 }
244
246 int32_t getExitStatus() const { return exitStatus.status; }
247
249 ResultProcess setWorkingDirectory(StringSpan processWorkingDirectory);
250
252 void inheritParentEnvironmentVariables(bool inherit) { inheritEnv = inherit; }
253
255 ResultProcess setEnvironment(StringSpan environmentVariable, StringSpan value);
256
258 [[nodiscard]] static size_t getNumberOfProcessors();
259
261 [[nodiscard]] static bool isWindowsConsoleSubsystem();
262
264 [[nodiscard]] static bool isWindowsEmulatedProcess();
265
269 Process(Span<native_char_t> commandMemory = {}, Span<native_char_t> environmentMemory = {})
270 : command({commandMemory}), environment({environmentMemory})
271 {
272 if (commandMemory.empty())
273 command = {commandStorage};
274 if (environmentMemory.empty())
275 environment = {environmentStorage};
276 }
277
278 private:
279 ProcessExitStatus exitStatus;
280
281 FileDescriptor stdInFd;
282 FileDescriptor stdOutFd;
283 FileDescriptor stdErrFd;
284
285 ResultProcess launch(const StdOut& stdOutput, const StdIn& stdInput, const StdErr& stdError);
286
287 ResultProcess formatArguments(Span<const StringSpan> cmd);
288
289 StringPath currentDirectory;
290#if SC_PLATFORM_WINDOWS
291 StringPath executablePathForLaunch;
292 bool executablePathLooksLikeFile = false;
293#endif
294
295 // On Windows command holds the concatenation of executable and arguments.
296 // On Posix command holds the concatenation of executable and arguments SEPARATED BY null-terminators (\0).
297 // This is done so that in this single buffer with no allocation (under 255) or a single allocation (above 255)
298 // we can track all arguments to be passed to execve.
299 native_char_t commandStorage[InlineCommandStorageCapacity];
300 StringSpan::NativeWritable command;
301#if !SC_PLATFORM_WINDOWS // On Posix we need to track the "sub-strings" hidden in command
302 static constexpr size_t MAX_NUM_ARGUMENTS = 64;
303 size_t commandArgumentsByteOffset[MAX_NUM_ARGUMENTS]; // Tracking length of each argument in the command string
304 size_t commandArgumentsNumber = 0; // Counts number of arguments (including executable name)
305#endif
306
307 native_char_t environmentStorage[4096 * 8];
308 StringSpan::NativeWritable environment;
309
310 static constexpr size_t MAX_NUM_ENVIRONMENT = 256;
311
312 size_t environmentByteOffset[MAX_NUM_ENVIRONMENT]; // Tracking length of each environment variable
313 size_t environmentNumber = 0; // Counts number of environment variable
314
315 bool inheritEnv = true;
316
317 friend struct ProcessChain;
318 ProcessChain* parent = nullptr;
319
320 Process* next = nullptr;
321 Process* prev = nullptr;
322 struct Internal;
323 struct InternalFork;
324 friend struct ProcessFork;
325 ResultProcess launchImplementation();
326 ResultProcess launchForkChild(PipeDescriptor& pipe);
327 ResultProcess launchForkParent(PipeDescriptor& pipe, const void* previousSignals);
328};
329
346struct SC_PROCESS_EXPORT ProcessChain
347{
348 Process::Options options;
353 ResultProcess pipe(Process& process, const Span<const StringSpan> cmd);
354
358 template <typename Out = Process::StdOut, typename In = Process::StdIn, typename Err = Process::StdErr>
359 ResultProcess launch(Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
360 {
361 GrowableBuffer<typename TypeTraits::RemoveReference<Out>::type> gbOut = {stdOut};
362 GrowableBuffer<typename TypeTraits::RemoveReference<In>::type> gbIn = {stdIn};
363 GrowableBuffer<typename TypeTraits::RemoveReference<Err>::type> gbErr = {stdErr};
364 return internalLaunch(gbOut, gbIn, gbErr);
365 }
366
370
373 template <typename Out = Process::StdOut, typename In = Process::StdIn, typename Err = Process::StdErr>
374 ResultProcess exec(Out&& stdOut = Out(), In&& stdIn = In(), Err&& stdErr = Err())
375 {
376 SC_TRY(launch(stdOut, stdIn, stdErr));
377 return waitForExitSync();
378 }
379
380 private:
381 ResultProcess internalLaunch(const Process::StdOut& stdOut, const Process::StdIn& stdIn,
382 const Process::StdErr& stdErr);
383 // Trimmed duplicate of IntrusiveDoubleLinkedList<T>
384 struct ProcessLinkedList
385 {
386 Process* back = nullptr; // has no next
387 Process* front = nullptr; // has no prev
388
389 [[nodiscard]] bool isEmpty() const { return front == nullptr; }
390
391 void clear();
392 void queueBack(Process& process);
393 };
394 ProcessLinkedList processes;
395};
396
402{
405
406 ProcessEnvironment(const ProcessEnvironment&) = delete;
408 ProcessEnvironment& operator=(const ProcessEnvironment&) = delete;
409 ProcessEnvironment& operator=(ProcessEnvironment&&) = delete;
410
412 [[nodiscard]] size_t size() const { return numberOfEnvironment; }
413
418 [[nodiscard]] bool get(size_t index, StringSpan& name, StringSpan& value) const;
419
424 [[nodiscard]] bool contains(StringSpan variableName, size_t* index = nullptr) const;
425
430 [[nodiscard]] bool get(StringSpan variableName, StringSpan& value) const;
431
432 private:
433 size_t numberOfEnvironment = 0;
434#if SC_PLATFORM_WINDOWS
435 static constexpr size_t MAX_ENVIRONMENTS = 256;
436
437 StringSpan envStrings[MAX_ENVIRONMENTS];
438 wchar_t* environment = nullptr;
439#else
440 char** environment = nullptr;
441#endif
442};
443
469struct SC_PROCESS_EXPORT ProcessFork
470{
471 ProcessFork();
472 ~ProcessFork();
473 ProcessFork(const ProcessFork&) = delete;
474 ProcessFork* operator=(const ProcessFork&) = delete;
475
476 enum Side
477 {
480 };
481
483 [[nodiscard]] Side getSide() const { return side; }
484
485 enum State
486 {
489 };
490
493
496
499
501 int32_t getExitStatus() const { return exitStatus.status; }
502
505
508
509 private:
510 Side side = ForkParent;
511#if SC_PLATFORM_WINDOWS
512 ProcessDescriptor::Handle processHandle = ProcessDescriptor::Invalid;
513 ProcessDescriptor::Handle threadHandle = ProcessDescriptor::Invalid;
514#else
515 ProcessID processID;
516#endif
517 ProcessExitStatus exitStatus;
518
519 PipeDescriptor parentToFork;
520 PipeDescriptor forkToParent;
521};
523
524} // 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:347
ResultProcess launch(Out &&stdOut=Out(), In &&stdIn=In(), Err &&stdErr=Err())
Launch the entire chain of processes.
Definition Process.h:359
ResultProcess waitForExitSync()
Waits (blocking) for entire process chain to exit.
ResultProcess pipe(Process &process, const Span< const StringSpan > cmd)
Add a process to the chain, with given arguments.
ResultProcess 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:374
Definition Process.h:29
Reads current process environment variables.
Definition Process.h:402
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:412
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:36
Forks current process exiting child at end of process A fork duplicates a parent process execution st...
Definition Process.h:470
ResultProcess waitForChild()
Waits for child fork to finish execution.
State
Definition Process.h:486
@ Suspended
Start the forked process suspended (resume it with ProcessFork::resumeChildFork)
Definition Process.h:487
@ Immediate
Start the forked process immediately.
Definition Process.h:488
ResultProcess fork(State state)
Forks current process (use ForkProcess::getType to know the side)
Side getSide() const
Obtain process parent / fork side.
Definition Process.h:483
FileDescriptor & getWritePipe()
Gets the descriptor to "write" something to the other side.
Side
Definition Process.h:477
@ ForkParent
Parent side of the fork.
Definition Process.h:478
@ ForkChild
Child side of the fork.
Definition Process.h:479
FileDescriptor & getReadPipe()
Gets the descriptor to "read" something from the other side.
ResultProcess resumeChildFork()
Sends 1 byte on parentToFork to resume State::Paused child fork.
int32_t getExitStatus() const
Gets the return code from the exited child fork.
Definition Process.h:501
Native os handle to a process identifier.
Definition Process.h:48
Definition Process.h:90
bool windowsHide
[Windows] Hides child process window
Definition Process.h:91
bool windowsCreateNewProcessGroup
[Windows] Creates a console process group rooted at the child
Definition Process.h:92
Definition Process.h:185
Definition Process.h:183
StdIn(GrowableBuffer< Span< const char > > span)
Fills standard input with content of a Span.
Definition Process.h:199
StdIn(GrowableBuffer< Inherit >)
Inherits child process Input from parent process.
Definition Process.h:188
StdIn(GrowableBuffer< const char[N]> &item)
Fills standard input with content of a C-String.
Definition Process.h:193
StdIn(GrowableBuffer< StringSpan > string)
Fills standard input with content of a StringSpan.
Definition Process.h:196
Definition Process.h:160
Definition Process.h:161
Definition Process.h:158
StdOut(GrowableBuffer< Span< char > > &span)
Read the process standard output/error into the given Span.
Definition Process.h:173
StdOut(GrowableBuffer< StdOut::Inherit >)
Inherits child process standard output/error (child process will print into parent process console)
Definition Process.h:168
StdOut(GrowableBuffer< StdOut::Ignore >)
Ignores child process standard output/error (child process output will be silenced)
Definition Process.h:165
Definition Process.h:127
Definition Process.h:97
StdStream(GrowableBuffer< FileDescriptor > &file)
Redirects child process standard output/error to a given file descriptor.
Definition Process.h:106
StdStream(IGrowableBuffer &destination)
Read the process standard output/error into the given String / Buffer.
Definition Process.h:99
Execute a child process with standard file descriptors redirection.
Definition Process.h:86
int32_t getExitStatus() const
gets the return code from the exited child process (valid only after exec or waitForExitSync)
Definition Process.h:246
ResultProcess 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:239
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:269
Options options
Options for the child process (hide console window etc.)
Definition Process.h:207
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:252
ResultProcess launch(Span< const StringSpan > cmd, Out &&stdOut=Out(), In &&stdIn=In(), Err &&stdErr=Err())
Launch child process with the given arguments.
Definition Process.h:222
ResultProcess setWorkingDirectory(StringSpan processWorkingDirectory)
Sets the starting working directory of the process that will be launched / executed.
ProcessID processID
ID of the process (can be the same as handle on Posix)
Definition Process.h:206
static bool isWindowsConsoleSubsystem()
Returns true only under Windows if executable is compiled with /SUBSYSTEM:Console
static size_t getNumberOfProcessors()
Returns number of (virtual) processors available.
ResultProcess waitForExitSync()
Waits (blocking) for process to exit after launch. It can only be called if Process::launch succeeded...
ResultProcess setEnvironment(StringSpan environmentVariable, StringSpan value)
Sets the environment variable for the newly spawned child process.
Process result retaining optional backend and native platform details.
Definition ProcessError.h:62