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,708 @@
|
|
|
1
|
+
//! A Filesystem a Host App writes in Ruby: any object answering `open`
|
|
2
|
+
//! and `stat`, asked through `gvl::reenter` on every call the guest makes.
|
|
3
|
+
//!
|
|
4
|
+
//! Its methods take the names and shapes of Ruby's own `File`, `Dir`, and
|
|
5
|
+
//! `IO`, so a Host App can hand an open call on to a real `::File`. A
|
|
6
|
+
//! change method the object lacks answers as read-only; any other method
|
|
7
|
+
//! it lacks answers as unsupported. A file's `EOFError` from `pread` is
|
|
8
|
+
//! its end, an `Errno` the guest has a class for reaches the guest as
|
|
9
|
+
//! that code, and any other `StandardError` reaches the guest as `EIO`.
|
|
10
|
+
|
|
11
|
+
use magnus::value::{Opaque, ReprValue};
|
|
12
|
+
use magnus::{prelude::*, Error as MagnusError, RClass, RModule, RString, Ruby, Value};
|
|
13
|
+
|
|
14
|
+
use std::sync::{Arc, Mutex, PoisonError, Weak};
|
|
15
|
+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
16
|
+
|
|
17
|
+
use kobako_runtime::filesystem::Rdir;
|
|
18
|
+
use kobako_runtime::filesystem::{
|
|
19
|
+
DentryOperations, DirEntry, FileOperations, FileSystemType, FileType, Filesystem,
|
|
20
|
+
FilesystemError, Finish, Iattr, InodeOperations, Kiocb, Kstat, Lookup, OpenHow, RenameFlag,
|
|
21
|
+
};
|
|
22
|
+
use kobako_runtime::vfs::{
|
|
23
|
+
generic_file_llseek, generic_read_dir, generic_write_sync, verify_dirent_name, Dentry,
|
|
24
|
+
DirContext, Identity, Inode, Whence, NAME_MAX,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
use crate::runtime::pinned_value::PinnedValue;
|
|
28
|
+
use crate::runtime::{escape, gvl};
|
|
29
|
+
|
|
30
|
+
/// The Ruby object behind a Mount or a Layer. Whatever holds it on the
|
|
31
|
+
/// Ruby side keeps it alive and in place: the invocation pinning its
|
|
32
|
+
/// Mounts, or the Overlay marking its Layers. The object answers by path,
|
|
33
|
+
/// so each call asks it by the path the dentry stands at now, as CIFS
|
|
34
|
+
/// builds one from the dentry, and a directory opened beneath it is the
|
|
35
|
+
/// same object.
|
|
36
|
+
#[derive(Clone, Copy)]
|
|
37
|
+
pub(crate) struct RubyFilesystem {
|
|
38
|
+
object: Opaque<Value>,
|
|
39
|
+
/// The device a stat without a `dev` names, the object's own, shared by
|
|
40
|
+
/// every directory opened beneath it.
|
|
41
|
+
dev: u64,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
impl RubyFilesystem {
|
|
45
|
+
/// The object as a Filesystem, when it answers `open` and `stat`.
|
|
46
|
+
pub(crate) fn from_value(value: Value) -> Option<Self> {
|
|
47
|
+
let answers = |name| value.respond_to(name, false).unwrap_or(false);
|
|
48
|
+
(answers("open") && answers("stat")).then(|| Self {
|
|
49
|
+
object: Opaque::from(value),
|
|
50
|
+
dev: Kstat::unique_device(),
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// What the object's `stat` answers for `path`.
|
|
55
|
+
fn stat(&self, path: String) -> Result<Kstat, FilesystemError> {
|
|
56
|
+
let own_dev = self.dev;
|
|
57
|
+
ask(
|
|
58
|
+
self.object,
|
|
59
|
+
"stat",
|
|
60
|
+
FilesystemError::Unsupported,
|
|
61
|
+
|_, fs| {
|
|
62
|
+
let stat: Value = fs.funcall("stat", (path,))?;
|
|
63
|
+
let kstat = Kstat::new(kind_of(stat)?, stat.funcall("size", ())?);
|
|
64
|
+
// A stat names its identity as `File::Stat` does; without a
|
|
65
|
+
// `dev` the Filesystem stands for a device of its own, so it
|
|
66
|
+
// shares an identity with no other Filesystem.
|
|
67
|
+
let dev = if stat.respond_to("dev", false)? {
|
|
68
|
+
stat.funcall("dev", ())?
|
|
69
|
+
} else {
|
|
70
|
+
own_dev
|
|
71
|
+
};
|
|
72
|
+
with_stat_record(stat, kstat.with_identity(dev, stat.funcall("ino", ())?))
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// What a stat says its entry is. A `File::Stat` of a special file or a link
|
|
79
|
+
/// says so through `ftype`, so it is judged as what it is rather than opened.
|
|
80
|
+
fn kind_of(stat: Value) -> Result<FileType, MagnusError> {
|
|
81
|
+
if stat.funcall::<_, _, bool>("directory?", ())? {
|
|
82
|
+
return Ok(FileType::Dir);
|
|
83
|
+
}
|
|
84
|
+
if !stat.respond_to("ftype", false)? {
|
|
85
|
+
return Ok(FileType::RegFile);
|
|
86
|
+
}
|
|
87
|
+
let ftype: String = stat.funcall("ftype", ())?;
|
|
88
|
+
Ok(match ftype.as_str() {
|
|
89
|
+
"link" => FileType::Symlink,
|
|
90
|
+
"fifo" => FileType::Fifo,
|
|
91
|
+
"socket" => FileType::Sock,
|
|
92
|
+
"blockSpecial" => FileType::Blkdev,
|
|
93
|
+
"characterSpecial" => FileType::Chrdev,
|
|
94
|
+
_ => FileType::RegFile,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// `kstat` with the times and the count of names `stat` records.
|
|
99
|
+
fn with_stat_record(stat: Value, kstat: Kstat) -> Result<Kstat, MagnusError> {
|
|
100
|
+
let kstat = kstat.with_times(
|
|
101
|
+
time_of(stat, "atime")?,
|
|
102
|
+
time_of(stat, "mtime")?,
|
|
103
|
+
time_of(stat, "ctime")?,
|
|
104
|
+
);
|
|
105
|
+
Ok(if stat.respond_to("nlink", false)? {
|
|
106
|
+
kstat.with_nlink(stat.funcall("nlink", ())?)
|
|
107
|
+
} else {
|
|
108
|
+
kstat
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Ask `object` its `name` method with the GVL held, `answer` making the
|
|
113
|
+
/// call and reading what it returns before Ruby runs anything else. An
|
|
114
|
+
/// object lacking the method fails as `missing`.
|
|
115
|
+
fn ask<T>(
|
|
116
|
+
object: Opaque<Value>,
|
|
117
|
+
name: &str,
|
|
118
|
+
missing: FilesystemError,
|
|
119
|
+
answer: impl FnOnce(&Ruby, Value) -> Result<T, MagnusError>,
|
|
120
|
+
) -> Result<T, FilesystemError> {
|
|
121
|
+
ask_or(object, name, Err(missing), answer)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// As `ask`, an object lacking the method answering `missing`.
|
|
125
|
+
fn ask_or<T>(
|
|
126
|
+
object: Opaque<Value>,
|
|
127
|
+
name: &str,
|
|
128
|
+
missing: Result<T, FilesystemError>,
|
|
129
|
+
answer: impl FnOnce(&Ruby, Value) -> Result<T, MagnusError>,
|
|
130
|
+
) -> Result<T, FilesystemError> {
|
|
131
|
+
gvl::reenter(|| {
|
|
132
|
+
let ruby = Ruby::get().expect("Ruby handle unavailable in a Filesystem call");
|
|
133
|
+
let object: Value = ruby.get_inner(object);
|
|
134
|
+
if !object.respond_to(name, false).unwrap_or(false) {
|
|
135
|
+
return missing;
|
|
136
|
+
}
|
|
137
|
+
answer(&ruby, object).map_err(|err| match escape::hold(&ruby, err) {
|
|
138
|
+
Err(err) => to_error(err),
|
|
139
|
+
// The invocation ends before the guest reads this.
|
|
140
|
+
Ok(()) => FilesystemError::Other(String::new()),
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// Ask `object` a change, answering nothing more than whether it was made.
|
|
146
|
+
fn change(
|
|
147
|
+
object: Opaque<Value>,
|
|
148
|
+
name: &str,
|
|
149
|
+
args: impl magnus::ArgList,
|
|
150
|
+
missing: FilesystemError,
|
|
151
|
+
) -> Result<(), FilesystemError> {
|
|
152
|
+
ask(object, name, missing, |_, object| {
|
|
153
|
+
object.funcall::<_, _, Value>(name, args).map(drop)
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Ask `object` a change it may lack, an object lacking `name` having
|
|
158
|
+
/// nothing of its own to change, while one that answers and refuses still
|
|
159
|
+
/// fails, whatever `Errno` it raises.
|
|
160
|
+
fn change_if_answered(object: Opaque<Value>, name: &str) -> Result<(), FilesystemError> {
|
|
161
|
+
ask_or(object, name, Ok(()), |_, object| {
|
|
162
|
+
object.funcall::<_, _, Value>(name, ()).map(drop)
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// The FilesystemError the nearest `Errno` class of `err` stands for, by
|
|
167
|
+
/// its POSIX name; anything else is `Other`, which reaches the guest as
|
|
168
|
+
/// `EIO`.
|
|
169
|
+
fn to_error(err: MagnusError) -> FilesystemError {
|
|
170
|
+
let named = err.value().and_then(|exception| {
|
|
171
|
+
exception
|
|
172
|
+
.class()
|
|
173
|
+
.ancestors()
|
|
174
|
+
.into_iter()
|
|
175
|
+
.find_map(|module| {
|
|
176
|
+
let name: Option<String> = module.funcall("name", ()).ok()?;
|
|
177
|
+
FilesystemError::from_posix_name(name?.strip_prefix("Errno::")?)
|
|
178
|
+
})
|
|
179
|
+
});
|
|
180
|
+
named.unwrap_or_else(|| FilesystemError::Other(err.to_string()))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// `File::Constants` flags for `how`, read from Ruby since their
|
|
184
|
+
/// values differ by platform.
|
|
185
|
+
fn to_flags(ruby: &Ruby, how: &OpenHow, create: bool) -> Result<i64, MagnusError> {
|
|
186
|
+
let constants: RModule = ruby
|
|
187
|
+
.class_object()
|
|
188
|
+
.const_get::<_, RClass>("File")?
|
|
189
|
+
.const_get("Constants")?;
|
|
190
|
+
let flag = |name| constants.const_get::<_, i64>(name);
|
|
191
|
+
let mut flags = match (how.read, how.write || how.truncate) {
|
|
192
|
+
(_, false) => flag("RDONLY")?,
|
|
193
|
+
(false, true) => flag("WRONLY")?,
|
|
194
|
+
(true, true) => flag("RDWR")?,
|
|
195
|
+
};
|
|
196
|
+
if create {
|
|
197
|
+
flags |= flag("CREAT")? | flag("EXCL")?;
|
|
198
|
+
}
|
|
199
|
+
if how.truncate {
|
|
200
|
+
flags |= flag("TRUNC")?;
|
|
201
|
+
}
|
|
202
|
+
// The runtime follows a link itself, so no open follows one, wherever
|
|
203
|
+
// the host's Ruby can say so: Windows defines no `NOFOLLOW`.
|
|
204
|
+
if constants.funcall::<_, _, bool>("const_defined?", ("NOFOLLOW",))? {
|
|
205
|
+
flags |= flag("NOFOLLOW")?;
|
|
206
|
+
}
|
|
207
|
+
Ok(flags)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// The `Time` a stat answers to `name`, as `File::Stat#mtime` gives one;
|
|
211
|
+
/// `None` where it answers none, or a time before the Unix epoch.
|
|
212
|
+
fn time_of(stat: Value, name: &str) -> Result<Option<SystemTime>, MagnusError> {
|
|
213
|
+
if !stat.respond_to(name, false)? {
|
|
214
|
+
return Ok(None);
|
|
215
|
+
}
|
|
216
|
+
let time: Value = stat.funcall(name, ())?;
|
|
217
|
+
let seconds: i64 = time.funcall("to_i", ())?;
|
|
218
|
+
let nanoseconds: u32 = time.funcall("nsec", ())?;
|
|
219
|
+
Ok(u64::try_from(seconds)
|
|
220
|
+
.ok()
|
|
221
|
+
.and_then(|seconds| UNIX_EPOCH.checked_add(Duration::new(seconds, nanoseconds))))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// The path the object is asked about for `dentry`: where it stands beneath the
|
|
225
|
+
/// root, with no leading `/`, so an object joining it to a directory of its own
|
|
226
|
+
/// stays within that directory, empty for the root.
|
|
227
|
+
fn path_of(dentry: &Dentry) -> String {
|
|
228
|
+
dentry.path_raw()[1..].to_owned()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fn join(dir: &str, name: &str) -> String {
|
|
232
|
+
if dir.is_empty() {
|
|
233
|
+
name.to_string()
|
|
234
|
+
} else {
|
|
235
|
+
format!("{dir}/{name}")
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
impl RubyFilesystem {
|
|
240
|
+
/// Open the file `dentry` names and keep it on the inode, so the entry
|
|
241
|
+
/// answers from it however its name changes, as 9p keeps an open fid.
|
|
242
|
+
/// What this made is recorded first, as `->atomic_open` calls `d_instantiate`.
|
|
243
|
+
fn open_at(
|
|
244
|
+
&self,
|
|
245
|
+
dentry: &Dentry,
|
|
246
|
+
how: &OpenHow,
|
|
247
|
+
create: bool,
|
|
248
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
249
|
+
let path = path_of(dentry);
|
|
250
|
+
let file = ask(
|
|
251
|
+
self.object,
|
|
252
|
+
"open",
|
|
253
|
+
FilesystemError::Unsupported,
|
|
254
|
+
|ruby, fs| {
|
|
255
|
+
let file: Value = fs.funcall("open", (path, to_flags(ruby, how, create)?))?;
|
|
256
|
+
Ok(RubyFile::pin(ruby, file))
|
|
257
|
+
},
|
|
258
|
+
)?;
|
|
259
|
+
match dentry.inode().map(|inode| inode.identity()) {
|
|
260
|
+
None => {
|
|
261
|
+
let made = self.stat(path_of(dentry))?;
|
|
262
|
+
dentry.add(Some(Inode::new(&made, Arc::new(RubyRegular(*self)))));
|
|
263
|
+
}
|
|
264
|
+
// The object opens by path, so whatever stands there now may be
|
|
265
|
+
// another entry than the one judged, as `Bind::open_file` finds.
|
|
266
|
+
Some(Some(identity)) => {
|
|
267
|
+
if Identity::of(&self.stat(path_of(dentry))?) != Some(identity) {
|
|
268
|
+
return Err(FilesystemError::NotFound);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
Some(None) => {}
|
|
272
|
+
}
|
|
273
|
+
let inode = dentry.inode().ok_or(FilesystemError::NotFound)?;
|
|
274
|
+
let kept = inode
|
|
275
|
+
.private()
|
|
276
|
+
.get_or_init(|| Arc::new(OpenRubyFile::default()))
|
|
277
|
+
.clone();
|
|
278
|
+
if let Ok(open) = kept.downcast::<OpenRubyFile>() {
|
|
279
|
+
*open.0.lock().unwrap_or_else(PoisonError::into_inner) = Arc::downgrade(&file.file);
|
|
280
|
+
}
|
|
281
|
+
Ok(Box::new(file))
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// The entries of the directory. No name a directory cannot hold is asked
|
|
285
|
+
/// about, as it could name a path the object would answer for, and a name
|
|
286
|
+
/// gone by the time it is asked about is one the listing no longer holds, as
|
|
287
|
+
/// any listing is a moment that may have passed.
|
|
288
|
+
fn entries_at(&self, dentry: &Dentry) -> Result<Vec<DirEntry>, FilesystemError> {
|
|
289
|
+
let at = path_of(dentry);
|
|
290
|
+
let names: Vec<String> = ask(
|
|
291
|
+
self.object,
|
|
292
|
+
"children",
|
|
293
|
+
FilesystemError::Unsupported,
|
|
294
|
+
|_, fs| fs.funcall("children", (at.as_str(),)),
|
|
295
|
+
)?;
|
|
296
|
+
names.iter().try_for_each(|name| verify_dirent_name(name))?;
|
|
297
|
+
let mut entries = Vec::with_capacity(names.len());
|
|
298
|
+
for name in names {
|
|
299
|
+
match self.stat(join(&at, &name)) {
|
|
300
|
+
Ok(stat) => entries
|
|
301
|
+
.push(DirEntry::new(name, stat.file_type).with_identity(stat.dev, stat.ino)),
|
|
302
|
+
Err(FilesystemError::NotFound) => {}
|
|
303
|
+
Err(err) => return Err(err),
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
Ok(entries)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// A link a stat reports. A Ruby object answers no `readlink`, so no walk goes
|
|
311
|
+
/// on through one, as a path past too many links stops.
|
|
312
|
+
struct RubyLink(RubyFilesystem);
|
|
313
|
+
|
|
314
|
+
impl InodeOperations for RubyLink {
|
|
315
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
316
|
+
self.0.stat(path_of(dentry))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
fn get_link(&self, _dentry: &Dentry) -> Result<String, FilesystemError> {
|
|
320
|
+
Err(FilesystemError::FilesystemLoop)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/// Any entry of a Ruby Filesystem but a directory.
|
|
325
|
+
struct RubyRegular(RubyFilesystem);
|
|
326
|
+
|
|
327
|
+
impl InodeOperations for RubyRegular {
|
|
328
|
+
/// A file open here answers for itself even once its name is gone, as 9p
|
|
329
|
+
/// asks an open fid: its `size`, and its `stat` where it has one. It stays
|
|
330
|
+
/// the entry its lookup recorded; anything else is asked by its path.
|
|
331
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
332
|
+
let Some(file) = find_open_file(dentry) else {
|
|
333
|
+
return self.0.stat(path_of(dentry));
|
|
334
|
+
};
|
|
335
|
+
let metadata = ask(
|
|
336
|
+
file.get(),
|
|
337
|
+
"size",
|
|
338
|
+
FilesystemError::Unsupported,
|
|
339
|
+
|_, file| {
|
|
340
|
+
let metadata = Kstat::new(FileType::RegFile, file.funcall("size", ())?);
|
|
341
|
+
if file.respond_to("stat", false)? {
|
|
342
|
+
with_stat_record(file.funcall("stat", ())?, metadata)
|
|
343
|
+
} else {
|
|
344
|
+
Ok(metadata)
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
)?;
|
|
348
|
+
Ok(match dentry.inode().and_then(|inode| inode.identity()) {
|
|
349
|
+
Some(identity) => identity.onto(metadata),
|
|
350
|
+
None => metadata,
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/// A Ruby object was handed `TRUNC` as it opened the file, so an open asking
|
|
355
|
+
/// to truncate has nothing left to ask, as `fuse_do_setattr` finds with
|
|
356
|
+
/// `atomic_o_trunc`.
|
|
357
|
+
fn setattr(&self, dentry: &Dentry, attr: &Iattr) -> Result<(), FilesystemError> {
|
|
358
|
+
let Some(len) = attr.size else {
|
|
359
|
+
return Err(FilesystemError::Unsupported);
|
|
360
|
+
};
|
|
361
|
+
if attr.open {
|
|
362
|
+
return Ok(());
|
|
363
|
+
}
|
|
364
|
+
let file = find_open_file(dentry).ok_or(FilesystemError::InvalidInput)?;
|
|
365
|
+
change(
|
|
366
|
+
file.get(),
|
|
367
|
+
"truncate",
|
|
368
|
+
(len,),
|
|
369
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
fn open(
|
|
374
|
+
&self,
|
|
375
|
+
dentry: &Dentry,
|
|
376
|
+
how: &OpenHow,
|
|
377
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
378
|
+
self.0.open_at(dentry, how, false)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/// Its root reports no identity without asking the Ruby object, as FUSE makes its
|
|
383
|
+
/// root from fixed attributes rather than ask the server.
|
|
384
|
+
impl FileSystemType for RubyFilesystem {
|
|
385
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
386
|
+
sb.set_default_d_op(self.clone());
|
|
387
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
388
|
+
Ok(())
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/// A directory of a Ruby Filesystem, asked by the path a dentry stands at, the
|
|
393
|
+
/// path a Policy judges.
|
|
394
|
+
impl InodeOperations for RubyFilesystem {
|
|
395
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
396
|
+
self.stat(path_of(dentry))
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/// A Ruby object matches names exactly, as its contract asks. A lookup
|
|
400
|
+
/// asks its `stat` what stands at the name, as FUSE asks its daemon.
|
|
401
|
+
fn lookup(&self, dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
402
|
+
if dentry.name().len() > NAME_MAX {
|
|
403
|
+
return Err(FilesystemError::InvalidFilename);
|
|
404
|
+
}
|
|
405
|
+
match self.stat(path_of(dentry)) {
|
|
406
|
+
Ok(found) => {
|
|
407
|
+
let ops: Arc<dyn InodeOperations> = match found.file_type {
|
|
408
|
+
FileType::Dir => Arc::new(*self),
|
|
409
|
+
FileType::Symlink => Arc::new(RubyLink(*self)),
|
|
410
|
+
_ => Arc::new(RubyRegular(*self)),
|
|
411
|
+
};
|
|
412
|
+
Ok(Lookup::Found(Inode::new(&found, ops)))
|
|
413
|
+
}
|
|
414
|
+
Err(FilesystemError::NotFound) => Ok(Lookup::Absent),
|
|
415
|
+
Err(err) => Err(err),
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/// A directory opens without asking the object anything; it is asked
|
|
420
|
+
/// for the names only when the guest lists them.
|
|
421
|
+
fn open(
|
|
422
|
+
&self,
|
|
423
|
+
_dentry: &Dentry,
|
|
424
|
+
_how: &OpenHow,
|
|
425
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
426
|
+
Ok(Box::new(RubyDir(*self, Rdir::default())))
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/// A name still in lookup is asked of the object's `stat` first, and any
|
|
430
|
+
/// name is left to the runtime unless the open may make it and nothing
|
|
431
|
+
/// stands there, as FUSE's `fuse_atomic_open` looks the name up before it
|
|
432
|
+
/// creates.
|
|
433
|
+
fn atomic_open(&self, dentry: &Dentry, how: &OpenHow) -> Result<Finish, FilesystemError> {
|
|
434
|
+
if dentry.in_lookup() {
|
|
435
|
+
let found = self.lookup(dentry)?;
|
|
436
|
+
if !how.create || !matches!(found, Lookup::Absent) {
|
|
437
|
+
return Ok(Finish::Lookup(found));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if !how.create {
|
|
441
|
+
return Ok(Finish::NoOpen { created: false });
|
|
442
|
+
}
|
|
443
|
+
let file = self.open_at(dentry, how, true)?;
|
|
444
|
+
Ok(Finish::Open {
|
|
445
|
+
file,
|
|
446
|
+
created: true,
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
451
|
+
change(
|
|
452
|
+
self.object,
|
|
453
|
+
"mkdir",
|
|
454
|
+
(path_of(dentry),),
|
|
455
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
460
|
+
change(
|
|
461
|
+
self.object,
|
|
462
|
+
"rmdir",
|
|
463
|
+
(path_of(dentry),),
|
|
464
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
465
|
+
)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
469
|
+
change(
|
|
470
|
+
self.object,
|
|
471
|
+
"unlink",
|
|
472
|
+
(path_of(dentry),),
|
|
473
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/// `rename(from, to)`, as `File.rename` takes it. What stands at `from`
|
|
478
|
+
/// after the runtime checked it is the object's to keep still. Both
|
|
479
|
+
/// directories are of one object, which their shared device names.
|
|
480
|
+
fn rename(
|
|
481
|
+
&self,
|
|
482
|
+
from: &Dentry,
|
|
483
|
+
_to_dir: &dyn InodeOperations,
|
|
484
|
+
to: &Dentry,
|
|
485
|
+
flag: Option<RenameFlag>,
|
|
486
|
+
) -> Result<(), FilesystemError> {
|
|
487
|
+
if flag.is_some() {
|
|
488
|
+
return Err(FilesystemError::InvalidInput);
|
|
489
|
+
}
|
|
490
|
+
change(
|
|
491
|
+
self.object,
|
|
492
|
+
"rename",
|
|
493
|
+
(path_of(from), path_of(to)),
|
|
494
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
495
|
+
)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/// A directory of the Ruby object, open: it asks the object by the path the
|
|
500
|
+
/// dentry it is handed stands at.
|
|
501
|
+
struct RubyDir(RubyFilesystem, Rdir);
|
|
502
|
+
|
|
503
|
+
impl FileOperations for RubyDir {
|
|
504
|
+
fn llseek(
|
|
505
|
+
&mut self,
|
|
506
|
+
dentry: &Dentry,
|
|
507
|
+
pos: u64,
|
|
508
|
+
offset: i64,
|
|
509
|
+
whence: Whence,
|
|
510
|
+
) -> Result<u64, FilesystemError> {
|
|
511
|
+
generic_file_llseek(dentry, pos, offset, whence)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
fn read_iter(&mut self, _iocb: &mut Kiocb, _buf: &mut [u8]) -> Result<usize, FilesystemError> {
|
|
515
|
+
generic_read_dir()
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/// What a listing began with stays on the open directory until it ends, so
|
|
519
|
+
/// a caller reading in small steps asks the object once, as FUSE's readdir
|
|
520
|
+
/// cache spares asking the server each time.
|
|
521
|
+
fn iterate_shared(
|
|
522
|
+
&mut self,
|
|
523
|
+
dentry: &Dentry,
|
|
524
|
+
ctx: &mut DirContext<'_>,
|
|
525
|
+
) -> Result<(), FilesystemError> {
|
|
526
|
+
let filesystem = self.0;
|
|
527
|
+
self.1
|
|
528
|
+
.iterate(dentry, ctx, || filesystem.entries_at(dentry))
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/// The object has nothing of a directory's own to flush, as FUSE's
|
|
532
|
+
/// `fuse_dir_fsync` answers for a server without `fsyncdir`.
|
|
533
|
+
fn fsync(&mut self, _datasync: bool) -> Result<(), FilesystemError> {
|
|
534
|
+
Ok(())
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/// One file the Ruby object opened, which only the guest's descriptor
|
|
539
|
+
/// refers to.
|
|
540
|
+
struct RubyFile {
|
|
541
|
+
file: Arc<PinnedValue>,
|
|
542
|
+
closed: bool,
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/// The file open on a dentry. A dentry holds at most one, as `revalidate`
|
|
546
|
+
/// has the next walk take a dentry of its own once one is open.
|
|
547
|
+
#[derive(Default)]
|
|
548
|
+
struct OpenRubyFile(Mutex<Weak<PinnedValue>>);
|
|
549
|
+
|
|
550
|
+
/// The file open on `dentry`, while one is.
|
|
551
|
+
fn find_open_file(dentry: &Dentry) -> Option<Arc<PinnedValue>> {
|
|
552
|
+
let inode = dentry.inode()?;
|
|
553
|
+
let open = inode
|
|
554
|
+
.private()
|
|
555
|
+
.get()?
|
|
556
|
+
.clone()
|
|
557
|
+
.downcast::<OpenRubyFile>()
|
|
558
|
+
.ok()?;
|
|
559
|
+
let file = open.0.lock().unwrap_or_else(PoisonError::into_inner);
|
|
560
|
+
file.upgrade()
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/// The backend changes outside the runtime, so a dentry someone holds is
|
|
564
|
+
/// asked again, as FUSE compares what a fresh lookup finds, and one no longer
|
|
565
|
+
/// naming what it recorded gets a new one. An open file answers for itself,
|
|
566
|
+
/// so a walk finding a dentry one is open on takes one of its own.
|
|
567
|
+
impl DentryOperations for RubyFilesystem {
|
|
568
|
+
fn revalidate(
|
|
569
|
+
&self,
|
|
570
|
+
_dir: &dyn InodeOperations,
|
|
571
|
+
dentry: &Dentry,
|
|
572
|
+
) -> Result<bool, FilesystemError> {
|
|
573
|
+
let Some(inode) = dentry.inode().filter(|_| find_open_file(dentry).is_none()) else {
|
|
574
|
+
return Ok(false);
|
|
575
|
+
};
|
|
576
|
+
match self.stat(path_of(dentry)) {
|
|
577
|
+
Ok(found) => {
|
|
578
|
+
Ok(Identity::of(&found) == inode.identity() && found.file_type == inode.kind())
|
|
579
|
+
}
|
|
580
|
+
Err(FilesystemError::NotFound | FilesystemError::NotADirectory) => Ok(false),
|
|
581
|
+
Err(err) => Err(err),
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/// Answering whether a name holds costs a `stat`, as finding it does, so
|
|
586
|
+
/// nothing is gained by keeping it, as FUSE's `entry_timeout=0` keeps none.
|
|
587
|
+
fn delete(&self, _dentry: &Dentry) -> bool {
|
|
588
|
+
true
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
impl RubyFile {
|
|
593
|
+
fn pin(ruby: &Ruby, file: Value) -> Self {
|
|
594
|
+
Self {
|
|
595
|
+
file: Arc::new(PinnedValue::new(ruby, file)),
|
|
596
|
+
closed: false,
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/// The file's `append` writes all of `buf` at its end in one step and
|
|
601
|
+
/// answers where the file then ends, as `IO#pos` does after a write to
|
|
602
|
+
/// a file opened to append. A file lacking it cannot append, since its
|
|
603
|
+
/// `size` and `pwrite` are two steps another writer can land between.
|
|
604
|
+
fn pwrite(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
605
|
+
let pos = iocb.pos;
|
|
606
|
+
let written = ask(
|
|
607
|
+
self.file.get(),
|
|
608
|
+
"pwrite",
|
|
609
|
+
FilesystemError::ReadOnlyFilesystem,
|
|
610
|
+
|ruby, file| file.funcall("pwrite", (ruby.str_from_slice(buf), pos)),
|
|
611
|
+
)
|
|
612
|
+
.and_then(|written: usize| match written {
|
|
613
|
+
written if written <= buf.len() => Ok(written),
|
|
614
|
+
written => Err(FilesystemError::Other(format!(
|
|
615
|
+
"pwrite answered {written} bytes written of the {} it was handed",
|
|
616
|
+
buf.len()
|
|
617
|
+
))),
|
|
618
|
+
})?;
|
|
619
|
+
iocb.pos += written as u64;
|
|
620
|
+
Ok(written)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
fn append(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
624
|
+
let end: u64 = ask(
|
|
625
|
+
self.file.get(),
|
|
626
|
+
"append",
|
|
627
|
+
FilesystemError::Unsupported,
|
|
628
|
+
|ruby, file| file.funcall("append", (ruby.str_from_slice(buf),)),
|
|
629
|
+
)?;
|
|
630
|
+
if end < buf.len() as u64 {
|
|
631
|
+
return Err(FilesystemError::Other(format!(
|
|
632
|
+
"append answered the file ending at {end}, before the {} bytes it wrote",
|
|
633
|
+
buf.len()
|
|
634
|
+
)));
|
|
635
|
+
}
|
|
636
|
+
iocb.pos = end;
|
|
637
|
+
Ok(buf.len())
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/// Ask the file to close, a file lacking `close` having nothing to
|
|
641
|
+
/// release.
|
|
642
|
+
fn close(&mut self) -> Result<(), FilesystemError> {
|
|
643
|
+
self.closed = true;
|
|
644
|
+
change_if_answered(self.file.get(), "close")
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
impl FileOperations for RubyFile {
|
|
649
|
+
fn llseek(
|
|
650
|
+
&mut self,
|
|
651
|
+
dentry: &Dentry,
|
|
652
|
+
pos: u64,
|
|
653
|
+
offset: i64,
|
|
654
|
+
whence: Whence,
|
|
655
|
+
) -> Result<u64, FilesystemError> {
|
|
656
|
+
generic_file_llseek(dentry, pos, offset, whence)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
fn read_iter(&mut self, iocb: &mut Kiocb, buf: &mut [u8]) -> Result<usize, FilesystemError> {
|
|
660
|
+
let pos = iocb.pos;
|
|
661
|
+
let read = ask(
|
|
662
|
+
self.file.get(),
|
|
663
|
+
"pread",
|
|
664
|
+
FilesystemError::Unsupported,
|
|
665
|
+
|ruby, file| {
|
|
666
|
+
// `IO#pread` raises `EOFError` at the end of a file, which reads as nothing.
|
|
667
|
+
let read: Option<RString> = match file.funcall("pread", (buf.len(), pos)) {
|
|
668
|
+
Err(err) if err.is_kind_of(ruby.exception_eof_error()) => None,
|
|
669
|
+
read => read?,
|
|
670
|
+
};
|
|
671
|
+
let Some(read) = read else { return Ok(0) };
|
|
672
|
+
// SAFETY: the bytes are copied out before Ruby runs again.
|
|
673
|
+
let bytes = unsafe { read.as_slice() };
|
|
674
|
+
let len = bytes.len().min(buf.len());
|
|
675
|
+
buf[..len].copy_from_slice(&bytes[..len]);
|
|
676
|
+
Ok(len)
|
|
677
|
+
},
|
|
678
|
+
)?;
|
|
679
|
+
iocb.pos += read as u64;
|
|
680
|
+
Ok(read)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
fn write_iter(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
684
|
+
let written = match iocb.append {
|
|
685
|
+
true => self.append(iocb, buf)?,
|
|
686
|
+
false => self.pwrite(iocb, buf)?,
|
|
687
|
+
};
|
|
688
|
+
generic_write_sync(self, iocb, written)
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/// A file lacking `fsync` has nothing of its own to flush.
|
|
692
|
+
fn fsync(&mut self, _datasync: bool) -> Result<(), FilesystemError> {
|
|
693
|
+
change_if_answered(self.file.get(), "fsync")
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
fn flush(&mut self) -> Result<(), FilesystemError> {
|
|
697
|
+
self.close()
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/// A file the guest never closed is closed here, its failure discarded.
|
|
702
|
+
impl Drop for RubyFile {
|
|
703
|
+
fn drop(&mut self) {
|
|
704
|
+
if !self.closed {
|
|
705
|
+
let _ = self.close();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|