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? 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. Are unix sockets similar to windows named pipes? If so, should they be supported? -> currently: no. Complication: they block.
- Child processes - Child process issues
Should not be too hard using CreatePipe, CreateProcessW and GetExitCodeProcess. * Communication between parent and child is slow; it uses a socketpair
Hooking up a child process to a file handle can be done; hooking up to a normal socket won't work; where a pipe would be much faster. Replace it by a pipe when there
we'd need some sort of pump() mechanism. is a libev backend that supports waiting for a pipe.
Waiting for the child to exit is tricky, probably would require a wait thread to wait for the child, then ev_async notify. * When a child process spawns the pid is not available straightaway.
How can we distinguish between the exit code and exception number after calling GetExitCodeProcess? 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) - Stdio (make TTY's / repl / readline work)
This will be hard: there is no ANSI escape code support in windows. 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__ #ifdef __POSIX__
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org> // Copyright 2009 Ryan Dahl <ry@tinyclouds.org>

50
src/node_child_process.h

@ -7,6 +7,10 @@
#include <v8.h> #include <v8.h>
#include <ev.h> #include <ev.h>
#ifdef __MINGW32__
# include <windows.h> // HANDLE type
#endif
// ChildProcess is a thin wrapper around ev_child. It has the extra // ChildProcess is a thin wrapper around ev_child. It has the extra
// functionality that it can spawn a child process with pipes connected to // 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 // 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); static v8::Handle<v8::Value> Kill(const v8::Arguments& args);
ChildProcess() : ObjectWrap() { ChildProcess() : ObjectWrap() {
#ifdef __POSIX__
ev_init(&child_watcher_, ChildProcess::on_chld); ev_init(&child_watcher_, ChildProcess::on_chld);
child_watcher_.data = this; child_watcher_.data = this;
#endif // __POSIX__
pid_ = -1; pid_ = -1;
#ifdef __MINGW32__
InitializeCriticalSection(&info_lock_);
kill_me_ = false;
did_start_ = false;
exit_signal_ = 0;
#endif // __MINGW32__
} }
~ChildProcess() { ~ChildProcess() {
#ifdef __POSIX__
Stop(); Stop();
#endif // __POSIX__
} }
// Returns 0 on success. stdio_fds will contain file desciptors for stdin, // Returns 0 on success. stdio_fds will contain file desciptors for stdin,
@ -48,8 +64,10 @@ class ChildProcess : ObjectWrap {
// called still. // called still.
int Kill(int sig); int Kill(int sig);
private: private:
void OnExit(int code); void OnExit(int code);
#ifdef __POSIX__ // Shouldn't this just move to node_child_process.cc?
void Stop(void); void Stop(void);
static void on_chld(EV_P_ ev_child *watcher, int revents) { static void on_chld(EV_P_ ev_child *watcher, int revents) {
@ -62,6 +80,36 @@ class ChildProcess : ObjectWrap {
ev_child child_watcher_; ev_child child_watcher_;
pid_t pid_; 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 } // 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_START
NODE_EXT_LIST_ITEM(node_buffer) NODE_EXT_LIST_ITEM(node_buffer)
NODE_EXT_LIST_ITEM(node_cares) NODE_EXT_LIST_ITEM(node_cares)
#ifdef __POSIX__
NODE_EXT_LIST_ITEM(node_child_process) NODE_EXT_LIST_ITEM(node_child_process)
#endif
#ifdef HAVE_OPENSSL #ifdef HAVE_OPENSSL
NODE_EXT_LIST_ITEM(node_crypto) NODE_EXT_LIST_ITEM(node_crypto)
#endif #endif

Loading…
Cancel
Save