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
3 changes: 3 additions & 0 deletions tests/rust/wasm32-wasip3/src/bin/filesystem-set-size.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dirs": ["fs-tests.dir"]
}
123 changes: 123 additions & 0 deletions tests/rust/wasm32-wasip3/src/bin/filesystem-set-size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use std::process;
extern crate wit_bindgen;

wit_bindgen::generate!({
inline: r"
package test:test;

world test {
include wasi:filesystem/[email protected];
include wasi:cli/[email protected];
}
",
additional_derives: [PartialEq, Eq, Hash, Clone],
// Work around https://github.com/bytecodealliance/wasm-tools/issues/2285.
features:["clocks-timezone"],
generate_all
});

use wasi::filesystem::types::Descriptor;
use wasi::filesystem::types::{DescriptorFlags, ErrorCode, OpenFlags, PathFlags};

async fn test_set_size(dir: &Descriptor) {
// set-size: async func(size: filesize) -> result<_, error-code>;
let open = |path: &str, oflags: OpenFlags, fdflags: DescriptorFlags| -> _ {
dir.open_at(PathFlags::empty(), path.to_string(), oflags, fdflags)
};
let open_r = |path: &str| -> _ { open(path, OpenFlags::empty(), DescriptorFlags::READ) };
let open_w = |path: &str| -> _ {
open(
path,
OpenFlags::empty(),
DescriptorFlags::READ | DescriptorFlags::WRITE,
)
};
let creat = |path: &str| -> _ {
open(
path,
OpenFlags::CREATE | OpenFlags::EXCLUSIVE,
DescriptorFlags::READ | DescriptorFlags::WRITE,
)
};
let trunc = |path: &str| -> _ {
open(
path,
OpenFlags::TRUNCATE,
DescriptorFlags::READ | DescriptorFlags::WRITE,
)
};
let rm = |path: &str| dir.unlink_file_at(path.to_string());

let c = creat("c.cleanup").await.unwrap();
assert_eq!(c.stat().await.unwrap().size, 0);
c.set_size(42).await.unwrap();
// Setting size is visible immediately.
assert_eq!(c.stat().await.unwrap().size, 42);

let c = open_w("c.cleanup").await.unwrap();
let r = open_r("c.cleanup").await.unwrap();
assert_eq!(c.stat().await.unwrap().size, 42);
assert_eq!(r.stat().await.unwrap().size, 42);
c.set_size(69).await.unwrap();
assert_eq!(c.stat().await.unwrap().size, 69);
assert_eq!(r.stat().await.unwrap().size, 69);

let c = trunc("c.cleanup").await.unwrap();
assert_eq!(c.stat().await.unwrap().size, 0);
assert_eq!(r.stat().await.unwrap().size, 0);

// https://github.com/WebAssembly/wasi-filesystem/issues/190
match r.set_size(100).await {
Ok(()) => {
panic!("set-size succeeded on read-only descriptor");
}
Err(ErrorCode::Invalid | ErrorCode::BadDescriptor) => {}
Err(err) => {
panic!("unexpected err: {}", err)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows Wasmtime:
Test filesystem-set-size failed
[exit_code] 0 == 3
STDOUT:

STDERR:

thread '' (1) panicked at src/bin/filesystem-set-size.rs:76:13:
unexpected err: access (error 0)
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace
Error: failed to run main module tests\rust\testsuite\wasm32-wasip3\filesystem-set-size.wasm

}
};

// https://github.com/WebAssembly/wasi-filesystem/issues/190
match c.set_size(u64::MAX).await {
Ok(()) => {
panic!("set-size(-1) succeeded");
}
Err(ErrorCode::Invalid | ErrorCode::FileTooLarge) => {}
Err(err) => {
panic!("unexpected err: {}", err)
}
};

rm("c.cleanup").await.unwrap();

// We still have `c` and `r` open, which refer to the file; on POSIX
// systems, the `c.cleanup` will have been removed from its dir,
// whereas on Windows that will happen when the last open descriptor
// (`c` and `r`) is closed. In any case we can still stat our
// descriptors, call `set-size` on it, and so on.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So.... this is not correct. I mean, in an ideal world there should be a single WASI semantics, but for some areas the cost to impose uniformity between Windows and POSIX is too high (#128 (comment)). Deletion of open files is probably one of these areas.

Apparently there is a FILE_DISPOSITION_POSIX_SEMANTICS thing that you can set on a file to turn on POSIX deletion semantics for that file. However it is new (Windows 10) and requires developer mode to be enabled (apparently), and so is probably not possible to impose.

There is also a FILE_SHARE_DELETE flag that one can pass when opening a file, which is weird (removal is enqueued for when all open descriptors close; any open descriptor without FILE_SHARE_DELETE causes unlink to fail, which can be the case for backup programs; apparently one can create a new file with the same name while the file is pending deletion; etc).

Or, we could just allow unlink to fail if there is an open descriptor.

What should we allow in WASI? :) @sunfishcode @alexcrichton @pchickey

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My recollection, from when we tried to figure this out in the p1 era, is that we couldn't do any better than allowing unlink to fail if there is an open descriptor.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that it's reasonable enough to require runtimes to use FILE_SHARE_DELETE, for example I think that's the default in Rust. For FILE_DISPOSITION_POSIX_SEMANTICS I would agree that if it's either very new or requires developer mode it would be unreasonable to require that.

In the abstract though I'd say it's best to allow either the Windows or the Unix behavior here to avoid causing too much trouble in runtimes, although it's of course a portabilty hazard for guests.

assert_eq!(c.stat().await.unwrap().size, 0);
c.set_size(42).await.unwrap();
assert_eq!(c.stat().await.unwrap().size, 42);
assert_eq!(r.stat().await.unwrap().size, 42);
}

struct Component;
export!(Component);
impl exports::wasi::cli::run::Guest for Component {
async fn run() -> Result<(), ()> {
match &wasi::filesystem::preopens::get_directories()[..] {
[(dir, dirname)] if dirname == "fs-tests.dir" => {
test_set_size(dir).await;
}
[..] => {
eprintln!("usage: run with one open dir named 'fs-tests.dir'");
process::exit(1)
}
};
Ok(())
}
}

fn main() {
unreachable!("main is a stub");
}
Loading