kobako 0.28.0 → 0.29.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 +170 -0
- data/Cargo.lock +111 -82
- data/README.md +154 -36
- data/ROADMAP.md +5 -0
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +86 -0
- data/crates/kobako-runtime/Cargo.toml +27 -6
- data/crates/kobako-runtime/README.md +13 -8
- data/crates/kobako-runtime/src/dispatch.rs +8 -0
- data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
- data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
- data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
- data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
- data/crates/kobako-runtime/src/filesystem.rs +392 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/lib.rs +11 -1
- data/crates/kobako-runtime/src/mount.rs +178 -0
- data/crates/kobako-runtime/src/profile.rs +4 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- data/crates/kobako-runtime/src/yielder.rs +11 -6
- data/crates/kobako-transport/CHANGELOG.md +17 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +1 -1
- data/crates/kobako-transport/src/abi.rs +1 -1
- data/crates/kobako-transport/src/envelope/fault.rs +2 -1
- data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
- data/crates/kobako-transport/src/envelope/reply.rs +32 -14
- data/crates/kobako-transport/src/envelope/request.rs +18 -8
- data/crates/kobako-wasmtime/CHANGELOG.md +119 -0
- data/crates/kobako-wasmtime/Cargo.toml +21 -16
- data/crates/kobako-wasmtime/README.md +5 -3
- data/crates/kobako-wasmtime/src/abi.rs +66 -32
- data/crates/kobako-wasmtime/src/ambient.rs +63 -4
- data/crates/kobako-wasmtime/src/cache.rs +185 -74
- data/crates/kobako-wasmtime/src/config.rs +58 -1
- data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
- data/crates/kobako-wasmtime/src/dispatch.rs +47 -29
- data/crates/kobako-wasmtime/src/driver.rs +46 -44
- data/crates/kobako-wasmtime/src/guest_mem.rs +13 -7
- data/crates/kobako-wasmtime/src/imports.rs +46 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +17 -45
- data/crates/kobako-wasmtime/src/invocation.rs +35 -46
- data/crates/kobako-wasmtime/src/io.rs +5 -202
- data/crates/kobako-wasmtime/src/lib.rs +4 -0
- data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
- data/crates/kobako-wasmtime/src/preview1.rs +461 -0
- data/crates/kobako-wasmtime/src/trap.rs +39 -19
- data/crates/kobako-wasmtime/src/wasi.rs +317 -0
- data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +3 -2
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +4 -3
- data/crates/kobako-wasmtime/tests/support/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +1 -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/escape.rs +53 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
- data/ext/kobako/src/runtime/filesystem.rs +236 -0
- data/ext/kobako/src/runtime/gvl.rs +7 -1
- 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 +120 -199
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/mount.rb +27 -0
- data/lib/kobako/catalog/services.rb +11 -0
- data/lib/kobako/catalog/snippets.rb +5 -9
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +1 -0
- data/lib/kobako/codec/ext_types.rb +1 -1
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +38 -11
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +38 -0
- data/lib/kobako/pool.rb +2 -2
- data/lib/kobako/sandbox.rb +40 -12
- data/lib/kobako/sandbox_options.rb +39 -12
- data/lib/kobako/snippet/{binary.rb → bytecode.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 +30 -26
- data/lib/kobako/transport/exposure.rb +5 -7
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/yielder.rb +8 -30
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/mount.rbs +28 -0
- data/sig/kobako/catalog/services.rbs +4 -0
- data/sig/kobako/catalog/snippets.rbs +1 -1
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +2 -0
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +88 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/pool.rbs +1 -1
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +7 -1
- data/sig/kobako/sandbox_options.rbs +24 -2
- 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 +3 -3
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +2 -2
- metadata +60 -7
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
//! The 10 `path_*` calls. Each resolves a path beneath a directory
|
|
2
|
+
//! descriptor, asks its Mount what the guest may do there, and asks the
|
|
3
|
+
//! Filesystem. Links and timestamps are no Filesystem's, so those calls
|
|
4
|
+
//! are unsupported.
|
|
5
|
+
|
|
6
|
+
use std::sync::Arc;
|
|
7
|
+
|
|
8
|
+
use kobako_runtime::filesystem::{
|
|
9
|
+
Access, FileType, Filesystem, FilesystemError, OpenOptions, Rename,
|
|
10
|
+
};
|
|
11
|
+
use kobako_runtime::mount::Mount;
|
|
12
|
+
|
|
13
|
+
use super::args::{
|
|
14
|
+
self, FDFLAGS_APPEND, FDFLAGS_NONBLOCK, FDFLAGS_SYNCHRONIZED, RIGHTS_FD_READ, RIGHTS_FD_WRITE,
|
|
15
|
+
};
|
|
16
|
+
use super::beneath::{resolve, ResolvedPath};
|
|
17
|
+
use super::errno::{Answer, Errno};
|
|
18
|
+
use super::fd::write_metadata;
|
|
19
|
+
use super::memory::GuestMemory;
|
|
20
|
+
use crate::descriptors::{Descriptor, Descriptors, Identity, OpenDirectory, OpenFile};
|
|
21
|
+
|
|
22
|
+
const LOOKUPFLAGS_SYMLINK_FOLLOW: u32 = 1 << 0;
|
|
23
|
+
const OFLAGS_CREAT: u32 = 1 << 0;
|
|
24
|
+
const OFLAGS_DIRECTORY: u32 = 1 << 1;
|
|
25
|
+
const OFLAGS_EXCL: u32 = 1 << 2;
|
|
26
|
+
const OFLAGS_TRUNC: u32 = 1 << 3;
|
|
27
|
+
|
|
28
|
+
/// The flags `path_open` takes besides the path itself.
|
|
29
|
+
pub(crate) struct OpenFlags {
|
|
30
|
+
pub(crate) dirflags: u32,
|
|
31
|
+
pub(crate) oflags: u32,
|
|
32
|
+
pub(crate) rights_base: u64,
|
|
33
|
+
pub(crate) rights_inheriting: u64,
|
|
34
|
+
pub(crate) fdflags: u32,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// The directory `fd` stands for; any other descriptor is `BADF`.
|
|
38
|
+
fn directory(table: &Descriptors, fd: u32) -> Answer<&OpenDirectory> {
|
|
39
|
+
match table.get(fd).ok_or(Errno::Badf)? {
|
|
40
|
+
Descriptor::Directory(dir) => Ok(dir),
|
|
41
|
+
Descriptor::Stdin(_) | Descriptor::Channel(_) | Descriptor::File(_) => {
|
|
42
|
+
Err(Errno::Badf.into())
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Whether the guest may reach `path` with `access`, as its Mount
|
|
48
|
+
/// decides. A Mount that follows no link refuses one first.
|
|
49
|
+
fn judge(mount: &Mount, path: &str, access: Access, follows_last: bool) -> Answer {
|
|
50
|
+
if !mount.follows_links() {
|
|
51
|
+
refuse_links(mount, path, follows_last)?;
|
|
52
|
+
}
|
|
53
|
+
Ok(mount.check(path, access)?)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Refuse a link standing on the way to `path`, and at its end when the
|
|
57
|
+
/// call would follow it, before the Mount judges a path the link would
|
|
58
|
+
/// lead elsewhere.
|
|
59
|
+
fn refuse_links(mount: &Mount, path: &str, follows_last: bool) -> Answer {
|
|
60
|
+
let ends = path.match_indices('/').map(|(end, _)| end);
|
|
61
|
+
for end in ends.chain(follows_last.then_some(path.len())) {
|
|
62
|
+
match mount.filesystem().symlink_metadata(&path[..end]) {
|
|
63
|
+
Ok(metadata) if metadata.file_type == FileType::Symlink => {
|
|
64
|
+
return Err(Errno::Loop.into())
|
|
65
|
+
}
|
|
66
|
+
Ok(_) => {}
|
|
67
|
+
// What is missing holds no link; the call answers for it.
|
|
68
|
+
Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => break,
|
|
69
|
+
Err(err) => return Err(err.into()),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
Ok(())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// A path beneath `fd`, relative to its mount's root, for a call acting
|
|
76
|
+
/// on what stands there rather than where a link leads: refused when it
|
|
77
|
+
/// ends in `/` and names a file or a link.
|
|
78
|
+
fn beneath<'a>(
|
|
79
|
+
table: &'a Descriptors,
|
|
80
|
+
mem: &GuestMemory,
|
|
81
|
+
fd: u32,
|
|
82
|
+
path: (u32, u32),
|
|
83
|
+
) -> Answer<(&'a OpenDirectory, String)> {
|
|
84
|
+
let dir = directory(table, fd)?;
|
|
85
|
+
let path = resolve(dir, &mem.path(path.0, path.1)?)?.check_directory(
|
|
86
|
+
dir.mount.filesystem().as_ref(),
|
|
87
|
+
<dyn Filesystem>::symlink_metadata,
|
|
88
|
+
)?;
|
|
89
|
+
Ok((dir, path))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// A trailing `/` asks for the directory being made, so it is no
|
|
93
|
+
/// refusal here, as `mkdir(2)` takes it.
|
|
94
|
+
pub(crate) fn create_directory(
|
|
95
|
+
table: &Descriptors,
|
|
96
|
+
mem: &GuestMemory,
|
|
97
|
+
fd: u32,
|
|
98
|
+
path: (u32, u32),
|
|
99
|
+
) -> Answer {
|
|
100
|
+
let dir = directory(table, fd)?;
|
|
101
|
+
let path = resolve(dir, &mem.path(path.0, path.1)?)?.path;
|
|
102
|
+
judge(&dir.mount, &path, Access::ReadWrite, false)?;
|
|
103
|
+
Ok(dir.mount.filesystem().create_dir(&path)?)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pub(crate) fn filestat_get(
|
|
107
|
+
table: &Descriptors,
|
|
108
|
+
mem: &mut GuestMemory,
|
|
109
|
+
fd: u32,
|
|
110
|
+
flags: u32,
|
|
111
|
+
path: (u32, u32),
|
|
112
|
+
out: u32,
|
|
113
|
+
) -> Answer {
|
|
114
|
+
args::flags(flags, args::LOOKUPFLAGS)?;
|
|
115
|
+
let dir = directory(table, fd)?;
|
|
116
|
+
let resolved = resolve(dir, &mem.path(path.0, path.1)?)?;
|
|
117
|
+
// A trailing `/` resolves a link standing there, as Linux's `lstat` does.
|
|
118
|
+
let follow = flags & LOOKUPFLAGS_SYMLINK_FOLLOW != 0 || resolved.names_directory;
|
|
119
|
+
let resolved = if follow {
|
|
120
|
+
resolved.follow_last(dir, false)?
|
|
121
|
+
} else {
|
|
122
|
+
resolved
|
|
123
|
+
};
|
|
124
|
+
let filesystem = dir.mount.filesystem();
|
|
125
|
+
let path = resolved.check_directory(filesystem.as_ref(), <dyn Filesystem>::metadata)?;
|
|
126
|
+
let metadata = if follow {
|
|
127
|
+
filesystem.metadata(&path)?
|
|
128
|
+
} else {
|
|
129
|
+
filesystem.symlink_metadata(&path)?
|
|
130
|
+
};
|
|
131
|
+
write_metadata(mem, out, metadata)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// A Filesystem keeps no timestamps of the guest's choosing.
|
|
135
|
+
pub(crate) fn filestat_set_times(
|
|
136
|
+
table: &Descriptors,
|
|
137
|
+
mem: &GuestMemory,
|
|
138
|
+
fd: u32,
|
|
139
|
+
flags: u32,
|
|
140
|
+
path: (u32, u32),
|
|
141
|
+
fst_flags: u32,
|
|
142
|
+
) -> Answer {
|
|
143
|
+
args::flags(flags, args::LOOKUPFLAGS)?;
|
|
144
|
+
args::fstflags(fst_flags)?;
|
|
145
|
+
beneath(table, mem, fd, path)?;
|
|
146
|
+
Err(Errno::Notsup.into())
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// A Filesystem holds no links, and answering as if one were made would
|
|
150
|
+
/// leave the guest believing it was.
|
|
151
|
+
pub(crate) fn link(
|
|
152
|
+
table: &Descriptors,
|
|
153
|
+
mem: &GuestMemory,
|
|
154
|
+
old_fd: u32,
|
|
155
|
+
old_flags: u32,
|
|
156
|
+
old_path: (u32, u32),
|
|
157
|
+
new_fd: u32,
|
|
158
|
+
new_path: (u32, u32),
|
|
159
|
+
) -> Answer {
|
|
160
|
+
args::flags(old_flags, args::LOOKUPFLAGS)?;
|
|
161
|
+
directory(table, old_fd)?;
|
|
162
|
+
directory(table, new_fd)?;
|
|
163
|
+
mem.path(old_path.0, old_path.1)?;
|
|
164
|
+
mem.path(new_path.0, new_path.1)?;
|
|
165
|
+
Err(Errno::Notsup.into())
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pub(crate) fn open(
|
|
169
|
+
table: &mut Descriptors,
|
|
170
|
+
mem: &mut GuestMemory,
|
|
171
|
+
fd: u32,
|
|
172
|
+
path: (u32, u32),
|
|
173
|
+
flags: &OpenFlags,
|
|
174
|
+
out: u32,
|
|
175
|
+
) -> Answer {
|
|
176
|
+
args::flags(flags.dirflags, args::LOOKUPFLAGS)?;
|
|
177
|
+
args::flags(flags.oflags, args::OFLAGS)?;
|
|
178
|
+
args::flags(flags.rights_base, args::RIGHTS)?;
|
|
179
|
+
args::flags(flags.rights_inheriting, args::RIGHTS)?;
|
|
180
|
+
args::flags(flags.fdflags, args::FDFLAGS)?;
|
|
181
|
+
let path = mem.path(path.0, path.1)?;
|
|
182
|
+
let dir = match table.get(fd).ok_or(Errno::Badf)? {
|
|
183
|
+
Descriptor::Directory(dir) => dir,
|
|
184
|
+
Descriptor::File(_) => return Err(Errno::Notdir.into()),
|
|
185
|
+
Descriptor::Stdin(_) | Descriptor::Channel(_) => return Err(Errno::Badf.into()),
|
|
186
|
+
};
|
|
187
|
+
let oflag = |bit| flags.oflags & bit != 0;
|
|
188
|
+
// A Filesystem makes no write durable on its own schedule, and every
|
|
189
|
+
// call waits for its answer, so an open asking otherwise is
|
|
190
|
+
// unsupported rather than quietly granted.
|
|
191
|
+
if flags.fdflags & (FDFLAGS_SYNCHRONIZED | FDFLAGS_NONBLOCK) != 0 {
|
|
192
|
+
return Err(Errno::Notsup.into());
|
|
193
|
+
}
|
|
194
|
+
if oflag(OFLAGS_DIRECTORY) && oflag(OFLAGS_CREAT | OFLAGS_EXCL | OFLAGS_TRUNC) {
|
|
195
|
+
return Err(Errno::Inval.into());
|
|
196
|
+
}
|
|
197
|
+
let resolved = resolve(dir, &path)?;
|
|
198
|
+
// A path ending in `/` asks for a directory, which cannot be created
|
|
199
|
+
// as a file.
|
|
200
|
+
if path.ends_with('/') && oflag(OFLAGS_CREAT) {
|
|
201
|
+
return Err(Errno::Isdir.into());
|
|
202
|
+
}
|
|
203
|
+
// A trailing `/` resolves a link standing there, as Linux's `open` does.
|
|
204
|
+
let follow = flags.dirflags & LOOKUPFLAGS_SYMLINK_FOLLOW != 0 || resolved.names_directory;
|
|
205
|
+
let resolved = if follow {
|
|
206
|
+
resolved.follow_last(dir, oflag(OFLAGS_CREAT))?
|
|
207
|
+
} else {
|
|
208
|
+
resolved
|
|
209
|
+
};
|
|
210
|
+
let path =
|
|
211
|
+
resolved.check_directory(dir.mount.filesystem().as_ref(), <dyn Filesystem>::metadata)?;
|
|
212
|
+
let mount = Arc::clone(&dir.mount);
|
|
213
|
+
if table.is_full() {
|
|
214
|
+
return Err(Errno::Mfile.into());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let read = flags.rights_base & RIGHTS_FD_READ != 0;
|
|
218
|
+
let write = flags.rights_base & RIGHTS_FD_WRITE != 0;
|
|
219
|
+
if !mount.follows_links() {
|
|
220
|
+
refuse_links(&mount, &path, follow)?;
|
|
221
|
+
}
|
|
222
|
+
let looked_up = if follow {
|
|
223
|
+
mount.filesystem().metadata(&path)
|
|
224
|
+
} else {
|
|
225
|
+
mount.filesystem().symlink_metadata(&path)
|
|
226
|
+
};
|
|
227
|
+
let (existing, identity) = match looked_up {
|
|
228
|
+
Ok(metadata) => (Some(metadata.file_type), Identity::of(&metadata)),
|
|
229
|
+
Err(FilesystemError::NotFound) => (None, None),
|
|
230
|
+
Err(err) => return Err(err.into()),
|
|
231
|
+
};
|
|
232
|
+
// What stands at the path answers before any access is judged, as
|
|
233
|
+
// Linux's `open` does on a read-only mount.
|
|
234
|
+
let refusal = match existing {
|
|
235
|
+
None if !oflag(OFLAGS_CREAT) => Some(Errno::Noent),
|
|
236
|
+
Some(_) if oflag(OFLAGS_CREAT) && oflag(OFLAGS_EXCL) => Some(Errno::Exist),
|
|
237
|
+
Some(FileType::Directory) if write || oflag(OFLAGS_TRUNC | OFLAGS_CREAT) => {
|
|
238
|
+
Some(Errno::Isdir)
|
|
239
|
+
}
|
|
240
|
+
Some(FileType::Directory) | None => None,
|
|
241
|
+
Some(_) if oflag(OFLAGS_DIRECTORY) => Some(Errno::Notdir),
|
|
242
|
+
Some(_) => None,
|
|
243
|
+
};
|
|
244
|
+
if let Some(errno) = refusal {
|
|
245
|
+
return Err(errno.into());
|
|
246
|
+
}
|
|
247
|
+
// Creating changes the Mount only where nothing stands yet, so the
|
|
248
|
+
// Mount judges the open once, knowing what it does.
|
|
249
|
+
let changes = write || oflag(OFLAGS_TRUNC) || (oflag(OFLAGS_CREAT) && existing.is_none());
|
|
250
|
+
let access = if changes {
|
|
251
|
+
Access::ReadWrite
|
|
252
|
+
} else {
|
|
253
|
+
Access::Read
|
|
254
|
+
};
|
|
255
|
+
mount.check(&path, access)?;
|
|
256
|
+
let descriptor = match existing {
|
|
257
|
+
Some(FileType::Directory) => Descriptor::Directory(OpenDirectory {
|
|
258
|
+
_hold: mount.filesystem().hold_directory(&path)?,
|
|
259
|
+
mount,
|
|
260
|
+
path,
|
|
261
|
+
identity,
|
|
262
|
+
preopen: false,
|
|
263
|
+
listing: None,
|
|
264
|
+
}),
|
|
265
|
+
Some(_) | None => {
|
|
266
|
+
let mut options = OpenOptions::default();
|
|
267
|
+
options.read = read;
|
|
268
|
+
options.write = write;
|
|
269
|
+
// A file already there is opened as it is, so a host that opens
|
|
270
|
+
// only to create never asks for more than the guest.
|
|
271
|
+
options.create = oflag(OFLAGS_CREAT) && existing.is_none();
|
|
272
|
+
options.create_new = oflag(OFLAGS_CREAT) && oflag(OFLAGS_EXCL);
|
|
273
|
+
options.truncate = oflag(OFLAGS_TRUNC);
|
|
274
|
+
options.nofollow = !follow;
|
|
275
|
+
Descriptor::File(OpenFile {
|
|
276
|
+
file: mount.filesystem().open(&path, &options)?,
|
|
277
|
+
position: 0,
|
|
278
|
+
append: flags.fdflags & FDFLAGS_APPEND != 0,
|
|
279
|
+
readable: read,
|
|
280
|
+
writable: write,
|
|
281
|
+
mount,
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
let opened = table.push(descriptor).ok_or(Errno::Mfile)?;
|
|
286
|
+
mem.write_u32(out, opened)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// A Filesystem holds no links to read.
|
|
290
|
+
pub(crate) fn readlink(
|
|
291
|
+
table: &Descriptors,
|
|
292
|
+
mem: &GuestMemory,
|
|
293
|
+
fd: u32,
|
|
294
|
+
path: (u32, u32),
|
|
295
|
+
) -> Answer {
|
|
296
|
+
beneath(table, mem, fd, path)?;
|
|
297
|
+
Err(Errno::Notsup.into())
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// Refuse a guest path ending in `.` or `..` as `refusal`, since it names
|
|
301
|
+
/// no entry a call may remove or move. Linux looks up what stands before
|
|
302
|
+
/// it first, so a missing or non-directory parent answers as itself.
|
|
303
|
+
fn refuse_dot_end(dir: &OpenDirectory, path: &str, refusal: Errno) -> Answer {
|
|
304
|
+
let trimmed = path.trim_end_matches('/');
|
|
305
|
+
let (parent, last) = trimmed.rsplit_once('/').unwrap_or(("", trimmed));
|
|
306
|
+
if !matches!(last, "." | "..") {
|
|
307
|
+
return Ok(());
|
|
308
|
+
}
|
|
309
|
+
if !parent.is_empty() {
|
|
310
|
+
let parent = resolve(dir, parent)?.path;
|
|
311
|
+
if dir.mount.filesystem().metadata(&parent)?.file_type != FileType::Directory {
|
|
312
|
+
return Err(Errno::Notdir.into());
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
Err(refusal.into())
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/// A Mount's root is where the guest stands, not a directory it may
|
|
319
|
+
/// remove or move; each call refuses it as Linux refuses its own root.
|
|
320
|
+
fn refuse_root(path: &str, refusal: Errno) -> Answer {
|
|
321
|
+
if path.is_empty() {
|
|
322
|
+
return Err(refusal.into());
|
|
323
|
+
}
|
|
324
|
+
Ok(())
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
pub(crate) fn remove_directory(
|
|
328
|
+
table: &Descriptors,
|
|
329
|
+
mem: &GuestMemory,
|
|
330
|
+
fd: u32,
|
|
331
|
+
path: (u32, u32),
|
|
332
|
+
) -> Answer {
|
|
333
|
+
refuse_dot_end(
|
|
334
|
+
directory(table, fd)?,
|
|
335
|
+
&mem.path(path.0, path.1)?,
|
|
336
|
+
Errno::Inval,
|
|
337
|
+
)?;
|
|
338
|
+
let (dir, path) = beneath(table, mem, fd, path)?;
|
|
339
|
+
refuse_root(&path, Errno::Inval)?;
|
|
340
|
+
judge(&dir.mount, &path, Access::ReadWrite, false)?;
|
|
341
|
+
Ok(dir.mount.filesystem().remove_dir(&path)?)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/// A rename stays within one Mount; across two it is `XDEV`, as
|
|
345
|
+
/// across two mounted devices. A directory moves only beneath a Mount
|
|
346
|
+
/// that allows it, the Filesystem checking what it moves as it moves it.
|
|
347
|
+
pub(crate) fn rename(
|
|
348
|
+
table: &Descriptors,
|
|
349
|
+
mem: &GuestMemory,
|
|
350
|
+
old: (u32, (u32, u32)),
|
|
351
|
+
new: (u32, (u32, u32)),
|
|
352
|
+
) -> Answer {
|
|
353
|
+
let from_dir = directory(table, old.0)?;
|
|
354
|
+
let to_dir = directory(table, new.0)?;
|
|
355
|
+
let (from, to) = (mem.path(old.1 .0, old.1 .1)?, mem.path(new.1 .0, new.1 .1)?);
|
|
356
|
+
refuse_dot_end(from_dir, &from, Errno::Busy)?;
|
|
357
|
+
refuse_dot_end(to_dir, &to, Errno::Busy)?;
|
|
358
|
+
let from = resolve(from_dir, &from)?;
|
|
359
|
+
let to = resolve(to_dir, &to)?;
|
|
360
|
+
refuse_root(&from.path, Errno::Busy)?;
|
|
361
|
+
refuse_root(&to.path, Errno::Busy)?;
|
|
362
|
+
// A trailing `/` on either side asks that the source be a directory,
|
|
363
|
+
// which `rename(2)` checks before it finds a rename onto its own path
|
|
364
|
+
// leaves everything as it was. A rename moves a link rather than what
|
|
365
|
+
// it points at, so each side is checked as it stands.
|
|
366
|
+
let names_directory = from.names_directory || to.names_directory;
|
|
367
|
+
let from = ResolvedPath {
|
|
368
|
+
names_directory,
|
|
369
|
+
..from
|
|
370
|
+
}
|
|
371
|
+
.check_directory(
|
|
372
|
+
from_dir.mount.filesystem().as_ref(),
|
|
373
|
+
<dyn Filesystem>::symlink_metadata,
|
|
374
|
+
)?;
|
|
375
|
+
let to = match to {
|
|
376
|
+
to if to.path == from => to.path,
|
|
377
|
+
to => to.check_directory(
|
|
378
|
+
to_dir.mount.filesystem().as_ref(),
|
|
379
|
+
<dyn Filesystem>::symlink_metadata,
|
|
380
|
+
)?,
|
|
381
|
+
};
|
|
382
|
+
if !Arc::ptr_eq(&from_dir.mount, &to_dir.mount) {
|
|
383
|
+
return Err(Errno::Xdev.into());
|
|
384
|
+
}
|
|
385
|
+
judge(&from_dir.mount, &from, Access::ReadWrite, false)?;
|
|
386
|
+
judge(&to_dir.mount, &to, Access::ReadWrite, false)?;
|
|
387
|
+
let rename = match (from_dir.mount.moves_directories(), names_directory) {
|
|
388
|
+
(false, _) => Rename::File,
|
|
389
|
+
(true, true) => Rename::Directory,
|
|
390
|
+
(true, false) => Rename::Any,
|
|
391
|
+
};
|
|
392
|
+
Ok(from_dir.mount.filesystem().rename(&from, &to, rename)?)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/// A Filesystem holds no links to make.
|
|
396
|
+
pub(crate) fn symlink(
|
|
397
|
+
table: &Descriptors,
|
|
398
|
+
mem: &GuestMemory,
|
|
399
|
+
old_path: (u32, u32),
|
|
400
|
+
fd: u32,
|
|
401
|
+
new_path: (u32, u32),
|
|
402
|
+
) -> Answer {
|
|
403
|
+
directory(table, fd)?;
|
|
404
|
+
mem.path(old_path.0, old_path.1)?;
|
|
405
|
+
mem.path(new_path.0, new_path.1)?;
|
|
406
|
+
Err(Errno::Notsup.into())
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
pub(crate) fn unlink_file(
|
|
410
|
+
table: &Descriptors,
|
|
411
|
+
mem: &GuestMemory,
|
|
412
|
+
fd: u32,
|
|
413
|
+
path: (u32, u32),
|
|
414
|
+
) -> Answer {
|
|
415
|
+
// Linux's `unlink` follows no link at the end, so a trailing `/` is
|
|
416
|
+
// answered by the name itself: no file removes as a directory.
|
|
417
|
+
let (dir, path) = beneath(table, mem, fd, path)?;
|
|
418
|
+
refuse_root(&path, Errno::Isdir)?;
|
|
419
|
+
judge(&dir.mount, &path, Access::ReadWrite, false)?;
|
|
420
|
+
Ok(dir.mount.filesystem().remove_file(&path)?)
|
|
421
|
+
}
|