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

wasi: allow WASI stdio to be configured #33544

Closed
wants to merge 1 commit into from
Closed
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
6 changes: 6 additions & 0 deletions doc/api/wasi.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ added:
process via the `__wasi_proc_exit()` function. Setting this option to `true`
causes `wasi.start()` to return the exit code rather than terminate the
process. **Default:** `false`.
* `stdin` {integer} The file descriptor used as standard input in the
WebAssembly application. **Default:** `0`.
* `stdout` {integer} The file descriptor used as standard output in the
WebAssembly application. **Default:** `1`.
* `stderr` {integer} The file descriptor used as standard error in the
WebAssembly application. **Default:** `2`.

### `wasi.start(instance)`
<!-- YAML
Expand Down
9 changes: 8 additions & 1 deletion lib/wasi.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const { isArrayBuffer } = require('internal/util/types');
const {
validateArray,
validateBoolean,
validateInt32,
validateObject,
} = require('internal/validators');
const { WASI: _WASI } = internalBinding('wasi');
Expand Down Expand Up @@ -51,7 +52,13 @@ class WASI {
}
}

const wrap = new _WASI(args, env, preopens);
const { stdin = 0, stdout = 1, stderr = 2 } = options;
validateInt32(stdin, 'options.stdin', 0);
validateInt32(stdout, 'options.stdout', 0);
validateInt32(stderr, 'options.stderr', 0);
const stdio = [stdin, stdout, stderr];

const wrap = new _WASI(args, env, preopens, stdio);

for (const prop in wrap) {
wrap[prop] = FunctionPrototypeBind(wrap[prop], wrap);
Expand Down
15 changes: 11 additions & 4 deletions src/node_wasi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -163,20 +163,27 @@ void WASI::DecreaseAllocatedSize(size_t size) {

void WASI::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
CHECK_EQ(args.Length(), 3);
CHECK_EQ(args.Length(), 4);
CHECK(args[0]->IsArray());
CHECK(args[1]->IsArray());
CHECK(args[2]->IsArray());
CHECK(args[3]->IsArray());

Environment* env = Environment::GetCurrent(args);
Local<Context> context = env->context();
Local<Array> argv = args[0].As<Array>();
const uint32_t argc = argv->Length();
uvwasi_options_t options;

options.in = 0;
options.out = 1;
options.err = 2;
Local<Array> stdio = args[3].As<Array>();
CHECK_EQ(stdio->Length(), 3);
options.in = stdio->Get(context, 0).ToLocalChecked()->
Int32Value(context).FromJust();
options.out = stdio->Get(context, 1).ToLocalChecked()->
Int32Value(context).FromJust();
options.err = stdio->Get(context, 2).ToLocalChecked()->
Int32Value(context).FromJust();

options.fd_table_size = 3;
options.argc = argc;
options.argv =
Expand Down
12 changes: 12 additions & 0 deletions test/wasi/test-wasi-options-validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ assert.throws(() => { new WASI({ preopens: 'fhqwhgads' }); },
assert.throws(() => { new WASI({ returnOnExit: 'fhqwhgads' }); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\breturnOnExit\b/ });

// If stdin is not an int32 and not undefined, it should throw.
assert.throws(() => { new WASI({ stdin: 'fhqwhgads' }); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bstdin\b/ });

// If stdout is not an int32 and not undefined, it should throw.
assert.throws(() => { new WASI({ stdout: 'fhqwhgads' }); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bstdout\b/ });

// If stderr is not an int32 and not undefined, it should throw.
assert.throws(() => { new WASI({ stderr: 'fhqwhgads' }); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bstderr\b/ });

// If options is provided, but not an object, the constructor should throw.
[null, 'foo', '', 0, NaN, Symbol(), true, false, () => {}].forEach((value) => {
assert.throws(() => { new WASI(value); },
Expand Down
34 changes: 34 additions & 0 deletions test/wasi/test-wasi-stdio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Flags: --experimental-wasi-unstable-preview1 --experimental-wasm-bigint
'use strict';
require('../common');
const tmpdir = require('../common/tmpdir');
const { strictEqual } = require('assert');
const { closeSync, openSync, readFileSync, writeFileSync } = require('fs');
const { join } = require('path');
const { WASI } = require('wasi');
const modulePath = join(__dirname, 'wasm', 'stdin.wasm');
const buffer = readFileSync(modulePath);
const stdinFile = join(tmpdir.path, 'stdin.txt');
const stdoutFile = join(tmpdir.path, 'stdout.txt');
const stderrFile = join(tmpdir.path, 'stderr.txt');

tmpdir.refresh();
// Write 33 x's. The test's buffer only holds 31 x's + a terminator.
writeFileSync(stdinFile, 'x'.repeat(33));

const stdin = openSync(stdinFile, 'r');
const stdout = openSync(stdoutFile, 'a');
const stderr = openSync(stderrFile, 'a');
const wasi = new WASI({ stdin, stdout, stderr, returnOnExit: true });
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };

(async () => {
const { instance } = await WebAssembly.instantiate(buffer, importObject);

strictEqual(wasi.start(instance), 0);
closeSync(stdin);
closeSync(stdout);
closeSync(stderr);
strictEqual(readFileSync(stdoutFile, 'utf8').trim(), 'x'.repeat(31));
strictEqual(readFileSync(stderrFile, 'utf8').trim(), '');
})();