@wasm-oj/server 0.2.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.
- package/LICENSE +21 -0
- package/README.md +6 -0
- package/THIRD_PARTY_NOTICES.md +302 -0
- package/crates/runtime-core/Cargo.lock +5099 -0
- package/crates/runtime-core/Cargo.toml +66 -0
- package/crates/runtime-core/README.md +47 -0
- package/crates/runtime-core/src/bin/wasm-oj-compiler.rs +418 -0
- package/crates/runtime-core/src/bin/wasm-oj-runner.rs +294 -0
- package/crates/runtime-core/src/capabilities.rs +118 -0
- package/crates/runtime-core/src/compiler.rs +658 -0
- package/crates/runtime-core/src/contract.rs +5 -0
- package/crates/runtime-core/src/deterministic.rs +1051 -0
- package/crates/runtime-core/src/error.rs +58 -0
- package/crates/runtime-core/src/filesystem.rs +547 -0
- package/crates/runtime-core/src/filesystem_quota.rs +167 -0
- package/crates/runtime-core/src/go_compiler_session.rs +297 -0
- package/crates/runtime-core/src/interactive.rs +1019 -0
- package/crates/runtime-core/src/judge_package.rs +1539 -0
- package/crates/runtime-core/src/lib.rs +98 -0
- package/crates/runtime-core/src/memory.rs +84 -0
- package/crates/runtime-core/src/meter.rs +549 -0
- package/crates/runtime-core/src/module_imports.rs +149 -0
- package/crates/runtime-core/src/module_policy.rs +714 -0
- package/crates/runtime-core/src/output.rs +204 -0
- package/crates/runtime-core/src/run/mod.rs +208 -0
- package/crates/runtime-core/src/run/native.rs +260 -0
- package/crates/runtime-core/src/run/web.rs +229 -0
- package/crates/runtime-core/src/run/web_runtime.rs +109 -0
- package/crates/runtime-core/src/types.rs +268 -0
- package/crates/runtime-core/src/web.rs +83 -0
- package/dist/chunks/go-toolchain-Dbt-lp2L.js +426 -0
- package/dist/chunks/java-toolchain-DajoRCHu.js +44 -0
- package/dist/chunks/python-toolchain-Dx834o2A.js +4 -0
- package/dist/chunks/rust-toolchain-CJ3sMxPE.js +252 -0
- package/dist/chunks/toolchains-C6KuA1yM.js +224 -0
- package/dist/go-stage.mjs +193 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.js +4393 -0
- package/dist/java-stage.mjs +111 -0
- package/dist/python-stage.mjs +90 -0
- package/dist/rustc-stage.mjs +301 -0
- package/dist/server-build-stage.mjs +2564 -0
- package/dist/server-runner-stage.mjs +155 -0
- package/licenses/fflate-MIT.txt +21 -0
- package/licenses/runtime-core-dependencies.html +6253 -0
- package/licenses/runtime-core-dependencies.json +3041 -0
- package/licenses/wasmer-sdk-MIT.txt +21 -0
- package/licenses/wasmer-sdk-dependencies.html +6901 -0
- package/licenses/wasmer-sdk-dependencies.json +3013 -0
- package/package.json +70 -0
- package/rust-toolchain.toml +5 -0
- package/testdata/wojjdg02-v2-text.hex +1 -0
- package/vendor/shared-buffer/Cargo.toml +22 -0
- package/vendor/shared-buffer/LICENSE_APACHE.md +176 -0
- package/vendor/shared-buffer/LICENSE_MIT.md +25 -0
- package/vendor/shared-buffer/README.md +34 -0
- package/vendor/shared-buffer/src/lib.rs +58 -0
- package/vendor/shared-buffer/src/mmap.rs +250 -0
- package/vendor/shared-buffer/src/owned.rs +389 -0
- package/vendor/virtual-fs/Cargo.toml +181 -0
- package/vendor/virtual-fs/LICENSE +25 -0
- package/vendor/virtual-fs/src/arc_box_file.rs +142 -0
- package/vendor/virtual-fs/src/arc_file.rs +182 -0
- package/vendor/virtual-fs/src/arc_fs.rs +68 -0
- package/vendor/virtual-fs/src/buffer_file.rs +103 -0
- package/vendor/virtual-fs/src/builder.rs +232 -0
- package/vendor/virtual-fs/src/combine_file.rs +101 -0
- package/vendor/virtual-fs/src/cow_file.rs +345 -0
- package/vendor/virtual-fs/src/dual_write_file.rs +113 -0
- package/vendor/virtual-fs/src/empty_fs.rs +81 -0
- package/vendor/virtual-fs/src/filesystems.rs +108 -0
- package/vendor/virtual-fs/src/host_fs.rs +1390 -0
- package/vendor/virtual-fs/src/lib.rs +782 -0
- package/vendor/virtual-fs/src/limiter.rs +252 -0
- package/vendor/virtual-fs/src/mem_fs/file.rs +1799 -0
- package/vendor/virtual-fs/src/mem_fs/file_opener.rs +941 -0
- package/vendor/virtual-fs/src/mem_fs/filesystem.rs +2134 -0
- package/vendor/virtual-fs/src/mem_fs/mod.rs +245 -0
- package/vendor/virtual-fs/src/mem_fs/offloaded_file.rs +474 -0
- package/vendor/virtual-fs/src/mem_fs/stdio.rs +318 -0
- package/vendor/virtual-fs/src/mount_fs.rs +2225 -0
- package/vendor/virtual-fs/src/null_file.rs +87 -0
- package/vendor/virtual-fs/src/ops.rs +364 -0
- package/vendor/virtual-fs/src/overlay_fs.rs +2216 -0
- package/vendor/virtual-fs/src/passthru_fs.rs +119 -0
- package/vendor/virtual-fs/src/pipe.rs +603 -0
- package/vendor/virtual-fs/src/random_file.rs +88 -0
- package/vendor/virtual-fs/src/special_file.rs +108 -0
- package/vendor/virtual-fs/src/static_file.rs +133 -0
- package/vendor/virtual-fs/src/static_fs.rs +460 -0
- package/vendor/virtual-fs/src/tmp_fs.rs +95 -0
- package/vendor/virtual-fs/src/trace_fs.rs +258 -0
- package/vendor/virtual-fs/src/webc_volume_fs.rs +829 -0
- package/vendor/virtual-fs/src/zero_file.rs +90 -0
|
@@ -0,0 +1,1799 @@
|
|
|
1
|
+
//! This module contains the `FileHandle` and `File`
|
|
2
|
+
//! implementations. They aren't exposed to the public API. Only
|
|
3
|
+
//! `FileHandle` can be used through the `VirtualFile` trait object.
|
|
4
|
+
|
|
5
|
+
use futures::future::BoxFuture;
|
|
6
|
+
use shared_buffer::OwnedBuffer;
|
|
7
|
+
use tokio::io::AsyncRead;
|
|
8
|
+
use tokio::io::{AsyncSeek, AsyncWrite};
|
|
9
|
+
|
|
10
|
+
use self::offloaded_file::OffloadWrite;
|
|
11
|
+
|
|
12
|
+
use super::*;
|
|
13
|
+
use crate::limiter::TrackedVec;
|
|
14
|
+
use crate::{CopyOnWriteFile, FsError, Result, VirtualFile};
|
|
15
|
+
use std::cmp;
|
|
16
|
+
use std::convert::TryInto;
|
|
17
|
+
use std::fmt;
|
|
18
|
+
use std::io;
|
|
19
|
+
use std::pin::Pin;
|
|
20
|
+
use std::task::{Context, Poll};
|
|
21
|
+
|
|
22
|
+
/// A file handle. The file system doesn't return the [`File`] type
|
|
23
|
+
/// directly, but rather this `FileHandle` type, which contains the
|
|
24
|
+
/// inode, the flags, and (a light copy of) the filesystem. For each
|
|
25
|
+
/// operations, it is checked that the permissions allow the
|
|
26
|
+
/// operations to be executed, and then it is checked that the file
|
|
27
|
+
/// still exists in the file system. After that, the operation is
|
|
28
|
+
/// delegated to the file itself.
|
|
29
|
+
pub(super) struct FileHandle {
|
|
30
|
+
inode: Inode,
|
|
31
|
+
filesystem: FileSystem,
|
|
32
|
+
lifecycle: Arc<FileLifecycle>,
|
|
33
|
+
readable: bool,
|
|
34
|
+
writable: bool,
|
|
35
|
+
append_mode: bool,
|
|
36
|
+
cursor: u64,
|
|
37
|
+
arc_file: Option<Result<Box<dyn VirtualFile + Send + Sync + 'static>>>,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl Clone for FileHandle {
|
|
41
|
+
fn clone(&self) -> Self {
|
|
42
|
+
self.lifecycle.opened();
|
|
43
|
+
Self {
|
|
44
|
+
inode: self.inode,
|
|
45
|
+
filesystem: self.filesystem.clone(),
|
|
46
|
+
lifecycle: self.lifecycle.clone(),
|
|
47
|
+
readable: self.readable,
|
|
48
|
+
writable: self.writable,
|
|
49
|
+
append_mode: self.append_mode,
|
|
50
|
+
cursor: self.cursor,
|
|
51
|
+
arc_file: None,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl FileHandle {
|
|
57
|
+
pub(super) fn new_opened(
|
|
58
|
+
inode: Inode,
|
|
59
|
+
filesystem: FileSystem,
|
|
60
|
+
lifecycle: Arc<FileLifecycle>,
|
|
61
|
+
readable: bool,
|
|
62
|
+
writable: bool,
|
|
63
|
+
append_mode: bool,
|
|
64
|
+
cursor: u64,
|
|
65
|
+
) -> Self {
|
|
66
|
+
Self {
|
|
67
|
+
inode,
|
|
68
|
+
filesystem,
|
|
69
|
+
lifecycle,
|
|
70
|
+
readable,
|
|
71
|
+
writable,
|
|
72
|
+
append_mode,
|
|
73
|
+
cursor,
|
|
74
|
+
arc_file: None,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fn lazy_load_arc_file_mut(&mut self) -> Result<&mut dyn VirtualFile> {
|
|
79
|
+
if self.arc_file.is_none() {
|
|
80
|
+
let fs = match self.filesystem.inner.read() {
|
|
81
|
+
Ok(fs) => fs,
|
|
82
|
+
_ => return Err(FsError::EntryNotFound),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let inode = fs.storage.get(self.inode);
|
|
86
|
+
match inode {
|
|
87
|
+
Some(Node::ArcFile(node)) => {
|
|
88
|
+
self.arc_file.replace(
|
|
89
|
+
node.fs
|
|
90
|
+
.new_open_options()
|
|
91
|
+
.read(self.readable)
|
|
92
|
+
.write(self.writable)
|
|
93
|
+
.append(self.append_mode)
|
|
94
|
+
.open(node.path.as_path()),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
_ => return Err(FsError::EntryNotFound),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
Ok(self
|
|
101
|
+
.arc_file
|
|
102
|
+
.as_mut()
|
|
103
|
+
.unwrap()
|
|
104
|
+
.as_mut()
|
|
105
|
+
.map_err(|err| *err)?
|
|
106
|
+
.as_mut())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn write_cursor(&self, file_size: u64) -> u64 {
|
|
110
|
+
if self.append_mode {
|
|
111
|
+
file_size
|
|
112
|
+
} else {
|
|
113
|
+
self.cursor
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
impl VirtualFile for FileHandle {
|
|
119
|
+
fn last_accessed(&self) -> u64 {
|
|
120
|
+
let fs = match self.filesystem.inner.read() {
|
|
121
|
+
Ok(fs) => fs,
|
|
122
|
+
_ => return 0,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let inode = fs.storage.get(self.inode);
|
|
126
|
+
match inode {
|
|
127
|
+
Some(node) => node.metadata().accessed,
|
|
128
|
+
_ => 0,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fn last_modified(&self) -> u64 {
|
|
133
|
+
let fs = match self.filesystem.inner.read() {
|
|
134
|
+
Ok(fs) => fs,
|
|
135
|
+
_ => return 0,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
let inode = fs.storage.get(self.inode);
|
|
139
|
+
match inode {
|
|
140
|
+
Some(node) => node.metadata().modified,
|
|
141
|
+
_ => 0,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn created_time(&self) -> u64 {
|
|
146
|
+
let fs = match self.filesystem.inner.read() {
|
|
147
|
+
Ok(fs) => fs,
|
|
148
|
+
_ => return 0,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
let inode = fs.storage.get(self.inode);
|
|
152
|
+
let node = match inode {
|
|
153
|
+
Some(node) => node,
|
|
154
|
+
_ => return 0,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
node.metadata().created
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
|
|
161
|
+
let mut fs = match self.filesystem.inner.write() {
|
|
162
|
+
Ok(fs) => fs,
|
|
163
|
+
_ => return Err(crate::FsError::Lock),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
167
|
+
if let Some(node) = inode {
|
|
168
|
+
if let Some(atime) = atime {
|
|
169
|
+
node.metadata_mut().accessed = atime;
|
|
170
|
+
}
|
|
171
|
+
if let Some(mtime) = mtime {
|
|
172
|
+
node.metadata_mut().modified = mtime;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return Ok(());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
Err(crate::FsError::UnknownError)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fn size(&self) -> u64 {
|
|
182
|
+
let fs = match self.filesystem.inner.read() {
|
|
183
|
+
Ok(fs) => fs,
|
|
184
|
+
_ => return 0,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
let inode = fs.storage.get(self.inode);
|
|
188
|
+
match inode {
|
|
189
|
+
Some(Node::File(node)) => node.file.len().try_into().unwrap_or(0),
|
|
190
|
+
Some(Node::OffloadedFile(node)) => node.file.len(),
|
|
191
|
+
Some(Node::ReadOnlyFile(node)) => node.file.len().try_into().unwrap_or(0),
|
|
192
|
+
Some(Node::CustomFile(node)) => {
|
|
193
|
+
let file = node.file.lock().unwrap();
|
|
194
|
+
file.size()
|
|
195
|
+
}
|
|
196
|
+
Some(Node::ArcFile(node)) => match self.arc_file.as_ref() {
|
|
197
|
+
Some(file) => file.as_ref().map(|file| file.size()).unwrap_or(0),
|
|
198
|
+
None => node
|
|
199
|
+
.fs
|
|
200
|
+
.new_open_options()
|
|
201
|
+
.read(self.readable)
|
|
202
|
+
.write(self.writable)
|
|
203
|
+
.append(self.append_mode)
|
|
204
|
+
.open(node.path.as_path())
|
|
205
|
+
.map(|file| file.size())
|
|
206
|
+
.unwrap_or(0),
|
|
207
|
+
},
|
|
208
|
+
_ => 0,
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fn set_len(&mut self, new_size: u64) -> Result<()> {
|
|
213
|
+
let mut fs = self.filesystem.inner.write().map_err(|_| FsError::Lock)?;
|
|
214
|
+
|
|
215
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
216
|
+
match inode {
|
|
217
|
+
Some(Node::File(FileNode { file, metadata, .. })) => {
|
|
218
|
+
file.buffer
|
|
219
|
+
.resize(new_size.try_into().map_err(|_| FsError::UnknownError)?, 0)?;
|
|
220
|
+
metadata.len = new_size;
|
|
221
|
+
}
|
|
222
|
+
Some(Node::OffloadedFile(OffloadedFileNode { file, metadata, .. })) => {
|
|
223
|
+
file.resize(new_size, 0);
|
|
224
|
+
metadata.len = new_size;
|
|
225
|
+
}
|
|
226
|
+
Some(Node::CustomFile(node)) => {
|
|
227
|
+
let mut file = node.file.lock().unwrap();
|
|
228
|
+
file.set_len(new_size)?;
|
|
229
|
+
node.metadata.len = new_size;
|
|
230
|
+
}
|
|
231
|
+
Some(Node::ReadOnlyFile { .. }) => return Err(FsError::PermissionDenied),
|
|
232
|
+
Some(Node::ArcFile { .. }) => {
|
|
233
|
+
drop(fs);
|
|
234
|
+
let file = self.lazy_load_arc_file_mut()?;
|
|
235
|
+
file.set_len(new_size)?;
|
|
236
|
+
}
|
|
237
|
+
_ => return Err(FsError::NotAFile),
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Update current cursor position if file got truncated
|
|
241
|
+
self.cursor = self.cursor.min(new_size);
|
|
242
|
+
Ok(())
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
fn unlink(&mut self) -> Result<()> {
|
|
246
|
+
let filesystem = self.filesystem.clone();
|
|
247
|
+
let inode = self.inode;
|
|
248
|
+
|
|
249
|
+
let (inode_of_parent, position, inode_of_file) = {
|
|
250
|
+
// Read lock.
|
|
251
|
+
let fs = filesystem.inner.read().map_err(|_| FsError::Lock)?;
|
|
252
|
+
|
|
253
|
+
// The inode of the file.
|
|
254
|
+
let inode_of_file = inode;
|
|
255
|
+
|
|
256
|
+
// Find the position of the file in the parent, and the
|
|
257
|
+
// inode of the parent.
|
|
258
|
+
let (position, inode_of_parent) = fs
|
|
259
|
+
.storage
|
|
260
|
+
.iter()
|
|
261
|
+
.find_map(|(inode_of_parent, node)| match node {
|
|
262
|
+
Node::Directory(DirectoryNode { children, .. }) => {
|
|
263
|
+
children.iter().enumerate().find_map(|(nth, inode)| {
|
|
264
|
+
if inode == &inode_of_file {
|
|
265
|
+
Some((nth, inode_of_parent))
|
|
266
|
+
} else {
|
|
267
|
+
None
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
_ => None,
|
|
273
|
+
})
|
|
274
|
+
.ok_or(FsError::BaseNotDirectory)?;
|
|
275
|
+
|
|
276
|
+
(inode_of_parent, position, inode_of_file)
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
{
|
|
280
|
+
// Write lock.
|
|
281
|
+
let mut fs = filesystem.inner.write().map_err(|_| FsError::Lock)?;
|
|
282
|
+
fs.unlink_file_inode(inode_of_parent, position, inode_of_file)?;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
Ok(())
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
fn get_special_fd(&self) -> Option<u32> {
|
|
289
|
+
let fs = match self.filesystem.inner.read() {
|
|
290
|
+
Ok(a) => a,
|
|
291
|
+
Err(_) => {
|
|
292
|
+
return None;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
let inode = fs.storage.get(self.inode);
|
|
297
|
+
match inode {
|
|
298
|
+
Some(Node::CustomFile(node)) => {
|
|
299
|
+
let file = node.file.lock().unwrap();
|
|
300
|
+
file.get_special_fd()
|
|
301
|
+
}
|
|
302
|
+
Some(Node::ArcFile(node)) => match self.arc_file.as_ref() {
|
|
303
|
+
Some(file) => file
|
|
304
|
+
.as_ref()
|
|
305
|
+
.map(|file| file.get_special_fd())
|
|
306
|
+
.unwrap_or(None),
|
|
307
|
+
None => node
|
|
308
|
+
.fs
|
|
309
|
+
.new_open_options()
|
|
310
|
+
.read(self.readable)
|
|
311
|
+
.write(self.writable)
|
|
312
|
+
.append(self.append_mode)
|
|
313
|
+
.open(node.path.as_path())
|
|
314
|
+
.map(|file| file.get_special_fd())
|
|
315
|
+
.unwrap_or(None),
|
|
316
|
+
},
|
|
317
|
+
_ => None,
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
fn copy_reference(
|
|
322
|
+
&mut self,
|
|
323
|
+
src: Box<dyn VirtualFile + Send + Sync + 'static>,
|
|
324
|
+
) -> BoxFuture<'_, std::io::Result<()>> {
|
|
325
|
+
let inner = self.filesystem.inner.clone();
|
|
326
|
+
Box::pin(async move {
|
|
327
|
+
let mut fs = inner.write().unwrap();
|
|
328
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
329
|
+
match inode {
|
|
330
|
+
Some(inode) => {
|
|
331
|
+
let metadata = Metadata {
|
|
332
|
+
ft: crate::FileType {
|
|
333
|
+
file: true,
|
|
334
|
+
..Default::default()
|
|
335
|
+
},
|
|
336
|
+
accessed: src.last_accessed(),
|
|
337
|
+
created: src.created_time(),
|
|
338
|
+
modified: src.last_modified(),
|
|
339
|
+
len: src.size(),
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
*inode = Node::CustomFile(CustomFileNode {
|
|
343
|
+
inode: inode.inode(),
|
|
344
|
+
name: inode.name().to_string_lossy().to_string().into(),
|
|
345
|
+
file: Mutex::new(Box::new(CopyOnWriteFile::new(src))),
|
|
346
|
+
metadata,
|
|
347
|
+
lifecycle: inode.file_lifecycle().cloned().unwrap_or_else(Arc::default),
|
|
348
|
+
});
|
|
349
|
+
Ok(())
|
|
350
|
+
}
|
|
351
|
+
None => Err(std::io::ErrorKind::InvalidInput.into()),
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
fn copy_from_owned_buffer(&mut self, src: &OwnedBuffer) -> BoxFuture<'_, std::io::Result<()>> {
|
|
357
|
+
let inner = self.filesystem.inner.clone();
|
|
358
|
+
let src = src.clone();
|
|
359
|
+
Box::pin(async move {
|
|
360
|
+
let mut fs = inner.write().unwrap();
|
|
361
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
362
|
+
match inode {
|
|
363
|
+
Some(inode) => {
|
|
364
|
+
let metadata = Metadata {
|
|
365
|
+
ft: crate::FileType {
|
|
366
|
+
file: true,
|
|
367
|
+
..Default::default()
|
|
368
|
+
},
|
|
369
|
+
accessed: 1,
|
|
370
|
+
created: 1,
|
|
371
|
+
modified: 1,
|
|
372
|
+
len: src.len() as u64,
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
*inode = Node::ReadOnlyFile(ReadOnlyFileNode {
|
|
376
|
+
inode: inode.inode(),
|
|
377
|
+
name: inode.name().to_string_lossy().to_string().into(),
|
|
378
|
+
file: ReadOnlyFile { buffer: src },
|
|
379
|
+
metadata,
|
|
380
|
+
lifecycle: inode.file_lifecycle().cloned().unwrap_or_else(Arc::default),
|
|
381
|
+
});
|
|
382
|
+
Ok(())
|
|
383
|
+
}
|
|
384
|
+
None => Err(std::io::ErrorKind::InvalidInput.into()),
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
fn poll_read_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
390
|
+
if !self.readable {
|
|
391
|
+
return Poll::Ready(Err(io::Error::new(
|
|
392
|
+
io::ErrorKind::PermissionDenied,
|
|
393
|
+
format!(
|
|
394
|
+
"the file (inode `{}) doesn't have the `read` permission",
|
|
395
|
+
self.inode
|
|
396
|
+
),
|
|
397
|
+
)));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let mut fs = self
|
|
401
|
+
.filesystem
|
|
402
|
+
.inner
|
|
403
|
+
.write()
|
|
404
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
405
|
+
|
|
406
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
407
|
+
match inode {
|
|
408
|
+
Some(Node::File(node)) => {
|
|
409
|
+
let remaining = node.file.buffer.len() - (self.cursor as usize);
|
|
410
|
+
Poll::Ready(Ok(remaining))
|
|
411
|
+
}
|
|
412
|
+
Some(Node::OffloadedFile(node)) => {
|
|
413
|
+
let remaining = node.file.len() as usize - (self.cursor as usize);
|
|
414
|
+
Poll::Ready(Ok(remaining))
|
|
415
|
+
}
|
|
416
|
+
Some(Node::ReadOnlyFile(node)) => {
|
|
417
|
+
let remaining = node.file.buffer.len() - (self.cursor as usize);
|
|
418
|
+
Poll::Ready(Ok(remaining))
|
|
419
|
+
}
|
|
420
|
+
Some(Node::CustomFile(node)) => {
|
|
421
|
+
let mut file = node.file.lock().unwrap();
|
|
422
|
+
let file = Pin::new(file.as_mut());
|
|
423
|
+
file.poll_read_ready(cx)
|
|
424
|
+
}
|
|
425
|
+
Some(Node::ArcFile(_)) => {
|
|
426
|
+
drop(fs);
|
|
427
|
+
match self.lazy_load_arc_file_mut() {
|
|
428
|
+
Ok(file) => {
|
|
429
|
+
let file = Pin::new(file);
|
|
430
|
+
file.poll_read_ready(cx)
|
|
431
|
+
}
|
|
432
|
+
Err(_) => Poll::Ready(Err(io::Error::new(
|
|
433
|
+
io::ErrorKind::NotFound,
|
|
434
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
435
|
+
))),
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
_ => Poll::Ready(Err(io::Error::new(
|
|
439
|
+
io::ErrorKind::NotFound,
|
|
440
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
441
|
+
))),
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
fn poll_write_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
446
|
+
if !self.writable {
|
|
447
|
+
return Poll::Ready(Err(io::Error::new(
|
|
448
|
+
io::ErrorKind::PermissionDenied,
|
|
449
|
+
format!(
|
|
450
|
+
"the file (inode `{}) doesn't have the `write` permission",
|
|
451
|
+
self.inode
|
|
452
|
+
),
|
|
453
|
+
)));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
let mut fs = self
|
|
457
|
+
.filesystem
|
|
458
|
+
.inner
|
|
459
|
+
.write()
|
|
460
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
461
|
+
|
|
462
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
463
|
+
match inode {
|
|
464
|
+
Some(Node::File(_)) => Poll::Ready(Ok(8192)),
|
|
465
|
+
Some(Node::OffloadedFile(_)) => Poll::Ready(Ok(8192)),
|
|
466
|
+
Some(Node::ReadOnlyFile(_)) => Poll::Ready(Ok(0)),
|
|
467
|
+
Some(Node::CustomFile(node)) => {
|
|
468
|
+
let mut file = node.file.lock().unwrap();
|
|
469
|
+
let file = Pin::new(file.as_mut());
|
|
470
|
+
file.poll_write_ready(cx)
|
|
471
|
+
}
|
|
472
|
+
Some(Node::ArcFile(_)) => {
|
|
473
|
+
drop(fs);
|
|
474
|
+
match self.lazy_load_arc_file_mut() {
|
|
475
|
+
Ok(file) => {
|
|
476
|
+
let file = Pin::new(file);
|
|
477
|
+
file.poll_write_ready(cx)
|
|
478
|
+
}
|
|
479
|
+
Err(_) => Poll::Ready(Err(io::Error::new(
|
|
480
|
+
io::ErrorKind::NotFound,
|
|
481
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
482
|
+
))),
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
_ => Poll::Ready(Err(io::Error::new(
|
|
486
|
+
io::ErrorKind::NotFound,
|
|
487
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
488
|
+
))),
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
fn write_from_mmap(&mut self, offset: u64, size: u64) -> std::io::Result<()> {
|
|
493
|
+
if !self.writable {
|
|
494
|
+
return Err(io::Error::new(
|
|
495
|
+
io::ErrorKind::PermissionDenied,
|
|
496
|
+
format!(
|
|
497
|
+
"the file (inode `{}) doesn't have the `write` permission",
|
|
498
|
+
self.inode
|
|
499
|
+
),
|
|
500
|
+
));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let mut cursor = self.cursor;
|
|
504
|
+
{
|
|
505
|
+
let mut fs = self
|
|
506
|
+
.filesystem
|
|
507
|
+
.inner
|
|
508
|
+
.write()
|
|
509
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
510
|
+
|
|
511
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
512
|
+
match inode {
|
|
513
|
+
Some(Node::OffloadedFile(node)) => {
|
|
514
|
+
node.file
|
|
515
|
+
.write(OffloadWrite::MmapOffset { offset, size }, &mut cursor)?;
|
|
516
|
+
node.metadata.len = node.file.len();
|
|
517
|
+
}
|
|
518
|
+
_ => {
|
|
519
|
+
return Err(io::ErrorKind::Unsupported.into());
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
self.cursor = cursor;
|
|
524
|
+
Ok(())
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
fn as_owned_buffer(&self) -> Option<OwnedBuffer> {
|
|
528
|
+
let fs = self.filesystem.inner.read().ok()?;
|
|
529
|
+
|
|
530
|
+
let inode = fs.storage.get(self.inode)?;
|
|
531
|
+
match inode {
|
|
532
|
+
Node::ReadOnlyFile(f) => Some(f.file.buffer.clone()),
|
|
533
|
+
Node::CustomFile(f) => f.file.lock().ok()?.as_owned_buffer(),
|
|
534
|
+
_ => None,
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
#[cfg(test)]
|
|
540
|
+
mod test_virtual_file {
|
|
541
|
+
use crate::{BufferFile, FileSystem as FS, FsError, mem_fs::*};
|
|
542
|
+
use std::io;
|
|
543
|
+
use std::pin::Pin;
|
|
544
|
+
use std::task::{Context, Poll, Waker};
|
|
545
|
+
use std::thread::sleep;
|
|
546
|
+
use std::time::Duration;
|
|
547
|
+
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
|
548
|
+
|
|
549
|
+
macro_rules! path {
|
|
550
|
+
($path:expr) => {
|
|
551
|
+
std::path::Path::new($path)
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
#[tokio::test]
|
|
556
|
+
async fn test_last_accessed() {
|
|
557
|
+
let fs = FileSystem::default();
|
|
558
|
+
|
|
559
|
+
let file = fs
|
|
560
|
+
.new_open_options()
|
|
561
|
+
.write(true)
|
|
562
|
+
.create_new(true)
|
|
563
|
+
.open(path!("/foo.txt"))
|
|
564
|
+
.expect("failed to create a new file");
|
|
565
|
+
let last_accessed_time = file.last_accessed();
|
|
566
|
+
|
|
567
|
+
assert!(last_accessed_time > 0, "last accessed time is not zero");
|
|
568
|
+
|
|
569
|
+
sleep(Duration::from_secs(3));
|
|
570
|
+
|
|
571
|
+
let file = fs
|
|
572
|
+
.new_open_options()
|
|
573
|
+
.read(true)
|
|
574
|
+
.open(path!("/foo.txt"))
|
|
575
|
+
.expect("failed to open a file");
|
|
576
|
+
let next_last_accessed_time = file.last_accessed();
|
|
577
|
+
|
|
578
|
+
assert!(
|
|
579
|
+
next_last_accessed_time > last_accessed_time,
|
|
580
|
+
"the last accessed time is updated"
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
#[tokio::test]
|
|
585
|
+
async fn test_last_modified() {
|
|
586
|
+
let fs = FileSystem::default();
|
|
587
|
+
|
|
588
|
+
let file = fs
|
|
589
|
+
.new_open_options()
|
|
590
|
+
.write(true)
|
|
591
|
+
.create_new(true)
|
|
592
|
+
.open(path!("/foo.txt"))
|
|
593
|
+
.expect("failed to create a new file");
|
|
594
|
+
|
|
595
|
+
assert!(file.last_modified() > 0, "last modified time is not zero");
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
#[tokio::test]
|
|
599
|
+
async fn test_created_time() {
|
|
600
|
+
let fs = FileSystem::default();
|
|
601
|
+
|
|
602
|
+
let file = fs
|
|
603
|
+
.new_open_options()
|
|
604
|
+
.write(true)
|
|
605
|
+
.create_new(true)
|
|
606
|
+
.open(path!("/foo.txt"))
|
|
607
|
+
.expect("failed to create a new file");
|
|
608
|
+
let created_time = file.created_time();
|
|
609
|
+
|
|
610
|
+
assert!(created_time > 0, "created time is not zero");
|
|
611
|
+
|
|
612
|
+
let file = fs
|
|
613
|
+
.new_open_options()
|
|
614
|
+
.read(true)
|
|
615
|
+
.open(path!("/foo.txt"))
|
|
616
|
+
.expect("failed to create a new file");
|
|
617
|
+
let next_created_time = file.created_time();
|
|
618
|
+
|
|
619
|
+
assert_eq!(
|
|
620
|
+
next_created_time, created_time,
|
|
621
|
+
"created time stays constant"
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
#[tokio::test]
|
|
626
|
+
async fn test_size() {
|
|
627
|
+
let fs = FileSystem::default();
|
|
628
|
+
|
|
629
|
+
let file = fs
|
|
630
|
+
.new_open_options()
|
|
631
|
+
.write(true)
|
|
632
|
+
.create_new(true)
|
|
633
|
+
.open(path!("/foo.txt"))
|
|
634
|
+
.expect("failed to create a new file");
|
|
635
|
+
|
|
636
|
+
assert_eq!(file.size(), 0, "new file is empty");
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
#[tokio::test]
|
|
640
|
+
async fn test_set_len() {
|
|
641
|
+
let fs = FileSystem::default();
|
|
642
|
+
|
|
643
|
+
let mut file = fs
|
|
644
|
+
.new_open_options()
|
|
645
|
+
.write(true)
|
|
646
|
+
.create_new(true)
|
|
647
|
+
.open(path!("/foo.txt"))
|
|
648
|
+
.expect("failed to create a new file");
|
|
649
|
+
|
|
650
|
+
assert!(matches!(file.set_len(7), Ok(())), "setting a new length");
|
|
651
|
+
assert_eq!(file.size(), 7, "file has a new length");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
#[tokio::test]
|
|
655
|
+
async fn test_unlink() {
|
|
656
|
+
let fs = FileSystem::default();
|
|
657
|
+
|
|
658
|
+
let mut file = fs
|
|
659
|
+
.new_open_options()
|
|
660
|
+
.read(true)
|
|
661
|
+
.write(true)
|
|
662
|
+
.create_new(true)
|
|
663
|
+
.open(path!("/foo.txt"))
|
|
664
|
+
.expect("failed to create a new file");
|
|
665
|
+
|
|
666
|
+
file.write_all(b"foo").await.expect("write before unlink");
|
|
667
|
+
|
|
668
|
+
{
|
|
669
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
670
|
+
|
|
671
|
+
assert_eq!(fs_inner.storage.len(), 2, "storage has the new file");
|
|
672
|
+
assert!(
|
|
673
|
+
matches!(
|
|
674
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
675
|
+
Some(Node::Directory(DirectoryNode {
|
|
676
|
+
inode: ROOT_INODE,
|
|
677
|
+
name,
|
|
678
|
+
children,
|
|
679
|
+
..
|
|
680
|
+
})) if name == "/" && children == &[1]
|
|
681
|
+
),
|
|
682
|
+
"`/` contains `foo.txt`",
|
|
683
|
+
);
|
|
684
|
+
assert!(
|
|
685
|
+
matches!(
|
|
686
|
+
fs_inner.storage.get(1),
|
|
687
|
+
Some(Node::File(FileNode {
|
|
688
|
+
inode: 1,
|
|
689
|
+
name,
|
|
690
|
+
..
|
|
691
|
+
})) if name == "foo.txt"
|
|
692
|
+
),
|
|
693
|
+
"`foo.txt` exists and is a file",
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
assert_eq!(file.unlink(), Ok(()), "unlinking the file");
|
|
698
|
+
assert!(
|
|
699
|
+
matches!(
|
|
700
|
+
fs.new_open_options().read(true).open(path!("/foo.txt")),
|
|
701
|
+
Err(FsError::EntryNotFound)
|
|
702
|
+
),
|
|
703
|
+
"the path disappears immediately after unlink",
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
file.write_all(b"bar")
|
|
707
|
+
.await
|
|
708
|
+
.expect("write after unlink should still work");
|
|
709
|
+
file.seek(io::SeekFrom::Start(0))
|
|
710
|
+
.await
|
|
711
|
+
.expect("rewind after unlink");
|
|
712
|
+
|
|
713
|
+
let mut contents = String::new();
|
|
714
|
+
file.read_to_string(&mut contents)
|
|
715
|
+
.await
|
|
716
|
+
.expect("read after unlink should still work");
|
|
717
|
+
assert_eq!(contents, "foobar");
|
|
718
|
+
|
|
719
|
+
{
|
|
720
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
721
|
+
|
|
722
|
+
assert_eq!(
|
|
723
|
+
fs_inner.storage.len(),
|
|
724
|
+
2,
|
|
725
|
+
"storage keeps the unlinked file alive while a handle is open"
|
|
726
|
+
);
|
|
727
|
+
assert!(
|
|
728
|
+
matches!(
|
|
729
|
+
fs_inner.storage.get(ROOT_INODE),
|
|
730
|
+
Some(Node::Directory(DirectoryNode {
|
|
731
|
+
inode: ROOT_INODE,
|
|
732
|
+
name,
|
|
733
|
+
children,
|
|
734
|
+
..
|
|
735
|
+
})) if name == "/" && children.is_empty()
|
|
736
|
+
),
|
|
737
|
+
"`/` is empty",
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
drop(file);
|
|
742
|
+
|
|
743
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
744
|
+
assert_eq!(
|
|
745
|
+
fs_inner.storage.len(),
|
|
746
|
+
1,
|
|
747
|
+
"storage drops the unlinked file after the last handle closes"
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
#[tokio::test]
|
|
752
|
+
async fn test_unlink_with_multiple_handles() {
|
|
753
|
+
let fs = FileSystem::default();
|
|
754
|
+
|
|
755
|
+
let mut first = fs
|
|
756
|
+
.new_open_options()
|
|
757
|
+
.read(true)
|
|
758
|
+
.write(true)
|
|
759
|
+
.create_new(true)
|
|
760
|
+
.open(path!("/foo.txt"))
|
|
761
|
+
.expect("failed to create a new file");
|
|
762
|
+
first.write_all(b"foo").await.expect("seed contents");
|
|
763
|
+
|
|
764
|
+
let mut second = fs
|
|
765
|
+
.new_open_options()
|
|
766
|
+
.read(true)
|
|
767
|
+
.write(true)
|
|
768
|
+
.open(path!("/foo.txt"))
|
|
769
|
+
.expect("failed to open the same file a second time");
|
|
770
|
+
|
|
771
|
+
assert_eq!(first.unlink(), Ok(()), "unlinking through the first handle");
|
|
772
|
+
|
|
773
|
+
second
|
|
774
|
+
.seek(io::SeekFrom::End(0))
|
|
775
|
+
.await
|
|
776
|
+
.expect("seek to end after unlink");
|
|
777
|
+
second
|
|
778
|
+
.write_all(b"bar")
|
|
779
|
+
.await
|
|
780
|
+
.expect("second handle stays writable after unlink");
|
|
781
|
+
|
|
782
|
+
drop(first);
|
|
783
|
+
|
|
784
|
+
{
|
|
785
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
786
|
+
assert_eq!(
|
|
787
|
+
fs_inner.storage.len(),
|
|
788
|
+
2,
|
|
789
|
+
"the inode stays alive until the last open handle is dropped"
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
second
|
|
794
|
+
.seek(io::SeekFrom::Start(0))
|
|
795
|
+
.await
|
|
796
|
+
.expect("rewind second handle");
|
|
797
|
+
let mut contents = String::new();
|
|
798
|
+
second
|
|
799
|
+
.read_to_string(&mut contents)
|
|
800
|
+
.await
|
|
801
|
+
.expect("read through surviving handle");
|
|
802
|
+
assert_eq!(contents, "foobar");
|
|
803
|
+
|
|
804
|
+
drop(second);
|
|
805
|
+
|
|
806
|
+
let fs_inner = fs.inner.read().unwrap();
|
|
807
|
+
assert_eq!(
|
|
808
|
+
fs_inner.storage.len(),
|
|
809
|
+
1,
|
|
810
|
+
"the inode is reclaimed after the last handle closes"
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
#[tokio::test]
|
|
815
|
+
async fn test_poll_write_ready_for_write_only_file() {
|
|
816
|
+
let fs = FileSystem::default();
|
|
817
|
+
let mut file = fs
|
|
818
|
+
.new_open_options()
|
|
819
|
+
.write(true)
|
|
820
|
+
.create_new(true)
|
|
821
|
+
.open(path!("/foo.txt"))
|
|
822
|
+
.expect("failed to create a new file");
|
|
823
|
+
let waker = Waker::noop();
|
|
824
|
+
let mut cx = Context::from_waker(waker);
|
|
825
|
+
|
|
826
|
+
assert!(matches!(
|
|
827
|
+
Pin::new(file.as_mut()).poll_write_ready(&mut cx),
|
|
828
|
+
Poll::Ready(Ok(8192))
|
|
829
|
+
));
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
#[tokio::test]
|
|
833
|
+
async fn test_poll_write_ready_for_custom_device_file() {
|
|
834
|
+
let fs = FileSystem::default();
|
|
835
|
+
fs.insert_device_file(path!("/dev").to_path_buf(), Box::<BufferFile>::default())
|
|
836
|
+
.expect("failed to insert a device file");
|
|
837
|
+
|
|
838
|
+
let mut file = fs
|
|
839
|
+
.new_open_options()
|
|
840
|
+
.write(true)
|
|
841
|
+
.open(path!("/dev"))
|
|
842
|
+
.expect("failed to open device file");
|
|
843
|
+
let waker = Waker::noop();
|
|
844
|
+
let mut cx = Context::from_waker(waker);
|
|
845
|
+
|
|
846
|
+
assert!(matches!(
|
|
847
|
+
Pin::new(file.as_mut()).poll_write_ready(&mut cx),
|
|
848
|
+
Poll::Ready(Ok(8192))
|
|
849
|
+
));
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
impl AsyncRead for FileHandle {
|
|
854
|
+
fn poll_read(
|
|
855
|
+
mut self: Pin<&mut Self>,
|
|
856
|
+
cx: &mut Context<'_>,
|
|
857
|
+
buf: &mut tokio::io::ReadBuf<'_>,
|
|
858
|
+
) -> Poll<io::Result<()>> {
|
|
859
|
+
if !self.readable {
|
|
860
|
+
return Poll::Ready(Err(io::Error::new(
|
|
861
|
+
io::ErrorKind::PermissionDenied,
|
|
862
|
+
format!(
|
|
863
|
+
"the file (inode `{}) doesn't have the `read` permission",
|
|
864
|
+
self.inode
|
|
865
|
+
),
|
|
866
|
+
)));
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
let mut cursor = self.cursor;
|
|
870
|
+
let ret = {
|
|
871
|
+
let mut fs = self
|
|
872
|
+
.filesystem
|
|
873
|
+
.inner
|
|
874
|
+
.write()
|
|
875
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
876
|
+
|
|
877
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
878
|
+
match inode {
|
|
879
|
+
Some(Node::File(node)) => {
|
|
880
|
+
let read = unsafe {
|
|
881
|
+
node.file.read(
|
|
882
|
+
std::mem::transmute::<&mut [std::mem::MaybeUninit<u8>], &mut [u8]>(
|
|
883
|
+
buf.unfilled_mut(),
|
|
884
|
+
),
|
|
885
|
+
&mut cursor,
|
|
886
|
+
)
|
|
887
|
+
};
|
|
888
|
+
if let Ok(read) = &read {
|
|
889
|
+
unsafe { buf.assume_init(*read) };
|
|
890
|
+
buf.advance(*read);
|
|
891
|
+
}
|
|
892
|
+
Poll::Ready(read.map(|_| ()))
|
|
893
|
+
}
|
|
894
|
+
Some(Node::OffloadedFile(node)) => {
|
|
895
|
+
let read = unsafe {
|
|
896
|
+
node.file.read(
|
|
897
|
+
std::mem::transmute::<&mut [std::mem::MaybeUninit<u8>], &mut [u8]>(
|
|
898
|
+
buf.unfilled_mut(),
|
|
899
|
+
),
|
|
900
|
+
&mut cursor,
|
|
901
|
+
)
|
|
902
|
+
};
|
|
903
|
+
if let Ok(read) = &read {
|
|
904
|
+
unsafe { buf.assume_init(*read) };
|
|
905
|
+
buf.advance(*read);
|
|
906
|
+
}
|
|
907
|
+
Poll::Ready(read.map(|_| ()))
|
|
908
|
+
}
|
|
909
|
+
Some(Node::ReadOnlyFile(node)) => {
|
|
910
|
+
let read = unsafe {
|
|
911
|
+
node.file.read(
|
|
912
|
+
std::mem::transmute::<&mut [std::mem::MaybeUninit<u8>], &mut [u8]>(
|
|
913
|
+
buf.unfilled_mut(),
|
|
914
|
+
),
|
|
915
|
+
&mut cursor,
|
|
916
|
+
)
|
|
917
|
+
};
|
|
918
|
+
if let Ok(read) = &read {
|
|
919
|
+
unsafe { buf.assume_init(*read) };
|
|
920
|
+
buf.advance(*read);
|
|
921
|
+
}
|
|
922
|
+
Poll::Ready(read.map(|_| ()))
|
|
923
|
+
}
|
|
924
|
+
Some(Node::CustomFile(node)) => {
|
|
925
|
+
let mut file = node.file.lock().unwrap();
|
|
926
|
+
let file = Pin::new(file.as_mut());
|
|
927
|
+
file.poll_read(cx, buf)
|
|
928
|
+
}
|
|
929
|
+
Some(Node::ArcFile(_)) => {
|
|
930
|
+
drop(fs);
|
|
931
|
+
match self.lazy_load_arc_file_mut() {
|
|
932
|
+
Ok(file) => {
|
|
933
|
+
let file = Pin::new(file);
|
|
934
|
+
file.poll_read(cx, buf)
|
|
935
|
+
}
|
|
936
|
+
Err(_) => {
|
|
937
|
+
return Poll::Ready(Err(io::Error::new(
|
|
938
|
+
io::ErrorKind::NotFound,
|
|
939
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
940
|
+
)));
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
_ => {
|
|
945
|
+
return Poll::Ready(Err(io::Error::new(
|
|
946
|
+
io::ErrorKind::NotFound,
|
|
947
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
948
|
+
)));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
self.cursor = cursor;
|
|
953
|
+
ret
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
impl AsyncSeek for FileHandle {
|
|
958
|
+
fn start_seek(mut self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> {
|
|
959
|
+
let mut cursor = self.cursor;
|
|
960
|
+
let ret = {
|
|
961
|
+
let mut fs = self
|
|
962
|
+
.filesystem
|
|
963
|
+
.inner
|
|
964
|
+
.write()
|
|
965
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
966
|
+
|
|
967
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
968
|
+
match inode {
|
|
969
|
+
Some(Node::File(node)) => {
|
|
970
|
+
node.file.seek(position, &mut cursor)?;
|
|
971
|
+
Ok(())
|
|
972
|
+
}
|
|
973
|
+
Some(Node::OffloadedFile(node)) => {
|
|
974
|
+
node.file.seek(position, &mut cursor)?;
|
|
975
|
+
Ok(())
|
|
976
|
+
}
|
|
977
|
+
Some(Node::ReadOnlyFile(node)) => {
|
|
978
|
+
node.file.seek(position, &mut cursor)?;
|
|
979
|
+
Ok(())
|
|
980
|
+
}
|
|
981
|
+
Some(Node::CustomFile(node)) => {
|
|
982
|
+
let mut file = node.file.lock().unwrap();
|
|
983
|
+
let file = Pin::new(file.as_mut());
|
|
984
|
+
file.start_seek(position)
|
|
985
|
+
}
|
|
986
|
+
Some(Node::ArcFile(_)) => {
|
|
987
|
+
drop(fs);
|
|
988
|
+
match self.lazy_load_arc_file_mut() {
|
|
989
|
+
Ok(file) => {
|
|
990
|
+
let file = Pin::new(file);
|
|
991
|
+
file.start_seek(position)
|
|
992
|
+
}
|
|
993
|
+
Err(_) => {
|
|
994
|
+
return Err(io::Error::new(
|
|
995
|
+
io::ErrorKind::NotFound,
|
|
996
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
997
|
+
));
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
_ => {
|
|
1002
|
+
return Err(io::Error::new(
|
|
1003
|
+
io::ErrorKind::NotFound,
|
|
1004
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1005
|
+
));
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
self.cursor = cursor;
|
|
1010
|
+
ret
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
|
1014
|
+
let mut fs = self
|
|
1015
|
+
.filesystem
|
|
1016
|
+
.inner
|
|
1017
|
+
.write()
|
|
1018
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
1019
|
+
|
|
1020
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
1021
|
+
match inode {
|
|
1022
|
+
Some(Node::File { .. }) => Poll::Ready(Ok(self.cursor)),
|
|
1023
|
+
Some(Node::OffloadedFile { .. }) => Poll::Ready(Ok(self.cursor)),
|
|
1024
|
+
Some(Node::ReadOnlyFile { .. }) => Poll::Ready(Ok(self.cursor)),
|
|
1025
|
+
Some(Node::CustomFile(node)) => {
|
|
1026
|
+
let mut file = node.file.lock().unwrap();
|
|
1027
|
+
let file = Pin::new(file.as_mut());
|
|
1028
|
+
file.poll_complete(cx)
|
|
1029
|
+
}
|
|
1030
|
+
Some(Node::ArcFile { .. }) => {
|
|
1031
|
+
drop(fs);
|
|
1032
|
+
match self.lazy_load_arc_file_mut() {
|
|
1033
|
+
Ok(file) => {
|
|
1034
|
+
let file = Pin::new(file);
|
|
1035
|
+
file.poll_complete(cx)
|
|
1036
|
+
}
|
|
1037
|
+
Err(_) => Poll::Ready(Err(io::Error::new(
|
|
1038
|
+
io::ErrorKind::NotFound,
|
|
1039
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1040
|
+
))),
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
_ => Poll::Ready(Err(io::Error::new(
|
|
1044
|
+
io::ErrorKind::NotFound,
|
|
1045
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1046
|
+
))),
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
impl AsyncWrite for FileHandle {
|
|
1052
|
+
fn poll_write(
|
|
1053
|
+
mut self: Pin<&mut Self>,
|
|
1054
|
+
cx: &mut Context<'_>,
|
|
1055
|
+
buf: &[u8],
|
|
1056
|
+
) -> Poll<io::Result<usize>> {
|
|
1057
|
+
if !self.writable {
|
|
1058
|
+
return Poll::Ready(Err(io::Error::new(
|
|
1059
|
+
io::ErrorKind::PermissionDenied,
|
|
1060
|
+
format!(
|
|
1061
|
+
"the file (inode `{}) doesn't have the `write` permission",
|
|
1062
|
+
self.inode
|
|
1063
|
+
),
|
|
1064
|
+
)));
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
let mut cursor;
|
|
1068
|
+
let bytes_written = {
|
|
1069
|
+
let mut fs = self
|
|
1070
|
+
.filesystem
|
|
1071
|
+
.inner
|
|
1072
|
+
.write()
|
|
1073
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
1074
|
+
|
|
1075
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
1076
|
+
match inode {
|
|
1077
|
+
Some(Node::File(node)) => {
|
|
1078
|
+
cursor = self.write_cursor(node.file.len() as u64);
|
|
1079
|
+
let bytes_written = node.file.write(buf, &mut cursor)?;
|
|
1080
|
+
node.metadata.len = node.file.len().try_into().unwrap();
|
|
1081
|
+
bytes_written
|
|
1082
|
+
}
|
|
1083
|
+
Some(Node::OffloadedFile(node)) => {
|
|
1084
|
+
cursor = self.write_cursor(node.file.len());
|
|
1085
|
+
let bytes_written = node.file.write(OffloadWrite::Buffer(buf), &mut cursor)?;
|
|
1086
|
+
node.metadata.len = node.file.len();
|
|
1087
|
+
bytes_written
|
|
1088
|
+
}
|
|
1089
|
+
Some(Node::ReadOnlyFile(node)) => {
|
|
1090
|
+
cursor = self.write_cursor(node.file.len() as u64);
|
|
1091
|
+
let bytes_written = node.file.write(buf, &mut cursor)?;
|
|
1092
|
+
node.metadata.len = node.file.len().try_into().unwrap();
|
|
1093
|
+
bytes_written
|
|
1094
|
+
}
|
|
1095
|
+
Some(Node::CustomFile(node)) => {
|
|
1096
|
+
let mut guard = node.file.lock().unwrap();
|
|
1097
|
+
cursor = self.write_cursor(guard.size());
|
|
1098
|
+
|
|
1099
|
+
let file = Pin::new(guard.as_mut());
|
|
1100
|
+
if let Err(err) = file.start_seek(io::SeekFrom::Start(cursor)) {
|
|
1101
|
+
return Poll::Ready(Err(err));
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
let file = Pin::new(guard.as_mut());
|
|
1105
|
+
let _ = file.poll_complete(cx);
|
|
1106
|
+
|
|
1107
|
+
let file = Pin::new(guard.as_mut());
|
|
1108
|
+
let bytes_written = match file.poll_write(cx, buf) {
|
|
1109
|
+
Poll::Ready(Ok(a)) => a,
|
|
1110
|
+
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
|
1111
|
+
Poll::Pending => return Poll::Pending,
|
|
1112
|
+
};
|
|
1113
|
+
cursor += bytes_written as u64;
|
|
1114
|
+
node.metadata.len = guard.size();
|
|
1115
|
+
bytes_written
|
|
1116
|
+
}
|
|
1117
|
+
Some(Node::ArcFile(_)) => {
|
|
1118
|
+
drop(fs);
|
|
1119
|
+
match self.lazy_load_arc_file_mut() {
|
|
1120
|
+
Ok(file) => {
|
|
1121
|
+
let file = Pin::new(file);
|
|
1122
|
+
return file.poll_write(cx, buf);
|
|
1123
|
+
}
|
|
1124
|
+
Err(_) => {
|
|
1125
|
+
return Poll::Ready(Err(io::Error::new(
|
|
1126
|
+
io::ErrorKind::NotFound,
|
|
1127
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1128
|
+
)));
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
_ => {
|
|
1133
|
+
return Poll::Ready(Err(io::Error::new(
|
|
1134
|
+
io::ErrorKind::NotFound,
|
|
1135
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1136
|
+
)));
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
self.cursor = cursor;
|
|
1141
|
+
Poll::Ready(Ok(bytes_written))
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
fn poll_write_vectored(
|
|
1145
|
+
mut self: Pin<&mut Self>,
|
|
1146
|
+
cx: &mut Context<'_>,
|
|
1147
|
+
bufs: &[io::IoSlice<'_>],
|
|
1148
|
+
) -> Poll<io::Result<usize>> {
|
|
1149
|
+
let mut cursor = self.cursor;
|
|
1150
|
+
let ret = {
|
|
1151
|
+
let mut fs = self
|
|
1152
|
+
.filesystem
|
|
1153
|
+
.inner
|
|
1154
|
+
.write()
|
|
1155
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
1156
|
+
|
|
1157
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
1158
|
+
match inode {
|
|
1159
|
+
Some(Node::File(node)) => {
|
|
1160
|
+
cursor = self.write_cursor(node.file.len() as u64);
|
|
1161
|
+
let buf = bufs
|
|
1162
|
+
.iter()
|
|
1163
|
+
.find(|b| !b.is_empty())
|
|
1164
|
+
.map_or(&[][..], |b| &**b);
|
|
1165
|
+
let bytes_written = node.file.write(buf, &mut cursor)?;
|
|
1166
|
+
node.metadata.len = node.file.buffer.len() as u64;
|
|
1167
|
+
Poll::Ready(Ok(bytes_written))
|
|
1168
|
+
}
|
|
1169
|
+
Some(Node::OffloadedFile(node)) => {
|
|
1170
|
+
cursor = self.write_cursor(node.file.len());
|
|
1171
|
+
let buf = bufs
|
|
1172
|
+
.iter()
|
|
1173
|
+
.find(|b| !b.is_empty())
|
|
1174
|
+
.map_or(&[][..], |b| &**b);
|
|
1175
|
+
let bytes_written = node.file.write(OffloadWrite::Buffer(buf), &mut cursor)?;
|
|
1176
|
+
node.metadata.len = node.file.len();
|
|
1177
|
+
Poll::Ready(Ok(bytes_written))
|
|
1178
|
+
}
|
|
1179
|
+
Some(Node::ReadOnlyFile(node)) => {
|
|
1180
|
+
cursor = self.write_cursor(node.file.len() as u64);
|
|
1181
|
+
let buf = bufs
|
|
1182
|
+
.iter()
|
|
1183
|
+
.find(|b| !b.is_empty())
|
|
1184
|
+
.map_or(&[][..], |b| &**b);
|
|
1185
|
+
let bytes_written = node.file.write(buf, &mut cursor)?;
|
|
1186
|
+
node.metadata.len = node.file.buffer.len() as u64;
|
|
1187
|
+
Poll::Ready(Ok(bytes_written))
|
|
1188
|
+
}
|
|
1189
|
+
Some(Node::CustomFile(node)) => {
|
|
1190
|
+
let mut file = node.file.lock().unwrap();
|
|
1191
|
+
let file = Pin::new(file.as_mut());
|
|
1192
|
+
file.poll_write_vectored(cx, bufs)
|
|
1193
|
+
}
|
|
1194
|
+
Some(Node::ArcFile(_)) => {
|
|
1195
|
+
drop(fs);
|
|
1196
|
+
match self.lazy_load_arc_file_mut() {
|
|
1197
|
+
Ok(file) => {
|
|
1198
|
+
let file = Pin::new(file);
|
|
1199
|
+
file.poll_write_vectored(cx, bufs)
|
|
1200
|
+
}
|
|
1201
|
+
Err(_) => Poll::Ready(Err(io::Error::new(
|
|
1202
|
+
io::ErrorKind::NotFound,
|
|
1203
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1204
|
+
))),
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
_ => Poll::Ready(Err(io::Error::new(
|
|
1208
|
+
io::ErrorKind::NotFound,
|
|
1209
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1210
|
+
))),
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
self.cursor = cursor;
|
|
1214
|
+
ret
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
1218
|
+
let mut fs = self
|
|
1219
|
+
.filesystem
|
|
1220
|
+
.inner
|
|
1221
|
+
.write()
|
|
1222
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
1223
|
+
|
|
1224
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
1225
|
+
match inode {
|
|
1226
|
+
Some(Node::File(node)) => Poll::Ready(node.file.flush()),
|
|
1227
|
+
Some(Node::OffloadedFile(node)) => Poll::Ready(node.file.flush()),
|
|
1228
|
+
Some(Node::ReadOnlyFile(node)) => Poll::Ready(node.file.flush()),
|
|
1229
|
+
Some(Node::CustomFile(node)) => {
|
|
1230
|
+
let mut file = node.file.lock().unwrap();
|
|
1231
|
+
let file = Pin::new(file.as_mut());
|
|
1232
|
+
file.poll_flush(cx)
|
|
1233
|
+
}
|
|
1234
|
+
Some(Node::ArcFile { .. }) => {
|
|
1235
|
+
drop(fs);
|
|
1236
|
+
match self.lazy_load_arc_file_mut() {
|
|
1237
|
+
Ok(file) => {
|
|
1238
|
+
let file = Pin::new(file);
|
|
1239
|
+
file.poll_flush(cx)
|
|
1240
|
+
}
|
|
1241
|
+
Err(_) => Poll::Ready(Err(io::Error::new(
|
|
1242
|
+
io::ErrorKind::NotFound,
|
|
1243
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1244
|
+
))),
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
_ => Poll::Ready(Err(io::Error::new(
|
|
1248
|
+
io::ErrorKind::NotFound,
|
|
1249
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1250
|
+
))),
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
1255
|
+
let mut fs = self
|
|
1256
|
+
.filesystem
|
|
1257
|
+
.inner
|
|
1258
|
+
.write()
|
|
1259
|
+
.map_err(|_| io::Error::other("failed to acquire a write lock"))?;
|
|
1260
|
+
|
|
1261
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
1262
|
+
match inode {
|
|
1263
|
+
Some(Node::File { .. }) => Poll::Ready(Ok(())),
|
|
1264
|
+
Some(Node::OffloadedFile { .. }) => Poll::Ready(Ok(())),
|
|
1265
|
+
Some(Node::ReadOnlyFile { .. }) => Poll::Ready(Ok(())),
|
|
1266
|
+
Some(Node::CustomFile(node)) => {
|
|
1267
|
+
let mut file = node.file.lock().unwrap();
|
|
1268
|
+
let file = Pin::new(file.as_mut());
|
|
1269
|
+
file.poll_shutdown(cx)
|
|
1270
|
+
}
|
|
1271
|
+
Some(Node::ArcFile { .. }) => {
|
|
1272
|
+
drop(fs);
|
|
1273
|
+
match self.lazy_load_arc_file_mut() {
|
|
1274
|
+
Ok(file) => {
|
|
1275
|
+
let file = Pin::new(file);
|
|
1276
|
+
file.poll_shutdown(cx)
|
|
1277
|
+
}
|
|
1278
|
+
Err(_) => Poll::Ready(Err(io::Error::new(
|
|
1279
|
+
io::ErrorKind::NotFound,
|
|
1280
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1281
|
+
))),
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
_ => Poll::Ready(Err(io::Error::new(
|
|
1285
|
+
io::ErrorKind::NotFound,
|
|
1286
|
+
format!("inode `{}` doesn't match a file", self.inode),
|
|
1287
|
+
))),
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
fn is_write_vectored(&self) -> bool {
|
|
1292
|
+
let mut fs = match self.filesystem.inner.write() {
|
|
1293
|
+
Ok(a) => a,
|
|
1294
|
+
Err(_) => return false,
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
let inode = fs.storage.get_mut(self.inode);
|
|
1298
|
+
match inode {
|
|
1299
|
+
Some(Node::File { .. }) => false,
|
|
1300
|
+
Some(Node::OffloadedFile { .. }) => false,
|
|
1301
|
+
Some(Node::ReadOnlyFile { .. }) => false,
|
|
1302
|
+
Some(Node::CustomFile(node)) => {
|
|
1303
|
+
let file = node.file.lock().unwrap();
|
|
1304
|
+
file.is_write_vectored()
|
|
1305
|
+
}
|
|
1306
|
+
Some(Node::ArcFile { .. }) => {
|
|
1307
|
+
drop(fs);
|
|
1308
|
+
match self.arc_file.as_ref() {
|
|
1309
|
+
Some(Ok(file)) => file.is_write_vectored(),
|
|
1310
|
+
_ => false,
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
_ => false,
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
#[cfg(test)]
|
|
1319
|
+
mod test_read_write_seek {
|
|
1320
|
+
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
|
1321
|
+
|
|
1322
|
+
use crate::{FileSystem as FS, mem_fs::*};
|
|
1323
|
+
use std::io;
|
|
1324
|
+
|
|
1325
|
+
macro_rules! path {
|
|
1326
|
+
($path:expr) => {
|
|
1327
|
+
std::path::Path::new($path)
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
#[tokio::test]
|
|
1332
|
+
async fn test_writing_at_various_positions() {
|
|
1333
|
+
let fs = FileSystem::default();
|
|
1334
|
+
|
|
1335
|
+
let mut file = fs
|
|
1336
|
+
.new_open_options()
|
|
1337
|
+
.read(true)
|
|
1338
|
+
.write(true)
|
|
1339
|
+
.create_new(true)
|
|
1340
|
+
.open(path!("/foo.txt"))
|
|
1341
|
+
.expect("failed to create a new file");
|
|
1342
|
+
|
|
1343
|
+
assert!(
|
|
1344
|
+
matches!(file.write(b"foo").await, Ok(3)),
|
|
1345
|
+
"writing `foo` at the end of the file",
|
|
1346
|
+
);
|
|
1347
|
+
assert_eq!(file.size(), 3, "checking the size of the file");
|
|
1348
|
+
|
|
1349
|
+
assert!(
|
|
1350
|
+
matches!(file.write(b"bar").await, Ok(3)),
|
|
1351
|
+
"writing `bar` at the end of the file",
|
|
1352
|
+
);
|
|
1353
|
+
assert_eq!(file.size(), 6, "checking the size of the file");
|
|
1354
|
+
|
|
1355
|
+
assert!(
|
|
1356
|
+
matches!(file.seek(io::SeekFrom::Start(0)).await, Ok(0)),
|
|
1357
|
+
"seeking to 0",
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
assert!(
|
|
1361
|
+
matches!(file.write(b"baz").await, Ok(3)),
|
|
1362
|
+
"writing `baz` at the beginning of the file",
|
|
1363
|
+
);
|
|
1364
|
+
assert_eq!(file.size(), 6, "checking the size of the file");
|
|
1365
|
+
|
|
1366
|
+
assert!(
|
|
1367
|
+
matches!(file.write(b"qux").await, Ok(3)),
|
|
1368
|
+
"writing `qux` in the middle of the file",
|
|
1369
|
+
);
|
|
1370
|
+
assert_eq!(file.size(), 6, "checking the size of the file");
|
|
1371
|
+
|
|
1372
|
+
assert!(
|
|
1373
|
+
matches!(file.seek(io::SeekFrom::Start(0)).await, Ok(0)),
|
|
1374
|
+
"seeking to 0",
|
|
1375
|
+
);
|
|
1376
|
+
|
|
1377
|
+
let mut string = String::new();
|
|
1378
|
+
assert!(
|
|
1379
|
+
matches!(file.read_to_string(&mut string).await, Ok(6)),
|
|
1380
|
+
"reading `bazqux`",
|
|
1381
|
+
);
|
|
1382
|
+
assert_eq!(string, "bazqux");
|
|
1383
|
+
|
|
1384
|
+
assert!(
|
|
1385
|
+
matches!(file.seek(io::SeekFrom::Current(-3)).await, Ok(3)),
|
|
1386
|
+
"seeking to 3",
|
|
1387
|
+
);
|
|
1388
|
+
|
|
1389
|
+
let mut string = String::new();
|
|
1390
|
+
assert!(
|
|
1391
|
+
matches!(file.read_to_string(&mut string).await, Ok(3)),
|
|
1392
|
+
"reading `qux`",
|
|
1393
|
+
);
|
|
1394
|
+
assert_eq!(string, "qux");
|
|
1395
|
+
|
|
1396
|
+
assert!(
|
|
1397
|
+
matches!(file.seek(io::SeekFrom::End(0)).await, Ok(6)),
|
|
1398
|
+
"seeking to 6",
|
|
1399
|
+
);
|
|
1400
|
+
|
|
1401
|
+
let mut string = String::new();
|
|
1402
|
+
assert!(
|
|
1403
|
+
matches!(file.read_to_string(&mut string).await, Ok(0)),
|
|
1404
|
+
"reading ``",
|
|
1405
|
+
);
|
|
1406
|
+
assert_eq!(string, "");
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
#[test]
|
|
1410
|
+
pub fn writing_to_middle() {
|
|
1411
|
+
fn assert_contents(file: &File, expected: &[u8]) {
|
|
1412
|
+
let mut buf = vec![0; expected.len() + 1];
|
|
1413
|
+
let mut cursor = 0;
|
|
1414
|
+
let read = file.read(buf.as_mut(), &mut cursor).unwrap();
|
|
1415
|
+
assert_eq!(read, expected.len(), "Must have the same amount of data");
|
|
1416
|
+
assert_eq!(buf[0..expected.len()], *expected);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
let mut file = File::new(None);
|
|
1420
|
+
|
|
1421
|
+
let mut cursor = 0;
|
|
1422
|
+
|
|
1423
|
+
// Write to empty file
|
|
1424
|
+
file.write(b"hello, world.", &mut cursor).unwrap();
|
|
1425
|
+
assert_eq!(cursor, 13);
|
|
1426
|
+
assert_contents(&file, b"hello, world.");
|
|
1427
|
+
|
|
1428
|
+
// Write to end of file
|
|
1429
|
+
file.write(b"goodbye!", &mut cursor).unwrap();
|
|
1430
|
+
assert_eq!(cursor, 21);
|
|
1431
|
+
assert_contents(&file, b"hello, world.goodbye!");
|
|
1432
|
+
|
|
1433
|
+
// Write to middle of file
|
|
1434
|
+
cursor = 5;
|
|
1435
|
+
file.write(b"BOOM", &mut cursor).unwrap();
|
|
1436
|
+
assert_eq!(cursor, 9);
|
|
1437
|
+
assert_contents(&file, b"helloBOOMrld.goodbye!");
|
|
1438
|
+
|
|
1439
|
+
// Write to middle of file until last byte
|
|
1440
|
+
cursor = 17;
|
|
1441
|
+
file.write(b"BANG", &mut cursor).unwrap();
|
|
1442
|
+
assert_eq!(cursor, 21);
|
|
1443
|
+
assert_contents(&file, b"helloBOOMrld.goodBANG");
|
|
1444
|
+
|
|
1445
|
+
// Write to middle past end of file
|
|
1446
|
+
cursor = 17;
|
|
1447
|
+
file.write(b"OUCH!", &mut cursor).unwrap();
|
|
1448
|
+
assert_eq!(cursor, 22);
|
|
1449
|
+
assert_contents(&file, b"helloBOOMrld.goodOUCH!");
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
#[tokio::test]
|
|
1453
|
+
async fn test_reading() {
|
|
1454
|
+
let fs = FileSystem::default();
|
|
1455
|
+
|
|
1456
|
+
let mut file = fs
|
|
1457
|
+
.new_open_options()
|
|
1458
|
+
.read(true)
|
|
1459
|
+
.write(true)
|
|
1460
|
+
.create_new(true)
|
|
1461
|
+
.open(path!("/foo.txt"))
|
|
1462
|
+
.expect("failed to create a new file");
|
|
1463
|
+
|
|
1464
|
+
assert!(
|
|
1465
|
+
matches!(fs.metadata(path!("/foo.txt")), Ok(Metadata { len: 0, .. })),
|
|
1466
|
+
"checking the `metadata.len` is 0",
|
|
1467
|
+
);
|
|
1468
|
+
assert!(
|
|
1469
|
+
matches!(file.write(b"foobarbazqux").await, Ok(12)),
|
|
1470
|
+
"writing `foobarbazqux`",
|
|
1471
|
+
);
|
|
1472
|
+
|
|
1473
|
+
assert!(
|
|
1474
|
+
matches!(file.seek(io::SeekFrom::Start(0)).await, Ok(0)),
|
|
1475
|
+
"seeking to 0",
|
|
1476
|
+
);
|
|
1477
|
+
|
|
1478
|
+
let mut buffer = [0; 6];
|
|
1479
|
+
assert!(
|
|
1480
|
+
matches!(file.read(&mut buffer[..]).await, Ok(6)),
|
|
1481
|
+
"reading 6 bytes",
|
|
1482
|
+
);
|
|
1483
|
+
assert_eq!(buffer, b"foobar"[..], "checking the 6 bytes");
|
|
1484
|
+
|
|
1485
|
+
assert!(
|
|
1486
|
+
matches!(file.seek(io::SeekFrom::Start(0)).await, Ok(0)),
|
|
1487
|
+
"seeking to 0",
|
|
1488
|
+
);
|
|
1489
|
+
|
|
1490
|
+
let mut buffer = [0; 16];
|
|
1491
|
+
assert!(
|
|
1492
|
+
matches!(file.read(&mut buffer[..]).await, Ok(12)),
|
|
1493
|
+
"reading more bytes than available",
|
|
1494
|
+
);
|
|
1495
|
+
assert_eq!(buffer[..12], b"foobarbazqux"[..], "checking the 12 bytes");
|
|
1496
|
+
assert!(
|
|
1497
|
+
matches!(fs.metadata(path!("/foo.txt")), Ok(Metadata { len: 12, .. })),
|
|
1498
|
+
"checking the `metadata.len` is 0",
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
#[tokio::test]
|
|
1503
|
+
async fn test_reading_to_the_end() {
|
|
1504
|
+
let fs = FileSystem::default();
|
|
1505
|
+
|
|
1506
|
+
let mut file = fs
|
|
1507
|
+
.new_open_options()
|
|
1508
|
+
.read(true)
|
|
1509
|
+
.write(true)
|
|
1510
|
+
.create_new(true)
|
|
1511
|
+
.open(path!("/foo.txt"))
|
|
1512
|
+
.expect("failed to create a new file");
|
|
1513
|
+
|
|
1514
|
+
assert!(
|
|
1515
|
+
matches!(file.write(b"foobarbazqux").await, Ok(12)),
|
|
1516
|
+
"writing `foobarbazqux`",
|
|
1517
|
+
);
|
|
1518
|
+
|
|
1519
|
+
assert!(
|
|
1520
|
+
matches!(file.seek(io::SeekFrom::Start(0)).await, Ok(0)),
|
|
1521
|
+
"seeking to 0",
|
|
1522
|
+
);
|
|
1523
|
+
|
|
1524
|
+
let mut buffer = Vec::new();
|
|
1525
|
+
assert!(
|
|
1526
|
+
matches!(file.read_to_end(&mut buffer).await, Ok(12)),
|
|
1527
|
+
"reading all bytes",
|
|
1528
|
+
);
|
|
1529
|
+
assert_eq!(buffer, b"foobarbazqux"[..], "checking all the bytes");
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
#[tokio::test]
|
|
1533
|
+
async fn test_reading_to_string() {
|
|
1534
|
+
let fs = FileSystem::default();
|
|
1535
|
+
|
|
1536
|
+
let mut file = fs
|
|
1537
|
+
.new_open_options()
|
|
1538
|
+
.read(true)
|
|
1539
|
+
.write(true)
|
|
1540
|
+
.create_new(true)
|
|
1541
|
+
.open(path!("/foo.txt"))
|
|
1542
|
+
.expect("failed to create a new file");
|
|
1543
|
+
|
|
1544
|
+
assert!(
|
|
1545
|
+
matches!(file.write(b"foobarbazqux").await, Ok(12)),
|
|
1546
|
+
"writing `foobarbazqux`",
|
|
1547
|
+
);
|
|
1548
|
+
|
|
1549
|
+
assert!(
|
|
1550
|
+
matches!(file.seek(io::SeekFrom::Start(6)).await, Ok(6)),
|
|
1551
|
+
"seeking to 0",
|
|
1552
|
+
);
|
|
1553
|
+
|
|
1554
|
+
let mut string = String::new();
|
|
1555
|
+
assert!(
|
|
1556
|
+
matches!(file.read_to_string(&mut string).await, Ok(6)),
|
|
1557
|
+
"reading a string",
|
|
1558
|
+
);
|
|
1559
|
+
assert_eq!(string, "bazqux", "checking the string");
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
#[tokio::test]
|
|
1563
|
+
async fn test_reading_exact_buffer() {
|
|
1564
|
+
let fs = FileSystem::default();
|
|
1565
|
+
|
|
1566
|
+
let mut file = fs
|
|
1567
|
+
.new_open_options()
|
|
1568
|
+
.read(true)
|
|
1569
|
+
.write(true)
|
|
1570
|
+
.create_new(true)
|
|
1571
|
+
.open(path!("/foo.txt"))
|
|
1572
|
+
.expect("failed to create a new file");
|
|
1573
|
+
|
|
1574
|
+
assert!(
|
|
1575
|
+
matches!(file.write(b"foobarbazqux").await, Ok(12)),
|
|
1576
|
+
"writing `foobarbazqux`",
|
|
1577
|
+
);
|
|
1578
|
+
|
|
1579
|
+
assert!(
|
|
1580
|
+
matches!(file.seek(io::SeekFrom::Start(6)).await, Ok(6)),
|
|
1581
|
+
"seeking to 0",
|
|
1582
|
+
);
|
|
1583
|
+
|
|
1584
|
+
let mut buffer = [0; 16];
|
|
1585
|
+
assert!(
|
|
1586
|
+
file.read_exact(&mut buffer).await.is_err(),
|
|
1587
|
+
"failing to read an exact buffer",
|
|
1588
|
+
);
|
|
1589
|
+
|
|
1590
|
+
assert!(
|
|
1591
|
+
matches!(file.seek(io::SeekFrom::End(-5)).await, Ok(7)),
|
|
1592
|
+
"seeking to 7",
|
|
1593
|
+
);
|
|
1594
|
+
|
|
1595
|
+
let mut buffer = [0; 3];
|
|
1596
|
+
assert!(
|
|
1597
|
+
file.read_exact(&mut buffer).await.is_ok(),
|
|
1598
|
+
"failing to read an exact buffer",
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
impl fmt::Debug for FileHandle {
|
|
1604
|
+
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
1605
|
+
formatter
|
|
1606
|
+
.debug_struct("FileHandle")
|
|
1607
|
+
.field("inode", &self.inode)
|
|
1608
|
+
.field("readable", &self.readable)
|
|
1609
|
+
.field("writable", &self.writable)
|
|
1610
|
+
.finish()
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
impl Drop for FileHandle {
|
|
1615
|
+
fn drop(&mut self) {
|
|
1616
|
+
let remaining = self.lifecycle.closed();
|
|
1617
|
+
if remaining != 0 || !self.lifecycle.is_unlinked() {
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
let Ok(mut fs) = self.filesystem.inner.write() else {
|
|
1622
|
+
return;
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1625
|
+
let Some(node) = fs.storage.get(self.inode) else {
|
|
1626
|
+
return;
|
|
1627
|
+
};
|
|
1628
|
+
|
|
1629
|
+
let Some(lifecycle) = node.file_lifecycle() else {
|
|
1630
|
+
return;
|
|
1631
|
+
};
|
|
1632
|
+
|
|
1633
|
+
if Arc::ptr_eq(lifecycle, &self.lifecycle)
|
|
1634
|
+
&& lifecycle.is_unlinked()
|
|
1635
|
+
&& lifecycle.open_handle_count() == 0
|
|
1636
|
+
{
|
|
1637
|
+
fs.remove_node(self.inode);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/// The real file! It is simply a buffer of bytes with a cursor that
|
|
1643
|
+
/// represents a read/write position in the buffer.
|
|
1644
|
+
#[derive(Debug)]
|
|
1645
|
+
pub(super) struct File {
|
|
1646
|
+
buffer: TrackedVec,
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
impl File {
|
|
1650
|
+
pub(super) fn new(limiter: Option<crate::limiter::DynFsMemoryLimiter>) -> Self {
|
|
1651
|
+
Self {
|
|
1652
|
+
buffer: TrackedVec::new(limiter),
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
pub(super) fn truncate(&mut self) {
|
|
1657
|
+
self.buffer.clear();
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
pub(super) fn len(&self) -> usize {
|
|
1661
|
+
self.buffer.len()
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
impl File {
|
|
1666
|
+
pub fn read(&self, buf: &mut [u8], cursor: &mut u64) -> io::Result<usize> {
|
|
1667
|
+
let cur_pos = *cursor as usize;
|
|
1668
|
+
|
|
1669
|
+
// POSIX regular files return EOF, not an error, when reading at or beyond EOF.
|
|
1670
|
+
if cur_pos >= self.buffer.len() {
|
|
1671
|
+
return Ok(0);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
let max_to_read = cmp::min(self.buffer.len() - cur_pos, buf.len());
|
|
1675
|
+
let data_to_copy = &self.buffer[cur_pos..][..max_to_read];
|
|
1676
|
+
|
|
1677
|
+
// SAFETY: `buf[..max_to_read]` and `data_to_copy` have the same size, due to
|
|
1678
|
+
// how `max_to_read` is computed.
|
|
1679
|
+
buf[..max_to_read].copy_from_slice(data_to_copy);
|
|
1680
|
+
|
|
1681
|
+
*cursor += max_to_read as u64;
|
|
1682
|
+
|
|
1683
|
+
Ok(max_to_read)
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
impl File {
|
|
1688
|
+
pub fn seek(&self, position: io::SeekFrom, cursor: &mut u64) -> io::Result<u64> {
|
|
1689
|
+
let to_err = |_| io::ErrorKind::InvalidInput;
|
|
1690
|
+
|
|
1691
|
+
// Calculate the next cursor.
|
|
1692
|
+
let next_cursor: i64 = match position {
|
|
1693
|
+
// Calculate from the beginning, so `0 + offset`.
|
|
1694
|
+
io::SeekFrom::Start(offset) => offset.try_into().map_err(to_err)?,
|
|
1695
|
+
|
|
1696
|
+
// Calculate from the end, so `buffer.len() + offset`.
|
|
1697
|
+
io::SeekFrom::End(offset) => {
|
|
1698
|
+
TryInto::<i64>::try_into(self.buffer.len()).map_err(to_err)? + offset
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// Calculate from the current cursor, so `cursor + offset`.
|
|
1702
|
+
io::SeekFrom::Current(offset) => {
|
|
1703
|
+
TryInto::<i64>::try_into(*cursor).map_err(to_err)? + offset
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
|
|
1707
|
+
// It's an error to seek before byte 0.
|
|
1708
|
+
if next_cursor < 0 {
|
|
1709
|
+
return Err(io::Error::new(
|
|
1710
|
+
io::ErrorKind::InvalidInput,
|
|
1711
|
+
"seeking before the byte 0",
|
|
1712
|
+
));
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
let next_cursor = next_cursor.try_into().map_err(to_err)?;
|
|
1716
|
+
*cursor = next_cursor;
|
|
1717
|
+
|
|
1718
|
+
let cursor = *cursor;
|
|
1719
|
+
Ok(cursor)
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
impl File {
|
|
1724
|
+
pub fn write(&mut self, buf: &[u8], cursor: &mut u64) -> io::Result<usize> {
|
|
1725
|
+
let position = *cursor as usize;
|
|
1726
|
+
let end = position
|
|
1727
|
+
.checked_add(buf.len())
|
|
1728
|
+
.ok_or(io::ErrorKind::InvalidInput)?;
|
|
1729
|
+
|
|
1730
|
+
if end > self.buffer.len() {
|
|
1731
|
+
// Grow the entire sparse range transactionally before modifying any
|
|
1732
|
+
// visible bytes. A quota failure therefore cannot leave a partial gap.
|
|
1733
|
+
self.buffer.resize(end, 0)?;
|
|
1734
|
+
}
|
|
1735
|
+
self.buffer[position..end].copy_from_slice(buf);
|
|
1736
|
+
|
|
1737
|
+
*cursor += buf.len() as u64;
|
|
1738
|
+
|
|
1739
|
+
Ok(buf.len())
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
fn flush(&mut self) -> io::Result<()> {
|
|
1743
|
+
Ok(())
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
/// Read only file that uses copy-on-write
|
|
1748
|
+
#[derive(Debug)]
|
|
1749
|
+
pub(super) struct ReadOnlyFile {
|
|
1750
|
+
buffer: OwnedBuffer,
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
impl ReadOnlyFile {
|
|
1754
|
+
pub(super) fn new(buffer: OwnedBuffer) -> Self {
|
|
1755
|
+
Self { buffer }
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
pub(super) fn len(&self) -> usize {
|
|
1759
|
+
self.buffer.len()
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
impl ReadOnlyFile {
|
|
1764
|
+
pub fn read(&self, buf: &mut [u8], cursor: &mut u64) -> io::Result<usize> {
|
|
1765
|
+
let cur_pos = *cursor as usize;
|
|
1766
|
+
let max_to_read = cmp::min(self.buffer.len() - cur_pos, buf.len());
|
|
1767
|
+
let data_to_copy = &self.buffer[cur_pos..][..max_to_read];
|
|
1768
|
+
|
|
1769
|
+
// SAFETY: `buf[..max_to_read]` and `data_to_copy` have the same size, due to
|
|
1770
|
+
// how `max_to_read` is computed.
|
|
1771
|
+
buf[..max_to_read].copy_from_slice(data_to_copy);
|
|
1772
|
+
|
|
1773
|
+
*cursor += max_to_read as u64;
|
|
1774
|
+
|
|
1775
|
+
Ok(max_to_read)
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
impl ReadOnlyFile {
|
|
1780
|
+
pub fn seek(&self, _position: io::SeekFrom, _cursor: &mut u64) -> io::Result<u64> {
|
|
1781
|
+
Err(io::Error::new(
|
|
1782
|
+
io::ErrorKind::PermissionDenied,
|
|
1783
|
+
"file is read-only",
|
|
1784
|
+
))
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
impl ReadOnlyFile {
|
|
1789
|
+
pub fn write(&mut self, _buf: &[u8], _cursor: &mut u64) -> io::Result<usize> {
|
|
1790
|
+
Err(io::Error::new(
|
|
1791
|
+
io::ErrorKind::PermissionDenied,
|
|
1792
|
+
"file is read-only",
|
|
1793
|
+
))
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
pub fn flush(&mut self) -> io::Result<()> {
|
|
1797
|
+
Ok(())
|
|
1798
|
+
}
|
|
1799
|
+
}
|