hosted/system/directories.ldn
1-- The hosted provider's `io.directories`: a directory is platform/posix's
2-- stream over a descriptor, read through the caller's buffer, and making
3-- and removing directories and files are POSIX's calls relative to the
4-- working directory. Written once, over platform/posix, like the rest.
5
6import core/io
7import core/text
8import platform/posix
9
10-- The open directory as platform/posix's stream, and the stream's
11-- progress kept back in the directory. The directory's handle is its
12-- descriptor's number and its stream the C library's, where there is
13-- one.
14stream_of: (opened: io.directory) -> (stream: posix.directory_stream) =
15 stream = (descriptor: posix.descriptor(i32(opened.handle)),
16 stream: opened.stream, filled: opened.filled,
17 cursor: opened.cursor, finished: opened.finished)
18end stream_of
19
20keep: (inout opened: io.directory, stream: posix.directory_stream) -> none =
21 opened.filled = stream.filled
22 opened.cursor = stream.cursor
23 opened.finished = stream.finished
24end keep
25
26system_open_directory: (self: host, path: text.host_string,
27 buffer: []mut u8)
28 -> (opened: io.directory from buffer)
29 ! io.not_found | io.no_access | io.io_failed =
30 self.failure.val = 0
31 -- Every name a hosted system gives fits a buffer this large, record
32 -- and all, so a read never meets one that cannot fit.
33 fail io.io_failed when lenof buffer < io.directory_buffer
34 loop do
35 -- open reports EINTR before publishing a descriptor, so the same
36 -- request is retried; every other errno is kept and mapped.
37 made: posix.directory_stream =
38 posix.open_directory(posix.working_directory, path) else (problem)
39 continue when problem == posix.eintr
40 try path_failure(self, problem)
41 fail io.io_failed
42 end
43 opened = (handle: usize(i32(made.descriptor)), stream: made.stream,
44 position: 0, buffer: buffer, filled: made.filled,
45 cursor: made.cursor, finished: made.finished)
46 return
47 end loop
48end system_open_directory
49
50-- `.` and `..` name the directory itself and its parent, never an entry.
51dot_name: (name: []u8) -> (yes: bool) =
52 yes = false
53 return when lenof name == 0 or lenof name > 2 or name[0] <> u8('.')
54 yes = lenof name == 1 or name[1] == u8('.')
55end dot_name
56
57-- What platform/posix's kind of a file is among core/io's four.
58kind_of: (kind: posix.file_kind) -> (named: io.entry_kind) =
59 named = if kind == posix.directory then io.subdirectory
60 elsif kind == posix.regular then io.regular
61 elsif kind == posix.symbolic_link then io.symbolic_link
62 else io.special end if
63end kind_of
64
65-- What the entry `next` of the directory open as `at` is, and whether it
66-- is still there. Where the file system keeps no kind in its records,
67-- the entry itself is asked, relative to the directory, and one removed
68-- since the directory was read is no entry.
69kind_at: (self: host, at: posix.descriptor, next: posix.directory_entry)
70 -> (kind: io.entry_kind, present: bool) ! io.io_failed =
71 kind = kind_of(next.kind)
72 present = true
73 return when next.kind <> posix.unknown_kind
74 found: posix.status = posix.status_at(at, next.name, false) else (problem)
75 if not absent(problem) then
76 record(self, problem)
77 fail io.io_failed
78 end if
79 present = false
80 return
81 end
82 kind = kind_of(found.kind)
83end kind_at
84
85system_read_entry: (self: host, inout opened: io.directory)
86 -> (item: io.entry from opened) ! io.io_failed =
87 self.failure.val = 0
88 loop do
89 mut stream: posix.directory_stream = stream_of(opened)
90 next: posix.directory_entry =
91 posix.read_directory(stream, opened.buffer) else (problem)
92 keep(opened, stream)
93 -- A read interrupted before it filled anything is read
94 -- again.
95 continue when problem == posix.eintr
96 record(self, problem)
97 fail io.io_failed
98 end
99 opened.filled = stream.filled
100 opened.cursor = stream.cursor
101 opened.finished = stream.finished
102 name: []u8 = text.host_bytes(next.name)
103 if lenof name == 0 then
104 item = (name: next.name, kind: io.special)
105 return
106 end if
107 if not dot_name(name) then
108 (kind, present) := try kind_at(self, stream.descriptor, next)
109 if present then
110 item = (name: next.name, kind: kind)
111 return
112 end if
113 end if
114 end loop
115end system_read_entry
116
117system_entry_size: (self: host, opened: io.directory,
118 name: text.host_string)
119 -> (size: u64) ! io.not_found | io.no_access | io.io_failed =
120 self.failure.val = 0
121 size = 0
122 -- No hosted system gives a name this long, or one with a `/` or a
123 -- NUL, so no entry has one, and the system is not asked.
124 bytes: []u8 = text.host_bytes(name)
125 fail io.not_found when lenof bytes == 0
126 or lenof bytes >= io.directory_buffer
127 for byte in bytes do
128 fail io.not_found when byte == 0 or byte == u8('/')
129 end for
130 found: posix.status =
131 posix.status_at(posix.descriptor(i32(opened.handle)), name,
132 false) else (problem)
133 try path_failure(self, problem)
134 fail io.io_failed
135 end
136 size = found.size
137end system_entry_size
138
139system_close_directory: (self: host, sink opened: io.directory)
140 -> none ! io.io_failed =
141 self.failure.val = 0
142 -- The descriptor may already be released, as with close, so a
143 -- failure is terminal and never retried.
144 posix.close_directory(stream_of(opened)) else (problem)
145 record(self, problem)
146 fail io.io_failed
147 end
148end system_close_directory
149
150system_make_directory: (self: host, path: text.host_string)
151 -> none
152 ! io.already_exists | io.not_found | io.no_access
153 | io.io_failed =
154 self.failure.val = 0
155 -- Not retried on EINTR: a directory made before the interruption
156 -- would then report EEXIST.
157 posix.make_directory_at(posix.working_directory, path,
158 created_directory_mode) else (problem)
159 record(self, problem)
160 fail io.already_exists when problem == posix.eexist
161 try path_failure(self, problem)
162 fail io.io_failed
163 end
164end system_make_directory
165
166-- Remove the file, or with `directory` the empty directory, at `path`.
167removed: (path: text.host_string, directory: bool) -> none ! posix.errno =
168 if directory then
169 try posix.remove_directory_at(posix.working_directory, path)
170 else
171 try posix.unlink_at(posix.working_directory, path)
172 end if
173end removed
174
175remove: (self: host, path: text.host_string, directory: bool)
176 -> none ! io.not_found | io.no_access | io.io_failed =
177 self.failure.val = 0
178 removed(path, directory) else (problem)
179 try path_failure(self, problem)
180 fail io.io_failed
181 end
182end remove
183
184system_remove_file: (self: host, path: text.host_string)
185 -> none ! io.not_found | io.no_access | io.io_failed =
186 try remove(self, path, false)
187end system_remove_file
188
189system_remove_directory: (self: host, path: text.host_string)
190 -> none ! io.not_found | io.no_access | io.io_failed =
191 try remove(self, path, true)
192end system_remove_directory
193
194host is io.directories
195 (open_directory: system_open_directory,
196 read_entry: system_read_entry, entry_size: system_entry_size,
197 close_directory: system_close_directory,
198 make_directory: system_make_directory,
199 remove_file: system_remove_file,
200 remove_directory: system_remove_directory)