Landin library reference discuss source

hosted/io/files.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: 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)