diff --git a/src/node_extensions.h b/src/node_extensions.h index 94bbf8a650..ab528e3278 100644 --- a/src/node_extensions.h +++ b/src/node_extensions.h @@ -47,6 +47,7 @@ NODE_EXT_LIST_ITEM(node_tcp_wrap) NODE_EXT_LIST_ITEM(node_pipe_wrap) NODE_EXT_LIST_ITEM(node_cares_wrap) NODE_EXT_LIST_ITEM(node_stdio_wrap) +NODE_EXT_LIST_ITEM(node_process_wrap) NODE_EXT_LIST_END diff --git a/src/pipe_wrap.cc b/src/pipe_wrap.cc index eb7069050f..e6faf55da2 100644 --- a/src/pipe_wrap.cc +++ b/src/pipe_wrap.cc @@ -3,6 +3,7 @@ #include #include #include +#include #define UNWRAP \ assert(!args.Holder().IsEmpty()); \ @@ -37,176 +38,187 @@ Persistent pipeConstructor; typedef class ReqWrap ConnectWrap; -class PipeWrap : StreamWrap { - public: +uv_pipe_t* PipeWrap::UVHandle() { + return &handle_; +} - static void Initialize(Handle target) { - StreamWrap::Initialize(target); - HandleScope scope; +PipeWrap* PipeWrap::Unwrap(Local obj) { + assert(!obj.IsEmpty()); + assert(obj->InternalFieldCount() > 0); + return static_cast(obj->GetPointerFromInternalField(0)); +} - Local t = FunctionTemplate::New(New); - t->SetClassName(String::NewSymbol("Pipe")); - t->InstanceTemplate()->SetInternalFieldCount(1); +void PipeWrap::Initialize(Handle target) { + StreamWrap::Initialize(target); - NODE_SET_PROTOTYPE_METHOD(t, "close", HandleWrap::Close); + HandleScope scope; - NODE_SET_PROTOTYPE_METHOD(t, "readStart", StreamWrap::ReadStart); - NODE_SET_PROTOTYPE_METHOD(t, "readStop", StreamWrap::ReadStop); - NODE_SET_PROTOTYPE_METHOD(t, "write", StreamWrap::Write); - NODE_SET_PROTOTYPE_METHOD(t, "shutdown", StreamWrap::Shutdown); + Local t = FunctionTemplate::New(New); + t->SetClassName(String::NewSymbol("Pipe")); - NODE_SET_PROTOTYPE_METHOD(t, "bind", Bind); - NODE_SET_PROTOTYPE_METHOD(t, "listen", Listen); - NODE_SET_PROTOTYPE_METHOD(t, "connect", Connect); + t->InstanceTemplate()->SetInternalFieldCount(1); - pipeConstructor = Persistent::New(t->GetFunction()); + NODE_SET_PROTOTYPE_METHOD(t, "close", HandleWrap::Close); - target->Set(String::NewSymbol("Pipe"), pipeConstructor); - } + NODE_SET_PROTOTYPE_METHOD(t, "readStart", StreamWrap::ReadStart); + NODE_SET_PROTOTYPE_METHOD(t, "readStop", StreamWrap::ReadStop); + NODE_SET_PROTOTYPE_METHOD(t, "write", StreamWrap::Write); + NODE_SET_PROTOTYPE_METHOD(t, "shutdown", StreamWrap::Shutdown); - private: - static Handle New(const Arguments& args) { - // This constructor should not be exposed to public javascript. - // Therefore we assert that we are not trying to call this as a - // normal function. - assert(args.IsConstructCall()); + NODE_SET_PROTOTYPE_METHOD(t, "bind", Bind); + NODE_SET_PROTOTYPE_METHOD(t, "listen", Listen); + NODE_SET_PROTOTYPE_METHOD(t, "connect", Connect); - HandleScope scope; - PipeWrap* wrap = new PipeWrap(args.This()); - assert(wrap); + pipeConstructor = Persistent::New(t->GetFunction()); - return scope.Close(args.This()); - } + target->Set(String::NewSymbol("Pipe"), pipeConstructor); +} - PipeWrap(Handle object) : StreamWrap(object, - (uv_stream_t*) &handle_) { - int r = uv_pipe_init(&handle_); - assert(r == 0); // How do we proxy this error up to javascript? - // Suggestion: uv_pipe_init() returns void. - handle_.data = reinterpret_cast(this); - UpdateWriteQueueSize(); - } - static Handle Bind(const Arguments& args) { - HandleScope scope; +Handle PipeWrap::New(const Arguments& args) { + // This constructor should not be exposed to public javascript. + // Therefore we assert that we are not trying to call this as a + // normal function. + assert(args.IsConstructCall()); - UNWRAP + HandleScope scope; + PipeWrap* wrap = new PipeWrap(args.This()); + assert(wrap); - String::AsciiValue name(args[0]->ToString()); + return scope.Close(args.This()); +} - int r = uv_pipe_bind(&wrap->handle_, *name); - // Error starting the pipe. - if (r) SetErrno(uv_last_error().code); +PipeWrap::PipeWrap(Handle object) : StreamWrap(object, + (uv_stream_t*) &handle_) { + int r = uv_pipe_init(&handle_); + assert(r == 0); // How do we proxy this error up to javascript? + // Suggestion: uv_pipe_init() returns void. + handle_.data = reinterpret_cast(this); + UpdateWriteQueueSize(); +} - return scope.Close(Integer::New(r)); - } - static Handle Listen(const Arguments& args) { - HandleScope scope; +Handle PipeWrap::Bind(const Arguments& args) { + HandleScope scope; - UNWRAP + UNWRAP - int backlog = args[0]->Int32Value(); + String::AsciiValue name(args[0]->ToString()); - int r = uv_listen((uv_stream_t*)&wrap->handle_, backlog, OnConnection); + int r = uv_pipe_bind(&wrap->handle_, *name); - // Error starting the pipe. - if (r) SetErrno(uv_last_error().code); + // Error starting the pipe. + if (r) SetErrno(uv_last_error().code); - return scope.Close(Integer::New(r)); - } + return scope.Close(Integer::New(r)); +} - // TODO maybe share with TCPWrap? - static void OnConnection(uv_stream_t* handle, int status) { - HandleScope scope; - PipeWrap* wrap = static_cast(handle->data); - assert(&wrap->handle_ == (uv_pipe_t*)handle); +Handle PipeWrap::Listen(const Arguments& args) { + HandleScope scope; - // We should not be getting this callback if someone as already called - // uv_close() on the handle. - assert(wrap->object_.IsEmpty() == false); + UNWRAP - if (status != 0) { - // TODO Handle server error (set errno and call onconnection with NULL) - assert(0); - return; - } + int backlog = args[0]->Int32Value(); - // Instanciate the client javascript object and handle. - Local client_obj = pipeConstructor->NewInstance(); + int r = uv_listen((uv_stream_t*)&wrap->handle_, backlog, OnConnection); - // Unwrap the client javascript object. - assert(client_obj->InternalFieldCount() > 0); - PipeWrap* client_wrap = - static_cast(client_obj->GetPointerFromInternalField(0)); + // Error starting the pipe. + if (r) SetErrno(uv_last_error().code); - int r = uv_accept(handle, (uv_stream_t*)&client_wrap->handle_); + return scope.Close(Integer::New(r)); +} - // uv_accept should always work. - assert(r == 0); - // Successful accept. Call the onconnection callback in JavaScript land. - Local argv[1] = { client_obj }; - MakeCallback(wrap->object_, "onconnection", 1, argv); +// TODO maybe share with TCPWrap? +void PipeWrap::OnConnection(uv_stream_t* handle, int status) { + HandleScope scope; + + PipeWrap* wrap = static_cast(handle->data); + assert(&wrap->handle_ == (uv_pipe_t*)handle); + + // We should not be getting this callback if someone as already called + // uv_close() on the handle. + assert(wrap->object_.IsEmpty() == false); + + if (status != 0) { + // TODO Handle server error (set errno and call onconnection with NULL) + assert(0); + return; } - // TODO Maybe share this with TCPWrap? - static void AfterConnect(uv_connect_t* req, int status) { - ConnectWrap* req_wrap = (ConnectWrap*) req->data; - PipeWrap* wrap = (PipeWrap*) req->handle->data; + // Instanciate the client javascript object and handle. + Local client_obj = pipeConstructor->NewInstance(); - HandleScope scope; + // Unwrap the client javascript object. + assert(client_obj->InternalFieldCount() > 0); + PipeWrap* client_wrap = + static_cast(client_obj->GetPointerFromInternalField(0)); - // The wrap and request objects should still be there. - assert(req_wrap->object_.IsEmpty() == false); - assert(wrap->object_.IsEmpty() == false); + int r = uv_accept(handle, (uv_stream_t*)&client_wrap->handle_); - if (status) { - SetErrno(uv_last_error().code); - } + // uv_accept should always work. + assert(r == 0); - Local argv[3] = { - Integer::New(status), - Local::New(wrap->object_), - Local::New(req_wrap->object_) - }; + // Successful accept. Call the onconnection callback in JavaScript land. + Local argv[1] = { client_obj }; + MakeCallback(wrap->object_, "onconnection", 1, argv); +} - MakeCallback(req_wrap->object_, "oncomplete", 3, argv); +// TODO Maybe share this with TCPWrap? +void PipeWrap::AfterConnect(uv_connect_t* req, int status) { + ConnectWrap* req_wrap = (ConnectWrap*) req->data; + PipeWrap* wrap = (PipeWrap*) req->handle->data; - delete req_wrap; + HandleScope scope; + + // The wrap and request objects should still be there. + assert(req_wrap->object_.IsEmpty() == false); + assert(wrap->object_.IsEmpty() == false); + + if (status) { + SetErrno(uv_last_error().code); } - static Handle Connect(const Arguments& args) { - HandleScope scope; + Local argv[3] = { + Integer::New(status), + Local::New(wrap->object_), + Local::New(req_wrap->object_) + }; - UNWRAP + MakeCallback(req_wrap->object_, "oncomplete", 3, argv); - String::AsciiValue name(args[0]->ToString()); + delete req_wrap; +} - ConnectWrap* req_wrap = new ConnectWrap(); - int r = uv_pipe_connect(&req_wrap->req_, - &wrap->handle_, - *name, - AfterConnect); +Handle PipeWrap::Connect(const Arguments& args) { + HandleScope scope; - req_wrap->Dispatched(); + UNWRAP - if (r) { - SetErrno(uv_last_error().code); - delete req_wrap; - return scope.Close(v8::Null()); - } else { - return scope.Close(req_wrap->object_); - } - } + String::AsciiValue name(args[0]->ToString()); + + ConnectWrap* req_wrap = new ConnectWrap(); - uv_pipe_t handle_; -}; + int r = uv_pipe_connect(&req_wrap->req_, + &wrap->handle_, + *name, + AfterConnect); + + req_wrap->Dispatched(); + + if (r) { + SetErrno(uv_last_error().code); + delete req_wrap; + return scope.Close(v8::Null()); + } else { + return scope.Close(req_wrap->object_); + } +} } // namespace node diff --git a/src/pipe_wrap.h b/src/pipe_wrap.h new file mode 100644 index 0000000000..65bec64315 --- /dev/null +++ b/src/pipe_wrap.h @@ -0,0 +1,32 @@ +#ifndef PIPE_WRAP_H_ +#define PIPE_WRAP_H_ +#include + +namespace node { + +class PipeWrap : StreamWrap { + public: + uv_pipe_t* UVHandle(); + + static PipeWrap* Unwrap(v8::Local obj); + static void Initialize(v8::Handle target); + + private: + PipeWrap(v8::Handle object); + + static v8::Handle New(const v8::Arguments& args); + static v8::Handle Bind(const v8::Arguments& args); + static v8::Handle Listen(const v8::Arguments& args); + static v8::Handle Connect(const v8::Arguments& args); + + static void OnConnection(uv_stream_t* handle, int status); + static void AfterConnect(uv_connect_t* req, int status); + + uv_pipe_t handle_; +}; + + +} // namespace node + + +#endif // PIPE_WRAP_H_ diff --git a/src/process_wrap.cc b/src/process_wrap.cc new file mode 100644 index 0000000000..4a8628ef54 --- /dev/null +++ b/src/process_wrap.cc @@ -0,0 +1,203 @@ +#include +#include +#include +#include + +#define UNWRAP \ + assert(!args.Holder().IsEmpty()); \ + assert(args.Holder()->InternalFieldCount() > 0); \ + ProcessWrap* wrap = \ + static_cast(args.Holder()->GetPointerFromInternalField(0)); \ + if (!wrap) { \ + SetErrno(UV_EBADF); \ + return scope.Close(Integer::New(-1)); \ + } + +namespace node { + +using v8::Object; +using v8::Handle; +using v8::Local; +using v8::Persistent; +using v8::Value; +using v8::HandleScope; +using v8::FunctionTemplate; +using v8::String; +using v8::Array; +using v8::Function; +using v8::TryCatch; +using v8::Context; +using v8::Arguments; +using v8::Integer; + + +class ProcessWrap : public HandleWrap { + public: + static void Initialize(Handle target) { + HandleScope scope; + + HandleWrap::Initialize(target); + + Local constructor = FunctionTemplate::New(New); + constructor->InstanceTemplate()->SetInternalFieldCount(1); + constructor->SetClassName(String::NewSymbol("Process")); + + NODE_SET_PROTOTYPE_METHOD(constructor, "close", HandleWrap::Close); + + NODE_SET_PROTOTYPE_METHOD(constructor, "spawn", Spawn); + NODE_SET_PROTOTYPE_METHOD(constructor, "kill", Kill); + + target->Set(String::NewSymbol("Process"), constructor->GetFunction()); + } + + private: + static Handle New(const Arguments& args) { + // This constructor should not be exposed to public javascript. + // Therefore we assert that we are not trying to call this as a + // normal function. + assert(args.IsConstructCall()); + + HandleScope scope; + ProcessWrap *wrap = new ProcessWrap(args.This()); + assert(wrap); + + return scope.Close(args.This()); + } + + ProcessWrap(Handle object) : HandleWrap(object, NULL) { } + ~ProcessWrap() { } + + static Handle Spawn(const Arguments& args) { + HandleScope scope; + + UNWRAP + + Local js_options = args[0]->ToObject(); + + uv_process_options_t options; + memset(&options, 0, sizeof(uv_process_options_t)); + + options.exit_cb = OnExit; + + // TODO is this possible to do without mallocing ? + + // options.file + Local file_v = js_options->Get(String::New("file")); + if (!file_v.IsEmpty() && file_v->IsString()) { + String::Utf8Value file(file_v->ToString()); + options.file = strdup(*file); + } + + // options.args + Local argv_v = js_options->Get(String::New("args")); + if (!argv_v.IsEmpty() && argv_v->IsArray()) { + Local js_argv = Local::Cast(argv_v); + int argc = js_argv->Length(); + // Heap allocate to detect errors. +1 is for NULL. + options.args = new char*[argc + 1]; + for (int i = 0; i < argc; i++) { + String::Utf8Value arg(js_argv->Get(i)->ToString()); + options.args[i] = strdup(*arg); + } + options.args[argc] = NULL; + } + + // options.cwd + Local cwd_v = js_options->Get(String::New("cwd")); + if (!cwd_v.IsEmpty() && cwd_v->IsString()) { + String::Utf8Value cwd(js_options->ToString()); + options.cwd = strdup(*cwd); + } + + // options.env + Local env_v = js_options->Get(String::New("env")); + if (!env_v.IsEmpty() && env_v->IsArray()) { + Local env = Local::Cast(env_v); + int envc = env->Length(); + options.env = new char*[envc + 1]; // Heap allocated to detect errors. + for (int i = 0; i < envc; i++) { + String::Utf8Value pair(env->Get(i)->ToString()); + options.env[i] = strdup(*pair); + } + options.env[envc] = NULL; + } + + // options.stdin_stream + Local stdin_stream_v = js_options->Get(String::New("stdinStream")); + if (!stdin_stream_v.IsEmpty() && stdin_stream_v->IsObject()) { + PipeWrap* stdin_wrap = PipeWrap::Unwrap(stdin_stream_v->ToObject()); + options.stdin_stream = stdin_wrap->UVHandle(); + } + + // options.stdout_stream + Local stdout_stream_v = js_options->Get(String::New("stdoutStream")); + if (!stdout_stream_v.IsEmpty() && stdout_stream_v->IsObject()) { + PipeWrap* stdout_wrap = PipeWrap::Unwrap(stdout_stream_v->ToObject()); + options.stdout_stream = stdout_wrap->UVHandle(); + } + + // options.stderr_stream + Local stderr_stream_v = js_options->Get(String::New("stderrStream")); + if (!stderr_stream_v.IsEmpty() && stderr_stream_v->IsObject()) { + PipeWrap* stderr_wrap = PipeWrap::Unwrap(stderr_stream_v->ToObject()); + options.stderr_stream = stderr_wrap->UVHandle(); + } + + int r = uv_spawn(&wrap->process_, options); + + wrap->SetHandle((uv_handle_t*)&wrap->process_); + assert(wrap->process_.data == wrap); + + if (options.args) { + for (int i = 0; options.args[i]; i++) free(options.args[i]); + delete [] options.args; + } + + free(options.cwd); + + if (options.env) { + for (int i = 0; options.env[i]; i++) free(options.env[i]); + delete [] options.env; + } + + if (r) SetErrno(uv_last_error().code); + + return scope.Close(Integer::New(r)); + } + + static Handle Kill(const Arguments& args) { + HandleScope scope; + + UNWRAP + + int signal = args[0]->Int32Value(); + + int r = uv_process_kill(&wrap->process_, signal); + + if (r) SetErrno(uv_last_error().code); + + return scope.Close(Integer::New(r)); + } + + static void OnExit(uv_process_t* handle, int exit_status, int term_signal) { + HandleScope scope; + + ProcessWrap* wrap = static_cast(handle->data); + assert(wrap); + assert(&wrap->process_ == handle); + + Local argv[2] = { + Integer::New(exit_status), + Integer::New(term_signal) + }; + + MakeCallback(wrap->object_, "onexit", 2, argv); + } + + uv_process_t process_; +}; + + +} // namespace node + +NODE_MODULE(node_process_wrap, node::ProcessWrap::Initialize); diff --git a/test/simple/test-process-wrap.js b/test/simple/test-process-wrap.js new file mode 100644 index 0000000000..70c1e43fce --- /dev/null +++ b/test/simple/test-process-wrap.js @@ -0,0 +1,63 @@ +// 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. + +var common = require('../common'); +var assert = require('assert'); +var Process = process.binding('process_wrap').Process; +var Pipe = process.binding('pipe_wrap').Pipe; +var pipe = new Pipe(); +var p = new Process(); + +var processExited = false; +var gotPipeEOF = false; +var gotPipeData = false; + +p.onexit = function() { + console.log("exit"); + p.close(); + pipe.readStart(); + + processExited = true; +} + +pipe.onread = function(b, off, len) { + assert.ok(processExited); + if (b) { + gotPipeData = true; + console.log("read %d", len); + } else { + gotPipeEOF = true; + pipe.close(); + } +} + +p.spawn({ + file: process.execPath, + args: [ process.execPath, "-v" ], + stdoutStream: pipe +}); + + +process.on('exit', function() { + assert.ok(processExited); + assert.ok(gotPipeEOF); + assert.ok(gotPipeData); +}); diff --git a/wscript b/wscript index c0164b22e9..6d0c108d94 100644 --- a/wscript +++ b/wscript @@ -868,6 +868,7 @@ def build(bld): src/pipe_wrap.cc src/cares_wrap.cc src/stdio_wrap.cc + src/process_wrap.cc """ if sys.platform.startswith("win32"):