mirror of
https://github.com/nodejs/node.git
synced 2024-12-01 16:10:02 +01:00
b6bfc19378
Its intended that *-inl.h header files are only included into the src files that call the inline methods. Explicitly include it into the files that need it. PR-URL: https://github.com/nodejs/node/pull/27631 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1100 lines
30 KiB
C++
1100 lines
30 KiB
C++
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
#include "spawn_sync.h"
|
|
#include "debug_utils.h"
|
|
#include "env-inl.h"
|
|
#include "node_internals.h"
|
|
#include "string_bytes.h"
|
|
#include "util-inl.h"
|
|
|
|
#include <cstring>
|
|
|
|
|
|
namespace node {
|
|
|
|
using v8::Array;
|
|
using v8::Context;
|
|
using v8::EscapableHandleScope;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::HandleScope;
|
|
using v8::Int32;
|
|
using v8::Integer;
|
|
using v8::Isolate;
|
|
using v8::Just;
|
|
using v8::Local;
|
|
using v8::Maybe;
|
|
using v8::MaybeLocal;
|
|
using v8::Nothing;
|
|
using v8::Null;
|
|
using v8::Number;
|
|
using v8::Object;
|
|
using v8::String;
|
|
using v8::Value;
|
|
|
|
void SyncProcessOutputBuffer::OnAlloc(size_t suggested_size,
|
|
uv_buf_t* buf) const {
|
|
if (used() == kBufferSize)
|
|
*buf = uv_buf_init(nullptr, 0);
|
|
else
|
|
*buf = uv_buf_init(data_ + used(), available());
|
|
}
|
|
|
|
|
|
void SyncProcessOutputBuffer::OnRead(const uv_buf_t* buf, size_t nread) {
|
|
// If we hand out the same chunk twice, this should catch it.
|
|
CHECK_EQ(buf->base, data_ + used());
|
|
used_ += static_cast<unsigned int>(nread);
|
|
}
|
|
|
|
|
|
size_t SyncProcessOutputBuffer::Copy(char* dest) const {
|
|
memcpy(dest, data_, used());
|
|
return used();
|
|
}
|
|
|
|
|
|
unsigned int SyncProcessOutputBuffer::available() const {
|
|
return sizeof data_ - used();
|
|
}
|
|
|
|
|
|
unsigned int SyncProcessOutputBuffer::used() const {
|
|
return used_;
|
|
}
|
|
|
|
|
|
SyncProcessOutputBuffer* SyncProcessOutputBuffer::next() const {
|
|
return next_;
|
|
}
|
|
|
|
|
|
void SyncProcessOutputBuffer::set_next(SyncProcessOutputBuffer* next) {
|
|
next_ = next;
|
|
}
|
|
|
|
|
|
SyncProcessStdioPipe::SyncProcessStdioPipe(SyncProcessRunner* process_handler,
|
|
bool readable,
|
|
bool writable,
|
|
uv_buf_t input_buffer)
|
|
: process_handler_(process_handler),
|
|
readable_(readable),
|
|
writable_(writable),
|
|
input_buffer_(input_buffer),
|
|
|
|
first_output_buffer_(nullptr),
|
|
last_output_buffer_(nullptr),
|
|
|
|
uv_pipe_(),
|
|
write_req_(),
|
|
shutdown_req_(),
|
|
|
|
lifecycle_(kUninitialized) {
|
|
CHECK(readable || writable);
|
|
}
|
|
|
|
|
|
SyncProcessStdioPipe::~SyncProcessStdioPipe() {
|
|
CHECK(lifecycle_ == kUninitialized || lifecycle_ == kClosed);
|
|
|
|
SyncProcessOutputBuffer* buf;
|
|
SyncProcessOutputBuffer* next;
|
|
|
|
for (buf = first_output_buffer_; buf != nullptr; buf = next) {
|
|
next = buf->next();
|
|
delete buf;
|
|
}
|
|
}
|
|
|
|
|
|
int SyncProcessStdioPipe::Initialize(uv_loop_t* loop) {
|
|
CHECK_EQ(lifecycle_, kUninitialized);
|
|
|
|
int r = uv_pipe_init(loop, uv_pipe(), 0);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
uv_pipe()->data = this;
|
|
|
|
lifecycle_ = kInitialized;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int SyncProcessStdioPipe::Start() {
|
|
CHECK_EQ(lifecycle_, kInitialized);
|
|
|
|
// Set the busy flag already. If this function fails no recovery is
|
|
// possible.
|
|
lifecycle_ = kStarted;
|
|
|
|
if (readable()) {
|
|
if (input_buffer_.len > 0) {
|
|
CHECK_NOT_NULL(input_buffer_.base);
|
|
|
|
int r = uv_write(&write_req_,
|
|
uv_stream(),
|
|
&input_buffer_,
|
|
1,
|
|
WriteCallback);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
int r = uv_shutdown(&shutdown_req_, uv_stream(), ShutdownCallback);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
if (writable()) {
|
|
int r = uv_read_start(uv_stream(), AllocCallback, ReadCallback);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::Close() {
|
|
CHECK(lifecycle_ == kInitialized || lifecycle_ == kStarted);
|
|
|
|
uv_close(uv_handle(), CloseCallback);
|
|
|
|
lifecycle_ = kClosing;
|
|
}
|
|
|
|
|
|
Local<Object> SyncProcessStdioPipe::GetOutputAsBuffer(Environment* env) const {
|
|
size_t length = OutputLength();
|
|
Local<Object> js_buffer = Buffer::New(env, length).ToLocalChecked();
|
|
CopyOutput(Buffer::Data(js_buffer));
|
|
return js_buffer;
|
|
}
|
|
|
|
|
|
bool SyncProcessStdioPipe::readable() const {
|
|
return readable_;
|
|
}
|
|
|
|
|
|
bool SyncProcessStdioPipe::writable() const {
|
|
return writable_;
|
|
}
|
|
|
|
|
|
uv_stdio_flags SyncProcessStdioPipe::uv_flags() const {
|
|
unsigned int flags;
|
|
|
|
flags = UV_CREATE_PIPE;
|
|
if (readable())
|
|
flags |= UV_READABLE_PIPE;
|
|
if (writable())
|
|
flags |= UV_WRITABLE_PIPE;
|
|
|
|
return static_cast<uv_stdio_flags>(flags);
|
|
}
|
|
|
|
|
|
uv_pipe_t* SyncProcessStdioPipe::uv_pipe() const {
|
|
CHECK_LT(lifecycle_, kClosing);
|
|
return &uv_pipe_;
|
|
}
|
|
|
|
|
|
uv_stream_t* SyncProcessStdioPipe::uv_stream() const {
|
|
return reinterpret_cast<uv_stream_t*>(uv_pipe());
|
|
}
|
|
|
|
|
|
uv_handle_t* SyncProcessStdioPipe::uv_handle() const {
|
|
return reinterpret_cast<uv_handle_t*>(uv_pipe());
|
|
}
|
|
|
|
|
|
size_t SyncProcessStdioPipe::OutputLength() const {
|
|
SyncProcessOutputBuffer* buf;
|
|
size_t size = 0;
|
|
|
|
for (buf = first_output_buffer_; buf != nullptr; buf = buf->next())
|
|
size += buf->used();
|
|
|
|
return size;
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::CopyOutput(char* dest) const {
|
|
SyncProcessOutputBuffer* buf;
|
|
size_t offset = 0;
|
|
|
|
for (buf = first_output_buffer_; buf != nullptr; buf = buf->next())
|
|
offset += buf->Copy(dest + offset);
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::OnAlloc(size_t suggested_size, uv_buf_t* buf) {
|
|
// This function assumes that libuv will never allocate two buffers for the
|
|
// same stream at the same time. There's an assert in
|
|
// SyncProcessOutputBuffer::OnRead that would fail if this assumption was
|
|
// ever violated.
|
|
|
|
if (last_output_buffer_ == nullptr) {
|
|
// Allocate the first capture buffer.
|
|
first_output_buffer_ = new SyncProcessOutputBuffer();
|
|
last_output_buffer_ = first_output_buffer_;
|
|
|
|
} else if (last_output_buffer_->available() == 0) {
|
|
// The current capture buffer is full so get us a new one.
|
|
SyncProcessOutputBuffer* buf = new SyncProcessOutputBuffer();
|
|
last_output_buffer_->set_next(buf);
|
|
last_output_buffer_ = buf;
|
|
}
|
|
|
|
last_output_buffer_->OnAlloc(suggested_size, buf);
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::OnRead(const uv_buf_t* buf, ssize_t nread) {
|
|
if (nread == UV_EOF) {
|
|
// Libuv implicitly stops reading on EOF.
|
|
|
|
} else if (nread < 0) {
|
|
SetError(static_cast<int>(nread));
|
|
// At some point libuv should really implicitly stop reading on error.
|
|
uv_read_stop(uv_stream());
|
|
|
|
} else {
|
|
last_output_buffer_->OnRead(buf, nread);
|
|
process_handler_->IncrementBufferSizeAndCheckOverflow(nread);
|
|
}
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::OnWriteDone(int result) {
|
|
if (result < 0)
|
|
SetError(result);
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::OnShutdownDone(int result) {
|
|
if (result < 0)
|
|
SetError(result);
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::OnClose() {
|
|
lifecycle_ = kClosed;
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::SetError(int error) {
|
|
CHECK_NE(error, 0);
|
|
process_handler_->SetPipeError(error);
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::AllocCallback(uv_handle_t* handle,
|
|
size_t suggested_size,
|
|
uv_buf_t* buf) {
|
|
SyncProcessStdioPipe* self =
|
|
reinterpret_cast<SyncProcessStdioPipe*>(handle->data);
|
|
self->OnAlloc(suggested_size, buf);
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::ReadCallback(uv_stream_t* stream,
|
|
ssize_t nread,
|
|
const uv_buf_t* buf) {
|
|
SyncProcessStdioPipe* self =
|
|
reinterpret_cast<SyncProcessStdioPipe*>(stream->data);
|
|
self->OnRead(buf, nread);
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::WriteCallback(uv_write_t* req, int result) {
|
|
SyncProcessStdioPipe* self =
|
|
reinterpret_cast<SyncProcessStdioPipe*>(req->handle->data);
|
|
self->OnWriteDone(result);
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::ShutdownCallback(uv_shutdown_t* req, int result) {
|
|
SyncProcessStdioPipe* self =
|
|
reinterpret_cast<SyncProcessStdioPipe*>(req->handle->data);
|
|
|
|
// On AIX, OS X and the BSDs, calling shutdown() on one end of a pipe
|
|
// when the other end has closed the connection fails with ENOTCONN.
|
|
// Libuv is not the right place to handle that because it can't tell
|
|
// if the error is genuine but we here can.
|
|
if (result == UV_ENOTCONN)
|
|
result = 0;
|
|
|
|
self->OnShutdownDone(result);
|
|
}
|
|
|
|
|
|
void SyncProcessStdioPipe::CloseCallback(uv_handle_t* handle) {
|
|
SyncProcessStdioPipe* self =
|
|
reinterpret_cast<SyncProcessStdioPipe*>(handle->data);
|
|
self->OnClose();
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::Initialize(Local<Object> target,
|
|
Local<Value> unused,
|
|
Local<Context> context,
|
|
void* priv) {
|
|
Environment* env = Environment::GetCurrent(context);
|
|
env->SetMethod(target, "spawn", Spawn);
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::Spawn(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
env->PrintSyncTrace();
|
|
SyncProcessRunner p(env);
|
|
Local<Value> result;
|
|
if (!p.Run(args[0]).ToLocal(&result)) return;
|
|
args.GetReturnValue().Set(result);
|
|
}
|
|
|
|
|
|
SyncProcessRunner::SyncProcessRunner(Environment* env)
|
|
: max_buffer_(0),
|
|
timeout_(0),
|
|
kill_signal_(SIGTERM),
|
|
|
|
uv_loop_(nullptr),
|
|
|
|
stdio_count_(0),
|
|
uv_stdio_containers_(nullptr),
|
|
stdio_pipes_initialized_(false),
|
|
|
|
uv_process_options_(),
|
|
file_buffer_(nullptr),
|
|
args_buffer_(nullptr),
|
|
env_buffer_(nullptr),
|
|
cwd_buffer_(nullptr),
|
|
|
|
uv_process_(),
|
|
killed_(false),
|
|
|
|
buffered_output_size_(0),
|
|
exit_status_(-1),
|
|
term_signal_(-1),
|
|
|
|
uv_timer_(),
|
|
kill_timer_initialized_(false),
|
|
|
|
error_(0),
|
|
pipe_error_(0),
|
|
|
|
lifecycle_(kUninitialized),
|
|
|
|
env_(env) {
|
|
}
|
|
|
|
|
|
SyncProcessRunner::~SyncProcessRunner() {
|
|
CHECK_EQ(lifecycle_, kHandlesClosed);
|
|
|
|
stdio_pipes_.clear();
|
|
delete[] file_buffer_;
|
|
delete[] args_buffer_;
|
|
delete[] cwd_buffer_;
|
|
delete[] env_buffer_;
|
|
delete[] uv_stdio_containers_;
|
|
}
|
|
|
|
|
|
Environment* SyncProcessRunner::env() const {
|
|
return env_;
|
|
}
|
|
|
|
MaybeLocal<Object> SyncProcessRunner::Run(Local<Value> options) {
|
|
EscapableHandleScope scope(env()->isolate());
|
|
|
|
CHECK_EQ(lifecycle_, kUninitialized);
|
|
|
|
Maybe<bool> r = TryInitializeAndRunLoop(options);
|
|
CloseHandlesAndDeleteLoop();
|
|
if (r.IsNothing()) return MaybeLocal<Object>();
|
|
|
|
Local<Object> result = BuildResultObject();
|
|
|
|
return scope.Escape(result);
|
|
}
|
|
|
|
Maybe<bool> SyncProcessRunner::TryInitializeAndRunLoop(Local<Value> options) {
|
|
int r;
|
|
|
|
// There is no recovery from failure inside TryInitializeAndRunLoop - the
|
|
// only option we'd have is to close all handles and destroy the loop.
|
|
CHECK_EQ(lifecycle_, kUninitialized);
|
|
lifecycle_ = kInitialized;
|
|
|
|
uv_loop_ = new uv_loop_t;
|
|
if (uv_loop_ == nullptr) {
|
|
SetError(UV_ENOMEM);
|
|
return Just(false);
|
|
}
|
|
CHECK_EQ(uv_loop_init(uv_loop_), 0);
|
|
|
|
if (!ParseOptions(options).To(&r)) return Nothing<bool>();
|
|
if (r < 0) {
|
|
SetError(r);
|
|
return Just(false);
|
|
}
|
|
|
|
if (timeout_ > 0) {
|
|
r = uv_timer_init(uv_loop_, &uv_timer_);
|
|
if (r < 0) {
|
|
SetError(r);
|
|
return Just(false);
|
|
}
|
|
|
|
uv_unref(reinterpret_cast<uv_handle_t*>(&uv_timer_));
|
|
|
|
uv_timer_.data = this;
|
|
kill_timer_initialized_ = true;
|
|
|
|
// Start the timer immediately. If uv_spawn fails then
|
|
// CloseHandlesAndDeleteLoop() will immediately close the timer handle
|
|
// which implicitly stops it, so there is no risk that the timeout callback
|
|
// runs when the process didn't start.
|
|
r = uv_timer_start(&uv_timer_, KillTimerCallback, timeout_, 0);
|
|
if (r < 0) {
|
|
SetError(r);
|
|
return Just(false);
|
|
}
|
|
}
|
|
|
|
uv_process_options_.exit_cb = ExitCallback;
|
|
r = uv_spawn(uv_loop_, &uv_process_, &uv_process_options_);
|
|
if (r < 0) {
|
|
SetError(r);
|
|
return Just(false);
|
|
}
|
|
uv_process_.data = this;
|
|
|
|
for (const auto& pipe : stdio_pipes_) {
|
|
if (pipe != nullptr) {
|
|
r = pipe->Start();
|
|
if (r < 0) {
|
|
SetPipeError(r);
|
|
return Just(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
r = uv_run(uv_loop_, UV_RUN_DEFAULT);
|
|
if (r < 0)
|
|
// We can't handle uv_run failure.
|
|
ABORT();
|
|
|
|
// If we get here the process should have exited.
|
|
CHECK_GE(exit_status_, 0);
|
|
return Just(true);
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::CloseHandlesAndDeleteLoop() {
|
|
CHECK_LT(lifecycle_, kHandlesClosed);
|
|
|
|
if (uv_loop_ != nullptr) {
|
|
CloseStdioPipes();
|
|
CloseKillTimer();
|
|
// Close the process handle when ExitCallback was not called.
|
|
uv_handle_t* uv_process_handle =
|
|
reinterpret_cast<uv_handle_t*>(&uv_process_);
|
|
|
|
// Close the process handle if it is still open. The handle type also
|
|
// needs to be checked because TryInitializeAndRunLoop() won't spawn a
|
|
// process if input validation fails.
|
|
if (uv_process_handle->type == UV_PROCESS &&
|
|
!uv_is_closing(uv_process_handle))
|
|
uv_close(uv_process_handle, nullptr);
|
|
|
|
// Give closing watchers a chance to finish closing and get their close
|
|
// callbacks called.
|
|
int r = uv_run(uv_loop_, UV_RUN_DEFAULT);
|
|
if (r < 0)
|
|
ABORT();
|
|
|
|
CheckedUvLoopClose(uv_loop_);
|
|
delete uv_loop_;
|
|
uv_loop_ = nullptr;
|
|
|
|
} else {
|
|
// If the loop doesn't exist, neither should any pipes or timers.
|
|
CHECK_EQ(false, stdio_pipes_initialized_);
|
|
CHECK_EQ(false, kill_timer_initialized_);
|
|
}
|
|
|
|
lifecycle_ = kHandlesClosed;
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::CloseStdioPipes() {
|
|
CHECK_LT(lifecycle_, kHandlesClosed);
|
|
|
|
if (stdio_pipes_initialized_) {
|
|
CHECK(!stdio_pipes_.empty());
|
|
CHECK_NOT_NULL(uv_loop_);
|
|
|
|
for (const auto& pipe : stdio_pipes_) {
|
|
if (pipe)
|
|
pipe->Close();
|
|
}
|
|
|
|
stdio_pipes_initialized_ = false;
|
|
}
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::CloseKillTimer() {
|
|
CHECK_LT(lifecycle_, kHandlesClosed);
|
|
|
|
if (kill_timer_initialized_) {
|
|
CHECK_GT(timeout_, 0);
|
|
CHECK_NOT_NULL(uv_loop_);
|
|
|
|
uv_handle_t* uv_timer_handle = reinterpret_cast<uv_handle_t*>(&uv_timer_);
|
|
uv_ref(uv_timer_handle);
|
|
uv_close(uv_timer_handle, KillTimerCloseCallback);
|
|
|
|
kill_timer_initialized_ = false;
|
|
}
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::Kill() {
|
|
// Only attempt to kill once.
|
|
if (killed_)
|
|
return;
|
|
killed_ = true;
|
|
|
|
// We might get here even if the process we spawned has already exited. This
|
|
// could happen when our child process spawned another process which
|
|
// inherited (one of) the stdio pipes. In this case we won't attempt to send
|
|
// a signal to the process, however we will still close our end of the stdio
|
|
// pipes so this situation won't make us hang.
|
|
if (exit_status_ < 0) {
|
|
int r = uv_process_kill(&uv_process_, kill_signal_);
|
|
|
|
// If uv_kill failed with an error that isn't ESRCH, the user probably
|
|
// specified an invalid or unsupported signal. Signal this to the user as
|
|
// and error and kill the process with SIGKILL instead.
|
|
if (r < 0 && r != UV_ESRCH) {
|
|
SetError(r);
|
|
|
|
r = uv_process_kill(&uv_process_, SIGKILL);
|
|
CHECK(r >= 0 || r == UV_ESRCH);
|
|
}
|
|
}
|
|
|
|
// Close all stdio pipes.
|
|
CloseStdioPipes();
|
|
|
|
// Stop the timeout timer immediately.
|
|
CloseKillTimer();
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::IncrementBufferSizeAndCheckOverflow(ssize_t length) {
|
|
buffered_output_size_ += length;
|
|
|
|
if (max_buffer_ > 0 && buffered_output_size_ > max_buffer_) {
|
|
SetError(UV_ENOBUFS);
|
|
Kill();
|
|
}
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::OnExit(int64_t exit_status, int term_signal) {
|
|
if (exit_status < 0)
|
|
return SetError(static_cast<int>(exit_status));
|
|
|
|
exit_status_ = exit_status;
|
|
term_signal_ = term_signal;
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::OnKillTimerTimeout() {
|
|
SetError(UV_ETIMEDOUT);
|
|
Kill();
|
|
}
|
|
|
|
|
|
int SyncProcessRunner::GetError() {
|
|
if (error_ != 0)
|
|
return error_;
|
|
else
|
|
return pipe_error_;
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::SetError(int error) {
|
|
if (error_ == 0)
|
|
error_ = error;
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::SetPipeError(int pipe_error) {
|
|
if (pipe_error_ == 0)
|
|
pipe_error_ = pipe_error;
|
|
}
|
|
|
|
|
|
Local<Object> SyncProcessRunner::BuildResultObject() {
|
|
EscapableHandleScope scope(env()->isolate());
|
|
Local<Context> context = env()->context();
|
|
|
|
Local<Object> js_result = Object::New(env()->isolate());
|
|
|
|
if (GetError() != 0) {
|
|
js_result->Set(context, env()->error_string(),
|
|
Integer::New(env()->isolate(), GetError())).Check();
|
|
}
|
|
|
|
if (exit_status_ >= 0) {
|
|
if (term_signal_ > 0) {
|
|
js_result->Set(context, env()->status_string(),
|
|
Null(env()->isolate())).Check();
|
|
} else {
|
|
js_result->Set(context, env()->status_string(),
|
|
Number::New(env()->isolate(),
|
|
static_cast<double>(exit_status_))).Check();
|
|
}
|
|
} else {
|
|
// If exit_status_ < 0 the process was never started because of some error.
|
|
js_result->Set(context, env()->status_string(),
|
|
Null(env()->isolate())).Check();
|
|
}
|
|
|
|
if (term_signal_ > 0)
|
|
js_result->Set(context, env()->signal_string(),
|
|
String::NewFromUtf8(env()->isolate(),
|
|
signo_string(term_signal_),
|
|
v8::NewStringType::kNormal)
|
|
.ToLocalChecked())
|
|
.Check();
|
|
else
|
|
js_result->Set(context, env()->signal_string(),
|
|
Null(env()->isolate())).Check();
|
|
|
|
if (exit_status_ >= 0)
|
|
js_result->Set(context, env()->output_string(),
|
|
BuildOutputArray()).Check();
|
|
else
|
|
js_result->Set(context, env()->output_string(),
|
|
Null(env()->isolate())).Check();
|
|
|
|
js_result->Set(context, env()->pid_string(),
|
|
Number::New(env()->isolate(), uv_process_.pid)).Check();
|
|
|
|
return scope.Escape(js_result);
|
|
}
|
|
|
|
|
|
Local<Array> SyncProcessRunner::BuildOutputArray() {
|
|
CHECK_GE(lifecycle_, kInitialized);
|
|
CHECK(!stdio_pipes_.empty());
|
|
|
|
EscapableHandleScope scope(env()->isolate());
|
|
Local<Context> context = env()->context();
|
|
Local<Array> js_output = Array::New(env()->isolate(), stdio_count_);
|
|
|
|
for (uint32_t i = 0; i < stdio_pipes_.size(); i++) {
|
|
SyncProcessStdioPipe* h = stdio_pipes_[i].get();
|
|
if (h != nullptr && h->writable())
|
|
js_output->Set(context, i, h->GetOutputAsBuffer(env())).Check();
|
|
else
|
|
js_output->Set(context, i, Null(env()->isolate())).Check();
|
|
}
|
|
|
|
return scope.Escape(js_output);
|
|
}
|
|
|
|
Maybe<int> SyncProcessRunner::ParseOptions(Local<Value> js_value) {
|
|
Isolate* isolate = env()->isolate();
|
|
HandleScope scope(isolate);
|
|
int r;
|
|
|
|
if (!js_value->IsObject()) return Just<int>(UV_EINVAL);
|
|
|
|
Local<Context> context = env()->context();
|
|
Local<Object> js_options = js_value.As<Object>();
|
|
|
|
Local<Value> js_file =
|
|
js_options->Get(context, env()->file_string()).ToLocalChecked();
|
|
if (!CopyJsString(js_file, &file_buffer_).To(&r)) return Nothing<int>();
|
|
if (r < 0) return Just(r);
|
|
uv_process_options_.file = file_buffer_;
|
|
|
|
Local<Value> js_args =
|
|
js_options->Get(context, env()->args_string()).ToLocalChecked();
|
|
if (!CopyJsStringArray(js_args, &args_buffer_).To(&r)) return Nothing<int>();
|
|
if (r < 0) return Just(r);
|
|
uv_process_options_.args = reinterpret_cast<char**>(args_buffer_);
|
|
|
|
Local<Value> js_cwd =
|
|
js_options->Get(context, env()->cwd_string()).ToLocalChecked();
|
|
if (IsSet(js_cwd)) {
|
|
if (!CopyJsString(js_cwd, &cwd_buffer_).To(&r)) return Nothing<int>();
|
|
if (r < 0) return Just(r);
|
|
uv_process_options_.cwd = cwd_buffer_;
|
|
}
|
|
|
|
Local<Value> js_env_pairs =
|
|
js_options->Get(context, env()->env_pairs_string()).ToLocalChecked();
|
|
if (IsSet(js_env_pairs)) {
|
|
if (!CopyJsStringArray(js_env_pairs, &env_buffer_).To(&r))
|
|
return Nothing<int>();
|
|
if (r < 0) return Just(r);
|
|
|
|
uv_process_options_.env = reinterpret_cast<char**>(env_buffer_);
|
|
}
|
|
Local<Value> js_uid =
|
|
js_options->Get(context, env()->uid_string()).ToLocalChecked();
|
|
if (IsSet(js_uid)) {
|
|
CHECK(js_uid->IsInt32());
|
|
const int32_t uid = js_uid.As<Int32>()->Value();
|
|
uv_process_options_.uid = static_cast<uv_uid_t>(uid);
|
|
uv_process_options_.flags |= UV_PROCESS_SETUID;
|
|
}
|
|
|
|
Local<Value> js_gid =
|
|
js_options->Get(context, env()->gid_string()).ToLocalChecked();
|
|
if (IsSet(js_gid)) {
|
|
CHECK(js_gid->IsInt32());
|
|
const int32_t gid = js_gid.As<Int32>()->Value();
|
|
uv_process_options_.gid = static_cast<uv_gid_t>(gid);
|
|
uv_process_options_.flags |= UV_PROCESS_SETGID;
|
|
}
|
|
|
|
Local<Value> js_detached =
|
|
js_options->Get(context, env()->detached_string()).ToLocalChecked();
|
|
if (js_detached->BooleanValue(isolate))
|
|
uv_process_options_.flags |= UV_PROCESS_DETACHED;
|
|
|
|
Local<Value> js_win_hide =
|
|
js_options->Get(context, env()->windows_hide_string()).ToLocalChecked();
|
|
if (js_win_hide->BooleanValue(isolate))
|
|
uv_process_options_.flags |= UV_PROCESS_WINDOWS_HIDE;
|
|
|
|
Local<Value> js_wva =
|
|
js_options->Get(context, env()->windows_verbatim_arguments_string())
|
|
.ToLocalChecked();
|
|
|
|
if (js_wva->BooleanValue(isolate))
|
|
uv_process_options_.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
|
|
|
|
Local<Value> js_timeout =
|
|
js_options->Get(context, env()->timeout_string()).ToLocalChecked();
|
|
if (IsSet(js_timeout)) {
|
|
CHECK(js_timeout->IsNumber());
|
|
int64_t timeout = js_timeout->IntegerValue(context).FromJust();
|
|
timeout_ = static_cast<uint64_t>(timeout);
|
|
}
|
|
|
|
Local<Value> js_max_buffer =
|
|
js_options->Get(context, env()->max_buffer_string()).ToLocalChecked();
|
|
if (IsSet(js_max_buffer)) {
|
|
CHECK(js_max_buffer->IsNumber());
|
|
max_buffer_ = js_max_buffer->NumberValue(context).FromJust();
|
|
}
|
|
|
|
Local<Value> js_kill_signal =
|
|
js_options->Get(context, env()->kill_signal_string()).ToLocalChecked();
|
|
if (IsSet(js_kill_signal)) {
|
|
CHECK(js_kill_signal->IsInt32());
|
|
kill_signal_ = js_kill_signal.As<Int32>()->Value();
|
|
}
|
|
|
|
Local<Value> js_stdio =
|
|
js_options->Get(context, env()->stdio_string()).ToLocalChecked();
|
|
r = ParseStdioOptions(js_stdio);
|
|
if (r < 0) return Just(r);
|
|
|
|
return Just(0);
|
|
}
|
|
|
|
|
|
int SyncProcessRunner::ParseStdioOptions(Local<Value> js_value) {
|
|
HandleScope scope(env()->isolate());
|
|
Local<Array> js_stdio_options;
|
|
|
|
if (!js_value->IsArray())
|
|
return UV_EINVAL;
|
|
|
|
Local<Context> context = env()->context();
|
|
js_stdio_options = js_value.As<Array>();
|
|
|
|
stdio_count_ = js_stdio_options->Length();
|
|
uv_stdio_containers_ = new uv_stdio_container_t[stdio_count_];
|
|
|
|
stdio_pipes_.clear();
|
|
stdio_pipes_.resize(stdio_count_);
|
|
stdio_pipes_initialized_ = true;
|
|
|
|
for (uint32_t i = 0; i < stdio_count_; i++) {
|
|
Local<Value> js_stdio_option =
|
|
js_stdio_options->Get(context, i).ToLocalChecked();
|
|
|
|
if (!js_stdio_option->IsObject())
|
|
return UV_EINVAL;
|
|
|
|
int r = ParseStdioOption(i, js_stdio_option.As<Object>());
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
uv_process_options_.stdio = uv_stdio_containers_;
|
|
uv_process_options_.stdio_count = stdio_count_;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int SyncProcessRunner::ParseStdioOption(int child_fd,
|
|
Local<Object> js_stdio_option) {
|
|
Local<Context> context = env()->context();
|
|
Local<Value> js_type =
|
|
js_stdio_option->Get(context, env()->type_string()).ToLocalChecked();
|
|
|
|
if (js_type->StrictEquals(env()->ignore_string())) {
|
|
return AddStdioIgnore(child_fd);
|
|
|
|
} else if (js_type->StrictEquals(env()->pipe_string())) {
|
|
Isolate* isolate = env()->isolate();
|
|
Local<String> rs = env()->readable_string();
|
|
Local<String> ws = env()->writable_string();
|
|
|
|
bool readable = js_stdio_option->Get(context, rs)
|
|
.ToLocalChecked()->BooleanValue(isolate);
|
|
bool writable =
|
|
js_stdio_option->Get(context, ws)
|
|
.ToLocalChecked()->BooleanValue(isolate);
|
|
|
|
uv_buf_t buf = uv_buf_init(nullptr, 0);
|
|
|
|
if (readable) {
|
|
Local<Value> input =
|
|
js_stdio_option->Get(context, env()->input_string()).ToLocalChecked();
|
|
if (Buffer::HasInstance(input)) {
|
|
buf = uv_buf_init(Buffer::Data(input),
|
|
static_cast<unsigned int>(Buffer::Length(input)));
|
|
} else if (!input->IsUndefined() && !input->IsNull()) {
|
|
// Strings, numbers etc. are currently unsupported. It's not possible
|
|
// to create a buffer for them here because there is no way to free
|
|
// them afterwards.
|
|
return UV_EINVAL;
|
|
}
|
|
}
|
|
|
|
return AddStdioPipe(child_fd, readable, writable, buf);
|
|
|
|
} else if (js_type->StrictEquals(env()->inherit_string()) ||
|
|
js_type->StrictEquals(env()->fd_string())) {
|
|
int inherit_fd = js_stdio_option->Get(context, env()->fd_string())
|
|
.ToLocalChecked()->Int32Value(context).FromJust();
|
|
return AddStdioInheritFD(child_fd, inherit_fd);
|
|
|
|
} else {
|
|
CHECK(0 && "invalid child stdio type");
|
|
return UV_EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
int SyncProcessRunner::AddStdioIgnore(uint32_t child_fd) {
|
|
CHECK_LT(child_fd, stdio_count_);
|
|
CHECK(!stdio_pipes_[child_fd]);
|
|
|
|
uv_stdio_containers_[child_fd].flags = UV_IGNORE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int SyncProcessRunner::AddStdioPipe(uint32_t child_fd,
|
|
bool readable,
|
|
bool writable,
|
|
uv_buf_t input_buffer) {
|
|
CHECK_LT(child_fd, stdio_count_);
|
|
CHECK(!stdio_pipes_[child_fd]);
|
|
|
|
std::unique_ptr<SyncProcessStdioPipe> h(
|
|
new SyncProcessStdioPipe(this, readable, writable, input_buffer));
|
|
|
|
int r = h->Initialize(uv_loop_);
|
|
if (r < 0) {
|
|
h.reset();
|
|
return r;
|
|
}
|
|
|
|
uv_stdio_containers_[child_fd].flags = h->uv_flags();
|
|
uv_stdio_containers_[child_fd].data.stream = h->uv_stream();
|
|
|
|
stdio_pipes_[child_fd] = std::move(h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int SyncProcessRunner::AddStdioInheritFD(uint32_t child_fd, int inherit_fd) {
|
|
CHECK_LT(child_fd, stdio_count_);
|
|
CHECK(!stdio_pipes_[child_fd]);
|
|
|
|
uv_stdio_containers_[child_fd].flags = UV_INHERIT_FD;
|
|
uv_stdio_containers_[child_fd].data.fd = inherit_fd;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool SyncProcessRunner::IsSet(Local<Value> value) {
|
|
return !value->IsUndefined() && !value->IsNull();
|
|
}
|
|
|
|
Maybe<int> SyncProcessRunner::CopyJsString(Local<Value> js_value,
|
|
const char** target) {
|
|
Isolate* isolate = env()->isolate();
|
|
Local<String> js_string;
|
|
size_t size, written;
|
|
char* buffer;
|
|
|
|
if (js_value->IsString())
|
|
js_string = js_value.As<String>();
|
|
else if (!js_value->ToString(env()->isolate()->GetCurrentContext())
|
|
.ToLocal(&js_string))
|
|
return Nothing<int>();
|
|
|
|
// Include space for null terminator byte.
|
|
if (!StringBytes::StorageSize(isolate, js_string, UTF8).To(&size))
|
|
return Nothing<int>();
|
|
size += 1;
|
|
|
|
buffer = new char[size];
|
|
|
|
written = StringBytes::Write(isolate, buffer, -1, js_string, UTF8);
|
|
buffer[written] = '\0';
|
|
|
|
*target = buffer;
|
|
return Just(0);
|
|
}
|
|
|
|
Maybe<int> SyncProcessRunner::CopyJsStringArray(Local<Value> js_value,
|
|
char** target) {
|
|
Isolate* isolate = env()->isolate();
|
|
Local<Array> js_array;
|
|
uint32_t length;
|
|
size_t list_size, data_size, data_offset;
|
|
char** list;
|
|
char* buffer;
|
|
|
|
if (!js_value->IsArray()) return Just<int>(UV_EINVAL);
|
|
|
|
Local<Context> context = env()->context();
|
|
js_array = js_value.As<Array>()->Clone().As<Array>();
|
|
length = js_array->Length();
|
|
data_size = 0;
|
|
|
|
// Index has a pointer to every string element, plus one more for a final
|
|
// null pointer.
|
|
list_size = (length + 1) * sizeof *list;
|
|
|
|
// Convert all array elements to string. Modify the js object itself if
|
|
// needed - it's okay since we cloned the original object. Also compute the
|
|
// length of all strings, including room for a null terminator after every
|
|
// string. Align strings to cache lines.
|
|
for (uint32_t i = 0; i < length; i++) {
|
|
auto value = js_array->Get(context, i).ToLocalChecked();
|
|
|
|
if (!value->IsString()) {
|
|
Local<String> string;
|
|
if (!value->ToString(env()->isolate()->GetCurrentContext())
|
|
.ToLocal(&string))
|
|
return Nothing<int>();
|
|
js_array
|
|
->Set(context,
|
|
i,
|
|
value->ToString(env()->isolate()->GetCurrentContext())
|
|
.ToLocalChecked())
|
|
.Check();
|
|
}
|
|
|
|
Maybe<size_t> maybe_size = StringBytes::StorageSize(isolate, value, UTF8);
|
|
if (maybe_size.IsNothing()) return Nothing<int>();
|
|
data_size += maybe_size.FromJust() + 1;
|
|
data_size = RoundUp(data_size, sizeof(void*));
|
|
}
|
|
|
|
buffer = new char[list_size + data_size];
|
|
|
|
list = reinterpret_cast<char**>(buffer);
|
|
data_offset = list_size;
|
|
|
|
for (uint32_t i = 0; i < length; i++) {
|
|
list[i] = buffer + data_offset;
|
|
auto value = js_array->Get(context, i).ToLocalChecked();
|
|
data_offset += StringBytes::Write(isolate,
|
|
buffer + data_offset,
|
|
-1,
|
|
value,
|
|
UTF8);
|
|
buffer[data_offset++] = '\0';
|
|
data_offset = RoundUp(data_offset, sizeof(void*));
|
|
}
|
|
|
|
list[length] = nullptr;
|
|
|
|
*target = buffer;
|
|
return Just(0);
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::ExitCallback(uv_process_t* handle,
|
|
int64_t exit_status,
|
|
int term_signal) {
|
|
SyncProcessRunner* self = reinterpret_cast<SyncProcessRunner*>(handle->data);
|
|
uv_close(reinterpret_cast<uv_handle_t*>(handle), nullptr);
|
|
self->OnExit(exit_status, term_signal);
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::KillTimerCallback(uv_timer_t* handle) {
|
|
SyncProcessRunner* self = reinterpret_cast<SyncProcessRunner*>(handle->data);
|
|
self->OnKillTimerTimeout();
|
|
}
|
|
|
|
|
|
void SyncProcessRunner::KillTimerCloseCallback(uv_handle_t* handle) {
|
|
// No-op.
|
|
}
|
|
|
|
} // namespace node
|
|
|
|
NODE_MODULE_CONTEXT_AWARE_INTERNAL(spawn_sync,
|
|
node::SyncProcessRunner::Initialize)
|