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