mirror of https://github.com/lukechilds/node.git
Urban Hafner
16 years ago
9 changed files with 790 additions and 0 deletions
@ -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); |
|||
} |
@ -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
|
@ -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); |
|||
} |
@ -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); |
|||
} |
Loading…
Reference in new issue