kobako 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +217 -10
- data/Cargo.lock +139 -141
- data/README.md +96 -574
- data/ROADMAP.md +2 -1
- data/crates/kobako-runtime/CHANGELOG.md +269 -1
- data/crates/kobako-runtime/Cargo.toml +25 -15
- data/crates/kobako-runtime/README.md +7 -3
- data/crates/kobako-runtime/src/dispatch.rs +1 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
- data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
- data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +21 -387
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +11 -6
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +31 -104
- data/crates/kobako-runtime/src/profile.rs +137 -1
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +8 -2
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +2 -2
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
- data/crates/kobako-transport/src/envelope/reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/request.rs +2 -2
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
- data/crates/kobako-wasmtime/Cargo.toml +15 -14
- data/crates/kobako-wasmtime/README.md +9 -5
- data/crates/kobako-wasmtime/src/abi.rs +12 -20
- data/crates/kobako-wasmtime/src/ambient.rs +1 -2
- data/crates/kobako-wasmtime/src/cache.rs +5 -5
- data/crates/kobako-wasmtime/src/config.rs +22 -7
- data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
- data/crates/kobako-wasmtime/src/driver.rs +29 -96
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +14 -8
- data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
- data/crates/kobako-wasmtime/src/invocation.rs +26 -46
- data/crates/kobako-wasmtime/src/lib.rs +1 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
- data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
- data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
- data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
- data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
- data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
- data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +464 -413
- data/crates/kobako-wasmtime/src/trap.rs +9 -9
- data/crates/kobako-wasmtime/src/wasi.rs +53 -37
- data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/ext/kobako/src/runtime/bridge.rs +1 -1
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +28 -14
- data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
- data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
- data/ext/kobako/src/runtime/filesystem.rs +109 -137
- data/ext/kobako/src/runtime/gvl.rs +43 -8
- data/ext/kobako/src/runtime/snapshot.rs +1 -1
- data/ext/kobako/src/runtime.rs +6 -1
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -2
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +2 -10
- data/lib/kobako/catalog/services.rb +2 -2
- data/lib/kobako/catalog/snippets.rb +7 -7
- data/lib/kobako/catalog.rb +1 -1
- data/lib/kobako/codec/decoder.rb +10 -10
- data/lib/kobako/codec/ext_types.rb +3 -5
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +3 -3
- data/lib/kobako/context.rb +5 -5
- data/lib/kobako/filesystem.rb +44 -13
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +4 -4
- data/lib/kobako/sandbox.rb +28 -28
- data/lib/kobako/sandbox_options.rb +2 -3
- data/lib/kobako/snippet/bytecode.rb +1 -1
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/transport/dispatcher.rb +5 -5
- data/lib/kobako/transport/exposure.rb +2 -2
- data/lib/kobako/transport/reflection.rb +7 -7
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +1 -1
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +34 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +0 -4
- data/sig/kobako/catalog/services.rbs +1 -1
- data/sig/kobako/catalog/snippets.rbs +3 -3
- data/sig/kobako/catalog.rbs +1 -1
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +1 -1
- data/sig/kobako/codec.rbs +1 -2
- data/sig/kobako/context.rbs +1 -1
- data/sig/kobako/filesystem.rbs +19 -14
- data/sig/kobako/guest_yielder.rbs +1 -1
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +2 -2
- data/sig/kobako/runtime.rbs +1 -1
- data/sig/kobako/sandbox.rbs +3 -5
- data/sig/kobako/sandbox_options.rbs +1 -1
- data/sig/kobako/transport/dispatcher.rbs +2 -2
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +2 -2
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport.rbs +1 -1
- metadata +67 -19
- data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
- data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
- data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
- data/crates/kobako-wasmtime/src/io.rs +0 -89
- data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
- /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
- /data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +0 -0
|
@@ -9,12 +9,19 @@
|
|
|
9
9
|
//! that code, and any other `StandardError` reaches the guest as `EIO`.
|
|
10
10
|
|
|
11
11
|
use magnus::value::{Opaque, ReprValue};
|
|
12
|
-
use magnus::{
|
|
12
|
+
use magnus::{prelude::*, Error as MagnusError, RClass, RModule, RString, Ruby, Value};
|
|
13
13
|
|
|
14
|
-
use std::sync::Arc;
|
|
14
|
+
use std::sync::{Arc, Mutex, PoisonError, Weak};
|
|
15
|
+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
15
16
|
|
|
17
|
+
use kobako_runtime::filesystem::Rdir;
|
|
16
18
|
use kobako_runtime::filesystem::{
|
|
17
|
-
|
|
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,
|
|
18
25
|
};
|
|
19
26
|
|
|
20
27
|
use crate::runtime::pinned_value::PinnedValue;
|
|
@@ -22,10 +29,15 @@ use crate::runtime::{escape, gvl};
|
|
|
22
29
|
|
|
23
30
|
/// The Ruby object behind a Mount or a Layer. Whatever holds it on the
|
|
24
31
|
/// Ruby side keeps it alive and in place: the invocation pinning its
|
|
25
|
-
/// Mounts, or the Overlay marking its Layers.
|
|
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)]
|
|
26
37
|
pub(crate) struct RubyFilesystem {
|
|
27
38
|
object: Opaque<Value>,
|
|
28
|
-
/// The device a stat without a `dev` names, the object's own
|
|
39
|
+
/// The device a stat without a `dev` names, the object's own, shared by
|
|
40
|
+
/// every directory opened beneath it.
|
|
29
41
|
dev: u64,
|
|
30
42
|
}
|
|
31
43
|
|
|
@@ -35,9 +47,66 @@ impl RubyFilesystem {
|
|
|
35
47
|
let answers = |name| value.respond_to(name, false).unwrap_or(false);
|
|
36
48
|
(answers("open") && answers("stat")).then(|| Self {
|
|
37
49
|
object: Opaque::from(value),
|
|
38
|
-
dev:
|
|
50
|
+
dev: Kstat::unique_device(),
|
|
39
51
|
})
|
|
40
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
|
+
})
|
|
41
110
|
}
|
|
42
111
|
|
|
43
112
|
/// Ask `object` its `name` method with the GVL held, `answer` making the
|
|
@@ -111,34 +180,54 @@ fn to_error(err: MagnusError) -> FilesystemError {
|
|
|
111
180
|
named.unwrap_or_else(|| FilesystemError::Other(err.to_string()))
|
|
112
181
|
}
|
|
113
182
|
|
|
114
|
-
/// `File::Constants` flags for `
|
|
183
|
+
/// `File::Constants` flags for `how`, read from Ruby since their
|
|
115
184
|
/// values differ by platform.
|
|
116
|
-
fn to_flags(ruby: &Ruby,
|
|
185
|
+
fn to_flags(ruby: &Ruby, how: &OpenHow, create: bool) -> Result<i64, MagnusError> {
|
|
117
186
|
let constants: RModule = ruby
|
|
118
187
|
.class_object()
|
|
119
188
|
.const_get::<_, RClass>("File")?
|
|
120
189
|
.const_get("Constants")?;
|
|
121
190
|
let flag = |name| constants.const_get::<_, i64>(name);
|
|
122
|
-
let mut flags = match (
|
|
191
|
+
let mut flags = match (how.read, how.write || how.truncate) {
|
|
123
192
|
(_, false) => flag("RDONLY")?,
|
|
124
193
|
(false, true) => flag("WRONLY")?,
|
|
125
194
|
(true, true) => flag("RDWR")?,
|
|
126
195
|
};
|
|
127
|
-
if
|
|
128
|
-
flags |= flag("CREAT")?;
|
|
129
|
-
}
|
|
130
|
-
if options.create_new {
|
|
131
|
-
flags |= flag("EXCL")?;
|
|
196
|
+
if create {
|
|
197
|
+
flags |= flag("CREAT")? | flag("EXCL")?;
|
|
132
198
|
}
|
|
133
|
-
if
|
|
199
|
+
if how.truncate {
|
|
134
200
|
flags |= flag("TRUNC")?;
|
|
135
201
|
}
|
|
136
|
-
|
|
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",))? {
|
|
137
205
|
flags |= flag("NOFOLLOW")?;
|
|
138
206
|
}
|
|
139
207
|
Ok(flags)
|
|
140
208
|
}
|
|
141
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
|
+
|
|
142
231
|
fn join(dir: &str, name: &str) -> String {
|
|
143
232
|
if dir.is_empty() {
|
|
144
233
|
name.to_string()
|
|
@@ -147,191 +236,378 @@ fn join(dir: &str, name: &str) -> String {
|
|
|
147
236
|
}
|
|
148
237
|
}
|
|
149
238
|
|
|
150
|
-
impl
|
|
151
|
-
|
|
152
|
-
|
|
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(
|
|
153
251
|
self.object,
|
|
154
252
|
"open",
|
|
155
253
|
FilesystemError::Unsupported,
|
|
156
254
|
|ruby, fs| {
|
|
157
|
-
let file: Value = fs.funcall("open", (path, to_flags(ruby,
|
|
158
|
-
Ok(
|
|
255
|
+
let file: Value = fs.funcall("open", (path, to_flags(ruby, how, create)?))?;
|
|
256
|
+
Ok(RubyFile::pin(ruby, file))
|
|
159
257
|
},
|
|
160
|
-
)
|
|
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))
|
|
161
282
|
}
|
|
162
283
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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(
|
|
166
291
|
self.object,
|
|
167
|
-
"
|
|
292
|
+
"children",
|
|
168
293
|
FilesystemError::Unsupported,
|
|
169
|
-
|_, fs|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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)
|
|
181
343
|
} else {
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
Ok(metadata.with_identity(dev, stat.funcall("ino", ())?))
|
|
344
|
+
Ok(metadata)
|
|
345
|
+
}
|
|
185
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,
|
|
186
370
|
)
|
|
187
371
|
}
|
|
188
372
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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)
|
|
192
379
|
}
|
|
380
|
+
}
|
|
193
381
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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(())
|
|
197
389
|
}
|
|
390
|
+
}
|
|
198
391
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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))
|
|
202
397
|
}
|
|
203
398
|
|
|
204
|
-
/// A Ruby object
|
|
205
|
-
|
|
206
|
-
|
|
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
|
+
}
|
|
207
417
|
}
|
|
208
418
|
|
|
209
|
-
/// A
|
|
210
|
-
///
|
|
211
|
-
fn
|
|
212
|
-
|
|
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())))
|
|
213
427
|
}
|
|
214
428
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
})
|
|
228
|
-
|
|
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
|
+
})
|
|
229
448
|
}
|
|
230
449
|
|
|
231
|
-
fn
|
|
450
|
+
fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
232
451
|
change(
|
|
233
452
|
self.object,
|
|
234
453
|
"mkdir",
|
|
235
|
-
(
|
|
454
|
+
(path_of(dentry),),
|
|
236
455
|
FilesystemError::ReadOnlyFilesystem,
|
|
237
456
|
)
|
|
238
457
|
}
|
|
239
458
|
|
|
240
|
-
fn
|
|
459
|
+
fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
241
460
|
change(
|
|
242
461
|
self.object,
|
|
243
462
|
"rmdir",
|
|
244
|
-
(
|
|
463
|
+
(path_of(dentry),),
|
|
245
464
|
FilesystemError::ReadOnlyFilesystem,
|
|
246
465
|
)
|
|
247
466
|
}
|
|
248
467
|
|
|
249
|
-
fn
|
|
468
|
+
fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
|
|
250
469
|
change(
|
|
251
470
|
self.object,
|
|
252
471
|
"unlink",
|
|
253
|
-
(
|
|
472
|
+
(path_of(dentry),),
|
|
254
473
|
FilesystemError::ReadOnlyFilesystem,
|
|
255
474
|
)
|
|
256
475
|
}
|
|
257
476
|
|
|
258
|
-
/// `rename(from, to
|
|
259
|
-
///
|
|
260
|
-
///
|
|
261
|
-
fn rename(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
+
}
|
|
270
490
|
change(
|
|
271
491
|
self.object,
|
|
272
492
|
"rename",
|
|
273
|
-
(from, to
|
|
493
|
+
(path_of(from), path_of(to)),
|
|
274
494
|
FilesystemError::ReadOnlyFilesystem,
|
|
275
495
|
)
|
|
276
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
|
+
}
|
|
277
530
|
|
|
278
|
-
///
|
|
279
|
-
|
|
280
|
-
|
|
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(())
|
|
281
535
|
}
|
|
282
536
|
}
|
|
283
537
|
|
|
284
538
|
/// One file the Ruby object opened, which only the guest's descriptor
|
|
285
539
|
/// refers to.
|
|
286
540
|
struct RubyFile {
|
|
287
|
-
file: PinnedValue
|
|
541
|
+
file: Arc<PinnedValue>,
|
|
288
542
|
closed: bool,
|
|
289
543
|
}
|
|
290
544
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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),
|
|
296
582
|
}
|
|
297
583
|
}
|
|
298
584
|
|
|
299
|
-
///
|
|
300
|
-
///
|
|
301
|
-
fn
|
|
302
|
-
|
|
303
|
-
change_if_answered(self.file.get(), "close")
|
|
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
|
|
304
589
|
}
|
|
305
590
|
}
|
|
306
591
|
|
|
307
|
-
impl
|
|
308
|
-
fn
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|ruby, file| {
|
|
314
|
-
// `IO#pread` raises `EOFError` at the end of a file, which reads as nothing.
|
|
315
|
-
let read: Option<RString> = match file.funcall("pread", (buf.len(), offset)) {
|
|
316
|
-
Err(err) if err.is_kind_of(ruby.exception_eof_error()) => None,
|
|
317
|
-
read => read?,
|
|
318
|
-
};
|
|
319
|
-
let Some(read) = read else { return Ok(0) };
|
|
320
|
-
// SAFETY: the bytes are copied out before Ruby runs again.
|
|
321
|
-
let bytes = unsafe { read.as_slice() };
|
|
322
|
-
let len = bytes.len().min(buf.len());
|
|
323
|
-
buf[..len].copy_from_slice(&bytes[..len]);
|
|
324
|
-
Ok(len)
|
|
325
|
-
},
|
|
326
|
-
)
|
|
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
|
+
}
|
|
327
598
|
}
|
|
328
599
|
|
|
329
|
-
|
|
330
|
-
|
|
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(
|
|
331
607
|
self.file.get(),
|
|
332
608
|
"pwrite",
|
|
333
609
|
FilesystemError::ReadOnlyFilesystem,
|
|
334
|
-
|ruby, file| file.funcall("pwrite", (ruby.str_from_slice(buf),
|
|
610
|
+
|ruby, file| file.funcall("pwrite", (ruby.str_from_slice(buf), pos)),
|
|
335
611
|
)
|
|
336
612
|
.and_then(|written: usize| match written {
|
|
337
613
|
written if written <= buf.len() => Ok(written),
|
|
@@ -339,54 +615,86 @@ impl File for RubyFile {
|
|
|
339
615
|
"pwrite answered {written} bytes written of the {} it was handed",
|
|
340
616
|
buf.len()
|
|
341
617
|
))),
|
|
342
|
-
})
|
|
618
|
+
})?;
|
|
619
|
+
iocb.pos += written as u64;
|
|
620
|
+
Ok(written)
|
|
343
621
|
}
|
|
344
622
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
/// a file opened to append. A file lacking it cannot append, since its
|
|
348
|
-
/// `size` and `pwrite` are two steps another writer can land between.
|
|
349
|
-
fn append(&mut self, buf: &[u8]) -> Result<(u64, usize), FilesystemError> {
|
|
350
|
-
ask(
|
|
623
|
+
fn append(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
624
|
+
let end: u64 = ask(
|
|
351
625
|
self.file.get(),
|
|
352
626
|
"append",
|
|
353
627
|
FilesystemError::Unsupported,
|
|
354
628
|
|ruby, file| file.funcall("append", (ruby.str_from_slice(buf),)),
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
None => Err(FilesystemError::Other(format!(
|
|
629
|
+
)?;
|
|
630
|
+
if end < buf.len() as u64 {
|
|
631
|
+
return Err(FilesystemError::Other(format!(
|
|
359
632
|
"append answered the file ending at {end}, before the {} bytes it wrote",
|
|
360
633
|
buf.len()
|
|
361
|
-
)))
|
|
362
|
-
}
|
|
634
|
+
)));
|
|
635
|
+
}
|
|
636
|
+
iocb.pos = end;
|
|
637
|
+
Ok(buf.len())
|
|
363
638
|
}
|
|
364
639
|
|
|
365
|
-
|
|
366
|
-
|
|
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(
|
|
367
662
|
self.file.get(),
|
|
368
|
-
"
|
|
663
|
+
"pread",
|
|
369
664
|
FilesystemError::Unsupported,
|
|
370
|
-
|
|
|
371
|
-
|
|
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)
|
|
372
681
|
}
|
|
373
682
|
|
|
374
|
-
fn
|
|
375
|
-
|
|
376
|
-
self.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
)
|
|
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)
|
|
381
689
|
}
|
|
382
690
|
|
|
383
691
|
/// A file lacking `fsync` has nothing of its own to flush.
|
|
384
|
-
fn
|
|
692
|
+
fn fsync(&mut self, _datasync: bool) -> Result<(), FilesystemError> {
|
|
385
693
|
change_if_answered(self.file.get(), "fsync")
|
|
386
694
|
}
|
|
387
695
|
|
|
388
|
-
fn
|
|
389
|
-
self.
|
|
696
|
+
fn flush(&mut self) -> Result<(), FilesystemError> {
|
|
697
|
+
self.close()
|
|
390
698
|
}
|
|
391
699
|
}
|
|
392
700
|
|
|
@@ -394,7 +702,7 @@ impl File for RubyFile {
|
|
|
394
702
|
impl Drop for RubyFile {
|
|
395
703
|
fn drop(&mut self) {
|
|
396
704
|
if !self.closed {
|
|
397
|
-
let _ = self.
|
|
705
|
+
let _ = self.close();
|
|
398
706
|
}
|
|
399
707
|
}
|
|
400
708
|
}
|