1-- The hosted provider's `io.file_system` and `io.terminals`, written
2-- once over platform/posix like the rest: a file of this provider is a
3-- descriptor, so what `files` opens these read and write at an offset,
4-- and what `open_file` opens `reader`, `writer` and `files` take.
5
6import core/io
7import core/text
8import platform/posix
9
10-- The access an open asks for.
11access_of: (options: io.open_options) -> (access: posix.access) =
12 access = if options.read and options.write then posix.read_write
13 elsif options.write then posix.write_only
14 else posix.read_only end if
15end access_of
16
17system_open_file: (self: system, path: text.host_string,
18 options: io.open_options)
19 -> (opened: io.file)
20 ! io.already_exists | io.not_found | io.no_access
21 | io.io_failed =
22 self.failure.val = 0
23 opened = file_of(posix.standard_input)
24 if not io.valid_options(options) then
25 record(self, posix.einval)
26 fail io.io_failed
27 end if
28 asked: posix.open_options = (access: access_of(options),
29 create: options.create,
30 exclusive: options.exclusive,
31 truncate: options.truncate,
32 append: options.append, directory: false)
33 loop do
34 made: posix.descriptor =
35 posix.open_at(posix.working_directory, path, asked,
36 created_mode) else (problem)
37 continue when problem == posix.eintr
38 record(self, problem)
39 fail io.already_exists when problem == posix.eexist
40 fail io.not_found when absent(problem)
41 fail io.no_access when refused(problem)
42 fail io.io_failed
43 end
44 opened = file_of(made)
45 return
46 end loop
47end system_open_file
48
49-- Not retried on EINTR: a rename made before the interruption would then
50-- report the old name missing.
51system_rename: (self: system, old: text.host_string, new: text.host_string)
52 -> none ! io.not_found | io.no_access | io.io_failed =
53 self.failure.val = 0
54 posix.rename_at(posix.working_directory, old, posix.working_directory,
55 new) else (problem)
56 record(self, problem)
57 fail io.not_found when problem == posix.enoent
58 fail io.no_access when refused(problem)
59 fail io.io_failed
60 end
61end system_rename
62
63-- What core/io says of a status: no permission bit lets anyone write a
64-- read-only file.
65status_from: (found: posix.status) -> (result: io.status) =
66 result = (size: found.size, kind: kind_of(found.kind),
67 modified: (seconds: found.modified.seconds,
68 nanoseconds: found.modified.nanoseconds),
69 read_only: (found.permissions & 0o222) == 0)
70end status_from
71
72system_status_of: (self: system, path: text.host_string, follow: bool)
73 -> (found: io.status)
74 ! io.not_found | io.no_access | io.io_failed =
75 self.failure.val = 0
76 given: posix.status =
77 posix.status_at(posix.working_directory, path, follow) else (problem)
78 record(self, problem)
79 fail io.not_found when absent(problem)
80 fail io.no_access when refused(problem)
81 fail io.io_failed
82 end
83 found = status_from(given)
84end system_status_of
85
86system_status_of_file: (self: system, opened: io.file)
87 -> (found: io.status) ! io.io_failed =
88 self.failure.val = 0
89 given: posix.status = posix.status_of(descriptor_of(opened)) else (problem)
90 record(self, problem)
91 fail io.io_failed
92 end
93 found = status_from(given)
94end system_status_of_file
95
96-- Read-only clears every write bit; writable again sets the owner's.
97-- The status is read and the bits set in two calls, so a change another
98-- program makes between them is lost.
99system_set_read_only: (self: system, path: text.host_string,
100 read_only: bool)
101 -> none ! io.not_found | io.no_access | io.io_failed =
102 self.failure.val = 0
103 given: posix.status =
104 posix.status_at(posix.working_directory, path, true) else (problem)
105 record(self, problem)
106 fail io.not_found when absent(problem)
107 fail io.no_access when refused(problem)
108 fail io.io_failed
109 end
110 bits: u32 = if read_only then given.permissions & ~u32(0o222)
111 else given.permissions | 0o200 end if
112 posix.change_mode_at(posix.working_directory, path, bits) else (problem)
113 record(self, problem)
114 fail io.not_found when absent(problem)
115 fail io.no_access when refused(problem)
116 fail io.io_failed
117 end
118end system_set_read_only
119
120system_synchronize: (self: system, opened: io.file, data_only: bool)
121 -> none ! io.io_failed =
122 self.failure.val = 0
123 loop do
124 posix.synchronize(descriptor_of(opened), data_only) else (problem)
125 continue when problem == posix.eintr
126 record(self, problem)
127 fail io.io_failed
128 end
129 return
130 end loop
131end system_synchronize
132
133system_truncate: (self: system, opened: io.file, size: u64)
134 -> none ! io.io_failed =
135 self.failure.val = 0
136 loop do
137 posix.truncate(descriptor_of(opened), size) else (problem)
138 continue when problem == posix.eintr
139 record(self, problem)
140 fail io.io_failed
141 end
142 return
143 end loop
144end system_truncate
145
146system_read_at: (self: system, opened: io.file, offset: u64, into: []mut u8)
147 -> (count: usize) ! io.io_failed =
148 self.failure.val = 0
149 count = 0
150 return when lenof into == 0
151 loop do
152 -- A read interrupted before it read a byte is made again; a short
153 -- one is returned as it came.
154 count = posix.read_at(descriptor_of(opened), offset, into)
155 else (problem)
156 continue when problem == posix.eintr
157 record(self, problem)
158 fail io.io_failed
159 end
160 return
161 end loop
162end system_read_at
163
164system_write_at: (self: system, opened: io.file, offset: u64, bytes: []u8)
165 -> none ! io.io_failed =
166 self.failure.val = 0
167 mut written: usize = 0
168 while written < lenof bytes do
169 accepted: usize =
170 posix.write_at(descriptor_of(opened), offset + u64(written),
171 bytes[written..<lenof bytes]) else (problem)
172 continue when problem == posix.eintr
173 record(self, problem)
174 fail io.io_failed
175 end
176 fail io.io_failed when accepted == 0
177 written += accepted
178 end while
179end system_write_at
180
181-- A temporary file is readable and writable by its owner alone.
182temporary_mode: u32 = 0o600
183
184-- The options a temporary file is made with: read and write, created
185-- exclusively, so a name another program took, or a symbolic link
186-- planted at it, is never opened in its place.
187temporary_options: posix.open_options =
188 (access: posix.read_write, create: true, exclusive: true,
189 truncate: false, append: false, directory: false)
190
191system_make_temporary: (self: system, directory: text.host_string,
192 names: any io.entropy, into: []mut u8)
193 -> (made: io.temporary from into)
194 ! io.not_found | io.no_access | io.io_failed =
195 self.failure.val = 0
196 made = (opened: file_of(posix.standard_input),
197 path: text.host_over(into[0..<0]))
198 mut tries: usize = 0
199 while tries < io.temporary_tries do
200 inc tries
201 mut bytes: [8]u8 = zeroed
202 try names.fill_random(bytes[0..<8])
203 path: text.host_string = try io.temporary_path(directory, bytes, into)
204 opened: posix.descriptor =
205 posix.open_at(posix.working_directory, path, temporary_options,
206 temporary_mode) else (problem)
207 -- Interrupted before it made anything, or the name is
208 -- taken: try another.
209 continue when problem == posix.eintr
210 record(self, problem)
211 continue when problem == posix.eexist
212 fail io.not_found when absent(problem)
213 fail io.no_access when refused(problem)
214 fail io.io_failed
215 end
216 self.failure.val = 0
217 made = (opened: file_of(opened), path: path)
218 return
219 end while
220 fail io.io_failed
221end system_make_temporary
222
223system_current_directory: (self: system, into: []mut u8)
224 -> (path: text.host_string from into)
225 ! io.io_failed =
226 self.failure.val = 0
227 path = posix.current_directory(into) else (problem)
228 record(self, problem)
229 fail io.io_failed
230 end
231end system_current_directory
232
233system_is_terminal: (self: system, stream: io.file) -> (yes: bool) =
234 _ = self.failure
235 yes = posix.is_terminal(descriptor_of(stream))
236end system_is_terminal
237
238system_window_size: (self: system, stream: io.file) -> (size: io.window)
239 ! io.not_a_terminal | io.io_failed =
240 self.failure.val = 0
241 found: posix.window_size =
242 posix.window_size_of(descriptor_of(stream)) else (problem)
243 record(self, problem)
244 fail io.not_a_terminal when problem == posix.enotty
245 fail io.io_failed
246 end
247 size = (rows: found.rows, columns: found.columns)
248end system_window_size
249
250system is io.file_system
251 (open_file: system_open_file, rename: system_rename,
252 status_of: system_status_of, status_of_file: system_status_of_file,
253 set_read_only: system_set_read_only, synchronize: system_synchronize,
254 truncate: system_truncate, read_at: system_read_at,
255 write_at: system_write_at, make_temporary: system_make_temporary,
256 current_directory: system_current_directory)
257system is io.terminals
258 (is_terminal: system_is_terminal, window_size: system_window_size)