mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
390 lines
10 KiB
390 lines
10 KiB
16 years ago
|
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
|
||
15 years ago
|
#include <node_child_process.h>
|
||
16 years ago
|
|
||
|
#include <assert.h>
|
||
15 years ago
|
#include <string.h>
|
||
16 years ago
|
#include <stdlib.h>
|
||
|
#include <errno.h>
|
||
16 years ago
|
#include <unistd.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <sys/types.h>
|
||
|
|
||
16 years ago
|
namespace node {
|
||
|
|
||
16 years ago
|
using namespace v8;
|
||
|
|
||
16 years ago
|
#define PID_SYMBOL String::NewSymbol("pid")
|
||
16 years ago
|
|
||
16 years ago
|
Persistent<FunctionTemplate> ChildProcess::constructor_template;
|
||
16 years ago
|
|
||
16 years ago
|
void ChildProcess::Initialize(Handle<Object> target) {
|
||
16 years ago
|
HandleScope scope;
|
||
|
|
||
16 years ago
|
Local<FunctionTemplate> t = FunctionTemplate::New(ChildProcess::New);
|
||
16 years ago
|
constructor_template = Persistent<FunctionTemplate>::New(t);
|
||
16 years ago
|
constructor_template->Inherit(EventEmitter::constructor_template);
|
||
16 years ago
|
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
|
||
16 years ago
|
constructor_template->SetClassName(String::NewSymbol("ChildProcess"));
|
||
16 years ago
|
|
||
16 years ago
|
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);
|
||
16 years ago
|
|
||
16 years ago
|
target->Set(String::NewSymbol("ChildProcess"),
|
||
|
constructor_template->GetFunction());
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
Handle<Value> ChildProcess::New(const Arguments& args) {
|
||
16 years ago
|
HandleScope scope;
|
||
|
|
||
16 years ago
|
ChildProcess *p = new ChildProcess();
|
||
16 years ago
|
p->Wrap(args.Holder());
|
||
16 years ago
|
|
||
16 years ago
|
return args.This();
|
||
|
}
|
||
|
|
||
15 years ago
|
// This is an internal function. The third argument should be an array
|
||
|
// of key value pairs seperated with '='.
|
||
16 years ago
|
Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
||
15 years ago
|
HandleScope scope;
|
||
|
|
||
|
if ( args.Length() != 3
|
||
|
|| !args[0]->IsString()
|
||
|
|| !args[1]->IsArray()
|
||
|
|| !args[2]->IsArray()
|
||
|
)
|
||
|
{
|
||
16 years ago
|
return ThrowException(Exception::Error(String::New("Bad argument.")));
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
|
||
16 years ago
|
|
||
15 years ago
|
String::Utf8Value file(args[0]->ToString());
|
||
|
|
||
|
int i;
|
||
|
|
||
|
// Copy second argument args[1] into a c-string array called argv.
|
||
|
// The array must be null terminated, and the first element must be
|
||
|
// the name of the executable -- hence the complication.
|
||
|
Local<Array> argv_handle = Local<Array>::Cast(args[1]);
|
||
|
int argc = argv_handle->Length();
|
||
|
int argv_length = argc + 1 + 1;
|
||
|
char **argv = new char*[argv_length]; // heap allocated to detect errors
|
||
|
argv[0] = strdup(*file); // + 1 for file
|
||
|
argv[argv_length-1] = NULL; // + 1 for NULL;
|
||
|
for (i = 0; i < argc; i++) {
|
||
|
String::Utf8Value arg(argv_handle->Get(Integer::New(i))->ToString());
|
||
|
argv[i+1] = strdup(*arg);
|
||
|
}
|
||
|
|
||
|
// Copy third argument, args[2], into a c-string array called env.
|
||
|
Local<Array> env_handle = Local<Array>::Cast(args[2]);
|
||
|
int envc = env_handle->Length();
|
||
|
char **env = new char*[envc+1]; // heap allocated to detect errors
|
||
|
env[envc] = NULL;
|
||
|
for (int i = 0; i < envc; i++) {
|
||
|
String::Utf8Value pair(env_handle->Get(Integer::New(i))->ToString());
|
||
|
env[i] = strdup(*pair);
|
||
|
}
|
||
|
|
||
|
int r = child->Spawn(argv[0], argv, env);
|
||
|
|
||
|
for (i = 0; i < argv_length; i++) free(argv[i]);
|
||
|
delete [] argv;
|
||
|
|
||
|
for (i = 0; i < envc; i++) free(env[i]);
|
||
|
delete [] env;
|
||
16 years ago
|
|
||
16 years ago
|
if (r != 0) {
|
||
16 years ago
|
return ThrowException(Exception::Error(String::New("Error spawning")));
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
child->handle_->Set(PID_SYMBOL, Integer::New(child->pid_));
|
||
16 years ago
|
|
||
16 years ago
|
return Undefined();
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
Handle<Value> ChildProcess::Write(const Arguments& args) {
|
||
16 years ago
|
HandleScope scope;
|
||
16 years ago
|
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
|
||
|
assert(child);
|
||
16 years ago
|
|
||
16 years ago
|
enum encoding enc = ParseEncoding(args[1]);
|
||
|
ssize_t len = DecodeBytes(args[0], enc);
|
||
16 years ago
|
|
||
16 years ago
|
if (len < 0) {
|
||
|
Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
|
||
|
return ThrowException(exception);
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
char * buf = new char[len];
|
||
16 years ago
|
ssize_t written = DecodeWrite(buf, len, args[0], enc);
|
||
|
assert(written == len);
|
||
16 years ago
|
int r = child->Write(buf, len);
|
||
16 years ago
|
delete [] buf;
|
||
16 years ago
|
|
||
16 years ago
|
return r == 0 ? True() : False();
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
Handle<Value> ChildProcess::Kill(const Arguments& args) {
|
||
16 years ago
|
HandleScope scope;
|
||
16 years ago
|
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
|
||
|
assert(child);
|
||
16 years ago
|
|
||
|
int sig = SIGTERM;
|
||
15 years ago
|
|
||
|
if (args.Length() > 0) {
|
||
|
if (args[0]->IsNumber()) {
|
||
|
sig = args[0]->Int32Value();
|
||
|
} else if (args[0]->IsString()) {
|
||
|
Local<String> signame = args[0]->ToString();
|
||
|
Local<Object> process = Context::GetCurrent()->Global();
|
||
|
Local<Object> node_obj = process->Get(String::NewSymbol("node"))->ToObject();
|
||
|
|
||
|
Local<Value> sig_v = node_obj->Get(signame);
|
||
|
if (!sig_v->IsNumber()) {
|
||
|
return ThrowException(Exception::Error(String::New("Unknown signal")));
|
||
|
}
|
||
|
sig = sig_v->Int32Value();
|
||
|
}
|
||
|
}
|
||
16 years ago
|
|
||
16 years ago
|
if (child->Kill(sig) != 0) {
|
||
15 years ago
|
return ThrowException(Exception::Error(String::New(strerror(errno))));
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
return Undefined();
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
Handle<Value> ChildProcess::Close(const Arguments& args) {
|
||
16 years ago
|
HandleScope scope;
|
||
16 years ago
|
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
|
||
|
assert(child);
|
||
|
return child->Close() == 0 ? True() : False();
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
void ChildProcess::reader_closed(evcom_reader *r) {
|
||
|
ChildProcess *child = static_cast<ChildProcess*>(r->data);
|
||
16 years ago
|
if (r == &child->stdout_reader_) {
|
||
|
child->stdout_fd_ = -1;
|
||
16 years ago
|
} else {
|
||
16 years ago
|
assert(r == &child->stderr_reader_);
|
||
|
child->stderr_fd_ = -1;
|
||
16 years ago
|
}
|
||
|
evcom_reader_detach(r);
|
||
16 years ago
|
child->MaybeShutdown();
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
void ChildProcess::stdin_closed(evcom_writer *w) {
|
||
|
ChildProcess *child = static_cast<ChildProcess*>(w->data);
|
||
16 years ago
|
assert(w == &child->stdin_writer_);
|
||
|
child->stdin_fd_ = -1;
|
||
16 years ago
|
evcom_writer_detach(w);
|
||
16 years ago
|
child->MaybeShutdown();
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
void ChildProcess::on_read(evcom_reader *r, const void *buf, size_t len) {
|
||
|
ChildProcess *child = static_cast<ChildProcess*>(r->data);
|
||
16 years ago
|
HandleScope scope;
|
||
|
|
||
16 years ago
|
bool isSTDOUT = (r == &child->stdout_reader_);
|
||
16 years ago
|
enum encoding encoding = isSTDOUT ?
|
||
|
child->stdout_encoding_ : child->stderr_encoding_;
|
||
16 years ago
|
|
||
16 years ago
|
Local<Value> data = Encode(buf, len, encoding);
|
||
|
child->Emit(isSTDOUT ? "output" : "error", 1, &data);
|
||
16 years ago
|
child->MaybeShutdown();
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
ChildProcess::ChildProcess() : EventEmitter() {
|
||
16 years ago
|
evcom_reader_init(&stdout_reader_);
|
||
|
stdout_reader_.data = this;
|
||
|
stdout_reader_.on_read = on_read;
|
||
|
stdout_reader_.on_close = reader_closed;
|
||
16 years ago
|
|
||
16 years ago
|
evcom_reader_init(&stderr_reader_);
|
||
|
stderr_reader_.data = this;
|
||
|
stderr_reader_.on_read = on_read;
|
||
|
stderr_reader_.on_close = reader_closed;
|
||
16 years ago
|
|
||
16 years ago
|
evcom_writer_init(&stdin_writer_);
|
||
|
stdin_writer_.data = this;
|
||
|
stdin_writer_.on_close = stdin_closed;
|
||
16 years ago
|
|
||
16 years ago
|
ev_init(&child_watcher_, ChildProcess::OnCHLD);
|
||
16 years ago
|
child_watcher_.data = this;
|
||
|
|
||
16 years ago
|
stdout_fd_ = -1;
|
||
|
stderr_fd_ = -1;
|
||
|
stdin_fd_ = -1;
|
||
|
|
||
|
stdout_encoding_ = UTF8;
|
||
|
stderr_encoding_ = UTF8;
|
||
16 years ago
|
|
||
16 years ago
|
got_chld_ = false;
|
||
|
exit_code_ = 0;
|
||
16 years ago
|
|
||
16 years ago
|
pid_ = 0;
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
ChildProcess::~ChildProcess() {
|
||
16 years ago
|
Shutdown();
|
||
|
}
|
||
|
|
||
16 years ago
|
void ChildProcess::Shutdown() {
|
||
16 years ago
|
if (stdin_fd_ >= 0) {
|
||
|
evcom_writer_close(&stdin_writer_);
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
if (stdin_fd_ >= 0) close(stdin_fd_);
|
||
|
if (stdout_fd_ >= 0) close(stdout_fd_);
|
||
|
if (stderr_fd_ >= 0) close(stderr_fd_);
|
||
16 years ago
|
|
||
16 years ago
|
stdin_fd_ = -1;
|
||
|
stdout_fd_ = -1;
|
||
|
stderr_fd_ = -1;
|
||
16 years ago
|
|
||
16 years ago
|
evcom_writer_detach(&stdin_writer_);
|
||
|
evcom_reader_detach(&stdout_reader_);
|
||
|
evcom_reader_detach(&stderr_reader_);
|
||
16 years ago
|
|
||
|
ev_child_stop(EV_DEFAULT_UC_ &child_watcher_);
|
||
16 years ago
|
|
||
16 years ago
|
/* XXX Kill the PID? */
|
||
|
pid_ = 0;
|
||
|
}
|
||
|
|
||
16 years ago
|
static inline int SetNonBlocking(int fd) {
|
||
16 years ago
|
int flags = fcntl(fd, F_GETFL, 0);
|
||
|
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||
|
if (r != 0) {
|
||
|
perror("SetNonBlocking()");
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
15 years ago
|
// Note that args[0] must be the same as the "file" param. This is an
|
||
|
// execvp() requirement.
|
||
|
int ChildProcess::Spawn(const char *file, char *const args[], char *const env[]) {
|
||
16 years ago
|
assert(pid_ == 0);
|
||
16 years ago
|
assert(stdout_fd_ == -1);
|
||
|
assert(stderr_fd_ == -1);
|
||
|
assert(stdin_fd_ == -1);
|
||
|
|
||
|
int stdout_pipe[2], stdin_pipe[2], stderr_pipe[2];
|
||
16 years ago
|
|
||
|
/* An implementation of popen(), basically */
|
||
16 years ago
|
if (pipe(stdout_pipe) < 0) {
|
||
16 years ago
|
perror("pipe()");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
16 years ago
|
if (pipe(stderr_pipe) < 0) {
|
||
16 years ago
|
perror("pipe()");
|
||
|
return -2;
|
||
|
}
|
||
|
|
||
16 years ago
|
if (pipe(stdin_pipe) < 0) {
|
||
16 years ago
|
perror("pipe()");
|
||
|
return -3;
|
||
|
}
|
||
|
|
||
|
switch (pid_ = vfork()) {
|
||
16 years ago
|
case -1: // Error.
|
||
16 years ago
|
Shutdown();
|
||
|
return -4;
|
||
|
|
||
16 years ago
|
case 0: // Child.
|
||
|
close(stdout_pipe[0]); // close read end
|
||
16 years ago
|
dup2(stdout_pipe[1], STDOUT_FILENO);
|
||
16 years ago
|
|
||
16 years ago
|
close(stderr_pipe[0]); // close read end
|
||
16 years ago
|
dup2(stderr_pipe[1], STDERR_FILENO);
|
||
16 years ago
|
|
||
16 years ago
|
close(stdin_pipe[1]); // close write end
|
||
16 years ago
|
dup2(stdin_pipe[0], STDIN_FILENO);
|
||
16 years ago
|
|
||
15 years ago
|
execvp(file, args);
|
||
|
perror("execvp()");
|
||
16 years ago
|
_exit(127);
|
||
15 years ago
|
|
||
|
// TODO search PATH and use: execve(file, argv, env);
|
||
16 years ago
|
}
|
||
|
|
||
|
// Parent.
|
||
|
|
||
|
ev_child_set(&child_watcher_, pid_, 0);
|
||
|
ev_child_start(EV_DEFAULT_UC_ &child_watcher_);
|
||
|
|
||
16 years ago
|
close(stdout_pipe[1]);
|
||
|
stdout_fd_ = stdout_pipe[0];
|
||
|
SetNonBlocking(stdout_fd_);
|
||
16 years ago
|
|
||
16 years ago
|
close(stderr_pipe[1]);
|
||
|
stderr_fd_ = stderr_pipe[0];
|
||
|
SetNonBlocking(stderr_fd_);
|
||
16 years ago
|
|
||
16 years ago
|
close(stdin_pipe[0]);
|
||
|
stdin_fd_ = stdin_pipe[1];
|
||
|
SetNonBlocking(stdin_fd_);
|
||
16 years ago
|
|
||
16 years ago
|
evcom_reader_set(&stdout_reader_, stdout_fd_);
|
||
|
evcom_reader_attach(EV_DEFAULT_UC_ &stdout_reader_);
|
||
16 years ago
|
|
||
16 years ago
|
evcom_reader_set(&stderr_reader_, stderr_fd_);
|
||
|
evcom_reader_attach(EV_DEFAULT_UC_ &stderr_reader_);
|
||
16 years ago
|
|
||
16 years ago
|
evcom_writer_set(&stdin_writer_, stdin_fd_);
|
||
|
evcom_writer_attach(EV_DEFAULT_UC_ &stdin_writer_);
|
||
16 years ago
|
|
||
16 years ago
|
Attach();
|
||
16 years ago
|
|
||
16 years ago
|
return 0;
|
||
16 years ago
|
}
|
||
|
|
||
16 years ago
|
void ChildProcess::OnCHLD(EV_P_ ev_child *watcher, int revents) {
|
||
16 years ago
|
ev_child_stop(EV_A_ watcher);
|
||
16 years ago
|
ChildProcess *child = static_cast<ChildProcess*>(watcher->data);
|
||
16 years ago
|
|
||
16 years ago
|
assert(revents == EV_CHILD);
|
||
16 years ago
|
assert(child->pid_ == watcher->rpid);
|
||
|
assert(&child->child_watcher_ == watcher);
|
||
16 years ago
|
|
||
16 years ago
|
child->got_chld_ = true;
|
||
|
child->exit_code_ = watcher->rstatus;
|
||
16 years ago
|
|
||
16 years ago
|
if (child->stdin_fd_ >= 0) evcom_writer_close(&child->stdin_writer_);
|
||
16 years ago
|
|
||
16 years ago
|
child->MaybeShutdown();
|
||
16 years ago
|
}
|
||
16 years ago
|
|
||
16 years ago
|
int ChildProcess::Write(const char *str, size_t len) {
|
||
16 years ago
|
if (stdin_fd_ < 0 || got_chld_) return -1;
|
||
|
evcom_writer_write(&stdin_writer_, str, len);
|
||
16 years ago
|
return 0;
|
||
|
}
|
||
|
|
||
16 years ago
|
int ChildProcess::Close(void) {
|
||
16 years ago
|
if (stdin_fd_ < 0 || got_chld_) return -1;
|
||
16 years ago
|
evcom_writer_close(&stdin_writer_);
|
||
16 years ago
|
return 0;
|
||
|
}
|
||
|
|
||
16 years ago
|
int ChildProcess::Kill(int sig) {
|
||
16 years ago
|
if (got_chld_ || pid_ == 0) return -1;
|
||
16 years ago
|
return kill(pid_, sig);
|
||
|
}
|
||
16 years ago
|
|
||
16 years ago
|
void ChildProcess::MaybeShutdown(void) {
|
||
16 years ago
|
if (stdout_fd_ < 0 && stderr_fd_ < 0 && got_chld_) {
|
||
16 years ago
|
HandleScope scope;
|
||
16 years ago
|
Handle<Value> argv[1] = { Integer::New(exit_code_) };
|
||
16 years ago
|
Emit("exit", 1, argv);
|
||
16 years ago
|
Shutdown();
|
||
16 years ago
|
Detach();
|
||
16 years ago
|
}
|
||
16 years ago
|
}
|
||
16 years ago
|
|
||
|
} // namespace node
|