kobako 0.28.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 +377 -0
- data/Cargo.lock +181 -154
- data/README.md +99 -459
- data/ROADMAP.md +7 -1
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +354 -0
- data/crates/kobako-runtime/Cargo.toml +38 -7
- data/crates/kobako-runtime/README.md +17 -8
- data/crates/kobako-runtime/src/dispatch.rs +9 -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/bind/lookup.rs +236 -0
- 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 +301 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
- 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 +26 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +16 -1
- 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 +105 -0
- data/crates/kobako-runtime/src/profile.rs +140 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- 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/src/yielder.rs +11 -6
- 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 +23 -0
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/abi.rs +1 -1
- 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 +4 -3
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
- data/crates/kobako-transport/src/envelope/reply.rs +34 -16
- data/crates/kobako-transport/src/envelope/request.rs +19 -9
- 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 +265 -0
- data/crates/kobako-wasmtime/Cargo.toml +25 -19
- data/crates/kobako-wasmtime/README.md +13 -7
- data/crates/kobako-wasmtime/src/abi.rs +75 -49
- data/crates/kobako-wasmtime/src/ambient.rs +64 -6
- data/crates/kobako-wasmtime/src/cache.rs +188 -77
- data/crates/kobako-wasmtime/src/config.rs +77 -5
- data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
- data/crates/kobako-wasmtime/src/driver.rs +70 -135
- 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 +52 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
- data/crates/kobako-wasmtime/src/invocation.rs +43 -74
- data/crates/kobako-wasmtime/src/lib.rs +5 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
- 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 +512 -0
- data/crates/kobako-wasmtime/src/trap.rs +45 -25
- data/crates/kobako-wasmtime/src/wasi.rs +333 -0
- data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
- data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -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 +4 -2
- data/ext/kobako/src/runtime/bridge.rs +154 -81
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +67 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
- data/ext/kobako/src/runtime/filesystem.rs +208 -0
- data/ext/kobako/src/runtime/gvl.rs +49 -8
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +125 -199
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +19 -0
- data/lib/kobako/catalog/services.rb +13 -2
- data/lib/kobako/catalog/snippets.rb +11 -15
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +10 -9
- data/lib/kobako/codec/ext_types.rb +4 -6
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +40 -13
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +69 -0
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +6 -6
- data/lib/kobako/sandbox.rb +55 -27
- data/lib/kobako/sandbox_options.rb +39 -13
- data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/snippet.rb +1 -1
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/dispatcher.rb +33 -29
- data/lib/kobako/transport/exposure.rb +7 -9
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +9 -31
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +35 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +24 -0
- data/sig/kobako/catalog/services.rbs +5 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/catalog.rbs +1 -0
- 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/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +3 -2
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +93 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +3 -3
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +9 -5
- data/sig/kobako/sandbox_options.rbs +25 -3
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +4 -4
- data/sig/kobako/transport/exposure.rbs +2 -2
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +3 -3
- metadata +116 -15
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
- data/crates/kobako-wasmtime/src/io.rs +0 -286
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,1420 @@
|
|
|
1
|
+
//! The 21 `fd_*` calls over kobako's descriptor table.
|
|
2
|
+
//!
|
|
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
|
+
|
|
16
|
+
use super::errno::{Answer, Errno};
|
|
17
|
+
use super::memory::GuestMemory;
|
|
18
|
+
use super::path::retry_eagain;
|
|
19
|
+
use super::types::{self, Fdflags, Filetype, Fstflags, Rights};
|
|
20
|
+
use crate::descriptors::{Descriptor, Descriptors};
|
|
21
|
+
|
|
22
|
+
/// What a preopen allows itself: every path call, as wasmtime-wasi hands
|
|
23
|
+
/// most userlands.
|
|
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
|
+
}
|
|
42
|
+
|
|
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
|
|
61
|
+
}
|
|
62
|
+
|
|
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())
|
|
67
|
+
}
|
|
68
|
+
|
|
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()),
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
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()),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
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),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
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(()),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
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,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
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),
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
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
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
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(()),
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
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)))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
pub(crate) fn advise(table: &mut Descriptors, fd: u32, offset: u64, len: u64) -> Answer {
|
|
178
|
+
Ok(file_mut(table, fd)?.fadvise(offset, len)?)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
pub(crate) fn allocate(table: &mut Descriptors, fd: u32, offset: u64, len: u64) -> Answer {
|
|
182
|
+
Ok(file_mut(table, fd)?.fallocate(offset, len)?)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
pub(crate) fn close(table: &mut Descriptors, fd: u32) -> Answer {
|
|
186
|
+
Ok(table.remove(fd).ok_or(Errno::Badf)?.file.close()?)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
pub(crate) fn sync(table: &mut Descriptors, fd: u32, datasync: bool) -> Answer {
|
|
190
|
+
Ok(file_mut(table, fd)?.fsync(datasync)?)
|
|
191
|
+
}
|
|
192
|
+
|
|
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;
|
|
216
|
+
}
|
|
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;
|
|
222
|
+
}
|
|
223
|
+
if !file.writable() {
|
|
224
|
+
rights -= Rights::FD_WRITE;
|
|
225
|
+
}
|
|
226
|
+
(rights, rights)
|
|
227
|
+
};
|
|
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
|
+
);
|
|
241
|
+
Ok(())
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// Rights only narrow at open, so a later change is unsupported.
|
|
245
|
+
pub(crate) fn fdstat_set_rights(table: &Descriptors, fd: u32) -> Answer {
|
|
246
|
+
refuse_stream(table, fd)?;
|
|
247
|
+
Err(Errno::Notsup.into())
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
pub(crate) fn filestat_get(table: &Descriptors, fd: u32) -> Answer<types::Filestat> {
|
|
251
|
+
Ok(to_filestat(table, descriptor(table, fd)?.file.getattr()?))
|
|
252
|
+
}
|
|
253
|
+
|
|
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`.
|
|
256
|
+
pub(crate) fn filestat_set_size(table: &mut Descriptors, fd: u32, size: u64) -> Answer {
|
|
257
|
+
if i64::try_from(size).is_err() {
|
|
258
|
+
return Err(Errno::Inval.into());
|
|
259
|
+
}
|
|
260
|
+
Ok(file_mut(table, fd)?.truncate(size)?)
|
|
261
|
+
}
|
|
262
|
+
|
|
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
|
+
})?)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
pub(crate) fn pread(
|
|
272
|
+
table: &mut Descriptors,
|
|
273
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
274
|
+
fd: u32,
|
|
275
|
+
iovs: types::IovecArray,
|
|
276
|
+
offset: u64,
|
|
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)
|
|
282
|
+
}
|
|
283
|
+
|
|
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()),
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
pub(crate) fn prestat_dir_name(
|
|
294
|
+
table: &Descriptors,
|
|
295
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
296
|
+
fd: u32,
|
|
297
|
+
path: GuestPtr<u8>,
|
|
298
|
+
path_len: u32,
|
|
299
|
+
) -> Answer {
|
|
300
|
+
match &descriptor(table, fd)?.preopen_path {
|
|
301
|
+
Some(at) => {
|
|
302
|
+
let name = at.as_bytes();
|
|
303
|
+
if name.len() > path_len as usize {
|
|
304
|
+
return Err(Errno::Nametoolong.into());
|
|
305
|
+
}
|
|
306
|
+
mem.write_bytes(path, name)
|
|
307
|
+
}
|
|
308
|
+
None => Err(Errno::Notdir.into()),
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
pub(crate) fn pwrite(
|
|
313
|
+
table: &mut Descriptors,
|
|
314
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
315
|
+
fd: u32,
|
|
316
|
+
ciovs: types::CiovecArray,
|
|
317
|
+
offset: u64,
|
|
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)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
pub(crate) fn read(
|
|
326
|
+
table: &mut Descriptors,
|
|
327
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
328
|
+
fd: u32,
|
|
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])?;
|
|
372
|
+
}
|
|
373
|
+
*used += take as u32;
|
|
374
|
+
whole &= take == part.len();
|
|
375
|
+
}
|
|
376
|
+
Ok(whole.then_some(at))
|
|
377
|
+
}
|
|
378
|
+
|
|
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.
|
|
394
|
+
pub(crate) fn readdir(
|
|
395
|
+
table: &mut Descriptors,
|
|
396
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
397
|
+
fd: u32,
|
|
398
|
+
start: GuestPtr<u8>,
|
|
399
|
+
capacity: u32,
|
|
400
|
+
cookie: u64,
|
|
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
|
+
}
|
|
411
|
+
let mut used: u32 = 0;
|
|
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()
|
|
426
|
+
}
|
|
427
|
+
Err(error) => {
|
|
428
|
+
failed = Some(error);
|
|
429
|
+
false
|
|
430
|
+
}
|
|
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())?;
|
|
438
|
+
}
|
|
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),
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
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.
|
|
451
|
+
pub(crate) fn renumber(table: &mut Descriptors, from: u32, to: u32) -> Answer {
|
|
452
|
+
if table.get(from).is_none() || table.get(to).is_none() {
|
|
453
|
+
return Err(Errno::Badf.into());
|
|
454
|
+
}
|
|
455
|
+
if from == to {
|
|
456
|
+
return Ok(());
|
|
457
|
+
}
|
|
458
|
+
let moved = table.remove(from).expect("found open above");
|
|
459
|
+
let replaced = table.remove(to).expect("found open above");
|
|
460
|
+
table.place(to, moved);
|
|
461
|
+
let _ = replaced.file.close();
|
|
462
|
+
Ok(())
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
pub(crate) fn seek(
|
|
466
|
+
table: &mut Descriptors,
|
|
467
|
+
fd: u32,
|
|
468
|
+
offset: i64,
|
|
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,
|
|
475
|
+
};
|
|
476
|
+
Ok(seekable_file(table, fd)?.llseek(offset, whence)?)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
pub(crate) fn tell(table: &mut Descriptors, fd: u32) -> Answer<u64> {
|
|
480
|
+
Ok(seekable_file(table, fd)?.llseek(0, Whence::Cur)?)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
pub(crate) fn write(
|
|
484
|
+
table: &mut Descriptors,
|
|
485
|
+
mem: &mut GuestMemory<'_, '_>,
|
|
486
|
+
fd: u32,
|
|
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)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
#[cfg(test)]
|
|
496
|
+
mod tests {
|
|
497
|
+
//! Witness that renumbering onto an open file closes it as `fd_close`
|
|
498
|
+
//! does, so its Filesystem learns of the close and may refuse it.
|
|
499
|
+
use std::sync::Arc;
|
|
500
|
+
|
|
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
|
+
}
|
|
558
|
+
|
|
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
|
+
}
|
|
584
|
+
|
|
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.
|
|
608
|
+
struct RefusesClose;
|
|
609
|
+
|
|
610
|
+
impl FileOperations for RefusesClose {
|
|
611
|
+
fn flush(&mut self) -> Result<(), FilesystemError> {
|
|
612
|
+
Err(FilesystemError::PermissionDenied)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
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
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
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(())
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
impl InodeOperations for Holds {
|
|
643
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
644
|
+
Ok(Kstat::new(FileType::Dir, 0))
|
|
645
|
+
}
|
|
646
|
+
|
|
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
|
+
)))
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
fn open(
|
|
656
|
+
&self,
|
|
657
|
+
_dentry: &Dentry,
|
|
658
|
+
_options: &OpenHow,
|
|
659
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
660
|
+
Ok((self.1)())
|
|
661
|
+
}
|
|
662
|
+
}
|
|
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
|
+
|
|
689
|
+
fn open_file() -> Descriptor {
|
|
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)
|
|
754
|
+
),
|
|
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
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
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
|
|
1021
|
+
#[test]
|
|
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"
|
|
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();
|
|
1388
|
+
let from = table.push(open_file()).expect("a descriptor to move");
|
|
1389
|
+
let to = table.push(open_file()).expect("a descriptor to move onto");
|
|
1390
|
+
assert_eq!(
|
|
1391
|
+
(
|
|
1392
|
+
errno(renumber(&mut table, from, to)),
|
|
1393
|
+
table.get(from).is_none(),
|
|
1394
|
+
table.get(to).is_some()
|
|
1395
|
+
),
|
|
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"
|
|
1400
|
+
);
|
|
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"
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
}
|