1-- platform/posix on Linux, over platform/linux's system calls. A call's
2-- failure is minus its errno in its result, so the errno is read from the
3-- result itself and no cell is shared between threads. The same calls
4-- serve a program linked with --link=libc, whose C library this module
5-- never calls.
6
7import core/text
8import platform/linux
9
10fixed if compiler.os == linux then
11 -- The flags `openat` takes for `options`.
12 flags_of: (options: open_options) -> (flags: i32) =
13 flags = match options.access
14 read_only: linux.o_rdonly
15 write_only: linux.o_wronly
16 read_write: linux.o_rdwr
17 end match
18 if options.create then flags = flags | linux.o_creat end if
19 if options.exclusive then flags = flags | linux.o_excl end if
20 if options.truncate then flags = flags | linux.o_trunc end if
21 if options.append then flags = flags | linux.o_append end if
22 if options.directory then flags = flags | linux.o_directory end if
23 end flags_of
24
25 system_open: (at: descriptor, path: ptr u8, options: open_options,
26 permissions: u32) -> (opened: descriptor) ! errno =
27 result: i32 = linux.openat(i32(at), path, flags_of(options),
28 permissions)
29 fail errno_of(-result) when result < 0
30 opened = descriptor(result)
31 end system_open
32
33 system_read: (source: descriptor, into: []mut u8)
34 -> (count: usize) ! errno =
35 mut spare: u8 = 0
36 base: ptr mut u8 = if lenof into > 0 then addr into[0]
37 else addr spare end if
38 result: isize = linux.read(i32(source), base, lenof into)
39 fail errno_of(i32(-result)) when result < 0
40 count = usize(result)
41 end system_read
42
43 system_write: (to: descriptor, bytes: []u8) -> (count: usize) ! errno =
44 spare: u8 = 0
45 base: ptr u8 = if lenof bytes > 0 then addr bytes[0]
46 else addr spare end if
47 result: isize = linux.write(i32(to), base, lenof bytes)
48 fail errno_of(i32(-result)) when result < 0
49 count = usize(result)
50 end system_write
51
52 system_close: (closing: descriptor) -> none ! errno =
53 result: i32 = linux.close(i32(closing))
54 fail errno_of(-result) when result < 0
55 end system_close
56
57 -- statx's record is the kernel's own on every architecture, so it
58 -- needs no layout check.
59 system_status: (at: descriptor, path: ptr u8, follow: bool)
60 -> (result: status) ! errno =
61 mut record: linux.file_status = zeroed
62 flags: i32 = if follow then 0 else linux.at_symlink_nofollow end if
63 outcome: i32 = linux.statx(i32(at), path, flags,
64 linux.statx_type | linux.statx_ino
65 | linux.statx_size,
66 addr record)
67 fail errno_of(-outcome) when outcome < 0
68 result = (device: (u64(record.device_major) << 32)
69 | u64(record.device_minor),
70 inode: record.inode, kind: kind_of_mode(u32(record.mode)),
71 size: record.size)
72 end system_status
73
74 system_open_directory: (at: descriptor, path: ptr u8)
75 -> (opened: directory_stream) ! errno =
76 result: i32 = linux.openat(i32(at), path,
77 linux.o_rdonly | linux.o_directory, 0)
78 fail errno_of(-result) when result < 0
79 opened = (descriptor: descriptor(result), stream: 0, filled: 0,
80 cursor: 0, finished: false)
81 end system_open_directory
82
83 -- A record's little-endian u16 at `at`.
84 record_u16: (bytes: []u8, at: usize) -> (value: usize) =
85 value = usize(bytes[at]) | (usize(bytes[at + 1]) << 8)
86 end record_u16
87
88 -- The next record in the buffer, refilling it from getdents64 when
89 -- every record in it has been read. A record is checked to lie
90 -- within what the kernel filled before any byte of it is read, and
91 -- its name to end within it, so a buffer's last record is never read
92 -- past; one that does not is `unlisted`, which no errno describes.
93 system_read_directory: (inout opened: directory_stream, buffer: []mut u8)
94 -> (item: directory_entry from buffer) ! errno =
95 loop do
96 if opened.cursor >= opened.filled then
97 if opened.finished then
98 item = (name: text.host_over(buffer[0..<0]),
99 kind: unknown_kind)
100 return
101 end if
102 got: isize = linux.getdents64(i32(opened.descriptor),
103 addr buffer[0], lenof buffer)
104 fail errno_of(i32(-got)) when got < 0
105 fail unlisted when usize(got) > lenof buffer
106 if got == 0 then
107 opened.finished = true
108 else
109 opened.filled = usize(got)
110 opened.cursor = 0
111 end if
112 else
113 at: usize = opened.cursor
114 -- The fixed part is 19 bytes and the name at least one
115 -- byte and its NUL.
116 fail unlisted when opened.filled - at < 21
117 length: usize = record_u16(buffer, at + 16)
118 fail unlisted when length < 21
119 or length > opened.filled - at
120 opened.cursor = at + length
121 mut stop: usize = at + 19
122 while stop < at + length and buffer[stop] <> 0 do
123 inc stop
124 end while
125 fail unlisted when stop == at + length
126 item = (name: text.host_over(buffer[at + 19..<stop]),
127 kind: kind_of_type(buffer[at + 18]))
128 return
129 end if
130 end loop
131 end system_read_directory
132
133 system_close_directory: (opened: directory_stream) -> none ! errno =
134 try system_close(opened.descriptor)
135 end system_close_directory
136
137 system_make_directory: (at: descriptor, path: ptr u8, permissions: u32)
138 -> none ! errno =
139 result: i32 = linux.mkdirat(i32(at), path, permissions)
140 fail errno_of(-result) when result < 0
141 end system_make_directory
142
143 system_remove: (at: descriptor, path: ptr u8, directory: bool)
144 -> none ! errno =
145 result: i32 = linux.unlinkat(i32(at), path,
146 if directory then linux.at_removedir
147 else 0 end if)
148 fail errno_of(-result) when result < 0
149 end system_remove
150
151 system_map: (length: usize) -> (base: ptr mut u8) ! errno =
152 result: i64 = linux.mmap(0, length, linux.prot_read | linux.prot_write,
153 linux.map_private | linux.map_anonymous, -1,
154 0)
155 fail errno_of(i32(-result)) when result < 0
156 base = ptr(usize(result))
157 end system_map
158
159 system_unmap: (base: ptr mut u8, length: usize) -> none ! errno =
160 result: i32 = linux.munmap(usize(base), length)
161 fail errno_of(-result) when result < 0
162 end system_unmap
163
164 system_page_size: () -> (bytes: usize) =
165 bytes = linux.page_size()
166 end system_page_size
167
168 system_exit: (status: i32) -> noreturn =
169 linux.exit_group(status)
170 end system_exit
171
172 -- arm64 and RV64 have no `poll` call, so every architecture makes
173 -- `ppoll`, with the milliseconds as its time limit.
174 system_poll: (entries: []mut poll_entry, milliseconds: i32)
175 -> (ready: usize) ! errno =
176 mut spare: linux.poll_record = zeroed
177 records: ptr mut linux.poll_record =
178 if lenof entries > 0 then ptr(usize(addr entries[0]))
179 else addr spare end if
180 mut limit: linux.timespec = zeroed
181 limit.seconds = i64(milliseconds / 1000)
182 limit.nanoseconds = i64(milliseconds % 1000) * 1000000
183 bound: linux.poll_limit = if milliseconds < 0 then linux.no_limit
184 else addr limit end if
185 result: i32 = linux.ppoll(records, lenof entries, bound)
186 fail errno_of(-result) when result < 0
187 ready = usize(result)
188 end system_poll
189end if