std/os
Environment, System, Memory, Processor, Volume, CurrentProcess, Directories, User, Entropy, MacAddress and NetworkInterface - the machine the program runs on - and OsError.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Die generierte Referenz von std/os zeigt jede öffentliche Deklaration mit Signatur, Doc-Kommentar und Beispielen.
Draft. Slices 1 to 4 of docs/design/OS.md and the user folders and the user of slice 6: the environment, the system's identity, the base and user directories, the memory and swap, the processors, the current process, the volumes and the user. The C of the macOS and FreeBSD natives is compiled on those systems only and has not run there yet; network interfaces are built on Linux only and their traffic is a later slice.
std/os answers what a program asks about the machine it runs on: the environment it was started with, which
operating system and version it is, the host name and how long the machine has been running, how much memory and swap
it has and how much of it is available, which process it is itself, and where the user's configuration, data, cache and temporary files belong, which account it runs as - and it hands out the system's source of randomness. It
re-exports OperatingSystem, Architecture and ByteOrder of std/core, so a file that branches on the system
writes one import. It is deliberately not in the prelude:
use System from "std/os" at the top of a file is the statement "this file asks the operating system".
Every question is a match OperatingSystem.current in TorbScript whose arm hands it to the directory of one system.
Only the arm of the build's target is compiled, and every arm is type checked on every machine (see
Compile-time branches).
Import#
use Environment, EnvironmentVariables, System, SystemVersion, Memory, Swap, Directories, OsError from "std/os"
use Processor, ProcessorTimes, ProcessorUsage, ProcessorDescription, ProcessorCache, ProcessorFeature, Frequency from "std/os"
use CurrentProcess, User from "std/os"
use Volume, VolumeKind, VolumeSpace from "std/os"
use Entropy from "std/os/entropy"
use MacAddress from "std/os"
use NetworkInterface, InterfaceAddress, InterfaceStatus, InterfaceKind from "std/os"use System, Directories, OsError from "std/os"
use Path from "std/path"
fn main(): Result<Void, OsError> {
const version = System.version()?
print "{version.name} ({version.kernel}) on {System.hostName()?}"
const settings = Directories.configuration()?
print settings.joined(Path.from("my-tool"))
Ok void
}Declarations#
Environment#
native type Environment {
native static fn get(name: String): String?
static fn variables(): EnvironmentVariables
static fn searchPath(): List<Path>
}The environment this process was started with. get(name) answers None if the variable is not set, or if the caller
is not allowed to see it - the sandbox and a variable that does not exist look the same on purpose. On Windows a name
is looked up without regard to case, as the system does. variables() is every variable the caller may see, as a
value; searchPath() is PATH split at the system's separator (; on Windows, : elsewhere), each entry a Path,
the empty ones dropped.
There is no Environment.set: changing a variable of the running process is global mutable state, and on POSIX a
data race besides. What setting a variable is for is a child process, and a child is handed an EnvironmentVariables
value.
Inside a sandboxed script the module std/os/environment has to be granted, and only the variables a granted pattern
matches (environment "APP_*") are visible to get and variables.
EnvironmentVariables#
type EnvironmentVariables with Show, Equals {
static fn empty(): Self
fn get(name: String): String?
fn names(): List<String>
var fn set(name: String, value: String)
var fn remove(name: String)
fn show(): String
}A set of environment variables as a value. Names compare the way the target compares them: without regard to case on
Windows, exactly everywhere else, so set("PATH", …) on Windows replaces an inherited Path. show() lists the
names only, because an environment carries secrets and a print of it must not.
use EnvironmentVariables from "std/os"
var variables = EnvironmentVariables.empty()
variables.set "APP_MODE", "debug"
variables.set "NO_COLOR", "1"
variables.remove "NO_COLOR"
print variablesSystem#
type System {
static fn version(): Result<SystemVersion, OsError>
static fn hostName(): Result<String, OsError>
static fn uptime(): Result<Duration, OsError>
static fn pageSize(): ByteSize
static fn machineArchitecture(): Result<Architecture, OsError>
}| Windows | Linux | macOS | FreeBSD | |
|---|---|---|---|---|
version | registry ProductName, DisplayVersion, UBR; RtlGetVersion | /etc/os-release, uname | kern.osproductversion, kern.osversion, kern.osrelease | uname |
hostName | GetComputerNameExW | gethostname | gethostname | gethostname |
uptime | GetTickCount64 | /proc/uptime | kern.boottime | kern.boottime |
pageSize | GetNativeSystemInfo | sysconf | sysconf | sysconf |
machineArchitecture | IsWow64Process2 | uname | uname, sysctl.proc_translated | uname |
hostName never asks DNS. uptime includes the time the machine was asleep on every system. pageSize is a
ByteSize and never fails. In the browser, where the playground runs a program, version, hostName and uptime are
Unsupported, pageSize is WebAssembly's 65536, and machineArchitecture is .Wasm64. machineArchitecture is the machine and not always Architecture.current: an x86-64
program emulated on Arm64 Windows, or translated by Rosetta on a Mac, runs on .Arm64.
SystemVersion#
type SystemVersion with Show, Equals {
name: String
identifier: String
release: String
build: String?
kernel: String
fn kernelNumbers(): List<Int>
}name is for people (Windows 11 Pro, Ubuntu 24.04.1 LTS, macOS 15.1, FreeBSD 14.1-RELEASE); identifier is
for programs, stable and lower case (windows, ubuntu, macos, freebsd); release is the product's own version
(24H2, 24.04, 15.1, 14.1); build is there where the product has one (26100.2314, 24B83); kernel is the
kernel's release, and kernelNumbers() its leading numbers for comparing ([6, 8, 0] of 6.8.0-45-generic).
Windows 11 still writes Windows 10 into the registry's ProductName, and the name says Windows 11 from build
22000 on.
Memory#
type Memory with Show, Equals {
total: ByteSize
available: ByteSize
swap: Swap
fn used(): ByteSize
static fn current(): Result<Memory, OsError>
}type Swap with Show, Equals {
total: ByteSize
used: ByteSize
fn free(): ByteSize
}Memory.current() is how much memory the machine has (total), how much of it a new allocation could still get
without swapping (available), and its swap; every size is a ByteSize of std/number.
use Memory, OsError from "std/os"
fn main(): Result<Void, OsError> {
const memory = Memory.current()?
print "{memory.available} of {memory.total} available, {memory.used()} used"
print "swap: {memory.swap.used} of {memory.swap.total}"
Ok void
}total | available | swap | |
|---|---|---|---|
| Windows | GlobalMemoryStatusEx, ullTotalPhys | ullAvailPhys | the page files, from NtQuerySystemInformation(SystemPageFileInformation) |
| Linux | MemTotal of /proc/meminfo | MemAvailable, or MemFree + Buffers + Cached before 3.14 | SwapTotal, SwapTotal - SwapFree |
| macOS | hw.memsize | the free (speculative included), inactive and purgeable pages of host_statistics64 | vm.swapusage |
available is each system's own estimate, and it counts the caches the system would give up: Linux and macOS use free
memory as a cache on purpose, so the memory nothing has claimed is small on a busy machine and says little. That is
why used() is total - available, never a number of the system's own, and why it is the one to watch. The swap of
Windows is the page files alone, never the commit limit, which is the memory and the page files together. FreeBSD
answers Unsupported until its slice, and so does the browser, which cannot tell. A /proc/meminfo that is not in
the form Linux writes it is OsError.Malformed.
Processor#
type Processor {
static fn logicalCount(): Result<Int, OsError>
static fn physicalCount(): Result<Int, OsError>
static fn describe(): Result<ProcessorDescription, OsError>
static fn times(): Result<ProcessorTimes, OsError>
static fn timesOfEach(): Result<List<ProcessorTimes>, OsError>
static fn usage(interval: Duration = 1.seconds()): Task<Result<ProcessorUsage, OsError>>
}type ProcessorTimes with Show, Equals, Subtract {
user: Duration
system: Duration
idle: Duration
waiting: Duration
other: Duration
fn total(): Duration
fn subtract(other: Self): Self
}type ProcessorUsage with Show, Equals {
busy: Float64
user: Float64
system: Float64
each: List<Float64>
static fn between(total: ProcessorTimes, each: List<ProcessorTimes>): ProcessorUsage
}type Frequency with Show, Compare {
static fn ofHertz(hertz: Int64): Frequency
static fn ofMegahertz(megahertz: Int64): Frequency
fn hertz(): Int64
fn megahertz(): Float64
fn gigahertz(): Float64
fn compare(other: Self): Ordering
fn show(): String
}Processor.logicalCount() is how many threads the system runs at once, hyper-threads included, and
physicalCount() how many cores they sit on; the first is never below one and the second never above the first.
times() is the time all processors together have spent in each state since the system started, and timesOfEach()
the same for every logical processor, in the system's order. Both are Durations that only grow, and the difference
of two readings (after - before) is the time that passed in between. usage(interval) takes the two readings with a
sleep between them and answers the fractions: busy is user + system of the total time, each the same for every
processor. A difference that covers no time answers 0, never NaN. A Frequency is a clock in hertz, shown in the unit
that fits (800 MHz, 3.6 GHz).
use Processor from "std/os"
const threads = Processor.logicalCount().expect("the logical processors")
const cores = Processor.physicalCount().expect("the cores")
print "{threads} threads on {cores} cores"
const usage = Processor.usage(250.milliseconds()).await().expect("the usage")
print "{usage.busy * 100.0}% busy over {usage.each.length()} processors"| counts | times | waiting, other | |
|---|---|---|---|
| Windows | GetLogicalProcessorInformationEx (Windows.processorCounts) | GetSystemTimes; each processor from NtQuerySystemInformation(SystemProcessorPerformanceInformation) | zero: the interrupt and deferred-procedure time is inside system |
| Linux | sysconf(_SC_NPROCESSORS_ONLN); the cores from the physical id and core id pairs of /proc/cpuinfo | the cpu and cpu<n> lines of /proc/stat, in clock ticks of _SC_CLK_TCK | waiting is iowait; other holds nice, interrupts and stolen time |
| macOS | hw.logicalcpu, hw.physicalcpu | host_statistics(HOST_CPU_LOAD_INFO); each processor from host_processor_info, in ticks of CLK_TCK | other is nice; waiting is zero |
FreeBSD answers Unsupported until its slice, and so does the browser, which cannot tell. A /proc/stat that is not
in the form Linux writes it is OsError.Malformed. Arm Linux writes no core identifiers in /proc/cpuinfo, so every
logical processor counts as a core there. What this process may use of them is CurrentProcess.processors().
What a processor says about itself has three types of its own:
type ProcessorDescription with Show, Equals {
vendor: String
model: String
packages: Int
physicalCores: Int
logicalProcessors: Int
baseFrequency: Frequency?
maximumFrequency: Frequency?
caches: List<ProcessorCache>
features: Set<ProcessorFeature>
}type ProcessorCache with Show, Equals {
level: Int
kind: "data" | "instruction" | "unified"
size: ByteSize
lineSize: ByteSize
}type ProcessorFeature with Show, Equals, Hash {
case Sse2
case Sse3
case Ssse3
case Sse41
case Sse42
case Avx
case Avx2
case Fma
case Bmi1
case Bmi2
case Avx512Foundation
case Aes
case Sha
case Neon
case Crc32
case DotProduct
case Sve
case Sve2
}A ProcessorCache is one cache the processor lists - its level, whether it holds data, instructions or both, its size and
its line size - and a cache that several cores share is one entry with its whole size. A ProcessorFeature is an
instruction-set extension a program may dispatch on, named as its vendor spells it. The list is closed and grows with
its users, and the Arm cases are declared before anything fills them. Both are what the decoders of std/os/x86 answer
today, and what Processor will describe next; neither reads the machine by itself. On x86-64 a feature is listed only
where the processor has it and the operating system saves its registers, so Avx, Avx2, Fma and
Avx512Foundation are never reported on a system that would corrupt a program using them.
Processor.describe() puts them together with the vendor and the model: the vendor string (GenuineIntel,
AuthenticAMD), the model (the brand string, trimmed; the vendor string where the processor has none), the caches
and the features. On x86-64 they come from cpuid on Windows, Linux and macOS alike, and the counts from the system,
because on a processor with cores of different kinds cpuid answers for the core it happens to run on:
logicalProcessors is Processor.logicalCount(), physicalCores is Processor.physicalCount(), and packages the
sockets the system names - GetLogicalProcessorInformationEx on Windows, hw.packages on macOS, the distinct
physical id values of /proc/cpuinfo on Linux, one where it names none - and the dump's own topology where the
system names none. baseFrequency and maximumFrequency are None until a source for them exists. FreeBSD and the
browser answer OsError.Unsupported, and so does every architecture other than x86-64, until its description is built.
use Processor, ProcessorFeature from "std/os"
const processor = Processor.describe().expect("the processor")
print "{processor.model} ({processor.vendor})"
print "{processor.packages} package, {processor.physicalCores} cores, {processor.logicalProcessors} threads"
for cache in processor.caches {
print "L{cache.level} {cache.kind}: {cache.size}"
}
print "AVX2: {processor.features.contains(ProcessorFeature.Avx2)}"CurrentProcess#
native type CurrentProcess {
static fn identifier(): Int
static fn parentIdentifier(): Result<Int, OsError>
static fn executable(): Result<Path, OsError>
static fn workingDirectory(): Result<Path, OsError>
static fn uptime(): Duration
static fn resources(): Result<ProcessResources, OsError>
static fn processors(): Result<Int, OsError>
static fn memoryLimit(): Result<ByteSize?, OsError>
}type ProcessResources with Show, Equals {
userTime: Duration
systemTime: Duration
resident: ByteSize
peakResident: ByteSize
}What the operating system knows about this process: its identifier and its parent's, the executable it runs, the
directory a relative path is resolved against, and how long it has been running. What the process does - its
arguments, exit, the children it starts - is Process of std/process.
use CurrentProcess, OsError from "std/os"
fn main(): Result<Void, OsError> {
const directory = CurrentProcess.workingDirectory()?
const executable = CurrentProcess.executable()?
print "process {CurrentProcess.identifier()} in {directory}"
print "running {executable} for {CurrentProcess.uptime()}"
Ok void
}identifier, parentIdentifier | executable | |
|---|---|---|
| Windows | GetCurrentProcessId, the parent from a CreateToolhelp32Snapshot of the processes | GetModuleFileNameW |
| Linux | getpid, getppid | /proc/self/exe |
| macOS | getpid, getppid | _NSGetExecutablePath, then realpath |
| FreeBSD | getpid, getppid | the sysctl KERN_PROC_PATHNAME |
identifier() always answers; in the browser, where a page is no process of the machine, it is 0, the identifier no
process has, and parentIdentifier() is Unsupported. A parent the system does not know is Missing: on Linux the
parent of a container's first process, which is outside its namespace, and on Windows a parent that has already ended.
executable() is absolute with / between its parts; inside torb run it is the entry file of the program, which is
what executes - or, for a program that names Resource files, the staged name the VM runs it as, beside which its
.resources directory is written. It is how a program finds the files it was installed beside: torb finds its std/ and runtime/
this way. workingDirectory() is always what File.absolutePath(".") of std/fs answers, because both
read it through the same function of the runtime, inside a sandbox too. There is no setWorkingDirectory: the working
directory is shared by every task of the process, so a child is given one when it is started instead. uptime() is
read on the monotonic clock from before main started, in whole milliseconds and rounded up.
resources() is what the process has used so far: its processor time in user and in system mode, as whole
milliseconds cut off, and its resident memory now and at its peak, as ByteSizes; the peak is never less than the
resident size. processors() is how many processors the process may keep busy at once - the number to size a pool
of workers by, never less than one - and memoryLimit() the most memory it may hold before the system stops it, or
None where only the machine limits it.
use CurrentProcess, OsError from "std/os"
fn main(): Result<Void, OsError> {
const used = CurrentProcess.resources()?
print "{used.userTime} in user mode, {used.resident} resident, at most {used.peakResident}"
const processors = CurrentProcess.processors()?
print "{processors} processors"
match CurrentProcess.memoryLimit()? {
Some(limit) => print "held to {limit}"
None => print "no memory limit"
}
Ok void
}resources | processors | memoryLimit | |
|---|---|---|---|
| Windows | GetProcessTimes, the working set of GetProcessMemoryInfo | the affinity mask, every processor group where it is not narrowed, a job object's CPU rate | the memory limit of the job object |
| Linux | getrusage, VmRSS of /proc/self/status | sched_getaffinity, the cgroups' cpu.max (version 1: cpu.cfs_quota_us) rounded up | the lowest memory.max (version 1: memory.limit_in_bytes) of the cgroups |
| macOS | getrusage, task_info(MACH_TASK_BASIC_INFO) | hw.ncpu | None: macOS has no such limit |
getrusage reports the peak in kibibytes on Linux and in bytes on macOS, and resources() converts it. The cgroups
are those /proc/self/cgroup names, from the process's own up to the root, so a container's --cpus and --memory
count; a cgroup file that is not there, or a /sys the process may not read, is no limit and answers Ok(None), not
an error. A dev binary of torb runs in a job object with a memory limit on Windows, which memoryLimit()
answers. FreeBSD answers Unsupported for all three until its slice, and so does the browser. Inside torb run the
answers are those of the torb process that runs the program.
Volume#
type Volume with Show, Equals {
mountPoint: Path
device: String
fileSystem: String
label: String?
kind: VolumeKind
readOnly: Bool
fn isRemovable(): Bool
static fn all(includeVirtual: Bool = false): Result<List<Volume>, OsError>
fn space(): Task<Result<VolumeSpace, OsError>>
static fn containing(path: Path): Task<Result<Volume, OsError>>
fn isVirtual(): Bool
}type VolumeKind with Show, Equals {
case Fixed
case Removable
case Network
case Optical
case Memory
case Virtual
case Unknown
}type VolumeSpace with Show, Equals {
total: ByteSize
free: ByteSize
available: ByteSize
fn used(): ByteSize
}Volume.all() is the mount table of the system: one Volume per mount point, with the device, the file system as the
system names it, the label where the system knows one, a VolumeKind and whether it is read-only. It never waits,
because it asks no file system anything. The virtual volumes - proc, sysfs, devfs - are left out unless
includeVirtual: true asks for them. A volume mounted in two places is two values. space() asks the file system how
big it is: total, free and available, which is what this user may still write and can be less than free;
used() is total - free. Volume.containing(path) answers the volume a path lives on: a relative path is resolved
against the working directory, . and .. are folded away, the path is asked about (so a path that does not exist is
OsError.Missing), and the mount point that is the longest prefix of it wins. A link is not followed, so a link into
another volume belongs to the volume of the link.
use Volume from "std/os"
for volume in Volume.all().expect("the volumes") {
const space = volume.space().await().expect("the space")
print "{volume.mountPoint} {volume.fileSystem} {space.used().gigabytes()} of {space.total.gigabytes()} GB"
}| the mount table | kind | the space | |
|---|---|---|---|
| Windows | FindFirstVolumeW, GetVolumePathNamesForVolumeNameW; the file system, label and read-only flag from GetVolumeInformationW | GetDriveTypeW (removable, fixed, remote, optical, RAM disk) | GetDiskFreeSpaceExW |
| Linux | /proc/self/mountinfo, octal escapes (\040) undone | the file system type; a disk whose /sys/block/<disk>/removable is 1 is removable | statvfs |
| macOS | getmntinfo | MNT_LOCAL, MNT_REMOVABLE and the file system type | statvfs |
FreeBSD answers Unsupported until its slice, and so does the browser, which has no file systems. A line of
/proc/self/mountinfo that is not in the form Linux writes it is OsError.Malformed. On Windows a drive with nothing
in it, such as a card reader with no card, and a volume with no path are left out of all(), and no dialog opens.
Directories#
type Directories {
static fn home(): Result<Path, OsError>
static fn configuration(): Result<Path, OsError>
static fn data(): Result<Path, OsError>
static fn state(): Result<Path, OsError>
static fn cache(): Result<Path, OsError>
static fn documents(): Result<Path, OsError>
static fn downloads(): Result<Path, OsError>
static fn desktop(): Result<Path, OsError>
static fn pictures(): Result<Path, OsError>
static fn music(): Result<Path, OsError>
static fn videos(): Result<Path, OsError>
static fn temporary(): Path
}Where a user's files belong, by the convention of the system. Each answers the base, and an application joins its own name to it: one join, the same on every system.
| Linux, FreeBSD (XDG) | Windows (Known Folders) | macOS | |
|---|---|---|---|
home | $HOME, else the account entry | FOLDERID_Profile | $HOME, else the account entry |
configuration | $XDG_CONFIG_HOME, else ~/.config | FOLDERID_RoamingAppData | ~/Library/Application Support |
data | $XDG_DATA_HOME, else ~/.local/share | FOLDERID_RoamingAppData | ~/Library/Application Support |
state | $XDG_STATE_HOME, else ~/.local/state | FOLDERID_LocalAppData | ~/Library/Application Support |
cache | $XDG_CACHE_HOME, else ~/.cache | FOLDERID_LocalAppData | ~/Library/Caches |
temporary | $TMPDIR, else /tmp | GetTempPath2W | $TMPDIR, else the user's own |
documents, downloads, desktop, pictures, music, videos | XDG_DOCUMENTS_DIR and its siblings in user-dirs.dirs of the configuration directory, else ~/Documents, ~/Downloads, ~/Desktop, ~/Pictures, ~/Music, ~/Videos | FOLDERID_Documents, FOLDERID_Downloads, FOLDERID_Desktop, FOLDERID_Pictures, FOLDERID_Music, FOLDERID_Videos | ~/Documents, ~/Downloads, ~/Desktop, ~/Pictures, ~/Music, ~/Movies |
An XDG variable that holds a relative path is ignored, as the XDG specification requires. temporary() always
answers, because every system has a temporary directory and /tmp is the last resort. In the browser, whose file
system is in memory and belongs to no user, every directory but temporary() (/tmp) is Unsupported.
The user folders are where a person looks for their files, not where an application keeps its own. A folder answers a
path whether or not it exists - the fallback below the home is not checked, so create it before writing to it. A folder
the user disabled in user-dirs.dirs (XDG_MUSIC_DIR="$HOME/") answers the fallback, never the home directory itself.
On Linux the file is read with File.readText of std/fs; a missing or unreadable one is the fallback, not an error.
use Directories, OsError from "std/os"
use Path from "std/path"
fn main(): Result<Void, OsError> {
const downloads = Directories.downloads()?
print downloads.joined(Path.from("report.pdf"))
Ok void
}User#
type User with Show, Equals {
name: String
fullName: String?
identifier: String
home: Path
isElevated: Bool
static fn current(): Task<Result<User, OsError>>
}User.current() answers the account the process runs as. It is a task, because the account database may be a
directory service on the network. name is the login name (ada), on Windows without its domain; fullName is the
name for people and None where the account has none; identifier is text, because the two kinds have nothing else in
common - 1000 on POSIX, the security identifier S-1-5-21-… on Windows; home is the account's own home directory;
isElevated says whether the process is the superuser or has an elevated token.
name, home, identifier | fullName | isElevated | |
|---|---|---|---|
| Linux, macOS, FreeBSD | the account entry of the user id (getpwuid_r); the user id as text | the GECOS field up to its first comma, None where it is empty | the effective user id is 0 (geteuid) |
| Windows | GetUserNameExW(NameSamCompatible) without the domain; FOLDERID_Profile; the SID of the process token (ConvertSidToStringSidW) | GetUserNameExW(NameDisplay), None where the account has none or the domain cannot be reached | TokenElevation of the process token |
A browser has no user and answers OsError.Unsupported. A user id with no account entry is OsError.Missing. home is
the account's, so it differs from Directories.home() where the HOME variable overrides it. On Windows a member of
the administrators group whose program was not started as administrator is not elevated.
use User from "std/os"
const user = User.current().await().expect("the user")
print "{user.name} ({user.identifier}) lives in {user.home}"
if user.isElevated {
print "running with elevated rights"
}Entropy#
type Entropy {
static fn bytes(count: Int): Bytes
static fn int64(): Int64
}Bytes of the operating system's source of randomness, which is fit for cryptography: what a key, a nonce, a token and
the seed of a generator are made from. bytes(count) answers that many, int64() eight of them as one number whose 64
bits are all random - the unpredictable seed of a generator. Nothing seeds it and nothing repeats it; a generator that
makes the same numbers from the same seed, for a test or a simulation, is a different thing (docs/design/RANDOM.md),
and this is where its seed comes from, never what it is.
| System | Source |
|---|---|
| Windows | BCryptGenRandom, the system's preferred generator |
| Linux, FreeBSD | getrandom(2), which needs no file and so works in a chroot without /dev |
| macOS | getentropy |
| the browser | crypto.getRandomValues of the page, through emscripten's getentropy |
use Entropy from "std/os/entropy"
const nonce = Entropy.bytes 18
print nonce.length()It lives in its own module, std/os/entropy, and is a capability of its own, entropy: it reads nothing about the
machine, so a package that makes a nonce is reported as touching entropy rather than the operating system (see
torb publish), whether it imports Entropy from std/os/entropy or from std/os. A
sandboxed script needs the module std/os/entropy granted, and that is the import to write where a script uses it.
It panics where the system does not answer, and bytes where count is negative. None of the systems above
fails once it has started, there is no safe way on without randomness, and a failure a caller could handle would
invite a weaker source in its place - which is why Go's crypto/rand decided the same.
MacAddress#
type MacAddress with Show, Equals, Hash, TryFrom<String, AddressError> {
static fn of(octets: List<Int>): Result<Self, AddressError>
fn octets(): List<Int>
fn show(): String
static fn tryFrom(text: String): Result<Self, AddressError>
}The hardware address of a network interface: six octets. It is a value like Ipv4Address and reads nothing from the
machine - what asks the machine for the address of an interface is a later slice of
docs/design/OS.md - so it lives in a module of its own, std/os/macaddress, and is no capability:
a package that only names a MAC address is not reported as touching the operating system, whether it imports the name
from std/os or from std/os/macaddress. It is not in std/ip because it is a link-layer address and std/ip holds
nothing that is not IP; AddressError is the error of both and is imported from std/ip.
MacAddress is a capsule: of(octets) takes six numbers from 0 to 255, most
significant first, and refuses any other count and an octet outside the range; octets() answers them again. tryFrom
reads six groups of exactly two hexadecimal digits, in either case, with one separator throughout, : or - (Windows
writes 3C-22-FB-0A-1B-9E). It refuses five or seven groups, a group of one or three digits, mixed separators and
any other character. show() is lower case with colons, 3c:22:fb:0a:1b:9e, and tryFrom(show()) gives the value
back.
use MacAddress from "std/os"
use AddressError from "std/ip"
fn main(): Result<Void, AddressError> {
const address = MacAddress.tryFrom("3C-22-FB-0A-1B-9E")?
print address
print address.octets()
Ok void
}NetworkInterface#
type NetworkInterface with Show, Equals {
name: String
description: String?
index: Int
hardwareAddress: MacAddress?
addresses: List<InterfaceAddress>
mtu: Int
status: InterfaceStatus
kind: InterfaceKind
static fn all(): Result<List<Self>, OsError>
}A network interface of the machine with its addresses. NetworkInterface.all() lists them in the order of their
indexes and asks the system once. On Linux it reads getifaddrs for the addresses and the hardware address and
/sys/class/net/<name>/ for the MTU, the state and the kind; a file that is not there leaves mtu at 0, status
Unknown and kind Other. description is None on Linux. Every other system answers OsError.Unsupported for now.
status is Up or Down as the system says it, and Unknown otherwise: an interface that is being brought up, or
one whose state the driver does not report (a loopback says unknown on most kernels). kind is Loopback for lo,
Wireless for an interface with a wireless directory, Tunnel for a tunnel type or a point-to-point link, and
Ethernet for an Ethernet one.
use NetworkInterface, OsError from "std/os"
fn main(): Result<Void, OsError> {
for interface in NetworkInterface.all()? {
print "{interface.name} {interface.kind} {interface.status} {interface.addresses}"
}
Ok void
}InterfaceAddress#
type InterfaceAddress with Show, Equals {
address: IpAddress
prefixLength: Int
fn show(): String
fn subnet(): Result<Subnet, OsError>
}One address of an interface with the length of its network prefix, shown as 192.168.1.23/24. The address holds no
zone. subnet() is the network the address is in, 192.168.1.0/24; it is an OsError.Malformed for a prefix length
below 0 or above 32 (IPv4) or 128 (IPv6), which the system never writes and a value built by hand can.
use InterfaceAddress, NetworkInterface, OsError from "std/os"
fn main(): Result<Void, OsError> {
for interface in NetworkInterface.all()? {
for address in interface.addresses {
print "{address} is in {address.subnet()?}"
}
}
Ok void
}InterfaceStatus and InterfaceKind#
type InterfaceStatus with Show, Equals {
case Up
case Down
case Unknown
}type InterfaceKind with Show, Equals {
case Ethernet
case Wireless
case Loopback
case Tunnel
case Other
}Unknown is not Down: the system did not say. Other is anything the kinds above do not name.
OsError#
type OsError with Show, Equals, Error {
case Unsupported(question: String, operatingSystem: OperatingSystem)
case Denied(question: String, reason: String)
case Missing(question: String, reason: String)
case Failed(question: String, message: String)
case Malformed(source: String, detail: String)
fn show(): String
}What asking the operating system went wrong with. Unsupported is a question the system cannot answer at all;
Denied a refusal (a permission, a container that hides /proc); Missing a question without an answer on this
machine (no HOME and no account entry); Failed the system's own words for anything else; Malformed an answer in
a form its reader did not expect. No error code of a system is carried, because a portable caller that could read one
would compare it. A cancelled question is no case of it: await() passes a cancellation on to the waiting task
instead of answering it (see std/task).
The per-system modules#
Each directory of std/os/src/ holds what one system, or one standard several share, has: windows/, linux/,
macos/, freebsd/ and browser/ (which has no natives), and posix/, bsd/ (the sysctl interface of macOS and
FreeBSD), xdg/ and x86/ (the cpuid of an x86-64 processor, which is the same on every system, and the pure
decoders of what it answers: vendor, brand, features, caches and topology). Their
native.trb modules are the raw natives of runtime/os/, and a program that needs one imports it directly
(use Windows from "std/os/windows/native") and reaches it from the arm of its system; torb check --every-target
reports a native that is reached on a system it does not exist on. They are not the documented surface of the package.
Related#
- std/core -
OperatingSystem,ArchitectureandByteOrder, the constants a branch on the target reads. - Compile-time branches - the rule that keeps one arm of a
matchon the target. - std/sandbox -
SandboxCapabilities.environment, which grants the patterns a script may read. - std/path -
Path, whatDirectories,Environment.searchPathandCurrentProcessanswer. - std/process -
Process, the arguments,exitand the children of the current process. - std/number -
ByteSize, whatMemory,SwapandSystem.pageSizeanswer. - The standard library - the other packages.