* Allow WASI to open directories without O_DIRECTORY The `O_DIRECTORY` flag is a request that open should fail if the named path is not a directory. Opening a path which turns out to be a directory is not supposed to fail if this flag is not specified. However, wasi-common required callers to use it when opening directories. With this PR, we always open the path the same way whether or not the `O_DIRECTORY` flag is specified. However, after opening it, we `stat` it to check whether it turned out to be a directory, and determine which operations the file descriptor should support accordingly. In addition, we explicitly check whether the precondition defined by `O_DIRECTORY` is satisfied. Closes #4947 and closes #4967, which were earlier attempts at fixing the same issue, but which had race conditions. prtest:full * Add tests from #4967/#4947 This test was authored by Roman Volosatovs <rvolosatovs@riseup.net> as part of #4947. * Tests: Close FDs before trying to unlink files On Windows, when opening a path which might be a directory using `CreateFile`, cap-primitives also removes the `FILE_SHARE_DELETE` mode. That means that if we implement WASI's `path_open` such that it always uses `CreateFile` on Windows, for both files and directories, then holding an open file handle prevents deletion of that file. So I'm changing these test programs to make sure they've closed the handle before trying to delete the file.
177 lines
5.3 KiB
Rust
177 lines
5.3 KiB
Rust
use anyhow::{Context, Error};
|
|
use cap_std::time::Duration;
|
|
use std::collections::HashMap;
|
|
use wasi_common::{
|
|
dir::OpenResult,
|
|
file::{FdFlags, OFlags},
|
|
sched::{Poll, RwEventFlags, SubscriptionResult, Userdata},
|
|
WasiDir, WasiFile,
|
|
};
|
|
use wasi_tokio::{clocks_ctx, sched::poll_oneoff, Dir};
|
|
|
|
const TIMEOUT: Duration = Duration::from_millis(200); // Required for slow execution in CI
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
async fn empty_file_readable() -> Result<(), Error> {
|
|
let clocks = clocks_ctx();
|
|
|
|
let workspace =
|
|
cap_tempfile::tempdir(cap_tempfile::ambient_authority()).expect("create tempdir");
|
|
workspace.create_dir("d").context("create dir")?;
|
|
let d = workspace.open_dir("d").context("open dir")?;
|
|
let d = Dir::from_cap_std(d);
|
|
|
|
let f = d
|
|
.open_file(false, "f", OFlags::CREATE, false, true, FdFlags::empty())
|
|
.await
|
|
.context("create writable file f")?;
|
|
let to_write: Vec<u8> = vec![0];
|
|
if let OpenResult::File(ref f) = f {
|
|
f.write_vectored(&vec![std::io::IoSlice::new(&to_write)])
|
|
.await
|
|
.context("write to f")?;
|
|
} else {
|
|
unreachable!();
|
|
}
|
|
drop(f);
|
|
|
|
let f = d
|
|
.open_file(false, "f", OFlags::empty(), true, false, FdFlags::empty())
|
|
.await
|
|
.context("open f as readable")?;
|
|
|
|
let mut poll = Poll::new();
|
|
if let OpenResult::File(ref f) = f {
|
|
poll.subscribe_read(f.as_ref(), Userdata::from(123));
|
|
} else {
|
|
unreachable!();
|
|
}
|
|
// Timeout bounds time in poll_oneoff
|
|
let monotonic = &*clocks.monotonic()?.abs_clock;
|
|
poll.subscribe_monotonic_clock(
|
|
monotonic,
|
|
monotonic
|
|
.now(monotonic.resolution())
|
|
.checked_add(TIMEOUT)
|
|
.unwrap(),
|
|
monotonic.resolution(),
|
|
Userdata::from(0),
|
|
);
|
|
poll_oneoff(&mut poll).await?;
|
|
|
|
let events = poll.results();
|
|
|
|
match events.get(0).expect("at least one event") {
|
|
(SubscriptionResult::Read(Ok((1, flags))), ud) => {
|
|
assert_eq!(*flags, RwEventFlags::empty());
|
|
assert_eq!(*ud, Userdata::from(123));
|
|
}
|
|
_ => panic!("expected (Read(Ok(1, empty), 123), got: {:?}", events[0]),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
async fn empty_file_writable() -> Result<(), Error> {
|
|
let clocks = clocks_ctx();
|
|
|
|
let workspace =
|
|
cap_tempfile::tempdir(cap_tempfile::ambient_authority()).expect("create tempdir");
|
|
workspace.create_dir("d").context("create dir")?;
|
|
let d = workspace.open_dir("d").context("open dir")?;
|
|
let d = Dir::from_cap_std(d);
|
|
|
|
let writable_f = d
|
|
.open_file(false, "f", OFlags::CREATE, true, true, FdFlags::empty())
|
|
.await
|
|
.context("create writable file")?;
|
|
|
|
let mut poll = Poll::new();
|
|
if let OpenResult::File(ref writable_f) = writable_f {
|
|
poll.subscribe_write(writable_f.as_ref(), Userdata::from(123));
|
|
} else {
|
|
unreachable!();
|
|
}
|
|
// Timeout bounds time in poll_oneoff
|
|
let monotonic = &*clocks.monotonic()?.abs_clock;
|
|
poll.subscribe_monotonic_clock(
|
|
monotonic,
|
|
monotonic
|
|
.now(monotonic.resolution())
|
|
.checked_add(TIMEOUT)
|
|
.unwrap(),
|
|
monotonic.resolution(),
|
|
Userdata::from(0),
|
|
);
|
|
poll_oneoff(&mut poll).await?;
|
|
|
|
let events = poll.results();
|
|
|
|
match events.get(0).expect("at least one event") {
|
|
(SubscriptionResult::Write(Ok((0, flags))), ud) => {
|
|
assert_eq!(*flags, RwEventFlags::empty());
|
|
assert_eq!(*ud, Userdata::from(123));
|
|
}
|
|
_ => panic!(""),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
async fn stdio_readable() -> Result<(), Error> {
|
|
let clocks = clocks_ctx();
|
|
|
|
let monotonic = &*clocks.monotonic()?.abs_clock;
|
|
let deadline = monotonic
|
|
.now(monotonic.resolution())
|
|
.checked_add(TIMEOUT)
|
|
.unwrap();
|
|
|
|
let mut waiting_on: HashMap<u64, Box<dyn WasiFile>> = vec![
|
|
(
|
|
1,
|
|
Box::new(wasi_tokio::stdio::stdout()) as Box<dyn WasiFile>,
|
|
),
|
|
(2, Box::new(wasi_tokio::stdio::stderr())),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
|
|
while !waiting_on.is_empty() {
|
|
let mut poll = Poll::new();
|
|
|
|
for (ix, file) in waiting_on.iter_mut() {
|
|
poll.subscribe_write(&mut **file, Userdata::from(*ix));
|
|
}
|
|
// Timeout bounds time in poll_oneoff
|
|
let monotonic = &*clocks.monotonic()?.abs_clock;
|
|
poll.subscribe_monotonic_clock(
|
|
monotonic,
|
|
deadline,
|
|
monotonic.resolution(),
|
|
Userdata::from(999),
|
|
);
|
|
poll_oneoff(&mut poll).await?;
|
|
let events = poll.results();
|
|
|
|
for e in events {
|
|
match e {
|
|
(SubscriptionResult::Write(Ok(_)), ud) => {
|
|
let _ = waiting_on.remove(&u64::from(ud));
|
|
}
|
|
(SubscriptionResult::Write(Err(_)), ud) => {
|
|
panic!("error on ix {}", u64::from(ud))
|
|
}
|
|
(SubscriptionResult::Read { .. }, _) => unreachable!(),
|
|
(SubscriptionResult::MonotonicClock { .. }, _) => {
|
|
panic!("timed out before stdin and stdout ready for reading")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|