1-- The hosted provider whose waiting operations block the thread (D476),
2-- `host`: reading and writing, running programs, their pipes and their
3-- ends in programs.ldn (D479), core/net's network in network.ldn,
4-- core/time's monotonic clock and its sleep in time.ldn, and core/fibre's
5-- scheduler in fibre.ldn, running each task to its end. It is built over
6-- a hosted/system handle and hands it every service that does not wait,
7-- in delegated.ldn, so one handle is an `io.world` and every concept the
8-- system supplies. Written once over platform/posix, like the system.
9-- Import this module only at a hosted authority root.
10
11import core/io
12import core/text
13import hosted/system
14import platform/posix
15
16blocking_value: type = struct
17 services: system.host
18end blocking_value
19
20--- Hosted implementation of all four I/O capabilities and so of
21--- `io.world`, of `io.programs`, `net.network`, the monotonic `time.clock`
22--- and `fibre.scheduler`, whose waits block the thread, and of every
23--- concept `hosted/system` supplies, which it hands the system's handle it
24--- is built over: `io.directories`, `io.environment`, `io.file_system`,
25--- `io.terminals`, `io.entropy` and `time.wall_clock`. A child is started
26--- by that handle's `start`, which does not wait (D479).
27--- A handle: copies share the process's descriptors, arguments and
28--- environment, and the last-error and last-signal cells they were made
29--- with. Its files are the system's, as `hosted/system`'s `descriptor_of`
30--- and `file_of` give them; borrowed arguments and variables refer to
31--- process storage.
32public host: type = blocking_value
33
34--- The system's last-error and last-signal cells of the caller's own,
35--- `hosted/system`'s `space`, for `over`.
36public space: type = system.space
37
38--- Return errno captured for the terminal failing provider call, as
39--- `hosted/system`'s `last_errno` does: the cell is the system handle's,
40--- which this provider's own calls report through too. Zero means the
41--- handle's most recent call of any concept did not fail with an errno.
42public last_errno: (handle: host) -> (number: i32) =
43 number = system.last_errno(handle.services)
44end last_errno
45
46--- Return the number of the signal that ended the child this handle's
47--- cell last saw end through `io.wait`, as `hosted/system`'s
48--- `last_signal` does; zero when it exited with a code or none was waited
49--- for.
50public last_signal: (handle: host) -> (number: i32) =
51 number = system.last_signal(handle.services)
52end last_signal
53
54-- Keep a failure's errno in the system handle's cell.
55record: (self: host, problem: posix.errno) -> none =
56 system.record_errno(self.services, problem)
57end record
58
59-- Clear the cell before a call asks the system.
60cleared: (self: host) -> none =
61 system.clear_errno(self.services)
62end cleared
63
64-- Write some of `bytes` to `opened`: a send on a socket, a write
65-- otherwise.
66put_some: (opened: io.file, bytes: []u8) -> (count: usize) ! posix.errno =
67 if system.is_socket(opened) then
68 count = try posix.send(system.descriptor_of(opened), bytes)
69 else
70 count = try posix.write(system.descriptor_of(opened), bytes)
71 end if
72end put_some
73
74blocking_read: (self: host, opened: io.file, into: []mut u8)
75 -> (count: usize) ! io.io_failed =
76 cleared(self)
77 if lenof into == 0 then
78 count = 0
79 return
80 end if
81 loop do
82 -- read returns EINTR only before transferring a byte, so the same
83 -- buffer and extent may be retried. A positive partial read is
84 -- returned and is never discarded by an unnecessary retry.
85 got: usize = posix.read(system.descriptor_of(opened), into)
86 else (problem)
87 continue when problem == posix.eintr
88 record(self, problem)
89 fail io.io_failed
90 end
91 -- A count beyond the buffer violates the system's contract and
92 -- fabricates no errno. Zero from a nonempty read is genuine EOF.
93 fail io.io_failed when got > lenof into
94 count = got
95 return
96 end loop
97end blocking_read
98
99write_all: (self: host, opened: io.file, bytes: []u8)
100 -> none ! io.io_failed =
101 mut written: usize = 0
102 while written < lenof bytes do
103 remaining: []u8 = bytes[written..<lenof bytes]
104 -- write reports EINTR only before this attempt transfers data.
105 -- Retry the same remaining suffix; a preceding positive partial
106 -- write has already advanced `written` and is never repeated.
107 accepted: usize =
108 put_some(opened, remaining) else (problem)
109 continue when problem == posix.eintr
110 record(self, problem)
111 fail io.io_failed
112 end
113 -- A nonempty zero-progress success cannot complete write-all, and
114 -- an oversized count violates the system's contract. Neither
115 -- fabricates errno.
116 fail io.io_failed when accepted == 0 or accepted > lenof remaining
117 written += accepted
118 end while
119end write_all
120
121blocking_write: (self: host, opened: io.file, bytes: []u8)
122 -> none ! io.io_failed =
123 cleared(self)
124 return when lenof bytes == 0
125 try write_all(self, opened, bytes)
126end blocking_write
127
128blocking_write_some: (self: host, opened: io.file, bytes: []u8)
129 -> (count: usize) ! io.io_failed =
130 cleared(self)
131 if lenof bytes == 0 then
132 count = 0
133 return
134 end if
135 -- Keep a single host request within a representable, bounded extent;
136 -- the caller can send the remaining suffix after this progress.
137 mut requested: usize = lenof bytes
138 if requested > 65536 then requested = 65536 end if
139 loop do
140 -- EINTR transfers nothing on this attempt. A positive short write
141 -- returns immediately so its exact prefix reaches the caller.
142 accepted: usize = put_some(opened, bytes[0..<requested])
143 else (problem)
144 continue when problem == posix.eintr
145 record(self, problem)
146 fail io.io_failed
147 end
148 fail io.io_failed when accepted == 0 or accepted > requested
149 count = accepted
150 return
151 end loop
152end blocking_write_some
153
154-- What does not wait is the system's: its files, streams and arguments.
155blocking_open_read: (self: host, path: text.host_string)
156 -> (opened: io.file)
157 ! io.not_found | io.no_access | io.io_failed =
158 opened = try io.open_read(self.services, path)
159end blocking_open_read
160
161blocking_open_write: (self: host, path: text.host_string)
162 -> (opened: io.file)
163 ! io.not_found | io.no_access | io.io_failed =
164 opened = try io.open_write(self.services, path)
165end blocking_open_write
166
167blocking_same_file: (self: host, left: text.host_string,
168 right: text.host_string)
169 -> (same: bool) ! io.io_failed =
170 same = try io.same_file(self.services, left, right)
171end blocking_same_file
172
173blocking_close: (self: host, sink opened: io.file) -> none ! io.io_failed =
174 try io.close(self.services, opened)
175end blocking_close
176
177blocking_input: (self: host) -> (stream: io.file) =
178 stream = io.input(self.services)
179end blocking_input
180
181blocking_out: (self: host) -> (stream: io.file) =
182 stream = io.out(self.services)
183end blocking_out
184
185blocking_err: (self: host) -> (stream: io.file) =
186 stream = io.err(self.services)
187end blocking_err
188
189blocking_argument_count: (self: host) -> (count: usize) =
190 count = io.argument_count(self.services)
191end blocking_argument_count
192
193blocking_argument: (self: host, index: usize)
194 -> (value: text.host_string from self) ! io.no_argument =
195 value = try io.argument_at(self.services, index)
196end blocking_argument
197
198host is io.reader (read: blocking_read)
199host is io.writer (write: blocking_write, write_some: blocking_write_some)
200host is io.files
201 (open_read: blocking_open_read, open_write: blocking_open_write,
202 same_file: blocking_same_file, close: blocking_close)
203host is io.process
204 (input: blocking_input, out: blocking_out, err: blocking_err,
205 argument_count: blocking_argument_count, argument: blocking_argument)
206host is io.world ()
207
208--- Return a handle to the hosted provider whose waits block the thread,
209--- over a new `hosted/system` handle: the process's arguments, the three
210--- standard streams, descriptors 0, 1 and 2, and the process's last-error
211--- cell. Regular open-write operations create or truncate files; close
212--- regular files explicitly. The handle borrows nothing. Use `over` for a
213--- cell of the caller's own.
214---
215--- Calling this before the hosted entry has established the process's
216--- arguments traps, as `hosted/system`'s `new` does.
217public new: () -> (handle: host) =
218 handle = (services: system.new())
219end new
220
221--- Fill `state` and return a handle that records its last error there
222--- rather than in the process's cell, over `hosted/system`'s `over`. It
223--- shares the process's arguments and descriptors like any other handle;
224--- calling this before any root exists traps, as `new` does.
225public over: (inout state: space) -> (handle: host from state) =
226 handle = (services: system.over(state))
227end over