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)