blob: 30b1a6878d46df102a413bdb411993992d957b3c [file] [log] [blame]
#include "node.h"
#include "child_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 PID_SYMBOL String::NewSymbol("pid")
Persistent<FunctionTemplate> ChildProcess::constructor_template;
void
ChildProcess::Initialize (Handle<Object> target)
{
HandleScope scope;
Local<FunctionTemplate> t = FunctionTemplate::New(ChildProcess::New);
constructor_template = Persistent<FunctionTemplate>::New(t);
constructor_template->Inherit(EventEmitter::constructor_template);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "spawn", ChildProcess::Spawn);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "write", ChildProcess::Write);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", ChildProcess::Close);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "kill", ChildProcess::Kill);
target->Set(String::NewSymbol("ChildProcess"), constructor_template->GetFunction());
}
Handle<Value>
ChildProcess::New (const Arguments& args)
{
HandleScope scope;
ChildProcess *p = new ChildProcess();
p->Wrap(args.Holder());
return args.This();
}
Handle<Value>
ChildProcess::Spawn (const Arguments& args)
{
if (args.Length() == 0 || !args[0]->IsString()) {
return ThrowException(String::New("Bad argument."));
}
HandleScope scope;
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
String::Utf8Value command(args[0]->ToString());
int r = child->Spawn(*command);
if (r != 0) {
return ThrowException(String::New("Error spawning"));
}
child->handle_->Set(PID_SYMBOL, Integer::New(child->pid_));
return Undefined();
}
Handle<Value>
ChildProcess::Write (const Arguments& args)
{
HandleScope scope;
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
assert(child);
ssize_t len;
Local<String> string;
Local<Array> array;
if (args[0]->IsArray()) {
array = Local<Array>::Cast(args[0]);
len = array->Length();
} else {
string = args[0]->ToString();
len = string->Utf8Length();
}
char buf[len];
if (args[0]->IsArray()) {
for (ssize_t index = 0; index < len; index++) {
Local<Value> int_value = array->Get(Integer::New(index));
buf[index] = int_value->IntegerValue();
}
} else {
switch (ParseEncoding(args[1])) {
case RAW:
case ASCII:
string->WriteAscii(buf, 0, len);
break;
case UTF8:
string->WriteUtf8(buf, len);
break;
default:
return ThrowException(String::New("Unknown encoding."));
}
}
return child->Write(buf, len) == 0 ? True() : False();
}
Handle<Value>
ChildProcess::Kill (const Arguments& args)
{
HandleScope scope;
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
assert(child);
int sig = SIGTERM;
if (args[0]->IsInt32()) sig = args[0]->Int32Value();
if (child->Kill(sig) != 0) {
return ThrowException(String::New("ChildProcess already dead"));
}
return Undefined();
}
Handle<Value>
ChildProcess::Close (const Arguments& args)
{
HandleScope scope;
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
assert(child);
return child->Close() == 0 ? True() : False();
}
void
ChildProcess::reader_closed (evcom_reader *r)
{
ChildProcess *child = static_cast<ChildProcess*> (r->data);
if (r == &child->stdout_reader_) {
child->stdout_fd_ = -1;
} else {
assert(r == &child->stderr_reader_);
child->stderr_fd_ = -1;
}
evcom_reader_detach(r);
child->MaybeShutdown();
}
void
ChildProcess::stdin_closed (evcom_writer *w)
{
ChildProcess *child = static_cast<ChildProcess*> (w->data);
assert(w == &child->stdin_writer_);
child->stdin_fd_ = -1;
evcom_writer_detach(w);
child->MaybeShutdown();
}
void
ChildProcess::on_read (evcom_reader *r, const void *buf, size_t len)
{
ChildProcess *child = static_cast<ChildProcess*> (r->data);
HandleScope scope;
bool isSTDOUT = (r == &child->stdout_reader_);
Local<Value> argv[1];
enum encoding encoding = isSTDOUT ? child->stdout_encoding_ : child->stderr_encoding_;
if (len == 0) {
argv[0] = Local<Value>::New(Null());
} else if (encoding == RAW) {
// raw encoding
Local<Array> array = Array::New(len);
for (size_t i = 0; i < len; i++) {
unsigned char val = static_cast<const unsigned char*>(buf)[i];
array->Set(Integer::New(i), Integer::New(val));
}
argv[0] = array;
} else {
// utf8 or ascii encoding
argv[0] = String::New((const char*)buf, len);
}
child->Emit(isSTDOUT ? "output" : "error", 1, argv);
child->MaybeShutdown();
}
ChildProcess::ChildProcess ()
: EventEmitter()
{
evcom_reader_init(&stdout_reader_);
stdout_reader_.data = this;
stdout_reader_.on_read = on_read;
stdout_reader_.on_close = reader_closed;
evcom_reader_init(&stderr_reader_);
stderr_reader_.data = this;
stderr_reader_.on_read = on_read;
stderr_reader_.on_close = reader_closed;
evcom_writer_init(&stdin_writer_);
stdin_writer_.data = this;
stdin_writer_.on_close = stdin_closed;
ev_init(&child_watcher_, ChildProcess::OnCHLD);
child_watcher_.data = this;
stdout_fd_ = -1;
stderr_fd_ = -1;
stdin_fd_ = -1;
stdout_encoding_ = UTF8;
stderr_encoding_ = UTF8;
got_chld_ = false;
exit_code_ = 0;
pid_ = 0;
}
ChildProcess::~ChildProcess ()
{
Shutdown();
}
void
ChildProcess::Shutdown ()
{
if (stdin_fd_ >= 0) {
evcom_writer_close(&stdin_writer_);
}
if (stdin_fd_ >= 0) close(stdin_fd_);
if (stdout_fd_ >= 0) close(stdout_fd_);
if (stderr_fd_ >= 0) close(stderr_fd_);
stdin_fd_ = -1;
stdout_fd_ = -1;
stderr_fd_ = -1;
evcom_writer_detach(&stdin_writer_);
evcom_reader_detach(&stdout_reader_);
evcom_reader_detach(&stderr_reader_);
ev_child_stop(EV_DEFAULT_UC_ &child_watcher_);
/* XXX Kill the PID? */
pid_ = 0;
}
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
ChildProcess::Spawn (const char *command)
{
assert(pid_ == 0);
assert(stdout_fd_ == -1);
assert(stderr_fd_ == -1);
assert(stdin_fd_ == -1);
int stdout_pipe[2], stdin_pipe[2], stderr_pipe[2];
/* 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);
_exit(127);
}
// Parent.
ev_child_set(&child_watcher_, pid_, 0);
ev_child_start(EV_DEFAULT_UC_ &child_watcher_);
close(stdout_pipe[1]);
stdout_fd_ = stdout_pipe[0];
SetNonBlocking(stdout_fd_);
close(stderr_pipe[1]);
stderr_fd_ = stderr_pipe[0];
SetNonBlocking(stderr_fd_);
close(stdin_pipe[0]);
stdin_fd_ = stdin_pipe[1];
SetNonBlocking(stdin_fd_);
evcom_reader_set(&stdout_reader_, stdout_fd_);
evcom_reader_attach(EV_DEFAULT_UC_ &stdout_reader_);
evcom_reader_set(&stderr_reader_, stderr_fd_);
evcom_reader_attach(EV_DEFAULT_UC_ &stderr_reader_);
evcom_writer_set(&stdin_writer_, stdin_fd_);
evcom_writer_attach(EV_DEFAULT_UC_ &stdin_writer_);
Attach();
return 0;
}
void
ChildProcess::OnCHLD (EV_P_ ev_child *watcher, int revents)
{
ev_child_stop(EV_A_ watcher);
ChildProcess *child = static_cast<ChildProcess*>(watcher->data);
assert(revents == EV_CHILD);
assert(child->pid_ == watcher->rpid);
assert(&child->child_watcher_ == watcher);
child->got_chld_ = true;
child->exit_code_ = watcher->rstatus;
if (child->stdin_fd_ >= 0) evcom_writer_close(&child->stdin_writer_);
child->MaybeShutdown();
}
int
ChildProcess::Write (const char *str, size_t len)
{
if (stdin_fd_ < 0 || got_chld_) return -1;
evcom_writer_write(&stdin_writer_, str, len);
return 0;
}
int
ChildProcess::Close (void)
{
if (stdin_fd_ < 0 || got_chld_) return -1;
evcom_writer_close(EV_DEFAULT_UC_ &stdin_writer_);
return 0;
}
int
ChildProcess::Kill (int sig)
{
if (got_chld_ || pid_ == 0) return -1;
return kill(pid_, sig);
}
void
ChildProcess::MaybeShutdown (void)
{
if (stdout_fd_ < 0 && stderr_fd_ < 0 && got_chld_) {
HandleScope scope;
Handle<Value> argv[1] = { Integer::New(exit_code_) };
Emit("exit", 1, argv);
Shutdown();
Detach();
}
}