summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUrban Hafner <urban@bettong.net>2009-06-21 16:10:20 +0200
committerUrban Hafner <urban@bettong.net>2009-06-21 16:10:20 +0200
commit3a44efea6915e93a4ae24634fffc0b9a6da8aebb (patch)
tree6a6c5a018e54b56485578530d5049fd5285a551c
parentfc63f840b0ecfe5c2e1549f2d081eeedbd23a1a1 (diff)
parentda03a02a98f67add4438002a3fb08510655f3507 (diff)
downloadnodejs-3a44efea6915e93a4ae24634fffc0b9a6da8aebb.tar.gz
nodejs-3a44efea6915e93a4ae24634fffc0b9a6da8aebb.tar.bz2
nodejs-3a44efea6915e93a4ae24634fffc0b9a6da8aebb.zip
Merge branch 'master' of git://github.com/ry/node
-rw-r--r--src/constants.cc137
-rw-r--r--src/http.h4
-rw-r--r--src/node.cc2
-rw-r--r--src/process.cc486
-rw-r--r--src/process.h56
-rw-r--r--test/test-process-kill.js15
-rw-r--r--test/test-process-simple.js28
-rw-r--r--website/api.html61
-rw-r--r--wscript1
9 files changed, 790 insertions, 0 deletions
diff --git a/src/constants.cc b/src/constants.cc
index 373b8ae4b..449b34f5d 100644
--- a/src/constants.cc
+++ b/src/constants.cc
@@ -4,6 +4,7 @@
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
+#include <signal.h>
using namespace v8;
using namespace node;
@@ -429,5 +430,141 @@ node::DefineConstants (Handle<Object> target)
NODE_DEFINE_CONSTANT(target, EXDEV);
#endif
+#ifdef SIGHUP
+ NODE_DEFINE_CONSTANT(target, SIGHUP);
+#endif
+
+#ifdef SIGINT
+ NODE_DEFINE_CONSTANT(target, SIGINT);
+#endif
+
+#ifdef SIGQUIT
+ NODE_DEFINE_CONSTANT(target, SIGQUIT);
+#endif
+
+#ifdef SIGILL
+ NODE_DEFINE_CONSTANT(target, SIGILL);
+#endif
+
+#ifdef SIGTRAP
+ NODE_DEFINE_CONSTANT(target, SIGTRAP);
+#endif
+
+#ifdef SIGABRT
+ NODE_DEFINE_CONSTANT(target, SIGABRT);
+#endif
+
+#ifdef SIGIOT
+ NODE_DEFINE_CONSTANT(target, SIGIOT);
+#endif
+
+#ifdef SIGBUS
+ NODE_DEFINE_CONSTANT(target, SIGBUS);
+#endif
+
+#ifdef SIGFPE
+ NODE_DEFINE_CONSTANT(target, SIGFPE);
+#endif
+
+#ifdef SIGKILL
+ NODE_DEFINE_CONSTANT(target, SIGKILL);
+#endif
+
+#ifdef SIGUSR1
+ NODE_DEFINE_CONSTANT(target, SIGUSR1);
+#endif
+
+#ifdef SIGSEGV
+ NODE_DEFINE_CONSTANT(target, SIGSEGV);
+#endif
+
+#ifdef SIGUSR2
+ NODE_DEFINE_CONSTANT(target, SIGUSR2);
+#endif
+
+#ifdef SIGPIPE
+ NODE_DEFINE_CONSTANT(target, SIGPIPE);
+#endif
+
+#ifdef SIGALRM
+ NODE_DEFINE_CONSTANT(target, SIGALRM);
+#endif
+
+ NODE_DEFINE_CONSTANT(target, SIGTERM);
+ NODE_DEFINE_CONSTANT(target, SIGCHLD);
+
+#ifdef SIGSTKFLT
+ NODE_DEFINE_CONSTANT(target, SIGSTKFLT);
+#endif
+
+
+#ifdef SIGCONT
+ NODE_DEFINE_CONSTANT(target, SIGCONT);
+#endif
+
+#ifdef SIGSTOP
+ NODE_DEFINE_CONSTANT(target, SIGSTOP);
+#endif
+
+#ifdef SIGTSTP
+ NODE_DEFINE_CONSTANT(target, SIGTSTP);
+#endif
+
+#ifdef SIGTTIN
+ NODE_DEFINE_CONSTANT(target, SIGTTIN);
+#endif
+
+#ifdef SIGTTOU
+ NODE_DEFINE_CONSTANT(target, SIGTTOU);
+#endif
+
+#ifdef SIGURG
+ NODE_DEFINE_CONSTANT(target, SIGURG);
+#endif
+
+#ifdef SIGXCPU
+ NODE_DEFINE_CONSTANT(target, SIGXCPU);
+#endif
+
+#ifdef SIGXFSZ
+ NODE_DEFINE_CONSTANT(target, SIGXFSZ);
+#endif
+
+#ifdef SIGVTALRM
+ NODE_DEFINE_CONSTANT(target, SIGVTALRM);
+#endif
+
+#ifdef SIGPROF
+ NODE_DEFINE_CONSTANT(target, SIGPROF);
+#endif
+
+#ifdef SIGWINCH
+ NODE_DEFINE_CONSTANT(target, SIGWINCH);
+#endif
+
+#ifdef SIGIO
+ NODE_DEFINE_CONSTANT(target, SIGIO);
+#endif
+
+#ifdef SIGPOLL
+ NODE_DEFINE_CONSTANT(target, SIGPOLL);
+#endif
+
+#ifdef SIGLOST
+ NODE_DEFINE_CONSTANT(target, SIGLOST);
+#endif
+
+#ifdef SIGPWR
+ NODE_DEFINE_CONSTANT(target, SIGPWR);
+#endif
+
+#ifdef SIGSYS
+ NODE_DEFINE_CONSTANT(target, SIGSYS);
+#endif
+
+#ifdef SIGUNUSED
+ NODE_DEFINE_CONSTANT(target, SIGUNUSED);
+#endif
+
}
diff --git a/src/http.h b/src/http.h
index ab207a3c5..eb7ecd774 100644
--- a/src/http.h
+++ b/src/http.h
@@ -13,6 +13,8 @@ public:
static v8::Persistent<v8::FunctionTemplate> client_constructor_template;
static v8::Persistent<v8::FunctionTemplate> server_constructor_template;
+
+ virtual size_t size (void) { return sizeof(HTTPConnection); };
protected:
static v8::Handle<v8::Value> NewClient (const v8::Arguments& args);
@@ -41,6 +43,8 @@ public:
static void Initialize (v8::Handle<v8::Object> target);
static v8::Persistent<v8::FunctionTemplate> constructor_template;
+ virtual size_t size (void) { return sizeof(HTTPServer); };
+
protected:
static v8::Handle<v8::Value> New (const v8::Arguments& args);
diff --git a/src/node.cc b/src/node.cc
index 07384838b..6036e158b 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -4,6 +4,7 @@
#include "file.h"
#include "http.h"
#include "timer.h"
+#include "process.h"
#include "constants.h"
#include "natives.h"
@@ -302,6 +303,7 @@ Load (int argc, char *argv[])
NODE_SET_METHOD(node_obj, "reallyExit", node_exit);
Timer::Initialize(node_obj);
+ Process::Initialize(node_obj);
DefineConstants(node_obj);
diff --git a/src/process.cc b/src/process.cc
new file mode 100644
index 000000000..3cdeadc39
--- /dev/null
+++ b/src/process.cc
@@ -0,0 +1,486 @@
+#include "node.h"
+#include "process.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+
+using namespace v8;
+using namespace node;
+
+#define ON_ERROR_SYMBOL String::NewSymbol("onError")
+#define ON_OUTPUT_SYMBOL String::NewSymbol("onOutput")
+#define ON_EXIT_SYMBOL String::NewSymbol("onExit")
+#define PID_SYMBOL String::NewSymbol("pid")
+
+Persistent<FunctionTemplate> Process::constructor_template;
+
+void
+Process::Initialize (Handle<Object> target)
+{
+ HandleScope scope;
+
+ Local<FunctionTemplate> t = FunctionTemplate::New(Process::New);
+ constructor_template = Persistent<FunctionTemplate>::New(t);
+ constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
+
+ NODE_SET_PROTOTYPE_METHOD(constructor_template, "write", Process::Write);
+ NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", Process::Close);
+ NODE_SET_PROTOTYPE_METHOD(constructor_template, "kill", Process::Kill);
+
+ constructor_template->PrototypeTemplate()->SetAccessor(PID_SYMBOL,
+ PIDGetter);
+
+ target->Set(String::NewSymbol("Process"), constructor_template->GetFunction());
+}
+
+Handle<Value>
+Process::New (const Arguments& args)
+{
+ if (args.Length() == 0) return Undefined();
+
+ HandleScope scope;
+
+ String::Utf8Value command(args[0]->ToString());
+
+ Process *p = new Process(args.Holder());
+ ObjectWrap::InformV8ofAllocation(p);
+
+ int r = p->Spawn(*command);
+ if (r != 0) {
+ return ThrowException(String::New("Error spawning"));
+ }
+
+ return args.This();
+}
+
+Handle<Value>
+Process::PIDGetter (Local<String> _, const AccessorInfo& info)
+{
+ Process *process = NODE_UNWRAP(Process, info.This());
+ assert(process);
+
+ HandleScope scope;
+
+ if (process->pid_ == 0) return Null();
+
+ Local<Integer> pid = Integer::New(process->pid_);
+ return scope.Close(pid);
+}
+
+static void
+free_buf (oi_buf *b)
+{
+ V8::AdjustAmountOfExternalAllocatedMemory(-b->len);
+ free(b);
+}
+
+static oi_buf *
+new_buf (size_t size)
+{
+ size_t total = sizeof(oi_buf) + size;
+ void *p = malloc(total);
+ if (p == NULL) return NULL;
+
+ oi_buf *b = static_cast<oi_buf*>(p);
+ b->base = static_cast<char*>(p) + sizeof(oi_buf);
+
+ b->len = size;
+ b->release = free_buf;
+ V8::AdjustAmountOfExternalAllocatedMemory(total);
+
+ return b;
+}
+
+Handle<Value>
+Process::Write (const Arguments& args)
+{
+ HandleScope scope;
+ Process *process = NODE_UNWRAP(Process, args.Holder());
+ assert(process);
+
+ // XXX
+ // A lot of improvement can be made here. First of all we're allocating
+ // oi_bufs for every send which is clearly inefficent - it should use a
+ // memory pool or ring buffer. Of course, expressing binary data as an
+ // array of integers is extremely inefficent. This can improved when v8
+ // bug 270 (http://code.google.com/p/v8/issues/detail?id=270) has been
+ // addressed.
+
+ oi_buf *buf;
+ size_t len;
+
+ if (args[0]->IsString()) {
+ enum encoding enc = ParseEncoding(args[1]);
+ Local<String> s = args[0]->ToString();
+ len = s->Utf8Length();
+ buf = new_buf(len);
+ switch (enc) {
+ case RAW:
+ case ASCII:
+ s->WriteAscii(buf->base, 0, len);
+ break;
+
+ case UTF8:
+ s->WriteUtf8(buf->base, len);
+ break;
+
+ default:
+ assert(0 && "unhandled string encoding");
+ }
+
+ } else if (args[0]->IsArray()) {
+ Handle<Array> array = Handle<Array>::Cast(args[0]);
+ len = array->Length();
+ buf = new_buf(len);
+ for (size_t i = 0; i < len; i++) {
+ Local<Value> int_value = array->Get(Integer::New(i));
+ buf->base[i] = int_value->IntegerValue();
+ }
+
+ } else return ThrowException(String::New("Bad argument"));
+
+ if (process->Write(buf) != 0) {
+ return ThrowException(String::New("Pipe already closed"));
+ }
+
+ return Undefined();
+}
+
+Handle<Value>
+Process::Kill (const Arguments& args)
+{
+ HandleScope scope;
+ Process *process = NODE_UNWRAP(Process, args.Holder());
+ assert(process);
+
+ int sig = SIGTERM;
+ if (args[0]->IsInt32()) sig = args[0]->Int32Value();
+
+ if (process->Kill(sig) != 0) {
+ return ThrowException(String::New("Process already dead"));
+ }
+
+ return Undefined();
+}
+
+Handle<Value>
+Process::Close (const Arguments& args)
+{
+ HandleScope scope;
+ Process *process = NODE_UNWRAP(Process, args.Holder());
+ assert(process);
+
+ if (process->Close() != 0) {
+ return ThrowException(String::New("Pipe already closed."));
+ }
+
+ return Undefined();
+}
+
+Process::Process (Handle<Object> handle)
+ : ObjectWrap(handle)
+{
+ ev_init(&stdout_watcher_, Process::OnOutput);
+ stdout_watcher_.data = this;
+
+ ev_init(&stderr_watcher_, Process::OnOutput);
+ stderr_watcher_.data = this;
+
+ ev_init(&stdin_watcher_, Process::OnWritable);
+ stdin_watcher_.data = this;
+
+ ev_init(&child_watcher_, Process::OnExit);
+ child_watcher_.data = this;
+
+ stdout_pipe_[0] = -1;
+ stdout_pipe_[1] = -1;
+ stderr_pipe_[0] = -1;
+ stderr_pipe_[1] = -1;
+ stdin_pipe_[0] = -1;
+ stdin_pipe_[1] = -1;
+
+ got_close_ = false;
+
+ pid_ = 0;
+
+ oi_queue_init(&out_stream_);
+}
+
+Process::~Process ()
+{
+ Shutdown();
+}
+
+void
+Process::Shutdown ()
+{
+ // Clear the out_stream
+ while (!oi_queue_empty(&out_stream_)) {
+ oi_queue *q = oi_queue_last(&out_stream_);
+ oi_buf *buf = (oi_buf*) oi_queue_data(q, oi_buf, queue);
+ oi_queue_remove(q);
+ if (buf->release) buf->release(buf);
+ }
+
+ if (stdout_pipe_[0] >= 0) close(stdout_pipe_[0]);
+ if (stdout_pipe_[1] >= 0) close(stdout_pipe_[1]);
+
+ if (stderr_pipe_[0] >= 0) close(stderr_pipe_[0]);
+ if (stderr_pipe_[1] >= 0) close(stderr_pipe_[1]);
+
+ if (stdin_pipe_[0] >= 0) close(stdin_pipe_[0]);
+ if (stdin_pipe_[1] >= 0) close(stdin_pipe_[1]);
+
+ stdout_pipe_[0] = -1;
+ stdout_pipe_[1] = -1;
+ stderr_pipe_[0] = -1;
+ stderr_pipe_[1] = -1;
+ stdin_pipe_[0] = -1;
+ stdin_pipe_[1] = -1;
+
+ ev_io_stop(EV_DEFAULT_UC_ &stdout_watcher_);
+ ev_io_stop(EV_DEFAULT_UC_ &stderr_watcher_);
+ ev_io_stop(EV_DEFAULT_UC_ &stdin_watcher_);
+
+ ev_child_stop(EV_DEFAULT_UC_ &child_watcher_);
+ /* XXX Kill the PID? */
+ pid_ = 0;
+
+ Detach();
+}
+
+static inline int
+SetNonBlocking (int fd)
+{
+ int flags = fcntl(fd, F_GETFL, 0);
+ int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ if (r != 0) {
+ perror("SetNonBlocking()");
+ }
+ return r;
+}
+
+int
+Process::Spawn (const char *command)
+{
+ assert(pid_ == 0);
+ assert(stdout_pipe_[0] == -1);
+ assert(stdout_pipe_[1] == -1);
+ assert(stderr_pipe_[0] == -1);
+ assert(stderr_pipe_[1] == -1);
+ assert(stdin_pipe_[0] == -1);
+ assert(stdin_pipe_[1] == -1);
+
+ /* An implementation of popen(), basically */
+ if (pipe(stdout_pipe_) < 0) {
+ perror("pipe()");
+ return -1;
+ }
+
+ if (pipe(stderr_pipe_) < 0) {
+ perror("pipe()");
+ return -2;
+ }
+
+ if (pipe(stdin_pipe_) < 0) {
+ perror("pipe()");
+ return -3;
+ }
+
+ switch (pid_ = vfork()) {
+ case -1: // Error.
+ Shutdown();
+ return -4;
+
+ case 0: // Child.
+ close(stdout_pipe_[0]); // close read end
+ dup2(stdout_pipe_[1], STDOUT_FILENO);
+
+ close(stderr_pipe_[0]); // close read end
+ dup2(stderr_pipe_[1], STDERR_FILENO);
+
+ close(stdin_pipe_[1]); // close write end
+ dup2(stdin_pipe_[0], STDIN_FILENO);
+
+ execl("/bin/sh", "sh", "-c", command, (char *)NULL);
+ //execl(_PATH_BSHELL, "sh", "-c", program, (char *)NULL);
+ _exit(127);
+ }
+
+ // Parent.
+
+ ev_child_set(&child_watcher_, pid_, 0);
+ ev_child_start(EV_DEFAULT_UC_ &child_watcher_);
+
+ SetNonBlocking(stdout_pipe_[0]);
+ ev_io_set(&stdout_watcher_, stdout_pipe_[0], EV_READ);
+ ev_io_start(EV_DEFAULT_UC_ &stdout_watcher_);
+ close(stdout_pipe_[1]); // close write end
+ stdout_pipe_[1] = -1;
+
+ SetNonBlocking(stderr_pipe_[0]);
+ ev_io_set(&stderr_watcher_, stderr_pipe_[0], EV_READ);
+ ev_io_start(EV_DEFAULT_UC_ &stderr_watcher_);
+ close(stderr_pipe_[1]); // close write end
+ stderr_pipe_[1] = -1;
+
+ SetNonBlocking(stdin_pipe_[1]);
+ ev_io_set(&stdin_watcher_, stdin_pipe_[1], EV_WRITE);
+ ev_io_start(EV_DEFAULT_UC_ &stdin_watcher_);
+ close(stdin_pipe_[0]); // close read end
+ stdin_pipe_[0] = -1;
+
+ Attach();
+
+ return 0;
+}
+
+void
+Process::OnOutput (EV_P_ ev_io *watcher, int revents)
+{
+ int r;
+ char buf[16*1024];
+ size_t buf_size = 16*1024;
+
+ Process *process = static_cast<Process*>(watcher->data);
+
+ bool is_stdout = (&process->stdout_watcher_ == watcher);
+ int fd = is_stdout ? process->stdout_pipe_[0] : process->stderr_pipe_[0];
+
+ assert(revents == EV_READ);
+ assert(fd >= 0);
+
+ HandleScope scope;
+ Handle<Value> callback_v =
+ process->handle_->Get(is_stdout ? ON_OUTPUT_SYMBOL : ON_ERROR_SYMBOL);
+ Handle<Function> callback;
+ if (callback_v->IsFunction()) {
+ callback = Handle<Function>::Cast(callback_v);
+ }
+ Handle<Value> argv[1];
+
+ for (;;) {
+ r = read(fd, buf, buf_size);
+
+ if (r < 0) {
+ if (errno != EAGAIN) perror("IPC pipe read error");
+ break;
+ }
+
+ if (!callback.IsEmpty()) {
+ if (r == 0) {
+ argv[0] = Null();
+ } else {
+ // TODO multiple encodings
+ argv[0] = String::New((const char*)buf, r);
+ }
+
+ TryCatch try_catch;
+ callback->Call(process->handle_, 1, argv);
+ if (try_catch.HasCaught()) {
+ FatalException(try_catch);
+ return;
+ }
+ }
+
+ if (r == 0) {
+ ev_io_stop(EV_DEFAULT_UC_ watcher);
+ break;
+ }
+ }
+}
+
+void
+Process::OnWritable (EV_P_ ev_io *watcher, int revents)
+{
+ Process *process = static_cast<Process*>(watcher->data);
+ int sent;
+
+ assert(revents == EV_WRITE);
+ assert(process->stdin_pipe_[1] >= 0);
+
+ while (!oi_queue_empty(&process->out_stream_)) {
+ oi_queue *q = oi_queue_last(&process->out_stream_);
+ oi_buf *to_write = (oi_buf*) oi_queue_data(q, oi_buf, queue);
+
+ sent = write( process->stdin_pipe_[1]
+ , to_write->base + to_write->written
+ , to_write->len - to_write->written
+ );
+ if (sent < 0) {
+ if (errno == EAGAIN) break;
+ perror("IPC pipe write error");
+ break;
+ }
+
+ to_write->written += sent;
+
+ if (to_write->written == to_write->len) {
+ oi_queue_remove(q);
+ if (to_write->release) to_write->release(to_write);
+ }
+ }
+
+ if (oi_queue_empty(&process->out_stream_)) {
+ ev_io_stop(EV_DEFAULT_UC_ &process->stdin_watcher_);
+ if (process->got_close_) {
+ close(process->stdin_pipe_[1]);
+ process->stdin_pipe_[1] = -1;
+ }
+ }
+}
+
+void
+Process::OnExit (EV_P_ ev_child *watcher, int revents)
+{
+ ev_child_stop(EV_A_ watcher);
+ Process *process = static_cast<Process*>(watcher->data);
+
+ assert(revents == EV_CHILD);
+ assert(process->pid_ == watcher->rpid);
+ assert(&process->child_watcher_ == watcher);
+
+ // Call onExit ( watcher->rstatus )
+ HandleScope scope;
+ Handle<Value> callback_v = process->handle_->Get(ON_EXIT_SYMBOL);
+
+ if (callback_v->IsFunction()) {
+ Handle<Function> callback = Handle<Function>::Cast(callback_v);
+ TryCatch try_catch;
+ Handle<Value> argv[1] = { Integer::New(watcher->rstatus) };
+ callback->Call(process->handle_, 1, argv);
+ if (try_catch.HasCaught()) FatalException(try_catch);
+ }
+ process->Shutdown();
+ process->Detach();
+}
+
+int
+Process::Write (oi_buf *buf)
+{
+ if (stdin_pipe_[1] < 0 || got_close_) return -1;
+ oi_queue_insert_head(&out_stream_, &buf->queue);
+ buf->written = 0;
+ ev_io_start(EV_DEFAULT_UC_ &stdin_watcher_);
+ return 0;
+}
+
+int
+Process::Close ()
+{
+ if (stdin_pipe_[1] < 0 || got_close_) return -1;
+ got_close_ = true;
+ ev_io_start(EV_DEFAULT_UC_ &stdin_watcher_);
+ return 0;
+}
+
+int
+Process::Kill (int sig)
+{
+ if (pid_ == 0) return -1;
+ return kill(pid_, sig);
+}
diff --git a/src/process.h b/src/process.h
new file mode 100644
index 000000000..38138b87c
--- /dev/null
+++ b/src/process.h
@@ -0,0 +1,56 @@
+#ifndef node_process_h
+#define node_process_h
+
+#include "node.h"
+#include <v8.h>
+#include <ev.h>
+#include <oi_socket.h>
+
+namespace node {
+
+class Process : ObjectWrap {
+ public:
+ static void Initialize (v8::Handle<v8::Object> target);
+ virtual size_t size (void) { return sizeof(Process); }
+
+ protected:
+ static v8::Persistent<v8::FunctionTemplate> constructor_template;
+ static v8::Handle<v8::Value> New (const v8::Arguments& args);
+ static v8::Handle<v8::Value> Write (const v8::Arguments& args);
+ static v8::Handle<v8::Value> Close (const v8::Arguments& args);
+ static v8::Handle<v8::Value> Kill (const v8::Arguments& args);
+ static v8::Handle<v8::Value> PIDGetter (v8::Local<v8::String> _, const v8::AccessorInfo& info);
+
+ Process(v8::Handle<v8::Object> handle);
+ ~Process();
+
+ void Shutdown ();
+ int Spawn (const char *command);
+ int Write (oi_buf *buf);
+ int Close ();
+ int Kill (int sig);
+
+ private:
+ static void OnOutput (EV_P_ ev_io *watcher, int revents);
+ static void OnError (EV_P_ ev_io *watcher, int revents);
+ static void OnWritable (EV_P_ ev_io *watcher, int revents);
+ static void OnExit (EV_P_ ev_child *watcher, int revents);
+
+ ev_io stdout_watcher_;
+ ev_io stderr_watcher_;
+ ev_io stdin_watcher_;
+ ev_child child_watcher_;
+
+ int stdout_pipe_[2];
+ int stderr_pipe_[2];
+ int stdin_pipe_[2];
+
+ pid_t pid_;
+
+ bool got_close_;
+
+ oi_queue out_stream_;
+};
+
+} // namespace node
+#endif // node_process_h
diff --git a/test/test-process-kill.js b/test/test-process-kill.js
new file mode 100644
index 000000000..72e7e1090
--- /dev/null
+++ b/test/test-process-kill.js
@@ -0,0 +1,15 @@
+include("mjsunit.js");
+
+var cat = new node.Process("cat");
+
+var exit_status = -1;
+
+cat.onOutput = function (chunk) { assertEquals(null, chunk); };
+cat.onError = function (chunk) { assertEquals(null, chunk); };
+cat.onExit = function (status) { exit_status = status; };
+
+cat.kill();
+
+function onExit () {
+ assertTrue(exit_status > 0);
+}
diff --git a/test/test-process-simple.js b/test/test-process-simple.js
new file mode 100644
index 000000000..f3a761abd
--- /dev/null
+++ b/test/test-process-simple.js
@@ -0,0 +1,28 @@
+include("mjsunit.js");
+
+var cat = new node.Process("cat");
+
+var response = "";
+var exit_status = -1;
+
+cat.onOutput = function (chunk) {
+ if (chunk) {
+ response += chunk;
+ if (response === "hello world") cat.close();
+ }
+};
+cat.onError = function (chunk) {
+ assertEquals(null, chunk);
+};
+cat.onExit = function (status) { exit_status = status; };
+
+function onLoad () {
+ cat.write("hello");
+ cat.write(" ");
+ cat.write("world");
+}
+
+function onExit () {
+ assertEquals(0, exit_status);
+ assertEquals("hello world", response);
+}
diff --git a/website/api.html b/website/api.html
index 59bb15a4a..3b3bcdbaf 100644
--- a/website/api.html
+++ b/website/api.html
@@ -13,6 +13,7 @@
<div id="toc">
<ol>
<li><a href="#timers">Timers</a></li>
+ <li><a href="#processes">Processes</a></li>
<li>
<a href="#files">File I/O</a>
<ol>
@@ -130,6 +131,66 @@
<dd>Stops a interval from triggering.</dd>
</dl>
+ <h2 id="processes">Processes and IPC</h2>
+
+ <p>
+ Node provides a tridirectional <code>popen(3)</code> facility.
+ It is possible to stream data through the child's <code>stdin</code>,
+ <code>stdout</code>, and <code>stderr</code> in a fully non-blocking
+ way.
+ </p>
+
+ <dl>
+ <dt><code>new node.Process(command)</code></dt>
+ <dd>Launches a new process with the given <code>command</code>. For example:
+ <pre>var ls = new Process("ls -lh /usr");</pre>
+ </dd>
+
+ <dt><code>process.pid</code></dt>
+ <dd>The PID of the child process.</dd>
+
+ <dt><code>process.onOutput = function (chunk) { };</code></dt>
+ <dd>A callback to receive output from the process's <code>stdout</code>.
+ At the moment the received data is always a string and utf8 encoded.
+ (More encodings will be supported in the future.)
+
+ <p>If the process closes it's <code>stdout</code>, this callback will
+ be issued with <code>null</code> as an argument. Be prepared for this
+ possibility.
+ </dd>
+
+ <dt><code>process.onError = function (chunk) { };</code></dt>
+ <dd>A callback to receive output from the process's <code>stderr</code>.
+ At the moment the received data is always a string and utf8 encoded.
+ (More encodings will be supported in the future.)
+
+ <p>If the process closes it's <code>stderr</code>, this callback will
+ be issued with <code>null</code> as an argument. Be prepared for this
+ possibility.
+ </dd>
+
+ <dt><code>process.onExit = function (exit_code) { };</code></dt>
+ <dd>A callback which is called when the sub-process exits. The argument
+ is the exit status of the child.
+ </dd>
+
+ <dt><code>process.write(data, encoding="ascii");</code></dt>
+ <dd>Write data to the child process's <code>stdin</code>. The second
+ argument is optional and specifies the encoding: possible values are
+ <code>"utf8"</code>, <code>"ascii"</code>, and <code>"raw"</code>.
+ </dd>
+
+ <dt><code>process.close();</code></dt>
+ <dd>Closes the processes <code>stdin</code> stream.</dd>
+
+ <dt><code>process.kill(signal=node.SIGTERM);</code></dt>
+ <dd>Kills the child process with the given signal. If no argument is
+ given, the process will be sent <code>node.SIGTERM</code>. The standard
+ POSIX signals are defined under the <code>node</code> namespace (e.g.
+ <code>node.SIGINT</code>, <code>node.SIGUSR1</code>).
+ </dd>
+ </dl>
+
<h2 id="files"><code>node.fs</code></h2>
<p>
diff --git a/wscript b/wscript
index 8d49a8811..ce85c400b 100644
--- a/wscript
+++ b/wscript
@@ -158,6 +158,7 @@ def build(bld):
src/net.cc
src/file.cc
src/timer.cc
+ src/process.cc
src/constants.cc
"""
node.includes = """