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

[c-api] add more functions for stdin/stderr/stdout control #2334

Closed
Pangoraw opened this issue May 23, 2021 · 9 comments · Fixed by #3145
Closed

[c-api] add more functions for stdin/stderr/stdout control #2334

Pangoraw opened this issue May 23, 2021 · 9 comments · Fixed by #3145
Assignees
Labels
🎉 enhancement New feature! ⚙️ feature request 📦 lib-c-api About wasmer-c-api 🕵️ needs investigation The issue/PR needs further investigation priority-medium Medium priority issue
Milestone

Comments

@Pangoraw
Copy link
Contributor

Motivation

Currently the c-api propose a limited set of possibilities when it comes to WASI and redirection over stdin/stderr/stdout.
As I understand it, the wasi_config_t will always inherit stdin from the host program (see #2059).

Proposed solution

Since the wasi_config_t has access to a WasmStateBuilder, one could replace any of the program pipes with a valid WasiFile. This could be a HostFile or a Pipe. stderr and stdout can already be captured using the wasi_config_capture_{stdout,stderr} functions, but there is no way to replace the inherited stdin.

A function could be added to replace the stdin from the WasiStateBuilder with a Pipe, example:

#[no_mangle]
pub extern "C" fn wasi_overwrite_stdin(config: &mut wasi_config_t) {
let piped_stdin = Box::new(Pipe::new());
config.state_builder.stdin(piped_stdin);
}

A function could be added to then write to this stdin (with less unwraps):

#[no_mangle]
pub unsafe extern "C" fn wasi_env_write_stdin(
env: &mut wasi_env_t,
buffer: *const u8,
buffer_len: usize
) {
let mut state = env.inner.state();
let wasi_stdin = state.fs.stdin_mut().unwrap().as_mut().unwrap();
let buffer = slice::from_raw_parts(buffer, buffer_len);
let msg = std::str::from_utf8(buffer).unwrap();
write!(wasi_stdin, "{}", msg).unwrap();
}

This would allow the instantiation of the same module with different inputs. The code above is from this commit on my fork, I can open a PR if the proposed change is accepted.

Alternatives

Since any WasiFile is valid, one could also provide file-backed outputs and inputs, similar to wasmtime's wasi_config_set_stdin_file(wasi_config_t *config, const char *path)

Additional context

An example usage where I control the stdin input given to cowsay.wasm using a wrapper written in Julia:

image

@Pangoraw Pangoraw added the 🎉 enhancement New feature! label May 23, 2021
@Hywan Hywan self-assigned this May 27, 2021
@Hywan
Copy link
Contributor

Hywan commented May 27, 2021

I agree we must improve the API for WasiFile in C :-).

@syrusakbary syrusakbary added the priority-medium Medium priority issue label Oct 20, 2021
@heyjdp heyjdp added this to the v3.0 milestone Apr 27, 2022
@syrusakbary
Copy link
Member

This issue Needs API design doc before working on the PR

@syrusakbary syrusakbary added the 🕵️ needs investigation The issue/PR needs further investigation label Jun 1, 2022
@syrusakbary
Copy link
Member

syrusakbary commented Jun 6, 2022

This issue should be resolved on wasmer3/context_api branch (using the new context API).

Go to stdin/stdout tests to see how things are done and think how to expose in C

@silwol
Copy link
Contributor

silwol commented Jun 30, 2022

The proposed C API looks sane to me, it's the equivalent to what we already provide for stdout and stderr, just the reverse direction (write vs. read).

@epilys
Copy link
Contributor

epilys commented Jun 30, 2022

@silwol once #2973 is merged you can start a draft PR against the context_api branch in lib/c-api

@fschutt
Copy link
Contributor

fschutt commented Aug 2, 2022

This has been fixed by #3032

#include "wasmer.h"
#include "string.h"
#include "stdio.h"

typedef struct {
    int invocation;
} CustomWasiStdin;

long CustomWasiStdin_destructor(
    const void* env,
    __u_long sz,
    __u_long ao
) {
    (void)env;
    (void)sz;
    (void)ao;
    return 0;
}

long CustomWasiStdin_onStdIn(
    const void* env,
    __u_long sz,
    __u_long ao,
    __u_long maxwrite,
    wasi_console_stdin_response_t* in
) {
    CustomWasiStdin* ptr = (CustomWasiStdin*)env;
    (void)sz;
    (void)ao;
    (void)maxwrite;
    if (ptr->invocation == 0) {
        wasi_console_stdin_response_write_str(in, "hello");
        ptr->invocation += 1;
        return 5; // sizeof("hello")
    } else {
        return 0;
    }
}

int main() {

    wasm_engine_t* engine = wasm_engine_new();
    if (!engine) {
        printf("> Error loading engine!\n");
        return 1;
    }
    wasm_store_t* store = wasm_store_new(engine);
    if (!store) {
        printf("> Error loading store!\n");
        return 1;
    }
    wasi_config_t* config = wasi_config_new("example_program");
    if (!config) {
        printf("> Error loading config!\n");
        return 1;
    }
    wasi_console_out_t* override_stdout = wasi_console_out_new_memory();
    if (!override_stdout) {
        printf("> Error loading override_stdout!\n");
        return 1;
    }
    wasi_console_out_t* override_stderr = wasi_console_out_new_memory();
    if (!override_stderr) {
        printf("> Error loading override_stderr!\n");
        return 1;
    }
    CustomWasiStdin stdin = { .invocation = 0 };
    wasi_console_stdin_t* override_stdin = wasi_console_stdin_new(
        CustomWasiStdin_onStdIn,
        CustomWasiStdin_destructor,
        &stdin,
        sizeof(stdin),
        8 // alignof(stdin)
    );

    // Cloning the `wasi_console_out_t` does not deep-clone the
    // internal stream, since that is locked behind an Arc<Mutex<T>>.
    wasi_console_out_t* stdout_receiver = wasi_console_out_clone(override_stdout);
    wasi_console_out_t* stderr_receiver = wasi_console_out_clone(override_stderr);

    // The override_stdin ownership is moved to the config
    wasi_config_overwrite_stdin(config, override_stdin);
    wasi_config_overwrite_stdout(config, override_stdout);
    wasi_config_overwrite_stderr(config, override_stderr);

    // Load binary.
    FILE* file = fopen("stdio.wasm", "rb");
    if (!file) {
        printf("> Error loading module!\n");
        return 1;
    }

    fseek(file, 0L, SEEK_END);
    size_t file_size = ftell(file);
    fseek(file, 0L, SEEK_SET);

    wasm_byte_vec_t binary;
    wasm_byte_vec_new_uninitialized(&binary, file_size);

    if (fread(binary.data, file_size, 1, file) != 1) {
        printf("> Error loading module!\n");
        return 1;
    }

    fclose(file);

    wasm_module_t* module = wasm_module_new(store, &binary);
    if (!module) {
        printf("> Error compiling module!\n");
        return 1;
    }

    // The env now has ownership of the config (using the custom stdout / stdin channels)              
    wasi_env_t *wasi_env = wasi_env_new(store, config);
    if (!wasi_env) {
        printf("> Error building WASI env!\n");
        return 1;
    }

    wasm_importtype_vec_t import_types;
    wasm_module_imports(module, &import_types);

    wasm_extern_vec_t imports;
    wasm_extern_vec_new_uninitialized(&imports, import_types.size);
    wasm_importtype_vec_delete(&import_types);

    bool get_imports_result = wasi_get_imports(store, wasi_env, module, &imports);

    if (!get_imports_result) {
        printf("Error getting WASI imports!\n");              
        return 1;
    }

    // The program should wait for a stdin, then print "stdout: $1" to stdout
    // and "stderr: $1" to stderr and exit.

    // Instantiate the module
    wasm_instance_t *instance = wasm_instance_new(store, module, &imports, NULL);
    if (!instance) {
        printf("> Error instantiating module!\n");
        return -1;
    }

    // Read the exports.
    wasm_extern_vec_t exports;
    wasm_instance_exports(instance, &exports);
    wasm_memory_t* mem = NULL;
    for (size_t i = 0; i < exports.size; i++) {
        mem = wasm_extern_as_memory(exports.data[i]);
        if (mem) {
            break;
        }
    }

    if (!mem) {
        printf("Failed to create instance: Could not find memory in exports\n");
        return -1;
    }
    wasi_env_set_memory(wasi_env, mem);

    // Get the _start function
    wasm_func_t* run_func = wasi_get_start_function(instance);
    if (run_func == NULL) {
        printf("> Error accessing export!\n");
        return 1;
    }

    // Run the _start function
    // Running the program should trigger the stdin to write "hello" to the stdin
    wasm_val_vec_t args = WASM_EMPTY_VEC;
    wasm_val_vec_t res = WASM_EMPTY_VEC;
    if (wasm_func_call(run_func, &args, &res)) {
        printf("> Error calling function!\n");
        return 1;
    }

    // Verify that the stdout / stderr worked as expected
    char* out;
    wasi_console_out_read_str(stdout_receiver, &out);
    assert(strcmp(out, "stdout: hello") == 0);
    wasi_console_out_delete_str(out);

    char* out2;
    wasi_console_out_read_str(stdout_receiver, &out2);
    assert(strcmp(out2, "") == 0);
    wasi_console_out_delete_str(out2);
    
    char* out3;
    wasi_console_out_read_str(stderr_receiver, &out3);
    assert(strcmp(out3, "stderr: hello") == 0);
    wasi_console_out_delete_str(out3);

    char* out4;
    wasi_console_out_read_str(stderr_receiver, &out4);
    assert(strcmp(out4, "") == 0);
    wasi_console_out_delete_str(out4);

    wasi_console_out_delete(stdout_receiver);
    wasi_console_out_delete(stderr_receiver);

    wasm_byte_vec_delete(&binary);
    wasm_module_delete(module);
    wasm_func_delete(run_func);
    wasi_env_delete(wasi_env);
    wasm_store_delete(store);
    wasm_engine_delete(engine);

    return 0;
}

@syrusakbary
Copy link
Member

This is implemented/done in #3145

@Pangoraw
Copy link
Contributor Author

It looks like the added functions where later removed in #3344, can we reopen?

@fschutt
Copy link
Contributor

fschutt commented Dec 20, 2022

@Pangoraw we've tracked this in #3410 but the latest status of this is that it's part of the WASIX merge, so we don't need to reopen it for now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🎉 enhancement New feature! ⚙️ feature request 📦 lib-c-api About wasmer-c-api 🕵️ needs investigation The issue/PR needs further investigation priority-medium Medium priority issue
Projects
None yet
7 participants