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)