Add Unix backend for the Process subsystem.

This patch adds the Unix backend for the Process subsystem. The Unix
backend attaches file descriptors from the child process's standard in,
out and error to Tor's libevent based main loop using traditional Unix
pipes. We use the already available `waitpid` module to get events
whenever the child process terminates.

See: https://bugs.torproject.org/28179
This commit is contained in:
Alexander Færøy 2018-11-22 04:43:27 +01:00 committed by Nick Mathewson
parent 35509978dd
commit 2e957027e2
6 changed files with 746 additions and 0 deletions

View File

@ -10,6 +10,7 @@ src_lib_libtor_process_a_SOURCES = \
src/lib/process/env.c \
src/lib/process/pidfile.c \
src/lib/process/process.c \
src/lib/process/process_unix.c \
src/lib/process/restrict.c \
src/lib/process/setuid.c \
src/lib/process/subprocess.c \
@ -26,6 +27,7 @@ noinst_HEADERS += \
src/lib/process/env.h \
src/lib/process/pidfile.h \
src/lib/process/process.h \
src/lib/process/process_unix.h \
src/lib/process/restrict.h \
src/lib/process/setuid.h \
src/lib/process/subprocess.h \

View File

@ -15,6 +15,7 @@
#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/env.h"
#ifdef HAVE_STDDEF_H
@ -64,6 +65,11 @@ struct process_t {
/** Do we need to store some custom data with the process? */
void *data;
#ifndef _WIN32
/** Our Unix process handle. */
process_unix_t *unix_process;
#endif
};
/** Convert a given process status in <b>status</b> to its string
@ -161,6 +167,11 @@ process_new(const char *command)
process->stderr_buffer = buf_new();
process->stdin_buffer = buf_new();
#ifndef _WIN32
/* Prepare our Unix process handle. */
process->unix_process = process_unix_new();
#endif
smartlist_add(processes, process);
return process;
@ -188,6 +199,11 @@ process_free_(process_t *process)
buf_free(process->stderr_buffer);
buf_free(process->stdin_buffer);
#ifndef _WIN32
/* Cleanup our Unix process handle. */
process_unix_free(process->unix_process);
#endif
smartlist_remove(processes, process);
tor_free(process);
@ -204,6 +220,10 @@ process_exec(process_t *process)
log_info(LD_PROCESS, "Starting new process: %s", process->command);
#ifndef _WIN32
status = process_unix_exec(process);
#endif
/* Update our state. */
process_set_status(process, status);
@ -391,6 +411,17 @@ process_get_environment(const process_t *process)
return process_environment_make(process->environment);
}
#ifndef _WIN32
/** Get the internal handle for the Unix backend. */
process_unix_t *
process_get_unix_process(const process_t *process)
{
tor_assert(process);
tor_assert(process->unix_process);
return process->unix_process;
}
#endif
/** Write <b>size</b> bytes of <b>data</b> to the given process's standard
* input. */
void
@ -510,7 +541,11 @@ 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 0;
#endif
}
/** This function is called whenever the Process backend have notified us that
@ -521,7 +556,11 @@ 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 0;
#endif
}
/** This function calls the backend function for the given process whenever
@ -531,6 +570,10 @@ MOCK_IMPL(STATIC void, process_write_stdin,
{
tor_assert(process);
tor_assert(buffer);
#ifndef _WIN32
process_unix_write(process, buffer);
#endif
}
/** This function calls the protocol handlers based on the value of

View File

@ -95,6 +95,11 @@ void *process_get_data(const process_t *process);
void process_set_status(process_t *process, process_status_t status);
process_status_t process_get_status(const process_t *process);
#ifndef _WIN32
struct process_unix_t;
struct process_unix_t *process_get_unix_process(const process_t *process);
#endif
void process_write(process_t *process,
const uint8_t *data, size_t size);
void process_vprintf(process_t *process,

View File

@ -0,0 +1,611 @@
/* Copyright (c) 2003, Roger Dingledine
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file process_unix.c
* \brief Module for working with Unix processes.
**/
#define PROCESS_UNIX_PRIVATE
#include "lib/intmath/cmp.h"
#include "lib/container/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
/** Maximum number of file descriptors, if we cannot get it via sysconf() */
#define DEFAULT_MAX_FD 256
/** Internal state for Unix handles. */
struct process_unix_handle_t {
/** Unix File Descriptor. */
int fd;
/** Have we reached end of file? */
bool reached_eof;
/** Event structure for libevent. */
struct event *event;
/** Are we writing? */
bool is_writing;
};
/** Internal state for our Unix process. */
struct process_unix_t {
/** Standard in handle. */
process_unix_handle_t stdin_handle;
/** Standard out handle. */
process_unix_handle_t stdout_handle;
/** Standard error handle. */
process_unix_handle_t stderr_handle;
/** The process identifier of our process. */
pid_t pid;
/** Waitpid Callback structure. */
waitpid_callback_t *waitpid;
};
/** Returns a newly allocated <b>process_unix_t</b>. */
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;
}
/** Deallocates the given <b>unix_process</b>. */
void
process_unix_free_(process_unix_t *unix_process)
{
if (! unix_process)
return;
/* Clean up our waitpid callback. */
clear_waitpid_callback(unix_process->waitpid);
/* FIXME(ahf): Refactor waitpid code? */
unix_process->waitpid = NULL;
/* Cleanup our events. */
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);
process_unix_stop_writing(&unix_process->stdin_handle);
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);
}
/** Executes the given process as a child process of Tor. This function is
* responsible for setting up the child process and run it. This includes
* setting up pipes for interprocess communication, initialize the waitpid
* callbacks, and finally run fork() followed by execve(). Returns
* <b>PROCESS_STATUS_RUNNING</b> upon success. */
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);
/* Create standard in pipe. */
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;
}
/* Create standard out pipe. */
retval = pipe(stdout_pipe);
if (-1 == retval) {
log_warn(LD_PROCESS,
"Unable to create pipe for stdout "
"communication with process: %s",
strerror(errno));
/** Cleanup standard in pipe. */
close(stdin_pipe[0]);
close(stdin_pipe[1]);
return PROCESS_STATUS_ERROR;
}
/* Create standard error pipe. */
retval = pipe(stderr_pipe);
if (-1 == retval) {
log_warn(LD_PROCESS,
"Unable to create pipe for stderr "
"communication with process: %s",
strerror(errno));
/** Cleanup standard in pipe. */
close(stdin_pipe[0]);
close(stdin_pipe[1]);
/** Cleanup standard out pipe. */
close(stdin_pipe[0]);
close(stdin_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 /* !(defined(_SC_OPEN_MAX)) */
max_fd = DEFAULT_MAX_FD;
#endif /* defined(_SC_OPEN_MAX) */
pid = fork();
if (0 == pid) {
/* This code is running in the child process context. */
#if defined(HAVE_SYS_PRCTL_H) && defined(__linux__)
/* Attempt to have the kernel issue a SIGTERM if the parent
* goes away. Certain attributes of the binary being execve()ed
* will clear this during the execve() call, but it's better
* than nothing.
*/
prctl(PR_SET_PDEATHSIG, SIGTERM);
#endif /* defined(HAVE_SYS_PRCTL_H) && defined(__linux__) */
/* Link process stdout to the write end of the pipe. */
retval = dup2(stdout_pipe[1], STDOUT_FILENO);
if (-1 == retval)
goto error;
/* Link process stderr to the write end of the pipe. */
retval = dup2(stderr_pipe[1], STDERR_FILENO);
if (-1 == retval)
goto error;
/* Link process stdin to the read end of the pipe */
retval = dup2(stdin_pipe[0], STDIN_FILENO);
if (-1 == retval)
goto error;
/* Close our pipes now after they have been dup2()'ed. */
close(stderr_pipe[0]);
close(stderr_pipe[1]);
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stdin_pipe[0]);
close(stdin_pipe[1]);
/* Close all other fds, including the read end of the pipe. XXX: We should
* now be doing enough FD_CLOEXEC setting to make this needless.
*/
for (fd = STDERR_FILENO + 1; fd < max_fd; fd++)
close(fd);
/* Create the argv value for our new process. */
char **argv = process_get_argv(process);
/* Create the env value for our new process. */
process_environment_t *env = process_get_environment(process);
/* Call the requested program. */
retval = execve(argv[0], argv, env->unixoid_environment_block);
/* If we made it here it is because execve failed :-( */
if (-1 == retval)
fprintf(stderr, "Call to execve() failed: %s", strerror(errno));
tor_free(argv);
process_environment_free(env);
tor_assert_unreached();
error:
/* LCOV_EXCL_START */
fprintf(stderr, "Error from child process: %s", strerror(errno));
_exit(1);
/* LCOV_EXCL_STOP */
}
/* We are in the parent process. */
if (-1 == pid) {
log_warn(LD_PROCESS,
"Failed to create child process: %s", strerror(errno));
/** Cleanup standard in pipe. */
close(stdin_pipe[0]);
close(stdin_pipe[1]);
/** Cleanup standard out pipe. */
close(stdin_pipe[0]);
close(stdin_pipe[1]);
/** Cleanup standard error pipe. */
close(stderr_pipe[0]);
close(stderr_pipe[1]);
return PROCESS_STATUS_ERROR;
}
/* Register our PID. */
unix_process->pid = pid;
/* Setup waitpid callbacks. */
unix_process->waitpid = set_waitpid_callback(pid,
process_unix_waitpid_callback,
process);
/* Handle standard out. */
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));
}
/* Handle standard error. */
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));
}
/* Handle standard in. */
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));
}
/* Setup our handles. */
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);
/* Start reading from standard out and standard error. */
process_unix_start_reading(&unix_process->stdout_handle);
process_unix_start_reading(&unix_process->stderr_handle);
return PROCESS_STATUS_RUNNING;
}
/** Write the given <b>buffer</b> as input to the given <b>process</b>'s
* standard input. Returns the number of bytes written. */
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 we have data to write (when buffer_flush_len > 0) and we are not
* currently getting file descriptor events from the kernel, we tell the
* kernel to start notifying us about when we can write to our file
* descriptor and return. */
if (buffer_flush_len > 0 && ! unix_process->stdin_handle.is_writing) {
process_unix_start_writing(&unix_process->stdin_handle);
return 0;
}
/* We don't have any data to write, but the kernel is currently notifying us
* about whether we are able to write or not. Tell the kernel to stop
* notifying us until we have data to write. */
if (buffer_flush_len == 0 && unix_process->stdin_handle.is_writing) {
process_unix_stop_writing(&unix_process->stdin_handle);
return 0;
}
/* We have data to write and the kernel have told us to write it. */
return buf_flush_to_pipe(buffer,
process_get_unix_process(process)->stdin_handle.fd,
max_to_write, &buffer_flush_len);
}
/** Read data from the given process's standard output and put it into
* <b>buffer</b>. Returns the number of bytes read. */
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);
}
/** Read data from the given process's standard error and put it into
* <b>buffer</b>. Returns the number of bytes read. */
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);
}
/** This function is called whenever libevent thinks we have data that could be
* read from the child process's standard output. We notify the Process
* subsystem, which is then responsible for calling back to us for doing the
* actual reading of the data. */
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);
}
/** This function is called whenever libevent thinks we have data that could be
* read from the child process's standard error. We notify the Process
* subsystem, which is then responsible for calling back to us for doing the
* actual reading of the data. */
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);
}
/** This function is called whenever libevent thinks we have data that could be
* written the child process's standard input. We notify the Process subsystem,
* which is then responsible for calling back to us for doing the actual write
* of the data. */
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);
}
/** This function tells libevent that we are interested in receiving read
* events from the given <b>handle</b>. */
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.");
}
/** This function tells libevent that we are no longer interested in receiving
* read events from the given <b>handle</b>. */
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.");
}
/** This function tells libevent that we are interested in receiving write
* events from the given <b>handle</b>. */
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;
}
/** This function tells libevent that we are no longer interested in receiving
* write events from the given <b>handle</b>. */
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;
}
/** This function is called when the waitpid system have detected that our
* process have terminated. We disable the waitpid system and notify the
* Process subsystem that we have terminated. */
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);
/* Notify our process. */
process_notify_event_exit(process, status);
/* Remove our waitpid callback. */
clear_waitpid_callback(unix_process->waitpid);
unix_process->waitpid = NULL;
}
/** This function sets the file descriptor in the <b>handle</b> as non-blocking
* and configures the libevent event structure based on the given <b>flags</b>
* to ensure that <b>callback</b> is called whenever we have events on the
* given <b>handle</b>. */
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);
/* Put our file descriptor into non-blocking mode. */
if (fcntl(handle->fd, F_SETFL, O_NONBLOCK) < 0) {
log_warn(LD_PROCESS, "Unable mark Unix handle as non-blocking: %s",
strerror(errno));
}
/* Setup libevent event. */
handle->event = tor_event_new(tor_libevent_get_base(),
handle->fd,
flags,
callback,
process);
}
/** This function reads data from the given <b>handle</b> and puts it into
* <b>buffer</b>. Returns the number of bytes read this way. */
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;
}
#endif /* defined(_WIN32). */

View File

@ -0,0 +1,64 @@
/* Copyright (c) 2003-2004, Roger Dingledine
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file process_unix.h
* \brief Header for process_unix.c
**/
#ifndef TOR_PROCESS_UNIX_H
#define TOR_PROCESS_UNIX_H
#ifndef _WIN32
#include "orconfig.h"
#include "lib/malloc/malloc.h"
#include <event2/event.h>
struct process_t;
struct process_unix_t;
typedef struct process_unix_t process_unix_t;
process_unix_t *process_unix_new(void);
void process_unix_free_(process_unix_t *unix_process);
#define process_unix_free(s) \
FREE_AND_NULL(process_unix_t, process_unix_free_, (s))
process_status_t process_unix_exec(struct process_t *process);
int process_unix_write(struct process_t *process, buf_t *buffer);
int process_unix_read_stdout(struct process_t *process, buf_t *buffer);
int process_unix_read_stderr(struct process_t *process, buf_t *buffer);
#ifdef PROCESS_UNIX_PRIVATE
struct process_unix_handle_t;
typedef struct process_unix_handle_t process_unix_handle_t;
STATIC void stdout_read_callback(evutil_socket_t fd, short event, void *data);
STATIC void stderr_read_callback(evutil_socket_t fd, short event, void *data);
STATIC void stdin_write_callback(evutil_socket_t fd, short event, void *data);
STATIC void process_unix_start_reading(process_unix_handle_t *);
STATIC void process_unix_stop_reading(process_unix_handle_t *);
STATIC void process_unix_start_writing(process_unix_handle_t *);
STATIC void process_unix_stop_writing(process_unix_handle_t *);
STATIC void process_unix_waitpid_callback(int status, void *data);
STATIC void process_unix_setup_handle(process_t *process,
process_unix_handle_t *handle,
short flags,
event_callback_fn callback);
STATIC int process_unix_read_handle(process_t *,
process_unix_handle_t *,
buf_t *);
#endif /* defined(PROCESS_UNIX_PRIVATE). */
#endif /* defined(_WIN32). */
#endif /* defined(TOR_PROCESS_UNIX_H). */

View File

@ -12,6 +12,8 @@
#define PROCESS_PRIVATE
#include "lib/process/process.h"
#define PROCESS_UNIX_PRIVATE
#include "lib/process/process_unix.h"
static const char *stdout_read_buffer;
static const char *stderr_read_buffer;
@ -544,6 +546,24 @@ test_argv_simple(void *arg)
process_free_all();
}
static void
test_unix(void *arg)
{
(void)arg;
#ifndef _WIN32
process_init();
process_t *process = process_new();
/* On Unix all processes should have a Unix process handle. */
tt_ptr_op(NULL, OP_NE, process_get_unix_process(process));
done:
process_free(process);
process_free_all();
#endif
}
struct testcase_t process_tests[] = {
{ "default_values", test_default_values, TT_FORK, NULL, NULL },
{ "stringified_types", test_stringified_types, TT_FORK, NULL, NULL },
@ -554,5 +574,6 @@ struct testcase_t process_tests[] = {
{ "write_simple", test_write_simple, TT_FORK, NULL, NULL },
{ "exit_simple", test_exit_simple, TT_FORK, NULL, NULL },
{ "argv_simple", test_argv_simple, TT_FORK, NULL, NULL },
{ "unix", test_unix, TT_FORK, NULL, NULL },
END_OF_TESTCASES
};