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
|
@@ -1,491 +1,453 @@
|
|
|
1
1
|
//! The 21 `fd_*` calls over kobako's descriptor table.
|
|
2
2
|
//!
|
|
3
|
-
//!
|
|
4
|
-
//!
|
|
5
|
-
//!
|
|
6
|
-
//!
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
use
|
|
10
|
-
|
|
11
|
-
use
|
|
12
|
-
|
|
13
|
-
use
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
use super::beneath::confirm;
|
|
3
|
+
//! The runtime's open file decides each answer, so this keeps only what
|
|
4
|
+
//! preview1 itself defines: which file a number stands for, its rights, a
|
|
5
|
+
//! preopen's name, and what wasi-testsuite asks of a directory's position.
|
|
6
|
+
//! It also keeps what a Linux syscall entry checks before the VFS: which
|
|
7
|
+
//! descriptor a call takes, the order of its errors, and the caller's buffer.
|
|
8
|
+
|
|
9
|
+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
10
|
+
|
|
11
|
+
use kobako_runtime::filesystem::{FileType, FilesystemError, Iattr, Kstat, SetTime};
|
|
12
|
+
use kobako_runtime::profile::Profile;
|
|
13
|
+
use kobako_runtime::vfs::{self, verify_dirent_name, DirContext, DirEmit, File, Whence};
|
|
14
|
+
use wiggle::{GuestMemory as WitxMemory, GuestPtr, GuestType};
|
|
15
|
+
|
|
17
16
|
use super::errno::{Answer, Errno};
|
|
18
17
|
use super::memory::GuestMemory;
|
|
19
|
-
use
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const FILETYPE_UNKNOWN: u8 = 0;
|
|
23
|
-
const FILETYPE_DIRECTORY: u8 = 3;
|
|
24
|
-
const FILETYPE_REGULAR_FILE: u8 = 4;
|
|
25
|
-
const FILETYPE_SYMBOLIC_LINK: u8 = 7;
|
|
26
|
-
|
|
27
|
-
const RIGHTS_FD_DATASYNC: u64 = 1 << 0;
|
|
28
|
-
const RIGHTS_FD_SEEK: u64 = 1 << 2;
|
|
29
|
-
const RIGHTS_FD_FDSTAT_SET_FLAGS: u64 = 1 << 3;
|
|
30
|
-
const RIGHTS_FD_SYNC: u64 = 1 << 4;
|
|
31
|
-
const RIGHTS_FD_TELL: u64 = 1 << 5;
|
|
32
|
-
const RIGHTS_FD_ADVISE: u64 = 1 << 7;
|
|
33
|
-
const RIGHTS_FD_ALLOCATE: u64 = 1 << 8;
|
|
34
|
-
const RIGHTS_FD_READDIR: u64 = 1 << 14;
|
|
35
|
-
const RIGHTS_PATH_FILESTAT_SET_SIZE: u64 = 1 << 19;
|
|
36
|
-
const RIGHTS_FD_FILESTAT_GET: u64 = 1 << 21;
|
|
37
|
-
const RIGHTS_FD_FILESTAT_SET_SIZE: u64 = 1 << 22;
|
|
38
|
-
const RIGHTS_FD_FILESTAT_SET_TIMES: u64 = 1 << 23;
|
|
39
|
-
const RIGHTS_POLL_FD_READWRITE: u64 = 1 << 27;
|
|
18
|
+
use super::path::retry_eagain;
|
|
19
|
+
use super::types::{self, Fdflags, Filetype, Fstflags, Rights};
|
|
20
|
+
use crate::descriptors::{Descriptor, Descriptors};
|
|
40
21
|
|
|
41
22
|
/// What a preopen allows itself: every path call, as wasmtime-wasi hands
|
|
42
23
|
/// most userlands.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/// both inherited whatever the Mount's access, since wasi-libc intersects
|
|
62
|
-
/// them before asking: a read-only Mount answers a write itself, as `ROFS`.
|
|
63
|
-
const PREOPEN_INHERITING: u64 = PREOPEN_RIGHTS
|
|
64
|
-
| RIGHTS_FD_DATASYNC
|
|
65
|
-
| RIGHTS_FD_READ
|
|
66
|
-
| RIGHTS_FD_SEEK
|
|
67
|
-
| RIGHTS_FD_FDSTAT_SET_FLAGS
|
|
68
|
-
| RIGHTS_FD_SYNC
|
|
69
|
-
| RIGHTS_FD_TELL
|
|
70
|
-
| RIGHTS_FD_WRITE
|
|
71
|
-
| RIGHTS_FD_ADVISE
|
|
72
|
-
| RIGHTS_FD_ALLOCATE
|
|
73
|
-
| RIGHTS_FD_FILESTAT_SET_SIZE
|
|
74
|
-
| RIGHTS_POLL_FD_READWRITE;
|
|
75
|
-
|
|
76
|
-
/// The most bytes one write moves into a capture pipe at a time, the
|
|
77
|
-
/// preview2 `blocking-write-and-flush` limit wasmtime-wasi writes under.
|
|
78
|
-
const WRITE_CHUNK: usize = 4096;
|
|
79
|
-
|
|
80
|
-
/// The size of one `dirent` header ahead of its name.
|
|
81
|
-
const DIRENT_SIZE: u32 = 24;
|
|
24
|
+
fn preopen_rights() -> Rights {
|
|
25
|
+
Rights::PATH_CREATE_DIRECTORY
|
|
26
|
+
| Rights::PATH_CREATE_FILE
|
|
27
|
+
| Rights::PATH_LINK_SOURCE
|
|
28
|
+
| Rights::PATH_LINK_TARGET
|
|
29
|
+
| Rights::PATH_OPEN
|
|
30
|
+
| Rights::FD_READDIR
|
|
31
|
+
| Rights::PATH_READLINK
|
|
32
|
+
| Rights::PATH_RENAME_SOURCE
|
|
33
|
+
| Rights::PATH_RENAME_TARGET
|
|
34
|
+
| Rights::PATH_FILESTAT_GET
|
|
35
|
+
| Rights::PATH_FILESTAT_SET_TIMES
|
|
36
|
+
| Rights::FD_FILESTAT_GET
|
|
37
|
+
| Rights::FD_FILESTAT_SET_TIMES
|
|
38
|
+
| Rights::PATH_SYMLINK
|
|
39
|
+
| Rights::PATH_REMOVE_DIRECTORY
|
|
40
|
+
| Rights::PATH_UNLINK_FILE
|
|
41
|
+
}
|
|
82
42
|
|
|
83
|
-
|
|
84
|
-
|
|
43
|
+
/// What a file opened beneath a directory may hold. Reading and writing are
|
|
44
|
+
/// both inherited whatever the Mount's access or the directory's own mode,
|
|
45
|
+
/// since wasi-libc intersects them before asking and Linux takes an open's
|
|
46
|
+
/// mode from its flags alone: a read-only Mount answers a write itself, as
|
|
47
|
+
/// `ROFS`.
|
|
48
|
+
fn directory_rights_inheriting() -> Rights {
|
|
49
|
+
preopen_rights()
|
|
50
|
+
| Rights::FD_DATASYNC
|
|
51
|
+
| Rights::FD_READ
|
|
52
|
+
| Rights::FD_SEEK
|
|
53
|
+
| Rights::FD_FDSTAT_SET_FLAGS
|
|
54
|
+
| Rights::FD_SYNC
|
|
55
|
+
| Rights::FD_TELL
|
|
56
|
+
| Rights::FD_WRITE
|
|
57
|
+
| Rights::FD_ADVISE
|
|
58
|
+
| Rights::FD_ALLOCATE
|
|
59
|
+
| Rights::FD_FILESTAT_SET_SIZE
|
|
60
|
+
| Rights::POLL_FD_READWRITE
|
|
85
61
|
}
|
|
86
62
|
|
|
87
|
-
|
|
88
|
-
|
|
63
|
+
/// What `fd` stands for, a file that stands only for its path included, as
|
|
64
|
+
/// `fdget_raw` takes one.
|
|
65
|
+
fn descriptor(table: &Descriptors, fd: u32) -> Answer<&Descriptor> {
|
|
66
|
+
table.get(fd).ok_or_else(|| Errno::Badf.into())
|
|
89
67
|
}
|
|
90
68
|
|
|
91
|
-
/// The open file `fd` stands for
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
69
|
+
/// The open file `fd` stands for, refusing one that stands only for its path,
|
|
70
|
+
/// as `fdget` does.
|
|
71
|
+
pub(crate) fn file(table: &Descriptors, fd: u32) -> Answer<&File> {
|
|
72
|
+
match table.get(fd) {
|
|
73
|
+
Some(descriptor) if !descriptor.file.is_path() => Ok(&descriptor.file),
|
|
74
|
+
_ => Err(Errno::Badf.into()),
|
|
98
75
|
}
|
|
99
76
|
}
|
|
100
77
|
|
|
101
|
-
///
|
|
102
|
-
///
|
|
103
|
-
fn
|
|
104
|
-
match
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Descriptor::Directory(_) => Err(Errno::Badf.into()),
|
|
78
|
+
/// The open file `fd` stands for, to change, refusing one that stands only for
|
|
79
|
+
/// its path, as `fdget` does.
|
|
80
|
+
fn file_mut(table: &mut Descriptors, fd: u32) -> Answer<&mut File> {
|
|
81
|
+
match table.get_mut(fd) {
|
|
82
|
+
Some(descriptor) if !descriptor.file.is_path() => Ok(&mut descriptor.file),
|
|
83
|
+
_ => Err(Errno::Badf.into()),
|
|
108
84
|
}
|
|
109
85
|
}
|
|
110
86
|
|
|
111
|
-
///
|
|
112
|
-
///
|
|
113
|
-
fn
|
|
114
|
-
|
|
115
|
-
|
|
87
|
+
/// The open file `fd` stands for, in a call about its position. wasi-testsuite
|
|
88
|
+
/// has a directory refuse one, where Linux would seek it.
|
|
89
|
+
fn seekable_file(table: &mut Descriptors, fd: u32) -> Answer<&mut File> {
|
|
90
|
+
match file_mut(table, fd)? {
|
|
91
|
+
file if file.is_directory() => Err(Errno::Badf.into()),
|
|
92
|
+
file => Ok(file),
|
|
116
93
|
}
|
|
117
|
-
Ok(offset)
|
|
118
94
|
}
|
|
119
95
|
|
|
120
|
-
/// A
|
|
121
|
-
///
|
|
122
|
-
fn
|
|
123
|
-
|
|
124
|
-
|
|
96
|
+
/// A call any file or directory answers, which preview1 refuses for
|
|
97
|
+
/// stdio.
|
|
98
|
+
fn refuse_stream(table: &Descriptors, fd: u32) -> Answer {
|
|
99
|
+
match descriptor(table, fd)?.file.is_stream() {
|
|
100
|
+
true => Err(Errno::Badf.into()),
|
|
101
|
+
false => Ok(()),
|
|
125
102
|
}
|
|
126
|
-
Ok(offset)
|
|
127
103
|
}
|
|
128
104
|
|
|
129
|
-
///
|
|
130
|
-
///
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
105
|
+
/// `file_type` as a preview1 `filetype`, which has no code for a FIFO and two
|
|
106
|
+
/// for the one kind `st_mode` names a socket.
|
|
107
|
+
fn to_filetype(file_type: FileType) -> Filetype {
|
|
108
|
+
match file_type {
|
|
109
|
+
FileType::Dir => Filetype::Directory,
|
|
110
|
+
FileType::Symlink => Filetype::SymbolicLink,
|
|
111
|
+
FileType::RegFile => Filetype::RegularFile,
|
|
112
|
+
FileType::Sock => Filetype::SocketStream,
|
|
113
|
+
FileType::Blkdev => Filetype::BlockDevice,
|
|
114
|
+
FileType::Chrdev => Filetype::CharacterDevice,
|
|
115
|
+
_ => Filetype::Unknown,
|
|
136
116
|
}
|
|
137
|
-
Ok(&buf[..buf.len().min(usize::try_from(room).unwrap_or(usize::MAX))])
|
|
138
117
|
}
|
|
139
118
|
|
|
140
|
-
/// A
|
|
141
|
-
///
|
|
142
|
-
fn
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
119
|
+
/// A `filestat` for what a Filesystem reports a path names, as the guest
|
|
120
|
+
/// holding `table` sees it, at time zero where it keeps no time.
|
|
121
|
+
pub(crate) fn to_filestat(table: &Descriptors, stat: Kstat) -> types::Filestat {
|
|
122
|
+
let stat = table.profile().guest_kstat(stat);
|
|
123
|
+
types::Filestat {
|
|
124
|
+
dev: stat.dev,
|
|
125
|
+
ino: stat.ino,
|
|
126
|
+
filetype: to_filetype(stat.file_type),
|
|
127
|
+
nlink: stat.nlink,
|
|
128
|
+
size: stat.size,
|
|
129
|
+
atim: to_timestamp(stat.atime),
|
|
130
|
+
mtim: to_timestamp(stat.mtime),
|
|
131
|
+
ctim: to_timestamp(stat.ctime),
|
|
146
132
|
}
|
|
147
133
|
}
|
|
148
134
|
|
|
149
|
-
///
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
135
|
+
/// `time` as a preview1 `timestamp`, nanoseconds since the Unix epoch;
|
|
136
|
+
/// zero for none, and the largest timestamp for a time past it.
|
|
137
|
+
fn to_timestamp(time: Option<SystemTime>) -> u64 {
|
|
138
|
+
time.and_then(|time| time.duration_since(UNIX_EPOCH).ok())
|
|
139
|
+
.map_or(0, |since| {
|
|
140
|
+
u64::try_from(since.as_nanos()).unwrap_or(u64::MAX)
|
|
141
|
+
})
|
|
156
142
|
}
|
|
157
143
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
144
|
+
/// Set the times `flags` ask through `call`, answering before it looks at the
|
|
145
|
+
/// name or the descriptor where they ask none, as Linux's `utimensat` does
|
|
146
|
+
/// for both. Setting a timestamp and setting it to now are exclusive: asking
|
|
147
|
+
/// both is `INVAL`.
|
|
148
|
+
pub(crate) fn utimensat(
|
|
149
|
+
atim: types::Timestamp,
|
|
150
|
+
mtim: types::Timestamp,
|
|
151
|
+
flags: Fstflags,
|
|
152
|
+
call: impl FnOnce(&Iattr) -> Answer,
|
|
153
|
+
) -> Answer {
|
|
154
|
+
match to_iattr(atim, mtim, flags)? {
|
|
155
|
+
Some(times) => call(×),
|
|
156
|
+
None => Ok(()),
|
|
164
157
|
}
|
|
165
158
|
}
|
|
166
159
|
|
|
167
|
-
///
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
160
|
+
/// The times `flags` ask to set, `None` where they ask none.
|
|
161
|
+
fn to_iattr(
|
|
162
|
+
atim: types::Timestamp,
|
|
163
|
+
mtim: types::Timestamp,
|
|
164
|
+
flags: Fstflags,
|
|
165
|
+
) -> Answer<Option<Iattr<'static>>> {
|
|
166
|
+
let time = |at, set, now| match (flags.contains(set), flags.contains(now)) {
|
|
167
|
+
(true, true) => Err(Errno::Inval),
|
|
168
|
+
(true, false) => Ok(Some(SetTime::At(UNIX_EPOCH + Duration::from_nanos(at)))),
|
|
169
|
+
(false, true) => Ok(Some(SetTime::Now)),
|
|
170
|
+
(false, false) => Ok(None),
|
|
171
|
+
};
|
|
172
|
+
let atime = time(atim, Fstflags::ATIM, Fstflags::ATIM_NOW)?;
|
|
173
|
+
let mtime = time(mtim, Fstflags::MTIM, Fstflags::MTIM_NOW)?;
|
|
174
|
+
Ok((atime.is_some() || mtime.is_some()).then(|| Iattr::times(atime, mtime)))
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
pub(crate) fn advise(
|
|
176
|
-
table: &mut Descriptors,
|
|
177
|
-
fd: u32,
|
|
178
|
-
offset: u64,
|
|
179
|
-
len: u64,
|
|
180
|
-
advice: u32,
|
|
181
|
-
) -> Answer {
|
|
182
|
-
args::variant(advice, args::ADVICE_MAX)?;
|
|
183
|
-
file(table, fd)?;
|
|
184
|
-
refuse_negative(offset)?;
|
|
185
|
-
refuse_negative(len).map(drop)
|
|
177
|
+
pub(crate) fn advise(table: &mut Descriptors, fd: u32, offset: u64, len: u64) -> Answer {
|
|
178
|
+
Ok(file_mut(table, fd)?.fadvise(offset, len)?)
|
|
186
179
|
}
|
|
187
180
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
pub(crate) fn allocate(table: &mut Descriptors, fd: u32) -> Answer {
|
|
191
|
-
file(table, fd)?;
|
|
192
|
-
Err(Errno::Notsup.into())
|
|
181
|
+
pub(crate) fn allocate(table: &mut Descriptors, fd: u32, offset: u64, len: u64) -> Answer {
|
|
182
|
+
Ok(file_mut(table, fd)?.fallocate(offset, len)?)
|
|
193
183
|
}
|
|
194
184
|
|
|
195
185
|
pub(crate) fn close(table: &mut Descriptors, fd: u32) -> Answer {
|
|
196
|
-
|
|
197
|
-
Descriptor::File(open) => Ok(open.file.close()?),
|
|
198
|
-
_ => Ok(()),
|
|
199
|
-
}
|
|
186
|
+
Ok(table.remove(fd).ok_or(Errno::Badf)?.file.close()?)
|
|
200
187
|
}
|
|
201
188
|
|
|
202
|
-
pub(crate) fn sync(table: &mut Descriptors, fd: u32) -> Answer {
|
|
203
|
-
Ok(
|
|
189
|
+
pub(crate) fn sync(table: &mut Descriptors, fd: u32, datasync: bool) -> Answer {
|
|
190
|
+
Ok(file_mut(table, fd)?.fsync(datasync)?)
|
|
204
191
|
}
|
|
205
192
|
|
|
206
|
-
pub(crate) fn fdstat_get(table: &Descriptors,
|
|
207
|
-
let
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
193
|
+
pub(crate) fn fdstat_get(table: &Descriptors, fd: u32) -> Answer<types::Fdstat> {
|
|
194
|
+
let descriptor = descriptor(table, fd)?;
|
|
195
|
+
let file = &descriptor.file;
|
|
196
|
+
let mut fs_flags = Fdflags::empty();
|
|
197
|
+
fs_flags.set(Fdflags::APPEND, file.append());
|
|
198
|
+
fs_flags.set(Fdflags::NONBLOCK, file.nonblock());
|
|
199
|
+
fs_flags.set(Fdflags::DSYNC, file.dsync());
|
|
200
|
+
fs_flags.set(Fdflags::SYNC, file.sync());
|
|
201
|
+
let (fs_rights_base, fs_rights_inheriting) = if file.is_stream() {
|
|
202
|
+
let mut rights = Rights::empty();
|
|
203
|
+
rights.set(Rights::FD_READ, file.readable());
|
|
204
|
+
rights.set(Rights::FD_WRITE, file.writable());
|
|
205
|
+
(rights, rights)
|
|
206
|
+
} else if descriptor.preopen_path.is_some() {
|
|
207
|
+
(preopen_rights(), directory_rights_inheriting())
|
|
208
|
+
} else if file.is_directory() {
|
|
209
|
+
let mut rights = Rights::all()
|
|
210
|
+
- Rights::FD_WRITE
|
|
211
|
+
- Rights::FD_SEEK
|
|
212
|
+
- Rights::FD_FILESTAT_SET_SIZE
|
|
213
|
+
- Rights::PATH_FILESTAT_SET_SIZE;
|
|
214
|
+
if !file.readable() {
|
|
215
|
+
rights -= Rights::FD_READ | Rights::FD_READDIR;
|
|
212
216
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
& !RIGHTS_PATH_FILESTAT_SET_SIZE;
|
|
219
|
-
(FILETYPE_DIRECTORY, 0, rights, rights)
|
|
217
|
+
(rights, directory_rights_inheriting())
|
|
218
|
+
} else {
|
|
219
|
+
let mut rights = Rights::all();
|
|
220
|
+
if !file.readable() {
|
|
221
|
+
rights -= Rights::FD_READ | Rights::FD_READDIR;
|
|
220
222
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if !file.readable {
|
|
224
|
-
rights &= !RIGHTS_FD_READ & !RIGHTS_FD_READDIR;
|
|
225
|
-
}
|
|
226
|
-
if !file.writable {
|
|
227
|
-
rights &= !RIGHTS_FD_WRITE;
|
|
228
|
-
}
|
|
229
|
-
let flags = if file.append {
|
|
230
|
-
FDFLAGS_APPEND as u16
|
|
231
|
-
} else {
|
|
232
|
-
0
|
|
233
|
-
};
|
|
234
|
-
(FILETYPE_REGULAR_FILE, flags, rights, rights)
|
|
223
|
+
if !file.writable() {
|
|
224
|
+
rights -= Rights::FD_WRITE;
|
|
235
225
|
}
|
|
226
|
+
(rights, rights)
|
|
236
227
|
};
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if flags & FDFLAGS_SYNCHRONIZED != 0 {
|
|
251
|
-
return Err(Errno::Inval.into());
|
|
252
|
-
}
|
|
253
|
-
if flags & FDFLAGS_NONBLOCK != 0 {
|
|
254
|
-
return Err(Errno::Notsup.into());
|
|
255
|
-
}
|
|
256
|
-
file.append = flags & FDFLAGS_APPEND != 0;
|
|
228
|
+
Ok(types::Fdstat {
|
|
229
|
+
fs_filetype: to_filetype(file.kind()),
|
|
230
|
+
fs_flags,
|
|
231
|
+
fs_rights_base,
|
|
232
|
+
fs_rights_inheriting,
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
pub(crate) fn fdstat_set_flags(table: &mut Descriptors, fd: u32, flags: Fdflags) -> Answer {
|
|
237
|
+
file_mut(table, fd)?.setfl(
|
|
238
|
+
flags.contains(Fdflags::APPEND),
|
|
239
|
+
flags.contains(Fdflags::NONBLOCK),
|
|
240
|
+
);
|
|
257
241
|
Ok(())
|
|
258
242
|
}
|
|
259
243
|
|
|
260
244
|
/// Rights only narrow at open, so a later change is unsupported.
|
|
261
|
-
pub(crate) fn fdstat_set_rights(
|
|
262
|
-
table
|
|
263
|
-
fd: u32,
|
|
264
|
-
base: u64,
|
|
265
|
-
inheriting: u64,
|
|
266
|
-
) -> Answer {
|
|
267
|
-
args::flags(base, args::RIGHTS)?;
|
|
268
|
-
args::flags(inheriting, args::RIGHTS)?;
|
|
269
|
-
file_or_directory(table, fd)?;
|
|
245
|
+
pub(crate) fn fdstat_set_rights(table: &Descriptors, fd: u32) -> Answer {
|
|
246
|
+
refuse_stream(table, fd)?;
|
|
270
247
|
Err(Errno::Notsup.into())
|
|
271
248
|
}
|
|
272
249
|
|
|
273
|
-
pub(crate) fn filestat_get(
|
|
274
|
-
table
|
|
275
|
-
mem: &mut GuestMemory,
|
|
276
|
-
fd: u32,
|
|
277
|
-
out: u32,
|
|
278
|
-
) -> Answer {
|
|
279
|
-
match descriptor(table, fd)? {
|
|
280
|
-
Descriptor::Stdin(_) | Descriptor::Channel(_) => {
|
|
281
|
-
write_filestat(mem, out, FILETYPE_UNKNOWN, 0, 0)
|
|
282
|
-
}
|
|
283
|
-
Descriptor::Directory(dir) => {
|
|
284
|
-
confirm(dir)?;
|
|
285
|
-
write_metadata(mem, out, dir.mount.filesystem().metadata(&dir.path)?)
|
|
286
|
-
}
|
|
287
|
-
Descriptor::File(file) => write_metadata(mem, out, file.file.metadata()?),
|
|
288
|
-
}
|
|
250
|
+
pub(crate) fn filestat_get(table: &Descriptors, fd: u32) -> Answer<types::Filestat> {
|
|
251
|
+
Ok(to_filestat(table, descriptor(table, fd)?.file.getattr()?))
|
|
289
252
|
}
|
|
290
253
|
|
|
291
|
-
/// A
|
|
292
|
-
///
|
|
293
|
-
/// `ftruncate` does.
|
|
254
|
+
/// A size Linux's signed `loff_t` reads as negative answers before the
|
|
255
|
+
/// descriptor is looked for, as `ksys_ftruncate` checks it before `fdget`.
|
|
294
256
|
pub(crate) fn filestat_set_size(table: &mut Descriptors, fd: u32, size: u64) -> Answer {
|
|
295
|
-
|
|
296
|
-
if !file.mount.allows_writes() {
|
|
297
|
-
return Err(Errno::Perm.into());
|
|
298
|
-
}
|
|
299
|
-
if !file.writable {
|
|
257
|
+
if i64::try_from(size).is_err() {
|
|
300
258
|
return Err(Errno::Inval.into());
|
|
301
259
|
}
|
|
302
|
-
Ok(
|
|
260
|
+
Ok(file_mut(table, fd)?.truncate(size)?)
|
|
303
261
|
}
|
|
304
262
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
263
|
+
pub(crate) fn filestat_set_times(table: &Descriptors, fd: u32, times: &Iattr) -> Answer {
|
|
264
|
+
let file = file(table, fd)?;
|
|
265
|
+
Ok(retry_eagain(|| {
|
|
266
|
+
table.walk_guard().check(file.path())?;
|
|
267
|
+
vfs::do_utimes_fd(table.cred(), file.path(), times)
|
|
268
|
+
})?)
|
|
311
269
|
}
|
|
312
270
|
|
|
313
271
|
pub(crate) fn pread(
|
|
314
272
|
table: &mut Descriptors,
|
|
315
|
-
mem: &mut GuestMemory,
|
|
273
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
316
274
|
fd: u32,
|
|
317
|
-
iovs:
|
|
275
|
+
iovs: types::IovecArray,
|
|
318
276
|
offset: u64,
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
let
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
Descriptor::Channel(_) | Descriptor::Directory(_) => return Err(Errno::Badf.into()),
|
|
325
|
-
Descriptor::File(file) => file,
|
|
326
|
-
};
|
|
327
|
-
if !file.readable {
|
|
328
|
-
return Err(Errno::Badf.into());
|
|
329
|
-
}
|
|
330
|
-
let offset = refuse_reading_past_largest_offset(refuse_negative(offset)?, len)?;
|
|
331
|
-
let read = file.file.read_at(mem.bytes_mut(ptr, len)?, offset)?;
|
|
332
|
-
mem.write_u32(nread, read as u32)
|
|
277
|
+
) -> Answer<types::Size> {
|
|
278
|
+
let buf = mem.first_non_empty_iovec(iovs)?;
|
|
279
|
+
let file = file_mut(table, fd)?;
|
|
280
|
+
file.may_pread(offset)?;
|
|
281
|
+
Ok(file.pread(mem.bytes_mut(buf)?, offset)? as u32)
|
|
333
282
|
}
|
|
334
283
|
|
|
335
|
-
pub(crate) fn prestat_get(table: &Descriptors,
|
|
336
|
-
match descriptor(table, fd)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
_ => Err(Errno::Badf.into()),
|
|
284
|
+
pub(crate) fn prestat_get(table: &Descriptors, fd: u32) -> Answer<types::Prestat> {
|
|
285
|
+
match &descriptor(table, fd)?.preopen_path {
|
|
286
|
+
Some(at) => Ok(types::Prestat::Dir(types::PrestatDir {
|
|
287
|
+
pr_name_len: at.len() as u32,
|
|
288
|
+
})),
|
|
289
|
+
None => Err(Errno::Badf.into()),
|
|
343
290
|
}
|
|
344
291
|
}
|
|
345
292
|
|
|
346
293
|
pub(crate) fn prestat_dir_name(
|
|
347
294
|
table: &Descriptors,
|
|
348
|
-
mem: &mut GuestMemory,
|
|
295
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
349
296
|
fd: u32,
|
|
350
|
-
path:
|
|
297
|
+
path: GuestPtr<u8>,
|
|
351
298
|
path_len: u32,
|
|
352
299
|
) -> Answer {
|
|
353
|
-
match descriptor(table, fd)
|
|
354
|
-
|
|
355
|
-
let name =
|
|
300
|
+
match &descriptor(table, fd)?.preopen_path {
|
|
301
|
+
Some(at) => {
|
|
302
|
+
let name = at.as_bytes();
|
|
356
303
|
if name.len() > path_len as usize {
|
|
357
304
|
return Err(Errno::Nametoolong.into());
|
|
358
305
|
}
|
|
359
|
-
mem.
|
|
306
|
+
mem.write_bytes(path, name)
|
|
360
307
|
}
|
|
361
|
-
|
|
308
|
+
None => Err(Errno::Notdir.into()),
|
|
362
309
|
}
|
|
363
310
|
}
|
|
364
311
|
|
|
365
312
|
pub(crate) fn pwrite(
|
|
366
313
|
table: &mut Descriptors,
|
|
367
|
-
mem: &mut GuestMemory,
|
|
314
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
368
315
|
fd: u32,
|
|
369
|
-
ciovs:
|
|
316
|
+
ciovs: types::CiovecArray,
|
|
370
317
|
offset: u64,
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
let
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
Descriptor::Stdin(_) | Descriptor::Directory(_) => return Err(Errno::Badf.into()),
|
|
377
|
-
Descriptor::File(file) => file,
|
|
378
|
-
};
|
|
379
|
-
// The buffer is bounded before the access is checked, as wasmtime-wasi
|
|
380
|
-
// bounds it before the host refuses the write.
|
|
381
|
-
let buf = mem.bytes(ptr, len)?;
|
|
382
|
-
if !file.writable {
|
|
383
|
-
return Err(Errno::Badf.into());
|
|
384
|
-
}
|
|
385
|
-
let offset = refuse_negative(offset)?;
|
|
386
|
-
// Linux's `pwrite` ignores the offset of a file opened to append.
|
|
387
|
-
let written = if file.append {
|
|
388
|
-
file.file.append(buf)?.1
|
|
389
|
-
} else {
|
|
390
|
-
file.file
|
|
391
|
-
.write_at(trim_to_largest_offset(buf, offset)?, offset)?
|
|
392
|
-
};
|
|
393
|
-
mem.write_u32(nwritten, written as u32)
|
|
318
|
+
) -> Answer<types::Size> {
|
|
319
|
+
let buf = mem.first_non_empty_ciovec(ciovs)?;
|
|
320
|
+
let file = file_mut(table, fd)?;
|
|
321
|
+
file.may_pwrite(offset)?;
|
|
322
|
+
Ok(file.pwrite(mem.bytes(buf)?, offset)? as u32)
|
|
394
323
|
}
|
|
395
324
|
|
|
396
325
|
pub(crate) fn read(
|
|
397
326
|
table: &mut Descriptors,
|
|
398
|
-
mem: &mut GuestMemory,
|
|
327
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
399
328
|
fd: u32,
|
|
400
|
-
iovs:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
let
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
329
|
+
iovs: types::IovecArray,
|
|
330
|
+
) -> Answer<types::Size> {
|
|
331
|
+
let buf = mem.first_non_empty_iovec(iovs)?;
|
|
332
|
+
let file = file_mut(table, fd)?;
|
|
333
|
+
file.may_read()?;
|
|
334
|
+
// Bounded before the file is asked, as a trap takes no bytes from a pipe.
|
|
335
|
+
Ok(file.read(mem.bytes_mut(buf)?)? as u32)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// The `dirent` header ahead of one entry's name, its `d_next` left for the
|
|
339
|
+
/// listing to fill in once it knows it.
|
|
340
|
+
fn to_dirent_header(emit: &DirEmit<'_>, d_ino: u64) -> Answer<Vec<u8>> {
|
|
341
|
+
let mut header = vec![0; types::Dirent::guest_size() as usize];
|
|
342
|
+
WitxMemory::Unshared(&mut header).write(
|
|
343
|
+
GuestPtr::new(0),
|
|
344
|
+
types::Dirent {
|
|
345
|
+
d_next: 0,
|
|
346
|
+
d_ino,
|
|
347
|
+
d_namlen: emit.name.len() as u32,
|
|
348
|
+
d_type: to_filetype(emit.file_type),
|
|
349
|
+
},
|
|
350
|
+
)?;
|
|
351
|
+
Ok(header)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/// One entry as a preview1 `dirent` at `used` bytes into the buffer, cut off
|
|
355
|
+
/// where it ends, answering where its header stands when it fit whole.
|
|
356
|
+
fn write_dirent(
|
|
357
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
358
|
+
buf: (GuestPtr<u8>, u32),
|
|
359
|
+
used: &mut u32,
|
|
360
|
+
profile: Profile,
|
|
361
|
+
emit: &DirEmit<'_>,
|
|
362
|
+
) -> Answer<Option<u32>> {
|
|
363
|
+
let (start, capacity) = buf;
|
|
364
|
+
let at = *used;
|
|
365
|
+
let header = to_dirent_header(emit, profile.guest_ino(emit.identity))?;
|
|
366
|
+
let mut whole = true;
|
|
367
|
+
for part in [&header[..], emit.name.as_bytes()] {
|
|
368
|
+
let room = (capacity - *used) as usize;
|
|
369
|
+
let take = part.len().min(room);
|
|
370
|
+
if take > 0 {
|
|
371
|
+
mem.write_bytes(start.add(*used)?, &part[..take])?;
|
|
423
372
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
373
|
+
*used += take as u32;
|
|
374
|
+
whole &= take == part.len();
|
|
375
|
+
}
|
|
376
|
+
Ok(whole.then_some(at))
|
|
427
377
|
}
|
|
428
378
|
|
|
429
|
-
///
|
|
430
|
-
///
|
|
431
|
-
///
|
|
432
|
-
|
|
433
|
-
|
|
379
|
+
/// Where a listing goes on from past the entry whose header stands at `at`,
|
|
380
|
+
/// as `filldir64` writes `prev->d_off` once the next entry or the listing's end
|
|
381
|
+
/// says, `d_next` leading each header.
|
|
382
|
+
fn write_d_next(
|
|
383
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
384
|
+
start: GuestPtr<u8>,
|
|
385
|
+
at: u32,
|
|
386
|
+
d_next: u64,
|
|
387
|
+
) -> Answer {
|
|
388
|
+
mem.write_bytes(start.add(at)?, &d_next.to_le_bytes())?;
|
|
389
|
+
Ok(())
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/// A directory's entries from the `cookie`th on, cut off where the buffer
|
|
393
|
+
/// ends.
|
|
434
394
|
pub(crate) fn readdir(
|
|
435
395
|
table: &mut Descriptors,
|
|
436
|
-
mem: &mut GuestMemory,
|
|
396
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
437
397
|
fd: u32,
|
|
438
|
-
|
|
398
|
+
start: GuestPtr<u8>,
|
|
399
|
+
capacity: u32,
|
|
439
400
|
cookie: u64,
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
let dir =
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
_ => dir.mount.filesystem().read_dir(&dir.path)?,
|
|
451
|
-
};
|
|
452
|
-
let entries = [(".", FILETYPE_DIRECTORY), ("..", FILETYPE_DIRECTORY)]
|
|
453
|
-
.into_iter()
|
|
454
|
-
.chain(
|
|
455
|
-
listing
|
|
456
|
-
.iter()
|
|
457
|
-
.map(|entry| (entry.name.as_str(), to_filetype(entry.file_type))),
|
|
458
|
-
);
|
|
459
|
-
let (start, capacity) = buf;
|
|
401
|
+
) -> Answer<types::Size> {
|
|
402
|
+
let profile = table.profile();
|
|
403
|
+
let dir = file_mut(table, fd)?;
|
|
404
|
+
// preview1 names where a listing goes on by its cookie, as wasi-libc's
|
|
405
|
+
// `seekdir` moves a directory's position before `getdents`; a listing going
|
|
406
|
+
// on from where the last one stopped needs no seek.
|
|
407
|
+
if cookie != dir.pos() {
|
|
408
|
+
let cookie = i64::try_from(cookie).map_err(|_| FilesystemError::InvalidInput)?;
|
|
409
|
+
dir.llseek(cookie, Whence::Set)?;
|
|
410
|
+
}
|
|
460
411
|
let mut used: u32 = 0;
|
|
461
|
-
let mut
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
412
|
+
let mut failed = None;
|
|
413
|
+
let mut last_whole = None;
|
|
414
|
+
let mut actor = |emit: &DirEmit<'_>| {
|
|
415
|
+
let written = verify_dirent_name(emit.name)
|
|
416
|
+
.map_err(Into::into)
|
|
417
|
+
.and_then(|()| match last_whole.take() {
|
|
418
|
+
Some(at) => write_d_next(mem, start, at, emit.offset),
|
|
419
|
+
None => Ok(()),
|
|
420
|
+
})
|
|
421
|
+
.and_then(|()| write_dirent(mem, (start, capacity), &mut used, profile, emit));
|
|
422
|
+
match written {
|
|
423
|
+
Ok(whole) => {
|
|
424
|
+
last_whole = whole;
|
|
425
|
+
whole.is_some()
|
|
472
426
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
break 'entries;
|
|
427
|
+
Err(error) => {
|
|
428
|
+
failed = Some(error);
|
|
429
|
+
false
|
|
477
430
|
}
|
|
478
431
|
}
|
|
432
|
+
};
|
|
433
|
+
let listed = dir.iterate_dir(&mut DirContext::new(0, &mut actor));
|
|
434
|
+
// The last entry written whole goes on from where the listing stopped,
|
|
435
|
+
// as `getdents64` sets the last `d_off` from `f_pos`.
|
|
436
|
+
if let Some(at) = last_whole {
|
|
437
|
+
write_d_next(mem, start, at, dir.pos())?;
|
|
479
438
|
}
|
|
480
|
-
|
|
481
|
-
|
|
439
|
+
// What was listed answers before what stopped the listing, which the next
|
|
440
|
+
// call meets first, as Linux's `getdents64` answers the bytes it wrote over
|
|
441
|
+
// `iterate_dir`'s error and `buf.error`.
|
|
442
|
+
match listed.err().map(Into::into).or(failed) {
|
|
443
|
+
Some(error) if used == 0 => Err(error),
|
|
444
|
+
_ => Ok(used),
|
|
482
445
|
}
|
|
483
|
-
mem.write_u32(bufused, used)
|
|
484
446
|
}
|
|
485
447
|
|
|
486
|
-
/// preview1 renumbering
|
|
487
|
-
///
|
|
488
|
-
///
|
|
448
|
+
/// preview1 renumbering moves `from` onto `to`, both already open, then
|
|
449
|
+
/// closes what stood at `to`, ignoring how its close answers, as Linux's
|
|
450
|
+
/// `do_dup2` installs the file before `filp_close` of the one it replaced.
|
|
489
451
|
pub(crate) fn renumber(table: &mut Descriptors, from: u32, to: u32) -> Answer {
|
|
490
452
|
if table.get(from).is_none() || table.get(to).is_none() {
|
|
491
453
|
return Err(Errno::Badf.into());
|
|
@@ -493,99 +455,41 @@ pub(crate) fn renumber(table: &mut Descriptors, from: u32, to: u32) -> Answer {
|
|
|
493
455
|
if from == to {
|
|
494
456
|
return Ok(());
|
|
495
457
|
}
|
|
496
|
-
|
|
497
|
-
let
|
|
458
|
+
let moved = table.remove(from).expect("found open above");
|
|
459
|
+
let replaced = table.remove(to).expect("found open above");
|
|
498
460
|
table.place(to, moved);
|
|
461
|
+
let _ = replaced.file.close();
|
|
499
462
|
Ok(())
|
|
500
463
|
}
|
|
501
464
|
|
|
502
465
|
pub(crate) fn seek(
|
|
503
466
|
table: &mut Descriptors,
|
|
504
|
-
mem: &mut GuestMemory,
|
|
505
467
|
fd: u32,
|
|
506
468
|
offset: i64,
|
|
507
|
-
whence:
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
0 => 0,
|
|
514
|
-
1 => file.position,
|
|
515
|
-
_ => file.file.metadata()?.len,
|
|
469
|
+
whence: types::Whence,
|
|
470
|
+
) -> Answer<u64> {
|
|
471
|
+
let whence = match whence {
|
|
472
|
+
types::Whence::Set => Whence::Set,
|
|
473
|
+
types::Whence::Cur => Whence::Cur,
|
|
474
|
+
types::Whence::End => Whence::End,
|
|
516
475
|
};
|
|
517
|
-
|
|
518
|
-
// is `OVERFLOW` rather than a position a write could wrap around.
|
|
519
|
-
let position = base.checked_add_signed(offset).ok_or(Errno::Inval)?;
|
|
520
|
-
if position > i64::MAX as u64 {
|
|
521
|
-
return Err(Errno::Overflow.into());
|
|
522
|
-
}
|
|
523
|
-
file.position = position;
|
|
524
|
-
mem.write_u64(out, position)
|
|
476
|
+
Ok(seekable_file(table, fd)?.llseek(offset, whence)?)
|
|
525
477
|
}
|
|
526
478
|
|
|
527
|
-
pub(crate) fn tell(table: &mut Descriptors,
|
|
528
|
-
|
|
529
|
-
mem.write_u64(out, position)
|
|
479
|
+
pub(crate) fn tell(table: &mut Descriptors, fd: u32) -> Answer<u64> {
|
|
480
|
+
Ok(seekable_file(table, fd)?.llseek(0, Whence::Cur)?)
|
|
530
481
|
}
|
|
531
482
|
|
|
532
483
|
pub(crate) fn write(
|
|
533
484
|
table: &mut Descriptors,
|
|
534
|
-
mem: &mut GuestMemory,
|
|
485
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
535
486
|
fd: u32,
|
|
536
|
-
ciovs:
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
let
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
Descriptor::File(file) => {
|
|
543
|
-
// Bounded before the access is checked, as in `pwrite`.
|
|
544
|
-
let buf = mem.bytes(ptr, len)?;
|
|
545
|
-
if !file.writable {
|
|
546
|
-
return Err(Errno::Badf.into());
|
|
547
|
-
}
|
|
548
|
-
let (at, written) = if file.append {
|
|
549
|
-
file.file.append(buf)?
|
|
550
|
-
} else {
|
|
551
|
-
let buf = trim_to_largest_offset(buf, file.position)?;
|
|
552
|
-
(file.position, file.file.write_at(buf, file.position)?)
|
|
553
|
-
};
|
|
554
|
-
file.position = at.checked_add(written as u64).ok_or(Errno::Fbig)?;
|
|
555
|
-
written
|
|
556
|
-
}
|
|
557
|
-
Descriptor::Stdin(_) | Descriptor::Directory(_) => return Err(Errno::Badf.into()),
|
|
558
|
-
};
|
|
559
|
-
mem.write_u32(nwritten, written as u32)
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/// A zero-length read of a stdin that still holds bytes is `INTR`, and one
|
|
563
|
-
/// of a drained stdin reads nothing, as wasmtime-wasi's blocking read
|
|
564
|
-
/// answers them.
|
|
565
|
-
fn read_stdin(pipe: &mut MemoryInputPipe, len: usize) -> Answer<Vec<u8>> {
|
|
566
|
-
match pipe.read(len) {
|
|
567
|
-
Ok(bytes) if bytes.is_empty() => Err(Errno::Intr.into()),
|
|
568
|
-
Ok(bytes) => Ok(bytes.to_vec()),
|
|
569
|
-
Err(StreamError::Closed) => Ok(Vec::new()),
|
|
570
|
-
Err(_) => Err(Errno::Io.into()),
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/// Fill the capture pipe as far as it holds. A pipe that fills before the
|
|
575
|
-
/// bytes run out answers `IO`, though what fit is kept: the cap one past
|
|
576
|
-
/// the configured limit is how a capture learns it was truncated.
|
|
577
|
-
fn write_channel(pipe: &mut MemoryOutputPipe, bytes: &[u8]) -> Answer<usize> {
|
|
578
|
-
for chunk in bytes.chunks(WRITE_CHUNK) {
|
|
579
|
-
let mut rest = chunk;
|
|
580
|
-
while !rest.is_empty() {
|
|
581
|
-
let permit = pipe.check_write().map_err(|_| Errno::Io)?;
|
|
582
|
-
let (now, later) = rest.split_at(rest.len().min(permit));
|
|
583
|
-
pipe.write(Bytes::copy_from_slice(now))
|
|
584
|
-
.map_err(|_| Errno::Io)?;
|
|
585
|
-
rest = later;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
Ok(bytes.len())
|
|
487
|
+
ciovs: types::CiovecArray,
|
|
488
|
+
) -> Answer<types::Size> {
|
|
489
|
+
let buf = mem.first_non_empty_ciovec(ciovs)?;
|
|
490
|
+
let file = file_mut(table, fd)?;
|
|
491
|
+
file.may_write()?;
|
|
492
|
+
Ok(file.write(mem.bytes(buf)?)? as u32)
|
|
589
493
|
}
|
|
590
494
|
|
|
591
495
|
#[cfg(test)]
|
|
@@ -594,79 +498,923 @@ mod tests {
|
|
|
594
498
|
//! does, so its Filesystem learns of the close and may refuse it.
|
|
595
499
|
use std::sync::Arc;
|
|
596
500
|
|
|
597
|
-
use
|
|
598
|
-
|
|
599
|
-
|
|
501
|
+
use super::{to_filetype, Filetype};
|
|
502
|
+
|
|
503
|
+
// @behavior RT-195
|
|
504
|
+
#[test]
|
|
505
|
+
fn a_special_files_kind_reads_as_preview1_names_it() {
|
|
506
|
+
assert_eq!(
|
|
507
|
+
[
|
|
508
|
+
FileType::Sock,
|
|
509
|
+
FileType::Blkdev,
|
|
510
|
+
FileType::Chrdev,
|
|
511
|
+
FileType::Fifo,
|
|
512
|
+
FileType::Unknown,
|
|
513
|
+
]
|
|
514
|
+
.map(to_filetype),
|
|
515
|
+
[
|
|
516
|
+
Filetype::SocketStream,
|
|
517
|
+
Filetype::BlockDevice,
|
|
518
|
+
Filetype::CharacterDevice,
|
|
519
|
+
Filetype::Unknown,
|
|
520
|
+
Filetype::Unknown,
|
|
521
|
+
],
|
|
522
|
+
"to_filetype must name a socket as a stream, each device as itself, and a FIFO, which \
|
|
523
|
+
preview1 has no code for, as unknown"
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
use super::{
|
|
528
|
+
fdstat_get, fdstat_set_flags, filestat_set_size, filestat_set_times, readdir, renumber,
|
|
529
|
+
seek, sync, tell, utimensat, Answer, Errno, Fdflags, Fstflags,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
/// `fd_filestat_set_times` of `fd` setting both times to zero as `flags`
|
|
533
|
+
/// ask, as the dispatcher answers it.
|
|
534
|
+
fn set_times(table: &Descriptors, fd: u32, flags: Fstflags) -> Answer {
|
|
535
|
+
utimensat(0, 0, flags, |times| filestat_set_times(table, fd, times))
|
|
536
|
+
}
|
|
537
|
+
use crate::descriptors::{Descriptor, Descriptors};
|
|
538
|
+
use crate::preview1::memory::GuestMemory;
|
|
539
|
+
use crate::preview1::path::{
|
|
540
|
+
create_directory, filestat_set_times as path_set_times, open as path_open, readlink,
|
|
541
|
+
rename, symlink, PathOpen,
|
|
542
|
+
};
|
|
543
|
+
use crate::preview1::types::{self, Rights, Whence};
|
|
544
|
+
use kobako_runtime::filesystem::{create_pipe_files, PipeInodeInfo};
|
|
545
|
+
use kobako_runtime::filesystem::{
|
|
546
|
+
FileOperations, FileType, Filesystem, FilesystemError, InodeOperations, Kstat, Lookup,
|
|
547
|
+
};
|
|
548
|
+
use kobako_runtime::mount::{Access, Mount};
|
|
549
|
+
use kobako_runtime::profile::Profile;
|
|
550
|
+
use kobako_runtime::security::prepare_creds_within;
|
|
551
|
+
use kobako_runtime::vfs::{self, Dentry, Inode, OpenHow, Path, PATH_MAX};
|
|
552
|
+
use wiggle::{GuestPtr, GuestType};
|
|
553
|
+
|
|
554
|
+
/// What `do_file_open` takes for an open `how` asks.
|
|
555
|
+
fn flags_of(how: &OpenHow) -> vfs::OpenFlags {
|
|
556
|
+
vfs::build_open_flags(how).expect("the flags must mean an open")
|
|
557
|
+
}
|
|
600
558
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
559
|
+
/// A table holding the three standard streams and `mounts`, at most `limit`
|
|
560
|
+
/// descriptors open, as a Driver makes one.
|
|
561
|
+
fn table_of(mounts: Vec<Mount>, limit: Option<usize>) -> Result<Descriptors, FilesystemError> {
|
|
562
|
+
let cred = prepare_creds_within(&[], None);
|
|
563
|
+
let ends = |pipe| create_pipe_files(&pipe, &cred);
|
|
564
|
+
Descriptors::new(
|
|
565
|
+
ends(PipeInodeInfo::from_bytes(Vec::new())).0,
|
|
566
|
+
ends(PipeInodeInfo::new(1)).1,
|
|
567
|
+
ends(PipeInodeInfo::new(1)).1,
|
|
568
|
+
mounts,
|
|
569
|
+
limit,
|
|
570
|
+
cred.clone(),
|
|
571
|
+
Profile::Hermetic,
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/// A table holding only the three standard streams.
|
|
576
|
+
fn standard_streams() -> Descriptors {
|
|
577
|
+
table_of(Vec::new(), None).expect("a table holding no Mount must make")
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/// The code a call answered, if it answered one.
|
|
581
|
+
fn errno<T>(answer: Answer<T>) -> Option<Errno> {
|
|
582
|
+
answer.err().and_then(|err| err.downcast().ok())
|
|
583
|
+
}
|
|
604
584
|
|
|
605
|
-
|
|
585
|
+
// @behavior RT-177
|
|
586
|
+
#[test]
|
|
587
|
+
fn a_standard_stream_answers_as_a_pipe_does() {
|
|
588
|
+
let mut table = standard_streams();
|
|
589
|
+
assert_eq!(
|
|
590
|
+
[
|
|
591
|
+
errno(sync(&mut table, 1, false)),
|
|
592
|
+
errno(filestat_set_size(&mut table, 1, 0)),
|
|
593
|
+
errno(tell(&mut table, 0)),
|
|
594
|
+
errno(tell(&mut table, 1)),
|
|
595
|
+
],
|
|
596
|
+
[
|
|
597
|
+
Some(Errno::Inval),
|
|
598
|
+
Some(Errno::Inval),
|
|
599
|
+
Some(Errno::Spipe),
|
|
600
|
+
Some(Errno::Spipe)
|
|
601
|
+
],
|
|
602
|
+
"fd_sync and fd_filestat_set_size on a standard stream through preview1 must answer \
|
|
603
|
+
EINVAL, and a call about its position ESPIPE, as Linux answers for a pipe"
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/// A file whose Filesystem refuses to close it.
|
|
606
608
|
struct RefusesClose;
|
|
607
609
|
|
|
608
|
-
impl
|
|
609
|
-
fn
|
|
610
|
-
|
|
610
|
+
impl FileOperations for RefusesClose {
|
|
611
|
+
fn flush(&mut self) -> Result<(), FilesystemError> {
|
|
612
|
+
Err(FilesystemError::PermissionDenied)
|
|
611
613
|
}
|
|
614
|
+
}
|
|
612
615
|
|
|
613
|
-
|
|
614
|
-
|
|
616
|
+
/// A file its Filesystem opens with no position.
|
|
617
|
+
struct Stream;
|
|
618
|
+
|
|
619
|
+
impl FileOperations for Stream {
|
|
620
|
+
fn is_stream(&self) -> bool {
|
|
621
|
+
true
|
|
615
622
|
}
|
|
623
|
+
}
|
|
616
624
|
|
|
617
|
-
|
|
618
|
-
|
|
625
|
+
/// A directory holding at every name an entry of the kind it names, whose
|
|
626
|
+
/// open answers what its function makes.
|
|
627
|
+
struct Holds(FileType, fn() -> Box<dyn FileOperations>);
|
|
628
|
+
|
|
629
|
+
/// A directory holding a file its Filesystem refuses to close.
|
|
630
|
+
const HOLDS_REFUSES_CLOSE: Holds = Holds(FileType::RegFile, || Box::new(RefusesClose));
|
|
631
|
+
|
|
632
|
+
impl kobako_runtime::filesystem::FileSystemType for Holds {
|
|
633
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
634
|
+
sb.d_make_root(kobako_runtime::vfs::Inode::new(
|
|
635
|
+
&kobako_runtime::Kstat::new(kobako_runtime::FileType::Dir, 0),
|
|
636
|
+
self,
|
|
637
|
+
));
|
|
638
|
+
Ok(())
|
|
619
639
|
}
|
|
640
|
+
}
|
|
620
641
|
|
|
621
|
-
|
|
622
|
-
|
|
642
|
+
impl InodeOperations for Holds {
|
|
643
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
644
|
+
Ok(Kstat::new(FileType::Dir, 0))
|
|
623
645
|
}
|
|
624
646
|
|
|
625
|
-
fn
|
|
626
|
-
|
|
647
|
+
fn lookup(&self, _dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
648
|
+
let found = Kstat::new(self.0, 0);
|
|
649
|
+
Ok(Lookup::Found(Inode::new(
|
|
650
|
+
&found,
|
|
651
|
+
Arc::new(Self(self.0, self.1)),
|
|
652
|
+
)))
|
|
627
653
|
}
|
|
628
654
|
|
|
629
|
-
fn
|
|
630
|
-
|
|
655
|
+
fn open(
|
|
656
|
+
&self,
|
|
657
|
+
_dentry: &Dentry,
|
|
658
|
+
_options: &OpenHow,
|
|
659
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
660
|
+
Ok((self.1)())
|
|
631
661
|
}
|
|
632
662
|
}
|
|
633
663
|
|
|
664
|
+
/// The file `holds` holds, opened as `how` asks beneath a Mount that
|
|
665
|
+
/// writes when `how` does.
|
|
666
|
+
fn open_held(holds: Holds, how: &OpenHow) -> Descriptor {
|
|
667
|
+
let access = match how.write {
|
|
668
|
+
true => Access::ReadWrite,
|
|
669
|
+
false => Access::Read,
|
|
670
|
+
};
|
|
671
|
+
let mount = Mount::new(
|
|
672
|
+
"/data",
|
|
673
|
+
Filesystem::new(Arc::new(holds)).expect("the instance must stand"),
|
|
674
|
+
access,
|
|
675
|
+
)
|
|
676
|
+
.expect("/data is a guest path");
|
|
677
|
+
let start = Path::root(mount.mnt());
|
|
678
|
+
Descriptor::for_file(
|
|
679
|
+
vfs::do_file_open(
|
|
680
|
+
&prepare_creds_within(&[], None),
|
|
681
|
+
&start,
|
|
682
|
+
"file",
|
|
683
|
+
&flags_of(how),
|
|
684
|
+
)
|
|
685
|
+
.unwrap_or_else(|_| panic!("the file must open")),
|
|
686
|
+
)
|
|
687
|
+
}
|
|
688
|
+
|
|
634
689
|
fn open_file() -> Descriptor {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
690
|
+
let mut how = OpenHow::default();
|
|
691
|
+
how.read = true;
|
|
692
|
+
open_held(HOLDS_REFUSES_CLOSE, &how)
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// @behavior RT-176
|
|
696
|
+
#[test]
|
|
697
|
+
fn a_file_without_fsync_refuses_a_sync() {
|
|
698
|
+
let mut table = standard_streams();
|
|
699
|
+
let fd = table.push(open_file()).expect("a descriptor to sync");
|
|
700
|
+
assert_eq!(
|
|
701
|
+
errno(sync(&mut table, fd, false)),
|
|
702
|
+
Some(Errno::Inval),
|
|
703
|
+
"fd_sync on a file whose Filesystem has no fsync must answer EINVAL"
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// @behavior RT-129
|
|
708
|
+
#[test]
|
|
709
|
+
fn a_file_set_not_to_block_reports_it() {
|
|
710
|
+
let mut table = standard_streams();
|
|
711
|
+
let fd = table.push(open_file()).expect("a descriptor to set");
|
|
712
|
+
let flags = |table: &Descriptors| {
|
|
713
|
+
fdstat_get(table, fd)
|
|
714
|
+
.unwrap_or_else(|_| panic!("the file must report its flags"))
|
|
715
|
+
.fs_flags
|
|
716
|
+
};
|
|
717
|
+
assert!(
|
|
718
|
+
fdstat_set_flags(&mut table, fd, Fdflags::NONBLOCK | Fdflags::SYNC).is_ok(),
|
|
719
|
+
"fd_fdstat_set_flags asking not to block and to sync must succeed, as Linux's setfl"
|
|
720
|
+
);
|
|
721
|
+
assert_eq!(
|
|
722
|
+
flags(&table),
|
|
723
|
+
Fdflags::NONBLOCK,
|
|
724
|
+
"fd_fdstat_get after fd_fdstat_set_flags must report not blocking and, as Linux's \
|
|
725
|
+
setfl ignores it, no sync"
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// A pipe end is open one way only, so a stream open both ways comes from
|
|
730
|
+
// a Filesystem's open.
|
|
731
|
+
// @behavior RT-275
|
|
732
|
+
#[test]
|
|
733
|
+
fn a_stream_reports_what_it_was_opened_and_set_for() {
|
|
734
|
+
let mut table = standard_streams();
|
|
735
|
+
let mut how = OpenHow::default();
|
|
736
|
+
how.read = true;
|
|
737
|
+
how.write = true;
|
|
738
|
+
let fd = table
|
|
739
|
+
.push(open_held(
|
|
740
|
+
Holds(FileType::RegFile, || Box::new(Stream)),
|
|
741
|
+
&how,
|
|
742
|
+
))
|
|
743
|
+
.expect("a descriptor to set");
|
|
744
|
+
assert!(
|
|
745
|
+
fdstat_set_flags(&mut table, fd, Fdflags::APPEND | Fdflags::NONBLOCK).is_ok(),
|
|
746
|
+
"fd_fdstat_set_flags asking a stream to append and not to block must succeed"
|
|
747
|
+
);
|
|
748
|
+
let stat = fdstat_get(&table, fd).unwrap_or_else(|_| panic!("the stream must report"));
|
|
749
|
+
assert_eq!(
|
|
750
|
+
(
|
|
751
|
+
stat.fs_flags,
|
|
752
|
+
stat.fs_rights_base
|
|
753
|
+
.contains(Rights::FD_READ | Rights::FD_WRITE)
|
|
644
754
|
),
|
|
645
|
-
|
|
755
|
+
(Fdflags::APPEND | Fdflags::NONBLOCK, true),
|
|
756
|
+
"fd_fdstat_get on a stream open to read and write, set to append and not to block, \
|
|
757
|
+
must report both flags and both rights, as Linux's F_GETFL reports a pipe's"
|
|
758
|
+
);
|
|
646
759
|
}
|
|
647
760
|
|
|
648
|
-
// @behavior RT-
|
|
761
|
+
// @behavior RT-276
|
|
762
|
+
#[test]
|
|
763
|
+
fn a_descriptor_reports_the_kind_its_file_is() {
|
|
764
|
+
let mut table = standard_streams();
|
|
765
|
+
let fd = table
|
|
766
|
+
.push(open_held(
|
|
767
|
+
Holds(FileType::Chrdev, || Box::new(RefusesClose)),
|
|
768
|
+
&OpenHow::default(),
|
|
769
|
+
))
|
|
770
|
+
.expect("a descriptor to ask about");
|
|
771
|
+
assert_eq!(
|
|
772
|
+
fdstat_get(&table, fd)
|
|
773
|
+
.unwrap_or_else(|_| panic!("the device must report"))
|
|
774
|
+
.fs_filetype,
|
|
775
|
+
types::Filetype::CharacterDevice,
|
|
776
|
+
"fd_fdstat_get on a character device opened for its path alone must report a \
|
|
777
|
+
character device, as fd_filestat_get does"
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// wasi-testsuite's dir_fd_op_failures names these answers and writes that
|
|
782
|
+
// they part from POSIX, where a directory seeks.
|
|
783
|
+
// @behavior RT-184
|
|
784
|
+
#[test]
|
|
785
|
+
fn a_directory_refuses_a_call_about_its_position() {
|
|
786
|
+
let mount = Mount::new(
|
|
787
|
+
"/data",
|
|
788
|
+
Filesystem::new(Arc::new(HOLDS_REFUSES_CLOSE)).expect("the instance must stand"),
|
|
789
|
+
Access::Read,
|
|
790
|
+
)
|
|
791
|
+
.expect("/data is a guest path");
|
|
792
|
+
let mut table =
|
|
793
|
+
table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
|
|
794
|
+
assert_eq!(
|
|
795
|
+
(
|
|
796
|
+
errno(tell(&mut table, 3)),
|
|
797
|
+
errno(seek(&mut table, 3, 0, Whence::Set)),
|
|
798
|
+
),
|
|
799
|
+
(Some(Errno::Badf), Some(Errno::Badf)),
|
|
800
|
+
"fd_tell and fd_seek on a directory through preview1 must answer EBADF, as \
|
|
801
|
+
wasi-testsuite requires"
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// @behavior RT-251
|
|
806
|
+
#[test]
|
|
807
|
+
fn a_descriptor_reports_how_far_it_was_opened_to_sync() {
|
|
808
|
+
let tmpfs: kobako_runtime::filesystem::Tmpfs = [("f", "x")].into_iter().collect();
|
|
809
|
+
let mount = Mount::new(
|
|
810
|
+
"/data",
|
|
811
|
+
Filesystem::new(Arc::new(tmpfs)).expect("the instance must stand"),
|
|
812
|
+
Access::ReadWrite,
|
|
813
|
+
)
|
|
814
|
+
.expect("/data is a guest path");
|
|
815
|
+
let root = Path::root(mount.mnt());
|
|
816
|
+
let mut table =
|
|
817
|
+
table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
|
|
818
|
+
let cred = table.cred().clone();
|
|
819
|
+
let mut reported = |dsync, sync| {
|
|
820
|
+
let mut how = OpenHow::default();
|
|
821
|
+
how.write = true;
|
|
822
|
+
how.dsync = dsync;
|
|
823
|
+
how.sync = sync;
|
|
824
|
+
let opened = vfs::do_file_open(&cred, &root, "f", &flags_of(&how))
|
|
825
|
+
.unwrap_or_else(|_| panic!("the file must open"));
|
|
826
|
+
let fd = table
|
|
827
|
+
.push(Descriptor::for_file(opened))
|
|
828
|
+
.unwrap_or_else(|| panic!("the table must have room"));
|
|
829
|
+
let Ok(stat) = fdstat_get(&table, fd) else {
|
|
830
|
+
panic!("the descriptor must report itself");
|
|
831
|
+
};
|
|
832
|
+
(
|
|
833
|
+
stat.fs_flags.contains(Fdflags::DSYNC),
|
|
834
|
+
stat.fs_flags.contains(Fdflags::SYNC),
|
|
835
|
+
)
|
|
836
|
+
};
|
|
837
|
+
assert_eq!(
|
|
838
|
+
(reported(true, false), reported(false, true)),
|
|
839
|
+
((true, false), (true, true)),
|
|
840
|
+
"fd_fdstat_get must report DSYNC alone for a file opened O_DSYNC, and both for one \
|
|
841
|
+
opened O_SYNC, whose bits hold O_DSYNC's as Linux's do"
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/// Allows every path, counting each it is asked about.
|
|
846
|
+
#[derive(Default)]
|
|
847
|
+
struct CountsAsks(std::sync::atomic::AtomicUsize);
|
|
848
|
+
|
|
849
|
+
impl kobako_runtime::mount::Policy for CountsAsks {
|
|
850
|
+
fn allows(&self, _path: &str, _access: Access) -> bool {
|
|
851
|
+
self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
852
|
+
true
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// The guard sits in the Driver rather than the VFS, so only this call shows
|
|
857
|
+
// that setting a descriptor's times asks it.
|
|
858
|
+
// @behavior RT-201
|
|
859
|
+
#[test]
|
|
860
|
+
fn setting_a_descriptors_times_asks_whether_it_still_stands() {
|
|
861
|
+
use std::sync::atomic::Ordering;
|
|
862
|
+
let policy = Arc::new(CountsAsks::default());
|
|
863
|
+
let tmpfs: kobako_runtime::filesystem::Tmpfs = [("f", "x")].into_iter().collect();
|
|
864
|
+
let mount = Mount::new(
|
|
865
|
+
"/data",
|
|
866
|
+
Filesystem::new(Arc::new(tmpfs)).expect("the instance must stand"),
|
|
867
|
+
Access::ReadWrite,
|
|
868
|
+
)
|
|
869
|
+
.expect("/data is a guest path")
|
|
870
|
+
.with_policy(policy.clone());
|
|
871
|
+
let root = Path::root(mount.mnt());
|
|
872
|
+
let mut table =
|
|
873
|
+
table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
|
|
874
|
+
let cred = table.cred().clone();
|
|
875
|
+
let mut read = OpenHow::default();
|
|
876
|
+
read.read = true;
|
|
877
|
+
let opened = vfs::do_file_open(&cred, &root, "f", &flags_of(&read))
|
|
878
|
+
.unwrap_or_else(|_| panic!("the file must open"));
|
|
879
|
+
let fd = table
|
|
880
|
+
.push(Descriptor::for_file(opened))
|
|
881
|
+
.unwrap_or_else(|| panic!("the table must have room"));
|
|
882
|
+
vfs::filename_unlinkat(&cred, &root, "f").unwrap_or_else(|_| panic!("the name must go"));
|
|
883
|
+
policy.0.store(0, Ordering::SeqCst);
|
|
884
|
+
assert_eq!(
|
|
885
|
+
(
|
|
886
|
+
errno(set_times(&table, fd, Fstflags::ATIM_NOW)),
|
|
887
|
+
policy.0.load(Ordering::SeqCst)
|
|
888
|
+
),
|
|
889
|
+
(Some(Errno::Noent), 0),
|
|
890
|
+
"fd_filestat_set_times through a descriptor of a file whose name is gone, beneath a \
|
|
891
|
+
Policy, must answer ENOENT without asking the Policy about the path it stood at"
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// @behavior RT-186 RT-187
|
|
896
|
+
#[test]
|
|
897
|
+
fn setting_a_descriptors_times_answers_as_a_paths_do() {
|
|
898
|
+
let mount = Mount::new(
|
|
899
|
+
"/data",
|
|
900
|
+
Filesystem::new(Arc::new(HOLDS_REFUSES_CLOSE)).expect("the instance must stand"),
|
|
901
|
+
Access::Read,
|
|
902
|
+
)
|
|
903
|
+
.expect("/data is a guest path");
|
|
904
|
+
let table = table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
|
|
905
|
+
assert_eq!(
|
|
906
|
+
(
|
|
907
|
+
errno(set_times(&table, 3, Fstflags::MTIM)),
|
|
908
|
+
errno(set_times(&table, 3, Fstflags::empty())),
|
|
909
|
+
errno(set_times(&table, 3, Fstflags::MTIM | Fstflags::MTIM_NOW)),
|
|
910
|
+
),
|
|
911
|
+
(Some(Errno::Rofs), None, Some(Errno::Inval)),
|
|
912
|
+
"fd_filestat_set_times beneath a read-only Mount through preview1 must answer EROFS, \
|
|
913
|
+
a call asking no time must succeed, and one asking a time and now EINVAL"
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// @behavior RT-185
|
|
918
|
+
#[test]
|
|
919
|
+
fn a_path_call_starting_from_a_file_is_not_a_directory() {
|
|
920
|
+
let mut table = standard_streams();
|
|
921
|
+
let fd = table.push(open_file()).expect("a descriptor to start from");
|
|
922
|
+
let mut bytes = *b"a";
|
|
923
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
924
|
+
let mem = GuestMemory::new(&mut memory, 1);
|
|
925
|
+
assert_eq!(
|
|
926
|
+
errno(create_directory(&table, &mem, fd, GuestPtr::new((0, 1)))),
|
|
927
|
+
Some(Errno::Notdir),
|
|
928
|
+
"path_create_directory starting from a file through preview1 must answer ENOTDIR, as \
|
|
929
|
+
Linux refuses a walk starting there"
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Linux reads the flags and the path before it looks at where a walk starts,
|
|
934
|
+
// so a Driver checking the start first would hide each of these.
|
|
935
|
+
// @behavior RT-225 RT-230 RT-231 RT-232
|
|
936
|
+
#[test]
|
|
937
|
+
fn a_call_no_walk_can_take_answers_before_where_it_starts() {
|
|
938
|
+
let mut table = standard_streams();
|
|
939
|
+
let fd = table.push(open_file()).expect("a descriptor to start from");
|
|
940
|
+
let mut bytes = vec![b'a'; PATH_MAX];
|
|
941
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
942
|
+
let mem = GuestMemory::new(&mut memory, 2 * PATH_MAX);
|
|
943
|
+
let call = PathOpen {
|
|
944
|
+
dirflags: types::Lookupflags::empty(),
|
|
945
|
+
oflags: types::Oflags::CREAT | types::Oflags::DIRECTORY,
|
|
946
|
+
rights_base: Rights::FD_READ,
|
|
947
|
+
fdflags: Fdflags::empty(),
|
|
948
|
+
};
|
|
949
|
+
let path = |len| GuestPtr::new((0, len));
|
|
950
|
+
assert_eq!(
|
|
951
|
+
(
|
|
952
|
+
errno(path_open(&mut table, &mem, fd, path(1), &call)),
|
|
953
|
+
errno(create_directory(&table, &mem, fd, path(0))),
|
|
954
|
+
errno(create_directory(&table, &mem, fd, path(PATH_MAX as u32))),
|
|
955
|
+
errno(utimensat(0, 0, Fstflags::empty(), |times| path_set_times(
|
|
956
|
+
&table,
|
|
957
|
+
&mem,
|
|
958
|
+
fd,
|
|
959
|
+
types::Lookupflags::empty(),
|
|
960
|
+
path(1),
|
|
961
|
+
times
|
|
962
|
+
))),
|
|
963
|
+
),
|
|
964
|
+
(
|
|
965
|
+
Some(Errno::Inval),
|
|
966
|
+
Some(Errno::Noent),
|
|
967
|
+
Some(Errno::Nametoolong),
|
|
968
|
+
None
|
|
969
|
+
),
|
|
970
|
+
"path_open with O_CREAT and O_DIRECTORY, and path_create_directory of an empty and of \
|
|
971
|
+
an overlong path, starting from a file through preview1 must answer EINVAL, ENOENT, \
|
|
972
|
+
and ENAMETOOLONG, and path_filestat_set_times setting no time must succeed"
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Linux reads the flags and the name before `path_init` takes the
|
|
977
|
+
// descriptor, so a number naming nothing answers only once both are fine.
|
|
978
|
+
// @behavior RT-257 RT-258
|
|
979
|
+
#[test]
|
|
980
|
+
fn a_call_no_walk_can_take_answers_before_the_descriptor_it_names() {
|
|
981
|
+
let mut table = standard_streams();
|
|
982
|
+
let mut bytes = vec![b'a'; PATH_MAX];
|
|
983
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
984
|
+
let mem = GuestMemory::new(&mut memory, 2 * PATH_MAX);
|
|
985
|
+
let call = |oflags| PathOpen {
|
|
986
|
+
dirflags: types::Lookupflags::empty(),
|
|
987
|
+
oflags,
|
|
988
|
+
rights_base: Rights::FD_READ,
|
|
989
|
+
fdflags: Fdflags::empty(),
|
|
990
|
+
};
|
|
991
|
+
let path = |len| GuestPtr::new((0, len));
|
|
992
|
+
let unopened = 99;
|
|
993
|
+
let create_dir = call(types::Oflags::CREAT | types::Oflags::DIRECTORY);
|
|
994
|
+
assert_eq!(
|
|
995
|
+
(
|
|
996
|
+
errno(path_open(&mut table, &mem, unopened, path(1), &create_dir)),
|
|
997
|
+
errno(create_directory(&table, &mem, unopened, path(0))),
|
|
998
|
+
errno(create_directory(
|
|
999
|
+
&table,
|
|
1000
|
+
&mem,
|
|
1001
|
+
unopened,
|
|
1002
|
+
path(PATH_MAX as u32)
|
|
1003
|
+
)),
|
|
1004
|
+
errno(create_directory(&table, &mem, unopened, path(1))),
|
|
1005
|
+
),
|
|
1006
|
+
(
|
|
1007
|
+
Some(Errno::Inval),
|
|
1008
|
+
Some(Errno::Noent),
|
|
1009
|
+
Some(Errno::Nametoolong),
|
|
1010
|
+
Some(Errno::Badf)
|
|
1011
|
+
),
|
|
1012
|
+
"path_open with O_CREAT and O_DIRECTORY, and path_create_directory of an empty and of \
|
|
1013
|
+
an overlong path, from a descriptor number naming nothing through preview1 must \
|
|
1014
|
+
answer EINVAL, ENOENT, and ENAMETOOLONG, and a path a walk can take EBADF"
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Standard output is a pipe's end, so a walk from it answers ENOTDIR; the
|
|
1019
|
+
// second path is wrong too, so only the order picks which answers.
|
|
1020
|
+
// @behavior RT-286
|
|
649
1021
|
#[test]
|
|
650
|
-
fn
|
|
651
|
-
let
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
1022
|
+
fn a_call_naming_two_paths_answers_for_its_first_before_its_second() {
|
|
1023
|
+
let table = standard_streams();
|
|
1024
|
+
let mut bytes = vec![b'a'; PATH_MAX];
|
|
1025
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
1026
|
+
let mem = GuestMemory::new(&mut memory, 2 * PATH_MAX);
|
|
1027
|
+
let path = |len| GuestPtr::new((0, len));
|
|
1028
|
+
let (stdout, unopened) = (1, 99);
|
|
1029
|
+
assert_eq!(
|
|
1030
|
+
(
|
|
1031
|
+
errno(rename(
|
|
1032
|
+
&table,
|
|
1033
|
+
&mem,
|
|
1034
|
+
(stdout, path(1)),
|
|
1035
|
+
(stdout, path(PATH_MAX as u32))
|
|
1036
|
+
)),
|
|
1037
|
+
errno(rename(&table, &mem, (stdout, path(1)), (unopened, path(1)))),
|
|
1038
|
+
),
|
|
1039
|
+
(Some(Errno::Notdir), Some(Errno::Notdir)),
|
|
1040
|
+
"path_rename from standard output to a path past PATH_MAX, or to a descriptor \
|
|
1041
|
+
number naming nothing, through preview1 must answer ENOTDIR for the first path"
|
|
657
1042
|
);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// @behavior RT-287
|
|
1046
|
+
#[test]
|
|
1047
|
+
fn an_absolute_path_answers_before_the_descriptor_it_names() {
|
|
1048
|
+
let table = standard_streams();
|
|
1049
|
+
let mut bytes = b"/a".to_vec();
|
|
1050
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
1051
|
+
let mem = GuestMemory::new(&mut memory, 16);
|
|
1052
|
+
assert_eq!(
|
|
1053
|
+
errno(create_directory(&table, &mem, 99, GuestPtr::new((0, 2)))),
|
|
1054
|
+
Some(Errno::Notcapable),
|
|
1055
|
+
"path_create_directory of an absolute path from a descriptor number naming nothing \
|
|
1056
|
+
through preview1 must answer ENOTCAPABLE, as path_init looks at no descriptor"
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// @behavior RT-277
|
|
1061
|
+
#[test]
|
|
1062
|
+
fn a_readlink_into_no_room_is_invalid() {
|
|
1063
|
+
let table = standard_streams();
|
|
1064
|
+
let mut bytes = vec![0u8; 8];
|
|
1065
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
1066
|
+
let mut mem = GuestMemory::new(&mut memory, 16);
|
|
1067
|
+
assert_eq!(
|
|
1068
|
+
errno(readlink(
|
|
1069
|
+
&table,
|
|
1070
|
+
&mut mem,
|
|
1071
|
+
99,
|
|
1072
|
+
GuestPtr::new((0, 0)),
|
|
1073
|
+
GuestPtr::new(0),
|
|
1074
|
+
0
|
|
1075
|
+
)),
|
|
1076
|
+
Some(Errno::Inval),
|
|
1077
|
+
"path_readlink into a buffer of no bytes, of an empty path from a descriptor number \
|
|
1078
|
+
naming nothing, through preview1 must answer EINVAL, as do_readlinkat refuses the \
|
|
1079
|
+
size first"
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Linux's `do_readlinkat` takes its name with `LOOKUP_EMPTY`, so an empty
|
|
1084
|
+
// path names the descriptor itself: one naming nothing answers as
|
|
1085
|
+
// `path_init` does, and a file, which is no link, as not found. The file
|
|
1086
|
+
// also shows the start need not be a directory.
|
|
1087
|
+
// @behavior RT-289
|
|
1088
|
+
#[test]
|
|
1089
|
+
fn a_readlink_of_an_empty_path_answers_for_its_descriptor() {
|
|
1090
|
+
let mut table = standard_streams();
|
|
1091
|
+
let fd = table.push(open_file()).expect("a descriptor to start from");
|
|
1092
|
+
let mut bytes = vec![0u8; 8];
|
|
1093
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
1094
|
+
let mut mem = GuestMemory::new(&mut memory, 16);
|
|
1095
|
+
let mut empty_from = |start| {
|
|
1096
|
+
errno(readlink(
|
|
1097
|
+
&table,
|
|
1098
|
+
&mut mem,
|
|
1099
|
+
start,
|
|
1100
|
+
GuestPtr::new((0, 0)),
|
|
1101
|
+
GuestPtr::new(0),
|
|
1102
|
+
8,
|
|
1103
|
+
))
|
|
1104
|
+
};
|
|
1105
|
+
assert_eq!(
|
|
1106
|
+
(empty_from(99), empty_from(fd)),
|
|
1107
|
+
(Some(Errno::Badf), Some(Errno::Noent)),
|
|
1108
|
+
"path_readlink of an empty path through preview1 must answer EBADF from a descriptor \
|
|
1109
|
+
number naming nothing and ENOENT from a file, as do_readlinkat reads the descriptor \
|
|
1110
|
+
itself"
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// Linux's `utimensat` answers a call asking no time before it takes the
|
|
1115
|
+
// name or the descriptor, so a number naming nothing never shows.
|
|
1116
|
+
// @behavior RT-271
|
|
1117
|
+
#[test]
|
|
1118
|
+
fn a_call_asking_no_time_succeeds_whatever_descriptor_it_names() {
|
|
1119
|
+
let table = standard_streams();
|
|
1120
|
+
let mut bytes = *b"a";
|
|
1121
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
1122
|
+
let mem = GuestMemory::new(&mut memory, 1);
|
|
1123
|
+
let unopened = 99;
|
|
1124
|
+
assert_eq!(
|
|
1125
|
+
(
|
|
1126
|
+
errno(set_times(&table, unopened, Fstflags::empty())),
|
|
1127
|
+
errno(utimensat(0, 0, Fstflags::empty(), |times| path_set_times(
|
|
1128
|
+
&table,
|
|
1129
|
+
&mem,
|
|
1130
|
+
unopened,
|
|
1131
|
+
types::Lookupflags::empty(),
|
|
1132
|
+
GuestPtr::new((0, 1)),
|
|
1133
|
+
times
|
|
1134
|
+
))),
|
|
1135
|
+
),
|
|
1136
|
+
(None, None),
|
|
1137
|
+
"fd_filestat_set_times and path_filestat_set_times asking no time through preview1 \
|
|
1138
|
+
must succeed from a descriptor number naming nothing"
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// Linux's `do_sys_openat2` reads the flags before `FD_ADD` reserves a
|
|
1143
|
+
// descriptor, and the name before the walk looks at where it starts.
|
|
1144
|
+
// @behavior RT-259
|
|
1145
|
+
#[test]
|
|
1146
|
+
fn a_full_table_answers_after_the_flags_and_before_the_start() {
|
|
1147
|
+
let mut table = table_of(Vec::new(), Some(3)).expect("a table holding no Mount must make");
|
|
1148
|
+
let mut bytes = *b"a";
|
|
1149
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
1150
|
+
let mem = GuestMemory::new(&mut memory, 1);
|
|
1151
|
+
let call = |oflags| PathOpen {
|
|
1152
|
+
dirflags: types::Lookupflags::empty(),
|
|
1153
|
+
oflags,
|
|
1154
|
+
rights_base: Rights::FD_READ,
|
|
1155
|
+
fdflags: Fdflags::empty(),
|
|
1156
|
+
};
|
|
1157
|
+
let unopened = 99;
|
|
1158
|
+
let path = GuestPtr::new((0, 1));
|
|
1159
|
+
assert_eq!(
|
|
1160
|
+
(
|
|
1161
|
+
errno(path_open(
|
|
1162
|
+
&mut table,
|
|
1163
|
+
&mem,
|
|
1164
|
+
unopened,
|
|
1165
|
+
path,
|
|
1166
|
+
&call(types::Oflags::CREAT | types::Oflags::DIRECTORY)
|
|
1167
|
+
)),
|
|
1168
|
+
errno(path_open(
|
|
1169
|
+
&mut table,
|
|
1170
|
+
&mem,
|
|
1171
|
+
unopened,
|
|
1172
|
+
path,
|
|
1173
|
+
&call(types::Oflags::empty())
|
|
1174
|
+
)),
|
|
1175
|
+
),
|
|
1176
|
+
(Some(Errno::Inval), Some(Errno::Mfile)),
|
|
1177
|
+
"path_open with no descriptor left through preview1 must answer EINVAL for O_CREAT \
|
|
1178
|
+
with O_DIRECTORY, and EMFILE from a descriptor number naming nothing"
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/// A directory listing `ok` and then `bad`, whatever `bad` is.
|
|
1183
|
+
struct ListsNames(&'static str);
|
|
1184
|
+
|
|
1185
|
+
impl kobako_runtime::filesystem::FileSystemType for ListsNames {
|
|
1186
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
1187
|
+
sb.d_make_root(kobako_runtime::vfs::Inode::new(
|
|
1188
|
+
&kobako_runtime::Kstat::new(kobako_runtime::FileType::Dir, 0),
|
|
1189
|
+
self,
|
|
1190
|
+
));
|
|
1191
|
+
Ok(())
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
impl InodeOperations for ListsNames {
|
|
1196
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
1197
|
+
Ok(Kstat::new(FileType::Dir, 0))
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
fn open(
|
|
1201
|
+
&self,
|
|
1202
|
+
_dentry: &Dentry,
|
|
1203
|
+
_options: &OpenHow,
|
|
1204
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
1205
|
+
Ok(Box::new(Self(self.0)))
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
impl FileOperations for ListsNames {
|
|
1210
|
+
fn iterate_shared(
|
|
1211
|
+
&mut self,
|
|
1212
|
+
dentry: &Dentry,
|
|
1213
|
+
ctx: &mut vfs::DirContext<'_>,
|
|
1214
|
+
) -> Result<(), FilesystemError> {
|
|
1215
|
+
let entries = vec![
|
|
1216
|
+
vfs::DirEntry::new("ok", FileType::RegFile),
|
|
1217
|
+
vfs::DirEntry::new(self.0, FileType::RegFile),
|
|
1218
|
+
];
|
|
1219
|
+
kobako_runtime::filesystem::Rdir::default().iterate(dentry, ctx, || Ok(entries))
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/// Each whole `dirent` in `bytes`, as its name and its `d_next`.
|
|
1224
|
+
fn dirents(bytes: &[u8]) -> Vec<(String, u64)> {
|
|
1225
|
+
let header = types::Dirent::guest_size() as usize;
|
|
1226
|
+
let mut seen = Vec::new();
|
|
1227
|
+
let mut at = 0;
|
|
1228
|
+
while at + header <= bytes.len() {
|
|
1229
|
+
let field = |from: usize, len: usize| &bytes[at + from..at + from + len];
|
|
1230
|
+
let d_next = u64::from_le_bytes(field(0, 8).try_into().expect("eight bytes"));
|
|
1231
|
+
let namlen = u32::from_le_bytes(field(16, 4).try_into().expect("four bytes")) as usize;
|
|
1232
|
+
if at + header + namlen > bytes.len() {
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
1235
|
+
let name = String::from_utf8_lossy(field(header, namlen)).into_owned();
|
|
1236
|
+
seen.push((name, d_next));
|
|
1237
|
+
at += header + namlen;
|
|
1238
|
+
}
|
|
1239
|
+
seen
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// The first buffer holds `.` and `..` exactly, so `..` learns where the
|
|
1243
|
+
// listing goes on only from the entry refused after it, as `filldir64`
|
|
1244
|
+
// writes `prev->d_off`, and the last entry of the second from where the
|
|
1245
|
+
// listing stopped, as `getdents64` sets it from `f_pos`.
|
|
1246
|
+
// @behavior RT-182
|
|
1247
|
+
#[test]
|
|
1248
|
+
fn each_listed_entry_names_where_the_listing_goes_on() {
|
|
1249
|
+
let mount = Mount::new(
|
|
1250
|
+
"/data",
|
|
1251
|
+
Filesystem::new(Arc::new(ListsNames("bb"))).expect("the instance must stand"),
|
|
1252
|
+
Access::Read,
|
|
1253
|
+
)
|
|
1254
|
+
.expect("/data is a guest path");
|
|
1255
|
+
let mut table =
|
|
1256
|
+
table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
|
|
1257
|
+
let header = types::Dirent::guest_size();
|
|
1258
|
+
let dots = 2 * header + 3;
|
|
1259
|
+
let mut bytes = vec![0u8; 256];
|
|
1260
|
+
let used = {
|
|
1261
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
1262
|
+
let mut mem = GuestMemory::new(&mut memory, 256);
|
|
1263
|
+
let first = readdir(&mut table, &mut mem, 3, GuestPtr::new(0), dots, 0);
|
|
1264
|
+
let rest = readdir(&mut table, &mut mem, 3, GuestPtr::new(128), 128, 2);
|
|
1265
|
+
(first.ok(), rest.ok())
|
|
1266
|
+
};
|
|
1267
|
+
let entry = |name: &str, d_next| (name.to_string(), d_next);
|
|
1268
|
+
assert_eq!(
|
|
1269
|
+
(
|
|
1270
|
+
used,
|
|
1271
|
+
dirents(&bytes[..dots as usize]),
|
|
1272
|
+
dirents(&bytes[128..128 + 2 * (header as usize + 2)])
|
|
1273
|
+
),
|
|
1274
|
+
(
|
|
1275
|
+
(Some(dots), Some(2 * (header + 2))),
|
|
1276
|
+
vec![entry(".", 1), entry("..", 2)],
|
|
1277
|
+
vec![entry("ok", 3), entry("bb", 4)],
|
|
1278
|
+
),
|
|
1279
|
+
"fd_readdir through preview1 must give each whole entry the cookie a listing goes on \
|
|
1280
|
+
from past it, the one filling the buffer included, and the last the position the \
|
|
1281
|
+
listing stopped at"
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// A built-in Filesystem cannot hold such a name, so only one that hands it
|
|
1286
|
+
// over shows the listing refuses it; Linux's `getdents64` answers what came
|
|
1287
|
+
// before it and leaves the name to the next call, whose cookie stands on it.
|
|
1288
|
+
// @behavior RT-212 RT-269
|
|
1289
|
+
#[test]
|
|
1290
|
+
fn a_listing_stops_at_a_name_no_directory_can_hold() {
|
|
1291
|
+
let listed = |bad: &'static str| {
|
|
1292
|
+
let mount = Mount::new(
|
|
1293
|
+
"/data",
|
|
1294
|
+
Filesystem::new(Arc::new(ListsNames(bad))).expect("the instance must stand"),
|
|
1295
|
+
Access::Read,
|
|
1296
|
+
)
|
|
1297
|
+
.expect("/data is a guest path");
|
|
1298
|
+
let mut table =
|
|
1299
|
+
table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
|
|
1300
|
+
let mut bytes = vec![0u8; 256];
|
|
1301
|
+
let (first, next) = {
|
|
1302
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
1303
|
+
let mut mem = GuestMemory::new(&mut memory, 256);
|
|
1304
|
+
let buf = GuestPtr::new(0);
|
|
1305
|
+
let first = readdir(&mut table, &mut mem, 3, buf, 256, 0).ok();
|
|
1306
|
+
(first, errno(readdir(&mut table, &mut mem, 3, buf, 256, 3)))
|
|
1307
|
+
};
|
|
1308
|
+
let handed_over = bytes.windows(bad.len().max(1)).any(|w| w == bad.as_bytes());
|
|
1309
|
+
(
|
|
1310
|
+
first.is_some_and(|used| used > 0),
|
|
1311
|
+
next,
|
|
1312
|
+
handed_over && !bad.is_empty(),
|
|
1313
|
+
)
|
|
1314
|
+
};
|
|
1315
|
+
assert_eq!(
|
|
1316
|
+
(listed("../escape"), listed("")),
|
|
1317
|
+
((true, Some(Errno::Io), false), (true, Some(Errno::Io), false)),
|
|
1318
|
+
"fd_readdir through preview1 of a Filesystem listing `ok` and then a name with a `/` or \
|
|
1319
|
+
an empty one must hand over what came before it, and answer EIO from where it stands, \
|
|
1320
|
+
handing over no such name"
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/// A directory that would make any link it is asked to.
|
|
1325
|
+
struct MakesLinks;
|
|
1326
|
+
|
|
1327
|
+
impl kobako_runtime::filesystem::FileSystemType for MakesLinks {
|
|
1328
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
1329
|
+
sb.d_make_root(kobako_runtime::vfs::Inode::new(
|
|
1330
|
+
&kobako_runtime::Kstat::new(kobako_runtime::FileType::Dir, 0),
|
|
1331
|
+
self,
|
|
1332
|
+
));
|
|
1333
|
+
Ok(())
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
impl InodeOperations for MakesLinks {
|
|
1338
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
1339
|
+
Ok(Kstat::new(FileType::Dir, 0))
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
fn lookup(&self, _dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
1343
|
+
Ok(Lookup::Absent)
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
fn symlink(&self, _dentry: &Dentry, _target: &str) -> Result<(), FilesystemError> {
|
|
1347
|
+
Ok(())
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// No built-in Filesystem makes a link, so only one that would shows the
|
|
1352
|
+
// call stopped here.
|
|
1353
|
+
// @behavior RT-202
|
|
1354
|
+
#[test]
|
|
1355
|
+
fn a_link_to_an_absolute_path_is_not_made() {
|
|
1356
|
+
let mount = Mount::new(
|
|
1357
|
+
"/data",
|
|
1358
|
+
Filesystem::new(Arc::new(MakesLinks)).expect("the instance must stand"),
|
|
1359
|
+
Access::ReadWrite,
|
|
1360
|
+
)
|
|
1361
|
+
.expect("/data is a guest path");
|
|
1362
|
+
let table = table_of(vec![mount], None).unwrap_or_else(|_| panic!("the table must make"));
|
|
1363
|
+
let mut bytes = *b"/trelative";
|
|
1364
|
+
let mut memory = wiggle::GuestMemory::Unshared(&mut bytes);
|
|
1365
|
+
let mem = GuestMemory::new(&mut memory, 64);
|
|
1366
|
+
let (absolute, relative, name) = (
|
|
1367
|
+
GuestPtr::new((0, 2)),
|
|
1368
|
+
GuestPtr::new((1, 1)),
|
|
1369
|
+
GuestPtr::new((2, 8)),
|
|
1370
|
+
);
|
|
1371
|
+
assert_eq!(
|
|
1372
|
+
(
|
|
1373
|
+
errno(symlink(&table, &mem, absolute, 3, name)),
|
|
1374
|
+
errno(symlink(&table, &mem, relative, 3, name)),
|
|
1375
|
+
),
|
|
1376
|
+
(Some(Errno::Perm), None),
|
|
1377
|
+
"path_symlink through preview1 to an absolute target must answer EPERM, as \
|
|
1378
|
+
wasi-testsuite requires, and to a relative one must reach the Filesystem"
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// The replaced file refuses its close, so only a renumber that moves the
|
|
1383
|
+
// source before closing, and ignores that answer, succeeds.
|
|
1384
|
+
// @behavior RT-114
|
|
1385
|
+
#[test]
|
|
1386
|
+
fn renumbering_onto_an_open_file_replaces_it_whatever_its_close_answers() {
|
|
1387
|
+
let mut table = standard_streams();
|
|
658
1388
|
let from = table.push(open_file()).expect("a descriptor to move");
|
|
659
1389
|
let to = table.push(open_file()).expect("a descriptor to move onto");
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
renumber(&mut table, from, to),
|
|
663
|
-
|
|
1390
|
+
assert_eq!(
|
|
1391
|
+
(
|
|
1392
|
+
errno(renumber(&mut table, from, to)),
|
|
1393
|
+
table.get(from).is_none(),
|
|
1394
|
+
table.get(to).is_some()
|
|
664
1395
|
),
|
|
665
|
-
|
|
1396
|
+
(None, true, true),
|
|
1397
|
+
"fd_renumber through preview1 onto a file whose Filesystem refuses the close must \
|
|
1398
|
+
succeed, the source now standing at the target and its own number free, as Linux's \
|
|
1399
|
+
do_dup2 ignores filp_close of the file it replaced"
|
|
666
1400
|
);
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// @behavior RT-309
|
|
1404
|
+
#[test]
|
|
1405
|
+
fn renumbering_onto_a_number_not_open_answers_ebadf_and_keeps_the_source() {
|
|
1406
|
+
let mut table = standard_streams();
|
|
1407
|
+
let from = table.push(open_file()).expect("a descriptor to move");
|
|
1408
|
+
let to = from + 1;
|
|
1409
|
+
assert_eq!(
|
|
1410
|
+
(
|
|
1411
|
+
errno(renumber(&mut table, from, to)),
|
|
1412
|
+
table.get(from).is_some(),
|
|
1413
|
+
table.get(to).is_none()
|
|
1414
|
+
),
|
|
1415
|
+
(Some(Errno::Badf), true, true),
|
|
1416
|
+
"fd_renumber through preview1 onto a number nothing stands at must answer EBADF and \
|
|
1417
|
+
leave the source at its own number, as wasmtime-wasi replaces only an open one"
|
|
670
1418
|
);
|
|
671
1419
|
}
|
|
672
1420
|
}
|