Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions crates/test-programs/src/bin/p1_stat_extreme_host_mtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")]

use std::{env, process};
use test_programs::preview1::open_scratch_directory;

const FILENAME: &str = "extreme.dat";
const EXPECTED: &[u8] = b"hello";

unsafe fn test_stat_extreme_host_mtime(dir_fd: wasip1::Fd) {
let st = wasip1::path_filestat_get(dir_fd, 0, FILENAME).expect("path_filestat_get");
assert_eq!(st.size, EXPECTED.len() as u64, "size");
let _ = st.mtim;
let _ = st.atim;

let fd =
wasip1::path_open(dir_fd, 0, FILENAME, 0, wasip1::RIGHTS_FD_READ, 0, 0).expect("path_open");
let mut buf = [0u8; 16];
let nread = wasip1::fd_read(
fd,
&[wasip1::Iovec {
buf: buf.as_mut_ptr(),
buf_len: buf.len(),
}],
)
.expect("fd_read");
assert_eq!(&buf[..nread], EXPECTED, "contents");
wasip1::fd_close(fd).expect("fd_close");
}

fn main() {
let mut args = env::args();
let prog = args.next().unwrap();
let arg = if let Some(arg) = args.next() {
arg
} else {
eprintln!("usage: {prog} <scratch directory>");
process::exit(1);
};

let dir_fd = match open_scratch_directory(&arg) {
Ok(dir_fd) => dir_fd,
Err(err) => {
eprintln!("{err}");
process::exit(1);
}
};

unsafe { test_stat_extreme_host_mtime(dir_fd) }
}
71 changes: 63 additions & 8 deletions crates/wasi/src/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,18 +310,22 @@ impl DescriptorStat {
/// Creates a `DescriptorStat` from a `Metadata` plus the hard link
/// count.
fn new(meta: &Metadata, link_count: u64) -> Self {
fn datetime_from(t: std::time::SystemTime) -> Datetime {
// FIXME make this infallible or handle errors properly
Datetime::try_from(t).unwrap()
}

Self {
type_: meta.file_type().into(),
link_count,
size: meta.len(),
data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(),
data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(),
status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(),
data_access_timestamp: meta
.accessed()
.ok()
.and_then(|t| Datetime::try_from(t.into_std()).ok()),
data_modification_timestamp: meta
.modified()
.ok()
.and_then(|t| Datetime::try_from(t.into_std()).ok()),
status_change_timestamp: meta
.created()
.ok()
.and_then(|t| Datetime::try_from(t.into_std()).ok()),
}
}
}
Expand Down Expand Up @@ -1167,3 +1171,54 @@ impl WasiFilesystemCtxView<'_> {
Ok(results)
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::clocks::Datetime;
use std::io::{Read, Write};
use std::time::{Duration, SystemTime};

#[test]
fn datetime_try_from_extreme_system_time() {
let extreme = SystemTime::UNIX_EPOCH
.checked_sub(Duration::from_secs((i64::MAX as u64) + 1))
.unwrap();
assert!(Datetime::try_from(extreme).ok().is_none());
}

#[test]
fn host_prepared_extreme_mtime_stats_and_reads() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("extreme.dat");
std::fs::File::create(&path)
.unwrap()
.write_all(b"hello")
.unwrap();

let extreme = SystemTime::UNIX_EPOCH
.checked_sub(Duration::from_secs((i64::MAX as u64) + 1))
.unwrap();
let f = std::fs::OpenOptions::new().write(true).open(&path).unwrap();
let times = std::fs::FileTimes::new()
.set_modified(extreme)
.set_accessed(extreme);
// Platforms may reject or clamp extreme times.
let _ = f.set_times(times);
drop(f);

let f = std::fs::File::open(&path).unwrap();
let stat = sys::stat(&f).expect("stat");
let mut buf = Vec::new();
std::fs::File::open(&path)
.unwrap()
.read_to_end(&mut buf)
.unwrap();
assert_eq!(buf, b"hello");
let _ = (
stat.data_access_timestamp,
stat.data_modification_timestamp,
stat.status_change_timestamp,
);
}
}
10 changes: 6 additions & 4 deletions crates/wasi/src/p2/host/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,15 +590,17 @@ impl TryFrom<crate::filesystem::DescriptorStat> for types::DescriptorStat {
status_change_timestamp,
}: crate::filesystem::DescriptorStat,
) -> Result<Self, ErrorCode> {
// Internal timestamps use i64 seconds; wasi:clocks/wall-clock uses u64
// (non-negative). Times outside that range become missing rather than
// failing the whole stat (e.g. host-clamped far-past mtimes on macOS).
Ok(Self {
type_: type_.into(),
link_count,
size,
data_access_timestamp: data_access_timestamp.map(|t| t.try_into()).transpose()?,
data_access_timestamp: data_access_timestamp.and_then(|t| t.try_into().ok()),
data_modification_timestamp: data_modification_timestamp
.map(|t| t.try_into())
.transpose()?,
status_change_timestamp: status_change_timestamp.map(|t| t.try_into()).transpose()?,
.and_then(|t| t.try_into().ok()),
status_change_timestamp: status_change_timestamp.and_then(|t| t.try_into().ok()),
})
}
}
Expand Down
49 changes: 49 additions & 0 deletions crates/wasi/tests/all/p1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,29 @@ async fn run(path: &str, with_builder: impl FnOnce(&mut WasiCtxBuilder)) -> Resu
Ok(())
}

async fn run_with_workspace_setup(
path: &str,
setup: impl FnOnce(&Path) -> Result<()>,
with_builder: impl FnOnce(&mut WasiCtxBuilder),
) -> Result<()> {
let path = Path::new(path);
let name = path.file_stem().unwrap().to_str().unwrap();
let engine = test_programs_artifacts::engine(|_config| {});
let mut linker = Linker::<Ctx<WasiP1Ctx>>::new(&engine);
add_to_linker_async(&mut linker, |t| &mut t.wasi)?;

let module = Module::from_file(&engine, path)?;
let (mut store, _td) = Ctx::new_with_workspace_setup(&engine, name, setup, |builder| {
with_builder(builder);
builder.build_p1()
})?;
store.data_mut().wasi.ctx().table.set_max_capacity(1000);
let instance = linker.instantiate_async(&mut store, &module).await?;
let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
start.call_async(&mut store, ()).await?;
Ok(())
}

foreach_p1!(assert_test_exists);

// Below here is mechanical: there should be one test for every binary in
Expand Down Expand Up @@ -69,6 +92,32 @@ async fn p1_fd_filestat_get() {
async fn p1_fd_filestat_set() {
run(P1_FD_FILESTAT_SET, |_| {}).await.unwrap()
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn p1_stat_extreme_host_mtime() {
use std::fs::{File, FileTimes};
use std::io::Write;
use std::time::{Duration, SystemTime};

run_with_workspace_setup(
P1_STAT_EXTREME_HOST_MTIME,
|dir| {
let path = dir.join("extreme.dat");
File::create(&path)?.write_all(b"hello")?;
let extreme = SystemTime::UNIX_EPOCH
.checked_sub(Duration::from_secs((i64::MAX as u64) + 1))
.expect("construct extreme SystemTime");
let f = File::options().write(true).open(&path)?;
let times = FileTimes::new().set_modified(extreme).set_accessed(extreme);
// Platforms may reject or clamp extreme times.
let _ = f.set_times(times);
Ok(())
},
|_| {},
)
.await
.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn p1_fd_flags_set() {
run(P1_FD_FLAGS_SET, |_| {}).await.unwrap()
Expand Down
48 changes: 48 additions & 0 deletions crates/wasi/tests/all/p2/async_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,30 @@ async fn run(path: &str, with_builder: impl FnOnce(&mut WasiCtxBuilder)) -> Resu
.map_err(|()| wasmtime::format_err!("run returned a failure"))
}

async fn run_with_workspace_setup(
path: &str,
setup: impl FnOnce(&Path) -> Result<()>,
with_builder: impl FnOnce(&mut WasiCtxBuilder),
) -> Result<()> {
let path = Path::new(path);
let name = path.file_stem().unwrap().to_str().unwrap();
let engine = test_programs_artifacts::engine(|_config| {});
let mut linker = Linker::new(&engine);
add_to_linker_async(&mut linker)?;

let (mut store, _td) = Ctx::new_with_workspace_setup(&engine, name, setup, |builder| {
with_builder(builder);
MyWasiCtx::new(builder.build())
})?;
let component = Component::from_file(&engine, path)?;
let command = Command::instantiate_async(&mut store, &component, &linker).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| wasmtime::format_err!("run returned a failure"))
}

foreach_p1!(assert_test_exists);
foreach_p2!(assert_test_exists);

Expand Down Expand Up @@ -73,6 +97,30 @@ async fn p1_fd_filestat_set() {
run(P1_FD_FILESTAT_SET_COMPONENT, |_| {}).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn p1_stat_extreme_host_mtime() {
use std::fs::{File, FileTimes};
use std::io::Write;
use std::time::{Duration, SystemTime};

run_with_workspace_setup(
P1_STAT_EXTREME_HOST_MTIME_COMPONENT,
|dir| {
let path = dir.join("extreme.dat");
File::create(&path)?.write_all(b"hello")?;
let extreme = SystemTime::UNIX_EPOCH
.checked_sub(Duration::from_secs((i64::MAX as u64) + 1))
.expect("construct extreme SystemTime");
let f = File::options().write(true).open(&path)?;
let times = FileTimes::new().set_modified(extreme).set_accessed(extreme);
let _ = f.set_times(times);
Ok(())
},
|_| {},
)
.await
.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn p1_fd_flags_set() {
run(P1_FD_FLAGS_SET_COMPONENT, |_| {}).await.unwrap()
}
Expand Down
51 changes: 51 additions & 0 deletions crates/wasi/tests/all/p2/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,34 @@ fn run(path: &str, with_builder: impl Fn(&mut WasiCtxBuilder)) -> Result<()> {
Ok(())
}

fn run_with_workspace_setup(
path: &str,
setup: impl Fn(&Path) -> Result<()>,
with_builder: impl Fn(&mut WasiCtxBuilder),
) -> Result<()> {
let path = Path::new(path);
let name = path.file_stem().unwrap().to_str().unwrap();
let engine = test_programs_artifacts::engine(|_| {});
let mut linker = Linker::new(&engine);
add_to_linker_sync(&mut linker)?;

let component = Component::from_file(&engine, path)?;

for blocking in [false, true] {
let (mut store, _td) = Ctx::new_with_workspace_setup(&engine, name, &setup, |builder| {
with_builder(builder);
builder.allow_blocking_current_thread(blocking);
MyWasiCtx::new(builder.build())
})?;
let command = Command::instantiate(&mut store, &component, &linker)?;
command
.wasi_cli_run()
.call_run(&mut store)?
.map_err(|()| wasmtime::format_err!("run returned a failure"))?;
}
Ok(())
}

foreach_p1!(assert_test_exists);
foreach_p2!(assert_test_exists);

Expand Down Expand Up @@ -77,6 +105,29 @@ fn p1_fd_filestat_set() {
run(P1_FD_FILESTAT_SET_COMPONENT, |_| {}).unwrap()
}
#[test_log::test]
fn p1_stat_extreme_host_mtime() {
use std::fs::{File, FileTimes};
use std::io::Write;
use std::time::{Duration, SystemTime};

run_with_workspace_setup(
P1_STAT_EXTREME_HOST_MTIME_COMPONENT,
|dir| {
let path = dir.join("extreme.dat");
File::create(&path)?.write_all(b"hello")?;
let extreme = SystemTime::UNIX_EPOCH
.checked_sub(Duration::from_secs((i64::MAX as u64) + 1))
.expect("construct extreme SystemTime");
let f = File::options().write(true).open(&path)?;
let times = FileTimes::new().set_modified(extreme).set_accessed(extreme);
let _ = f.set_times(times);
Ok(())
},
|_| {},
)
.unwrap()
}
#[test_log::test]
fn p1_fd_flags_set() {
run(P1_FD_FLAGS_SET_COMPONENT, |_| {}).unwrap()
}
Expand Down
14 changes: 13 additions & 1 deletion crates/wasi/tests/all/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,23 @@ impl<T> Ctx<T> {
engine: &Engine,
name: &str,
configure: impl FnOnce(&mut WasiCtxBuilder) -> T,
) -> Result<(Store<Ctx<T>>, TempDir)> {
Self::new_with_workspace_setup(engine, name, |_| Ok(()), configure)
}

/// Like [`Self::new`], but allows seeding the preopened scratch directory
/// before the guest runs (for host-prepared filesystem fixtures).
pub fn new_with_workspace_setup(
engine: &Engine,
name: &str,
setup: impl FnOnce(&std::path::Path) -> Result<()>,
configure: impl FnOnce(&mut WasiCtxBuilder) -> T,
) -> Result<(Store<Ctx<T>>, TempDir)> {
const MAX_OUTPUT_SIZE: usize = 10 << 20;
let stdout = MemoryOutputPipe::new(MAX_OUTPUT_SIZE);
let stderr = MemoryOutputPipe::new(MAX_OUTPUT_SIZE);
let workspace = prepare_workspace(name)?;
setup(workspace.path())?;

// Create our wasi context.
let mut builder = WasiCtxBuilder::new();
Expand All @@ -54,7 +66,7 @@ impl<T> Ctx<T> {
stdout,
};

Ok((Store::new(&engine, ctx), workspace))
Ok((Store::new(engine, ctx), workspace))
}
}

Expand Down
Loading