Landin library reference discuss source

core/io/fake/volume.ldn

1--  A file system in memory, over the caller's nodes: the fake of
2--  `file_system`, and of `reader`, `writer`, `files` and `directories`
3--  over the same files, so a test of code that makes, renames, rewrites
4--  and lists a tree runs the same way in every run and on every target.
5
6import core/cmp
7import core/io
8import core/mem
9import core/text
10import core/time
11
12--- One node of a memory volume: a regular file, a directory or a
13--- symbolic link, named by its whole path from the volume's root.  The
14--- caller gives each node room for a path, `path`, and for a file's bytes
15--- or a link's target, `contents`, sets `path_length` to zero for a node
16--- the volume may use for a new file, and gives the others their path,
17--- `length`, `kind`, `read_only` and `modified`.  A path has no leading
18--- `./`, no trailing `/`, and no `.` or `..` part; its directory must be
19--- a node too, or the root, `.`.  The volume keeps the fields as files
20--- change; a test reads them.
21public volume_node: type = struct
22    path: []mut u8
23    path_length: usize
24    contents: []mut u8
25    length: usize
26    kind: io.entry_kind
27    read_only: bool
28    modified: time.wall_time
29end volume_node
30
31--- One file a memory volume has open: which node, how it may be used, and
32--- the position `read` and `write` use.  The volume's own.
33public volume_opening: type = struct
34    node: usize
35    position: u64
36    readable: bool
37    writable: bool
38    appending: bool
39    open: bool
40end volume_opening
41
42--- How many files a memory volume holds open at once.
43public volume_openings: usize = 8
44
45--- Caller-owned state of a memory volume: the wall-clock time each change
46--- is stamped with, which the test sets, the files it holds open, call
47--- counts, and a call to fail.  Declare it `zeroed` and give it to
48--- `volume_over`, which fills it; it must outlive the handle, and its
49--- fields may be read, and the time and the failure set, while the handle
50--- is in use.  `calls` counts the calls made of the volume, from one:
51--- every call of `reader`, `writer`, `files`, `directories` and
52--- `file_system` counts once, each `read_entry`, `entry_size` and
53--- `same_file` among them, except that `write` counts each piece
54--- `write_some` writes and `make_temporary` counts once and again for each
55--- name it tries to open.  The call `fail_at` names fails `io_failed`;
56--- zero fails none.
57public volume_space: type = struct
58    now_seconds: i64
59    now_nanoseconds: u32
60    openings: [8]volume_opening
61    calls: usize
62    fail_at: usize
63    opens: usize
64    renames: usize
65    synchronizations: usize
66    temporary_tries: usize
67end volume_space
68
69volume_value: type = struct
70    state: ptr mut volume_space
71    nodes: []mut volume_node
72    working: []u8
73end volume_value
74
75--- A file system in memory over the caller's nodes and their storage: a
76--- provider of `reader`, `writer`, `files`, `directories` and
77--- `file_system`.  A file grows only into its node's `contents`, and a
78--- write that finds no room left is `io_failed`, as a full device is; a
79--- new file takes a free node, and a path longer than its room, or a
80--- volume with no free node, is `io_failed` too.  A symbolic link is a
81--- node the caller makes, followed as a system follows one: always before
82--- a path's last part, and in its last part where the call follows links,
83--- a target that begins with `/` from the volume's root and any other from
84--- the link's own directory.  Opening a dangling link to create a file
85--- creates its target.  More than eight links in one path are
86--- `io_failed`, as ELOOP is, and a path that grows past 256 bytes as its
87--- links' targets are put in it is `io_failed` too.  A node a file is open
88--- on cannot be removed or replaced:
89--- `io_failed`, where POSIX would keep the open file without its name, and
90--- a node whose path is longer than 256 bytes is not renamed.
91--- Read-only files refuse writing, `no_access`, as they do to every
92--- process but a privileged one.  Copies share the volume.
93public volume: type = volume_value
94
95--- Fill `state` for a memory volume over `nodes`, whose working directory
96--- is `working`, and return its handle, which lends both; keep them alive
97--- while it is used.
98public volume_over: (inout state: volume_space, nodes: []mut volume_node,
99                     working: []u8)
100                    -> (host: volume from state, nodes, working) =
101    closed: volume_opening = (node: 0, position: 0, readable: false,
102                              writable: false, appending: false, open: false)
103    state = (now_seconds: 0, now_nanoseconds: 0, openings: [8 of closed],
104             calls: 0, fail_at: 0, opens: 0, renames: 0,
105             synchronizations: 0, temporary_tries: 0)
106    host = (state: addr state, nodes: nodes, working: working)
107end volume_over
108
109--- The node at `path`, without following a link, or the node count when
110--- there is none; a test reads what it holds there.
111public volume_find: (host: volume, path: []u8) -> (index: usize) =
112    index = lenof host.nodes
113    for at in usize(0)..<lenof host.nodes do
114        node: ptr volume_node = addr host.nodes[at]
115        if node.val.path_length > 0
116          and volume_same(node.val.path[0..<node.val.path_length], path) then
117            index = at
118            return
119        end if
120    end for
121end volume_find
122
123volume_same: (left: []u8, right: []u8) -> (yes: bool) =
124    yes = cmp.compare(left, right) == cmp.equal
125end volume_same
126
127--  Count a call, failing the one the test chose.
128volume_call: (self: volume) -> none ! io.io_failed =
129    state: ptr mut volume_space = self.state
130    inc state.val.calls
131    fail io.io_failed when state.val.calls == state.val.fail_at
132end volume_call
133
134--  The time a change is stamped with.
135volume_now: (self: volume) -> (at: time.wall_time) =
136    at = (seconds: self.state.val.now_seconds,
137          nanoseconds: self.state.val.now_nanoseconds)
138end volume_now
139
140--  The directory part of PATH, `.` for one at the root.
141volume_parent: (path: []u8) -> (parent: []u8 from path) =
142    mut at: usize = lenof path
143    while at > 0 and path[at - 1] <> u8('/') do
144        dec at
145    end while
146    parent = if at == 0 then "." else path[0..<at - 1] end if
147end volume_parent
148
149--  The last part of PATH.
150volume_leaf: (path: []u8) -> (leaf: []u8 from path) =
151    mut at: usize = lenof path
152    while at > 0 and path[at - 1] <> u8('/') do
153        dec at
154    end while
155    leaf = path[at..<lenof path]
156end volume_leaf
157
158--  Whether PATH is the root or names a directory node.
159volume_is_directory: (self: volume, path: []u8) -> (yes: bool) =
160    yes = volume_same(path, ".")
161    return when yes
162    index: usize = volume_find(self, path)
163    yes = index < lenof self.nodes
164          and self.nodes[index].kind == io.subdirectory
165end volume_is_directory
166
167--  A path a node can have: nonempty, not the root, with no empty, `.` or
168--  `..` part and no NUL.
169volume_valid: (path: []u8) -> (yes: bool) =
170    yes = lenof path > 0 and not volume_same(path, ".")
171    mut start: usize = 0
172    for at in usize(0)..<lenof path + 1 do
173        if at == lenof path or path[at] == u8('/') then
174            part: []u8 = path[start..<at]
175            if lenof part == 0 or volume_same(part, ".")
176              or volume_same(part, "..") then
177                yes = false
178            end if
179            start = at + 1
180        elsif path[at] == 0 then
181            yes = false
182        end if
183    end for
184end volume_valid
185
186--  The most a path may hold once the volume has put its links' targets
187--  in it.
188volume_room: usize = 256
189
190--  PATH of LENGTH bytes in INTO with PART after it, a `/` between them:
191--  the new length, or `io_failed` when it does not fit.
192volume_append: (into: []mut u8, length: usize, part: []u8)
193               -> (grown: usize) ! io.io_failed =
194    mut at: usize = length
195    if at > 0 then
196        fail io.io_failed when at >= lenof into
197        into[at] = u8('/')
198        inc at
199    end if
200    fail io.io_failed when lenof part > lenof into - at
201    mem.copy(into[at..<at + lenof part], part) else (problem)
202        _ = problem
203        fail io.io_failed
204    end
205    grown = at + lenof part
206end volume_append
207
208--  Where PATH, as the caller gives it, leads: the path from the root with
209--  no empty, `.` or `..` part, written into INTO, and its length, zero for
210--  the root.  A symbolic link before the last part is always followed, and
211--  in the last part when FOLLOW, as a system follows them: a target that
212--  begins with `/` from the root and any other from the link's own
213--  directory.  A part before the last that is missing or is no directory,
214--  and an empty target, are `not_found`, as ENOENT and ENOTDIR are; more
215--  than eight links, ELOOP, and a path longer than the room are
216--  `io_failed`.
217volume_walk: (self: volume, path: []u8, follow: bool,
218              into: []mut u8) -> (length: usize) ! io.not_found | io.io_failed =
219    count: usize = lenof self.nodes
220    length = 0
221    mut rest: [256]u8 = zeroed
222    fail io.io_failed when lenof path > volume_room
223    mem.copy(rest[0..<lenof path], path) else (problem)
224        _ = problem
225        fail io.io_failed
226    end
227    mut rest_length: usize = lenof path
228    mut start: usize = 0
229    mut hops: usize = 0
230    loop do
231        while start < rest_length and rest[start] == u8('/') do
232            inc start
233        end while
234        break when start == rest_length
235        mut stop: usize = start
236        while stop < rest_length and rest[stop] <> u8('/') do
237            inc stop
238        end while
239        mut after: usize = stop
240        while after < rest_length and rest[after] == u8('/') do
241            inc after
242        end while
243        last: bool = after == rest_length
244        before: usize = length
245        dot: bool = volume_same(rest[start..<stop], ".")
246        dots: bool = volume_same(rest[start..<stop], "..")
247        if dots then
248            while length > 0 and into[length - 1] <> u8('/') do
249                dec length
250            end while
251            if length > 0 then dec length end if
252        elsif not dot then
253            length = try volume_append(into, length, rest[start..<stop])
254        end if
255        start = stop
256        continue when dot or dots or (last and not follow)
257        index: usize = volume_find(self, into[0..<length])
258        if index < count and self.nodes[index].kind == io.symbolic_link then
259            inc hops
260            fail io.io_failed when hops > 8
261            node: ptr volume_node = addr self.nodes[index]
262            target: []u8 = node.val.contents[0..<node.val.length]
263            fail io.not_found when lenof target == 0
264            length = if target[0] == u8('/') then 0 else before end if
265            tail: usize = rest_length - start
266            fail io.io_failed when lenof target + 1 + tail > volume_room
267            mem.copy(rest[lenof target + 1..<lenof target + 1 + tail],
268                     rest[start..<rest_length]) else (problem)
269                _ = problem
270                fail io.io_failed
271            end
272            mem.copy(rest[0..<lenof target], target) else (problem)
273                _ = problem
274                fail io.io_failed
275            end
276            rest[lenof target] = u8('/')
277            rest_length = lenof target + 1 + tail
278            start = 0
279        elsif not last then
280            fail io.not_found when index == count
281              or self.nodes[index].kind <> io.subdirectory
282        end if
283    end loop
284end volume_walk
285
286--  The node PATH leads to, following a link in its last part when FOLLOW,
287--  or the node count when it leads to none or to the root.
288volume_resolve: (self: volume, path: []u8, follow: bool)
289                -> (index: usize) =
290    index = lenof self.nodes
291    mut room: [256]u8 = zeroed
292    length: usize = volume_walk(self, path, follow, room[0..<256])
293        else (problem)
294        _ = problem
295        return
296    end
297    return when length == 0
298    index = volume_find(self, room[0..<length])
299end volume_resolve
300
301--  Whether PATH leads to the root or to a directory, following links.
302volume_directory_at: (self: volume, path: []u8) -> (yes: bool) =
303    yes = false
304    mut room: [256]u8 = zeroed
305    length: usize = volume_walk(self, path, true, room[0..<256])
306        else (problem)
307        _ = problem
308        return
309    end
310    yes = length == 0 or volume_is_directory(self, room[0..<length])
311end volume_directory_at
312
313--  A free node with room for PATH, or the node count.
314volume_free_node: (self: volume, path: []u8) -> (index: usize) =
315    index = lenof self.nodes
316    for at in usize(0)..<lenof self.nodes do
317        node: ptr volume_node = addr self.nodes[at]
318        if node.val.path_length == 0 and lenof node.val.path >= lenof path then
319            index = at
320            return
321        end if
322    end for
323end volume_free_node
324
325--  Whether a file is open on the node INDEX.
326volume_busy: (self: volume, index: usize) -> (yes: bool) =
327    yes = false
328    for opening in self.state.val.openings do
329        if opening.open and opening.node == index then yes = true end if
330    end for
331end volume_busy
332
333--  Stamp the directory holding PATH as changed now.
334volume_touch_parent: (self: volume, path: []u8) -> none =
335    parent: usize = volume_find(self, volume_parent(path))
336    if parent < lenof self.nodes then
337        self.nodes[parent].modified = volume_now(self)
338    end if
339end volume_touch_parent
340
341--  Make a node of KIND at PATH, empty: its index, or `not_found` when its
342--  directory is missing and `io_failed` when there is no room.
343volume_make: (self: volume, path: []u8, kind: io.entry_kind)
344             -> (index: usize) ! io.not_found | io.io_failed =
345    index = lenof self.nodes
346    fail io.io_failed when not volume_valid(path)
347    fail io.not_found when not volume_is_directory(self, volume_parent(path))
348    index = volume_free_node(self, path)
349    fail io.io_failed when index == lenof self.nodes
350    node: ptr mut volume_node = addr self.nodes[index]
351    mem.copy(node.val.path, path) else (problem)
352        _ = problem
353        fail io.io_failed
354    end
355    node.val.path_length = lenof path
356    node.val.length = 0
357    node.val.kind = kind
358    node.val.read_only = false
359    node.val.modified = volume_now(self)
360    volume_touch_parent(self, path)
361end volume_make
362
363--  The opening a file of this volume is.
364volume_opening_of: (self: volume, opened: io.file)
365                   -> (opening: ptr mut volume_opening from self) ! io.io_failed =
366    fail io.io_failed when usize(opened) < 3
367      or usize(opened) - 3 >= volume_openings
368    opening = addr self.state.val.openings[usize(opened) - 3]
369    fail io.io_failed when not opening.val.open
370      or opening.val.node >= lenof self.nodes
371end volume_opening_of
372
373volume_open_file: (self: volume, path: text.host_string,
374                   options: io.open_options)
375                  -> (opened: io.file)
376                  ! io.already_exists | io.not_found | io.no_access | io.io_failed =
377    try volume_call(self)
378    inc self.state.val.opens
379    opened = io.file(0)
380    fail io.io_failed when not io.valid_options(options)
381    wanted: []u8 = text.host_bytes(path)
382    count: usize = lenof self.nodes
383    mut room: [256]u8 = zeroed
384    length: usize = volume_walk(self, wanted, true, room[0..<256])
385        else (problem)
386        fail io.not_found when problem == io.not_found
387        fail io.io_failed
388    end
389    fail io.io_failed when length == 0
390    mut index: usize = volume_find(self, room[0..<length])
391    if index < count then
392        fail io.already_exists when options.exclusive
393        fail io.io_failed when self.nodes[index].kind <> io.regular
394        fail io.no_access when options.write and self.nodes[index].read_only
395    else
396        --  A dangling link is a name that exists, and its target is the
397        --  file a creating open makes.
398        fail io.already_exists when options.exclusive
399          and volume_resolve(self, wanted, false) < count
400        fail io.not_found when not options.create
401        index = try volume_make(self, room[0..<length], io.regular)
402    end if
403    mut slot: usize = volume_openings
404    for at in usize(0)..<volume_openings do
405        if slot == volume_openings and not self.state.val.openings[at].open
406        then
407            slot = at
408        end if
409    end for
410    fail io.io_failed when slot == volume_openings
411    if options.truncate and self.nodes[index].length > 0 then
412        self.nodes[index].length = 0
413        self.nodes[index].modified = volume_now(self)
414    end if
415    self.state.val.openings[slot] =
416        (node: index, position: 0, readable: options.read,
417         writable: options.write, appending: options.append, open: true)
418    opened = io.file(slot + 3)
419end volume_open_file
420
421volume_open_read: (self: volume, path: text.host_string)
422                  -> (opened: io.file) ! io.not_found | io.no_access | io.io_failed =
423    opened = volume_open_file(self, path,
424                              (read: true, write: false, append: false,
425                               create: false, exclusive: false,
426                               truncate: false)) else (problem)
427        fail io.not_found when problem == io.not_found
428        fail io.no_access when problem == io.no_access
429        fail io.io_failed
430    end
431end volume_open_read
432
433volume_open_write: (self: volume, path: text.host_string)
434                   -> (opened: io.file) ! io.not_found | io.no_access | io.io_failed =
435    opened = volume_open_file(self, path,
436                              (read: false, write: true, append: false,
437                               create: true, exclusive: false,
438                               truncate: true)) else (problem)
439        fail io.not_found when problem == io.not_found
440        fail io.no_access when problem == io.no_access
441        fail io.io_failed
442    end
443end volume_open_write
444
445volume_same_file: (self: volume, left: text.host_string,
446                   right: text.host_string) -> (same: bool) ! io.io_failed =
447    try volume_call(self)
448    count: usize = lenof self.nodes
449    first: usize = volume_resolve(self, text.host_bytes(left), true)
450    fail io.io_failed when first == count
451    same = first == volume_resolve(self, text.host_bytes(right), true)
452end volume_same_file
453
454volume_close: (self: volume, sink opened: io.file) -> none ! io.io_failed =
455    opening: ptr mut volume_opening = try volume_opening_of(self, opened)
456    opening.val.open = false
457    try volume_call(self)
458end volume_close
459
460--  Copy what the node holds from OFFSET on into INTO.
461volume_take: (node: ptr volume_node, offset: u64, into: []mut u8)
462             -> (count: usize) =
463    count = 0
464    return when offset >= u64(node.val.length)
465    start: usize = usize(offset)
466    count = node.val.length - start
467    if count > lenof into then count = lenof into end if
468    for at in usize(0)..<count do
469        into[at] = node.val.contents[start + at]
470    end for
471end volume_take
472
473--  Put BYTES into the node from OFFSET on, zeros before them when OFFSET
474--  is past its end, as many as there is room for: how many.
475volume_put: (self: volume, index: usize, offset: u64, bytes: []u8)
476            -> (count: usize) ! io.io_failed =
477    node: ptr mut volume_node = addr self.nodes[index]
478    room: usize = lenof node.val.contents
479    fail io.io_failed when offset >= u64(room) and lenof bytes > 0
480    count = 0
481    return when lenof bytes == 0
482    start: usize = usize(offset)
483    count = room - start
484    if count > lenof bytes then count = lenof bytes end if
485    while node.val.length < start do
486        node.val.contents[node.val.length] = 0
487        inc node.val.length
488    end while
489    for at in usize(0)..<count do
490        node.val.contents[start + at] = bytes[at]
491    end for
492    if start + count > node.val.length then
493        node.val.length = start + count
494    end if
495    node.val.modified = volume_now(self)
496end volume_put
497
498volume_read: (self: volume, opened: io.file, into: []mut u8)
499             -> (count: usize) ! io.io_failed =
500    opening: ptr mut volume_opening = try volume_opening_of(self, opened)
501    fail io.io_failed when not opening.val.readable
502    try volume_call(self)
503    count = volume_take(addr self.nodes[opening.val.node],
504                        opening.val.position, into)
505    opening.val.position = opening.val.position + u64(count)
506end volume_read
507
508volume_write_some: (self: volume, opened: io.file, bytes: []u8)
509                   -> (count: usize) ! io.io_failed =
510    opening: ptr mut volume_opening = try volume_opening_of(self, opened)
511    fail io.io_failed when not opening.val.writable
512    try volume_call(self)
513    if opening.val.appending then
514        opening.val.position = u64(self.nodes[opening.val.node].length)
515    end if
516    count = try volume_put(self, opening.val.node, opening.val.position,
517                           bytes)
518    opening.val.position = opening.val.position + u64(count)
519end volume_write_some
520
521volume_write: (self: volume, opened: io.file, bytes: []u8)
522              -> none ! io.io_failed =
523    mut at: usize = 0
524    while at < lenof bytes do
525        count: usize = try volume_write_some(self, opened,
526                                             bytes[at..<lenof bytes])
527        at += count
528    end while
529end volume_write
530
531volume is io.reader (read: volume_read)
532volume is io.writer (write: volume_write, write_some: volume_write_some)
533volume is io.files
534    (open_read: volume_open_read, open_write: volume_open_write,
535     same_file: volume_same_file, close: volume_close)
536
537--  An open directory's `handle` is the index of its node, or the node
538--  count for the root, and `position` the next node a refill looks at.
539--  Its buffer holds records of a kind byte, a two-byte length and the
540--  name.
541
542--  The directory a node lies directly in is DIRECTORY, given as a node
543--  index or the node count for the root.
544volume_in: (self: volume, at: usize, directory: usize) -> (yes: bool) =
545    node: ptr volume_node = addr self.nodes[at]
546    yes = false
547    return when node.val.path_length == 0
548    parent: []u8 = volume_parent(node.val.path[0..<node.val.path_length])
549    if directory == lenof self.nodes then
550        yes = volume_same(parent, ".")
551        return
552    end if
553    holder: ptr volume_node = addr self.nodes[directory]
554    yes = volume_same(parent, holder.val.path[0..<holder.val.path_length])
555end volume_in
556
557volume_open_directory: (self: volume, path: text.host_string,
558                        buffer: []mut u8)
559                       -> (opened: io.directory from buffer)
560                       ! io.not_found | io.no_access | io.io_failed =
561    try volume_call(self)
562    wanted: []u8 = text.host_bytes(path)
563    count: usize = lenof self.nodes
564    mut room: [256]u8 = zeroed
565    length: usize = volume_walk(self, wanted, true, room[0..<256])
566        else (problem)
567        fail io.not_found when problem == io.not_found
568        fail io.io_failed
569    end
570    mut index: usize = count
571    if length > 0 then
572        index = volume_find(self, room[0..<length])
573        fail io.not_found when index == count
574          or self.nodes[index].kind <> io.subdirectory
575    end if
576    opened = (handle: index, stream: 0, position: 0, buffer: buffer,
577              filled: 0, cursor: 0, finished: false)
578end volume_open_directory
579
580volume_refill: (self: volume, inout opened: io.directory)
581               -> none ! io.io_failed =
582    opened.filled = 0
583    opened.cursor = 0
584    while opened.position < lenof self.nodes do
585        at: usize = opened.position
586        if volume_in(self, at, opened.handle) then
587            node: ptr volume_node = addr self.nodes[at]
588            name: []u8 = volume_leaf(node.val.path[0..<node.val.path_length])
589            fail io.io_failed when lenof name > 65535
590            needed: usize = lenof name + 3
591            if needed > lenof opened.buffer - opened.filled then
592                fail io.io_failed when opened.filled == 0
593                return
594            end if
595            place: usize = opened.filled
596            opened.buffer[place] = match node.val.kind
597                io.regular: 0
598                io.subdirectory: 1
599                io.symbolic_link: 2
600                io.special: 3
601            end match
602            opened.buffer[place + 1] = u8(lenof name & 255)
603            opened.buffer[place + 2] = u8(lenof name >> 8)
604            for n in usize(0)..<lenof name do
605                opened.buffer[place + 3 + n] = name[n]
606            end for
607            opened.filled += needed
608        end if
609        inc opened.position
610    end while
611    opened.finished = true
612end volume_refill
613
614volume_read_entry: (self: volume, inout opened: io.directory)
615                   -> (item: io.entry from opened) ! io.io_failed =
616    --  Every call counts, whether it refills the buffer or reads what an
617    --  earlier refill left there.
618    try volume_call(self)
619    if opened.cursor >= opened.filled and not opened.finished then
620        try volume_refill(self, opened)
621    end if
622    if opened.cursor >= opened.filled then
623        item = (name: text.host_over(opened.buffer[0..<0]), kind: io.special)
624        return
625    end if
626    fail io.io_failed when opened.filled - opened.cursor < 3
627    at: usize = opened.cursor
628    length: usize = usize(opened.buffer[at + 1])
629                    | (usize(opened.buffer[at + 2]) << 8)
630    fail io.io_failed when length > opened.filled - at - 3
631    opened.cursor = at + 3 + length
632    kind: io.entry_kind = if opened.buffer[at] == 0 then io.regular
633                          elsif opened.buffer[at] == 1 then io.subdirectory
634                          elsif opened.buffer[at] == 2 then io.symbolic_link
635                          else io.special end if
636    item = (name: text.host_over(opened.buffer[at + 3..<at + 3 + length]),
637            kind: kind)
638end volume_read_entry
639
640volume_entry_size: (self: volume, opened: io.directory,
641                    name: text.host_string)
642                   -> (size: u64) ! io.not_found | io.no_access | io.io_failed =
643    size = 0
644    try volume_call(self)
645    wanted: []u8 = text.host_bytes(name)
646    fail io.not_found when lenof wanted == 0
647    for byte in wanted do
648        fail io.not_found when byte == 0 or byte == u8('/')
649    end for
650    for at in usize(0)..<lenof self.nodes do
651        if volume_in(self, at, opened.handle) then
652            node: ptr volume_node = addr self.nodes[at]
653            if volume_same(volume_leaf(node.val.path[0..<node.val.path_length]),
654                           wanted) then
655                size = if node.val.kind == io.subdirectory then 0
656                       else u64(node.val.length) end if
657                return
658            end if
659        end if
660    end for
661    fail io.not_found
662end volume_entry_size
663
664volume_close_directory: (self: volume, sink opened: io.directory)
665                        -> none ! io.io_failed =
666    _ = opened.handle
667    try volume_call(self)
668end volume_close_directory
669
670volume_make_directory: (self: volume, path: text.host_string)
671                       -> none
672                       ! io.already_exists | io.not_found | io.no_access
673                       | io.io_failed =
674    try volume_call(self)
675    mut room: [256]u8 = zeroed
676    length: usize = volume_walk(self, text.host_bytes(path), false,
677                                room[0..<256]) else (problem)
678        fail io.not_found when problem == io.not_found
679        fail io.io_failed
680    end
681    fail io.already_exists when length == 0
682      or volume_find(self, room[0..<length]) < lenof self.nodes
683    _ = try volume_make(self, room[0..<length], io.subdirectory)
684end volume_make_directory
685
686--  Free the node at PATH when it is of the kind asked for, a link itself
687--  rather than its target.  A directory asked of a node that is none is
688--  `not_found`, as ENOTDIR is, and a file asked of a directory
689--  `io_failed`, as Linux's EISDIR is.
690volume_remove: (self: volume, path: text.host_string,
691                directory: bool) -> none ! io.not_found | io.no_access | io.io_failed =
692    try volume_call(self)
693    mut room: [256]u8 = zeroed
694    length: usize = volume_walk(self, text.host_bytes(path), false,
695                                room[0..<256]) else (problem)
696        fail io.not_found when problem == io.not_found
697        fail io.io_failed
698    end
699    fail io.io_failed when length == 0
700    wanted: []u8 = room[0..<length]
701    count: usize = lenof self.nodes
702    index: usize = volume_find(self, wanted)
703    fail io.not_found when index == count
704    node: ptr mut volume_node = addr self.nodes[index]
705    is_directory: bool = node.val.kind == io.subdirectory
706    fail io.not_found when directory and not is_directory
707    fail io.io_failed when is_directory <> directory or volume_busy(self, index)
708    if directory then
709        for at in usize(0)..<count do
710            fail io.io_failed when volume_in(self, at, index)
711        end for
712    end if
713    node.val.path_length = 0
714    volume_touch_parent(self, wanted)
715end volume_remove
716
717volume_remove_file: (self: volume, path: text.host_string)
718                    -> none ! io.not_found | io.no_access | io.io_failed =
719    try volume_remove(self, path, false)
720end volume_remove_file
721
722volume_remove_directory: (self: volume, path: text.host_string)
723                         -> none ! io.not_found | io.no_access | io.io_failed =
724    try volume_remove(self, path, true)
725end volume_remove_directory
726
727volume is io.directories
728    (open_directory: volume_open_directory, read_entry: volume_read_entry,
729     entry_size: volume_entry_size, close_directory: volume_close_directory,
730     make_directory: volume_make_directory,
731     remove_file: volume_remove_file,
732     remove_directory: volume_remove_directory)
733
734--  Whether PATH lies inside the directory PREFIX.
735volume_below: (path: []u8, prefix: []u8) -> (yes: bool) =
736    yes = lenof path > lenof prefix
737          and volume_same(path[0..<lenof prefix], prefix)
738          and path[lenof prefix] == u8('/')
739end volume_below
740
741--  Rename the node at INDEX, and with it everything under it, from OLD to
742--  NEW: `io_failed`, changing nothing, when a path would not fit its room.
743volume_move: (self: volume, index: usize, old: []u8, new: []u8)
744             -> none ! io.io_failed =
745    count: usize = lenof self.nodes
746    for at in usize(0)..<count do
747        node: ptr volume_node = addr self.nodes[at]
748        if node.val.path_length > 0
749          and volume_below(node.val.path[0..<node.val.path_length], old) then
750            moved: usize = node.val.path_length - lenof old + lenof new
751            fail io.io_failed when moved > lenof node.val.path
752        end if
753    end for
754    fail io.io_failed when lenof new > lenof self.nodes[index].path
755    for at in usize(0)..<count do
756        node: ptr mut volume_node = addr self.nodes[at]
757        if node.val.path_length > 0
758          and volume_below(node.val.path[0..<node.val.path_length], old) then
759            tail: usize = node.val.path_length - lenof old
760            mem.copy(node.val.path[lenof new..<lenof new + tail],
761                     node.val.path[lenof old..<node.val.path_length]) else (problem)
762                _ = problem
763                fail io.io_failed
764            end
765            mem.copy(node.val.path[0..<lenof new], new) else (problem)
766                _ = problem
767                fail io.io_failed
768            end
769            node.val.path_length = lenof new + tail
770        end if
771    end for
772    node: ptr mut volume_node = addr self.nodes[index]
773    mem.copy(node.val.path[0..<lenof new], new) else (problem)
774        _ = problem
775        fail io.io_failed
776    end
777    node.val.path_length = lenof new
778end volume_move
779
780volume_rename: (self: volume, old: text.host_string,
781                new: text.host_string)
782               -> none ! io.not_found | io.no_access | io.io_failed =
783    try volume_call(self)
784    inc self.state.val.renames
785    mut from_room: [256]u8 = zeroed
786    from_length: usize = volume_walk(self, text.host_bytes(old), false,
787                                     from_room[0..<256]) else (problem)
788        fail io.not_found when problem == io.not_found
789        fail io.io_failed
790    end
791    mut to_room: [256]u8 = zeroed
792    to_length: usize = volume_walk(self, text.host_bytes(new), false,
793                                   to_room[0..<256]) else (problem)
794        fail io.not_found when problem == io.not_found
795        fail io.io_failed
796    end
797    fail io.io_failed when from_length == 0 or to_length == 0
798    from_path: []u8 = from_room[0..<from_length]
799    to_path: []u8 = to_room[0..<to_length]
800    count: usize = lenof self.nodes
801    moving: usize = volume_find(self, from_path)
802    fail io.not_found when moving == count
803    fail io.not_found when not volume_is_directory(self, volume_parent(to_path))
804    fail io.io_failed when not volume_valid(to_path)
805    replaced: usize = volume_find(self, to_path)
806    return when replaced == moving
807    is_directory: bool = self.nodes[moving].kind == io.subdirectory
808    fail io.io_failed when is_directory and volume_below(to_path, from_path)
809    if replaced < count then
810        other: bool = self.nodes[replaced].kind == io.subdirectory
811        fail io.io_failed when other <> is_directory
812          or volume_busy(self, replaced)
813        if is_directory then
814            for at in usize(0)..<count do
815                fail io.io_failed when volume_in(self, at, replaced)
816            end for
817        end if
818    end if
819    try volume_move(self, moving, from_path, to_path)
820    if replaced < count then
821        self.nodes[replaced].path_length = 0
822    end if
823    volume_touch_parent(self, from_path)
824    volume_touch_parent(self, to_path)
825end volume_rename
826
827volume_status_of_node: (self: volume, index: usize) -> (found: io.status) =
828    node: ptr volume_node = addr self.nodes[index]
829    found = (size: if node.val.kind == io.subdirectory then 0
830                   else u64(node.val.length) end if,
831             kind: node.val.kind, modified: node.val.modified,
832             read_only: node.val.read_only)
833end volume_status_of_node
834
835volume_status_of: (self: volume, path: text.host_string,
836                   links: io.link_handling)
837                  -> (found: io.status) ! io.not_found | io.no_access | io.io_failed =
838    try volume_call(self)
839    wanted: []u8 = text.host_bytes(path)
840    found = (size: 0, kind: io.subdirectory, modified: volume_now(self),
841             read_only: false)
842    mut room: [256]u8 = zeroed
843    length: usize = volume_walk(self, wanted, links == io.following,
844                                room[0..<256]) else (problem)
845        fail io.not_found when problem == io.not_found
846        fail io.io_failed
847    end
848    return when length == 0
849    index: usize = volume_find(self, room[0..<length])
850    fail io.not_found when index == lenof self.nodes
851    found = volume_status_of_node(self, index)
852end volume_status_of
853
854volume_status_of_file: (self: volume, opened: io.file)
855                       -> (found: io.status) ! io.io_failed =
856    opening: ptr mut volume_opening = try volume_opening_of(self, opened)
857    try volume_call(self)
858    found = volume_status_of_node(self, opening.val.node)
859end volume_status_of_file
860
861volume_set_read_only: (self: volume, path: text.host_string,
862                       read_only: bool)
863                      -> none ! io.not_found | io.no_access | io.io_failed =
864    try volume_call(self)
865    index: usize = volume_resolve(self, text.host_bytes(path), true)
866    fail io.not_found when index == lenof self.nodes
867    self.nodes[index].read_only = read_only
868end volume_set_read_only
869
870volume_synchronize: (self: volume, opened: io.file,
871                     writing: io.synchronizing)
872                    -> none ! io.io_failed =
873    _ = writing
874    _ = try volume_opening_of(self, opened)
875    try volume_call(self)
876    inc self.state.val.synchronizations
877end volume_synchronize
878
879volume_truncate: (self: volume, opened: io.file, size: u64)
880                 -> none ! io.io_failed =
881    opening: ptr mut volume_opening = try volume_opening_of(self, opened)
882    fail io.io_failed when not opening.val.writable
883    try volume_call(self)
884    node: ptr mut volume_node = addr self.nodes[opening.val.node]
885    fail io.io_failed when size > u64(lenof node.val.contents)
886    wanted: usize = usize(size)
887    while node.val.length < wanted do
888        node.val.contents[node.val.length] = 0
889        inc node.val.length
890    end while
891    node.val.length = wanted
892    node.val.modified = volume_now(self)
893end volume_truncate
894
895volume_read_at: (self: volume, opened: io.file, offset: u64,
896                 into: []mut u8) -> (count: usize) ! io.io_failed =
897    opening: ptr mut volume_opening = try volume_opening_of(self, opened)
898    fail io.io_failed when not opening.val.readable
899    try volume_call(self)
900    count = volume_take(addr self.nodes[opening.val.node], offset, into)
901end volume_read_at
902
903volume_write_at: (self: volume, opened: io.file, offset: u64,
904                  bytes: []u8) -> none ! io.io_failed =
905    opening: ptr mut volume_opening = try volume_opening_of(self, opened)
906    fail io.io_failed when not opening.val.writable
907    try volume_call(self)
908    mut written: usize = 0
909    while written < lenof bytes do
910        count: usize = try volume_put(self, opening.val.node,
911                                      offset + u64(written),
912                                      bytes[written..<lenof bytes])
913        written += count
914    end while
915end volume_write_at
916
917volume_make_temporary: (self: volume, directory: text.host_string,
918                        names: any io.entropy, into: []mut u8)
919                       -> (made: io.temporary from into)
920                       ! io.not_found | io.no_access | io.io_failed =
921    try volume_call(self)
922    made = (opened: io.file(0), path: text.host_over(into[0..<0]))
923    folder: []u8 = text.host_bytes(directory)
924    fail io.not_found when lenof folder > 0
925      and not volume_directory_at(self, folder)
926    for attempt in usize(0)..<io.temporary_tries do
927        _ = attempt
928        inc self.state.val.temporary_tries
929        mut bytes: [8]u8 = zeroed
930        try names.fill_random(bytes[0..<8])
931        path: text.host_string = try io.temporary_path(directory, bytes, into)
932        opened: io.file = volume_open_file(self, path, io.creating) else (problem)
933            continue when problem == io.already_exists
934            fail io.not_found when problem == io.not_found
935            fail io.no_access when problem == io.no_access
936            fail io.io_failed
937        end
938        made = (opened: opened, path: path)
939        return
940    end for
941    fail io.io_failed
942end volume_make_temporary
943
944volume_current_directory: (self: volume, into: []mut u8)
945                          -> (path: text.host_string from into) ! io.io_failed =
946    path = text.host_over(into[0..<0])
947    try volume_call(self)
948    working: []u8 = self.working
949    fail io.io_failed when lenof working > lenof into
950    mem.copy(into[0..<lenof working], working) else (problem)
951        _ = problem
952        fail io.io_failed
953    end
954    path = text.host_over(into[0..<lenof working])
955end volume_current_directory
956
957volume is io.file_system
958    (open_file: volume_open_file, rename: volume_rename,
959     status_of: volume_status_of, status_of_file: volume_status_of_file,
960     set_read_only: volume_set_read_only, synchronize: volume_synchronize,
961     truncate: volume_truncate, read_at: volume_read_at,
962     write_at: volume_write_at, make_temporary: volume_make_temporary,
963     current_directory: volume_current_directory)