@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,1390 @@
|
|
|
1
|
+
use crate::{
|
|
2
|
+
DirEntry, FileType, FsError, Metadata, OpenOptions, OpenOptionsConfig, ReadDir, Result,
|
|
3
|
+
VirtualFile,
|
|
4
|
+
};
|
|
5
|
+
use bytes::{Buf, Bytes};
|
|
6
|
+
use futures::future::BoxFuture;
|
|
7
|
+
use std::convert::TryInto;
|
|
8
|
+
use std::fs;
|
|
9
|
+
use std::io::{self, Seek};
|
|
10
|
+
use std::path::{Component, Path, PathBuf};
|
|
11
|
+
use std::pin::Pin;
|
|
12
|
+
use std::sync::Arc;
|
|
13
|
+
use std::task::{Context, Poll};
|
|
14
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
15
|
+
use tokio::fs as tfs;
|
|
16
|
+
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
|
17
|
+
use tokio::runtime::Handle;
|
|
18
|
+
|
|
19
|
+
#[derive(Debug, Clone)]
|
|
20
|
+
pub struct FileSystem {
|
|
21
|
+
handle: Handle,
|
|
22
|
+
root: PathBuf,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[allow(dead_code)]
|
|
26
|
+
fn default_handle() -> Handle {
|
|
27
|
+
Handle::current()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub fn canonicalize(path: &Path) -> Result<PathBuf> {
|
|
31
|
+
if !path.exists() {
|
|
32
|
+
return Err(FsError::InvalidInput);
|
|
33
|
+
}
|
|
34
|
+
dunce::canonicalize(path).map_err(Into::into)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Copied from cargo
|
|
38
|
+
// https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
|
|
39
|
+
pub fn normalize_path(path: &Path) -> PathBuf {
|
|
40
|
+
let mut components = path.components().peekable();
|
|
41
|
+
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
|
42
|
+
components.next();
|
|
43
|
+
PathBuf::from(c.as_os_str())
|
|
44
|
+
} else {
|
|
45
|
+
PathBuf::new()
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
for component in components {
|
|
49
|
+
match component {
|
|
50
|
+
Component::Prefix(..) => unreachable!(),
|
|
51
|
+
Component::RootDir => {
|
|
52
|
+
ret.push(component.as_os_str());
|
|
53
|
+
}
|
|
54
|
+
Component::CurDir => {}
|
|
55
|
+
Component::ParentDir => {
|
|
56
|
+
ret.pop();
|
|
57
|
+
}
|
|
58
|
+
Component::Normal(c) => {
|
|
59
|
+
ret.push(c);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
ret
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fn path_suffix_to_guest_absolute(stripped: &Path) -> PathBuf {
|
|
67
|
+
let mut stripped = stripped.to_string_lossy().into_owned();
|
|
68
|
+
if std::path::MAIN_SEPARATOR == '\\' {
|
|
69
|
+
stripped = stripped.replace('\\', "/");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
PathBuf::from(format!("/{}", stripped.trim_start_matches('/')))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fn strip_host_root(root: &Path, target: &Path) -> Option<PathBuf> {
|
|
76
|
+
target
|
|
77
|
+
.strip_prefix(root)
|
|
78
|
+
.ok()
|
|
79
|
+
.map(path_suffix_to_guest_absolute)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn host_root_relative_target(root: &Path, target: PathBuf) -> PathBuf {
|
|
83
|
+
if root == Path::new("/") || !target.is_absolute() {
|
|
84
|
+
return target;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if let Some(target) = strip_host_root(root, &target) {
|
|
88
|
+
return target;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if let Ok(canonical_target) = canonicalize(&target)
|
|
92
|
+
&& let Some(target) = strip_host_root(root, &canonical_target)
|
|
93
|
+
{
|
|
94
|
+
return target;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
target
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
impl FileSystem {
|
|
101
|
+
pub fn new(handle: Handle, root: impl Into<PathBuf>) -> Result<Self> {
|
|
102
|
+
let root = canonicalize(&root.into())?;
|
|
103
|
+
|
|
104
|
+
Ok(FileSystem { handle, root })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fn prepare_path(&self, path: &Path) -> Result<PathBuf> {
|
|
108
|
+
let path = normalize_path(path);
|
|
109
|
+
|
|
110
|
+
if matches!(path.components().next(), Some(Component::Prefix(..))) {
|
|
111
|
+
return Err(FsError::InvalidInput);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if self.root != Path::new("/") && path.starts_with(&self.root) {
|
|
115
|
+
return Err(FsError::InvalidInput);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let path = path.strip_prefix("/").unwrap_or(&path);
|
|
119
|
+
let path = self.root.join(path);
|
|
120
|
+
|
|
121
|
+
debug_assert!(path.starts_with(&self.root));
|
|
122
|
+
Ok(path)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
impl crate::FileSystem for FileSystem {
|
|
127
|
+
fn readlink(&self, path: &Path) -> Result<PathBuf> {
|
|
128
|
+
let path = self.prepare_path(path)?;
|
|
129
|
+
|
|
130
|
+
let target = fs::read_link(path)?;
|
|
131
|
+
Ok(host_root_relative_target(&self.root, target))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn read_dir(&self, path: &Path) -> Result<ReadDir> {
|
|
135
|
+
let path = self.prepare_path(path)?;
|
|
136
|
+
|
|
137
|
+
let read_dir = fs::read_dir(path)?;
|
|
138
|
+
let mut data = read_dir
|
|
139
|
+
.map(|entry| {
|
|
140
|
+
let entry = entry?;
|
|
141
|
+
|
|
142
|
+
let path = entry
|
|
143
|
+
.path()
|
|
144
|
+
.strip_prefix(&self.root)
|
|
145
|
+
.map_err(|_| FsError::InvalidData)?
|
|
146
|
+
.to_owned();
|
|
147
|
+
let path = Path::new("/").join(path);
|
|
148
|
+
|
|
149
|
+
let metadata = fs::symlink_metadata(entry.path())?;
|
|
150
|
+
|
|
151
|
+
Ok(DirEntry {
|
|
152
|
+
path,
|
|
153
|
+
metadata: Ok(metadata.try_into()?),
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
.collect::<std::result::Result<Vec<DirEntry>, io::Error>>()
|
|
157
|
+
.map_err::<FsError, _>(Into::into)?;
|
|
158
|
+
data.sort_by(|a, b| a.path.file_name().cmp(&b.path.file_name()));
|
|
159
|
+
Ok(ReadDir::new(data))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fn create_dir(&self, path: &Path) -> Result<()> {
|
|
163
|
+
let path = self.prepare_path(path)?;
|
|
164
|
+
|
|
165
|
+
if path.parent().is_none() {
|
|
166
|
+
return Err(FsError::BaseNotDirectory);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fs::create_dir(path).map_err(Into::into)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fn remove_dir(&self, path: &Path) -> Result<()> {
|
|
173
|
+
let path = self.prepare_path(path)?;
|
|
174
|
+
|
|
175
|
+
if path.parent().is_none() {
|
|
176
|
+
return Err(FsError::BaseNotDirectory);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// https://github.com/rust-lang/rust/issues/86442
|
|
180
|
+
// DirectoryNotEmpty is not implemented consistently
|
|
181
|
+
if path.is_dir()
|
|
182
|
+
&& fs::read_dir(&path)
|
|
183
|
+
.map(|mut s| s.next().is_some())
|
|
184
|
+
.unwrap_or(false)
|
|
185
|
+
{
|
|
186
|
+
return Err(FsError::DirectoryNotEmpty);
|
|
187
|
+
}
|
|
188
|
+
fs::remove_dir(path).map_err(Into::into)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
|
|
192
|
+
Box::pin(async move {
|
|
193
|
+
use filetime::{FileTime, set_file_mtime};
|
|
194
|
+
let norm_from = normalize_path(from);
|
|
195
|
+
let norm_to = normalize_path(to);
|
|
196
|
+
|
|
197
|
+
if norm_from.parent().is_none() {
|
|
198
|
+
return Err(FsError::BaseNotDirectory);
|
|
199
|
+
}
|
|
200
|
+
if norm_to.parent().is_none() {
|
|
201
|
+
return Err(FsError::BaseNotDirectory);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let from = self.prepare_path(from)?;
|
|
205
|
+
let to = self.prepare_path(to)?;
|
|
206
|
+
|
|
207
|
+
if !from.exists() {
|
|
208
|
+
return Err(FsError::EntryNotFound);
|
|
209
|
+
}
|
|
210
|
+
let from_parent = from.parent().unwrap();
|
|
211
|
+
let to_parent = to.parent().unwrap();
|
|
212
|
+
if !from_parent.exists() {
|
|
213
|
+
return Err(FsError::EntryNotFound);
|
|
214
|
+
}
|
|
215
|
+
if !to_parent.exists() {
|
|
216
|
+
return Err(FsError::EntryNotFound);
|
|
217
|
+
}
|
|
218
|
+
let result = if from_parent != to_parent {
|
|
219
|
+
let _ = std::fs::create_dir_all(to_parent);
|
|
220
|
+
if from.is_dir() {
|
|
221
|
+
fs_extra::move_items(
|
|
222
|
+
&[&from],
|
|
223
|
+
&to,
|
|
224
|
+
&fs_extra::dir::CopyOptions {
|
|
225
|
+
copy_inside: true,
|
|
226
|
+
..Default::default()
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
.map(|_| ())
|
|
230
|
+
.map_err(|_| FsError::UnknownError)?;
|
|
231
|
+
let _ = fs_extra::remove_items(&[&from]);
|
|
232
|
+
Ok(())
|
|
233
|
+
} else {
|
|
234
|
+
fs::copy(&from, &to).map(|_| ()).map_err(FsError::from)?;
|
|
235
|
+
fs::remove_file(&from).map(|_| ()).map_err(Into::into)
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
fs::rename(&from, &to).map_err(Into::into)
|
|
239
|
+
};
|
|
240
|
+
let _ = set_file_mtime(&to, FileTime::now()).map(|_| ());
|
|
241
|
+
result
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
fn remove_file(&self, path: &Path) -> Result<()> {
|
|
246
|
+
let path = self.prepare_path(path)?;
|
|
247
|
+
|
|
248
|
+
if path.parent().is_none() {
|
|
249
|
+
return Err(FsError::BaseNotDirectory);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fs::remove_file(path).map_err(Into::into)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fn new_open_options(&self) -> OpenOptions<'_> {
|
|
256
|
+
OpenOptions::new(self)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fn metadata(&self, path: &Path) -> Result<Metadata> {
|
|
260
|
+
let path = self.prepare_path(path)?;
|
|
261
|
+
|
|
262
|
+
fs::metadata(path)
|
|
263
|
+
.and_then(TryInto::try_into)
|
|
264
|
+
.map_err(Into::into)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
|
|
268
|
+
let path = self.prepare_path(path)?;
|
|
269
|
+
|
|
270
|
+
fs::symlink_metadata(path)
|
|
271
|
+
.and_then(TryInto::try_into)
|
|
272
|
+
.map_err(Into::into)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
impl TryInto<Metadata> for std::fs::Metadata {
|
|
277
|
+
type Error = io::Error;
|
|
278
|
+
|
|
279
|
+
fn try_into(self) -> std::result::Result<Metadata, Self::Error> {
|
|
280
|
+
let filetype = self.file_type();
|
|
281
|
+
let (char_device, block_device, socket, fifo) = {
|
|
282
|
+
#[cfg(unix)]
|
|
283
|
+
{
|
|
284
|
+
use std::os::unix::fs::FileTypeExt;
|
|
285
|
+
(
|
|
286
|
+
filetype.is_char_device(),
|
|
287
|
+
filetype.is_block_device(),
|
|
288
|
+
filetype.is_socket(),
|
|
289
|
+
filetype.is_fifo(),
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
#[cfg(not(unix))]
|
|
293
|
+
{
|
|
294
|
+
(false, false, false, false)
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
Ok(Metadata {
|
|
299
|
+
ft: FileType {
|
|
300
|
+
dir: filetype.is_dir(),
|
|
301
|
+
file: filetype.is_file(),
|
|
302
|
+
symlink: filetype.is_symlink(),
|
|
303
|
+
char_device,
|
|
304
|
+
block_device,
|
|
305
|
+
socket,
|
|
306
|
+
fifo,
|
|
307
|
+
},
|
|
308
|
+
accessed: self
|
|
309
|
+
.accessed()
|
|
310
|
+
.and_then(|time| time.duration_since(UNIX_EPOCH).map_err(io::Error::other))
|
|
311
|
+
.map_or(0, |time| time.as_nanos() as u64),
|
|
312
|
+
created: self
|
|
313
|
+
.created()
|
|
314
|
+
.and_then(|time| time.duration_since(UNIX_EPOCH).map_err(io::Error::other))
|
|
315
|
+
.map_or(0, |time| time.as_nanos() as u64),
|
|
316
|
+
modified: self
|
|
317
|
+
.modified()
|
|
318
|
+
.and_then(|time| time.duration_since(UNIX_EPOCH).map_err(io::Error::other))
|
|
319
|
+
.map_or(0, |time| time.as_nanos() as u64),
|
|
320
|
+
len: self.len(),
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
impl crate::FileOpener for FileSystem {
|
|
326
|
+
fn open(
|
|
327
|
+
&self,
|
|
328
|
+
path: &Path,
|
|
329
|
+
conf: &OpenOptionsConfig,
|
|
330
|
+
) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
|
|
331
|
+
let path = self.prepare_path(path)?;
|
|
332
|
+
|
|
333
|
+
// TODO: handle create implying write, etc.
|
|
334
|
+
let read = conf.read();
|
|
335
|
+
let write = conf.write();
|
|
336
|
+
|
|
337
|
+
// according to Rust's stdlib, specifying both truncate and append is nonsensical,
|
|
338
|
+
// and it will return an error if we try to open a file with both flags set.
|
|
339
|
+
// in order to prevent this, and stay compatible with native binaries, we just ignore
|
|
340
|
+
// the append flag if truncate is set. the rationale behind this decision is that
|
|
341
|
+
// truncate is going to be applied first and append is going to be ignored anyway.
|
|
342
|
+
let append = if conf.truncate { false } else { conf.append() };
|
|
343
|
+
|
|
344
|
+
let mut oo = fs::OpenOptions::new();
|
|
345
|
+
oo.read(conf.read())
|
|
346
|
+
.write(conf.write())
|
|
347
|
+
.create_new(conf.create_new())
|
|
348
|
+
.create(conf.create())
|
|
349
|
+
.append(append)
|
|
350
|
+
.truncate(conf.truncate())
|
|
351
|
+
.open(&path)
|
|
352
|
+
.map_err(Into::into)
|
|
353
|
+
.map(|file| {
|
|
354
|
+
Box::new(File::new(
|
|
355
|
+
self.handle.clone(),
|
|
356
|
+
file,
|
|
357
|
+
path.to_owned(),
|
|
358
|
+
read,
|
|
359
|
+
write,
|
|
360
|
+
append,
|
|
361
|
+
)) as Box<dyn VirtualFile + Send + Sync + 'static>
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/// A thin wrapper around `std::fs::File`
|
|
367
|
+
#[derive(Debug)]
|
|
368
|
+
pub struct File {
|
|
369
|
+
handle: Handle,
|
|
370
|
+
inner: tfs::File,
|
|
371
|
+
inner_std: fs::File,
|
|
372
|
+
pub host_path: PathBuf,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
impl File {
|
|
376
|
+
const READ: u16 = 1;
|
|
377
|
+
const WRITE: u16 = 2;
|
|
378
|
+
const APPEND: u16 = 4;
|
|
379
|
+
|
|
380
|
+
/// creates a new host file from a `std::fs::File` and a path
|
|
381
|
+
pub fn new(
|
|
382
|
+
handle: Handle,
|
|
383
|
+
file: fs::File,
|
|
384
|
+
host_path: PathBuf,
|
|
385
|
+
read: bool,
|
|
386
|
+
write: bool,
|
|
387
|
+
append: bool,
|
|
388
|
+
) -> Self {
|
|
389
|
+
let mut _flags = 0;
|
|
390
|
+
|
|
391
|
+
if read {
|
|
392
|
+
_flags |= Self::READ;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if write {
|
|
396
|
+
_flags |= Self::WRITE;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if append {
|
|
400
|
+
_flags |= Self::APPEND;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let async_file = tfs::File::from_std(file.try_clone().unwrap());
|
|
404
|
+
Self {
|
|
405
|
+
handle,
|
|
406
|
+
inner_std: file,
|
|
407
|
+
inner: async_file,
|
|
408
|
+
host_path,
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
fn metadata(&self) -> std::fs::Metadata {
|
|
413
|
+
// FIXME: no unwrap!
|
|
414
|
+
self.inner_std.metadata().unwrap()
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
#[async_trait::async_trait]
|
|
419
|
+
impl VirtualFile for File {
|
|
420
|
+
fn last_accessed(&self) -> u64 {
|
|
421
|
+
self.metadata()
|
|
422
|
+
.accessed()
|
|
423
|
+
.ok()
|
|
424
|
+
.and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
|
|
425
|
+
.map(|ct| ct.as_nanos() as u64)
|
|
426
|
+
.unwrap_or(0)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
fn last_modified(&self) -> u64 {
|
|
430
|
+
self.metadata()
|
|
431
|
+
.modified()
|
|
432
|
+
.ok()
|
|
433
|
+
.and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
|
|
434
|
+
.map(|ct| ct.as_nanos() as u64)
|
|
435
|
+
.unwrap_or(0)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
fn created_time(&self) -> u64 {
|
|
439
|
+
self.metadata()
|
|
440
|
+
.created()
|
|
441
|
+
.ok()
|
|
442
|
+
.and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
|
|
443
|
+
.map(|ct| ct.as_nanos() as u64)
|
|
444
|
+
.unwrap_or(0)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
|
|
448
|
+
let atime = atime.map(|t| filetime::FileTime::from_unix_time(t as i64, 0));
|
|
449
|
+
let mtime = mtime.map(|t| filetime::FileTime::from_unix_time(t as i64, 0));
|
|
450
|
+
|
|
451
|
+
filetime::set_file_handle_times(&self.inner_std, atime, mtime)
|
|
452
|
+
.map_err(|_| crate::FsError::IOError)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
fn size(&self) -> u64 {
|
|
456
|
+
self.metadata().len()
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
|
|
460
|
+
fs::File::set_len(&self.inner_std, new_size).map_err(Into::into)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
fn unlink(&mut self) -> Result<()> {
|
|
464
|
+
fs::remove_file(&self.host_path).map_err(Into::into)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
fn get_special_fd(&self) -> Option<u32> {
|
|
468
|
+
None
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
fn poll_read_ready(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
472
|
+
let cursor = match self.inner_std.stream_position() {
|
|
473
|
+
Ok(a) => a,
|
|
474
|
+
Err(err) => return Poll::Ready(Err(err)),
|
|
475
|
+
};
|
|
476
|
+
let end = match self.inner_std.seek(io::SeekFrom::End(0)) {
|
|
477
|
+
Ok(a) => a,
|
|
478
|
+
Err(err) => return Poll::Ready(Err(err)),
|
|
479
|
+
};
|
|
480
|
+
let _ = self.inner_std.seek(io::SeekFrom::Start(cursor));
|
|
481
|
+
|
|
482
|
+
let remaining = end - cursor;
|
|
483
|
+
Poll::Ready(Ok(remaining as usize))
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
487
|
+
Poll::Ready(Ok(8192))
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
impl AsyncRead for File {
|
|
492
|
+
fn poll_read(
|
|
493
|
+
mut self: Pin<&mut Self>,
|
|
494
|
+
cx: &mut Context<'_>,
|
|
495
|
+
buf: &mut tokio::io::ReadBuf<'_>,
|
|
496
|
+
) -> Poll<io::Result<()>> {
|
|
497
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
498
|
+
let inner = Pin::new(&mut self.inner);
|
|
499
|
+
inner.poll_read(cx, buf)
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
impl AsyncWrite for File {
|
|
504
|
+
fn poll_write(
|
|
505
|
+
mut self: Pin<&mut Self>,
|
|
506
|
+
cx: &mut Context<'_>,
|
|
507
|
+
buf: &[u8],
|
|
508
|
+
) -> Poll<io::Result<usize>> {
|
|
509
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
510
|
+
let inner = Pin::new(&mut self.inner);
|
|
511
|
+
inner.poll_write(cx, buf)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
515
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
516
|
+
let inner = Pin::new(&mut self.inner);
|
|
517
|
+
inner.poll_flush(cx)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
521
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
522
|
+
let inner = Pin::new(&mut self.inner);
|
|
523
|
+
inner.poll_shutdown(cx)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
fn poll_write_vectored(
|
|
527
|
+
mut self: Pin<&mut Self>,
|
|
528
|
+
cx: &mut Context<'_>,
|
|
529
|
+
bufs: &[io::IoSlice<'_>],
|
|
530
|
+
) -> Poll<io::Result<usize>> {
|
|
531
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
532
|
+
let inner = Pin::new(&mut self.inner);
|
|
533
|
+
inner.poll_write_vectored(cx, bufs)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
fn is_write_vectored(&self) -> bool {
|
|
537
|
+
self.inner.is_write_vectored()
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
impl AsyncSeek for File {
|
|
542
|
+
fn start_seek(mut self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> {
|
|
543
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
544
|
+
let inner = Pin::new(&mut self.inner);
|
|
545
|
+
inner.start_seek(position)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
|
549
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
550
|
+
let inner = Pin::new(&mut self.inner);
|
|
551
|
+
inner.poll_complete(cx)
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
impl Drop for File {
|
|
556
|
+
fn drop(&mut self) {
|
|
557
|
+
tracing::trace!(?self.host_path, "Closing host file");
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/// A wrapper type around Stdout that implements `VirtualFile`.
|
|
562
|
+
#[derive(Debug)]
|
|
563
|
+
pub struct Stdout {
|
|
564
|
+
handle: Handle,
|
|
565
|
+
inner: tokio::io::Stdout,
|
|
566
|
+
}
|
|
567
|
+
#[allow(dead_code)]
|
|
568
|
+
fn default_stdout() -> tokio::io::Stdout {
|
|
569
|
+
tokio::io::stdout()
|
|
570
|
+
}
|
|
571
|
+
impl Default for Stdout {
|
|
572
|
+
fn default() -> Self {
|
|
573
|
+
Self {
|
|
574
|
+
handle: Handle::current(),
|
|
575
|
+
inner: tokio::io::stdout(),
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/// Default size for write buffers.
|
|
581
|
+
///
|
|
582
|
+
/// Chosen to be both sufficiently large, and a multiple of the default page
|
|
583
|
+
/// size on most systems.
|
|
584
|
+
///
|
|
585
|
+
/// This value has limited meaning, since it is only used for buffer size hints,
|
|
586
|
+
/// and those hints are often ignored.
|
|
587
|
+
const DEFAULT_BUF_SIZE_HINT: usize = 8 * 1024;
|
|
588
|
+
|
|
589
|
+
#[async_trait::async_trait]
|
|
590
|
+
impl VirtualFile for Stdout {
|
|
591
|
+
fn last_accessed(&self) -> u64 {
|
|
592
|
+
0
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
fn last_modified(&self) -> u64 {
|
|
596
|
+
0
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
fn created_time(&self) -> u64 {
|
|
600
|
+
0
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
fn size(&self) -> u64 {
|
|
604
|
+
0
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
|
|
608
|
+
Ok(())
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
fn unlink(&mut self) -> Result<()> {
|
|
612
|
+
Ok(())
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
fn get_special_fd(&self) -> Option<u32> {
|
|
616
|
+
Some(1)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
620
|
+
Poll::Ready(Ok(0))
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
624
|
+
Poll::Ready(Ok(DEFAULT_BUF_SIZE_HINT))
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
impl AsyncRead for Stdout {
|
|
629
|
+
fn poll_read(
|
|
630
|
+
self: Pin<&mut Self>,
|
|
631
|
+
_cx: &mut Context<'_>,
|
|
632
|
+
_buf: &mut tokio::io::ReadBuf<'_>,
|
|
633
|
+
) -> Poll<io::Result<()>> {
|
|
634
|
+
Poll::Ready(Err(io::Error::other("can not read from stdout")))
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
impl AsyncWrite for Stdout {
|
|
639
|
+
fn poll_write(
|
|
640
|
+
mut self: Pin<&mut Self>,
|
|
641
|
+
cx: &mut Context<'_>,
|
|
642
|
+
buf: &[u8],
|
|
643
|
+
) -> Poll<io::Result<usize>> {
|
|
644
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
645
|
+
let inner = Pin::new(&mut self.inner);
|
|
646
|
+
inner.poll_write(cx, buf)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
650
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
651
|
+
let inner = Pin::new(&mut self.inner);
|
|
652
|
+
inner.poll_flush(cx)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
656
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
657
|
+
let inner = Pin::new(&mut self.inner);
|
|
658
|
+
inner.poll_shutdown(cx)
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
fn poll_write_vectored(
|
|
662
|
+
mut self: Pin<&mut Self>,
|
|
663
|
+
cx: &mut Context<'_>,
|
|
664
|
+
bufs: &[io::IoSlice<'_>],
|
|
665
|
+
) -> Poll<io::Result<usize>> {
|
|
666
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
667
|
+
let inner = Pin::new(&mut self.inner);
|
|
668
|
+
inner.poll_write_vectored(cx, bufs)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
fn is_write_vectored(&self) -> bool {
|
|
672
|
+
self.inner.is_write_vectored()
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
impl AsyncSeek for Stdout {
|
|
677
|
+
fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
|
|
678
|
+
Err(io::Error::other("can not seek stdout"))
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
|
682
|
+
Poll::Ready(Err(io::Error::other("can not seek stdout")))
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/// A wrapper type around Stderr that implements `VirtualFile`.
|
|
687
|
+
#[derive(Debug)]
|
|
688
|
+
pub struct Stderr {
|
|
689
|
+
handle: Handle,
|
|
690
|
+
inner: tokio::io::Stderr,
|
|
691
|
+
}
|
|
692
|
+
#[allow(dead_code)]
|
|
693
|
+
fn default_stderr() -> tokio::io::Stderr {
|
|
694
|
+
tokio::io::stderr()
|
|
695
|
+
}
|
|
696
|
+
impl Default for Stderr {
|
|
697
|
+
fn default() -> Self {
|
|
698
|
+
Self {
|
|
699
|
+
handle: Handle::current(),
|
|
700
|
+
inner: tokio::io::stderr(),
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
impl AsyncRead for Stderr {
|
|
706
|
+
fn poll_read(
|
|
707
|
+
self: Pin<&mut Self>,
|
|
708
|
+
_cx: &mut Context<'_>,
|
|
709
|
+
_buf: &mut tokio::io::ReadBuf<'_>,
|
|
710
|
+
) -> Poll<io::Result<()>> {
|
|
711
|
+
Poll::Ready(Err(io::Error::other("can not read from stderr")))
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
impl AsyncWrite for Stderr {
|
|
716
|
+
fn poll_write(
|
|
717
|
+
mut self: Pin<&mut Self>,
|
|
718
|
+
cx: &mut Context<'_>,
|
|
719
|
+
buf: &[u8],
|
|
720
|
+
) -> Poll<io::Result<usize>> {
|
|
721
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
722
|
+
let inner = Pin::new(&mut self.inner);
|
|
723
|
+
inner.poll_write(cx, buf)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
727
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
728
|
+
let inner = Pin::new(&mut self.inner);
|
|
729
|
+
inner.poll_flush(cx)
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
733
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
734
|
+
let inner = Pin::new(&mut self.inner);
|
|
735
|
+
inner.poll_shutdown(cx)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
fn poll_write_vectored(
|
|
739
|
+
mut self: Pin<&mut Self>,
|
|
740
|
+
cx: &mut Context<'_>,
|
|
741
|
+
bufs: &[io::IoSlice<'_>],
|
|
742
|
+
) -> Poll<io::Result<usize>> {
|
|
743
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
744
|
+
let inner = Pin::new(&mut self.inner);
|
|
745
|
+
inner.poll_write_vectored(cx, bufs)
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
fn is_write_vectored(&self) -> bool {
|
|
749
|
+
self.inner.is_write_vectored()
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
impl AsyncSeek for Stderr {
|
|
754
|
+
fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
|
|
755
|
+
Err(io::Error::other("can not seek stderr"))
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
|
759
|
+
Poll::Ready(Err(io::Error::other("can not seek stderr")))
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
#[async_trait::async_trait]
|
|
764
|
+
impl VirtualFile for Stderr {
|
|
765
|
+
fn last_accessed(&self) -> u64 {
|
|
766
|
+
0
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
fn last_modified(&self) -> u64 {
|
|
770
|
+
0
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
fn created_time(&self) -> u64 {
|
|
774
|
+
0
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
fn size(&self) -> u64 {
|
|
778
|
+
0
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
|
|
782
|
+
Ok(())
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
fn unlink(&mut self) -> Result<()> {
|
|
786
|
+
Ok(())
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
fn get_special_fd(&self) -> Option<u32> {
|
|
790
|
+
Some(2)
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
794
|
+
Poll::Ready(Ok(0))
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
798
|
+
Poll::Ready(Ok(8192))
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/// A wrapper type around Stdin that implements `VirtualFile`.
|
|
803
|
+
#[derive(Debug)]
|
|
804
|
+
pub struct Stdin {
|
|
805
|
+
read_buffer: Arc<std::sync::Mutex<Option<Bytes>>>,
|
|
806
|
+
handle: Handle,
|
|
807
|
+
inner: tokio::io::Stdin,
|
|
808
|
+
}
|
|
809
|
+
#[allow(dead_code)]
|
|
810
|
+
fn default_stdin() -> tokio::io::Stdin {
|
|
811
|
+
tokio::io::stdin()
|
|
812
|
+
}
|
|
813
|
+
impl Default for Stdin {
|
|
814
|
+
fn default() -> Self {
|
|
815
|
+
Self {
|
|
816
|
+
handle: Handle::current(),
|
|
817
|
+
read_buffer: Arc::new(std::sync::Mutex::new(None)),
|
|
818
|
+
inner: tokio::io::stdin(),
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
impl AsyncRead for Stdin {
|
|
824
|
+
fn poll_read(
|
|
825
|
+
mut self: Pin<&mut Self>,
|
|
826
|
+
cx: &mut Context<'_>,
|
|
827
|
+
buf: &mut tokio::io::ReadBuf<'_>,
|
|
828
|
+
) -> Poll<io::Result<()>> {
|
|
829
|
+
let max_size = buf.remaining();
|
|
830
|
+
{
|
|
831
|
+
let mut read_buffer = self.read_buffer.lock().unwrap();
|
|
832
|
+
if let Some(read_buffer) = read_buffer.as_mut() {
|
|
833
|
+
let buf_len = read_buffer.len();
|
|
834
|
+
if buf_len > 0 {
|
|
835
|
+
let read = buf_len.min(max_size);
|
|
836
|
+
buf.put_slice(&read_buffer[..read]);
|
|
837
|
+
read_buffer.advance(read);
|
|
838
|
+
return Poll::Ready(Ok(()));
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
844
|
+
let inner = Pin::new(&mut self.inner);
|
|
845
|
+
inner.poll_read(cx, buf)
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
impl AsyncWrite for Stdin {
|
|
850
|
+
fn poll_write(
|
|
851
|
+
self: Pin<&mut Self>,
|
|
852
|
+
_cx: &mut Context<'_>,
|
|
853
|
+
_buf: &[u8],
|
|
854
|
+
) -> Poll<io::Result<usize>> {
|
|
855
|
+
Poll::Ready(Err(io::Error::other("can not wrote to stdin")))
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
859
|
+
Poll::Ready(Err(io::Error::other("can not flush stdin")))
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
863
|
+
Poll::Ready(Err(io::Error::other("can not wrote to stdin")))
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
fn poll_write_vectored(
|
|
867
|
+
self: Pin<&mut Self>,
|
|
868
|
+
_cx: &mut Context<'_>,
|
|
869
|
+
_bufs: &[io::IoSlice<'_>],
|
|
870
|
+
) -> Poll<io::Result<usize>> {
|
|
871
|
+
Poll::Ready(Err(io::Error::other("can not wrote to stdin")))
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
impl AsyncSeek for Stdin {
|
|
876
|
+
fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
|
|
877
|
+
Err(io::Error::other("can not seek stdin"))
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
|
881
|
+
Poll::Ready(Err(io::Error::other("can not seek stdin")))
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
#[async_trait::async_trait]
|
|
886
|
+
impl VirtualFile for Stdin {
|
|
887
|
+
fn last_accessed(&self) -> u64 {
|
|
888
|
+
0
|
|
889
|
+
}
|
|
890
|
+
fn last_modified(&self) -> u64 {
|
|
891
|
+
0
|
|
892
|
+
}
|
|
893
|
+
fn created_time(&self) -> u64 {
|
|
894
|
+
0
|
|
895
|
+
}
|
|
896
|
+
fn size(&self) -> u64 {
|
|
897
|
+
0
|
|
898
|
+
}
|
|
899
|
+
fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
|
|
900
|
+
Ok(())
|
|
901
|
+
}
|
|
902
|
+
fn unlink(&mut self) -> Result<()> {
|
|
903
|
+
Ok(())
|
|
904
|
+
}
|
|
905
|
+
fn get_special_fd(&self) -> Option<u32> {
|
|
906
|
+
Some(0)
|
|
907
|
+
}
|
|
908
|
+
fn poll_read_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
909
|
+
{
|
|
910
|
+
let read_buffer = self.read_buffer.lock().unwrap();
|
|
911
|
+
if let Some(read_buffer) = read_buffer.as_ref() {
|
|
912
|
+
let buf_len = read_buffer.len();
|
|
913
|
+
if buf_len > 0 {
|
|
914
|
+
return Poll::Ready(Ok(buf_len));
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
let _guard = Handle::try_current().map_err(|_| self.handle.enter());
|
|
920
|
+
let inner = Pin::new(&mut self.inner);
|
|
921
|
+
|
|
922
|
+
let mut buf = [0u8; 8192];
|
|
923
|
+
let mut read_buf = ReadBuf::new(&mut buf[..]);
|
|
924
|
+
match inner.poll_read(cx, &mut read_buf) {
|
|
925
|
+
Poll::Pending => Poll::Pending,
|
|
926
|
+
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
|
|
927
|
+
Poll::Ready(Ok(())) => {
|
|
928
|
+
let buf = read_buf.filled();
|
|
929
|
+
let buf_len = buf.len();
|
|
930
|
+
|
|
931
|
+
let mut read_buffer = self.read_buffer.lock().unwrap();
|
|
932
|
+
read_buffer.replace(Bytes::from(buf.to_vec()));
|
|
933
|
+
Poll::Ready(Ok(buf_len))
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
938
|
+
Poll::Ready(Ok(0))
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
#[cfg(test)]
|
|
943
|
+
mod tests {
|
|
944
|
+
use tempfile::TempDir;
|
|
945
|
+
use tokio::runtime::Handle;
|
|
946
|
+
|
|
947
|
+
use super::FileSystem;
|
|
948
|
+
use crate::FileSystem as FileSystemTrait;
|
|
949
|
+
use crate::FsError;
|
|
950
|
+
use std::path::Path;
|
|
951
|
+
|
|
952
|
+
#[tokio::test]
|
|
953
|
+
async fn test_new_filesystem() {
|
|
954
|
+
let temp = TempDir::new().unwrap();
|
|
955
|
+
std::fs::write(temp.path().join("foo2.txt"), b"").unwrap();
|
|
956
|
+
|
|
957
|
+
let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
|
|
958
|
+
assert!(
|
|
959
|
+
fs.read_dir(Path::new("/")).is_ok(),
|
|
960
|
+
"NativeFS can read root"
|
|
961
|
+
);
|
|
962
|
+
assert!(
|
|
963
|
+
fs.new_open_options()
|
|
964
|
+
.read(true)
|
|
965
|
+
.open(Path::new("/foo2.txt"))
|
|
966
|
+
.is_ok(),
|
|
967
|
+
"created foo2.txt"
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
#[tokio::test]
|
|
972
|
+
async fn test_create_dir() {
|
|
973
|
+
let temp: TempDir = TempDir::new().unwrap();
|
|
974
|
+
let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
|
|
975
|
+
|
|
976
|
+
assert_eq!(
|
|
977
|
+
fs.create_dir(Path::new("../")),
|
|
978
|
+
Err(FsError::AlreadyExists),
|
|
979
|
+
"creating a directory out of bounds",
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
assert_eq!(
|
|
983
|
+
fs.create_dir(Path::new("/foo")),
|
|
984
|
+
Ok(()),
|
|
985
|
+
"creating a directory",
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
assert!(
|
|
989
|
+
temp.path().join("foo").exists(),
|
|
990
|
+
"foo dir exists in host_fs"
|
|
991
|
+
);
|
|
992
|
+
|
|
993
|
+
let cur_dir = read_dir_names(&fs, "/");
|
|
994
|
+
|
|
995
|
+
if !cur_dir.contains(&"foo".to_string()) {
|
|
996
|
+
panic!("cur_dir does not contain foo: {cur_dir:#?}");
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
assert!(
|
|
1000
|
+
cur_dir.contains(&"foo".to_string()),
|
|
1001
|
+
"the root is updated and well-defined"
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
assert_eq!(
|
|
1005
|
+
fs.create_dir(Path::new("foo/bar")),
|
|
1006
|
+
Ok(()),
|
|
1007
|
+
"creating a sub-directory",
|
|
1008
|
+
);
|
|
1009
|
+
|
|
1010
|
+
assert!(
|
|
1011
|
+
temp.path().join("foo").join("bar").exists(),
|
|
1012
|
+
"foo dir exists in host_fs"
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
let foo_dir = read_dir_names(&fs, Path::new("/foo"));
|
|
1016
|
+
|
|
1017
|
+
assert!(
|
|
1018
|
+
foo_dir.contains(&"bar".to_string()),
|
|
1019
|
+
"the foo directory is updated and well-defined"
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
let bar_dir = read_dir_names(&fs, Path::new("/foo/bar"));
|
|
1023
|
+
|
|
1024
|
+
assert!(
|
|
1025
|
+
bar_dir.is_empty(),
|
|
1026
|
+
"the foo directory is updated and well-defined"
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
#[tokio::test]
|
|
1031
|
+
async fn test_remove_dir() {
|
|
1032
|
+
let temp: TempDir = TempDir::new().unwrap();
|
|
1033
|
+
let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
|
|
1034
|
+
|
|
1035
|
+
assert_eq!(
|
|
1036
|
+
fs.remove_dir(Path::new("/foo")),
|
|
1037
|
+
Err(FsError::EntryNotFound),
|
|
1038
|
+
"cannot remove a directory that doesn't exist",
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
assert_eq!(
|
|
1042
|
+
fs.create_dir(Path::new("foo")),
|
|
1043
|
+
Ok(()),
|
|
1044
|
+
"creating a directory",
|
|
1045
|
+
);
|
|
1046
|
+
|
|
1047
|
+
assert_eq!(
|
|
1048
|
+
fs.create_dir(Path::new("foo/bar")),
|
|
1049
|
+
Ok(()),
|
|
1050
|
+
"creating a sub-directory",
|
|
1051
|
+
);
|
|
1052
|
+
|
|
1053
|
+
assert!(temp.path().join("foo/bar").exists(), "./foo/bar exists");
|
|
1054
|
+
|
|
1055
|
+
assert_eq!(
|
|
1056
|
+
fs.remove_dir(Path::new("foo")),
|
|
1057
|
+
Err(FsError::DirectoryNotEmpty),
|
|
1058
|
+
"removing a directory that has children",
|
|
1059
|
+
);
|
|
1060
|
+
|
|
1061
|
+
assert_eq!(
|
|
1062
|
+
fs.remove_dir(Path::new("foo/bar")),
|
|
1063
|
+
Ok(()),
|
|
1064
|
+
"removing a sub-directory",
|
|
1065
|
+
);
|
|
1066
|
+
|
|
1067
|
+
assert_eq!(
|
|
1068
|
+
fs.remove_dir(Path::new("foo")),
|
|
1069
|
+
Ok(()),
|
|
1070
|
+
"removing a directory",
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
let cur_dir = read_dir_names(&fs, "/");
|
|
1074
|
+
|
|
1075
|
+
assert!(
|
|
1076
|
+
!cur_dir.contains(&"foo".to_string()),
|
|
1077
|
+
"the foo directory still exists"
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
fn read_dir_names(fs: &FileSystem, path: impl AsRef<Path>) -> Vec<String> {
|
|
1082
|
+
fs.read_dir(path.as_ref())
|
|
1083
|
+
.unwrap()
|
|
1084
|
+
.filter_map(|entry| Some(entry.ok()?.file_name().to_str()?.to_string()))
|
|
1085
|
+
.collect::<Vec<_>>()
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
#[tokio::test]
|
|
1089
|
+
async fn test_rename() {
|
|
1090
|
+
let temp: TempDir = TempDir::new().unwrap();
|
|
1091
|
+
let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
|
|
1092
|
+
std::fs::create_dir_all(temp.path().join("foo").join("qux")).unwrap();
|
|
1093
|
+
let foo = Path::new("foo");
|
|
1094
|
+
let bar = Path::new("bar");
|
|
1095
|
+
let foo_realpath = temp.path().join(foo);
|
|
1096
|
+
let bar_realpath = temp.path().join(bar);
|
|
1097
|
+
|
|
1098
|
+
assert_eq!(
|
|
1099
|
+
fs.rename(Path::new("/"), Path::new("/bar")).await,
|
|
1100
|
+
Err(FsError::BaseNotDirectory),
|
|
1101
|
+
"renaming a directory that has no parent",
|
|
1102
|
+
);
|
|
1103
|
+
assert_eq!(
|
|
1104
|
+
fs.rename(Path::new("/foo"), Path::new("/")).await,
|
|
1105
|
+
Err(FsError::BaseNotDirectory),
|
|
1106
|
+
"renaming to a directory that has no parent",
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
assert_eq!(
|
|
1110
|
+
fs.rename(foo, &foo.join("bar").join("baz"),).await,
|
|
1111
|
+
Err(FsError::EntryNotFound),
|
|
1112
|
+
"renaming to a directory that has parent that doesn't exist",
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
// On Windows, rename "to" must not be an existing directory
|
|
1116
|
+
#[cfg(not(target_os = "windows"))]
|
|
1117
|
+
assert_eq!(fs.create_dir(bar), Ok(()));
|
|
1118
|
+
|
|
1119
|
+
assert_eq!(
|
|
1120
|
+
fs.rename(foo, bar).await,
|
|
1121
|
+
Ok(()),
|
|
1122
|
+
"renaming to a directory that has parent that exists",
|
|
1123
|
+
);
|
|
1124
|
+
|
|
1125
|
+
assert!(
|
|
1126
|
+
fs.new_open_options()
|
|
1127
|
+
.write(true)
|
|
1128
|
+
.create_new(true)
|
|
1129
|
+
.open(bar.join("hello1.txt"))
|
|
1130
|
+
.is_ok(),
|
|
1131
|
+
"creating a new file (`hello1.txt`)",
|
|
1132
|
+
);
|
|
1133
|
+
assert!(
|
|
1134
|
+
fs.new_open_options()
|
|
1135
|
+
.write(true)
|
|
1136
|
+
.create_new(true)
|
|
1137
|
+
.open(bar.join("hello2.txt"))
|
|
1138
|
+
.is_ok(),
|
|
1139
|
+
"creating a new file (`hello2.txt`)",
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
let cur_dir = read_dir_names(&fs, Path::new("/"));
|
|
1143
|
+
|
|
1144
|
+
assert!(
|
|
1145
|
+
!cur_dir.contains(&"foo".to_string()),
|
|
1146
|
+
"the foo directory still exists"
|
|
1147
|
+
);
|
|
1148
|
+
|
|
1149
|
+
assert!(
|
|
1150
|
+
cur_dir.contains(&"bar".to_string()),
|
|
1151
|
+
"the bar directory still exists"
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
let bar_dir = read_dir_names(&fs, bar);
|
|
1155
|
+
|
|
1156
|
+
if !bar_dir.contains(&"qux".to_string()) {
|
|
1157
|
+
println!("qux does not exist: {bar_dir:?}")
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
let qux_dir = read_dir_names(&fs, bar.join("qux"));
|
|
1161
|
+
|
|
1162
|
+
assert!(qux_dir.is_empty(), "the qux directory is empty");
|
|
1163
|
+
|
|
1164
|
+
assert!(
|
|
1165
|
+
bar_realpath.join("hello1.txt").exists(),
|
|
1166
|
+
"the /bar/hello1.txt file exists"
|
|
1167
|
+
);
|
|
1168
|
+
|
|
1169
|
+
assert!(
|
|
1170
|
+
bar_realpath.join("hello2.txt").exists(),
|
|
1171
|
+
"the /bar/hello2.txt file exists"
|
|
1172
|
+
);
|
|
1173
|
+
|
|
1174
|
+
assert_eq!(fs.create_dir(foo), Ok(()), "create ./foo again");
|
|
1175
|
+
|
|
1176
|
+
assert_eq!(
|
|
1177
|
+
fs.rename(&bar.join("hello2.txt"), &foo.join("world2.txt"))
|
|
1178
|
+
.await,
|
|
1179
|
+
Ok(()),
|
|
1180
|
+
"renaming (and moving) a file",
|
|
1181
|
+
);
|
|
1182
|
+
|
|
1183
|
+
assert_eq!(
|
|
1184
|
+
fs.rename(foo, &bar.join("baz")).await,
|
|
1185
|
+
Ok(()),
|
|
1186
|
+
"renaming a directory",
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
assert_eq!(
|
|
1190
|
+
fs.rename(&bar.join("hello1.txt"), &bar.join("world1.txt"))
|
|
1191
|
+
.await,
|
|
1192
|
+
Ok(()),
|
|
1193
|
+
"renaming a file (in the same directory)",
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
assert!(bar_realpath.exists(), "./bar exists");
|
|
1197
|
+
assert!(bar_realpath.join("baz").exists(), "./bar/baz exists");
|
|
1198
|
+
assert!(!foo_realpath.exists(), "foo does not exist anymore");
|
|
1199
|
+
assert!(
|
|
1200
|
+
bar_realpath.join("baz/world2.txt").exists(),
|
|
1201
|
+
"/bar/baz/world2.txt exists"
|
|
1202
|
+
);
|
|
1203
|
+
assert!(
|
|
1204
|
+
bar_realpath.join("world1.txt").exists(),
|
|
1205
|
+
"/bar/world1.txt (ex hello1.txt) exists"
|
|
1206
|
+
);
|
|
1207
|
+
assert!(
|
|
1208
|
+
!bar_realpath.join("hello1.txt").exists(),
|
|
1209
|
+
"hello1.txt was moved"
|
|
1210
|
+
);
|
|
1211
|
+
assert!(
|
|
1212
|
+
!bar_realpath.join("hello2.txt").exists(),
|
|
1213
|
+
"hello2.txt was moved"
|
|
1214
|
+
);
|
|
1215
|
+
assert!(
|
|
1216
|
+
bar_realpath.join("baz/world2.txt").exists(),
|
|
1217
|
+
"world2.txt was moved to the correct place"
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
#[tokio::test]
|
|
1222
|
+
async fn test_metadata() {
|
|
1223
|
+
use std::thread::sleep;
|
|
1224
|
+
use std::time::Duration;
|
|
1225
|
+
|
|
1226
|
+
let temp = TempDir::new().unwrap();
|
|
1227
|
+
|
|
1228
|
+
let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
|
|
1229
|
+
|
|
1230
|
+
let root_metadata = fs.metadata(Path::new("/")).unwrap();
|
|
1231
|
+
|
|
1232
|
+
assert!(root_metadata.ft.dir);
|
|
1233
|
+
// it seems created is not available on musl, at least on CI testing.
|
|
1234
|
+
#[cfg(not(target_env = "musl"))]
|
|
1235
|
+
assert_eq!(root_metadata.accessed, root_metadata.created);
|
|
1236
|
+
#[cfg(not(target_env = "musl"))]
|
|
1237
|
+
assert_eq!(root_metadata.modified, root_metadata.created);
|
|
1238
|
+
assert!(root_metadata.modified > 0);
|
|
1239
|
+
|
|
1240
|
+
let foo = Path::new("foo");
|
|
1241
|
+
|
|
1242
|
+
assert_eq!(fs.create_dir(foo), Ok(()));
|
|
1243
|
+
|
|
1244
|
+
let foo_metadata = fs.metadata(foo);
|
|
1245
|
+
assert!(foo_metadata.is_ok());
|
|
1246
|
+
let foo_metadata = foo_metadata.unwrap();
|
|
1247
|
+
|
|
1248
|
+
assert!(foo_metadata.ft.dir);
|
|
1249
|
+
#[cfg(not(target_env = "musl"))]
|
|
1250
|
+
assert_eq!(foo_metadata.accessed, foo_metadata.created);
|
|
1251
|
+
#[cfg(not(target_env = "musl"))]
|
|
1252
|
+
assert_eq!(foo_metadata.modified, foo_metadata.created);
|
|
1253
|
+
assert!(foo_metadata.modified > 0);
|
|
1254
|
+
|
|
1255
|
+
sleep(Duration::from_secs(3));
|
|
1256
|
+
|
|
1257
|
+
let bar = Path::new("bar");
|
|
1258
|
+
|
|
1259
|
+
assert_eq!(fs.rename(foo, bar).await, Ok(()));
|
|
1260
|
+
|
|
1261
|
+
let bar_metadata = fs.metadata(bar).unwrap();
|
|
1262
|
+
assert!(bar_metadata.ft.dir);
|
|
1263
|
+
assert!(bar_metadata.accessed >= foo_metadata.accessed);
|
|
1264
|
+
assert_eq!(bar_metadata.created, foo_metadata.created);
|
|
1265
|
+
assert!(bar_metadata.modified > foo_metadata.modified);
|
|
1266
|
+
|
|
1267
|
+
let root_metadata = fs.metadata(bar).unwrap();
|
|
1268
|
+
assert!(
|
|
1269
|
+
root_metadata.modified > foo_metadata.modified,
|
|
1270
|
+
"the parent modified time was updated"
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
#[tokio::test]
|
|
1275
|
+
async fn test_rejects_host_absolute_paths_inside_root() {
|
|
1276
|
+
let temp = TempDir::new().unwrap();
|
|
1277
|
+
// Some platforms (e.g. mac) symlink /tmp to /private/tmp, so we need to canonicalize
|
|
1278
|
+
// the path to get the real one, making sure the guest and host paths line up.
|
|
1279
|
+
let temp_canon = super::canonicalize(temp.path()).expect("canonicalize temp dir");
|
|
1280
|
+
|
|
1281
|
+
let file_path = temp_canon.join("foo.txt");
|
|
1282
|
+
std::fs::write(&file_path, b"hello").unwrap();
|
|
1283
|
+
|
|
1284
|
+
let fs = FileSystem::new(Handle::current(), &temp_canon).expect("get filesystem");
|
|
1285
|
+
|
|
1286
|
+
assert_eq!(fs.metadata(&file_path), Err(FsError::InvalidInput));
|
|
1287
|
+
assert!(matches!(
|
|
1288
|
+
fs.new_open_options().read(true).open(&file_path),
|
|
1289
|
+
Err(FsError::InvalidInput)
|
|
1290
|
+
));
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
#[tokio::test]
|
|
1294
|
+
async fn test_remove_file() {
|
|
1295
|
+
let temp = TempDir::new().unwrap();
|
|
1296
|
+
let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
|
|
1297
|
+
|
|
1298
|
+
assert!(
|
|
1299
|
+
fs.new_open_options()
|
|
1300
|
+
.write(true)
|
|
1301
|
+
.create_new(true)
|
|
1302
|
+
.open(Path::new("foo.txt"))
|
|
1303
|
+
.is_ok(),
|
|
1304
|
+
"creating a new file",
|
|
1305
|
+
);
|
|
1306
|
+
|
|
1307
|
+
assert!(read_dir_names(&fs, Path::new("/")).contains(&"foo.txt".to_string()));
|
|
1308
|
+
|
|
1309
|
+
assert!(temp.path().join("foo.txt").is_file());
|
|
1310
|
+
|
|
1311
|
+
assert_eq!(
|
|
1312
|
+
fs.remove_file(Path::new("foo.txt")),
|
|
1313
|
+
Ok(()),
|
|
1314
|
+
"removing a file that exists",
|
|
1315
|
+
);
|
|
1316
|
+
|
|
1317
|
+
assert!(!temp.path().join("foo.txt").exists());
|
|
1318
|
+
|
|
1319
|
+
assert_eq!(
|
|
1320
|
+
fs.remove_file(Path::new("foo.txt")),
|
|
1321
|
+
Err(FsError::EntryNotFound),
|
|
1322
|
+
"removing a file that doesn't exists",
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
#[tokio::test]
|
|
1327
|
+
async fn test_readdir() {
|
|
1328
|
+
let temp = TempDir::new().unwrap();
|
|
1329
|
+
let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
|
|
1330
|
+
|
|
1331
|
+
assert_eq!(fs.create_dir(Path::new("foo")), Ok(()), "creating `foo`");
|
|
1332
|
+
assert_eq!(
|
|
1333
|
+
fs.create_dir(Path::new("foo/sub")),
|
|
1334
|
+
Ok(()),
|
|
1335
|
+
"creating `sub`"
|
|
1336
|
+
);
|
|
1337
|
+
assert_eq!(fs.create_dir(Path::new("bar")), Ok(()), "creating `bar`");
|
|
1338
|
+
assert_eq!(fs.create_dir(Path::new("baz")), Ok(()), "creating `bar`");
|
|
1339
|
+
assert!(
|
|
1340
|
+
fs.new_open_options()
|
|
1341
|
+
.write(true)
|
|
1342
|
+
.create_new(true)
|
|
1343
|
+
.open(Path::new("a.txt"))
|
|
1344
|
+
.is_ok(),
|
|
1345
|
+
"creating `a.txt`",
|
|
1346
|
+
);
|
|
1347
|
+
assert!(
|
|
1348
|
+
fs.new_open_options()
|
|
1349
|
+
.write(true)
|
|
1350
|
+
.create_new(true)
|
|
1351
|
+
.open(Path::new("b.txt"))
|
|
1352
|
+
.is_ok(),
|
|
1353
|
+
"creating `b.txt`",
|
|
1354
|
+
);
|
|
1355
|
+
|
|
1356
|
+
let readdir = fs.read_dir(Path::new("/"));
|
|
1357
|
+
|
|
1358
|
+
assert!(
|
|
1359
|
+
readdir.is_ok(),
|
|
1360
|
+
"reading the directory `{}`",
|
|
1361
|
+
Path::new("/").display()
|
|
1362
|
+
);
|
|
1363
|
+
|
|
1364
|
+
let mut readdir = readdir.unwrap();
|
|
1365
|
+
|
|
1366
|
+
let next = readdir.next().unwrap().unwrap();
|
|
1367
|
+
assert!(next.path.ends_with("a.txt"), "checking entry #1");
|
|
1368
|
+
assert!(next.metadata().unwrap().is_file(), "checking entry #1");
|
|
1369
|
+
|
|
1370
|
+
let next = readdir.next().unwrap().unwrap();
|
|
1371
|
+
assert!(next.path.ends_with("b.txt"), "checking entry #2");
|
|
1372
|
+
assert!(next.metadata().unwrap().is_file(), "checking entry #2");
|
|
1373
|
+
|
|
1374
|
+
let next = readdir.next().unwrap().unwrap();
|
|
1375
|
+
assert!(next.path.ends_with("bar"), "checking entry #3");
|
|
1376
|
+
assert!(next.metadata().unwrap().is_dir(), "checking entry #3");
|
|
1377
|
+
|
|
1378
|
+
let next = readdir.next().unwrap().unwrap();
|
|
1379
|
+
assert!(next.path.ends_with("baz"), "checking entry #4");
|
|
1380
|
+
assert!(next.metadata().unwrap().is_dir(), "checking entry #4");
|
|
1381
|
+
|
|
1382
|
+
let next = readdir.next().unwrap().unwrap();
|
|
1383
|
+
assert!(next.path.ends_with("foo"), "checking entry #5");
|
|
1384
|
+
assert!(next.metadata().unwrap().is_dir(), "checking entry #5");
|
|
1385
|
+
|
|
1386
|
+
if let Some(s) = readdir.next() {
|
|
1387
|
+
panic!("next: {s:?}");
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|