| // 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-inl.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); |
| |
| // Deliberately ignore the return value, we might not have |
| // sufficient privileges to signal the child process. |
| USE(uv_process_kill(&uv_process_, SIGKILL)); |
| } |
| } |
| |
| // 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()); |
| MaybeStackBuffer<Local<Value>, 8> js_output(stdio_pipes_.size()); |
| |
| for (uint32_t i = 0; i < stdio_pipes_.size(); i++) { |
| SyncProcessStdioPipe* h = stdio_pipes_[i].get(); |
| if (h != nullptr && h->writable()) |
| js_output[i] = h->GetOutputAsBuffer(env()); |
| else |
| js_output[i] = Null(env()->isolate()); |
| } |
| |
| return scope.Escape( |
| Array::New(env()->isolate(), js_output.out(), js_output.length())); |
| } |
| |
| 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, |
| string) |
| .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) |