Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wasi-common/src/sync/dir.rs
1693 views
1
use crate::sync::file::{File, filetype_from};
2
use crate::{
3
Error, ErrorExt,
4
dir::{ReaddirCursor, ReaddirEntity, WasiDir},
5
file::{FdFlags, FileType, Filestat, OFlags},
6
};
7
use cap_fs_ext::{DirEntryExt, DirExt, MetadataExt, OpenOptionsMaybeDirExt, SystemTimeSpec};
8
use cap_std::fs;
9
use std::any::Any;
10
use std::path::{Path, PathBuf};
11
use system_interface::fs::GetSetFdFlags;
12
13
pub struct Dir(fs::Dir);
14
15
pub enum OpenResult {
16
File(File),
17
Dir(Dir),
18
}
19
20
impl Dir {
21
pub fn from_cap_std(dir: fs::Dir) -> Self {
22
Dir(dir)
23
}
24
25
pub fn open_file_(
26
&self,
27
symlink_follow: bool,
28
path: &str,
29
oflags: OFlags,
30
read: bool,
31
write: bool,
32
fdflags: FdFlags,
33
) -> Result<OpenResult, Error> {
34
use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt};
35
36
let mut opts = fs::OpenOptions::new();
37
opts.maybe_dir(true);
38
39
if oflags.contains(OFlags::CREATE | OFlags::EXCLUSIVE) {
40
opts.create_new(true);
41
opts.write(true);
42
} else if oflags.contains(OFlags::CREATE) {
43
opts.create(true);
44
opts.write(true);
45
}
46
if oflags.contains(OFlags::TRUNCATE) {
47
opts.truncate(true);
48
}
49
if read {
50
opts.read(true);
51
}
52
if write {
53
opts.write(true);
54
} else {
55
// If not opened write, open read. This way the OS lets us open the file.
56
// If FileCaps::READ is not set, read calls will be rejected at the
57
// get_cap check.
58
opts.read(true);
59
}
60
if fdflags.contains(FdFlags::APPEND) {
61
opts.append(true);
62
}
63
64
if symlink_follow {
65
opts.follow(FollowSymlinks::Yes);
66
} else {
67
opts.follow(FollowSymlinks::No);
68
}
69
// the DSYNC, SYNC, and RSYNC flags are ignored! We do not
70
// have support for them in cap-std yet.
71
// ideally OpenOptions would just support this though:
72
// https://github.com/bytecodealliance/cap-std/issues/146
73
if fdflags.intersects(
74
crate::file::FdFlags::DSYNC | crate::file::FdFlags::SYNC | crate::file::FdFlags::RSYNC,
75
) {
76
return Err(Error::not_supported().context("SYNC family of FdFlags"));
77
}
78
79
if oflags.contains(OFlags::DIRECTORY) {
80
if oflags.contains(OFlags::CREATE)
81
|| oflags.contains(OFlags::EXCLUSIVE)
82
|| oflags.contains(OFlags::TRUNCATE)
83
{
84
return Err(Error::invalid_argument().context("directory oflags"));
85
}
86
}
87
88
let mut f = self.0.open_with(Path::new(path), &opts)?;
89
if f.metadata()?.is_dir() {
90
Ok(OpenResult::Dir(Dir::from_cap_std(fs::Dir::from_std_file(
91
f.into_std(),
92
))))
93
} else if oflags.contains(OFlags::DIRECTORY) {
94
Err(Error::not_dir().context("expected directory but got file"))
95
} else {
96
// NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags:
97
if fdflags.contains(crate::file::FdFlags::NONBLOCK) {
98
let set_fd_flags = f.new_set_fd_flags(system_interface::fs::FdFlags::NONBLOCK)?;
99
f.set_fd_flags(set_fd_flags)?;
100
}
101
Ok(OpenResult::File(File::from_cap_std(f)))
102
}
103
}
104
105
pub fn rename_(&self, src_path: &str, dest_dir: &Self, dest_path: &str) -> Result<(), Error> {
106
self.0
107
.rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path))?;
108
Ok(())
109
}
110
pub fn hard_link_(
111
&self,
112
src_path: &str,
113
target_dir: &Self,
114
target_path: &str,
115
) -> Result<(), Error> {
116
let src_path = Path::new(src_path);
117
let target_path = Path::new(target_path);
118
self.0.hard_link(src_path, &target_dir.0, target_path)?;
119
Ok(())
120
}
121
}
122
123
#[wiggle::async_trait]
124
impl WasiDir for Dir {
125
fn as_any(&self) -> &dyn Any {
126
self
127
}
128
async fn open_file(
129
&self,
130
symlink_follow: bool,
131
path: &str,
132
oflags: OFlags,
133
read: bool,
134
write: bool,
135
fdflags: FdFlags,
136
) -> Result<crate::dir::OpenResult, Error> {
137
let f = self.open_file_(symlink_follow, path, oflags, read, write, fdflags)?;
138
match f {
139
OpenResult::File(f) => Ok(crate::dir::OpenResult::File(Box::new(f))),
140
OpenResult::Dir(d) => Ok(crate::dir::OpenResult::Dir(Box::new(d))),
141
}
142
}
143
144
async fn create_dir(&self, path: &str) -> Result<(), Error> {
145
self.0.create_dir(Path::new(path))?;
146
Ok(())
147
}
148
async fn readdir(
149
&self,
150
cursor: ReaddirCursor,
151
) -> Result<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>> + Send>, Error> {
152
// We need to keep a full-fidelity io Error around to check for a special failure mode
153
// on windows, but also this function can fail due to an illegal byte sequence in a
154
// filename, which we can't construct an io Error to represent.
155
enum ReaddirError {
156
Io(std::io::Error),
157
IllegalSequence,
158
}
159
impl From<std::io::Error> for ReaddirError {
160
fn from(e: std::io::Error) -> ReaddirError {
161
ReaddirError::Io(e)
162
}
163
}
164
165
// cap_std's read_dir does not include . and .., we should prepend these.
166
// Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't
167
// have enough info to make a ReaddirEntity yet.
168
let dir_meta = self.0.dir_metadata()?;
169
let rd = vec![
170
{
171
let name = ".".to_owned();
172
Ok::<_, ReaddirError>((FileType::Directory, dir_meta.ino(), name))
173
},
174
{
175
let name = "..".to_owned();
176
Ok((FileType::Directory, dir_meta.ino(), name))
177
},
178
]
179
.into_iter()
180
.chain({
181
// Now process the `DirEntry`s:
182
let entries = self.0.entries()?.map(|entry| {
183
let entry = entry?;
184
let meta = entry.full_metadata()?;
185
let inode = meta.ino();
186
let filetype = filetype_from(&meta.file_type());
187
let name = entry
188
.file_name()
189
.into_string()
190
.map_err(|_| ReaddirError::IllegalSequence)?;
191
Ok((filetype, inode, name))
192
});
193
194
// On Windows, filter out files like `C:\DumpStack.log.tmp` which we
195
// can't get a full metadata for.
196
#[cfg(windows)]
197
let entries = entries.filter(|entry| {
198
use windows_sys::Win32::Foundation::{
199
ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION,
200
};
201
if let Err(ReaddirError::Io(err)) = entry {
202
if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)
203
|| err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)
204
{
205
return false;
206
}
207
}
208
true
209
});
210
211
entries
212
})
213
// Enumeration of the iterator makes it possible to define the ReaddirCursor
214
.enumerate()
215
.map(|(ix, r)| match r {
216
Ok((filetype, inode, name)) => Ok(ReaddirEntity {
217
next: ReaddirCursor::from(ix as u64 + 1),
218
filetype,
219
inode,
220
name,
221
}),
222
Err(ReaddirError::Io(e)) => Err(e.into()),
223
Err(ReaddirError::IllegalSequence) => Err(Error::illegal_byte_sequence()),
224
})
225
.skip(u64::from(cursor) as usize);
226
227
Ok(Box::new(rd))
228
}
229
230
async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> {
231
self.0.symlink(src_path, dest_path)?;
232
Ok(())
233
}
234
async fn remove_dir(&self, path: &str) -> Result<(), Error> {
235
self.0.remove_dir(Path::new(path))?;
236
Ok(())
237
}
238
239
async fn unlink_file(&self, path: &str) -> Result<(), Error> {
240
self.0.remove_file_or_symlink(Path::new(path))?;
241
Ok(())
242
}
243
async fn read_link(&self, path: &str) -> Result<PathBuf, Error> {
244
let link = self.0.read_link(Path::new(path))?;
245
Ok(link)
246
}
247
async fn get_filestat(&self) -> Result<Filestat, Error> {
248
let meta = self.0.dir_metadata()?;
249
Ok(Filestat {
250
device_id: meta.dev(),
251
inode: meta.ino(),
252
filetype: filetype_from(&meta.file_type()),
253
nlink: meta.nlink(),
254
size: meta.len(),
255
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
256
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
257
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
258
})
259
}
260
async fn get_path_filestat(
261
&self,
262
path: &str,
263
follow_symlinks: bool,
264
) -> Result<Filestat, Error> {
265
let meta = if follow_symlinks {
266
self.0.metadata(Path::new(path))?
267
} else {
268
self.0.symlink_metadata(Path::new(path))?
269
};
270
Ok(Filestat {
271
device_id: meta.dev(),
272
inode: meta.ino(),
273
filetype: filetype_from(&meta.file_type()),
274
nlink: meta.nlink(),
275
size: meta.len(),
276
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
277
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
278
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
279
})
280
}
281
async fn rename(
282
&self,
283
src_path: &str,
284
dest_dir: &dyn WasiDir,
285
dest_path: &str,
286
) -> Result<(), Error> {
287
let dest_dir = dest_dir
288
.as_any()
289
.downcast_ref::<Self>()
290
.ok_or(Error::badf().context("failed downcast to cap-std Dir"))?;
291
self.rename_(src_path, dest_dir, dest_path)
292
}
293
async fn hard_link(
294
&self,
295
src_path: &str,
296
target_dir: &dyn WasiDir,
297
target_path: &str,
298
) -> Result<(), Error> {
299
let target_dir = target_dir
300
.as_any()
301
.downcast_ref::<Self>()
302
.ok_or(Error::badf().context("failed downcast to cap-std Dir"))?;
303
self.hard_link_(src_path, target_dir, target_path)
304
}
305
async fn set_times(
306
&self,
307
path: &str,
308
atime: Option<crate::SystemTimeSpec>,
309
mtime: Option<crate::SystemTimeSpec>,
310
follow_symlinks: bool,
311
) -> Result<(), Error> {
312
if follow_symlinks {
313
self.0.set_times(
314
Path::new(path),
315
convert_systimespec(atime),
316
convert_systimespec(mtime),
317
)?;
318
} else {
319
self.0.set_symlink_times(
320
Path::new(path),
321
convert_systimespec(atime),
322
convert_systimespec(mtime),
323
)?;
324
}
325
Ok(())
326
}
327
}
328
329
fn convert_systimespec(t: Option<crate::SystemTimeSpec>) -> Option<SystemTimeSpec> {
330
match t {
331
Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)),
332
Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
333
None => None,
334
}
335
}
336
337
#[cfg(test)]
338
mod test {
339
use super::Dir;
340
use crate::file::{FdFlags, OFlags};
341
use cap_std::ambient_authority;
342
#[test]
343
fn scratch_dir() {
344
let tempdir = tempfile::Builder::new()
345
.prefix("cap-std-sync")
346
.tempdir()
347
.expect("create temporary dir");
348
let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority())
349
.expect("open ambient temporary dir");
350
let preopen_dir = Dir::from_cap_std(preopen_dir);
351
run(crate::WasiDir::open_file(
352
&preopen_dir,
353
false,
354
".",
355
OFlags::empty(),
356
false,
357
false,
358
FdFlags::empty(),
359
))
360
.expect("open the same directory via WasiDir abstraction");
361
}
362
363
// Readdir does not work on windows, so we won't test it there.
364
#[cfg(not(windows))]
365
#[test]
366
fn readdir() {
367
use crate::dir::{ReaddirCursor, ReaddirEntity, WasiDir};
368
use crate::file::{FdFlags, FileType, OFlags};
369
use std::collections::HashMap;
370
371
fn readdir_into_map(dir: &dyn WasiDir) -> HashMap<String, ReaddirEntity> {
372
let mut out = HashMap::new();
373
for readdir_result in
374
run(dir.readdir(ReaddirCursor::from(0))).expect("readdir succeeds")
375
{
376
let entity = readdir_result.expect("readdir entry is valid");
377
out.insert(entity.name.clone(), entity);
378
}
379
out
380
}
381
382
let tempdir = tempfile::Builder::new()
383
.prefix("cap-std-sync")
384
.tempdir()
385
.expect("create temporary dir");
386
let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority())
387
.expect("open ambient temporary dir");
388
let preopen_dir = Dir::from_cap_std(preopen_dir);
389
390
let entities = readdir_into_map(&preopen_dir);
391
assert_eq!(
392
entities.len(),
393
2,
394
"should just be . and .. in empty dir: {entities:?}"
395
);
396
assert!(entities.get(".").is_some());
397
assert!(entities.get("..").is_some());
398
399
run(preopen_dir.open_file(
400
false,
401
"file1",
402
OFlags::CREATE,
403
true,
404
false,
405
FdFlags::empty(),
406
))
407
.expect("create file1");
408
409
let entities = readdir_into_map(&preopen_dir);
410
assert_eq!(entities.len(), 3, "should be ., .., file1 {entities:?}");
411
assert_eq!(
412
entities.get(".").expect(". entry").filetype,
413
FileType::Directory
414
);
415
assert_eq!(
416
entities.get("..").expect(".. entry").filetype,
417
FileType::Directory
418
);
419
assert_eq!(
420
entities.get("file1").expect("file1 entry").filetype,
421
FileType::RegularFile
422
);
423
}
424
425
fn run<F: std::future::Future>(future: F) -> F::Output {
426
use std::pin::Pin;
427
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
428
429
let mut f = Pin::from(Box::new(future));
430
let waker = dummy_waker();
431
let mut cx = Context::from_waker(&waker);
432
match f.as_mut().poll(&mut cx) {
433
Poll::Ready(val) => return val,
434
Poll::Pending => {
435
panic!(
436
"Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store"
437
)
438
}
439
}
440
441
fn dummy_waker() -> Waker {
442
return unsafe { Waker::from_raw(clone(5 as *const _)) };
443
444
unsafe fn clone(ptr: *const ()) -> RawWaker {
445
assert_eq!(ptr as usize, 5);
446
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
447
RawWaker::new(ptr, &VTABLE)
448
}
449
450
unsafe fn wake(ptr: *const ()) {
451
assert_eq!(ptr as usize, 5);
452
}
453
454
unsafe fn wake_by_ref(ptr: *const ()) {
455
assert_eq!(ptr as usize, 5);
456
}
457
458
unsafe fn drop(ptr: *const ()) {
459
assert_eq!(ptr as usize, 5);
460
}
461
}
462
}
463
}
464
465