Landin library reference discuss source

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)