1-- Linux/Darwin libc-backed provider of every core/io concept: reader,
2-- writer, files and process, and so of their composition, world.
3-- Import this module only at a hosted authority root.
4
5import core/io
6
7extern(c) _landin_host_argument_count: () -> (count: usize)
8extern(c) _landin_host_argument_table: () -> (table: ptr ptr u8)
9extern(c) _landin_host_argument_at_from: (table: ptr ptr u8, index: usize)
10 -> (data: ptr u8 from table)
11extern(c) _landin_host_text_length: (data: ptr u8) -> (length: usize)
12extern(c) _landin_host_open_read: (path: ptr u8) -> (descriptor: i32)
13extern(c) _landin_host_open_write: (path: ptr u8) -> (descriptor: i32)
14extern(c) _landin_host_read: (descriptor: i32, data: ptr mut u8,
15 length: usize) -> (read_count: usize)
16extern(c) _landin_host_write: (descriptor: i32, data: ptr u8,
17 length: usize) -> (write_count: usize)
18extern(c) _landin_host_close: (descriptor: i32) -> (status: i32)
19extern(c) _landin_host_errno: () -> (number: i32)
20
21-- Values shared by Linux and Darwin (historical names). Kept at the only
22-- provider that interprets libc errno, rather than leaking into its users.
23linux_eperm: i32 = 1
24linux_enoent: i32 = 2
25linux_eintr: i32 = 4
26linux_eacces: i32 = 13
27linux_erofs: i32 = 30
28
29system_value: type = struct
30 arguments: ptr ptr u8
31 argument_length: usize
32 failure_errno: i32
33end system_value
34
35--- Hosted implementation of all four I/O capabilities and `io.world`, backed
36--- by libc. File identifiers belong to this provider; borrowed arguments
37--- refer to process storage.
38public system: type = system_value
39
40--- Return errno captured for the terminal failing provider call. Read it
41--- immediately after failure; it is provider state, not a portable error
42--- category.
43---
44--- Zero means that the most recent open/read/write/close did not report a
45--- failing libc call. A nonzero value is the exact errno captured for the
46--- terminal call before the provider made any other host call. Retriable
47--- EINTR attempts are internal and do not replace a previously reported
48--- operation with a failure.
49public last_errno: (state: system) -> (number: i32) =
50 number = state.failure_errno
51end last_errno
52
53system_open_read: (self: ptr mut system, path: ptr u8)
54 -> (opened: io.file) ! io.not_found | io.no_access | io.io_failed =
55 self.val.failure_errno = 0
56 loop do
57 descriptor: i32 = _landin_host_open_read(path)
58 if descriptor >= 0 then
59 opened = io.file(descriptor)
60 return
61 end if
62
63 -- Nothing may call into the host between the failed operation and
64 -- this capture. Linux open reports EINTR before publishing a file
65 -- descriptor, so retrying this same request is safe.
66 number: i32 = _landin_host_errno()
67 if number <> linux_eintr then
68 self.val.failure_errno = number
69 fail io.not_found when number == linux_enoent
70 fail io.no_access when number == linux_eperm or number == linux_eacces
71 or number == linux_erofs
72 fail io.io_failed
73 end if
74 end loop
75end system_open_read
76
77system_open_write: (self: ptr mut system, path: ptr u8)
78 -> (opened: io.file)
79 ! io.not_found | io.no_access | io.io_failed =
80 self.val.failure_errno = 0
81 loop do
82 descriptor: i32 = _landin_host_open_write(path)
83 if descriptor >= 0 then
84 opened = io.file(descriptor)
85 return
86 end if
87
88 -- The bridge's write-open request is fixed across attempts. An
89 -- interrupted open has not returned a descriptor, so retry it; every
90 -- other errno is retained before mapping to the stable atom set.
91 number: i32 = _landin_host_errno()
92 if number <> linux_eintr then
93 self.val.failure_errno = number
94 fail io.not_found when number == linux_enoent
95 fail io.no_access when number == linux_eperm or number == linux_eacces
96 or number == linux_erofs
97 fail io.io_failed
98 end if
99 end loop
100end system_open_write
101
102-- A stable-path identity check, not an atomic check-and-open operation.
103-- Every input lookup failure refuses; only an absent output is allowed.
104system_same_file: (self: ptr mut system, left: ptr u8, right: ptr u8)
105 -> (same: bool) ! io.io_failed =
106 self.val.failure_errno = 0
107 fail io.io_failed when alignof native_stat <> 8
108 or sizeof native_stat <> native_stat_size
109 mut first: native_stat = zeroed
110 mut second: native_stat = zeroed
111 if stat(left, addr first) <> 0 then
112 self.val.failure_errno = _landin_host_errno()
113 fail io.io_failed
114 end if
115 if stat(right, addr second) <> 0 then
116 number: i32 = _landin_host_errno()
117 if number == linux_enoent then
118 same = false
119 return
120 end if
121 self.val.failure_errno = number
122 fail io.io_failed
123 end if
124 same = first.device == second.device and first.inode == second.inode
125end system_same_file
126
127system_close: (self: ptr mut system, sink opened: io.file)
128 -> none ! io.io_failed =
129 self.val.failure_errno = 0
130 status: i32 = _landin_host_close(i32(opened))
131 if status <> 0 then
132 -- Capture first, then report. A failed Linux close may already have
133 -- released the descriptor; retrying could close an unrelated reused
134 -- descriptor, so EINTR is deliberately terminal here.
135 self.val.failure_errno = _landin_host_errno()
136 fail io.io_failed
137 end if
138end system_close
139
140system_read: (self: ptr mut system, opened: io.file, into: []mut u8)
141 -> (count: usize) ! io.io_failed =
142 self.val.failure_errno = 0
143 if lenof into == 0 then
144 count = 0
145 return
146 end if
147 base: ptr mut u8 = addr into[0]
148 failed: usize = 0 -% 1
149 loop do
150 read_count: usize = _landin_host_read
151 (i32(opened), base, lenof into)
152 if read_count <> failed then
153 fail io.io_failed when read_count > lenof into
154 -- Zero from a nonempty read is genuine EOF, not stalled progress.
155 count = read_count
156 return
157 end if
158
159 -- Linux read returns EINTR only before transferring a byte, so the
160 -- same buffer and extent may be retried. A positive partial read is
161 -- returned above and is never discarded by an unnecessary retry.
162 number: i32 = _landin_host_errno()
163 if number <> linux_eintr then
164 self.val.failure_errno = number
165 fail io.io_failed
166 end if
167 end loop
168end system_read
169
170write_all: (self: ptr mut system, descriptor: i32, bytes: []u8)
171 -> none ! io.io_failed =
172 failed: usize = 0 -% 1
173 mut written: usize = 0
174 while written < lenof bytes do
175 remaining: []u8 = bytes[written..<lenof bytes]
176 count: usize = lenof remaining
177 base: ptr u8 = addr remaining[0]
178 mut accepted: usize = failed
179 loop do
180 accepted = _landin_host_write(descriptor, base, count)
181 break when accepted <> failed
182
183 -- Linux reports EINTR only before this attempt transfers data.
184 -- Retry the same remaining suffix; a preceding positive partial
185 -- write has already advanced `written` and is never repeated.
186 number: i32 = _landin_host_errno()
187 if number <> linux_eintr then
188 self.val.failure_errno = number
189 fail io.io_failed
190 end if
191 end loop
192 -- A nonempty zero-progress success cannot complete write-all, and an
193 -- oversized count violates the host seam. Neither fabricates errno.
194 fail io.io_failed when accepted == 0 or accepted > count
195 written += accepted
196 end while
197end write_all
198
199system_write: (self: ptr mut system, opened: io.file, bytes: []u8)
200 -> none ! io.io_failed =
201 self.val.failure_errno = 0
202 return when lenof bytes == 0
203 try write_all(self, i32(opened), bytes)
204end system_write
205
206system_write_some: (self: ptr mut system, opened: io.file, bytes: []u8)
207 -> (count: usize) ! io.io_failed =
208 self.val.failure_errno = 0
209 if lenof bytes == 0 then
210 count = 0
211 return
212 end if
213 failed: usize = 0 -% 1
214 base: ptr u8 = addr bytes[0]
215 -- Keep a single host request within a representable, bounded extent;
216 -- the caller can send the remaining suffix after this progress.
217 mut requested: usize = lenof bytes
218 if requested > 65536 then requested = 65536 end if
219 loop do
220 count = _landin_host_write(i32(opened), base, requested)
221 if count <> failed then
222 fail io.io_failed when count == 0 or count > requested
223 return
224 end if
225 -- EINTR transfers nothing on this attempt. A positive short write
226 -- returns immediately so its exact prefix reaches the caller.
227 number: i32 = _landin_host_errno()
228 if number <> linux_eintr then
229 self.val.failure_errno = number
230 fail io.io_failed
231 end if
232 end loop
233end system_write_some
234
235system_out: (self: ptr system) -> (stream: io.file) =
236 _ = self.val.argument_length
237 stream = io.file(1)
238end system_out
239
240system_err: (self: ptr system) -> (stream: io.file) =
241 _ = self.val.argument_length
242 stream = io.file(2)
243end system_err
244
245system_argument_count: (self: ptr system) -> (count: usize) =
246 count = self.val.argument_length
247end system_argument_count
248
249system_argument_table: (self: ptr system)
250 -> (table: ptr ptr u8 from self) =
251 table = self.val.arguments
252end system_argument_table
253
254system_argument: (self: ptr system, index: usize)
255 -> (value: io.argument from self) ! io.no_argument =
256 fail io.no_argument when index >= self.val.argument_length
257 table: ptr ptr u8 = system_argument_table(self)
258 data: ptr u8 = _landin_host_argument_at_from(table, index)
259 length: usize = _landin_host_text_length(data)
260 value = (data: data, length: length)
261end system_argument
262
263system is io.reader (read: system_read)
264system is io.writer (write: system_write, write_some: system_write_some)
265system is io.files
266 (open_read: system_open_read, open_write: system_open_write,
267 same_file: system_same_file, close: system_close)
268system is io.process
269 (out: system_out, err: system_err,
270 argument_count: system_argument_count, argument: system_argument)
271system is io.world ()
272
273--- Create the hosted I/O provider and expose process arguments and standard
274--- streams. Regular open-write operations create or truncate files; close
275--- regular files explicitly.
276public host: () -> (state: system) =
277 state = (arguments: _landin_host_argument_table(),
278 argument_length: _landin_host_argument_count(),
279 failure_errno: 0)
280end host