diff --git a/src/spawn_sync.cc b/src/spawn_sync.cc index 47c4aaef348433..2cf8c7a9631345 100644 --- a/src/spawn_sync.cc +++ b/src/spawn_sync.cc @@ -25,6 +25,7 @@ #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" +#include "stream_wrap.h" #include "string_bytes.h" #include "util-inl.h" @@ -1063,12 +1064,26 @@ Maybe SyncProcessRunner::ParseStdioOption(int child_fd, return Nothing(); } return Just(AddStdioInheritFD(child_fd, inherit_fd)); - } + } else if (js_type->StrictEquals(env()->wrap_string())) { + Local val; + if (!js_stdio_option->Get(context, env()->handle_string()).ToLocal(&val)) { + return Nothing(); + } + if (!val->IsObject()) { + return Just(UV_EINVAL); + } + + Local handle = val.As(); + Local sw = env()->libuv_stream_wrap_ctor_template(); + if (sw.IsEmpty() || !sw->HasInstance(handle)) { + return Just(UV_EINVAL); + } - Utf8Value stdio_type(env()->isolate(), js_type); - fprintf(stderr, "invalid child stdio type: %s\n", stdio_type.out()); + uv_stream_t* stream = LibuvStreamWrap::From(env(), handle)->stream(); + return Just(AddStdioInheritStream(child_fd, stream)); + } - UNREACHABLE(); + return Just(UV_EINVAL); } int SyncProcessRunner::AddStdioIgnore(uint32_t child_fd) { @@ -1116,6 +1131,18 @@ int SyncProcessRunner::AddStdioInheritFD(uint32_t child_fd, int inherit_fd) { return 0; } + +int SyncProcessRunner::AddStdioInheritStream(uint32_t child_fd, + uv_stream_t* stream) { + CHECK_LT(child_fd, stdio_count_); + CHECK(!stdio_pipes_[child_fd]); + + uv_stdio_containers_[child_fd].flags = UV_INHERIT_STREAM; + uv_stdio_containers_[child_fd].data.stream = stream; + + return 0; +} + Maybe SyncProcessRunner::CopyJsString(Local js_value, const char** target) { Isolate* isolate = env()->isolate(); diff --git a/src/spawn_sync.h b/src/spawn_sync.h index 9c8b0c563c4c45..09242dd128572d 100644 --- a/src/spawn_sync.h +++ b/src/spawn_sync.h @@ -185,6 +185,7 @@ class SyncProcessRunner { bool writable, uv_buf_t input_buffer); inline int AddStdioInheritFD(uint32_t child_fd, int inherit_fd); + inline int AddStdioInheritStream(uint32_t child_fd, uv_stream_t* stream); static bool IsSet(v8::Local value); v8::Maybe CopyJsString(v8::Local js_value, diff --git a/test/parallel/test-child-process-spawnsync.js b/test/parallel/test-child-process-spawnsync.js index 9ec125ea891689..6e1753803583dc 100644 --- a/test/parallel/test-child-process-spawnsync.js +++ b/test/parallel/test-child-process-spawnsync.js @@ -65,3 +65,33 @@ assert.deepStrictEqual(ret_err.spawnargs, ['bar']); ]; assert.deepStrictEqual(retUTF8.output, stringifiedDefault); } + +{ + // Verify support for stdio.wrap via using another child process stream. + // This is the same logic as in ch_sy.js, expressed as a deterministic test. + if (common.isWindows) { + common.skip('Not applicable on Windows'); + } + + const { spawn } = require('child_process'); + const childA = spawn(process.execPath, + ['-e', 'process.stdin.pipe(process.stdout);'], + { stdio: ['pipe', 'pipe', 'ignore'] }); + + let collected = ''; + childA.stdout.setEncoding('utf8'); + childA.stdout.on('data', (chunk) => { collected += chunk; }); + + childA.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + assert.strictEqual(collected, 'hi'); + })); + + const result = spawnSync(process.execPath, + ['-e', 'process.stdout.write("hi")'], + { stdio: ['inherit', childA.stdin, 'inherit'] }); + + assert.strictEqual(result.status, 0); + // Explicitly close the wrapped stream on the parent side so childA receives EOF. + childA.stdin.end(); +}