System Services

System Information

Introduction

The free functions in erbsland::system::info provide small machine-wide queries without exposing native types. With the default flattened namespace, the same functions are available through erbsland::sys_info and el::sys_info. This deliberate alias avoids the ambiguous erbsland::info name.

operatingSystem() classifies the current host operating-system family, and cpuArchitecture() reports the native host architecture rather than an emulated executable ABI such as Rosetta or WOW.

logicalCpuCount() is an affinity-aware thread-count hint where the platform supports that query. It falls back through the platform’s active-CPU count and the standard-library hardware concurrency value and always returns at least one. The count is not a promise of dedicated cores or a representation of a container CPU quota.

OperatingSystem and CpuArchitecture are comparable smart-enum values with stable lowercase toString() representations. Unsupported values use Unknown.

Process Information

Introduction

ProcessId is an opaque, platform-independent identifier created by system APIs. Its default value is invalid, and validity only means that the value can represent a native process identifier; it does not prove that a process currently exists. Valid identifiers format as decimal text, while an invalid identifier formats as an empty string.

ProcessInfo eagerly loads one independently owned snapshot for the current process or a supplied ProcessId. Every accessor reads that snapshot without another platform query, and ordinary copies keep independent snapshot data. Call reload() or reloadOrThrow() to replace all cached fields together.

Process Lifetime and Identifier Reuse

A process can exit while information is collected. When the backend can prove that the process vanished, the result is a normal absent snapshot with exists() false. The non-throwing reload also converts unexpected lookup failures into an absent snapshot, while reloadOrThrow() reports those failures as PlatformError after replacing the previous snapshot.

Reload follows the numeric identifier. An operating system can reuse that identifier for a new process, so code that needs continuity must retain the old valid startTime() and compare it with the reloaded valid value.

auto info = erbsland::system::ProcessInfo{process.processId()};
const auto originalStart = info.startTime();

info.reload();
const bool sameProcess = info.exists() && originalStart.isValid() &&
                         info.startTime().isValid() && info.startTime() == originalStart;

Partial Access

An existing process can expose only part of its information because of platform permissions or native limitations. The non-throwing accessors return the normal invalid sentinel for an unavailable field: an empty Path, invalid ProcessId or DateTime, or empty UserId. Each throwing accessor reports the native diagnostic cached when the snapshot was loaded; it does not perform a second lookup.

The first version intentionally excludes command lines, working directories, environment data, per-process CPU architecture, and mutable resource-usage metrics.

Environment Variables

Introduction

EnvironmentVariables provides portable access to the environment of the current process. Each instance uses the native backend for the current operating system by default. A custom backend can be supplied for isolated tests or specialized hosts.

Environment entries are process-wide. Changes are visible to subsequent native lookups and to child processes that inherit the environment. Names retain the native platform’s case-sensitivity rules.

Reading Variables

Use get(name) when absence must be distinguished from an empty stored value. It returns std::nullopt for a missing variable or a failed lookup. The two-argument overload returns its explicit fallback in these cases.

getOrThrow(name) returns an empty string for a stored empty value and throws PlatformError if the variable does not exist or the native lookup fails.

Writing and Removing Variables

set() and remove() report failure through their boolean result. Their OrThrow variants preserve platform failure details in PlatformError. Removal is idempotent, so removing a variable that is already absent succeeds. Setting an empty value stores an empty value; it does not remove the entry.

Names must not be empty or contain an equals sign or embedded null byte. Values must not contain embedded null bytes. The throwing methods report these portable input errors as ParameterError.

User Lookup

Introduction

The system namespace provides application-shared services that expose small, portable wrappers around native operating-system facilities.

Native failures use immutable PlatformErrorContext values. The internal platform backends capture native details immediately and expose a portable PlatformErrorCategory for domain diagnostics and actionable help. PlatformError is the concrete exception used when the native failure itself is the reported error.

UserLookup resolves owner and group identifiers into display names and resolves names back to platform identifiers. It caches successful lookups and is available through Application::userLookup() for code that needs a shared service instance.

Subprocesses

Introduction

Subprocess starts and owns one operating-system child process without invoking a command shell. The executable and each argument are passed as separate values, so shell quoting, expansion, redirection, and pipelines are never applied.

An owned child is reaped when it exits. Destroying a Subprocess that is still running first requests termination, waits briefly, then forcefully terminates and reaps the child. This makes the type suitable for helpers whose lifetime must not outlive an application scope or test. startDetached() is the explicit launch-and-forget operation; an internal waiter still prevents a zombie process on POSIX systems.

Starting a Process

start() requires a direct executable path and an optional StringList of arguments. It does not search the PATH environment variable. SubprocessOptions can select a working directory, environment inheritance and changes, standard-input inheritance, and output handling.

Environment changes are applied after optional inheritance. A value assigns the variable and std::nullopt removes it. Variable-name case sensitivity follows the operating system.

Waiting and Termination

wait() blocks until the child exits. The timed overload returns std::nullopt when its non-negative deadline expires. isRunning() performs a non-blocking status update. Once observed, the SubprocessExitStatus remains available through exitStatus(). The processId() accessor returns the identifier assigned at launch and keeps returning it after the child exits. A moved-from Subprocess returns an invalid identifier. Use the process-information interfaces described above to inspect a running child through a portable snapshot.

On POSIX, terminate() sends SIGTERM and kill() sends SIGKILL. Windows has no equivalent graceful process signal, so both operations use native forced process termination. Normal Windows termination is reported as an exit code; POSIX signal termination is reported separately.

Standard Streams

Standard output and standard error can independently be inherited, discarded, or captured. Standard error can instead be merged into the selected standard-output destination. Standard input can only be inherited or connected to an immediately closed null stream; interactive input piping is intentionally outside this API.

Capture retains a bounded prefix while background readers continue draining the native pipes. This prevents a verbose child from blocking after the capture limit is reached. The default limit is one MiB per stream and the maximum configurable limit is 64 MiB. Use the truncation accessors to detect discarded suffixes. Detached processes cannot use capture because no owner remains to consume the result.

Interface

class CpuArchitecture

A supported native CPU architecture.

Public Types

enum Value

CPU-architecture values.

Values:

enumerator Unknown

An unknown or unsupported architecture.

enumerator X86

32-bit x86.

enumerator X86_64

64-bit x86.

enumerator Arm32

32-bit Arm.

enumerator Arm64

64-bit Arm.

Public Functions

inline constexpr CpuArchitecture(const Value value) noexcept

Create an architecture from its raw value.

inline constexpr Value toRawValue() const noexcept

Get the raw architecture value.

text::String toString() const

Get the stable lowercase architecture identifier.

class EnvironmentVariables

Provides portable access to process environment variables.

See: System Services

Public Functions

EnvironmentVariables()

Create an instance using the backend for the current platform.

explicit EnvironmentVariables(impl::EnvironmentVariableBackendPtr backend)

Create an instance using a custom backend.

Parameters:

backend – The custom backend, which must not be null.

Throws:

err::ParameterError – If backend is null.

std::optional<text::String> get(const text::String &name) const noexcept

Read an environment variable without throwing.

Parameters:

name – The variable name.

Returns:

The variable value, or std::nullopt if it does not exist or the lookup fails.

text::String get(const text::String &name, text::String defaultValue) const noexcept

Read an environment variable without throwing, using a fallback value.

Parameters:
  • name – The variable name.

  • defaultValue – The value returned if the variable does not exist or the lookup fails.

Returns:

The variable value or the supplied fallback.

text::String getOrThrow(const text::String &name) const

Read an environment variable.

Parameters:

name – The variable name.

Throws:
Returns:

The variable value, including an empty value if one is stored.

bool set(const text::String &name, const text::String &value) noexcept

Set an environment variable without throwing.

Parameters:
  • name – The variable name.

  • value – The new variable value.

Returns:

true on success, otherwise false.

void setOrThrow(const text::String &name, const text::String &value)

Set an environment variable.

Parameters:
  • name – The variable name.

  • value – The new variable value.

Throws:
bool remove(const text::String &name) noexcept

Remove an environment variable without throwing.

Removing a variable that does not exist succeeds.

Parameters:

name – The variable name.

Returns:

true on success, otherwise false.

void removeOrThrow(const text::String &name)

Remove an environment variable.

Removing a variable that does not exist succeeds.

Parameters:

name – The variable name.

Throws:
class FileIdentity

An opaque identity for one filesystem object.

Public Functions

FileIdentity() = default

Create an invalid file identity.

inline constexpr bool isValid() const noexcept

Test if this identity represents a filesystem object.

Public Static Functions

static inline constexpr FileIdentity fromNativeValues(const uint64_t first, const uint64_t second) noexcept

Create a valid identity from two platform-specific values.

The values remain opaque and are meaningful only for equality comparison.

Parameters:
  • first – The first native identity component.

  • second – The second native identity component.

Returns:

A valid identity containing both native components.

class GroupId

A platform group identifier.

POSIX stores the numeric GID as text, Windows stores the SID string.

Public Functions

GroupId() = default

Create an empty group identifier.

inline explicit GroupId(const text::String &value)

Create a group identifier from its platform representation.

bool operator==(const GroupId &other) const noexcept = default

Compare two identifiers.

inline bool isEmpty() const noexcept

Test if this identifier is empty.

inline const text::String &value() const noexcept

Access the platform representation.

inline text::String toString() const

Convert this identifier to text.

inline std::size_t hash() const noexcept

Get a stable hash for this identifier.

class GroupName

A platform group name with an optional domain.

Public Functions

GroupName() = default

Create an empty group name.

inline explicit GroupName(const text::String &name)

Create a group name without a domain.

inline GroupName(const text::String &name, const text::String &domain)

Create a group name with an optional domain.

bool operator==(const GroupName &other) const noexcept = default

Compare two names.

inline bool isEmpty() const noexcept

Test if this group name is empty.

inline const text::String &name() const noexcept

Access the group name without its domain.

inline const text::String &domain() const noexcept

Access the optional domain.

text::String toString() const

Convert this name to display text.

std::size_t hash() const noexcept

Get a stable hash for this name.

Public Static Functions

static GroupName fromString(const text::String &text)

Create a name from display text, splitting a Windows-style domain if present.

class OperatingSystem

A supported operating-system family.

Public Types

enum Value

Operating-system family values.

Values:

enumerator Unknown

An unknown or unsupported operating system.

enumerator Windows

Microsoft Windows.

enumerator Macos

Apple macOS.

enumerator Linux

Linux.

Public Functions

inline constexpr OperatingSystem(const Value value) noexcept

Create an operating-system value from its raw value.

inline constexpr Value toRawValue() const noexcept

Get the raw operating-system value.

text::String toString() const

Get the stable lowercase operating-system identifier.

class PlatformError : public erbsland::err::RuntimeError

A native platform error with immutable diagnostic context.

Public Functions

explicit PlatformError(text::String reason, PlatformErrorContextConstPtr context = {}, std::exception_ptr cause = {}) noexcept

Create a native platform error.

Parameters:
  • reason – A developer-authored description of the failed operation.

  • context – The captured native diagnostic context.

  • cause – An optional independent cause.

virtual err::DiagnosticConstPtr diagnostic() const override

Convert the error with all its details into a structured diagnostic.

inline const PlatformErrorContextConstPtr &context() const noexcept

Get the captured native diagnostic context.

class PlatformErrorCategory

A platform-neutral category for a native operating-system error.

Public Types

enum Value

Portable native failure categories.

Values:

enumerator Unknown
enumerator NotFound
enumerator PermissionDenied
enumerator AlreadyExists
enumerator InvalidPath
enumerator NotDirectory
enumerator IsDirectory
enumerator NameTooLong
enumerator SymbolicLinkLoop
enumerator ReadOnlyFileSystem
enumerator StorageFull
enumerator QuotaExceeded
enumerator CrossDevice
enumerator ResourceBusy
enumerator TooManyOpenFiles
enumerator FileTooLarge
enumerator Unsupported

Public Functions

inline constexpr PlatformErrorCategory(const Value value) noexcept

Create a category from its raw value.

inline constexpr Value toRawValue() const noexcept

Get the raw category value.

text::String toString() const

Get the stable category identifier.

class PlatformErrorContext

Immutable diagnostic details captured from a native operating-system error.

Subclassed by erbsland::network::impl::HostResolverErrorContext, erbsland::system::impl::PosixErrorContext, erbsland::system::impl::WindowsErrorContext

Public Functions

virtual PlatformErrorCategory category() const noexcept = 0

Get the platform-neutral error category.

virtual text::String toString() const noexcept = 0

Get the native error message or a compact code representation.

virtual text::TextDocument toTextDocument() const = 0

Convert the native details into a field-only diagnostic document.

class ProcessId

A platform-independent process identifier.

A valid identifier can be compared and passed to process APIs, but does not guarantee that a process currently exists for it. Only system APIs create valid identifiers.

See: System Services

Public Functions

constexpr ProcessId() noexcept = default

Create an invalid process identifier.

inline constexpr bool isValid() const noexcept

Test if this process identifier can represent a native process.

text::String toString() const

Convert the identifier to decimal text, or return empty text for an invalid identifier.

inline std::size_t toHash() const noexcept

Get a stable hash for this identifier.

class ProcessInfo

A cached snapshot of portable information about one process identifier.

Construction eagerly loads all attributes. The snapshot remains unchanged until reload() is called. Reloading follows the numeric identifier, so compare startTime() values when process-identifier reuse matters.

See: System Services

Public Functions

ProcessInfo() noexcept

Create and load process information for the current process.

explicit ProcessInfo(ProcessId processId) noexcept

Create and load process information for the given identifier.

An invalid or currently unused identifier produces a non-existing snapshot.

Parameters:

processId – The process identifier to inspect.

inline ProcessId processId() const noexcept

Get the process identifier represented by this snapshot.

inline bool exists() const noexcept

Test if the platform reported a process for this identifier when the snapshot was loaded.

inline const path::Path &executablePath() const noexcept

Get the absolute executable image path, or an empty path when unavailable.

path::Path executablePathOrThrow() const

Get the absolute executable image path.

Throws:

PlatformError – If the path was unavailable in this snapshot.

inline ProcessId parentProcessId() const noexcept

Get the parent process identifier, or an invalid identifier when unavailable.

ProcessId parentProcessIdOrThrow() const

Get the parent process identifier.

Throws:

PlatformError – If the identifier was unavailable in this snapshot.

inline time::DateTime startTime() const noexcept

Get the UTC process start time, or an invalid date/time when unavailable.

time::DateTime startTimeOrThrow() const

Get the UTC process start time.

Throws:

PlatformError – If the start time was unavailable in this snapshot.

inline UserId ownerId() const noexcept

Get the process owner identifier, or an empty identifier when unavailable.

UserId ownerIdOrThrow() const

Get the process owner identifier.

Throws:

PlatformError – If the owner was unavailable in this snapshot.

void reload() noexcept

Replace this snapshot with newly loaded information without throwing.

void reloadOrThrow()

Replace this snapshot with newly loaded information.

An unused identifier is a normal non-existing result.

Throws:

PlatformError – If the platform cannot determine the process snapshot.

class Subprocess

Owns and controls one directly launched operating-system child process.

No method invokes a command shell. Destruction of a running owned process requests termination, waits briefly, then forcefully terminates and reaps it. Use startDetached() for deliberate launch-and-forget behavior.

See: System Services

Public Functions

~Subprocess()

Destroy the subprocess owner and ensure a running child is terminated and reaped.

Subprocess(Subprocess &&other) noexcept

Move ownership of a subprocess.

Subprocess &operator=(Subprocess &&other) noexcept

Replace this subprocess owner by moving another owner into it.

ProcessId processId() const noexcept

Get the identifier assigned to the child process.

The identifier remains valid after exit; a moved-from subprocess returns an invalid identifier.

bool isRunning()

Test if the child is still running and update cached exit state.

const std::optional<SubprocessExitStatus> &exitStatus() const noexcept

Get the cached child exit status, if it has been observed.

SubprocessExitStatus wait()

Wait until the child exits and return its status.

Throws:

PlatformError – If the native wait operation fails.

std::optional<SubprocessExitStatus> wait(time::TimeDelta timeout)

Wait up to the given timeout for the child to exit.

Parameters:

timeout – A non-negative timeout.

Throws:
Returns:

The exit status, or no value if the timeout expired.

void terminate()

Request child termination using the platform’s regular termination mechanism.

Throws:

PlatformError – If the native operation fails.

void kill()

Force the child to terminate.

Throws:

PlatformError – If the native operation fails.

text::String standardOutput() const

Get the currently captured standard output prefix.

text::String standardError() const

Get the currently captured standard error prefix.

This is empty when standard error was merged into standard output.

bool wasStandardOutputTruncated() const noexcept

Test if captured standard output exceeded its configured limit.

bool wasStandardErrorTruncated() const noexcept

Test if captured standard error exceeded its configured limit.

Public Static Functions

static auto start(const path::Path &executable, const text::StringList &arguments = {}, const SubprocessOptions &options = {}) -> Subprocess

Launch an owned subprocess.

Parameters:
  • executable – A non-empty valid executable path. No PATH lookup or shell interpretation is performed.

  • arguments – Arguments following argv[0] in the child command line.

  • options – Child environment and standard-stream options.

Throws:
  • err::ParameterError – If a path, argument, environment entry, or option is invalid.

  • PlatformError – If native process creation fails.

Returns:

The owning subprocess object.

static void startDetached(const path::Path &executable, const text::StringList &arguments = {}, const SubprocessOptions &options = {})

Launch a subprocess without retaining ownership or process control.

Output capture is not permitted for detached children. An internal waiter prevents POSIX zombies.

Parameters:
  • executable – A non-empty valid executable path. No PATH lookup or shell interpretation is performed.

  • arguments – Arguments following argv[0] in the child command line.

  • options – Child environment and standard-stream options.

Throws:
  • err::ParameterError – If a path, argument, environment entry, or option is invalid.

  • PlatformError – If native process creation fails.

class SubprocessExitStatus

Describes how an owned subprocess exited.

POSIX signal termination has no direct Windows equivalent. On Windows, native termination is represented by the process exit code and terminationSignal() remains empty.

See: System Services

Public Functions

inline constexpr bool hasExited() const noexcept

Test if the process returned an exit code.

inline constexpr bool wasSignaled() const noexcept

Test if the process was terminated by a POSIX signal.

inline constexpr bool isSuccess() const noexcept

Test if the process returned exit code zero.

inline constexpr const std::optional<std::int32_t> &exitCode() const noexcept

Get the process exit code, if the platform reported one.

inline constexpr const std::optional<std::int32_t> &terminationSignal() const noexcept

Get the POSIX termination signal, if the platform reported one.

Public Static Functions

static inline constexpr SubprocessExitStatus exited(const std::int32_t exitCode) noexcept

Create a status for a process that returned an exit code.

Parameters:

exitCode – The process exit code.

static inline constexpr SubprocessExitStatus signaled(const std::int32_t signal) noexcept

Create a status for a process terminated by a POSIX signal.

Parameters:

signal – The positive native signal number.

class SubprocessOptions

Configures the launch environment and standard streams of a subprocess.

Environment changes are applied after optional inheritance. Assigning no value removes the variable from the child environment. Names retain the native platform’s case-sensitivity rules.

See: System Services

Public Functions

inline const std::optional<path::Path> &workingDirectory() const noexcept

Get the optional child working directory.

inline SubprocessOptions &setWorkingDirectory(path::Path value) noexcept

Set the child working directory.

inline SubprocessOptions &clearWorkingDirectory() noexcept

Clear the configured child working directory.

inline bool inheritsEnvironment() const noexcept

Test if the child inherits the current process environment.

inline SubprocessOptions &setInheritEnvironment(const bool value) noexcept

Select whether the child inherits the current process environment.

inline const text::StringMap<std::optional<text::String>> &environmentChanges() const noexcept

Get environment assignments and removals applied to the child environment.

inline SubprocessOptions &setEnvironmentVariable(const text::String &name, text::String value)

Assign one child environment variable.

inline SubprocessOptions &removeEnvironmentVariable(const text::String &name)

Remove one variable from the child environment.

inline SubprocessOptions &clearEnvironmentChanges() noexcept

Remove all pending child environment changes.

inline bool inheritsStandardInput() const noexcept

Test if the child inherits the parent’s standard input stream.

inline SubprocessOptions &setInheritStandardInput(const bool value) noexcept

Select whether the child inherits standard input or receives an immediately closed stream.

inline SubprocessOutputMode standardOutputMode() const noexcept

Get the standard output handling mode.

inline SubprocessOptions &setStandardOutputMode(const SubprocessOutputMode value) noexcept

Set the standard output handling mode.

inline SubprocessOutputMode standardErrorMode() const noexcept

Get the standard error handling mode.

inline SubprocessOptions &setStandardErrorMode(const SubprocessOutputMode value) noexcept

Set the standard error handling mode.

inline bool mergesStandardError() const noexcept

Test if standard error is redirected into standard output.

inline SubprocessOptions &setMergeStandardError(const bool value) noexcept

Select whether standard error is redirected into standard output.

inline unit::ByteLength captureLimit() const noexcept

Get the number of bytes retained for each captured output stream.

inline SubprocessOptions &setCaptureLimit(const unit::ByteLength value) noexcept

Set the number of bytes retained for each captured output stream.

Public Static Attributes

static constexpr auto cDefaultCaptureLimit = unit::ByteLength{1024U * 1024U}

Default number of bytes retained for each captured output stream.

static constexpr auto cMaximumCaptureLimit = unit::ByteLength{64U * 1024U * 1024U}

Maximum configurable number of retained bytes for each captured output stream.

enum class erbsland::system::SubprocessOutputMode : std::uint8_t

Selects how a subprocess output stream is handled.

Values:

enumerator Inherit

Inherit the matching stream from the parent process.

enumerator Discard

Discard all bytes written to the stream.

enumerator Capture

Capture a bounded prefix and continue draining excess bytes.

OperatingSystem erbsland::system::info::operatingSystem() noexcept

Get the current operating-system family.

CpuArchitecture erbsland::system::info::cpuArchitecture() noexcept

Get the native host CPU architecture.

This is the host architecture, not the current executable’s emulated architecture.

std::uint32_t erbsland::system::info::logicalCpuCount() noexcept

Get the usable logical CPU count as a thread-count hint.

The returned count is always at least one.

class UserId

A platform user identifier.

POSIX stores the numeric UID as text, Windows stores the SID string.

Public Functions

UserId() = default

Create an empty user identifier.

inline explicit UserId(const text::String &value)

Create a user identifier from its platform representation.

bool operator==(const UserId &other) const noexcept = default

Compare two identifiers.

inline bool isEmpty() const noexcept

Test if this identifier is empty.

inline const text::String &value() const noexcept

Access the platform representation.

inline text::String toString() const

Convert this identifier to text.

inline std::size_t hash() const noexcept

Get a stable hash for this identifier.

class UserLookup

Cached lookup service for platform user and group identities.

Public Functions

UserLookup()

Create a lookup service with the default platform backend.

explicit UserLookup(impl::UserLookupBackendPtr backend)

Create a lookup service with a custom backend.

UserName userNameForId(const UserId &id)

Resolve an owner name from a platform owner identifier.

Throws:

path::PathError – If the identifier cannot be resolved.

GroupName groupNameForId(const GroupId &id)

Resolve a group name from a platform group identifier.

Throws:

path::PathError – If the identifier cannot be resolved.

UserId userIdForName(const UserName &name)

Resolve a platform owner identifier from an owner name.

Throws:

path::PathError – If the name cannot be resolved.

GroupId groupIdForName(const GroupName &name)

Resolve a platform group identifier from a group name.

Throws:

path::PathError – If the name cannot be resolved.

void clearCache() noexcept

Clear all cached lookups.

class UserName

A platform user name with an optional domain.

Public Functions

UserName() = default

Create an empty user name.

inline explicit UserName(const text::String &name)

Create a user name without a domain.

inline UserName(const text::String &name, const text::String &domain)

Create a user name with an optional domain.

bool operator==(const UserName &other) const noexcept = default

Compare two names.

inline bool isEmpty() const noexcept

Test if this user name is empty.

inline const text::String &name() const noexcept

Access the account name without its domain.

inline const text::String &domain() const noexcept

Access the optional domain.

text::String toString() const

Convert this name to display text.

std::size_t hash() const noexcept

Get a stable hash for this name.

Public Static Functions

static UserName fromString(const text::String &text)

Create a name from display text, splitting a Windows-style domain if present.