Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

failed to find a pre-opened file descriptor from WASM WASI module with standalone wastime #2902

Closed
proohit opened this issue May 12, 2021 · 9 comments
Labels
bug Incorrect behavior in the current implementation that needs fixing

Comments

@proohit
Copy link

proohit commented May 12, 2021

Test Case

smartcore_wasi_lib.wasm - https://easyupload.io/7l8u6z
test1.txt

Steps to Reproduce

Just run the wasm file in the same folder as test1.txt.
wasmtime run --dir=. smartcore_wasi_lib.wasm --invoke load_model test1.txt test2.txt

Expected Results

A new file inside ./ with the name test2.txt and the same contents as test1.txt.

Actual Results

$ wasmtime run --dir=. smartcore_wasi_lib.wasm --invoke load_model test1.txt test2.txt
error opening input test1.txt: failed to find a pre-opened file descriptor through which "test1.txt" could be opened

Versions and Environment

Wasmtime version or commit: 0.26.0

Operating system: Ubuntu 20.04

Architecture: x86_64

Extra Info

This is the example for WASI from https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-tutorial.md

Here's the code:

use std::fs;
use std::io::{Read, Write};
use std::env;

fn process(input_fname: &str, output_fname: &str) -> Result<(), String> {
    let mut input_file = fs::File::open(input_fname)
        .map_err(|err| format!("error opening input {}: {}", input_fname, err))?;
    let mut contents = Vec::new();
    input_file
        .read_to_end(&mut contents)
        .map_err(|err| format!("read error: {}", err))?;

    let mut output_file = fs::File::create(output_fname)
        .map_err(|err| format!("error opening output {}: {}", output_fname, err))?;
    output_file
        .write_all(&contents)
        .map_err(|err| format!("write error: {}", err))
}

#[no_mangle]
pub fn load_model() -> () {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    if args.len() < 3 {
        eprintln!("usage: {} <input_file> <output_file>", program);
        return;
    }

    if let Err(err) = process(&args[1], &args[2]) {
        eprintln!("{}", err)
    }
}

And compiled via cargo build --target=wasm32-wasi --release

@proohit proohit added the bug Incorrect behavior in the current implementation that needs fixing label May 12, 2021
@bjorn3
Copy link
Contributor

bjorn3 commented May 12, 2021

Does it work if you specify ./test1.txt and ./test2.txt?

@proohit
Copy link
Author

proohit commented May 12, 2021

Unfortunately not.. Same error:

$ wasmtime run --dir=. smartcore_wasi_lib.wasm --invoke load_model ./test1.txt ./test2.txt
error opening input ./test1.txt: failed to find a pre-opened file descriptor through which "./test1.txt" could be opened

@bjorn3
Copy link
Contributor

bjorn3 commented May 12, 2021

Oh, I think I now what is going on. By using load_model as entry point, you are skipping wasi-libc's entry point which initializes libpreopen. As it isn't initialized, libpreopen doesn't know how to open files.

@proohit
Copy link
Author

proohit commented May 12, 2021

Hmm, this project was meant for another use-case, where it is used as a lib. I was hoping to quickly test it with wasmtime. Is this on purpose that invoked functions skip other necessary entry points?

@bjorn3
Copy link
Contributor

bjorn3 commented May 12, 2021

Wasi has two execution models:

  • command: This is like an executable. There is a single entry point and after the entry point returns you must discard the whole instance. This entry point is the _start function declared in wasi-libc which in turn calls the user main.
  • reactor: This is like a library. There is first a call to _initialize declared in wasi-libc. After that it is possible to call any user defined function.

To compile a reactor with rustc you need to use the unstable -Z wasi-exec-model=reactor flag. I am not sure if wasmtime cli supports reactors, but the api does, though it may be necessary to manually call _initialize. (not sure if wasmtime already automatically does this for your)

WebAssembly/WASI#13

rust-lang/rust#79997

@proohit
Copy link
Author

proohit commented May 12, 2021

Thanks for your valuable input! That was new to me and actually solved the problem!

Compiling the project with cargo rustc --release --target wasm32-wasi -- -Z wasi-exec-model=reactor did the trick. Now wasmtime run --dir=. smartcore_wasi_lib.wasm --invoke load_model test1.txt test2.txt results in test2.txt.

@proohit proohit closed this as completed May 12, 2021
@Timmmm
Copy link

Timmmm commented Sep 20, 2024

I'm also getting a very similar error. I'm building a very simple Rust WASI binary not in reactor mode, that tries to opens a file.

I start it in command mode via Node - see code below.

I get the same failed to find a pre-opened file descriptor error, but I don't want to switch to "reactor" (why didn't they call this "library"?) mode. I don't understand why this still happens though because surely I am using the _start entry point which should initialise this libpreload library?

(Sorry to slightly hijack this issue, but there's not much on the internet about this.)

import { WASI } from "node:wasi";
import { readFile } from "node:fs/promises";
import { join } from "node:path";

async function main() {
	const wasm = await WebAssembly.compile(
		await readFile(join(__dirname, "server.wasm")),
	);
	const wasi = new WASI({
		env: {
			RUST_BACKTRACE: "1",
		},
		// This option is mandatory.
		version: "preview1",
	});
	const instance = await WebAssembly.instantiate(wasm, <WebAssembly.Imports>wasi.getImportObject());

	wasi.start(instance);
}

main();

@Timmmm
Copy link

Timmmm commented Sep 20, 2024

Oh wait... you just need to do this:

	const wasi = new WASI({
		env: {
			RUST_BACKTRACE: "1",
		},
		// This option is mandatory.
		version: "preview1",
		preopens: {
			"/": "/",
		},
	});

Sorry for the noise!

@Timmmm
Copy link

Timmmm commented Sep 20, 2024

For Windows you need this nonsense:

			"/": "/",
			"a:/": "a:/",
			"b:/": "b:/",
			"c:/": "c:/",
			"d:/": "d:/",
...

(Or I guess narrow it down to a specific directory if your application allows that.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Incorrect behavior in the current implementation that needs fixing
Projects
None yet
Development

No branches or pull requests

3 participants