int uv_spawn(uv_loop_t* loop, uv_process_t* process, uv_process_options_t options) { int err = 0, keep_child_stdio_open = 0; wchar_t* path = NULL; int size; BOOL result; wchar_t* application_path = NULL, *application = NULL, *arguments = NULL, *env = NULL, *cwd = NULL; HANDLE* child_stdio = process->child_stdio; STARTUPINFOW startup; PROCESS_INFORMATION info; if (!options.file) { uv__set_artificial_error(loop, UV_EINVAL); return -1; } uv_process_init(loop, process); process->exit_cb = options.exit_cb; UTF8_TO_UTF16(options.file, application); arguments = options.args ? make_program_args(options.args, options.windows_verbatim_arguments) : NULL; env = options.env ? make_program_env(options.env) : NULL; if (options.cwd) { UTF8_TO_UTF16(options.cwd, cwd); } else { size = GetCurrentDirectoryW(0, NULL) * sizeof(wchar_t); if (size) { cwd = (wchar_t*)malloc(size); if (!cwd) { uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); } GetCurrentDirectoryW(size, cwd); } else { uv__set_sys_error(loop, GetLastError()); err = -1; goto done; } } /* Get PATH env. variable. */ size = GetEnvironmentVariableW(L"PATH", NULL, 0) + 1; path = (wchar_t*)malloc(size * sizeof(wchar_t)); if (!path) { uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); } GetEnvironmentVariableW(L"PATH", path, size * sizeof(wchar_t)); path[size - 1] = L'\0'; application_path = search_path(application, cwd, path); if (!application_path) { /* CreateProcess will fail, but this allows us to pass this error to */ /* the user asynchronously. */ application_path = application; } /* Create stdio pipes. */ if (options.stdin_stream) { if (options.stdin_stream->ipc) { err = uv_create_stdio_pipe_pair( loop, options.stdin_stream, &child_stdio[0], PIPE_ACCESS_DUPLEX, GENERIC_READ | FILE_WRITE_ATTRIBUTES | GENERIC_WRITE, 1); } else { err = uv_create_stdio_pipe_pair( loop, options.stdin_stream, &child_stdio[0], PIPE_ACCESS_OUTBOUND, GENERIC_READ | FILE_WRITE_ATTRIBUTES, 0); } } else { err = duplicate_std_handle(loop, STD_INPUT_HANDLE, &child_stdio[0]); } if (err) { goto done; } if (options.stdout_stream) { err = uv_create_stdio_pipe_pair( loop, options.stdout_stream, &child_stdio[1], PIPE_ACCESS_INBOUND, GENERIC_WRITE, 0); } else { err = duplicate_std_handle(loop, STD_OUTPUT_HANDLE, &child_stdio[1]); } if (err) { goto done; } if (options.stderr_stream) { err = uv_create_stdio_pipe_pair( loop, options.stderr_stream, &child_stdio[2], PIPE_ACCESS_INBOUND, GENERIC_WRITE, 0); } else { err = duplicate_std_handle(loop, STD_ERROR_HANDLE, &child_stdio[2]); } if (err) { goto done; } 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[0]; startup.hStdOutput = child_stdio[1]; startup.hStdError = child_stdio[2]; if (CreateProcessW(application_path, arguments, NULL, NULL, 1, CREATE_UNICODE_ENVIRONMENT, env, cwd, &startup, &info)) { /* Spawn succeeded */ process->process_handle = info.hProcess; process->pid = info.dwProcessId; if (options.stdin_stream && options.stdin_stream->ipc) { options.stdin_stream->ipc_pid = info.dwProcessId; } /* Setup notifications for when the child process exits. */ result = RegisterWaitForSingleObject(&process->wait_handle, process->process_handle, exit_wait_callback, (void*)process, INFINITE, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE); if (!result) { uv_fatal_error(GetLastError(), "RegisterWaitForSingleObject"); } CloseHandle(info.hThread); } else { /* CreateProcessW failed, but this failure should be delivered */ /* asynchronously to retain unix compatibility. So pretent spawn */ /* succeeded, and start a thread instead that prints an error */ /* to the child's intended stderr. */ process->spawn_errno = GetLastError(); keep_child_stdio_open = 1; if (!QueueUserWorkItem(spawn_failure, process, WT_EXECUTEDEFAULT)) { uv_fatal_error(GetLastError(), "QueueUserWorkItem"); } } done: free(application); if (application_path != application) { free(application_path); } free(arguments); free(cwd); free(env); free(path); /* Under normal circumstances we should close the stdio handles now - */ /* the child now has its own duplicates, or something went horribly wrong. */ /* The only exception is when CreateProcess has failed, then we actually */ /* need to keep the stdio handles to report the error asynchronously. */ if (!keep_child_stdio_open) { close_child_stdio(process); } else { /* We're keeping the handles open, the thread pool is going to have */ /* it's way with them. But at least make them noninheritable. */ int i; for (i = 0; i < COUNTOF(process->child_stdio); i++) { SetHandleInformation(child_stdio[i], HANDLE_FLAG_INHERIT, 0); } } if (err) { if (process->wait_handle != INVALID_HANDLE_VALUE) { UnregisterWait(process->wait_handle); process->wait_handle = INVALID_HANDLE_VALUE; } if (process->process_handle != INVALID_HANDLE_VALUE) { CloseHandle(process->process_handle); process->process_handle = INVALID_HANDLE_VALUE; } } return err; }
int uv_spawn(uv_process_t* process, uv_process_options_t options) { int err = 0, i; wchar_t* path; int size; wchar_t* application_path, *application, *arguments, *env, *cwd; STARTUPINFOW startup; PROCESS_INFORMATION info; uv_process_init(process); process->exit_cb = options.exit_cb; UTF8_TO_UTF16(options.file, application); arguments = options.args ? make_program_args(options.args, options.windows_verbatim_arguments) : NULL; env = options.env ? make_program_env(options.env) : NULL; if (options.cwd) { UTF8_TO_UTF16(options.cwd, cwd); } else { size = GetCurrentDirectoryW(0, NULL) * sizeof(wchar_t); if (size) { cwd = (wchar_t*)malloc(size); if (!cwd) { uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); } GetCurrentDirectoryW(size, cwd); } else { uv_set_sys_error(GetLastError()); err = -1; goto done; } } /* Get PATH env. variable. */ size = GetEnvironmentVariableW(L"PATH", NULL, 0) + 1; path = (wchar_t*)malloc(size * sizeof(wchar_t)); if (!path) { uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); } GetEnvironmentVariableW(L"PATH", path, size * sizeof(wchar_t)); path[size - 1] = L'\0'; application_path = search_path(application, cwd, path, DEFAULT_PATH_EXT); if (!application_path) { /* CreateProcess will fail, but this allows us to pass this error to */ /* the user asynchronously. */ application_path = application; } /* Create stdio pipes. */ if (options.stdin_stream) { err = uv_create_stdio_pipe_pair(options.stdin_stream, &process->stdio_pipes[0].child_pipe, PIPE_ACCESS_OUTBOUND, GENERIC_READ | FILE_WRITE_ATTRIBUTES); if (err) { goto done; } process->stdio_pipes[0].server_pipe = options.stdin_stream; } if (options.stdout_stream) { err = uv_create_stdio_pipe_pair(options.stdout_stream, &process->stdio_pipes[1].child_pipe, PIPE_ACCESS_INBOUND, GENERIC_WRITE); if (err) { goto done; } process->stdio_pipes[1].server_pipe = options.stdout_stream; } if (options.stderr_stream) { err = uv_create_stdio_pipe_pair(options.stderr_stream, &process->stdio_pipes[2].child_pipe, PIPE_ACCESS_INBOUND, GENERIC_WRITE); if (err) { goto done; } process->stdio_pipes[2].server_pipe = options.stderr_stream; } 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 = process->stdio_pipes[0].child_pipe; startup.hStdOutput = process->stdio_pipes[1].child_pipe; startup.hStdError = process->stdio_pipes[2].child_pipe; if (CreateProcessW(application_path, arguments, NULL, NULL, 1, CREATE_UNICODE_ENVIRONMENT, env, cwd, &startup, &info)) { /* Spawn succeeded */ process->process_handle = info.hProcess; process->pid = info.dwProcessId; /* Setup notifications for when the child process exits. */ if (!RegisterWaitForSingleObject(&process->wait_handle, process->process_handle, exit_wait_callback, (void*)process, INFINITE, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE)) { uv_fatal_error(GetLastError(), "RegisterWaitForSingleObject"); } CloseHandle(info.hThread); } else { /* CreateProcessW failed, but this failure should be delivered */ /* asynchronously to retain unix compatibility. So pretent spawn */ /* succeeded, and start a thread instead that prints an error */ /* to the child's intended stderr. */ process->spawn_errno = GetLastError(); if (!QueueUserWorkItem(spawn_failure, process, WT_EXECUTEDEFAULT)) { uv_fatal_error(GetLastError(), "QueueUserWorkItem"); } } done: free(application); if (application_path != application) { free(application_path); } free(arguments); free(cwd); free(env); free(path); if (err) { for (i = 0; i < COUNTOF(process->stdio_pipes); i++) { if (process->stdio_pipes[i].child_pipe != INVALID_HANDLE_VALUE) { CloseHandle(process->stdio_pipes[i].child_pipe); process->stdio_pipes[i].child_pipe = INVALID_HANDLE_VALUE; } } if (process->wait_handle != INVALID_HANDLE_VALUE) { UnregisterWait(process->wait_handle); process->wait_handle = INVALID_HANDLE_VALUE; } if (process->process_handle != INVALID_HANDLE_VALUE) { CloseHandle(process->process_handle); process->process_handle = INVALID_HANDLE_VALUE; } } return err; }