Landin library reference discuss source

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)