kobako 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +217 -10
- data/Cargo.lock +139 -141
- data/README.md +96 -574
- data/ROADMAP.md +2 -1
- data/crates/kobako-runtime/CHANGELOG.md +269 -1
- data/crates/kobako-runtime/Cargo.toml +25 -15
- data/crates/kobako-runtime/README.md +7 -3
- data/crates/kobako-runtime/src/dispatch.rs +1 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
- data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
- data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +21 -387
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +11 -6
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +31 -104
- data/crates/kobako-runtime/src/profile.rs +137 -1
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +8 -2
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +2 -2
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
- data/crates/kobako-transport/src/envelope/reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/request.rs +2 -2
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
- data/crates/kobako-wasmtime/Cargo.toml +15 -14
- data/crates/kobako-wasmtime/README.md +9 -5
- data/crates/kobako-wasmtime/src/abi.rs +12 -20
- data/crates/kobako-wasmtime/src/ambient.rs +1 -2
- data/crates/kobako-wasmtime/src/cache.rs +5 -5
- data/crates/kobako-wasmtime/src/config.rs +22 -7
- data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
- data/crates/kobako-wasmtime/src/driver.rs +29 -96
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +14 -8
- data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
- data/crates/kobako-wasmtime/src/invocation.rs +26 -46
- data/crates/kobako-wasmtime/src/lib.rs +1 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
- data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
- data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
- data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
- data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
- data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
- data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +464 -413
- data/crates/kobako-wasmtime/src/trap.rs +9 -9
- data/crates/kobako-wasmtime/src/wasi.rs +53 -37
- data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/ext/kobako/src/runtime/bridge.rs +1 -1
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +28 -14
- data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
- data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
- data/ext/kobako/src/runtime/filesystem.rs +109 -137
- data/ext/kobako/src/runtime/gvl.rs +43 -8
- data/ext/kobako/src/runtime/snapshot.rs +1 -1
- data/ext/kobako/src/runtime.rs +6 -1
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -2
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +2 -10
- data/lib/kobako/catalog/services.rb +2 -2
- data/lib/kobako/catalog/snippets.rb +7 -7
- data/lib/kobako/catalog.rb +1 -1
- data/lib/kobako/codec/decoder.rb +10 -10
- data/lib/kobako/codec/ext_types.rb +3 -5
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +3 -3
- data/lib/kobako/context.rb +5 -5
- data/lib/kobako/filesystem.rb +44 -13
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +4 -4
- data/lib/kobako/sandbox.rb +28 -28
- data/lib/kobako/sandbox_options.rb +2 -3
- data/lib/kobako/snippet/bytecode.rb +1 -1
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/transport/dispatcher.rb +5 -5
- data/lib/kobako/transport/exposure.rb +2 -2
- data/lib/kobako/transport/reflection.rb +7 -7
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +1 -1
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +34 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +0 -4
- data/sig/kobako/catalog/services.rbs +1 -1
- data/sig/kobako/catalog/snippets.rbs +3 -3
- data/sig/kobako/catalog.rbs +1 -1
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +1 -1
- data/sig/kobako/codec.rbs +1 -2
- data/sig/kobako/context.rbs +1 -1
- data/sig/kobako/filesystem.rbs +19 -14
- data/sig/kobako/guest_yielder.rbs +1 -1
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +2 -2
- data/sig/kobako/runtime.rbs +1 -1
- data/sig/kobako/sandbox.rbs +3 -5
- data/sig/kobako/sandbox_options.rbs +1 -1
- data/sig/kobako/transport/dispatcher.rbs +2 -2
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +2 -2
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport.rbs +1 -1
- metadata +67 -19
- data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
- data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
- data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
- data/crates/kobako-wasmtime/src/io.rs +0 -89
- data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
- /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
- /data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +0 -0
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
//! writes beneath a mounted path, and how it fails where nothing is
|
|
3
3
|
//! mounted, nothing is there, or the Mount only reads.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
use crate::support;
|
|
6
6
|
|
|
7
7
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
8
8
|
use std::sync::Arc;
|
|
9
9
|
|
|
10
|
-
use kobako_runtime::filesystem::Access;
|
|
11
10
|
use kobako_runtime::filesystem::{
|
|
12
|
-
|
|
11
|
+
FileOperations, FileSystemType, FileType, Filesystem, FilesystemError, Finish, InodeOperations,
|
|
12
|
+
Kiocb, Kstat, Lookup, OpenHow, Tmpfs,
|
|
13
13
|
};
|
|
14
14
|
use kobako_runtime::grant::Grant;
|
|
15
|
-
use kobako_runtime::mount::Mount;
|
|
16
|
-
use kobako_runtime::
|
|
15
|
+
use kobako_runtime::mount::{Access, Mount};
|
|
16
|
+
use kobako_runtime::security::prepare_creds_within;
|
|
17
|
+
use kobako_runtime::vfs::Inode;
|
|
18
|
+
use kobako_runtime::vfs::{self, Dentry, Path};
|
|
17
19
|
use kobako_runtime::{Completion, Entry, Runtime};
|
|
18
20
|
use kobako_transport::envelope::{Flag, Outcome, Preamble};
|
|
19
21
|
|
|
@@ -43,24 +45,42 @@ fn run(source: &str, grants: &[Grant], flags: Vec<Flag>) -> Option<Result<String
|
|
|
43
45
|
})
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
fn mount(filesystem: Arc<dyn
|
|
47
|
-
Grant::Mount(
|
|
48
|
+
fn mount(filesystem: Arc<dyn FileSystemType>, access: Access) -> Grant {
|
|
49
|
+
Grant::Mount(
|
|
50
|
+
Mount::new(
|
|
51
|
+
"/data",
|
|
52
|
+
Filesystem::new(filesystem).expect("the instance must stand"),
|
|
53
|
+
access,
|
|
54
|
+
)
|
|
55
|
+
.expect("/data is a guest path"),
|
|
56
|
+
)
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
fn
|
|
59
|
+
fn note_tmpfs() -> Arc<Tmpfs> {
|
|
51
60
|
Arc::new([("note.txt", "hello\nworld\n")].into_iter().collect())
|
|
52
61
|
}
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
/// The root of `filesystem` as the host reaches it through the VFS.
|
|
64
|
+
fn host<F: FileSystemType>(filesystem: &Arc<F>) -> Path {
|
|
65
|
+
let filesystem = Filesystem::new(filesystem.clone()).expect("the instance must stand");
|
|
66
|
+
let mount = Mount::new("/data", filesystem, Access::Read).expect("/data is a guest path");
|
|
67
|
+
Path::root(mount.mnt())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fn contents<F: FileSystemType>(filesystem: &Arc<F>, path: &str) -> Vec<u8> {
|
|
71
|
+
let mut how = OpenHow::default();
|
|
72
|
+
how.read = true;
|
|
73
|
+
let op = vfs::build_open_flags(&how).expect("a read means an open");
|
|
74
|
+
let Ok(mut file) = vfs::do_file_open(
|
|
75
|
+
&prepare_creds_within(&[], None),
|
|
76
|
+
&host(filesystem),
|
|
77
|
+
path,
|
|
78
|
+
&op,
|
|
79
|
+
) else {
|
|
80
|
+
panic!("the file must exist");
|
|
81
|
+
};
|
|
62
82
|
let mut buf = vec![0; 64];
|
|
63
|
-
let read = file.
|
|
83
|
+
let read = file.pread(&mut buf, 0).expect("the file must read");
|
|
64
84
|
buf.truncate(read);
|
|
65
85
|
buf
|
|
66
86
|
}
|
|
@@ -70,7 +90,7 @@ fn contents(filesystem: &dyn Filesystem, path: &str) -> Vec<u8> {
|
|
|
70
90
|
fn a_script_reads_a_file_beneath_a_mounted_path() {
|
|
71
91
|
let Some(answer) = run(
|
|
72
92
|
r#"print File.read("/data/note.txt")"#,
|
|
73
|
-
&[mount(
|
|
93
|
+
&[mount(note_tmpfs(), Access::Read)],
|
|
74
94
|
Vec::new(),
|
|
75
95
|
) else {
|
|
76
96
|
return;
|
|
@@ -85,10 +105,10 @@ fn a_script_reads_a_file_beneath_a_mounted_path() {
|
|
|
85
105
|
// @behavior FL-002
|
|
86
106
|
#[test]
|
|
87
107
|
fn a_script_writes_a_file_beneath_a_read_write_mount() {
|
|
88
|
-
let
|
|
108
|
+
let tmpfs = Arc::new(Tmpfs::new());
|
|
89
109
|
let Some(answer) = run(
|
|
90
110
|
r#"print File.open("/data/out.txt", "w") { |f| f.write("kept"); :done }"#,
|
|
91
|
-
&[mount(
|
|
111
|
+
&[mount(tmpfs.clone(), Access::ReadWrite)],
|
|
92
112
|
Vec::new(),
|
|
93
113
|
) else {
|
|
94
114
|
return;
|
|
@@ -99,9 +119,9 @@ fn a_script_writes_a_file_beneath_a_read_write_mount() {
|
|
|
99
119
|
"File.open must answer its block's value"
|
|
100
120
|
);
|
|
101
121
|
assert_eq!(
|
|
102
|
-
contents(
|
|
122
|
+
contents(&tmpfs, "out.txt"),
|
|
103
123
|
b"kept",
|
|
104
|
-
"a file written in File.open's block through the bundled guest must land in the
|
|
124
|
+
"a file written in File.open's block through the bundled guest must land in the Tmpfs"
|
|
105
125
|
);
|
|
106
126
|
}
|
|
107
127
|
|
|
@@ -110,7 +130,7 @@ fn a_script_writes_a_file_beneath_a_read_write_mount() {
|
|
|
110
130
|
fn a_script_reads_a_file_line_by_line() {
|
|
111
131
|
let Some(answer) = run(
|
|
112
132
|
r#"File.open("/data/note.txt") { |f| f.each_line { |line| print line.upcase } }; nil"#,
|
|
113
|
-
&[mount(
|
|
133
|
+
&[mount(note_tmpfs(), Access::Read)],
|
|
114
134
|
Vec::new(),
|
|
115
135
|
) else {
|
|
116
136
|
return;
|
|
@@ -141,7 +161,7 @@ fn opening_a_file_with_nothing_mounted_is_unimplemented() {
|
|
|
141
161
|
fn opening_a_missing_file_raises_enoent() {
|
|
142
162
|
let Some(answer) = run(
|
|
143
163
|
r#"File.read("/data/missing.txt")"#,
|
|
144
|
-
&[mount(
|
|
164
|
+
&[mount(note_tmpfs(), Access::Read)],
|
|
145
165
|
Vec::new(),
|
|
146
166
|
) else {
|
|
147
167
|
return;
|
|
@@ -159,7 +179,7 @@ fn opening_a_missing_file_raises_enoent() {
|
|
|
159
179
|
fn writing_beneath_a_read_only_mount_raises_erofs() {
|
|
160
180
|
let Some(answer) = run(
|
|
161
181
|
r#"File.write("/data/note.txt", "changed")"#,
|
|
162
|
-
&[mount(
|
|
182
|
+
&[mount(note_tmpfs(), Access::Read)],
|
|
163
183
|
Vec::new(),
|
|
164
184
|
) else {
|
|
165
185
|
return;
|
|
@@ -176,7 +196,7 @@ fn writing_beneath_a_read_only_mount_raises_erofs() {
|
|
|
176
196
|
// @behavior FL-012
|
|
177
197
|
#[test]
|
|
178
198
|
fn a_write_past_a_mounts_space_raises_enospc() {
|
|
179
|
-
let full = Arc::new(
|
|
199
|
+
let full = Arc::new(Tmpfs::new().with_size(Some(4)));
|
|
180
200
|
let Some(answer) = run(
|
|
181
201
|
r#"File.open("/data/note.txt", "w") { |file| file.write("five!") }"#,
|
|
182
202
|
&[mount(full, Access::ReadWrite)],
|
|
@@ -187,7 +207,7 @@ fn a_write_past_a_mounts_space_raises_enospc() {
|
|
|
187
207
|
assert_eq!(
|
|
188
208
|
answer,
|
|
189
209
|
Err("Errno::ENOSPC".to_string()),
|
|
190
|
-
"IO#write past a
|
|
210
|
+
"IO#write past a Tmpfs's limit through the bundled guest must raise Errno::ENOSPC"
|
|
191
211
|
);
|
|
192
212
|
}
|
|
193
213
|
|
|
@@ -214,7 +234,7 @@ fn a_name_or_path_past_linuxs_limits_raises_enametoolong() {
|
|
|
214
234
|
"#;
|
|
215
235
|
let Some(answer) = run(
|
|
216
236
|
script,
|
|
217
|
-
&[mount(Arc::new(
|
|
237
|
+
&[mount(Arc::new(Tmpfs::new()), Access::ReadWrite)],
|
|
218
238
|
Vec::new(),
|
|
219
239
|
) else {
|
|
220
240
|
return;
|
|
@@ -233,7 +253,7 @@ fn a_name_or_path_past_linuxs_limits_raises_enametoolong() {
|
|
|
233
253
|
fn a_code_the_engine_answers_raises_its_own_errno() {
|
|
234
254
|
let Some(answer) = run(
|
|
235
255
|
r#"File.rename("/data", "/data/moved")"#,
|
|
236
|
-
&[mount(
|
|
256
|
+
&[mount(note_tmpfs(), Access::ReadWrite)],
|
|
237
257
|
Vec::new(),
|
|
238
258
|
) else {
|
|
239
259
|
return;
|
|
@@ -281,7 +301,7 @@ fn the_disable_file_flag_removes_file() {
|
|
|
281
301
|
fn an_io_may_stand_for_a_file_the_guest_opened() {
|
|
282
302
|
let Some(answer) = run(
|
|
283
303
|
r#"File.open("/data/note.txt") { |f| print IO.new(f.fileno, "r").read }; nil"#,
|
|
284
|
-
&[mount(
|
|
304
|
+
&[mount(note_tmpfs(), Access::Read)],
|
|
285
305
|
Vec::new(),
|
|
286
306
|
) else {
|
|
287
307
|
return;
|
|
@@ -298,16 +318,16 @@ fn an_io_may_stand_for_a_file_the_guest_opened() {
|
|
|
298
318
|
// @behavior FL-024
|
|
299
319
|
#[test]
|
|
300
320
|
fn an_exclusive_create_refuses_a_file_already_there() {
|
|
301
|
-
let
|
|
321
|
+
let tmpfs = note_tmpfs();
|
|
302
322
|
let Some(answer) = run(
|
|
303
323
|
r#"File.open("/data/note.txt", "wx") { |f| f.write("lost") }"#,
|
|
304
|
-
&[mount(
|
|
324
|
+
&[mount(tmpfs.clone(), Access::ReadWrite)],
|
|
305
325
|
Vec::new(),
|
|
306
326
|
) else {
|
|
307
327
|
return;
|
|
308
328
|
};
|
|
309
329
|
assert_eq!(
|
|
310
|
-
(answer, contents(
|
|
330
|
+
(answer, contents(&tmpfs, "note.txt")),
|
|
311
331
|
(Err("Errno::EEXIST".to_string()), b"hello\nworld\n".to_vec()),
|
|
312
332
|
"File.open with wx of a file already there through the bundled guest must raise \
|
|
313
333
|
Errno::EEXIST and leave the file as it was"
|
|
@@ -317,16 +337,16 @@ fn an_exclusive_create_refuses_a_file_already_there() {
|
|
|
317
337
|
// @behavior FL-025
|
|
318
338
|
#[test]
|
|
319
339
|
fn an_exclusive_create_makes_a_file_nothing_is_at() {
|
|
320
|
-
let
|
|
340
|
+
let tmpfs = Arc::new(Tmpfs::new());
|
|
321
341
|
let Some(answer) = run(
|
|
322
342
|
r#"File.open("/data/lock", "w+x") { |f| f.write("mine") }; nil"#,
|
|
323
|
-
&[mount(
|
|
343
|
+
&[mount(tmpfs.clone(), Access::ReadWrite)],
|
|
324
344
|
Vec::new(),
|
|
325
345
|
) else {
|
|
326
346
|
return;
|
|
327
347
|
};
|
|
328
348
|
assert_eq!(
|
|
329
|
-
(answer, contents(
|
|
349
|
+
(answer, contents(&tmpfs, "lock")),
|
|
330
350
|
(Ok(String::new()), b"mine".to_vec()),
|
|
331
351
|
"File.open with w+x of a path nothing is at through the bundled guest must create the \
|
|
332
352
|
file with what was written"
|
|
@@ -351,7 +371,7 @@ fn an_access_mode_cruby_refuses_raises_argument_error() {
|
|
|
351
371
|
"#;
|
|
352
372
|
let Some(answer) = run(
|
|
353
373
|
script,
|
|
354
|
-
&[mount(
|
|
374
|
+
&[mount(note_tmpfs(), Access::ReadWrite)],
|
|
355
375
|
Vec::new(),
|
|
356
376
|
) else {
|
|
357
377
|
return;
|
|
@@ -371,16 +391,16 @@ fn an_access_mode_cruby_refuses_raises_argument_error() {
|
|
|
371
391
|
// @behavior FL-027
|
|
372
392
|
#[test]
|
|
373
393
|
fn an_integer_mode_opens_by_its_file_constants_flags() {
|
|
374
|
-
let
|
|
394
|
+
let tmpfs = note_tmpfs();
|
|
375
395
|
let Some(answer) = run(
|
|
376
396
|
r#"File.open("/data/note.txt", File::WRONLY | File::CREAT | File::EXCL) { }"#,
|
|
377
|
-
&[mount(
|
|
397
|
+
&[mount(tmpfs.clone(), Access::ReadWrite)],
|
|
378
398
|
Vec::new(),
|
|
379
399
|
) else {
|
|
380
400
|
return;
|
|
381
401
|
};
|
|
382
402
|
assert_eq!(
|
|
383
|
-
(answer, contents(
|
|
403
|
+
(answer, contents(&tmpfs, "note.txt")),
|
|
384
404
|
(Err("Errno::EEXIST".to_string()), b"hello\nworld\n".to_vec()),
|
|
385
405
|
"File.open with WRONLY | CREAT | EXCL of a file already there through the bundled guest \
|
|
386
406
|
must raise Errno::EEXIST and leave the file as it was"
|
|
@@ -392,16 +412,25 @@ fn an_integer_mode_opens_by_its_file_constants_flags() {
|
|
|
392
412
|
// @behavior FL-028
|
|
393
413
|
#[test]
|
|
394
414
|
fn an_exclusive_open_without_create_makes_no_file() {
|
|
395
|
-
let
|
|
415
|
+
let tmpfs = Arc::new(Tmpfs::new());
|
|
396
416
|
let Some(answer) = run(
|
|
397
417
|
r#"File.open("/data/lock", File::RDONLY | File::EXCL) { }"#,
|
|
398
|
-
&[mount(
|
|
418
|
+
&[mount(tmpfs.clone(), Access::ReadWrite)],
|
|
399
419
|
Vec::new(),
|
|
400
420
|
) else {
|
|
401
421
|
return;
|
|
402
422
|
};
|
|
403
423
|
assert_eq!(
|
|
404
|
-
(
|
|
424
|
+
(
|
|
425
|
+
answer,
|
|
426
|
+
vfs::vfs_fstatat(
|
|
427
|
+
&prepare_creds_within(&[], None),
|
|
428
|
+
&host(&tmpfs),
|
|
429
|
+
"lock",
|
|
430
|
+
false
|
|
431
|
+
)
|
|
432
|
+
.is_err()
|
|
433
|
+
),
|
|
405
434
|
(Err("Errno::ENOENT".to_string()), true),
|
|
406
435
|
"File.open with RDONLY | EXCL of a path nothing is at through the bundled guest must \
|
|
407
436
|
raise Errno::ENOENT and make no file"
|
|
@@ -427,7 +456,7 @@ fn an_integer_mode_with_a_flag_file_constants_lacks_raises_einval() {
|
|
|
427
456
|
"#;
|
|
428
457
|
let Some(answer) = run(
|
|
429
458
|
script,
|
|
430
|
-
&[mount(
|
|
459
|
+
&[mount(note_tmpfs(), Access::ReadWrite)],
|
|
431
460
|
Vec::new(),
|
|
432
461
|
) else {
|
|
433
462
|
return;
|
|
@@ -444,7 +473,7 @@ fn an_integer_mode_with_a_flag_file_constants_lacks_raises_einval() {
|
|
|
444
473
|
// @behavior FL-031
|
|
445
474
|
#[test]
|
|
446
475
|
fn rdonly_beside_wronly_opens_to_write_only() {
|
|
447
|
-
let
|
|
476
|
+
let tmpfs = note_tmpfs();
|
|
448
477
|
let script = r#"
|
|
449
478
|
File.open("/data/note.txt", File::RDONLY | File::WRONLY) do |file|
|
|
450
479
|
file.write("J")
|
|
@@ -457,13 +486,13 @@ fn rdonly_beside_wronly_opens_to_write_only() {
|
|
|
457
486
|
"#;
|
|
458
487
|
let Some(answer) = run(
|
|
459
488
|
script,
|
|
460
|
-
&[mount(
|
|
489
|
+
&[mount(tmpfs.clone(), Access::ReadWrite)],
|
|
461
490
|
Vec::new(),
|
|
462
491
|
) else {
|
|
463
492
|
return;
|
|
464
493
|
};
|
|
465
494
|
assert_eq!(
|
|
466
|
-
(answer, contents(
|
|
495
|
+
(answer, contents(&tmpfs, "note.txt")),
|
|
467
496
|
(Ok("IOError".to_string()), b"Jello\nworld\n".to_vec()),
|
|
468
497
|
"File.open with RDONLY | WRONLY through the bundled guest must write and raise IOError \
|
|
469
498
|
on a read"
|
|
@@ -472,18 +501,18 @@ fn rdonly_beside_wronly_opens_to_write_only() {
|
|
|
472
501
|
|
|
473
502
|
// Neither open asks to write, so only a mode that reaches the host as it
|
|
474
503
|
// stands, rather than one refused before it leaves the guest, changes
|
|
475
|
-
// the
|
|
504
|
+
// the Tmpfs.
|
|
476
505
|
// @behavior FL-032
|
|
477
506
|
#[test]
|
|
478
507
|
fn an_integer_mode_may_create_or_truncate_without_asking_to_write() {
|
|
479
|
-
let
|
|
508
|
+
let tmpfs = note_tmpfs();
|
|
480
509
|
let script = r#"
|
|
481
510
|
File.open("/data/lock", File::CREAT | File::EXCL) { }
|
|
482
511
|
File.open("/data/note.txt", File::RDONLY | File::TRUNC) { }
|
|
483
512
|
"#;
|
|
484
513
|
let Some(answer) = run(
|
|
485
514
|
script,
|
|
486
|
-
&[mount(
|
|
515
|
+
&[mount(tmpfs.clone(), Access::ReadWrite)],
|
|
487
516
|
Vec::new(),
|
|
488
517
|
) else {
|
|
489
518
|
return;
|
|
@@ -491,8 +520,8 @@ fn an_integer_mode_may_create_or_truncate_without_asking_to_write() {
|
|
|
491
520
|
assert_eq!(
|
|
492
521
|
(
|
|
493
522
|
answer,
|
|
494
|
-
contents(
|
|
495
|
-
contents(
|
|
523
|
+
contents(&tmpfs, "lock"),
|
|
524
|
+
contents(&tmpfs, "note.txt")
|
|
496
525
|
),
|
|
497
526
|
(Ok(String::new()), Vec::new(), Vec::new()),
|
|
498
527
|
"File.open with CREAT | EXCL of a new path and with RDONLY | TRUNC of a file through the \
|
|
@@ -503,17 +532,27 @@ fn an_integer_mode_may_create_or_truncate_without_asking_to_write() {
|
|
|
503
532
|
// @behavior FL-033
|
|
504
533
|
#[test]
|
|
505
534
|
fn a_path_holding_a_nul_byte_raises_argument_error() {
|
|
535
|
+
let script = r#"
|
|
536
|
+
path = "/data/note.txt\0x"
|
|
537
|
+
calls = [-> { File.open(path) { } }, -> { File.read(path) }, -> { File.write(path, "x") },
|
|
538
|
+
-> { File.exist?(path) }, -> { File.stat(path) }, -> { Dir.exist?(path) },
|
|
539
|
+
-> { Dir.children(path) }]
|
|
540
|
+
print calls.map { |call| begin; call.call; "none"; rescue Exception => e; e.class.to_s; end }.join(",")
|
|
541
|
+
"#;
|
|
506
542
|
let Some(answer) = run(
|
|
507
|
-
|
|
508
|
-
&[mount(
|
|
543
|
+
script,
|
|
544
|
+
&[mount(note_tmpfs(), Access::ReadWrite)],
|
|
509
545
|
Vec::new(),
|
|
510
546
|
) else {
|
|
511
547
|
return;
|
|
512
548
|
};
|
|
513
549
|
assert_eq!(
|
|
514
550
|
answer,
|
|
515
|
-
|
|
516
|
-
|
|
551
|
+
Ok("ArgumentError,ArgumentError,ArgumentError,ArgumentError,ArgumentError,ArgumentError,\
|
|
552
|
+
ArgumentError"
|
|
553
|
+
.to_string()),
|
|
554
|
+
"File.open, File.read, File.write, File.exist?, File.stat, Dir.exist?, and Dir.children of a \
|
|
555
|
+
path holding a NUL byte through the bundled guest must each raise ArgumentError"
|
|
517
556
|
);
|
|
518
557
|
}
|
|
519
558
|
|
|
@@ -538,104 +577,122 @@ fn file_constants_names_the_flags_an_open_honors() {
|
|
|
538
577
|
);
|
|
539
578
|
}
|
|
540
579
|
|
|
541
|
-
/// A
|
|
580
|
+
/// A Tmpfs whose every open file is handed through `wrap` first.
|
|
542
581
|
struct WrapsFiles<W> {
|
|
543
|
-
|
|
544
|
-
wrap: W
|
|
582
|
+
inner: Arc<dyn InodeOperations>,
|
|
583
|
+
wrap: Arc<W>,
|
|
545
584
|
}
|
|
546
585
|
|
|
547
586
|
impl<W> WrapsFiles<W> {
|
|
548
587
|
fn over_note(contents: &str, wrap: W) -> Arc<Self> {
|
|
588
|
+
let tmpfs: Tmpfs = [("note.txt", contents)].into_iter().collect();
|
|
549
589
|
Arc::new(Self {
|
|
550
|
-
|
|
551
|
-
wrap,
|
|
590
|
+
inner: Arc::new(tmpfs),
|
|
591
|
+
wrap: Arc::new(wrap),
|
|
552
592
|
})
|
|
553
593
|
}
|
|
554
594
|
}
|
|
555
595
|
|
|
556
|
-
impl<W, F>
|
|
596
|
+
impl<W, F> WrapsFiles<W>
|
|
557
597
|
where
|
|
558
|
-
W: Fn(Box<dyn
|
|
559
|
-
F:
|
|
598
|
+
W: Fn(Box<dyn FileOperations>) -> F + Send + Sync + 'static,
|
|
599
|
+
F: FileOperations + 'static,
|
|
560
600
|
{
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
self.memory.symlink_metadata(path)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
|
|
574
|
-
self.memory.stored_path(path)
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
fn canonicalize_beneath(&self, base: &str, path: &str) -> Result<String, FilesystemError> {
|
|
578
|
-
self.memory.canonicalize_beneath(base, path)
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
fn for_policy(&self) -> Option<Arc<dyn Filesystem>> {
|
|
582
|
-
self.memory.for_policy()
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
fn hold_directory(&self, path: &str) -> Result<Option<Hold>, FilesystemError> {
|
|
586
|
-
self.memory.hold_directory(path)
|
|
601
|
+
/// `found` with the entry it names handing its files through `wrap` too.
|
|
602
|
+
fn wrap_found(&self, found: Lookup) -> Lookup {
|
|
603
|
+
match found {
|
|
604
|
+
Lookup::Found(inode) => Lookup::Found(inode.with_ops(Arc::new(Self {
|
|
605
|
+
inner: inode.ops().clone(),
|
|
606
|
+
wrap: self.wrap.clone(),
|
|
607
|
+
}))),
|
|
608
|
+
other => other,
|
|
609
|
+
}
|
|
587
610
|
}
|
|
611
|
+
}
|
|
588
612
|
|
|
589
|
-
|
|
590
|
-
|
|
613
|
+
impl<W, F> FileSystemType for WrapsFiles<W>
|
|
614
|
+
where
|
|
615
|
+
W: Fn(Box<dyn FileOperations>) -> F + Send + Sync + 'static,
|
|
616
|
+
F: FileOperations + 'static,
|
|
617
|
+
{
|
|
618
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
619
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
620
|
+
Ok(())
|
|
591
621
|
}
|
|
622
|
+
}
|
|
592
623
|
|
|
593
|
-
|
|
594
|
-
|
|
624
|
+
impl<W, F> InodeOperations for WrapsFiles<W>
|
|
625
|
+
where
|
|
626
|
+
W: Fn(Box<dyn FileOperations>) -> F + Send + Sync + 'static,
|
|
627
|
+
F: FileOperations + 'static,
|
|
628
|
+
{
|
|
629
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
630
|
+
self.inner.getattr(dentry)
|
|
595
631
|
}
|
|
596
632
|
|
|
597
|
-
fn
|
|
598
|
-
self
|
|
633
|
+
fn setattr(
|
|
634
|
+
&self,
|
|
635
|
+
dentry: &Dentry,
|
|
636
|
+
attr: &kobako_runtime::filesystem::Iattr,
|
|
637
|
+
) -> Result<(), FilesystemError> {
|
|
638
|
+
self.inner.setattr(dentry, attr)
|
|
599
639
|
}
|
|
600
640
|
|
|
601
|
-
fn
|
|
602
|
-
self.
|
|
641
|
+
fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
642
|
+
Ok(self.wrap_found(self.inner.lookup(dentry)?))
|
|
603
643
|
}
|
|
604
644
|
|
|
605
|
-
fn
|
|
606
|
-
self
|
|
645
|
+
fn open(
|
|
646
|
+
&self,
|
|
647
|
+
dentry: &Dentry,
|
|
648
|
+
how: &OpenHow,
|
|
649
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
650
|
+
let opened = self.inner.open(dentry, how)?;
|
|
651
|
+
if dentry
|
|
652
|
+
.inode()
|
|
653
|
+
.is_none_or(|inode| inode.kind() == kobako_runtime::filesystem::FileType::Dir)
|
|
654
|
+
{
|
|
655
|
+
return Ok(opened);
|
|
656
|
+
}
|
|
657
|
+
Ok(Box::new((self.wrap)(opened)))
|
|
607
658
|
}
|
|
608
659
|
|
|
609
|
-
|
|
610
|
-
|
|
660
|
+
/// Opens what it made, as a Filesystem ending in `finish_open` does, so the
|
|
661
|
+
/// new file is wrapped too; what it only looked up is handed back wrapped.
|
|
662
|
+
fn atomic_open(&self, dentry: &Dentry, how: &OpenHow) -> Result<Finish, FilesystemError> {
|
|
663
|
+
let (made, created) = match self.inner.atomic_open(dentry, how)? {
|
|
664
|
+
Finish::Open { file, created } => (file, created),
|
|
665
|
+
Finish::NoOpen { created } => (self.inner.open(dentry, how)?, created),
|
|
666
|
+
Finish::Lookup(found) => return Ok(Finish::Lookup(self.wrap_found(found))),
|
|
667
|
+
Finish::Spelled { file, name, inode } => {
|
|
668
|
+
let Lookup::Found(inode) = self.wrap_found(Lookup::Found(inode)) else {
|
|
669
|
+
unreachable!("a found entry stays found once wrapped");
|
|
670
|
+
};
|
|
671
|
+
let file = Box::new((self.wrap)(file));
|
|
672
|
+
return Ok(Finish::Spelled { file, name, inode });
|
|
673
|
+
}
|
|
674
|
+
_ => unreachable!("Finish has no other end"),
|
|
675
|
+
};
|
|
676
|
+
Ok(Finish::Open {
|
|
677
|
+
file: Box::new((self.wrap)(made)),
|
|
678
|
+
created,
|
|
679
|
+
})
|
|
611
680
|
}
|
|
612
681
|
}
|
|
613
682
|
|
|
614
683
|
struct CountedFile {
|
|
615
|
-
file: Box<dyn
|
|
684
|
+
file: Box<dyn FileOperations>,
|
|
616
685
|
reads: Arc<AtomicUsize>,
|
|
617
686
|
}
|
|
618
687
|
|
|
619
|
-
impl
|
|
620
|
-
fn
|
|
688
|
+
impl FileOperations for CountedFile {
|
|
689
|
+
fn read_iter(&mut self, iocb: &mut Kiocb, buf: &mut [u8]) -> Result<usize, FilesystemError> {
|
|
621
690
|
self.reads.fetch_add(1, Ordering::Relaxed);
|
|
622
|
-
self.file.
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
fn write_at(&mut self, buf: &[u8], offset: u64) -> Result<usize, FilesystemError> {
|
|
626
|
-
self.file.write_at(buf, offset)
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
fn append(&mut self, buf: &[u8]) -> Result<(u64, usize), FilesystemError> {
|
|
630
|
-
self.file.append(buf)
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
fn metadata(&self) -> Result<Metadata, FilesystemError> {
|
|
634
|
-
self.file.metadata()
|
|
691
|
+
self.file.read_iter(iocb, buf)
|
|
635
692
|
}
|
|
636
693
|
|
|
637
|
-
fn
|
|
638
|
-
self.file.
|
|
694
|
+
fn write_iter(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
695
|
+
self.file.write_iter(iocb, buf)
|
|
639
696
|
}
|
|
640
697
|
}
|
|
641
698
|
|
|
@@ -667,56 +724,32 @@ fn a_read_of_a_length_asks_the_filesystem_once() {
|
|
|
667
724
|
|
|
668
725
|
/// An open file whose Filesystem refuses to release it, as a database
|
|
669
726
|
/// refusing the commit a close stands for.
|
|
670
|
-
struct RefusesClose(Box<dyn
|
|
727
|
+
struct RefusesClose(Box<dyn FileOperations>);
|
|
671
728
|
|
|
672
|
-
impl
|
|
673
|
-
fn
|
|
674
|
-
self.0.
|
|
729
|
+
impl FileOperations for RefusesClose {
|
|
730
|
+
fn read_iter(&mut self, iocb: &mut Kiocb, buf: &mut [u8]) -> Result<usize, FilesystemError> {
|
|
731
|
+
self.0.read_iter(iocb, buf)
|
|
675
732
|
}
|
|
676
733
|
|
|
677
|
-
fn
|
|
678
|
-
self.0.
|
|
734
|
+
fn write_iter(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
735
|
+
self.0.write_iter(iocb, buf)
|
|
679
736
|
}
|
|
680
737
|
|
|
681
|
-
fn
|
|
682
|
-
self.0.append(buf)
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
fn metadata(&self) -> Result<Metadata, FilesystemError> {
|
|
686
|
-
self.0.metadata()
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
fn set_len(&mut self, len: u64) -> Result<(), FilesystemError> {
|
|
690
|
-
self.0.set_len(len)
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
fn close(self: Box<Self>) -> Result<(), FilesystemError> {
|
|
738
|
+
fn flush(&mut self) -> Result<(), FilesystemError> {
|
|
694
739
|
Err(FilesystemError::PermissionDenied)
|
|
695
740
|
}
|
|
696
741
|
}
|
|
697
742
|
|
|
698
743
|
/// An open file whose every read fails in a way no error code names.
|
|
699
|
-
struct FailsReads(Box<dyn
|
|
744
|
+
struct FailsReads(Box<dyn FileOperations>);
|
|
700
745
|
|
|
701
|
-
impl
|
|
702
|
-
fn
|
|
746
|
+
impl FileOperations for FailsReads {
|
|
747
|
+
fn read_iter(&mut self, _iocb: &mut Kiocb, _buf: &mut [u8]) -> Result<usize, FilesystemError> {
|
|
703
748
|
Err(FilesystemError::Other("the disk went away".to_string()))
|
|
704
749
|
}
|
|
705
750
|
|
|
706
|
-
fn
|
|
707
|
-
self.0.
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
fn append(&mut self, buf: &[u8]) -> Result<(u64, usize), FilesystemError> {
|
|
711
|
-
self.0.append(buf)
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
fn metadata(&self) -> Result<Metadata, FilesystemError> {
|
|
715
|
-
self.0.metadata()
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
fn set_len(&mut self, len: u64) -> Result<(), FilesystemError> {
|
|
719
|
-
self.0.set_len(len)
|
|
751
|
+
fn write_iter(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
752
|
+
self.0.write_iter(iocb, buf)
|
|
720
753
|
}
|
|
721
754
|
}
|
|
722
755
|
|
|
@@ -758,27 +791,15 @@ fn a_close_the_filesystem_refuses_fails_the_scripts_close() {
|
|
|
758
791
|
|
|
759
792
|
/// An open file taking at most three bytes per write, as a Filesystem a
|
|
760
793
|
/// Host App writes in Ruby may.
|
|
761
|
-
struct WritesInParts(Box<dyn
|
|
762
|
-
|
|
763
|
-
impl File for WritesInParts {
|
|
764
|
-
fn read_at(&mut self, buf: &mut [u8], offset: u64) -> Result<usize, FilesystemError> {
|
|
765
|
-
self.0.read_at(buf, offset)
|
|
766
|
-
}
|
|
794
|
+
struct WritesInParts(Box<dyn FileOperations>);
|
|
767
795
|
|
|
768
|
-
|
|
769
|
-
|
|
796
|
+
impl FileOperations for WritesInParts {
|
|
797
|
+
fn read_iter(&mut self, iocb: &mut Kiocb, buf: &mut [u8]) -> Result<usize, FilesystemError> {
|
|
798
|
+
self.0.read_iter(iocb, buf)
|
|
770
799
|
}
|
|
771
800
|
|
|
772
|
-
fn
|
|
773
|
-
self.0.
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
fn metadata(&self) -> Result<Metadata, FilesystemError> {
|
|
777
|
-
self.0.metadata()
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
fn set_len(&mut self, len: u64) -> Result<(), FilesystemError> {
|
|
781
|
-
self.0.set_len(len)
|
|
801
|
+
fn write_iter(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
802
|
+
self.0.write_iter(iocb, &buf[..buf.len().min(3)])
|
|
782
803
|
}
|
|
783
804
|
}
|
|
784
805
|
|
|
@@ -793,7 +814,7 @@ fn an_errno_a_script_raises_reads_as_crubys() {
|
|
|
793
814
|
out << e.class.to_s << e.errno
|
|
794
815
|
print out.inspect
|
|
795
816
|
"#;
|
|
796
|
-
let Some(answer) = run(script, &[mount(
|
|
817
|
+
let Some(answer) = run(script, &[mount(note_tmpfs(), Access::Read)], Vec::new()) else {
|
|
797
818
|
return;
|
|
798
819
|
};
|
|
799
820
|
assert_eq!(
|
|
@@ -812,7 +833,7 @@ fn an_errno_a_script_raises_reads_as_crubys() {
|
|
|
812
833
|
fn a_system_call_error_keeps_a_number_no_class_names() {
|
|
813
834
|
let Some(answer) = run(
|
|
814
835
|
r#"e = SystemCallError.new(9999); print [e.class.to_s, e.message, e.errno].inspect"#,
|
|
815
|
-
&[mount(
|
|
836
|
+
&[mount(note_tmpfs(), Access::Read)],
|
|
816
837
|
Vec::new(),
|
|
817
838
|
) else {
|
|
818
839
|
return;
|
|
@@ -835,7 +856,7 @@ fn a_read_length_past_64_bits_raises_range_error() {
|
|
|
835
856
|
print e.class.to_s
|
|
836
857
|
end
|
|
837
858
|
"#;
|
|
838
|
-
let Some(answer) = run(script, &[mount(
|
|
859
|
+
let Some(answer) = run(script, &[mount(note_tmpfs(), Access::Read)], Vec::new()) else {
|
|
839
860
|
return;
|
|
840
861
|
};
|
|
841
862
|
assert_eq!(
|
|
@@ -859,7 +880,7 @@ fn a_system_call_error_number_past_the_errno_range_raises_range_error() {
|
|
|
859
880
|
end
|
|
860
881
|
print out.inspect
|
|
861
882
|
"#;
|
|
862
|
-
let Some(answer) = run(script, &[mount(
|
|
883
|
+
let Some(answer) = run(script, &[mount(note_tmpfs(), Access::Read)], Vec::new()) else {
|
|
863
884
|
return;
|
|
864
885
|
};
|
|
865
886
|
assert_eq!(
|
|
@@ -884,7 +905,7 @@ fn a_read_or_write_a_stream_was_not_opened_for_raises_ioerror() {
|
|
|
884
905
|
"#;
|
|
885
906
|
let Some(answer) = run(
|
|
886
907
|
script,
|
|
887
|
-
&[mount(
|
|
908
|
+
&[mount(note_tmpfs(), Access::ReadWrite)],
|
|
888
909
|
Vec::new(),
|
|
889
910
|
) else {
|
|
890
911
|
return;
|
|
@@ -905,7 +926,7 @@ fn a_read_or_write_a_stream_was_not_opened_for_raises_ioerror() {
|
|
|
905
926
|
fn a_path_that_is_no_valid_utf8_raises_eilseq() {
|
|
906
927
|
let Some(answer) = run(
|
|
907
928
|
r#"File.read("/data/\xff")"#,
|
|
908
|
-
&[mount(
|
|
929
|
+
&[mount(note_tmpfs(), Access::Read)],
|
|
909
930
|
Vec::new(),
|
|
910
931
|
) else {
|
|
911
932
|
return;
|
|
@@ -929,7 +950,7 @@ fn a_write_lands_whole_when_the_filesystem_takes_it_in_parts() {
|
|
|
929
950
|
return;
|
|
930
951
|
};
|
|
931
952
|
assert_eq!(
|
|
932
|
-
(answer, contents(filesystem
|
|
953
|
+
(answer, contents(&filesystem,"note.txt")),
|
|
933
954
|
(Ok("11".to_string()), b"hello world".to_vec()),
|
|
934
955
|
"IO#write to a file whose Filesystem takes three bytes per write through the bundled guest \
|
|
935
956
|
must answer every byte and land them all"
|
|
@@ -938,35 +959,45 @@ fn a_write_lands_whole_when_the_filesystem_takes_it_in_parts() {
|
|
|
938
959
|
|
|
939
960
|
/// An open file reporting a length it does not hold, so a script meets a
|
|
940
961
|
/// file of several GiB without the test allocating one.
|
|
941
|
-
struct ReportsLen(
|
|
962
|
+
struct ReportsLen(Arc<dyn InodeOperations>, u64);
|
|
942
963
|
|
|
943
|
-
impl
|
|
944
|
-
fn
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
fn write_at(&mut self, buf: &[u8], offset: u64) -> Result<usize, FilesystemError> {
|
|
949
|
-
self.0.write_at(buf, offset)
|
|
964
|
+
impl FileSystemType for ReportsLen {
|
|
965
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
966
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
967
|
+
Ok(())
|
|
950
968
|
}
|
|
969
|
+
}
|
|
951
970
|
|
|
952
|
-
|
|
953
|
-
|
|
971
|
+
impl InodeOperations for ReportsLen {
|
|
972
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
973
|
+
let mut stat = self.0.getattr(dentry)?;
|
|
974
|
+
if stat.file_type == FileType::RegFile {
|
|
975
|
+
stat.size = self.1;
|
|
976
|
+
}
|
|
977
|
+
Ok(stat)
|
|
954
978
|
}
|
|
955
979
|
|
|
956
|
-
fn
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
980
|
+
fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
981
|
+
Ok(match self.0.lookup(dentry)? {
|
|
982
|
+
Lookup::Found(inode) => {
|
|
983
|
+
Lookup::Found(inode.with_ops(Arc::new(Self(inode.ops().clone(), self.1))))
|
|
984
|
+
}
|
|
985
|
+
other => other,
|
|
986
|
+
})
|
|
960
987
|
}
|
|
961
988
|
|
|
962
|
-
fn
|
|
963
|
-
self
|
|
989
|
+
fn open(
|
|
990
|
+
&self,
|
|
991
|
+
dentry: &Dentry,
|
|
992
|
+
how: &OpenHow,
|
|
993
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
994
|
+
self.0.open(dentry, how)
|
|
964
995
|
}
|
|
965
996
|
}
|
|
966
997
|
|
|
967
998
|
fn three_gib_note() -> Grant {
|
|
968
|
-
let
|
|
969
|
-
mount(
|
|
999
|
+
let tmpfs: Tmpfs = [("note.txt", "hello")].into_iter().collect();
|
|
1000
|
+
mount(Arc::new(ReportsLen(Arc::new(tmpfs), 3 << 30)), Access::Read)
|
|
970
1001
|
}
|
|
971
1002
|
|
|
972
1003
|
// @behavior FL-020
|
|
@@ -1010,7 +1041,7 @@ fn a_position_past_2_gib_is_reported_whole() {
|
|
|
1010
1041
|
fn setting_a_files_position_moves_where_the_next_read_starts() {
|
|
1011
1042
|
let Some(answer) = run(
|
|
1012
1043
|
r#"File.open("/data/note.txt") { |f| f.pos = 2; print f.read }"#,
|
|
1013
|
-
&[mount(
|
|
1044
|
+
&[mount(note_tmpfs(), Access::Read)],
|
|
1014
1045
|
Vec::new(),
|
|
1015
1046
|
) else {
|
|
1016
1047
|
return;
|
|
@@ -1040,7 +1071,7 @@ fn a_seek_before_the_start_or_by_an_unknown_whence_raises_einval() {
|
|
|
1040
1071
|
end
|
|
1041
1072
|
end
|
|
1042
1073
|
nil"#,
|
|
1043
|
-
&[mount(
|
|
1074
|
+
&[mount(note_tmpfs(), Access::Read)],
|
|
1044
1075
|
Vec::new(),
|
|
1045
1076
|
) else {
|
|
1046
1077
|
return;
|