|
@ -50,16 +50,18 @@ static inline WCHAR* search_path_join_test( |
|
|
WCHAR *result, *result_pos; |
|
|
WCHAR *result, *result_pos; |
|
|
|
|
|
|
|
|
if (dir_len >= 1 && (dir[0] == L'/' || dir[0] == L'\\')) { |
|
|
if (dir_len >= 1 && (dir[0] == L'/' || dir[0] == L'\\')) { |
|
|
// It's a full path with drive letter, don't use cwd
|
|
|
// It's a full path without drive letter, use cwd's drive letter only
|
|
|
cwd_len = 0; |
|
|
cwd_len = 2; |
|
|
} else if (dir_len == 2 && dir[1] == L':') { |
|
|
} else if (dir_len >= 2 && dir[1] == L':' && |
|
|
|
|
|
(dir_len < 3 || (dir[2] != L'/' && dir[2] != L'\\'))) { |
|
|
// It's a relative path with drive letter (ext.g. D:../some/file)
|
|
|
// 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,
|
|
|
// Replace drive letter in dir by full cwd if it points to the same drive,
|
|
|
// otherwise use the dir only.
|
|
|
// otherwise use the dir only.
|
|
|
if (cwd_len < 2 || _wcsnicmp(cwd, dir, 2) != 0) { |
|
|
if (cwd_len < 2 || _wcsnicmp(cwd, dir, 2) != 0) { |
|
|
cwd_len = 0; |
|
|
cwd_len = 0; |
|
|
} else { |
|
|
} else { |
|
|
dir_len = 0; |
|
|
dir += 2; |
|
|
|
|
|
dir_len -= 2; |
|
|
} |
|
|
} |
|
|
} else if (dir_len > 2 && dir[1] == L':') { |
|
|
} else if (dir_len > 2 && dir[1] == L':') { |
|
|
// It's an absolute path with drive letter
|
|
|
// It's an absolute path with drive letter
|
|
@ -327,7 +329,7 @@ void ChildProcess::close_stdio_handles(ChildProcess *child) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Called from the main thread
|
|
|
// Called from the main thread
|
|
|
void ChildProcess::notify_exit(ev_async *ev, int revent) { |
|
|
void ChildProcess::notify_exit(EV_P_ ev_async *ev, int revent) { |
|
|
// Get the child process, then release the lock
|
|
|
// Get the child process, then release the lock
|
|
|
ChildProcess *child = watcher_status.child; |
|
|
ChildProcess *child = watcher_status.child; |
|
|
|
|
|
|
|
@ -355,7 +357,6 @@ void ChildProcess::notify_exit(ev_async *ev, int revent) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Close and unset the process handle
|
|
|
// Close and unset the process handle
|
|
|
EnterCriticalSection(&child->info_lock_); |
|
|
|
|
|
CloseHandle(child->process_handle_); |
|
|
CloseHandle(child->process_handle_); |
|
|
child->process_handle_ = NULL; |
|
|
child->process_handle_ = NULL; |
|
|
child->pid_ = 0; |
|
|
child->pid_ = 0; |
|
@ -363,7 +364,6 @@ void ChildProcess::notify_exit(ev_async *ev, int revent) { |
|
|
|
|
|
|
|
|
LeaveCriticalSection(&child->info_lock_); |
|
|
LeaveCriticalSection(&child->info_lock_); |
|
|
|
|
|
|
|
|
// The process never even started
|
|
|
|
|
|
child->OnExit(exit_code); |
|
|
child->OnExit(exit_code); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -381,7 +381,7 @@ void ChildProcess::notify_spawn_failure(ChildProcess *child) { |
|
|
|
|
|
|
|
|
watcher_status.child = child; |
|
|
watcher_status.child = child; |
|
|
|
|
|
|
|
|
ev_async_send(&watcher_status.async_watcher); |
|
|
ev_async_send(EV_DEFAULT_UC_ &watcher_status.async_watcher); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -401,7 +401,7 @@ void CALLBACK ChildProcess::watch_wait_callback(void *data, |
|
|
assert(result == WAIT_OBJECT_0); |
|
|
assert(result == WAIT_OBJECT_0); |
|
|
|
|
|
|
|
|
watcher_status.child = child; |
|
|
watcher_status.child = child; |
|
|
ev_async_send(&watcher_status.async_watcher); |
|
|
ev_async_send(EV_DEFAULT_UC_ &watcher_status.async_watcher); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -415,7 +415,7 @@ inline void ChildProcess::watch(ChildProcess *child) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// We must retain the lock here because we don't want the RegisterWait
|
|
|
// We must retain the lock here because we don't want the RegisterWait
|
|
|
// to complete before the waithandle is set to the child process.
|
|
|
// to complete before the wait handle is set to the child process.
|
|
|
RegisterWaitForSingleObject(&child->wait_handle_, child->process_handle_, |
|
|
RegisterWaitForSingleObject(&child->wait_handle_, child->process_handle_, |
|
|
watch_wait_callback, (void*)child, INFINITE, |
|
|
watch_wait_callback, (void*)child, INFINITE, |
|
|
WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE); |
|
|
WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE); |
|
@ -480,6 +480,7 @@ int ChildProcess::do_spawn(eio_req *req) { |
|
|
WCHAR* application_path = search_path(child->application_, child->cwd_, |
|
|
WCHAR* application_path = search_path(child->application_, child->cwd_, |
|
|
child->path_, child->path_ext_); |
|
|
child->path_, child->path_ext_); |
|
|
|
|
|
|
|
|
|
|
|
if (application_path) { |
|
|
STARTUPINFOW startup; |
|
|
STARTUPINFOW startup; |
|
|
PROCESS_INFORMATION info; |
|
|
PROCESS_INFORMATION info; |
|
|
|
|
|
|
|
@ -520,14 +521,16 @@ int ChildProcess::do_spawn(eio_req *req) { |
|
|
// Not interesting
|
|
|
// Not interesting
|
|
|
CloseHandle(info.hThread); |
|
|
CloseHandle(info.hThread); |
|
|
|
|
|
|
|
|
|
|
|
LeaveCriticalSection(&child->info_lock_); |
|
|
return 0; |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// kill_me set or process failed to start
|
|
|
|
|
|
LeaveCriticalSection(&child->info_lock_); |
|
|
LeaveCriticalSection(&child->info_lock_); |
|
|
notify_spawn_failure(child); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// not found, kill_me set or process failed to start
|
|
|
|
|
|
notify_spawn_failure(child); |
|
|
return 0; |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -560,23 +563,23 @@ int ChildProcess::after_spawn(eio_req *req) { |
|
|
// Called from the main thread while eio/wait threads may still be busy with
|
|
|
// Called from the main thread while eio/wait threads may still be busy with
|
|
|
// the process
|
|
|
// the process
|
|
|
int ChildProcess::do_kill(ChildProcess *child, int sig) { |
|
|
int ChildProcess::do_kill(ChildProcess *child, int sig) { |
|
|
|
|
|
int rv = 0; |
|
|
|
|
|
|
|
|
EnterCriticalSection(&child->info_lock_); |
|
|
EnterCriticalSection(&child->info_lock_); |
|
|
|
|
|
|
|
|
child->exit_signal_ = sig; |
|
|
child->exit_signal_ = sig; |
|
|
|
|
|
|
|
|
if (child->did_start_) { |
|
|
if (child->did_start_) { |
|
|
// On windows killed processes normally return 1
|
|
|
// On windows killed processes normally return 1
|
|
|
if (TerminateProcess(child->process_handle_, 1) != 0) { |
|
|
if (!TerminateProcess(child->process_handle_, 1)) |
|
|
return 0; |
|
|
rv = -1; |
|
|
} else { |
|
|
|
|
|
return GetLastError(); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
child->kill_me_ = true; |
|
|
child->kill_me_ = true; |
|
|
return 0; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
LeaveCriticalSection(&child->info_lock_); |
|
|
LeaveCriticalSection(&child->info_lock_); |
|
|
|
|
|
|
|
|
|
|
|
return rv; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -609,57 +612,58 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) { |
|
|
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder()); |
|
|
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder()); |
|
|
|
|
|
|
|
|
// Copy appplication name
|
|
|
// Copy appplication name
|
|
|
String::Value application(args[0]->ToString()); |
|
|
Handle<String> app_handle = args[0]->ToString(); |
|
|
child->application_ = _wcsdup((WCHAR*)*application); |
|
|
int app_len = app_handle->Length(); |
|
|
|
|
|
String::Value app(app_handle); |
|
|
|
|
|
child->application_ = new WCHAR[app_len + 1]; |
|
|
|
|
|
wcsncpy(child->application_, (WCHAR*)*app, app_len + 1); |
|
|
|
|
|
|
|
|
/*
|
|
|
/*
|
|
|
* Copy second argument args[1] into a c-string called argv. |
|
|
* Copy second argument args[1] into a c-string called argv. |
|
|
* On windows command line arguments are all quoted and concatenated to |
|
|
* On windows command line arguments are all quoted and concatenated to |
|
|
* one string. |
|
|
* one string. The executable name must be prepended. This is not really |
|
|
* Assuming that all arguments must be wrapped in quotes, |
|
|
* required by windows but if you don't do this programs that rely on |
|
|
|
|
|
* argv[0] being the executable misbehave. |
|
|
|
|
|
* Assuming that executable plus all arguments must be wrapped in quotes, |
|
|
* every character needs to be quoted with a backslash, |
|
|
* every character needs to be quoted with a backslash, |
|
|
* and every argument is followed by either a space or a nul char, |
|
|
* and every argument is followed by either a space or a nul char, |
|
|
* the maximum required buffer size is Σ[arg1..argc](2 * length + 3). |
|
|
* the maximum required buffer size is Σ[exe and args](2 * length + 3). |
|
|
*/ |
|
|
*/ |
|
|
Local<Array> cmd_args_handle = Local<Array>::Cast(args[1]); |
|
|
Local<Array> cmd_args_handle = Local<Array>::Cast(args[1]); |
|
|
int cmd_argc = cmd_args_handle->Length(); |
|
|
int cmd_argc = cmd_args_handle->Length(); |
|
|
|
|
|
|
|
|
if (cmd_argc > 0) { |
|
|
|
|
|
// Compute required buffer
|
|
|
// Compute required buffer
|
|
|
int max_buf = cmd_argc * 3, |
|
|
int max_buf = (1 + cmd_argc) * 3 + app_len * 2, |
|
|
i; |
|
|
i; |
|
|
for (i = 0; i < cmd_argc; i++) { |
|
|
for (i = 0; i < cmd_argc; i++) { |
|
|
Local<String> arg_handle = |
|
|
Local<String> arg_handle = |
|
|
cmd_args_handle->Get(Integer::New(i))->ToString(); |
|
|
cmd_args_handle->Get(Integer::New(i))->ToString(); |
|
|
max_buf += 2 * arg_handle->Length(); |
|
|
max_buf += arg_handle->Length() * 2; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
child->arguments_ = new WCHAR[max_buf]; |
|
|
child->arguments_ = new WCHAR[max_buf]; |
|
|
WCHAR *pos = child->arguments_; |
|
|
WCHAR *pos = child->arguments_; |
|
|
for (i = 0; i < cmd_argc - 1; i++) { |
|
|
pos = quote_cmd_arg((WCHAR*)*app, pos, cmd_argc ? L' ' : L'\0'); |
|
|
String::Value arg(cmd_args_handle->Get(Integer::New(i))->ToString()); |
|
|
for (i = 0; i < cmd_argc; i++) { |
|
|
pos = quote_cmd_arg((WCHAR*)*arg, pos, L' '); |
|
|
|
|
|
} |
|
|
|
|
|
String::Value arg(cmd_args_handle->Get(Integer::New(i))->ToString()); |
|
|
String::Value arg(cmd_args_handle->Get(Integer::New(i))->ToString()); |
|
|
quote_cmd_arg((WCHAR*)*arg, pos, L'\0'); |
|
|
pos = quote_cmd_arg((WCHAR*)*arg, pos, (i < cmd_argc - 1) ? L' ' : L'\0'); |
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
// No arguments
|
|
|
|
|
|
child->arguments_ = _wcsdup(L"\0"); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Copy command-line arguments
|
|
|
// Current working directory
|
|
|
Local<String>cwd_handle = Local<String>::Cast(args[2]); |
|
|
Local<String>cwd_handle = Local<String>::Cast(args[2]); |
|
|
if (cwd_handle->Length() > 0) { |
|
|
int cwd_len = cwd_handle->Length(); |
|
|
|
|
|
if (cwd_len > 0) { |
|
|
// Cwd was specified
|
|
|
// Cwd was specified
|
|
|
String::Value cwd(args[2]); |
|
|
String::Value cwd(cwd_handle); |
|
|
child->cwd_ = _wcsdup((WCHAR*)*cwd); |
|
|
child->cwd_ = new WCHAR[cwd_len + 1]; |
|
|
|
|
|
wcsncpy(child->cwd_, (WCHAR*)*cwd, cwd_len + 1); |
|
|
} else { |
|
|
} else { |
|
|
// Cwd not specified
|
|
|
// Cwd not specified
|
|
|
int chars = GetCurrentDirectoryW(0, NULL); |
|
|
int chars = GetCurrentDirectoryW(0, NULL); |
|
|
if (!chars) { |
|
|
if (!chars) { |
|
|
winapi_perror("GetCurrentDirectoryW"); |
|
|
winapi_perror("GetCurrentDirectoryW"); |
|
|
child->cwd_ = _wcsdup(L""); |
|
|
child->cwd_ = new WCHAR[0]; |
|
|
|
|
|
child->cwd_[0] = '\0'; |
|
|
} else { |
|
|
} else { |
|
|
child->cwd_ = new WCHAR[chars]; |
|
|
child->cwd_ = new WCHAR[chars]; |
|
|
GetCurrentDirectoryW(chars, child->cwd_); |
|
|
GetCurrentDirectoryW(chars, child->cwd_); |
|
@ -765,10 +769,11 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) { |
|
|
// Use this custom fd
|
|
|
// Use this custom fd
|
|
|
HANDLE custom_handle = (HANDLE)_get_osfhandle(custom_fd); |
|
|
HANDLE custom_handle = (HANDLE)_get_osfhandle(custom_fd); |
|
|
|
|
|
|
|
|
// Make handle inheritable
|
|
|
// Make handle inheritable, don't care it it fails
|
|
|
if (!SetHandleInformation(child_handles[i], HANDLE_FLAG_INHERIT, |
|
|
// It may fail for certain types of handles - but always try to
|
|
|
HANDLE_FLAG_INHERIT)) |
|
|
// spawn; it'll still work for e.g. console handles
|
|
|
winapi_perror("SetHandleInformation"); |
|
|
SetHandleInformation(custom_handle, HANDLE_FLAG_INHERIT, |
|
|
|
|
|
HANDLE_FLAG_INHERIT); |
|
|
|
|
|
|
|
|
has_custom_fds[i] = true; |
|
|
has_custom_fds[i] = true; |
|
|
child_handles[i] = custom_handle; |
|
|
child_handles[i] = custom_handle; |
|
@ -785,6 +790,9 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) { |
|
|
assert(parent_fds[2] >= 0); |
|
|
assert(parent_fds[2] >= 0); |
|
|
result->Set(2, Integer::New(parent_fds[2])); |
|
|
result->Set(2, Integer::New(parent_fds[2])); |
|
|
|
|
|
|
|
|
|
|
|
// Grab a reference so it doesn't get GC'ed
|
|
|
|
|
|
child->Ref(); |
|
|
|
|
|
|
|
|
eio_custom(do_spawn, EIO_PRI_DEFAULT, after_spawn, (void*)child); |
|
|
eio_custom(do_spawn, EIO_PRI_DEFAULT, after_spawn, (void*)child); |
|
|
|
|
|
|
|
|
return scope.Close(result); |
|
|
return scope.Close(result); |
|
@ -819,6 +827,9 @@ Handle<Value> ChildProcess::Kill(const Arguments& args) { |
|
|
void ChildProcess::OnExit(int status) { |
|
|
void ChildProcess::OnExit(int status) { |
|
|
HandleScope scope; |
|
|
HandleScope scope; |
|
|
|
|
|
|
|
|
|
|
|
// Unref() the child, as it's no longer used by threads
|
|
|
|
|
|
Unref(); |
|
|
|
|
|
|
|
|
handle_->Set(pid_symbol, Null()); |
|
|
handle_->Set(pid_symbol, Null()); |
|
|
|
|
|
|
|
|
Local<Value> onexit_v = handle_->Get(onexit_symbol); |
|
|
Local<Value> onexit_v = handle_->Get(onexit_symbol); |
|
@ -859,7 +870,7 @@ void ChildProcess::Initialize(Handle<Object> target) { |
|
|
|
|
|
|
|
|
target->Set(String::NewSymbol("ChildProcess"), t->GetFunction()); |
|
|
target->Set(String::NewSymbol("ChildProcess"), t->GetFunction()); |
|
|
|
|
|
|
|
|
ev_async_init(&watcher_status.async_watcher, notify_exit); |
|
|
ev_async_init(EV_DEFAULT_UC_ &watcher_status.async_watcher, notify_exit); |
|
|
watcher_status.lock = CreateSemaphore(NULL, 1, 1, NULL); |
|
|
watcher_status.lock = CreateSemaphore(NULL, 1, 1, NULL); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|