1-- platform/posix on Darwin and FreeBSD, over platform/libc: their C
2-- library is their system interface. A failing call's errno is read
3-- from the calling thread's own cell immediately after it, before any
4-- other call, and becomes the atom the call fails with, so no number is
5-- shared between threads. A path relative to the working directory goes
6-- to `open` and `stat`, and only a path relative to an open directory to
7-- `openat` and `fstatat`, so a program interposing on the C library meets
8-- the calls it always has.
9
10import core/text
11import platform/libc
12
13fixed if compiler.os == darwin or compiler.os == freebsd then
14 -- The errno the calling thread's last failing call left, as an atom.
15 failure: () -> (problem: errno) =
16 cell: ptr mut i32 = libc.errno_cell()
17 problem = errno_of(cell.val)
18 end failure
19
20 -- The flags `open` takes for `options`.
21 flags_of: (options: open_options) -> (flags: i32) =
22 flags = match options.access
23 read_only: libc.o_rdonly
24 write_only: libc.o_wronly
25 read_write: libc.o_rdwr
26 end match
27 if options.create then flags = flags | libc.o_creat end if
28 if options.exclusive then flags = flags | libc.o_excl end if
29 if options.truncate then flags = flags | libc.o_trunc end if
30 if options.append then flags = flags | libc.o_append end if
31 if options.directory then flags = flags | libc.o_directory end if
32 end flags_of
33
34 -- `open`, or `openat` relative to an open directory, with the
35 -- permission bits as the one variadic argument only when creating.
36 opened_at: (at: descriptor, path: ptr u8, flags: i32, permissions: u32)
37 -> (result: i32) =
38 if at == working_directory then
39 result = if (flags & libc.o_creat) <> 0
40 then libc.open(path, flags, i32(permissions))
41 else libc.open(path, flags) end if
42 else
43 result = if (flags & libc.o_creat) <> 0
44 then libc.openat(i32(at), path, flags, i32(permissions))
45 else libc.openat(i32(at), path, flags) end if
46 end if
47 end opened_at
48
49 system_open: (at: descriptor, path: ptr u8, options: open_options,
50 permissions: u32) -> (opened: descriptor) ! errno =
51 result: i32 = opened_at(at, path, flags_of(options), permissions)
52 fail failure() when result < 0
53 opened = descriptor(result)
54 end system_open
55
56 system_read: (source: descriptor, into: []mut u8)
57 -> (count: usize) ! errno =
58 mut spare: u8 = 0
59 base: ptr mut u8 = if lenof into > 0 then addr into[0]
60 else addr spare end if
61 result: isize = libc.read(i32(source), base, lenof into)
62 fail failure() when result < 0
63 count = usize(result)
64 end system_read
65
66 system_write: (to: descriptor, bytes: []u8) -> (count: usize) ! errno =
67 spare: u8 = 0
68 base: ptr u8 = if lenof bytes > 0 then addr bytes[0]
69 else addr spare end if
70 result: isize = libc.write(i32(to), base, lenof bytes)
71 fail failure() when result < 0
72 count = usize(result)
73 end system_write
74
75 system_close: (closing: descriptor) -> none ! errno =
76 fail failure() when libc.close(i32(closing)) <> 0
77 end system_close
78
79 -- The record is checked against the native header's size before it
80 -- is used, as abi/core-io-file-identity holds it to that header: a
81 -- disagreement is `unlisted`, which no errno describes.
82 system_status: (at: descriptor, path: ptr u8, follow: bool)
83 -> (result: status) ! errno =
84 fail unlisted when alignof libc.file_status <> 8
85 or sizeof libc.file_status <> libc.file_status_size
86 mut record: libc.file_status = zeroed
87 outcome: i32 =
88 if at == working_directory and follow
89 then libc.stat(path, addr record)
90 else libc.fstatat(i32(at), path, addr record,
91 if follow then 0
92 else libc.at_symlink_nofollow end if) end if
93 fail failure() when outcome <> 0
94 result = (device: u64(record.device), inode: u64(record.inode),
95 kind: kind_of_mode(u32(record.mode)),
96 size: u64(record.bytes))
97 end system_status
98
99 -- The directory is opened with o_directory, which makes anything else
100 -- ENOTDIR, and a C directory stream made over it, which then owns the
101 -- descriptor.
102 system_open_directory: (at: descriptor, path: ptr u8)
103 -> (opened: directory_stream) ! errno =
104 result: i32 = opened_at(at, path, libc.o_rdonly | libc.o_directory, 0)
105 fail failure() when result < 0
106 made: libc.directory_stream = libc.fdopendir(result)
107 match made
108 libc.no_directory: begin
109 problem: errno = failure()
110 _ = libc.close(result)
111 fail problem
112 end
113 ptr (stream): opened = (descriptor: descriptor(result),
114 stream: usize(stream), filled: 0,
115 cursor: 0, finished: false)
116 end match
117 end system_open_directory
118
119 -- The stream's next record, its name copied into the buffer with a
120 -- NUL after it, since the record is the C library's and good only
121 -- until the next readdir. The buffer holds any name a record can:
122 -- `directory_buffer` is larger than every d_name. readdir's end and
123 -- its failure are told apart by errno, zeroed before the call.
124 system_read_directory: (inout opened: directory_stream, buffer: []mut u8)
125 -> (item: directory_entry from buffer) ! errno =
126 cell: ptr mut i32 = libc.errno_cell()
127 cell.val = 0
128 if opened.finished then
129 item = (name: text.host_over(buffer[0..<0]), kind: unknown_kind)
130 return
131 end if
132 stream: ptr mut u8 = ptr(opened.stream)
133 record: libc.directory_entry = libc.readdir(stream)
134 match record
135 libc.no_directory: begin
136 fail errno_of(cell.val) when cell.val <> 0
137 opened.finished = true
138 item = (name: text.host_over(buffer[0..<0]),
139 kind: unknown_kind)
140 end
141 ptr (found): begin
142 length: usize = usize(found.val.name_length)
143 fail unlisted when length >= lenof buffer
144 for at in usize(0)..<length do
145 buffer[at] = found.val.name[at]
146 end for
147 buffer[length] = 0
148 item = (name: text.host_over(buffer[0..<length]),
149 kind: kind_of_type(found.val.kind))
150 end
151 end match
152 end system_read_directory
153
154 system_close_directory: (opened: directory_stream) -> none ! errno =
155 stream: ptr mut u8 = ptr(opened.stream)
156 fail failure() when libc.closedir(stream) <> 0
157 end system_close_directory
158
159 system_make_directory: (at: descriptor, path: ptr u8, permissions: u32)
160 -> none ! errno =
161 fail failure() when libc.mkdirat(i32(at), path, u16(permissions)) <> 0
162 end system_make_directory
163
164 system_remove: (at: descriptor, path: ptr u8, directory: bool)
165 -> none ! errno =
166 flags: i32 = if directory then libc.at_removedir else 0 end if
167 fail failure() when libc.unlinkat(i32(at), path, flags) <> 0
168 end system_remove
169
170 system_map: (length: usize) -> (base: ptr mut u8) ! errno =
171 result: usize = libc.mmap(0, length, libc.prot_read | libc.prot_write,
172 libc.map_private | libc.map_anonymous, -1,
173 0)
174 fail failure() when result == libc.map_failed
175 base = ptr(result)
176 end system_map
177
178 system_unmap: (base: ptr mut u8, length: usize) -> none ! errno =
179 fail failure() when libc.munmap(usize(base), length) <> 0
180 end system_unmap
181
182 system_page_size: () -> (bytes: usize) =
183 bytes = usize(libc.getpagesize())
184 end system_page_size
185
186 system_exit: (status: i32) -> noreturn =
187 libc._exit(status)
188 end system_exit
189
190 system_poll: (entries: []mut poll_entry, milliseconds: i32)
191 -> (ready: usize) ! errno =
192 mut spare: libc.poll_record = zeroed
193 records: ptr mut libc.poll_record =
194 if lenof entries > 0 then ptr(usize(addr entries[0]))
195 else addr spare end if
196 result: i32 = libc.poll(records, libc.poll_count(lenof entries),
197 milliseconds)
198 fail failure() when result < 0
199 ready = usize(result)
200 end system_poll
201end if