1-- POSIX's facilities in Landin types, on the systems that give them:
2-- Linux, Darwin and FreeBSD. A descriptor is a handle type of its own,
3-- a failure is the errno as an atom whose number `number` keeps, a path
4-- or an argument is a host string, and a file's status is a record. It
5-- is written twice, beneath this file: once over platform/linux's system
6-- calls in linux.ldn, and once over platform/libc, the C library Darwin
7-- and FreeBSD give as their system interface, in libc.ldn. hosted/io and
8-- hosted/heap are written once over it. A system that is not POSIX, as
9-- Windows is not, has no platform/posix: its providers implement the
10-- core concepts over its own interface instead.
11--
12-- The calls are POSIX's own, not policy: a call interrupted by a signal
13-- fails with `eintr` and is not retried here, a short read or write is
14-- returned as it came, and nothing is closed on the caller's behalf. A
15-- path is copied into a buffer of the system's PATH_MAX with a NUL after
16-- it on its way to the system, so the terminator is this module's and
17-- never the caller's: a path holding a NUL fails with `einval`, as no
18-- system could be given it, and one too long for the buffer with
19-- `enametoolong`, as the system says of a path longer than it takes.
20
21import core/text
22
23compiler.assert(compiler.os == linux or compiler.os == darwin
24 or compiler.os == freebsd)
25
26descriptor_value: type = distinct i32
27
28--- An open file's descriptor: a handle the system gives, which names one
29--- open file until it is closed. It is a handle and not a number to
30--- count with; `descriptor(n)` and `i32(d)` convert at a C boundary.
31public descriptor: type = descriptor_value
32
33--- Standard input, descriptor 0.
34public standard_input: descriptor = descriptor_value(0)
35--- Standard output, descriptor 1.
36public standard_output: descriptor = descriptor_value(1)
37--- Standard error, descriptor 2.
38public standard_error: descriptor = descriptor_value(2)
39
40fixed if compiler.os == darwin then
41 --- The working directory as the directory an `*_at` call's relative
42 --- path is relative to: C's `AT_FDCWD`, which is no open file.
43 public working_directory: descriptor = descriptor_value(-2)
44 -- PATH_MAX: room for the longest path a call takes, its NUL
45 -- included.
46 path_room: type = [1024]u8
47elsif compiler.os == freebsd then
48 --- The working directory as the directory an `*_at` call's relative
49 --- path is relative to: C's `AT_FDCWD`, which is no open file.
50 public working_directory: descriptor = descriptor_value(-100)
51 path_room: type = [1024]u8
52else
53 --- The working directory as the directory an `*_at` call's relative
54 --- path is relative to: C's `AT_FDCWD`, which is no open file.
55 public working_directory: descriptor = descriptor_value(-100)
56 path_room: type = [4096]u8
57end if
58
59--- Open for reading only.
60public read_only: atom
61--- Open for writing only.
62public write_only: atom
63--- Open for reading and writing.
64public read_write: atom
65--- How an open file may be used: `read_only`, `write_only` or
66--- `read_write`.
67public access: type = read_only | write_only | read_write
68
69--- How `open_at` opens a file: its access, and whether to create it when
70--- it is missing, refuse it when it exists (`exclusive`, with `create`),
71--- truncate it, append every write to its end, or refuse anything that is
72--- not a directory with `enotdir`.
73public open_options: type = struct
74 access: access
75 create: bool
76 exclusive: bool
77 truncate: bool
78 append: bool
79 directory: bool
80end open_options
81
82--- The options for reading an existing file.
83public reading: open_options = (access: read_only, create: false,
84 exclusive: false, truncate: false,
85 append: false, directory: false)
86
87--- The options for writing a file from its start, creating it when it is
88--- missing and truncating it when it is not.
89public writing: open_options = (access: write_only, create: true,
90 exclusive: false, truncate: true,
91 append: false, directory: false)
92
93--- Open the file `path` names, relative to the directory open as `at`
94--- (or `working_directory`), with `options`; a file it creates gets the
95--- permission bits `permissions`, which the process umask filters. The
96--- new descriptor, or the errno: `enoent` for a missing name, `enotdir`
97--- when a component is not a directory, `eacces` when refused, `eexist`
98--- for an `exclusive` name that is taken, `eintr` when a signal came
99--- first.
100public link(symbol: "_landin_posix_open_at") open_at:
101 (at: descriptor, path: text.host_string,
102 options: open_options, permissions: u32)
103 -> (opened: descriptor) ! errno =
104 mut room: path_room = zeroed
105 terminated: ptr u8 = try terminate(path, room[0..<lenof room])
106 opened = try system_open(at, terminated, options, permissions)
107end open_at
108
109--- Read at most `lenof into` bytes from `source` into `into`: the count
110--- read, zero at the end of a file, or the errno; `eintr` means nothing
111--- was read.
112public link(symbol: "_landin_posix_read") read:
113 (source: descriptor, into: []mut u8) -> (count: usize) ! errno =
114 count = try system_read(source, into)
115end read
116
117--- Write at most `lenof bytes` bytes of `bytes` to `to`: the count
118--- accepted, which may be short, or the errno; `eintr` means nothing was
119--- written.
120public link(symbol: "_landin_posix_write") write:
121 (to: descriptor, bytes: []u8) -> (count: usize) ! errno =
122 count = try system_write(to, bytes)
123end write
124
125--- Close `closing`. The descriptor is released even when this fails, so
126--- a failed close is never retried: the number may already name another
127--- file.
128public link(symbol: "_landin_posix_close") close:
129 (closing: descriptor) -> none ! errno =
130 try system_close(closing)
131end close
132
133--- A regular file.
134public regular: atom
135--- A directory.
136public directory: atom
137--- A symbolic link, reported as itself when it is not followed.
138public symbolic_link: atom
139--- A FIFO, a named pipe.
140public fifo: atom
141--- A socket: `socket_file`, since `socket` is the name of a call.
142public socket_file: atom
143--- A character device.
144public character_device: atom
145--- A block device.
146public block_device: atom
147--- A kind the system did not say: a directory entry whose file system
148--- records none, which `status_at` then tells.
149public unknown_kind: atom
150--- What a file is.
151public file_kind: type = regular | directory | symbolic_link | fifo
152 | socket_file | character_device | block_device
153 | unknown_kind
154
155--- A file's status, POSIX's `stat`: the device and inode that identify
156--- it, what it is and its size in bytes. On Linux the device is the
157--- kernel's major number in the upper 32 bits and its minor in the lower,
158--- as `statx` reports them; elsewhere it is the system's `st_dev`. Two
159--- paths name the same file when both identify it alike.
160public status: type = struct
161 device: u64
162 inode: u64
163 kind: file_kind
164 size: u64
165end status
166
167--- The status of the file `path` names, relative to the directory open as
168--- `at` (or `working_directory`), following a final symbolic link when
169--- `follow` is true and reporting the link itself when it is not.
170public link(symbol: "_landin_posix_status_at") status_at:
171 (at: descriptor, path: text.host_string, follow: bool)
172 -> (result: status) ! errno =
173 mut room: path_room = zeroed
174 terminated: ptr u8 = try terminate(path, room[0..<lenof room])
175 result = try system_status(at, terminated, follow)
176end status_at
177
178--- The least a buffer `read_directory` reads into holds: room for any
179--- entry any of the three systems gives, its record and its NUL included.
180public directory_buffer: usize = 1024
181
182--- An open directory stream, POSIX's `DIR`: a value its caller keeps and
183--- passes `inout` to `read_directory`, with the same buffer each time.
184--- Its fields belong to this module: the directory's descriptor, the C
185--- library's stream where there is one, and how much of the buffer the
186--- system has filled and the caller has read.
187public directory_stream: type = struct
188 descriptor: descriptor
189 stream: usize
190 filled: usize
191 cursor: usize
192 finished: bool
193end directory_stream
194
195--- An entry of a directory: its name, lent from the buffer it was read
196--- into until the next read, and its kind, `unknown_kind` where the file
197--- system records none.
198public directory_entry: type = struct
199 name: text.host_string
200 kind: file_kind
201end directory_entry
202
203--- Open the directory `path` names, relative to `at`, for reading:
204--- `enotdir` when it is not a directory, `enoent` when it is missing.
205--- Close it with `close_directory`, which also closes its descriptor.
206public link(symbol: "_landin_posix_open_directory") open_directory:
207 (at: descriptor, path: text.host_string)
208 -> (opened: directory_stream) ! errno =
209 mut room: path_room = zeroed
210 terminated: ptr u8 = try terminate(path, room[0..<lenof room])
211 opened = try system_open_directory(at, terminated)
212end open_directory
213
214--- The directory's next entry, read into `buffer`, which must be the
215--- buffer every earlier read of this stream was given and hold at least
216--- `directory_buffer` bytes, or `einval`. The entry lends its name from
217--- `buffer` until the next read. An entry with an empty name is the end
218--- of the directory. Entries come in the system's order, `.` and `..`
219--- among them, as POSIX's `readdir` gives them.
220public link(symbol: "_landin_posix_read_directory") read_directory:
221 (inout opened: directory_stream, buffer: []mut u8)
222 -> (item: directory_entry from buffer) ! errno =
223 fail einval when lenof buffer < directory_buffer
224 item = try system_read_directory(opened, buffer)
225end read_directory
226
227--- Close the directory stream and its descriptor, which are released even
228--- when this fails.
229public link(symbol: "_landin_posix_close_directory") close_directory:
230 (opened: directory_stream) -> none ! errno =
231 try system_close_directory(opened)
232end close_directory
233
234--- Make the directory `path` names, relative to `at`, with the permission
235--- bits `permissions`, which the process umask filters: `eexist` when the
236--- name is taken, `enoent` when its parent is missing.
237public link(symbol: "_landin_posix_make_directory_at") make_directory_at:
238 (at: descriptor, path: text.host_string,
239 permissions: u32) -> none ! errno =
240 mut room: path_room = zeroed
241 terminated: ptr u8 = try terminate(path, room[0..<lenof room])
242 try system_make_directory(at, terminated, permissions)
243end make_directory_at
244
245--- Remove the name `path`, relative to `at`, of a file that is not a
246--- directory, POSIX's `unlinkat`.
247public link(symbol: "_landin_posix_unlink_at") unlink_at:
248 (at: descriptor, path: text.host_string) -> none ! errno =
249 mut room: path_room = zeroed
250 terminated: ptr u8 = try terminate(path, room[0..<lenof room])
251 try system_remove(at, terminated, false)
252end unlink_at
253
254--- Remove the empty directory `path` names, relative to `at`, POSIX's
255--- `unlinkat` with `AT_REMOVEDIR`: `enotempty` when it holds anything.
256public link(symbol: "_landin_posix_remove_directory_at") remove_directory_at:
257 (at: descriptor, path: text.host_string)
258 -> none ! errno =
259 mut room: path_room = zeroed
260 terminated: ptr u8 = try terminate(path, room[0..<lenof room])
261 try system_remove(at, terminated, true)
262end remove_directory_at
263
264--- Map `length` bytes of fresh, zeroed memory that this process alone may
265--- read and write, POSIX's anonymous private `mmap`: its first byte, which
266--- is aligned to a page, or the errno, `enomem` when the system has no
267--- room.
268public link(symbol: "_landin_posix_map") map:
269 (length: usize) -> (base: ptr mut u8) ! errno =
270 base = try system_map(length)
271end map
272
273--- Give back the `length` bytes mapped at `base`, POSIX's `munmap`.
274public link(symbol: "_landin_posix_unmap") unmap:
275 (base: ptr mut u8, length: usize) -> none ! errno =
276 try system_unmap(base, length)
277end unmap
278
279--- The size of the system's pages in bytes, which `map` aligns to: 16 KiB
280--- on Darwin arm64 and 4 KiB on the others. On Linux it is what the
281--- kernel told a static executable's entry, and 4096 when the C runtime
282--- started the program instead.
283public link(symbol: "_landin_posix_page_size") page_size:
284 () -> (bytes: usize) =
285 bytes = system_page_size()
286end page_size
287
288--- End the process with `status` at once, POSIX's `_exit`: nothing more
289--- of the program runs, and no C stream is flushed.
290public link(symbol: "_landin_posix_exit") exit:
291 (status: i32) -> noreturn =
292 system_exit(status)
293end exit
294
295events_value: type = distinct u16
296
297--- Poll events, POSIX's `poll` bits as a set: a value combines with `|`,
298--- `&` and `~` and compares with `==` and `<>`. `poll_input`,
299--- `poll_output` and `poll_urgent` are what an entry can ask about;
300--- `poll_error`, `poll_hung_up` and `poll_invalid` are only ever
301--- reported. The bits are the same on Linux,
302--- Darwin and FreeBSD.
303public poll_events: type = events_value
304
305--- No event.
306public poll_no_events: poll_events = events_value(0)
307--- Data to read, or the end of a file.
308public poll_input: poll_events = events_value(1)
309--- Out-of-band data to read.
310public poll_urgent: poll_events = events_value(2)
311--- Room to write.
312public poll_output: poll_events = events_value(4)
313--- An error on the file.
314public poll_error: poll_events = events_value(8)
315--- The peer has gone.
316public poll_hung_up: poll_events = events_value(16)
317--- A descriptor that is not open.
318public poll_invalid: poll_events = events_value(32)
319
320-- C's `struct pollfd`, the same on all three systems, so a slice of
321-- entries is what the system reads and fills: `short` fields, whose bits
322-- these are.
323poll_record: type = struct
324 descriptor: descriptor
325 events: poll_events
326 returned: poll_events
327end poll_record
328
329--- One descriptor for `poll` to watch and the events asked about, made by
330--- `watch`, and after the call what happened, which `happened` reads.
331public poll_entry: type = poll_record
332
333--- An entry watching `watched` for the events `wanted` asks about; its
334--- `poll_error`, `poll_hung_up` and `poll_invalid` are reported whether
335--- asked or not.
336public link(symbol: "_landin_posix_watch") watch:
337 (watched: descriptor, wanted: poll_events)
338 -> (entry: poll_entry) =
339 asked: poll_events = poll_input | poll_urgent | poll_output
340 entry = (descriptor: watched, events: wanted & asked,
341 returned: poll_no_events)
342end watch
343
344--- What happened to an entry's descriptor in the last `poll`; nothing,
345--- before one.
346public link(symbol: "_landin_posix_happened") happened:
347 (entry: poll_entry) -> (events: poll_events) =
348 events = entry.returned
349end happened
350
351--- Wait until an event happens to one of `entries`' descriptors, or
352--- `milliseconds` pass, waiting indefinitely when it is negative and not
353--- at all when it is zero, POSIX's `poll`: how many entries had an event,
354--- each of which `happened` reads, zero when the time passed first, or
355--- the errno, `eintr` when a signal came first.
356public link(symbol: "_landin_posix_poll") poll:
357 (entries: []mut poll_entry, milliseconds: i32)
358 -> (ready: usize) ! errno =
359 ready = try system_poll(entries, milliseconds)
360end poll
361
362-- What the argument root holds before it is established, and what C's
363-- null pointer is in an argv entry: no table, no string.
364no_table: atom
365no_string: atom
366root_table: type = no_table | ptr ptr u8
367argument_string: type = no_string | ptr u8
368
369-- The argument root: C's argv and argc as the process's entry gave them,
370-- retained without copying. The table is the pointer the entry gave,
371-- kept in module storage, so what is lent from it outlives every call.
372mut root_values: root_table = no_table
373mut root_count: i32
374
375word: usize = sizeof usize
376
377--- Establish the process's argument root from C's `main` carriers. The
378--- hosted entry calls this with its own `argc` and `argv` before its body
379--- runs; a C `main` that drives Landin exports calls it itself, before
380--- it reads an argument and before starting a thread that may.
381---
382--- The first call needs a nonnegative count and a non-null table, and
383--- keeps that exact pair without copying: the caller keeps the table and
384--- its strings readable while any argument lent from them is in use.
385--- Repeating the identical pair changes nothing; a negative count, a null
386--- table or a different pair traps. The published arguments omit
387--- `argv[0]`, so their count is `max(argc - 1, 0)`.
388public extern(c) _landin_host_initialize_arguments:
389 (count: i32, escaping values: ptr ptr u8) -> none =
390 -- Every misuse of the root stops at compiler.unreachable(), which a
391 -- selected panic handler sees as `unreachable` at that site [1560].
392 -- C may pass a null table, which no Landin pointer is, so its address
393 -- is what is checked.
394 given: usize = usize(values)
395 held: usize = match root_values
396 no_table: usize(0)
397 ptr (table): usize(table)
398 end match
399 if held <> 0 then
400 -- An identical repeat changes nothing; another pair would replace
401 -- a root that lent arguments may still use.
402 if count <> root_count or given <> held then
403 compiler.unreachable()
404 end if
405 return
406 end if
407 if count < 0 or given == 0 then
408 compiler.unreachable()
409 end if
410 root_count = count
411 root_values = values
412end _landin_host_initialize_arguments
413
414--- The process's arguments as its entry gave them: argv after `argv[0]`
415--- and their count. `arguments` gives the table, which lends what it
416--- points to from storage the process keeps, and `argument` reads it.
417public argument_table: type = struct
418 values: ptr ptr u8
419 count: usize
420end argument_table
421
422--- The process's arguments, `argv[0]` not among them. It traps when no
423--- argument root has been established: the hosted entry establishes one
424--- before `main` runs, and a C `main` calls
425--- `_landin_host_initialize_arguments` itself.
426public link(symbol: "_landin_posix_arguments") arguments:
427 () -> (table: argument_table) =
428 held: ptr ptr u8 = match root_values
429 no_table: compiler.unreachable()
430 ptr (found): found
431 end match
432 first: ptr ptr u8 = ptr(usize(held) + word)
433 table = (values: first,
434 count: if root_count > 0 then usize(root_count - 1)
435 else usize(0) end if)
436end arguments
437
438--- The argument `index`, from zero, as a host string lending the
439--- process's own storage, which nothing copies. An index at or past the
440--- table's `count` traps, as does a null entry, which C promises none
441--- below argc is.
442public link(symbol: "_landin_posix_argument") argument:
443 (table: argument_table, index: usize)
444 -> (value: text.host_string from table) =
445 if index >= table.count then
446 compiler.unreachable()
447 end if
448 entry: ptr argument_string = ptr(usize(table.values) + index * word)
449 match entry.val
450 no_string: compiler.unreachable()
451 ptr (data): value = text.host_over(view(data,
452 terminated_length(data)))
453 end match
454end argument
455
456-- The bytes before the first NUL at `first`.
457terminated_length: (first: ptr u8) -> (length: usize) =
458 length = 0
459 loop do
460 at: ptr u8 = ptr(usize(first) + length)
461 break when at.val == 0
462 inc length
463 end loop
464end terminated_length
465
466-- The `length` bytes at `first`, which the system or the process owns and
467-- keeps readable.
468view: (first: ptr u8, length: usize) -> (bytes: []u8 from first) =
469 whole: ptr [1]u8 = ptr(usize(first))
470 bytes = whole.val[0..<0]
471 if length > 0 then
472 unchecked begin
473 bytes = whole.val[0..<length]
474 end unchecked
475 end if
476end view
477
478-- Copy PATH into ROOM with a NUL after it, for a call that takes a C
479-- path: `enametoolong` when it and its NUL do not fit, before anything
480-- is copied, and `einval` when it holds a NUL.
481terminate: (path: text.host_string, room: []mut u8)
482 -> (terminated: ptr u8 from room) ! errno =
483 bytes: []u8 = text.host_bytes(path)
484 fail enametoolong when lenof bytes >= lenof room
485 for at in usize(0)..<lenof bytes do
486 fail einval when bytes[at] == 0
487 room[at] = bytes[at]
488 end for
489 room[lenof bytes] = 0
490 terminated = addr room[0]
491end terminate
492
493-- What a mode's type bits, the same on all three systems, say a file is.
494kind_of_mode: (mode: u32) -> (kind: file_kind) =
495 bits: u32 = mode & 0o170000
496 kind = if bits == 0o100000 then regular
497 elsif bits == 0o040000 then directory
498 elsif bits == 0o120000 then symbolic_link
499 elsif bits == 0o010000 then fifo
500 elsif bits == 0o140000 then socket_file
501 elsif bits == 0o020000 then character_device
502 elsif bits == 0o060000 then block_device
503 else unknown_kind end if
504end kind_of_mode
505
506-- What a directory record's `d_type`, the same on all three systems, says
507-- an entry is; DT_UNKNOWN and BSD's whiteout are `unknown_kind`.
508kind_of_type: (type_byte: u8) -> (kind: file_kind) =
509 kind = if type_byte == 8 then regular
510 elsif type_byte == 4 then directory
511 elsif type_byte == 10 then symbolic_link
512 elsif type_byte == 1 then fifo
513 elsif type_byte == 12 then socket_file
514 elsif type_byte == 2 then character_device
515 elsif type_byte == 6 then block_device
516 else unknown_kind end if
517end kind_of_type