Landin library reference discuss source

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