#define PROCESS_UNIX_PRIVATE
#include "lib/intmath/cmp.h"
#include "lib/buf/buffers.h"
#include "lib/net/buffers_net.h"
#include "lib/container/smartlist.h"
#include "lib/evloop/compat_libevent.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/waitpid.h"
#include "lib/process/env.h"
#include <stdio.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if defined(HAVE_SYS_PRCTL_H) && defined(__linux__)
#include <sys/prctl.h>
#endif
#if HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifndef _WIN32
#define DEFAULT_MAX_FD 256
struct process_unix_handle_t {
int fd;
bool reached_eof;
struct event *event;
bool is_writing;
};
struct process_unix_t {
process_unix_handle_t stdin_handle;
process_unix_handle_t stdout_handle;
process_unix_handle_t stderr_handle;
pid_t pid;
waitpid_callback_t *waitpid;
};
process_unix_t *
process_unix_new(void)
{
process_unix_t *unix_process;
unix_process = tor_malloc_zero(sizeof(process_unix_t));
unix_process->stdin_handle.fd = -1;
unix_process->stderr_handle.fd = -1;
unix_process->stdout_handle.fd = -1;
return unix_process;
}
void
process_unix_free_(process_unix_t *unix_process)
{
if (! unix_process)
return;
clear_waitpid_callback(unix_process->waitpid);
unix_process->waitpid = NULL;
process_unix_close_file_descriptors(unix_process);
tor_event_free(unix_process->stdout_handle.event);
tor_event_free(unix_process->stderr_handle.event);
tor_event_free(unix_process->stdin_handle.event);
tor_free(unix_process);
}
process_status_t
process_unix_exec(process_t *process)
{
static int max_fd = -1;
process_unix_t *unix_process;
pid_t pid;
int stdin_pipe[2];
int stdout_pipe[2];
int stderr_pipe[2];
int retval, fd;
unix_process = process_get_unix_process(process);
retval = pipe(stdin_pipe);
if (-1 == retval) {
log_warn(LD_PROCESS,
"Unable to create pipe for stdin "
"communication with process: %s",
strerror(errno));
return PROCESS_STATUS_ERROR;
}
retval = pipe(stdout_pipe);
if (-1 == retval) {
log_warn(LD_PROCESS,
"Unable to create pipe for stdout "
"communication with process: %s",
strerror(errno));
close(stdin_pipe[0]);
close(stdin_pipe[1]);
return PROCESS_STATUS_ERROR;
}
retval = pipe(stderr_pipe);
if (-1 == retval) {
log_warn(LD_PROCESS,
"Unable to create pipe for stderr "
"communication with process: %s",
strerror(errno));
close(stdin_pipe[0]);
close(stdin_pipe[1]);
close(stdout_pipe[0]);
close(stdout_pipe[1]);
return PROCESS_STATUS_ERROR;
}
#ifdef _SC_OPEN_MAX
if (-1 == max_fd) {
max_fd = (int)sysconf(_SC_OPEN_MAX);
if (max_fd == -1) {
max_fd = DEFAULT_MAX_FD;
log_warn(LD_PROCESS,
"Cannot find maximum file descriptor, assuming: %d", max_fd);
}
}
#else
max_fd = DEFAULT_MAX_FD;
#endif
pid = fork();
if (0 == pid) {
#if defined(HAVE_SYS_PRCTL_H) && defined(__linux__)
prctl(PR_SET_PDEATHSIG, SIGTERM);
#endif
retval = dup2(stdout_pipe[1], STDOUT_FILENO);
if (-1 == retval)
goto error;
retval = dup2(stderr_pipe[1], STDERR_FILENO);
if (-1 == retval)
goto error;
retval = dup2(stdin_pipe[0], STDIN_FILENO);
if (-1 == retval)
goto error;
close(stderr_pipe[0]);
close(stderr_pipe[1]);
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stdin_pipe[0]);
close(stdin_pipe[1]);
for (fd = STDERR_FILENO + 1; fd < max_fd; fd++)
close(fd);
char **argv = process_get_argv(process);
process_environment_t *env = process_get_environment(process);
execve(argv[0], argv, env->unixoid_environment_block);
tor_free(argv);
process_environment_free(env);
error:
fprintf(stderr, "Error from child process: %s", strerror(errno));
_exit(1);
}
if (-1 == pid) {
log_warn(LD_PROCESS,
"Failed to create child process: %s", strerror(errno));
close(stdin_pipe[0]);
close(stdin_pipe[1]);
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stderr_pipe[0]);
close(stderr_pipe[1]);
return PROCESS_STATUS_ERROR;
}
unix_process->pid = pid;
unix_process->waitpid = set_waitpid_callback(pid,
process_unix_waitpid_callback,
process);
unix_process->stdout_handle.fd = stdout_pipe[0];
retval = close(stdout_pipe[1]);
if (-1 == retval) {
log_warn(LD_PROCESS, "Failed to close write end of standard out pipe: %s",
strerror(errno));
}
unix_process->stderr_handle.fd = stderr_pipe[0];
retval = close(stderr_pipe[1]);
if (-1 == retval) {
log_warn(LD_PROCESS,
"Failed to close write end of standard error pipe: %s",
strerror(errno));
}
unix_process->stdin_handle.fd = stdin_pipe[1];
retval = close(stdin_pipe[0]);
if (-1 == retval) {
log_warn(LD_PROCESS, "Failed to close read end of standard in pipe: %s",
strerror(errno));
}
process_unix_setup_handle(process,
&unix_process->stdout_handle,
EV_READ|EV_PERSIST,
stdout_read_callback);
process_unix_setup_handle(process,
&unix_process->stderr_handle,
EV_READ|EV_PERSIST,
stderr_read_callback);
process_unix_setup_handle(process,
&unix_process->stdin_handle,
EV_WRITE|EV_PERSIST,
stdin_write_callback);
process_unix_start_reading(&unix_process->stdout_handle);
process_unix_start_reading(&unix_process->stderr_handle);
return PROCESS_STATUS_RUNNING;
}
bool
process_unix_terminate(process_t *process)
{
tor_assert(process);
process_unix_t *unix_process = process_get_unix_process(process);
if (BUG(unix_process->waitpid == NULL))
return false;
bool success = true;
int ret;
ret = kill(unix_process->pid, SIGTERM);
if (ret == -1) {
log_warn(LD_PROCESS, "Unable to terminate process: %s",
strerror(errno));
success = false;
}
if (! process_unix_close_file_descriptors(unix_process))
success = false;
return success;
}
process_pid_t
process_unix_get_pid(process_t *process)
{
tor_assert(process);
process_unix_t *unix_process = process_get_unix_process(process);
return (process_pid_t)unix_process->pid;
}
int
process_unix_write(process_t *process, buf_t *buffer)
{
tor_assert(process);
tor_assert(buffer);
process_unix_t *unix_process = process_get_unix_process(process);
size_t buffer_flush_len = buf_datalen(buffer);
const size_t max_to_write = MIN(PROCESS_MAX_WRITE, buffer_flush_len);
if (buffer_flush_len > 0 && ! unix_process->stdin_handle.is_writing) {
process_unix_start_writing(&unix_process->stdin_handle);
return 0;
}
if (buffer_flush_len == 0 && unix_process->stdin_handle.is_writing) {
process_unix_stop_writing(&unix_process->stdin_handle);
return 0;
}
return buf_flush_to_pipe(buffer,
process_get_unix_process(process)->stdin_handle.fd,
max_to_write, &buffer_flush_len);
}
int
process_unix_read_stdout(process_t *process, buf_t *buffer)
{
tor_assert(process);
tor_assert(buffer);
process_unix_t *unix_process = process_get_unix_process(process);
return process_unix_read_handle(process,
&unix_process->stdout_handle,
buffer);
}
int
process_unix_read_stderr(process_t *process, buf_t *buffer)
{
tor_assert(process);
tor_assert(buffer);
process_unix_t *unix_process = process_get_unix_process(process);
return process_unix_read_handle(process,
&unix_process->stderr_handle,
buffer);
}
STATIC void
stdout_read_callback(evutil_socket_t fd, short event, void *data)
{
(void)fd;
(void)event;
process_t *process = data;
tor_assert(process);
process_notify_event_stdout(process);
}
STATIC void
stderr_read_callback(evutil_socket_t fd, short event, void *data)
{
(void)fd;
(void)event;
process_t *process = data;
tor_assert(process);
process_notify_event_stderr(process);
}
STATIC void
stdin_write_callback(evutil_socket_t fd, short event, void *data)
{
(void)fd;
(void)event;
process_t *process = data;
tor_assert(process);
process_notify_event_stdin(process);
}
STATIC void
process_unix_start_reading(process_unix_handle_t *handle)
{
tor_assert(handle);
if (event_add(handle->event, NULL))
log_warn(LD_PROCESS,
"Unable to add libevent event for handle.");
}
STATIC void
process_unix_stop_reading(process_unix_handle_t *handle)
{
tor_assert(handle);
if (handle->event == NULL)
return;
if (event_del(handle->event))
log_warn(LD_PROCESS,
"Unable to delete libevent event for handle.");
}
STATIC void
process_unix_start_writing(process_unix_handle_t *handle)
{
tor_assert(handle);
if (event_add(handle->event, NULL))
log_warn(LD_PROCESS,
"Unable to add libevent event for handle.");
handle->is_writing = true;
}
STATIC void
process_unix_stop_writing(process_unix_handle_t *handle)
{
tor_assert(handle);
if (handle->event == NULL)
return;
if (event_del(handle->event))
log_warn(LD_PROCESS,
"Unable to delete libevent event for handle.");
handle->is_writing = false;
}
STATIC void
process_unix_waitpid_callback(int status, void *data)
{
tor_assert(data);
process_t *process = data;
process_unix_t *unix_process = process_get_unix_process(process);
clear_waitpid_callback(unix_process->waitpid);
unix_process->waitpid = NULL;
process_notify_event_exit(process, status);
}
STATIC void
process_unix_setup_handle(process_t *process,
process_unix_handle_t *handle,
short flags,
event_callback_fn callback)
{
tor_assert(process);
tor_assert(handle);
tor_assert(callback);
if (fcntl(handle->fd, F_SETFL, O_NONBLOCK) < 0) {
log_warn(LD_PROCESS, "Unable mark Unix handle as non-blocking: %s",
strerror(errno));
}
handle->event = tor_event_new(tor_libevent_get_base(),
handle->fd,
flags,
callback,
process);
}
STATIC int
process_unix_read_handle(process_t *process,
process_unix_handle_t *handle,
buf_t *buffer)
{
tor_assert(process);
tor_assert(handle);
tor_assert(buffer);
int ret = 0;
int eof = 0;
int error = 0;
ret = buf_read_from_pipe(buffer,
handle->fd,
PROCESS_MAX_READ,
&eof,
&error);
if (error)
log_warn(LD_PROCESS,
"Unable to read data: %s", strerror(error));
if (eof) {
handle->reached_eof = true;
process_unix_stop_reading(handle);
}
return ret;
}
STATIC bool
process_unix_close_file_descriptors(process_unix_t *unix_process)
{
tor_assert(unix_process);
int ret;
bool success = true;
if (! unix_process->stdout_handle.reached_eof)
process_unix_stop_reading(&unix_process->stdout_handle);
if (! unix_process->stderr_handle.reached_eof)
process_unix_stop_reading(&unix_process->stderr_handle);
if (unix_process->stdin_handle.is_writing)
process_unix_stop_writing(&unix_process->stdin_handle);
if (unix_process->stdin_handle.fd != -1) {
ret = close(unix_process->stdin_handle.fd);
if (ret == -1) {
log_warn(LD_PROCESS, "Unable to close standard in");
success = false;
}
unix_process->stdin_handle.fd = -1;
}
if (unix_process->stdout_handle.fd != -1) {
ret = close(unix_process->stdout_handle.fd);
if (ret == -1) {
log_warn(LD_PROCESS, "Unable to close standard out");
success = false;
}
unix_process->stdout_handle.fd = -1;
}
if (unix_process->stderr_handle.fd != -1) {
ret = close(unix_process->stderr_handle.fd);
if (ret == -1) {
log_warn(LD_PROCESS, "Unable to close standard error");
success = false;
}
unix_process->stderr_handle.fd = -1;
}
return success;
}
#endif