// Copyright 2009 Ryan Dahl #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__ ) || defined(__OpenBSD__) #include #endif extern char **environ; namespace node { using namespace v8; static Persistent pid_symbol; static Persistent onexit_symbol; // TODO share with other modules 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; } void ChildProcess::Initialize(Handle target) { HandleScope scope; Local t = FunctionTemplate::New(ChildProcess::New); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(String::NewSymbol("ChildProcess")); pid_symbol = NODE_PSYMBOL("pid"); onexit_symbol = NODE_PSYMBOL("onexit"); NODE_SET_PROTOTYPE_METHOD(t, "spawn", ChildProcess::Spawn); NODE_SET_PROTOTYPE_METHOD(t, "kill", ChildProcess::Kill); target->Set(String::NewSymbol("ChildProcess"), t->GetFunction()); } Handle ChildProcess::New(const Arguments& args) { HandleScope scope; ChildProcess *p = new ChildProcess(); p->Wrap(args.Holder()); return args.This(); } // This is an internal function. The third argument should be an array // of key value pairs seperated with '='. Handle ChildProcess::Spawn(const Arguments& args) { HandleScope scope; if (args.Length() != 3 || !args[0]->IsString() || !args[1]->IsArray() || !args[2]->IsArray()) { return ThrowException(Exception::Error(String::New("Bad argument."))); } ChildProcess *child = ObjectWrap::Unwrap(args.Holder()); 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 argv_handle = Local::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 env_handle = Local::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 fds[3]; int r = child->Spawn(argv[0], argv, env, fds); for (i = 0; i < argv_length; i++) free(argv[i]); delete [] argv; for (i = 0; i < envc; i++) free(env[i]); delete [] env; if (r != 0) { return ThrowException(Exception::Error(String::New("Error spawning"))); } Local a = Array::New(3); assert(fds[0] >= 0); a->Set(0, Integer::New(fds[0])); // stdin assert(fds[1] >= 0); a->Set(1, Integer::New(fds[1])); // stdout assert(fds[2] >= 0); a->Set(2, Integer::New(fds[2])); // stderr return scope.Close(a); } Handle ChildProcess::Kill(const Arguments& args) { HandleScope scope; ChildProcess *child = ObjectWrap::Unwrap(args.Holder()); assert(child); if (child->pid_ < 1) { return ThrowException(Exception::Error(String::New("No such process"))); } int sig = SIGTERM; if (args.Length() > 0) { if (args[0]->IsNumber()) { sig = args[0]->Int32Value(); } else if (args[0]->IsString()) { Local signame = args[0]->ToString(); Local process = Context::GetCurrent()->Global(); Local node_obj = process->Get(String::NewSymbol("process"))->ToObject(); Local sig_v = node_obj->Get(signame); if (!sig_v->IsNumber()) { return ThrowException(Exception::Error(String::New("Unknown signal"))); } sig = sig_v->Int32Value(); } } if (child->Kill(sig) != 0) { return ThrowException(Exception::Error(String::New(strerror(errno)))); } return Undefined(); } void ChildProcess::Stop() { if (ev_is_active(&child_watcher_)) { ev_child_stop(EV_DEFAULT_UC_ &child_watcher_); Unref(); } // Don't kill the PID here. We want to allow for killing the parent // process and reparenting to initd. This is perhaps not going the best // technique for daemonizing, but I don't want to rule it out. pid_ = -1; } // 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 **env, int stdio_fds[3]) { HandleScope scope; assert(pid_ == -1); assert(!ev_is_active(&child_watcher_)); int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2]; /* An implementation of popen(), basically */ if (pipe(stdin_pipe) < 0 || pipe(stdout_pipe) < 0 || pipe(stderr_pipe) < 0) { perror("pipe()"); return -1; } // Save environ in the case that we get it clobbered // by the child process. char **save_our_env = environ; switch (pid_ = vfork()) { case -1: // Error. Stop(); return -4; case 0: // Child. close(stdin_pipe[1]); // close write end dup2(stdin_pipe[0], STDIN_FILENO); 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); environ = env; execvp(file, args); perror("execvp()"); _exit(127); } // Parent. // Restore environment. environ = save_our_env; ev_child_set(&child_watcher_, pid_, 0); ev_child_start(EV_DEFAULT_UC_ &child_watcher_); Ref(); handle_->Set(pid_symbol, Integer::New(pid_)); close(stdin_pipe[0]); stdio_fds[0] = stdin_pipe[1]; SetNonBlocking(stdin_pipe[1]); close(stdout_pipe[1]); stdio_fds[1] = stdout_pipe[0]; SetNonBlocking(stdout_pipe[0]); close(stderr_pipe[1]); stdio_fds[2] = stderr_pipe[0]; SetNonBlocking(stderr_pipe[0]); return 0; } void ChildProcess::OnExit(int status) { HandleScope scope; pid_ = -1; Stop(); handle_->Set(pid_symbol, Null()); Local onexit_v = handle_->Get(onexit_symbol); assert(onexit_v->IsFunction()); Local onexit = Local::Cast(onexit_v); TryCatch try_catch; Local argv[2]; if (WIFEXITED(status)) { argv[0] = Integer::New(WEXITSTATUS(status)); } else { argv[0] = Local::New(Null()); } if (WIFSIGNALED(status)) { argv[1] = String::NewSymbol(signo_string(WTERMSIG(status))); } else { argv[1] = Local::New(Null()); } onexit->Call(handle_, 2, argv); if (try_catch.HasCaught()) { FatalException(try_catch); } } int ChildProcess::Kill(int sig) { if (pid_ < 1) return -1; return kill(pid_, sig); } } // namespace node