hosted/system
Hosted targets only.
Hosted file, stream and process operations, written once over platform/posix: the provider host, made by new, imported as system with no alias (D347).
host supplies the shared I/O concepts. new makes a handle exposing process arguments, lent as host strings, and standard streams, from the root the hosted entry establishes through platform/posix's _landin_host_initialize_arguments; a C main that drives Landin exports calls that initializer itself. Standard input, output and error are descriptors 0, 1 and 2. Open-write creates or truncates regular files. A path that names nothing is not_found, whether a component is missing or one before the last is not a directory, ENOENT or ENOTDIR; same_file answers false for such an output path. host returns a handle that borrows nothing, since the root, the descriptors and its last-error cell belong to the process; over makes a handle that keeps its last errno in a space the caller declares, for a C thread or a library that must not share the process's. last_errno captures the terminal failing call as provider-specific detail, the number of the errno platform/posix failed with; it is not a portable error type. last_signal is the number of the signal that ended the child last waited for, beside io.ending's signalled, and zero after a child that exited. A path that holds a NUL is io_failed with EINVAL, and one longer than the system's PATH_MAX with ENAMETOOLONG, as platform/posix refuses them. host also supplies directories. A directory's buffer must hold at least io.directory_buffer, 1024 bytes, or opening it fails with io_failed before anything is opened, so that every entry a hosted system gives fits. It is read through platform/posix's directory streams, . and .. left out. An entry whose type the file system does not record is asked with a status call relative to the directory. make_directory gives a new directory 0777, which the process umask filters, and is not retried when interrupted, because a directory made before the interruption would then report already_exists.
The environment is the one the process's entry was given, read where the entry left it, never copied and never changed. host also supplies programs, over platform/posix's spawn: a discarded stream is the system's /dev/null, and a piped one a pipe whose child end is closed in the parent once the child has it. A child receives only its three streams, since every descriptor the provider makes is closed on exec and the spawn closes any other. read_child polls both pipes when both are open and reads the output first when both have something. wait closes the child's pipes before it waits, so a child still writing to one meets a closed pipe rather than a parent that never reads. An interrupted spawn, poll, read or wait is made again.
host supplies network too, over platform/posix's sockets: a socket is a descriptor and a file of the provider like any other, so read and write take a connection, and a write to a socket the provider made is a send, which raises no SIGPIPE beside a C handler on any system (D340); socket_of makes one from a socket of platform/posix's. A listener reuses its address and an IPv6 socket takes IPv6 alone on every system, which BSD does from the start and Linux does not; up to 128 connections wait to be accepted. An interrupted call is made again, a connection a signal interrupted is waited out with poll and its outcome read from the socket, an accept refused because its connection was reset before it was taken accepts the next, and a send goes on after a short one until every byte is sent. A send to a peer that has gone is disconnected, EPIPE or ECONNRESET kept as the last errno, and a write to it io_failed with EPIPE: the process ignores SIGPIPE, so neither ends it.
host supplies file_system and terminals too, over platform/posix. open_file creates a file 0666, which the process umask filters, and refuses options no open can have with io_failed and EINVAL kept before it asks the system; a taken name exclusive meets is already_exists. rename is renameat, ENOENT not_found and the rest io_failed with the errno kept, and is not retried when interrupted. A file is read_only when no permission bit lets anyone write it; set_read_only clears every write bit, or sets the owner's again, reading the bits and setting them in two calls. synchronize is fsync, or fdatasync for the data alone, and on Darwin F_FULLFSYNC either way. A temporary file is created exclusively with 0600, and when every try finds its name taken the last errno is EEXIST. window_size of anything but a terminal is not_a_terminal, ENOTTY kept. descriptor_of gives the platform/posix descriptor a file of this provider is, for a call the portable concepts do not make, a permission bit, a lock or a terminal's raw mode, and file_of the file a descriptor is.
host supplies clock, wall_clock and entropy too. now is platform/posix's monotonic clock, in nanoseconds since the system started, and wall_now its realtime clock; a clock the system cannot read ends the program, since no hosted system fails to. sleep takes its deadline from the monotonic clock and asks the system to sleep for what is left until that clock has reached it, so a sleep a signal interrupts, or the system ends early, sleeps on, and it never returns before the deadline as now reads it. fill_random is platform/posix's random_bytes until the buffer is full, asked again after an interruption: getrandom on Linux, which waits at boot until the kernel's pool is ready, and arc4random_buf on Darwin and FreeBSD. runtime/clock-system holds all three to their oracles, and the published shell runner times its child.
Items
- host type
- space type
- descriptor_of function
- file_of function
- last_errno function
- last_signal function
- new function
- over function
- socket_of function
Executable example
This complete program is maintained in the repository runtime tests. View source.
import core/io import core/text import hosted/system import platform/posix -- A host string is the bytes it was made over, lent and unconverted: -- these are not UTF-8, which the checked conversion refuses, and they -- spell no text but their own. host_string_rules_code: () -> (code: i32) = code = 10 raw: []u8 = "A\xffB" name: text.host_string = text.host_over(raw) bytes: []u8 = text.host_bytes(name) return when lenof bytes <> 3 or usize(addr bytes[0]) <> usize(addr raw[0]) code = 11 shown: utf8 = text.from_host(name) else (problem) if problem == text.invalid_text then code = 12 end if "" end _ = shown return when code <> 12 code = 13 plain: text.host_string = text.host_over("plain") return when not text.host_equal(plain, "plain") or text.host_equal(plain, "plai") or text.host_equal(name, "AB") code = 14 as_text: utf8 = text.from_host(plain) else (problem) _ = problem return end return when not text.equal(as_text, "plain") code = 42 end host_string_rules_code -- What the system says of a path no file can have: an empty one names -- nothing, ENOENT; one holding a NUL is refused by the POSIX layer with -- EINVAL, since no system can be given it; and one longer than the -- system's PATH_MAX is ENAMETOOLONG. Each errno is kept. path_rules_code: (state: system.host) -> (code: i32) = mut opened: io.file = io.out(state) code = 32 opened = io.open_read(state, text.host_over("")) else (problem) if problem <> io.not_found or system.last_errno(state) <> posix.number(posix.enoent) then return end if code = 33 io.out(state) end return when code <> 33 opened = io.open_read(state, text.host_over("A\x00B")) else (problem) if problem <> io.io_failed or system.last_errno(state) <> posix.number(posix.einval) then return end if code = 34 io.out(state) end return when code <> 34 long: [5000]u8 = [5000 of 97] opened = io.open_read(state, text.host_over(long[0..<5000])) else (problem) if problem <> io.io_failed or system.last_errno(state) <> posix.number(posix.enametoolong) then return end if code = 42 io.out(state) end end path_rules_code no_second_argument: (state: any io.world) -> (yes: bool) = second: usize = 1 unexpected := state.argument(second) else (problem) yes = problem == io.no_argument return end _ = text.host_bytes(unexpected) yes = false end no_second_argument missing_read_is_reported: (state: any io.world, path: text.host_string) -> (yes: bool) = opened := state.open_read(path) else (problem) yes = problem == io.not_found return end state.close(opened) else none yes = false end missing_read_is_reported missing_write_is_reported: (state: any io.world, path: text.host_string) -> (yes: bool) = opened := state.open_write(path) else (problem) yes = problem == io.not_found return end state.close(opened) else none yes = false end missing_write_is_reported denied_write_is_reported: (state: any io.world, path: text.host_string) -> (yes: bool) = opened := state.open_write(path) else (problem) yes = problem == io.no_access return end state.close(opened) else none yes = false end denied_write_is_reported public main: () -> (code: i32) = code = 1 string_code: i32 = host_string_rules_code() if string_code <> 42 then code = string_code return end if host := system.new() code = 2 if io.argument_count(host) <> 1 then return end if direct_index: usize = 0 direct_source := io.argument_at(host, direct_index) else (problem) _ = problem return end code = 3 direct_bytes: []u8 = text.host_bytes(direct_source) if lenof direct_bytes == 0 then return end if path_code: i32 = path_rules_code(host) if path_code <> 42 then code = path_code return end if world: any io.world = any(addr host) code = 4 if world.argument_count() <> 1 then return end if code = 5 first: usize = 0 source := world.argument(first) else (problem) _ = problem return end code = 6 source_bytes: []u8 = text.host_bytes(source) if lenof source_bytes == 0 then return end if return when not no_second_argument(world) code = 7 -- The argument is passed on as a path, never converted. opened := world.open_read(source) else (problem) _ = problem return end code = 8 mut copied_opened: io.file = opened mut input: [13]u8 = zeroed count: usize = world.read(opened, input[0..<13]) else (problem) _ = problem return end code = 9 if count <> 13 or input[0] <> 112 or input[6] <> 32 or input[12] <> 10 then return end if code = 14 eof: usize = world.read(opened, input[0..<13]) else (problem) _ = problem return end if eof <> 0 then return end if code = 15 world.close(opened) else (problem) _ = problem return end code = 16 world.close(copied_opened) else (problem) if problem <> io.io_failed then return end if copied_opened = world.out() end code = 17 generic_host := system.new() written_path: text.host_string = text.host_over("/tmp/landin-r420-io-world.out") code = 18 output := io.open_write(generic_host, written_path) else (problem) _ = problem return end code = 19 payload: [7]u8 = [76, 97, 110, 100, 105, 110, 10] world.write(output, payload[0..<0]) else (problem) _ = problem return end world.write(output, payload[0..<7]) else (problem) _ = problem return end code = 20 world.close(output) else (problem) _ = problem return end code = 21 verify := io.open_read(generic_host, written_path) else (problem) _ = problem return end code = 22 mut result: [8]u8 = zeroed result_length: usize = io.read(generic_host, verify, result[0..<8]) else (problem) _ = problem return end code = 23 io.close(generic_host, verify) else (problem) _ = problem return end code = 24 if result_length <> 7 or result[0] <> 76 or result[1] <> 97 or result[2] <> 110 or result[3] <> 100 or result[4] <> 105 or result[5] <> 110 or result[6] <> 10 then return end if code = 25 missing_path: text.host_string = text.host_over("/definitely-not-a-landin-directory-r420/file") return when not missing_read_is_reported(world, missing_path) code = 26 return when not missing_write_is_reported(world, missing_path) code = 27 denied_path: text.host_string = text.host_over("/sys/landin-r420-denied") return when not denied_write_is_reported(world, denied_path) code = 28 full_path: text.host_string = text.host_over("/dev/full") full := world.open_write(full_path) else (problem) _ = problem return end code = 29 world.write(full, payload[0..<0]) else (problem) _ = problem return end mut saw_write_failure: bool = false world.write(full, payload[0..<7]) else (problem) if problem <> io.io_failed then return end if saw_write_failure = true end return when not saw_write_failure code = 30 world.close(full) else (problem) _ = problem return end code = 31 output_stream := world.out() error_stream := world.err() world.write(error_stream, payload[0..<0]) else (problem) _ = problem return end okay: [3]u8 = [79, 75, 10] world.write(output_stream, okay[0..<3]) else (problem) _ = problem return end code = 42 end main
host type
public host: type = system_valueHosted implementation of all four I/O capabilities, io.world, io.directories, io.environment, io.programs, the clocks time.clock and time.wall_clock, the system's io.entropy, net.network, io.file_system and io.terminals, over platform/posix, as a handle: copies share the process's descriptors, arguments and environment, and the last-error and last-signal cells they were made with. File identifiers belong to this provider; borrowed arguments and variables refer to process storage.
space type
public space: type = struct
failure: i32
signal: i32
end spaceA last-error cell and a last-signal cell of the caller's own, for code that must not share the process's: a C thread calling into Landin, or a library keeping its own detail. Declare it zeroed and make its handle with over.
descriptor_of function
public descriptor_of: (opened: io.file) -> (result: posix.descriptor)The platform/posix descriptor a file of this provider is, for a call the portable concepts do not make: a file's permission bits, a lock, a terminal's raw mode. The file stays this provider's, to close through io.close.
file_of function
public file_of: (opened: posix.descriptor) -> (result: io.file)The file of this provider a platform/posix descriptor is, so io.read, io.write and the rest take one that platform/posix opened. Close it once, through either. A socket platform/posix made is written with POSIX's write through it, which on Linux raises SIGPIPE when its peer has gone; socket_of makes one whose writes are sends.
last_errno function
public last_errno: (handle: host) -> (number: i32)Return errno captured for the terminal failing provider call. Read it immediately after failure; it belongs to the cell the handle was made with, the process's from host or the caller's from over, and is not a portable error category.
Zero means that the handle's most recent call of any concept did not fail with an errno: each call clears the cell before it asks the system. A nonzero value is the exact errno captured for the terminal call before the provider made any other host call: its number as the system gives it, platform/posix's number of the atom the call failed with. Retriable EINTR attempts are internal and do not replace a previously reported operation with a failure.
last_signal function
public last_signal: (handle: host) -> (number: i32)Return the number of the signal that ended the child this handle's cell last saw end through io.wait, as POSIX numbers it on this system: SIGTERM is 15 and SIGKILL 9 on Linux, Darwin and FreeBSD alike. Zero means that child exited with a code, or that no child has been waited for. It is the system's detail behind io.ending's portable signalled, as last_errno is behind an error atom.
new function
public new: () -> (handle: host)Return a handle to the hosted I/O provider, exposing process arguments and the three standard streams, descriptors 0, 1 and 2. Regular open-write operations create or truncate files; close regular files explicitly. The handle borrows nothing: the argument root, the descriptors and its last-error cell belong to the process. Use over for a cell of the caller's own.
The arguments are the root platform/posix's _landin_host_initialize_arguments established, which the hosted entry establishes before its body runs; calling this before any root exists traps.
over function
public over: (inout state: space) -> (handle: host from state)Fill state and return a handle that records its last error there rather than in the process's cell. It shares the process's arguments and descriptors like any other handle; calling this before any root exists traps, as host does.
socket_of function
public socket_of: (opened: posix.descriptor) -> (result: io.file)The file of this provider a platform/posix socket is: io.write sends on it as net.send does, so a peer that has gone fails the write with EPIPE on every system and raises no SIGPIPE, beside a C handler too. net.connect, net.listen, net.accept and net.open_datagram give their sockets so.