Browse Source

Child processes

v0.7.4-release
Bert Belder 14 years ago
parent
commit
0a2f1cb334
  1. 23
      TODO.win32
  2. 4
      src/node_child_process.cc
  3. 50
      src/node_child_process.h
  4. 877
      src/node_child_process_win32.cc
  5. 2
      src/node_extensions.h

23
TODO.win32

@ -8,12 +8,23 @@
E.g. getaddrinfo() is ansi-only; GetAddrInfoW is utf16-only. Can we get utf16 straight out of v8?
Are unix sockets similar to windows named pipes? If so, should they be supported? -> currently: no. Complication: they block.
- Child processes
Should not be too hard using CreatePipe, CreateProcessW and GetExitCodeProcess.
Hooking up a child process to a file handle can be done; hooking up to a normal socket won't work;
we'd need some sort of pump() mechanism.
Waiting for the child to exit is tricky, probably would require a wait thread to wait for the child, then ev_async notify.
How can we distinguish between the exit code and exception number after calling GetExitCodeProcess?
- Child process issues
* Communication between parent and child is slow; it uses a socketpair
where a pipe would be much faster. Replace it by a pipe when there
is a libev backend that supports waiting for a pipe.
* When a child process spawns the pid is not available straightaway.
On linux the pid is available immediately because fork() doesn't
block; on windows a libeio thread is used to call CreateProcess.
So this can't really be fixed, but it could be worked around by adding a
'spawn' or 'pid' method.
* kill() doesn't work when the pid is not available yet. All the plumbing
is there to make it work, but lib/child_process.js just doesn't call
ChildProcess::Kill() as long as the pid is not known.
* passing socket custom_fds is not supported
* child_process.exec() only works on systems with msys installed.
It's because it relies on the 'sh' shell. The default windows shell
is 'cmd' and it works a little differently. Maybe add an option to
specify the shell to exec()?
- Stdio (make TTY's / repl / readline work)
This will be hard: there is no ANSI escape code support in windows.

4
src/node_child_process.cc

@ -1,3 +1,7 @@
#ifdef __MINGW32__
# include <node_child_process_win32.cc>
#endif
#ifdef __POSIX__
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>

50
src/node_child_process.h

@ -7,6 +7,10 @@
#include <v8.h>
#include <ev.h>
#ifdef __MINGW32__
# include <windows.h> // HANDLE type
#endif
// ChildProcess is a thin wrapper around ev_child. It has the extra
// functionality that it can spawn a child process with pipes connected to
// its stdin, stdout, stderr. This class is not meant to be exposed to but
@ -28,13 +32,25 @@ class ChildProcess : ObjectWrap {
static v8::Handle<v8::Value> Kill(const v8::Arguments& args);
ChildProcess() : ObjectWrap() {
#ifdef __POSIX__
ev_init(&child_watcher_, ChildProcess::on_chld);
child_watcher_.data = this;
#endif // __POSIX__
pid_ = -1;
#ifdef __MINGW32__
InitializeCriticalSection(&info_lock_);
kill_me_ = false;
did_start_ = false;
exit_signal_ = 0;
#endif // __MINGW32__
}
~ChildProcess() {
#ifdef __POSIX__
Stop();
#endif // __POSIX__
}
// Returns 0 on success. stdio_fds will contain file desciptors for stdin,
@ -48,8 +64,10 @@ class ChildProcess : ObjectWrap {
// called still.
int Kill(int sig);
private:
private:
void OnExit(int code);
#ifdef __POSIX__ // Shouldn't this just move to node_child_process.cc?
void Stop(void);
static void on_chld(EV_P_ ev_child *watcher, int revents) {
@ -62,6 +80,36 @@ class ChildProcess : ObjectWrap {
ev_child child_watcher_;
pid_t pid_;
#endif // __POSIX__
#ifdef __MINGW32__
static int do_spawn(eio_req *req);
static int after_spawn(eio_req *req);
static void watch(ChildProcess *child);
static void CALLBACK watch_wait_callback(void *data, BOOLEAN didTimeout);
static void notify_spawn_failure(ChildProcess *child);
static void notify_exit(ev_async *ev, int revent);
static int do_kill(ChildProcess *child, int sig);static void close_stdio_handles(ChildProcess *child);
int pid_;
int exit_signal_;
WCHAR *application_;
WCHAR *arguments_;
WCHAR *env_win_;
WCHAR *cwd_;
const WCHAR *path_;
const WCHAR *path_ext_;
HANDLE stdio_handles_[3];
bool got_custom_fds_[3];
CRITICAL_SECTION info_lock_;
bool did_start_;
bool kill_me_;
HANDLE wait_handle_;
HANDLE process_handle_;
#endif // __MINGW32__
};
} // namespace node

877
src/node_child_process_win32.cc

@ -0,0 +1,877 @@
// RegisterWaitForSingleObject requires Windows 2000,
// GetProcessId requires windows XP SP1
#define _WIN32_WINNT 0x0501
#include <node.h>
#include <node_child_process.h>
#include <platform_win32.h>
#include <platform_win32_winsock.h>
#include <windows.h>
#include <winsock.h>
#include <v8.h>
#include <ev.h>
#include <eio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
namespace node {
using namespace v8;
static const WCHAR DEFAULT_PATH[1] = L"";
static const WCHAR DEFAULT_PATH_EXT[20] = L".COM;.EXE;.BAT;.CMD";
static Persistent<String> pid_symbol;
static Persistent<String> onexit_symbol;
static struct watcher_status_struct {
ev_async async_watcher;
ChildProcess *child;
HANDLE lock;
int num_active;
} watcher_status;
/*
* Path search functions
*/
/*
* Helper function for search_path
*/
static inline WCHAR* search_path_join_test(
const WCHAR* dir, int dir_len, const WCHAR* name, int name_len,
const WCHAR* ext, int ext_len, const WCHAR* cwd, int cwd_len) {
WCHAR *result, *result_pos;
if (dir_len >= 1 && (dir[0] == L'/' || dir[0] == L'\\')) {
// It's a full path with drive letter, don't use cwd
cwd_len = 0;
} else if (dir_len == 2 && dir[1] == L':') {
// It's a relative path with drive letter (ext.g. D:../some/file)
// Replace dir by full cwd if it points to the same drive,
// otherwise use the dir only.
if (cwd_len < 2 || _wcsnicmp(cwd, dir, 2) != 0) {
cwd_len = 0;
} else {
dir_len = 0;
}
} else if (dir_len > 2 && dir[1] == L':') {
// It's an absolute path with drive letter
// Don't use the cwd at all
cwd_len = 0;
}
// Allocate buffer for output
result = result_pos =
new WCHAR[cwd_len + 1 + dir_len + 1 + name_len + 1 + ext_len + 1];
// Copy cwd
wcsncpy(result_pos, cwd, cwd_len);
result_pos += cwd_len;
// Add a path separator if cwd didn't end with one
if (cwd_len && wcsrchr(L"\\/:", result_pos[-1]) == NULL) {
result_pos[0] = L'\\';
result_pos++;
}
// Copy dir
wcsncpy(result_pos, dir, dir_len);
result_pos += dir_len;
// Add a separator if the dir didn't end with one
if (dir_len && wcsrchr(L"\\/:", result_pos[-1]) == NULL) {
result_pos[0] = L'\\';
result_pos++;
}
// Copy filename
wcsncpy(result_pos, name, name_len);
result_pos += name_len;
// Copy extension
if (ext_len) {
result_pos[0] = L'.';
result_pos++;
wcsncpy(result_pos, ext, ext_len);
result_pos += ext_len;
}
// Null terminator
result_pos[0] = L'\0';
DWORD attrs = GetFileAttributesW(result);
if (attrs != INVALID_FILE_ATTRIBUTES &&
!(attrs & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))) {
return result;
}
delete[] result;
return NULL;
}
/*
* Helper function for search_path
*/
static inline WCHAR* path_search_walk_ext(
const WCHAR *dir, int dir_len, const WCHAR *name, int name_len,
WCHAR *cwd, int cwd_len, const WCHAR *path_ext, bool name_has_ext) {
WCHAR* result = NULL;
const WCHAR *ext_start,
*ext_end = path_ext;
// If the name itself has a nonemtpy extension, try this extension first
if (name_has_ext) {
result = search_path_join_test(dir, dir_len,
name, name_len,
L"", 0,
cwd, cwd_len);
}
// Add path_ext extensions and try to find a name that matches
while (result == NULL) {
if (*ext_end == L'\0') {
break;
}
// Skip the separator that ext_end now points to
if (ext_end != path_ext) {
ext_end++;
}
// Find the next dot in path_ext
ext_start = wcschr(ext_end, L'.');
if (ext_start == NULL) {
break;
}
// Skip the dot
ext_start++;
// Slice until we found a ; or alternatively a \0
ext_end = wcschr(ext_start, L';');
if (ext_end == NULL) {
ext_end = wcschr(ext_start, '\0');
}
result = search_path_join_test(dir, dir_len,
name, name_len,
ext_start, (ext_end - ext_start),
cwd, cwd_len);
}
return result;
}
/*
* search_path searches the system path for an executable filename -
* the windows API doesn't provide this as a standalone function nor as an
* option to CreateProcess.
*
* It tries to return an absolute filename.
*
* Furthermore, it tries to follow the semantics that cmd.exe uses as closely
* as possible:
*
* - Do not search the path if the filename already contains a path (either
* relative or absolute).
* (but do use path_ext)
*
* - If there's really only a filename, check the current directory for file,
* then search all path directories.
*
* - If filename specifies has *any* extension, search for the file with the
* specified extension first.
* (not necessary an executable one or one that appears in path_ext;
* *but* no extension or just a dot is *not* allowed)
*
* - If the literal filename is not found in a directory, try *appending*
* (not replacing) extensions from path_ext in the specified order.
* (an extension consisting of just a dot *may* appear in path_ext;
* unlike what happens if the specified filename ends with a dot,
* if path_ext specifies a single dot cmd.exe *does* look for an
* extension-less file)
*
* - The path variable may contain relative paths; relative paths are relative
* to the cwd.
*
* - Directories in path may or may not end with a trailing backslash.
*
* - Extensions path_ext portions must always start with a dot.
*
* - CMD does not trim leading/trailing whitespace from path/pathex entries
* nor from the environment variables as a whole.
*
* - When cmd.exe cannot read a directory, it wil just skip it and go on
* searching. However, unlike posix-y systems, it will happily try to run a
* file that is not readable/executable; if the spawn fails it will not
* continue searching.
*
* TODO: correctly interpret UNC paths
* TODO: check with cmd what should happen when a pathext entry does not start
* with a dot
*/
static inline WCHAR* search_path(const WCHAR *file, WCHAR *cwd,
const WCHAR *path, const WCHAR *path_ext) {
WCHAR* result = NULL;
int file_len = wcslen(file);
int cwd_len = wcslen(cwd);
// If the caller supplies an empty filename,
// we're not gonna return c:\windows\.exe -- GFY!
if (file_len == 0
|| (file_len == 1 && file[0] == L'.')) {
return NULL;
}
// Find the start of the filename so we can split the directory from the name
WCHAR *file_name_start;
for (file_name_start = (WCHAR*)file + file_len;
file_name_start > file
&& file_name_start[-1] != L'\\'
&& file_name_start[-1] != L'/'
&& file_name_start[-1] != L':';
file_name_start--);
bool file_has_dir = file_name_start != file;
// Check if the filename includes an extension
WCHAR *dot = wcschr(file_name_start, L'.');
bool name_has_ext = (dot != NULL && dot[1] != L'\0');
if (file_has_dir) {
// The file has a path inside, don't use path (but do use path_ex)
result = path_search_walk_ext(
file, file_name_start - file,
file_name_start, file_len - (file_name_start - file),
cwd, cwd_len,
path_ext, name_has_ext);
} else {
const WCHAR *dir_start,
*dir_end = path;
// The file is really only a name; look in cwd first, then scan path
result = path_search_walk_ext(L"", 0,
file, file_len,
cwd, cwd_len,
path_ext, name_has_ext);
while (result == NULL) {
if (*dir_end == L'\0') {
break;
}
// Skip the separator that dir_end now points to
if (dir_end != path) {
dir_end++;
}
// Next slice starts just after where the previous one ended
dir_start = dir_end;
// Slice until the next ; or \0 is found
dir_end = wcschr(dir_start, L';');
if (dir_end == NULL) {
dir_end = wcschr(dir_start, L'\0');
}
// If the slice is zero-length, don't bother
if (dir_end - dir_start == 0) {
continue;
}
result = path_search_walk_ext(dir_start, dir_end - dir_start,
file, file_len,
cwd, cwd_len,
path_ext, name_has_ext);
}
}
return result;
}
/*
* Process exit "watcher" functions. It's not like a real libev watcher,
* it's more like a wrapper around ev_async, and RegisterWaitForSingleObject.
* And its not generalized, it only works with child processes.
* BTW there is only one exit watcher that watches all childs!
*/
// Called from either a eio, a wait thread or a callback thread created by a
// wait thread
void ChildProcess::close_stdio_handles(ChildProcess *child) {
// Before we proceed to synchronize with the main thread, first close
// the stdio sockets that the child process has used, because it may
// take some time and would deadlock if done in the main thread.
for (int i = 0; i < 3; i++) {
if (!child->got_custom_fds_[i]) {
wsa_disconnect_ex((SOCKET)child->stdio_handles_[i], NULL, 0, 0);
closesocket((SOCKET)child->stdio_handles_[i]);
}
}
}
// Called from the main thread
void ChildProcess::notify_exit(ev_async *ev, int revent) {
// Get the child process, then release the lock
ChildProcess *child = watcher_status.child;
// Stop the watcher if appropriate
if (!--watcher_status.num_active) {
ev_async_stop(EV_DEFAULT_UC_ &watcher_status.async_watcher);
}
ReleaseSemaphore(watcher_status.lock, 1, NULL);
DWORD exit_code = -127;
EnterCriticalSection(&child->info_lock_);
// Did the process even start anyway?
if (child->did_start_) {
// Process launched, then exited
// Drop the wait handle
UnregisterWait(child->wait_handle_);
// Fetch the process exit code
if (GetExitCodeProcess(child->process_handle_, &exit_code) == 0) {
winapi_perror("GetExitCodeProcess");
}
// Close and unset the process handle
EnterCriticalSection(&child->info_lock_);
CloseHandle(child->process_handle_);
child->process_handle_ = NULL;
child->pid_ = 0;
}
LeaveCriticalSection(&child->info_lock_);
// The process never even started
child->OnExit(exit_code);
}
// Called from the eio thread
void ChildProcess::notify_spawn_failure(ChildProcess *child) {
close_stdio_handles(child);
DWORD result = WaitForSingleObject(watcher_status.lock, INFINITE);
assert(result == WAIT_OBJECT_0);
if (!watcher_status.num_active++) {
ev_async_start(EV_DEFAULT_UC_ &watcher_status.async_watcher);
}
watcher_status.child = child;
ev_async_send(&watcher_status.async_watcher);
}
// Called from the windows-managed wait thread
void CALLBACK ChildProcess::watch_wait_callback(void *data,
BOOLEAN didTimeout) {
assert(didTimeout == FALSE);
ChildProcess *child = (ChildProcess*)data;
close_stdio_handles(child);
// If the main thread is blocked, and more than one child process returns,
// the wait thread will block as well here. It doesn't matter because the
// main thread can only do one thing at a time anyway.
DWORD result = WaitForSingleObject(watcher_status.lock, INFINITE);
assert(result == WAIT_OBJECT_0);
watcher_status.child = child;
ev_async_send(&watcher_status.async_watcher);
}
// Called from the eio thread
inline void ChildProcess::watch(ChildProcess *child) {
DWORD result = WaitForSingleObject(watcher_status.lock, INFINITE);
assert(result == WAIT_OBJECT_0);
if (!watcher_status.num_active++) {
ev_async_start(EV_DEFAULT_UC_ &watcher_status.async_watcher);
}
// We must retain the lock here because we don't want the RegisterWait
// to complete before the waithandle is set to the child process.
RegisterWaitForSingleObject(&child->wait_handle_, child->process_handle_,
watch_wait_callback, (void*)child, INFINITE,
WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE);
ReleaseSemaphore(watcher_status.lock, 1, NULL);
}
/*
* Spawn helper functions
*/
/*
* Quotes command line arguments
* Returns a pointer to the end (next char to be written) of the buffer
*/
static inline WCHAR* quote_cmd_arg(WCHAR *source, WCHAR *target,
WCHAR terminator) {
int len = wcslen(source),
i;
// Check if the string must be quoted;
// if unnecessary, don't do it, it may only confuse older programs.
if (len == 0) {
goto quote;
}
for (i = 0; i < len; i++) {
if (source[i] == L' ' || source[i] == L'"') {
goto quote;
}
}
// No quotation needed
wcsncpy(target, source, len);
target += len;
*(target++) = terminator;
return target;
quote:
// Quote
*(target++) = L'"';
for (i = 0; i < len; i++) {
if (source[i] == L'"' || source[i] == L'\\') {
*(target++) = '\\';
}
*(target++) = source[i];
}
*(target++) = L'"';
*(target++) = terminator;
return target;
}
/*
* Spawns a child process from a libeio thread
*/
int ChildProcess::do_spawn(eio_req *req) {
ChildProcess* child = (ChildProcess*)req->data;
WCHAR* application_path = search_path(child->application_, child->cwd_,
child->path_, child->path_ext_);
STARTUPINFOW startup;
PROCESS_INFORMATION info;
startup.cb = sizeof(startup);
startup.lpReserved = NULL;
startup.lpDesktop = NULL;
startup.lpTitle = NULL;
startup.dwFlags = STARTF_USESTDHANDLES;
startup.cbReserved2 = 0;
startup.lpReserved2 = NULL;
startup.hStdInput = child->stdio_handles_[0];
startup.hStdOutput = child->stdio_handles_[1];
startup.hStdError = child->stdio_handles_[2];
EnterCriticalSection(&child->info_lock_);
if (!child->kill_me_) {
// Try start the process
BOOL success = CreateProcessW(
application_path,
child->arguments_,
NULL,
NULL,
1,
CREATE_UNICODE_ENVIRONMENT,
child->env_win_,
child->cwd_,
&startup,
&info
);
if (success) {
child->process_handle_ = info.hProcess;
child->pid_ = GetProcessId(info.hProcess);
child->did_start_ = true;
watch(child);
LeaveCriticalSection(&child->info_lock_);
// Not interesting
CloseHandle(info.hThread);
return 0;
}
}
// kill_me set or process failed to start
notify_spawn_failure(child);
LeaveCriticalSection(&child->info_lock_);
return 0;
}
// Called from the main thread after spawn has finished,
// there's no need to lock the child because did_start is reliable
int ChildProcess::after_spawn(eio_req *req) {
ChildProcess* child = (ChildProcess*)req->data;
if (child->did_start_) {
child->handle_->Set(pid_symbol, Integer::New(child->pid_));
} else {
child->handle_->Set(pid_symbol, Local<Value>::New(Null()));
}
// Cleanup data structures needed only for spawn() here
delete [] child->application_;
delete [] child->arguments_;
delete [] child->env_win_;
delete [] child->cwd_;
return 0;
}
/*
* Kill helper functions
*/
// Called from the main thread while eio/wait threads may still be busy with
// the process
int ChildProcess::do_kill(ChildProcess *child, int sig) {
EnterCriticalSection(&child->info_lock_);
child->exit_signal_ = sig;
if (child->did_start_) {
// On windows killed processes normally return 1
if (TerminateProcess(child->process_handle_, 1) != 0) {
return 0;
} else {
return GetLastError();
}
} else {
child->kill_me_ = true;
return 0;
}
LeaveCriticalSection(&child->info_lock_);
}
/*
* ChildProcess non-static Methods
*/
Handle<Value> 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<Value> ChildProcess::Spawn(const Arguments& args) {
HandleScope scope;
if (args.Length() < 3 ||
!args[0]->IsString() ||
!args[1]->IsArray() ||
!args[2]->IsString() ||
!args[3]->IsArray()) {
return ThrowException(Exception::Error(String::New("Bad argument.")));
}
// Get ChildProcess object
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
// Copy appplication name
String::Value application(args[0]->ToString());
child->application_ = _wcsdup((WCHAR*)*application);
/*
* Copy second argument args[1] into a c-string called argv.
* On windows command line arguments are all quoted and concatenated to
* one string.
* Assuming that all arguments must be wrapped in quotes,
* every character needs to be quoted with a backslash,
* and every argument is followed by either a space or a nul char,
* the maximum required buffer size is Σ[arg1..argc](2 * length + 3).
*/
Local<Array> cmd_args_handle = Local<Array>::Cast(args[1]);
int cmd_argc = cmd_args_handle->Length();
if (cmd_argc > 0) {
// Compute required buffer
int max_buf = cmd_argc * 3,
i;
for (i = 0; i < cmd_argc; i++) {
Local<String> arg_handle =
cmd_args_handle->Get(Integer::New(i))->ToString();
max_buf += 2 * arg_handle->Length();
}
child->arguments_ = new WCHAR[max_buf];
WCHAR *pos = child->arguments_;
for (i = 0; i < cmd_argc - 1; i++) {
String::Value arg(cmd_args_handle->Get(Integer::New(i))->ToString());
pos = quote_cmd_arg((WCHAR*)*arg, pos, L' ');
}
String::Value arg(cmd_args_handle->Get(Integer::New(i))->ToString());
quote_cmd_arg((WCHAR*)*arg, pos, L'\0');
} else {
// No arguments
child->arguments_ = _wcsdup(L"\0");
}
// Copy command-line arguments
Local<String>cwd_handle = Local<String>::Cast(args[2]);
if (cwd_handle->Length() > 0) {
// Cwd was specified
String::Value cwd(args[2]);
child->cwd_ = _wcsdup((WCHAR*)*cwd);
} else {
// Cwd not specified
int chars = GetCurrentDirectoryW(0, NULL);
if (!chars) {
winapi_perror("GetCurrentDirectoryW");
child->cwd_ = _wcsdup(L"");
} else {
child->cwd_ = new WCHAR[chars];
GetCurrentDirectoryW(chars, child->cwd_);
}
}
/*
* args[3] holds the environment as a js array containing key=value pairs.
* The way windows takes environment variables is different than what C does;
* Windows wants a contiguous block of null-terminated strings, terminated
* with an additional null.
* Get a pointer to the pathext and path environment variables as well,
* because do_spawn needs it. These are just pointers into env_win.
*/
Local<Array> env_list_handle = Local<Array>::Cast(args[3]);
int envc = env_list_handle->Length();
Local<String> env_val_handle[envc];
int env_win_len = envc + 1; // room for \0 terminators plus closing null
for (int i = 0; i < envc; i++) {
env_val_handle[i] = env_list_handle->Get(Integer::New(i))->ToString();
env_win_len += env_val_handle[i]->Length();
}
WCHAR *env_win = new WCHAR[env_win_len],
*env_win_pos = env_win;
WCHAR *path = NULL, *path_ext = NULL;
for (int i = 0; i < envc; i++) {
int len = env_val_handle[i]->Length() + 1; // including \0
String::Value pair(env_val_handle[i]);
wcsncpy(env_win_pos, (WCHAR*)*pair, (size_t)len);
// Try to get a pointer to PATH and PATHEXT
if (_wcsnicmp(L"PATH=", env_win_pos, 5) == 0) {
path = env_win_pos + 5;
}
if (_wcsnicmp(L"PATHEXT=", env_win_pos, 8) == 0) {
path_ext = env_win_pos + 8;
}
env_win_pos += len;
}
*env_win_pos = L'\0';
child->env_win_ = env_win;
if (path != NULL) {
child->path_ = path;
} else {
child->path_ = DEFAULT_PATH;
}
if (path_ext != NULL) {
child->path_ext_ = path_ext;
} else {
child->path_ext_ = DEFAULT_PATH_EXT;
}
// Open pipes or re-use custom_fds to talk to child
Local<Array> custom_fds_handle = Local<Array>::Cast(args[4]);
int custom_fds_len = custom_fds_handle->Length();
HANDLE *child_handles = (HANDLE*)&child->stdio_handles_;
bool *has_custom_fds = (bool*)&child->got_custom_fds_;
int parent_fds[3];
for (int i = 0; i < 3; i++) {
int custom_fd = -1;
if (i < custom_fds_len && !custom_fds_handle->Get(i)->IsUndefined())
custom_fd = custom_fds_handle->Get(i)->ToInteger()->Value();
if (custom_fd == -1) {
// Create a new pipe
HANDLE parent_handle, child_handle;
if (wsa_sync_async_socketpair(AF_INET, SOCK_STREAM, IPPROTO_IP,
(SOCKET*)&child_handle, (SOCKET*)&parent_handle) == SOCKET_ERROR)
wsa_perror("wsa_sync_async_socketpair");
// Make parent handle nonblocking
unsigned long ioctl_value = 1;
if (ioctlsocket((SOCKET)parent_handle, FIONBIO, &ioctl_value) ==
SOCKET_ERROR)
wsa_perror("ioctlsocket");
// Make child handle inheritable
if (!SetHandleInformation(child_handle, HANDLE_FLAG_INHERIT,
HANDLE_FLAG_INHERIT))
winapi_perror("SetHandleInformation");
// Enable linger on socket so all written data gets through
BOOL opt_value = 0;
if (setsockopt((SOCKET)child_handle, SOL_SOCKET, SO_DONTLINGER,
(char*)&opt_value, sizeof(opt_value)) == SOCKET_ERROR)
wsa_perror("setsockopt");
has_custom_fds[i] = false;
child_handles[i] = child_handle;
parent_fds[i] = (int)_open_osfhandle((intptr_t)parent_handle, 0);
} else {
// Use this custom fd
HANDLE custom_handle = (HANDLE)_get_osfhandle(custom_fd);
// Make handle inheritable
if (!SetHandleInformation(child_handles[i], HANDLE_FLAG_INHERIT,
HANDLE_FLAG_INHERIT))
winapi_perror("SetHandleInformation");
has_custom_fds[i] = true;
child_handles[i] = custom_handle;
parent_fds[i] = custom_fd;
}
}
// Return the opened fds
Local<Array> result = Array::New(3);
assert(parent_fds[0] >= 0);
result->Set(0, Integer::New(parent_fds[0]));
assert(parent_fds[1] >= 0);
result->Set(1, Integer::New(parent_fds[1]));
assert(parent_fds[2] >= 0);
result->Set(2, Integer::New(parent_fds[2]));
eio_custom(do_spawn, EIO_PRI_DEFAULT, after_spawn, (void*)child);
return scope.Close(result);
}
Handle<Value> ChildProcess::Kill(const Arguments& args) {
HandleScope scope;
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
assert(child);
int sig = SIGTERM;
if (args.Length() > 0) {
if (args[0]->IsNumber()) {
sig = args[0]->Int32Value();
} else {
return ThrowException(Exception::Error(String::New("Bad argument.")));
}
}
if (do_kill(child, sig) != 0) {
return ThrowException(Exception::Error(String::New(strerror(errno))));
}
return Undefined();
}
// Called from the main thread _after_ all eio/wait threads are done with the
// process, so there's no need to lock here.
void ChildProcess::OnExit(int status) {
HandleScope scope;
handle_->Set(pid_symbol, Null());
Local<Value> onexit_v = handle_->Get(onexit_symbol);
assert(onexit_v->IsFunction());
Local<Function> onexit = Local<Function>::Cast(onexit_v);
TryCatch try_catch;
Local<Value> argv[2];
argv[0] = Integer::New(status);
if (exit_signal_ != 0) {
argv[1] = Integer::New(exit_signal_);
} else {
argv[1] = Local<Value>::New(Null());
}
onexit->Call(handle_, 2, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
}
void ChildProcess::Initialize(Handle<Object> target) {
HandleScope scope;
Local<FunctionTemplate> 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());
ev_async_init(&watcher_status.async_watcher, notify_exit);
watcher_status.lock = CreateSemaphore(NULL, 1, 1, NULL);
}
} // namespace node
NODE_MODULE(node_child_process, node::ChildProcess::Initialize);

2
src/node_extensions.h

@ -2,9 +2,7 @@
NODE_EXT_LIST_START
NODE_EXT_LIST_ITEM(node_buffer)
NODE_EXT_LIST_ITEM(node_cares)
#ifdef __POSIX__
NODE_EXT_LIST_ITEM(node_child_process)
#endif
#ifdef HAVE_OPENSSL
NODE_EXT_LIST_ITEM(node_crypto)
#endif

Loading…
Cancel
Save