#define PROCESS_WIN32_PRIVATE
#include "lib/intmath/cmp.h"
#include "lib/buf/buffers.h"
#include "lib/net/buffers_net.h"
#include "lib/container/smartlist.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#include "lib/log/win32err.h"
#include "lib/process/process.h"
#include "lib/process/process_win32.h"
#include "lib/process/env.h"
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef _WIN32
#define BUFFER_SIZE (1024)
static periodic_timer_t *periodic_timer;
struct process_win32_handle_t {
HANDLE pipe;
bool reached_eof;
size_t data_available;
char buffer[BUFFER_SIZE];
OVERLAPPED overlapped;
bool busy;
};
struct process_win32_t {
process_win32_handle_t stdin_handle;
process_win32_handle_t stdout_handle;
process_win32_handle_t stderr_handle;
PROCESS_INFORMATION process_information;
};
process_win32_t *
process_win32_new(void)
{
process_win32_t *win32_process;
win32_process = tor_malloc_zero(sizeof(process_win32_t));
win32_process->stdin_handle.pipe = INVALID_HANDLE_VALUE;
win32_process->stdout_handle.pipe = INVALID_HANDLE_VALUE;
win32_process->stderr_handle.pipe = INVALID_HANDLE_VALUE;
return win32_process;
}
void
process_win32_free_(process_win32_t *win32_process)
{
if (! win32_process)
return;
process_win32_cleanup_handle(&win32_process->stdin_handle);
process_win32_cleanup_handle(&win32_process->stdout_handle);
process_win32_cleanup_handle(&win32_process->stderr_handle);
tor_free(win32_process);
}
void
process_win32_init(void)
{
}
void
process_win32_deinit(void)
{
if (process_win32_timer_running())
process_win32_timer_stop();
}
process_status_t
process_win32_exec(process_t *process)
{
tor_assert(process);
process_win32_t *win32_process = process_get_win32_process(process);
HANDLE stdout_pipe_read = NULL;
HANDLE stdout_pipe_write = NULL;
HANDLE stderr_pipe_read = NULL;
HANDLE stderr_pipe_write = NULL;
HANDLE stdin_pipe_read = NULL;
HANDLE stdin_pipe_write = NULL;
BOOL ret = FALSE;
SECURITY_ATTRIBUTES security_attributes;
security_attributes.nLength = sizeof(security_attributes);
security_attributes.bInheritHandle = TRUE;
security_attributes.lpSecurityDescriptor = NULL;
if (! process_win32_create_pipe(&stdout_pipe_read,
&stdout_pipe_write,
&security_attributes,
PROCESS_WIN32_PIPE_TYPE_READER)) {
return PROCESS_STATUS_ERROR;
}
if (! process_win32_create_pipe(&stderr_pipe_read,
&stderr_pipe_write,
&security_attributes,
PROCESS_WIN32_PIPE_TYPE_READER)) {
return PROCESS_STATUS_ERROR;
}
if (! process_win32_create_pipe(&stdin_pipe_read,
&stdin_pipe_write,
&security_attributes,
PROCESS_WIN32_PIPE_TYPE_WRITER)) {
return PROCESS_STATUS_ERROR;
}
STARTUPINFOA startup_info;
memset(&startup_info, 0, sizeof(startup_info));
startup_info.cb = sizeof(startup_info);
startup_info.hStdError = stderr_pipe_write;
startup_info.hStdOutput = stdout_pipe_write;
startup_info.hStdInput = stdin_pipe_read;
startup_info.dwFlags |= STARTF_USESTDHANDLES;
process_environment_t *env = process_get_environment(process);
char **argv = process_get_argv(process);
char *joined_argv = tor_join_win_cmdline((const char **)argv);
ret = CreateProcessA(NULL,
joined_argv,
NULL,
NULL,
TRUE,
CREATE_NO_WINDOW,
env->windows_environment_block[0] == '\0' ?
NULL : env->windows_environment_block,
NULL,
&startup_info,
&win32_process->process_information);
tor_free(argv);
tor_free(joined_argv);
process_environment_free(env);
if (! ret) {
log_warn(LD_PROCESS, "CreateProcessA() failed: %s",
format_win32_error(GetLastError()));
CloseHandle(stdout_pipe_read);
CloseHandle(stdout_pipe_write);
CloseHandle(stderr_pipe_read);
CloseHandle(stderr_pipe_write);
CloseHandle(stdin_pipe_read);
CloseHandle(stdin_pipe_write);
process_notify_event_exit(process, 0);
return PROCESS_STATUS_ERROR;
}
win32_process->stdout_handle.pipe = stdout_pipe_read;
win32_process->stderr_handle.pipe = stderr_pipe_read;
win32_process->stdin_handle.pipe = stdin_pipe_write;
CloseHandle(stdout_pipe_write);
CloseHandle(stderr_pipe_write);
CloseHandle(stdin_pipe_read);
win32_process->stdout_handle.overlapped.hEvent = (HANDLE)process;
win32_process->stderr_handle.overlapped.hEvent = (HANDLE)process;
win32_process->stdin_handle.overlapped.hEvent = (HANDLE)process;
if (! process_win32_timer_running())
process_win32_timer_start();
process_notify_event_stdout(process);
process_notify_event_stderr(process);
return PROCESS_STATUS_RUNNING;
}
bool
process_win32_terminate(process_t *process)
{
tor_assert(process);
process_win32_t *win32_process = process_get_win32_process(process);
BOOL ret;
ret = TerminateProcess(win32_process->process_information.hProcess, 0);
if (! ret) {
log_warn(LD_PROCESS, "TerminateProcess() failed: %s",
format_win32_error(GetLastError()));
return false;
}
process_win32_cleanup_handle(&win32_process->stdin_handle);
process_win32_cleanup_handle(&win32_process->stdout_handle);
process_win32_cleanup_handle(&win32_process->stderr_handle);
return true;
}
process_pid_t
process_win32_get_pid(process_t *process)
{
tor_assert(process);
process_win32_t *win32_process = process_get_win32_process(process);
return (process_pid_t)win32_process->process_information.dwProcessId;
}
int
process_win32_write(struct process_t *process, buf_t *buffer)
{
tor_assert(process);
tor_assert(buffer);
process_win32_t *win32_process = process_get_win32_process(process);
BOOL ret = FALSE;
DWORD error_code = 0;
const size_t buffer_size = buf_datalen(buffer);
if (win32_process->stdin_handle.busy)
return 0;
if (buffer_size == 0)
return 0;
if (BUG(win32_process->stdin_handle.reached_eof))
return 0;
const size_t write_size = MIN(buffer_size,
sizeof(win32_process->stdin_handle.buffer));
buf_get_bytes(buffer, win32_process->stdin_handle.buffer, write_size);
SetLastError(0);
ret = WriteFileEx(win32_process->stdin_handle.pipe,
win32_process->stdin_handle.buffer,
write_size,
&win32_process->stdin_handle.overlapped,
process_win32_stdin_write_done);
if (! ret) {
error_code = GetLastError();
if (error_code == ERROR_HANDLE_EOF || error_code == ERROR_BROKEN_PIPE) {
log_debug(LD_PROCESS, "WriteFileEx() returned EOF from pipe: %s",
format_win32_error(error_code));
} else {
log_warn(LD_PROCESS, "WriteFileEx() failed: %s",
format_win32_error(error_code));
}
win32_process->stdin_handle.reached_eof = true;
return 0;
}
error_code = GetLastError();
if (error_code != ERROR_SUCCESS) {
log_warn(LD_PROCESS, "WriteFileEx() failed after returning success: %s",
format_win32_error(error_code));
win32_process->stdin_handle.reached_eof = true;
return 0;
}
return (int)write_size;
}
int
process_win32_read_stdout(struct process_t *process, buf_t *buffer)
{
tor_assert(process);
tor_assert(buffer);
process_win32_t *win32_process = process_get_win32_process(process);
return process_win32_read_from_handle(&win32_process->stdout_handle,
buffer,
process_win32_stdout_read_done);
}
int
process_win32_read_stderr(struct process_t *process, buf_t *buffer)
{
tor_assert(process);
tor_assert(buffer);
process_win32_t *win32_process = process_get_win32_process(process);
return process_win32_read_from_handle(&win32_process->stderr_handle,
buffer,
process_win32_stderr_read_done);
}
void
process_win32_trigger_completion_callbacks(void)
{
DWORD ret;
ret = SleepEx(0, TRUE);
if (ret != 0 && ret != WAIT_IO_COMPLETION) {
log_warn(LD_PROCESS, "SleepEx() returned %lu", ret);
}
}
void
process_win32_timer_start(void)
{
if (BUG(process_win32_timer_running()))
return;
static const struct timeval interval = {1, 0};
log_info(LD_PROCESS, "Starting Windows Process I/O timer");
periodic_timer = periodic_timer_new(tor_libevent_get_base(),
&interval,
process_win32_timer_callback,
NULL);
}
void
process_win32_timer_stop(void)
{
if (BUG(periodic_timer == NULL))
return;
log_info(LD_PROCESS, "Stopping Windows Process I/O timer");
periodic_timer_free(periodic_timer);
}
bool
process_win32_timer_running(void)
{
return periodic_timer != NULL;
}
STATIC void
process_win32_timer_callback(periodic_timer_t *timer, void *data)
{
tor_assert(timer == periodic_timer);
tor_assert(data == NULL);
process_win32_trigger_completion_callbacks();
bool done;
do {
const smartlist_t *processes = process_get_all_processes();
done = true;
SMARTLIST_FOREACH_BEGIN(processes, process_t *, process) {
if (process_win32_timer_test_process(process)) {
done = false;
break;
}
} SMARTLIST_FOREACH_END(process);
} while (! done);
}
STATIC bool
process_win32_timer_test_process(process_t *process)
{
tor_assert(process);
if (process_get_status(process) != PROCESS_STATUS_RUNNING)
return false;
process_win32_t *win32_process = process_get_win32_process(process);
BOOL ret = FALSE;
DWORD exit_code = 0;
if (! win32_process->stdout_handle.reached_eof)
return false;
if (! win32_process->stderr_handle.reached_eof)
return false;
ret = GetExitCodeProcess(win32_process->process_information.hProcess,
&exit_code);
if (! ret) {
log_warn(LD_PROCESS, "GetExitCodeProcess() failed: %s",
format_win32_error(GetLastError()));
return false;
}
if (exit_code != STILL_ACTIVE) {
process_notify_event_exit(process, exit_code);
return true;
}
return false;
}
STATIC bool
process_win32_create_pipe(HANDLE *read_pipe,
HANDLE *write_pipe,
SECURITY_ATTRIBUTES *attributes,
process_win32_pipe_type_t pipe_type)
{
tor_assert(read_pipe);
tor_assert(write_pipe);
tor_assert(attributes);
BOOL ret = FALSE;
const size_t size = 4096;
DWORD read_mode = 0;
DWORD write_mode = 0;
char pipe_name[MAX_PATH];
static DWORD process_id = 0;
static DWORD counter = 0;
if (process_id == 0)
process_id = GetCurrentProcessId();
tor_snprintf(pipe_name, sizeof(pipe_name),
"\\\\.\\Pipe\\Tor-Process-Pipe-%lu-%lu",
process_id, counter++);
switch (pipe_type) {
case PROCESS_WIN32_PIPE_TYPE_READER:
read_mode = FILE_FLAG_OVERLAPPED;
break;
case PROCESS_WIN32_PIPE_TYPE_WRITER:
write_mode = FILE_FLAG_OVERLAPPED;
break;
default:
tor_assert_nonfatal_unreached_once();
}
HANDLE read_handle;
HANDLE write_handle;
read_handle = CreateNamedPipeA(pipe_name,
(PIPE_ACCESS_INBOUND|read_mode),
(PIPE_TYPE_BYTE|PIPE_WAIT),
1,
size,
size,
1000,
attributes);
if (read_handle == INVALID_HANDLE_VALUE) {
log_warn(LD_PROCESS, "CreateNamedPipeA() failed: %s",
format_win32_error(GetLastError()));
return false;
}
write_handle = CreateFileA(pipe_name,
GENERIC_WRITE,
0,
attributes,
OPEN_EXISTING,
(FILE_ATTRIBUTE_NORMAL|write_mode),
NULL);
if (write_handle == INVALID_HANDLE_VALUE) {
log_warn(LD_PROCESS, "CreateFileA() failed: %s",
format_win32_error(GetLastError()));
CloseHandle(read_handle);
return false;
}
switch (pipe_type) {
case PROCESS_WIN32_PIPE_TYPE_READER:
ret = SetHandleInformation(read_handle, HANDLE_FLAG_INHERIT, 0);
break;
case PROCESS_WIN32_PIPE_TYPE_WRITER:
ret = SetHandleInformation(write_handle, HANDLE_FLAG_INHERIT, 0);
break;
default:
tor_assert_nonfatal_unreached_once();
}
if (! ret) {
log_warn(LD_PROCESS, "SetHandleInformation() failed: %s",
format_win32_error(GetLastError()));
CloseHandle(read_handle);
CloseHandle(write_handle);
return false;
}
*read_pipe = read_handle;
*write_pipe = write_handle;
return true;
}
STATIC void
process_win32_cleanup_handle(process_win32_handle_t *handle)
{
tor_assert(handle);
#if 0#endif
if (handle->pipe != INVALID_HANDLE_VALUE) {
CloseHandle(handle->pipe);
handle->pipe = INVALID_HANDLE_VALUE;
handle->reached_eof = true;
}
}
STATIC VOID WINAPI
process_win32_stdout_read_done(DWORD error_code,
DWORD byte_count,
LPOVERLAPPED overlapped)
{
tor_assert(overlapped);
tor_assert(overlapped->hEvent);
process_t *process = (process_t *)overlapped->hEvent;
process_win32_t *win32_process = process_get_win32_process(process);
if (process_win32_handle_read_completion(&win32_process->stdout_handle,
error_code,
byte_count)) {
process_notify_event_stdout(process);
}
}
STATIC VOID WINAPI
process_win32_stderr_read_done(DWORD error_code,
DWORD byte_count,
LPOVERLAPPED overlapped)
{
tor_assert(overlapped);
tor_assert(overlapped->hEvent);
process_t *process = (process_t *)overlapped->hEvent;
process_win32_t *win32_process = process_get_win32_process(process);
if (process_win32_handle_read_completion(&win32_process->stderr_handle,
error_code,
byte_count)) {
process_notify_event_stderr(process);
}
}
STATIC VOID WINAPI
process_win32_stdin_write_done(DWORD error_code,
DWORD byte_count,
LPOVERLAPPED overlapped)
{
tor_assert(overlapped);
tor_assert(overlapped->hEvent);
(void)byte_count;
process_t *process = (process_t *)overlapped->hEvent;
process_win32_t *win32_process = process_get_win32_process(process);
win32_process->stdin_handle.busy = false;
if (BUG(win32_process->stdin_handle.reached_eof))
return;
if (error_code == 0) {
win32_process->stdin_handle.data_available = 0;
memset(win32_process->stdin_handle.buffer, 0,
sizeof(win32_process->stdin_handle.buffer));
process_notify_event_stdin(process);
} else if (error_code == ERROR_HANDLE_EOF ||
error_code == ERROR_BROKEN_PIPE) {
tor_assert(byte_count == 0);
win32_process->stdin_handle.reached_eof = true;
} else {
log_warn(LD_PROCESS,
"Error in I/O completion routine from WriteFileEx(): %s",
format_win32_error(error_code));
win32_process->stdin_handle.reached_eof = true;
}
}
STATIC int
process_win32_read_from_handle(process_win32_handle_t *handle,
buf_t *buffer,
LPOVERLAPPED_COMPLETION_ROUTINE callback)
{
tor_assert(handle);
tor_assert(buffer);
tor_assert(callback);
BOOL ret = FALSE;
int bytes_available = 0;
DWORD error_code = 0;
if (BUG(handle->busy))
return 0;
if (BUG(handle->reached_eof))
return 0;
bytes_available = (int)handle->data_available;
if (handle->data_available > 0) {
buf_add(buffer, handle->buffer, handle->data_available);
handle->data_available = 0;
memset(handle->buffer, 0, sizeof(handle->buffer));
}
SetLastError(0);
ret = ReadFileEx(handle->pipe,
handle->buffer,
sizeof(handle->buffer),
&handle->overlapped,
callback);
if (! ret) {
error_code = GetLastError();
if (error_code == ERROR_HANDLE_EOF || error_code == ERROR_BROKEN_PIPE) {
log_debug(LD_PROCESS, "ReadFileEx() returned EOF from pipe: %s",
format_win32_error(error_code));
} else {
log_warn(LD_PROCESS, "ReadFileEx() failed: %s",
format_win32_error(error_code));
}
handle->reached_eof = true;
return bytes_available;
}
error_code = GetLastError();
if (error_code != ERROR_SUCCESS) {
log_warn(LD_PROCESS, "ReadFileEx() failed after returning success: %s",
format_win32_error(error_code));
handle->reached_eof = true;
return bytes_available;
}
handle->busy = true;
return bytes_available;
}
STATIC bool
process_win32_handle_read_completion(process_win32_handle_t *handle,
DWORD error_code,
DWORD byte_count)
{
tor_assert(handle);
handle->busy = false;
if (error_code == 0) {
tor_assert(byte_count <= BUFFER_SIZE);
handle->data_available = (size_t)byte_count;
return true;
} else if (error_code == ERROR_HANDLE_EOF ||
error_code == ERROR_BROKEN_PIPE) {
tor_assert(byte_count == 0);
handle->reached_eof = true;
} else {
log_warn(LD_PROCESS,
"Error in I/O completion routine from ReadFileEx(): %s",
format_win32_error(error_code));
handle->reached_eof = true;
}
return false;
}
STATIC char *
format_win_cmdline_argument(const char *arg)
{
char *formatted_arg;
char need_quotes;
const char *c;
int i;
int bs_counter = 0;
const char backslash = '\\';
smartlist_t *arg_chars;
arg_chars = smartlist_new();
need_quotes = (strchr(arg, ' ') || strchr(arg, '\t') || '\0' == arg[0]);
for (c=arg; *c != '\0'; c++) {
if ('"' == *c) {
for (i=0; i<(bs_counter*2); i++)
smartlist_add(arg_chars, (void*)&backslash);
bs_counter = 0;
smartlist_add(arg_chars, (void*)&backslash);
smartlist_add(arg_chars, (void*)c);
} else if ('\\' == *c) {
bs_counter++;
} else {
for (i=0; i<bs_counter; i++)
smartlist_add(arg_chars, (void*)&backslash);
bs_counter = 0;
smartlist_add(arg_chars, (void*)c);
}
}
for (i=0; i<bs_counter; i++)
smartlist_add(arg_chars, (void*)&backslash);
const size_t formatted_arg_len = smartlist_len(arg_chars) +
(need_quotes ? 2 : 0) + 1;
formatted_arg = tor_malloc_zero(formatted_arg_len);
i=0;
if (need_quotes)
formatted_arg[i++] = '"';
SMARTLIST_FOREACH(arg_chars, char*, ch,
{
formatted_arg[i++] = *ch;
});
if (need_quotes)
formatted_arg[i++] = '"';
formatted_arg[i] = '\0';
smartlist_free(arg_chars);
return formatted_arg;
}
STATIC char *
tor_join_win_cmdline(const char *argv[])
{
smartlist_t *argv_list;
char *joined_argv;
int i;
argv_list = smartlist_new();
for (i=0; argv[i] != NULL; i++) {
smartlist_add(argv_list, (void *)format_win_cmdline_argument(argv[i]));
}
joined_argv = smartlist_join_strings(argv_list, " ", 0, NULL);
SMARTLIST_FOREACH(argv_list, char *, arg,
{
tor_free(arg);
});
smartlist_free(argv_list);
return joined_argv;
}
#endif