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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +217 -10
  4. data/Cargo.lock +139 -141
  5. data/README.md +96 -574
  6. data/ROADMAP.md +2 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +269 -1
  8. data/crates/kobako-runtime/Cargo.toml +25 -15
  9. data/crates/kobako-runtime/README.md +7 -3
  10. data/crates/kobako-runtime/src/dispatch.rs +1 -1
  11. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  12. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  13. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  14. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  15. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  16. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  17. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  18. data/crates/kobako-runtime/src/filesystem/{directory/spelling.rs → bind/lookup.rs} +91 -49
  19. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  20. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  21. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +227 -734
  29. data/crates/kobako-runtime/src/filesystem/overlay.rs +2649 -1135
  30. data/crates/kobako-runtime/src/filesystem/path.rs +2 -41
  31. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  32. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  33. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  37. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  38. data/crates/kobako-runtime/src/filesystem.rs +21 -387
  39. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  40. data/crates/kobako-runtime/src/lib.rs +11 -6
  41. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  42. data/crates/kobako-runtime/src/locking.rs +218 -0
  43. data/crates/kobako-runtime/src/mount.rs +31 -104
  44. data/crates/kobako-runtime/src/profile.rs +137 -1
  45. data/crates/kobako-runtime/src/sched.rs +210 -0
  46. data/crates/kobako-runtime/src/security.rs +696 -0
  47. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  48. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  49. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  50. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  51. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  52. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  53. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  54. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  55. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  56. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  57. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  58. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  59. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  60. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  61. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  62. data/crates/kobako-runtime/src/vfs.rs +60 -0
  63. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  64. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  65. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  66. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  67. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  68. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  70. data/crates/kobako-transport/CHANGELOG.md +8 -2
  71. data/crates/kobako-transport/Cargo.toml +3 -3
  72. data/crates/kobako-transport/README.md +6 -5
  73. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  74. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  75. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  76. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  77. data/crates/kobako-transport/src/envelope/fault.rs +2 -2
  78. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  79. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  80. data/crates/kobako-transport/src/envelope/reply.rs +2 -2
  81. data/crates/kobako-transport/src/envelope/request.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  83. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  84. data/crates/kobako-transport/src/envelope.rs +2 -2
  85. data/crates/kobako-wasmtime/CHANGELOG.md +154 -8
  86. data/crates/kobako-wasmtime/Cargo.toml +15 -14
  87. data/crates/kobako-wasmtime/README.md +9 -5
  88. data/crates/kobako-wasmtime/src/abi.rs +12 -20
  89. data/crates/kobako-wasmtime/src/ambient.rs +1 -2
  90. data/crates/kobako-wasmtime/src/cache.rs +5 -5
  91. data/crates/kobako-wasmtime/src/config.rs +22 -7
  92. data/crates/kobako-wasmtime/src/descriptors.rs +133 -100
  93. data/crates/kobako-wasmtime/src/driver.rs +29 -96
  94. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  95. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  96. data/crates/kobako-wasmtime/src/imports.rs +14 -8
  97. data/crates/kobako-wasmtime/src/instance_pre.rs +3 -3
  98. data/crates/kobako-wasmtime/src/invocation.rs +26 -46
  99. data/crates/kobako-wasmtime/src/lib.rs +1 -4
  100. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  101. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +69 -46
  102. data/crates/kobako-wasmtime/src/preview1/differential.rs +121 -7
  103. data/crates/kobako-wasmtime/src/preview1/errno.rs +74 -206
  104. data/crates/kobako-wasmtime/src/preview1/fd.rs +1248 -500
  105. data/crates/kobako-wasmtime/src/preview1/memory.rs +117 -150
  106. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  107. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +1939 -672
  108. data/crates/kobako-wasmtime/src/preview1/path.rs +230 -349
  109. data/crates/kobako-wasmtime/src/preview1/poll.rs +148 -100
  110. data/crates/kobako-wasmtime/src/preview1/sock.rs +6 -23
  111. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  112. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  113. data/crates/kobako-wasmtime/src/preview1.rs +464 -413
  114. data/crates/kobako-wasmtime/src/trap.rs +9 -9
  115. data/crates/kobako-wasmtime/src/wasi.rs +53 -37
  116. data/crates/kobako-wasmtime/tests/{guest_file.rs → integration/guest_file.rs} +232 -201
  117. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  118. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +1 -1
  119. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +1 -1
  120. data/crates/kobako-wasmtime/tests/{unanswered_call.rs → integration/unanswered_call.rs} +1 -1
  121. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +1 -1
  122. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  123. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  124. data/data/kobako.wasm +0 -0
  125. data/ext/kobako/Cargo.toml +1 -1
  126. data/ext/kobako/src/runtime/bridge.rs +1 -1
  127. data/ext/kobako/src/runtime/errors.rs +1 -1
  128. data/ext/kobako/src/runtime/escape.rs +28 -14
  129. data/ext/kobako/src/runtime/filesystem/grants.rs +21 -5
  130. data/ext/kobako/src/runtime/filesystem/ruby.rs +464 -156
  131. data/ext/kobako/src/runtime/filesystem.rs +109 -137
  132. data/ext/kobako/src/runtime/gvl.rs +43 -8
  133. data/ext/kobako/src/runtime/snapshot.rs +1 -1
  134. data/ext/kobako/src/runtime.rs +6 -1
  135. data/lib/kobako/catalog/extensions.rb +5 -5
  136. data/lib/kobako/catalog/flags.rb +2 -2
  137. data/lib/kobako/catalog/handles.rb +6 -6
  138. data/lib/kobako/catalog/mount.rb +2 -10
  139. data/lib/kobako/catalog/services.rb +2 -2
  140. data/lib/kobako/catalog/snippets.rb +7 -7
  141. data/lib/kobako/catalog.rb +1 -1
  142. data/lib/kobako/codec/decoder.rb +10 -10
  143. data/lib/kobako/codec/ext_types.rb +3 -5
  144. data/lib/kobako/codec/handle_walk.rb +4 -6
  145. data/lib/kobako/codec/nesting.rb +7 -7
  146. data/lib/kobako/codec/utils.rb +1 -1
  147. data/lib/kobako/codec/value_budget.rb +3 -3
  148. data/lib/kobako/context.rb +5 -5
  149. data/lib/kobako/filesystem.rb +44 -13
  150. data/lib/kobako/payload/arguments.rb +2 -2
  151. data/lib/kobako/pool.rb +4 -4
  152. data/lib/kobako/sandbox.rb +28 -28
  153. data/lib/kobako/sandbox_options.rb +2 -3
  154. data/lib/kobako/snippet/bytecode.rb +1 -1
  155. data/lib/kobako/snippet/source.rb +1 -1
  156. data/lib/kobako/transport/dispatcher.rb +5 -5
  157. data/lib/kobako/transport/exposure.rb +2 -2
  158. data/lib/kobako/transport/reflection.rb +7 -7
  159. data/lib/kobako/transport/run.rb +5 -5
  160. data/lib/kobako/transport/yielder.rb +1 -1
  161. data/lib/kobako/version.rb +1 -1
  162. data/lib/kobako.rb +34 -0
  163. data/release-please-config.json +1 -0
  164. data/sig/kobako/catalog/extensions.rbs +3 -3
  165. data/sig/kobako/catalog/handles.rbs +3 -3
  166. data/sig/kobako/catalog/mount.rbs +0 -4
  167. data/sig/kobako/catalog/services.rbs +1 -1
  168. data/sig/kobako/catalog/snippets.rbs +3 -3
  169. data/sig/kobako/catalog.rbs +1 -1
  170. data/sig/kobako/codec/decoder.rbs +1 -1
  171. data/sig/kobako/codec/handle_walk.rbs +1 -1
  172. data/sig/kobako/codec/nesting.rbs +2 -2
  173. data/sig/kobako/codec/utils.rbs +1 -1
  174. data/sig/kobako/codec/value_budget.rbs +1 -1
  175. data/sig/kobako/codec.rbs +1 -2
  176. data/sig/kobako/context.rbs +1 -1
  177. data/sig/kobako/filesystem.rbs +19 -14
  178. data/sig/kobako/guest_yielder.rbs +1 -1
  179. data/sig/kobako/payload/arguments.rbs +1 -1
  180. data/sig/kobako/pool.rbs +2 -2
  181. data/sig/kobako/runtime.rbs +1 -1
  182. data/sig/kobako/sandbox.rbs +3 -5
  183. data/sig/kobako/sandbox_options.rbs +1 -1
  184. data/sig/kobako/transport/dispatcher.rbs +2 -2
  185. data/sig/kobako/transport/exposure.rbs +1 -1
  186. data/sig/kobako/transport/reflection.rbs +2 -2
  187. data/sig/kobako/transport/run.rbs +3 -3
  188. data/sig/kobako/transport.rbs +1 -1
  189. metadata +67 -19
  190. data/crates/kobako-runtime/src/filesystem/directory.rs +0 -1082
  191. data/crates/kobako-runtime/src/filesystem/memory.rs +0 -1100
  192. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +0 -88
  193. data/crates/kobako-runtime/src/filesystem/temporary.rs +0 -167
  194. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  195. data/crates/kobako-wasmtime/src/dispatch.rs +0 -125
  196. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -271
  197. data/crates/kobako-wasmtime/src/io.rs +0 -89
  198. data/crates/kobako-wasmtime/src/preview1/args.rs +0 -60
  199. data/crates/kobako-wasmtime/src/preview1/beneath.rs +0 -188
  200. /data/crates/kobako-wasmtime/tests/{compile_posture.rs → integration/compile_posture.rs} +0 -0
  201. /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::{kwargs, prelude::*, Error as MagnusError, RClass, RModule, RString, Ruby, Value};
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
- DirEntry, File, FileType, Filesystem, FilesystemError, Hold, Metadata, OpenOptions, Rename,
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: Metadata::unique_device(),
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 `options`, read from Ruby since their
183
+ /// `File::Constants` flags for `how`, read from Ruby since their
115
184
  /// values differ by platform.
116
- fn to_flags(ruby: &Ruby, options: &OpenOptions) -> Result<i64, MagnusError> {
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 (options.read, options.write || options.truncate) {
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 options.create || options.create_new {
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 options.truncate {
199
+ if how.truncate {
134
200
  flags |= flag("TRUNC")?;
135
201
  }
136
- if options.nofollow {
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 Filesystem for RubyFilesystem {
151
- fn open(&self, path: &str, options: &OpenOptions) -> Result<Box<dyn File>, FilesystemError> {
152
- ask(
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, options)?))?;
158
- Ok(Box::new(RubyFile::pin(ruby, file)) as Box<dyn File>)
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
- fn metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
164
- let own_dev = self.dev;
165
- ask(
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
- "stat",
292
+ "children",
168
293
  FilesystemError::Unsupported,
169
- |_, fs| {
170
- let stat: Value = fs.funcall("stat", (path,))?;
171
- let metadata = if stat.funcall::<_, _, bool>("directory?", ())? {
172
- Metadata::new(FileType::Directory, 0)
173
- } else {
174
- Metadata::new(FileType::File, stat.funcall("size", ())?)
175
- };
176
- // A stat names its identity as `File::Stat` does; without a
177
- // `dev` the Filesystem stands for a device of its own, so it
178
- // shares an identity with no other Filesystem.
179
- let dev = if stat.respond_to("dev", false)? {
180
- stat.funcall("dev", ())?
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
- own_dev
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
- /// A Ruby object answers `stat` alone, so it holds no links.
190
- fn symlink_metadata(&self, path: &str) -> Result<Metadata, FilesystemError> {
191
- self.metadata(path)
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
- /// A Ruby object matches names exactly, as its contract asks.
195
- fn stored_path(&self, path: &str) -> Result<String, FilesystemError> {
196
- Ok(path.to_string())
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
- /// A Ruby object holds no links to follow.
200
- fn canonicalize_beneath(&self, _base: &str, path: &str) -> Result<String, FilesystemError> {
201
- Ok(path.to_string())
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 holds no links a Policy needs kept from being followed.
205
- fn for_policy(&self) -> Option<Arc<dyn Filesystem>> {
206
- None
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 Ruby object keeps an `ino` its own while open, as its contract
210
- /// asks, so nothing is held.
211
- fn hold_directory(&self, _path: &str) -> Result<Option<Hold>, FilesystemError> {
212
- Ok(None)
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
- fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>, FilesystemError> {
216
- let names: Vec<String> = ask(
217
- self.object,
218
- "children",
219
- FilesystemError::Unsupported,
220
- |_, fs| fs.funcall("children", (path,)),
221
- )?;
222
- names
223
- .into_iter()
224
- .map(|name| {
225
- let file_type = self.metadata(&join(path, &name))?.file_type;
226
- Ok(DirEntry::new(name, file_type))
227
- })
228
- .collect()
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 create_dir(&self, path: &str) -> Result<(), FilesystemError> {
450
+ fn mkdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
232
451
  change(
233
452
  self.object,
234
453
  "mkdir",
235
- (path,),
454
+ (path_of(dentry),),
236
455
  FilesystemError::ReadOnlyFilesystem,
237
456
  )
238
457
  }
239
458
 
240
- fn remove_dir(&self, path: &str) -> Result<(), FilesystemError> {
459
+ fn rmdir(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
241
460
  change(
242
461
  self.object,
243
462
  "rmdir",
244
- (path,),
463
+ (path_of(dentry),),
245
464
  FilesystemError::ReadOnlyFilesystem,
246
465
  )
247
466
  }
248
467
 
249
- fn remove_file(&self, path: &str) -> Result<(), FilesystemError> {
468
+ fn unlink(&self, dentry: &Dentry) -> Result<(), FilesystemError> {
250
469
  change(
251
470
  self.object,
252
471
  "unlink",
253
- (path,),
472
+ (path_of(dentry),),
254
473
  FilesystemError::ReadOnlyFilesystem,
255
474
  )
256
475
  }
257
476
 
258
- /// `rename(from, to, directory:)`: `true` moves only a directory,
259
- /// `false` only a file, `nil` either. What stands at `from` meanwhile
260
- /// is the object's to keep still.
261
- fn rename(&self, from: &str, to: &str, rename: Rename) -> Result<(), FilesystemError> {
262
- // A move the keyword cannot tell the object is refused rather than
263
- // widened into one of either kind.
264
- let directory = match rename {
265
- Rename::Any => None,
266
- Rename::File => Some(false),
267
- Rename::Directory => Some(true),
268
- _ => return Err(FilesystemError::Unsupported),
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, kwargs!("directory" => directory)),
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
- /// Every Invocation shares the Ruby object.
279
- fn for_invocation(&self) -> Option<Arc<dyn Filesystem>> {
280
- None
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
- impl RubyFile {
292
- fn pin(ruby: &Ruby, file: Value) -> Self {
293
- Self {
294
- file: PinnedValue::new(ruby, file),
295
- closed: false,
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
- /// Ask the file to close, a file lacking `close` having nothing to
300
- /// release.
301
- fn release(&mut self) -> Result<(), FilesystemError> {
302
- self.closed = true;
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 File for RubyFile {
308
- fn read_at(&mut self, buf: &mut [u8], offset: u64) -> Result<usize, FilesystemError> {
309
- ask(
310
- self.file.get(),
311
- "pread",
312
- FilesystemError::Unsupported,
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
- fn write_at(&mut self, buf: &[u8], offset: u64) -> Result<usize, FilesystemError> {
330
- ask(
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), offset)),
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
- /// The file's `append` writes all of `buf` at its end in one step and
346
- /// answers where the file then ends, as `IO#pos` does after a write to
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
- .and_then(|end: u64| match end.checked_sub(buf.len() as u64) {
357
- Some(at) => Ok((at, buf.len())),
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
- fn metadata(&self) -> Result<Metadata, FilesystemError> {
366
- ask(
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
- "size",
663
+ "pread",
369
664
  FilesystemError::Unsupported,
370
- |_, file| Ok(Metadata::new(FileType::File, file.funcall("size", ())?)),
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 set_len(&mut self, len: u64) -> Result<(), FilesystemError> {
375
- change(
376
- self.file.get(),
377
- "truncate",
378
- (len,),
379
- FilesystemError::ReadOnlyFilesystem,
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 sync(&mut self) -> Result<(), FilesystemError> {
692
+ fn fsync(&mut self, _datasync: bool) -> Result<(), FilesystemError> {
385
693
  change_if_answered(self.file.get(), "fsync")
386
694
  }
387
695
 
388
- fn close(mut self: Box<Self>) -> Result<(), FilesystemError> {
389
- self.release()
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.release();
705
+ let _ = self.close();
398
706
  }
399
707
  }
400
708
  }