summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBert Belder <bertbelder@gmail.com>2010-12-03 01:44:09 +0100
committerBert Belder <bertbelder@gmail.com>2010-12-20 23:51:31 +0100
commit0a2f1cb334e1633f0687bbd3442d433599dc08b0 (patch)
treee3b06deece17f1e75e8439973cac30d58b89e32e /src
parent0b96c5bc3b060615719c001ec68279a114429083 (diff)
downloadnode-0a2f1cb334e1633f0687bbd3442d433599dc08b0.tar.gz
Child processes
Diffstat (limited to 'src')
-rw-r--r--src/node_child_process.cc4
-rw-r--r--src/node_child_process.h50
-rw-r--r--src/node_child_process_win32.cc877
-rw-r--r--src/node_extensions.h2
4 files changed, 930 insertions, 3 deletions
diff --git a/src/node_child_process.cc b/src/node_child_process.cc
index 000817316..87c385708 100644
--- a/src/node_child_process.cc
+++ b/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>
diff --git a/src/node_child_process.h b/src/node_child_process.h
index 0eb38e783..6d7896123 100644
--- a/src/node_child_process.h
+++ b/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
diff --git a/src/node_child_process_win32.cc b/src/node_child_process_win32.cc
new file mode 100644
index 000000000..749cd70e6
--- /dev/null
+++ b/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);
diff --git a/src/node_extensions.h b/src/node_extensions.h
index a7db557ec..131d34e6f 100644
--- a/src/node_extensions.h
+++ b/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