Skip to content

Commit af14c1f

Browse files
cjihrigcodebytere
authored andcommitted
wasi: allow WASI stdio to be configured
This commit adds stdin, stderr, and stdout options to WASI, which allow the stdio streams to be configured. PR-URL: #33544 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: David Carlier <[email protected]> Reviewed-By: Juan José Arboleda <[email protected]> Reviewed-By: Zeyu Yang <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 6d25b57 commit af14c1f

File tree

5 files changed

+71
-5
lines changed

5 files changed

+71
-5
lines changed

doc/api/wasi.md

+6
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ added:
6666
process via the `__wasi_proc_exit()` function. Setting this option to `true`
6767
causes `wasi.start()` to return the exit code rather than terminate the
6868
process. **Default:** `false`.
69+
* `stdin` {integer} The file descriptor used as standard input in the
70+
WebAssembly application. **Default:** `0`.
71+
* `stdout` {integer} The file descriptor used as standard output in the
72+
WebAssembly application. **Default:** `1`.
73+
* `stderr` {integer} The file descriptor used as standard error in the
74+
WebAssembly application. **Default:** `2`.
6975

7076
### `wasi.start(instance)`
7177
<!-- YAML

lib/wasi.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const { isArrayBuffer } = require('internal/util/types');
1616
const {
1717
validateArray,
1818
validateBoolean,
19+
validateInt32,
1920
validateObject,
2021
} = require('internal/validators');
2122
const { WASI: _WASI } = internalBinding('wasi');
@@ -51,7 +52,13 @@ class WASI {
5152
}
5253
}
5354

54-
const wrap = new _WASI(args, env, preopens);
55+
const { stdin = 0, stdout = 1, stderr = 2 } = options;
56+
validateInt32(stdin, 'options.stdin', 0);
57+
validateInt32(stdout, 'options.stdout', 0);
58+
validateInt32(stderr, 'options.stderr', 0);
59+
const stdio = [stdin, stdout, stderr];
60+
61+
const wrap = new _WASI(args, env, preopens, stdio);
5562

5663
for (const prop in wrap) {
5764
wrap[prop] = FunctionPrototypeBind(wrap[prop], wrap);

src/node_wasi.cc

+11-4
Original file line numberDiff line numberDiff line change
@@ -163,20 +163,27 @@ void WASI::DecreaseAllocatedSize(size_t size) {
163163

164164
void WASI::New(const FunctionCallbackInfo<Value>& args) {
165165
CHECK(args.IsConstructCall());
166-
CHECK_EQ(args.Length(), 3);
166+
CHECK_EQ(args.Length(), 4);
167167
CHECK(args[0]->IsArray());
168168
CHECK(args[1]->IsArray());
169169
CHECK(args[2]->IsArray());
170+
CHECK(args[3]->IsArray());
170171

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

177-
options.in = 0;
178-
options.out = 1;
179-
options.err = 2;
178+
Local<Array> stdio = args[3].As<Array>();
179+
CHECK_EQ(stdio->Length(), 3);
180+
options.in = stdio->Get(context, 0).ToLocalChecked()->
181+
Int32Value(context).FromJust();
182+
options.out = stdio->Get(context, 1).ToLocalChecked()->
183+
Int32Value(context).FromJust();
184+
options.err = stdio->Get(context, 2).ToLocalChecked()->
185+
Int32Value(context).FromJust();
186+
180187
options.fd_table_size = 3;
181188
options.argc = argc;
182189
options.argv =

test/wasi/test-wasi-options-validation.js

+12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ assert.throws(() => { new WASI({ preopens: 'fhqwhgads' }); },
2525
assert.throws(() => { new WASI({ returnOnExit: 'fhqwhgads' }); },
2626
{ code: 'ERR_INVALID_ARG_TYPE', message: /\breturnOnExit\b/ });
2727

28+
// If stdin is not an int32 and not undefined, it should throw.
29+
assert.throws(() => { new WASI({ stdin: 'fhqwhgads' }); },
30+
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bstdin\b/ });
31+
32+
// If stdout is not an int32 and not undefined, it should throw.
33+
assert.throws(() => { new WASI({ stdout: 'fhqwhgads' }); },
34+
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bstdout\b/ });
35+
36+
// If stderr is not an int32 and not undefined, it should throw.
37+
assert.throws(() => { new WASI({ stderr: 'fhqwhgads' }); },
38+
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bstderr\b/ });
39+
2840
// If options is provided, but not an object, the constructor should throw.
2941
[null, 'foo', '', 0, NaN, Symbol(), true, false, () => {}].forEach((value) => {
3042
assert.throws(() => { new WASI(value); },

test/wasi/test-wasi-stdio.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Flags: --experimental-wasi-unstable-preview1 --experimental-wasm-bigint
2+
'use strict';
3+
require('../common');
4+
const tmpdir = require('../common/tmpdir');
5+
const { strictEqual } = require('assert');
6+
const { closeSync, openSync, readFileSync, writeFileSync } = require('fs');
7+
const { join } = require('path');
8+
const { WASI } = require('wasi');
9+
const modulePath = join(__dirname, 'wasm', 'stdin.wasm');
10+
const buffer = readFileSync(modulePath);
11+
const stdinFile = join(tmpdir.path, 'stdin.txt');
12+
const stdoutFile = join(tmpdir.path, 'stdout.txt');
13+
const stderrFile = join(tmpdir.path, 'stderr.txt');
14+
15+
tmpdir.refresh();
16+
// Write 33 x's. The test's buffer only holds 31 x's + a terminator.
17+
writeFileSync(stdinFile, 'x'.repeat(33));
18+
19+
const stdin = openSync(stdinFile, 'r');
20+
const stdout = openSync(stdoutFile, 'a');
21+
const stderr = openSync(stderrFile, 'a');
22+
const wasi = new WASI({ stdin, stdout, stderr, returnOnExit: true });
23+
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
24+
25+
(async () => {
26+
const { instance } = await WebAssembly.instantiate(buffer, importObject);
27+
28+
strictEqual(wasi.start(instance), 0);
29+
closeSync(stdin);
30+
closeSync(stdout);
31+
closeSync(stderr);
32+
strictEqual(readFileSync(stdoutFile, 'utf8').trim(), 'x'.repeat(31));
33+
strictEqual(readFileSync(stderrFile, 'utf8').trim(), '');
34+
})();

0 commit comments

Comments
 (0)