#define PROCESS_PRIVATE
#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/process/process.h"
#include "lib/process/process_unix.h"
#include "lib/process/process_win32.h"
#include "lib/process/env.h"
#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif
static smartlist_t *processes;
static int may_spawn_background_process = 1;
struct process_t {
process_status_t status;
process_protocol_t protocol;
process_read_callback_t stdout_read_callback;
process_read_callback_t stderr_read_callback;
process_exit_callback_t exit_callback;
process_exit_code_t exit_code;
char *command;
smartlist_t *arguments;
smartlist_t *environment;
buf_t *stdout_buffer;
buf_t *stderr_buffer;
buf_t *stdin_buffer;
void *data;
#ifndef _WIN32
process_unix_t *unix_process;
#else
process_win32_t *win32_process;
#endif
};
const char *
process_status_to_string(process_status_t status)
{
switch (status) {
case PROCESS_STATUS_NOT_RUNNING:
return "not running";
case PROCESS_STATUS_RUNNING:
return "running";
case PROCESS_STATUS_ERROR:
return "error";
}
tor_assert_unreached();
return NULL;
}
const char *
process_protocol_to_string(process_protocol_t protocol)
{
switch (protocol) {
case PROCESS_PROTOCOL_LINE:
return "Line";
case PROCESS_PROTOCOL_RAW:
return "Raw";
}
tor_assert_unreached();
return NULL;
}
void
tor_disable_spawning_background_processes(void)
{
may_spawn_background_process = 0;
}
void
process_init(void)
{
processes = smartlist_new();
#ifdef _WIN32
process_win32_init();
#endif
}
void
process_free_all(void)
{
#ifdef _WIN32
process_win32_deinit();
#endif
SMARTLIST_FOREACH(processes, process_t *, x, process_free(x));
smartlist_free(processes);
}
const smartlist_t *
process_get_all_processes(void)
{
return processes;
}
process_t *
process_new(const char *command)
{
tor_assert(command);
process_t *process;
process = tor_malloc_zero(sizeof(process_t));
process->command = tor_strdup(command);
process->status = PROCESS_STATUS_NOT_RUNNING;
process->arguments = smartlist_new();
process->environment = smartlist_new();
process->stdout_buffer = buf_new();
process->stderr_buffer = buf_new();
process->stdin_buffer = buf_new();
#ifndef _WIN32
process->unix_process = process_unix_new();
#else
process->win32_process = process_win32_new();
#endif
smartlist_add(processes, process);
return process;
}
void
process_free_(process_t *process)
{
if (! process)
return;
tor_free(process->command);
SMARTLIST_FOREACH(process->arguments, char *, x, tor_free(x));
smartlist_free(process->arguments);
SMARTLIST_FOREACH(process->environment, char *, x, tor_free(x));
smartlist_free(process->environment);
buf_free(process->stdout_buffer);
buf_free(process->stderr_buffer);
buf_free(process->stdin_buffer);
#ifndef _WIN32
process_unix_free(process->unix_process);
#else
process_win32_free(process->win32_process);
#endif
smartlist_remove(processes, process);
tor_free(process);
}
process_status_t
process_exec(process_t *process)
{
tor_assert(process);
if (BUG(may_spawn_background_process == 0))
return PROCESS_STATUS_ERROR;
process_status_t status = PROCESS_STATUS_NOT_RUNNING;
log_info(LD_PROCESS, "Starting new process: %s", process->command);
#ifndef _WIN32
status = process_unix_exec(process);
#else
status = process_win32_exec(process);
#endif
process_set_status(process, status);
if (status != PROCESS_STATUS_RUNNING) {
log_warn(LD_PROCESS, "Failed to start process: %s",
process_get_command(process));
}
return status;
}
bool
process_terminate(process_t *process)
{
tor_assert(process);
if (process_get_status(process) != PROCESS_STATUS_RUNNING)
return false;
log_debug(LD_PROCESS, "Terminating process");
#ifndef _WIN32
return process_unix_terminate(process);
#else
return process_win32_terminate(process);
#endif
}
process_pid_t
process_get_pid(process_t *process)
{
tor_assert(process);
#ifndef _WIN32
return process_unix_get_pid(process);
#else
return process_win32_get_pid(process);
#endif
}
void
process_set_stdout_read_callback(process_t *process,
process_read_callback_t callback)
{
tor_assert(process);
process->stdout_read_callback = callback;
}
void
process_set_stderr_read_callback(process_t *process,
process_read_callback_t callback)
{
tor_assert(process);
process->stderr_read_callback = callback;
}
void
process_set_exit_callback(process_t *process,
process_exit_callback_t callback)
{
tor_assert(process);
process->exit_callback = callback;
}
const char *
process_get_command(const process_t *process)
{
tor_assert(process);
return process->command;
}
void
process_set_protocol(process_t *process, process_protocol_t protocol)
{
tor_assert(process);
process->protocol = protocol;
}
process_protocol_t
process_get_protocol(const process_t *process)
{
tor_assert(process);
return process->protocol;
}
void
process_set_data(process_t *process, void *data)
{
tor_assert(process);
process->data = data;
}
void *
process_get_data(const process_t *process)
{
tor_assert(process);
return process->data;
}
void
process_set_status(process_t *process, process_status_t status)
{
tor_assert(process);
process->status = status;
}
process_status_t
process_get_status(const process_t *process)
{
tor_assert(process);
return process->status;
}
void
process_append_argument(process_t *process, const char *argument)
{
tor_assert(process);
tor_assert(argument);
smartlist_add(process->arguments, tor_strdup(argument));
}
const smartlist_t *
process_get_arguments(const process_t *process)
{
tor_assert(process);
return process->arguments;
}
char **
process_get_argv(const process_t *process)
{
tor_assert(process);
char **argv = NULL;
char *filename = process->command;
const smartlist_t *arguments = process->arguments;
const size_t size = smartlist_len(arguments);
argv = tor_malloc_zero(sizeof(char *) * (size + 2));
argv[0] = filename;
SMARTLIST_FOREACH_BEGIN(arguments, char *, arg_val) {
tor_assert(arg_val != NULL);
argv[arg_val_sl_idx + 1] = arg_val;
} SMARTLIST_FOREACH_END(arg_val);
return argv;
}
void
process_reset_environment(process_t *process, const smartlist_t *env)
{
tor_assert(process);
tor_assert(env);
SMARTLIST_FOREACH(process->environment, char *, x, tor_free(x));
smartlist_free(process->environment);
process->environment = smartlist_new();
SMARTLIST_FOREACH(env, char *, x,
smartlist_add(process->environment, tor_strdup(x)));
}
void
process_set_environment(process_t *process,
const char *key,
const char *value)
{
tor_assert(process);
tor_assert(key);
tor_assert(value);
smartlist_add_asprintf(process->environment, "%s=%s", key, value);
}
process_environment_t *
process_get_environment(const process_t *process)
{
tor_assert(process);
return process_environment_make(process->environment);
}
#ifndef _WIN32
process_unix_t *
process_get_unix_process(const process_t *process)
{
tor_assert(process);
tor_assert(process->unix_process);
return process->unix_process;
}
#else
process_win32_t *
process_get_win32_process(const process_t *process)
{
tor_assert(process);
tor_assert(process->win32_process);
return process->win32_process;
}
#endif
void
process_write(process_t *process,
const uint8_t *data, size_t size)
{
tor_assert(process);
tor_assert(data);
buf_add(process->stdin_buffer, (char *)data, size);
process_write_stdin(process, process->stdin_buffer);
}
void
process_vprintf(process_t *process,
const char *format, va_list args)
{
tor_assert(process);
tor_assert(format);
int size;
char *data;
size = tor_vasprintf(&data, format, args);
tor_assert(data != NULL);
process_write(process, (uint8_t *)data, size);
tor_free(data);
}
void
process_printf(process_t *process,
const char *format, ...)
{
tor_assert(process);
tor_assert(format);
va_list ap;
va_start(ap, format);
process_vprintf(process, format, ap);
va_end(ap);
}
void
process_notify_event_stdout(process_t *process)
{
tor_assert(process);
int ret;
ret = process_read_stdout(process, process->stdout_buffer);
if (ret > 0)
process_read_data(process,
process->stdout_buffer,
process->stdout_read_callback);
}
void
process_notify_event_stderr(process_t *process)
{
tor_assert(process);
int ret;
ret = process_read_stderr(process, process->stderr_buffer);
if (ret > 0)
process_read_data(process,
process->stderr_buffer,
process->stderr_read_callback);
}
void
process_notify_event_stdin(process_t *process)
{
tor_assert(process);
process_write_stdin(process, process->stdin_buffer);
}
void
process_notify_event_exit(process_t *process, process_exit_code_t exit_code)
{
tor_assert(process);
log_debug(LD_PROCESS,
"Process terminated with exit code: %"PRIu64, exit_code);
process_set_status(process, PROCESS_STATUS_NOT_RUNNING);
process->exit_code = exit_code;
bool free_process_handle = false;
if (process->exit_callback)
free_process_handle = process->exit_callback(process, exit_code);
if (free_process_handle)
process_free(process);
}
MOCK_IMPL(STATIC int, process_read_stdout, (process_t *process, buf_t *buffer))
{
tor_assert(process);
tor_assert(buffer);
#ifndef _WIN32
return process_unix_read_stdout(process, buffer);
#else
return process_win32_read_stdout(process, buffer);
#endif
}
MOCK_IMPL(STATIC int, process_read_stderr, (process_t *process, buf_t *buffer))
{
tor_assert(process);
tor_assert(buffer);
#ifndef _WIN32
return process_unix_read_stderr(process, buffer);
#else
return process_win32_read_stderr(process, buffer);
#endif
}
MOCK_IMPL(STATIC void, process_write_stdin,
(process_t *process, buf_t *buffer))
{
tor_assert(process);
tor_assert(buffer);
#ifndef _WIN32
process_unix_write(process, buffer);
#else
process_win32_write(process, buffer);
#endif
}
STATIC void
process_read_data(process_t *process,
buf_t *buffer,
process_read_callback_t callback)
{
tor_assert(process);
tor_assert(buffer);
switch (process_get_protocol(process)) {
case PROCESS_PROTOCOL_RAW:
process_read_buffer(process, buffer, callback);
break;
case PROCESS_PROTOCOL_LINE:
process_read_lines(process, buffer, callback);
break;
default:
tor_assert_unreached();
return;
}
}
STATIC void
process_read_buffer(process_t *process,
buf_t *buffer,
process_read_callback_t callback)
{
tor_assert(process);
tor_assert(buffer);
const size_t size = buf_datalen(buffer);
char *data = tor_malloc_zero(size + 1);
buf_get_bytes(buffer, data, size);
log_debug(LD_PROCESS, "Read data from process");
if (callback)
callback(process, data, size);
tor_free(data);
}
STATIC void
process_read_lines(process_t *process,
buf_t *buffer,
process_read_callback_t callback)
{
tor_assert(process);
tor_assert(buffer);
const size_t size = buf_datalen(buffer) + 1;
size_t line_size = 0;
char *data = tor_malloc_zero(size);
int ret;
while (true) {
line_size = size;
ret = buf_get_line(buffer, data, &line_size);
tor_assert(ret != -1);
if (line_size >= 1 && data[line_size - 1] == '\n') {
data[line_size - 1] = '\0';
--line_size;
}
if (line_size >= 1 && data[line_size - 1] == '\r') {
data[line_size - 1] = '\0';
--line_size;
}
if (ret == 1) {
log_debug(LD_PROCESS, "Read line from process: \"%s\"", data);
if (callback)
callback(process, data, line_size);
continue;
}
tor_assert_nonfatal(ret == 0);
break;
}
tor_free(data);
}