Merge branch 'ticket28179_squashed' into ticket28179_squashed_merged

This commit is contained in:
Nick Mathewson 2018-12-17 16:41:01 -05:00
commit e969d9c6b4
38 changed files with 4320 additions and 2348 deletions

View File

@ -76,6 +76,8 @@ test_script:
$buildpath = @("C:\msys64\${env:compiler_path}\bin") + $oldpath
$env:Path = $buildpath -join ';'
Set-Location "${env:build}"
Copy-Item "C:/msys64/${env:compiler_path}/bin/libssp-0.dll" -Destination "${env:build}/src/test"
Copy-Item "C:/msys64/${env:compiler_path}/bin/zlib1.dll" -Destination "${env:build}/src/test"
Execute-Bash "VERBOSE=1 make -j2 check"
}

4
.gitignore vendored
View File

@ -241,7 +241,7 @@ uptime-*.json
/src/test/test
/src/test/test-slow
/src/test/test-bt-cl
/src/test/test-child
/src/test/test-process
/src/test/test-memwipe
/src/test/test-ntor-cl
/src/test/test-hs-ntor-cl
@ -251,7 +251,7 @@ uptime-*.json
/src/test/test.exe
/src/test/test-slow.exe
/src/test/test-bt-cl.exe
/src/test/test-child.exe
/src/test/test-process.exe
/src/test/test-ntor-cl.exe
/src/test/test-hs-ntor-cl.exe
/src/test/test-memwipe.exe

View File

@ -675,7 +675,8 @@ GENERAL OPTIONS
+
The currently recognized domains are: general, crypto, net, config, fs,
protocol, mm, http, app, control, circ, rend, bug, dir, dirserv, or, edge,
acct, hist, handshake, heartbeat, channel, sched, guard, consdiff, and dos.
acct, hist, handshake, heartbeat, channel, sched, guard, consdiff, dos,
process, and pt.
Domain names are case-insensitive. +
+
For example, "`Log [handshake]debug [~net,~mm]info notice stdout`" sends

View File

@ -142,7 +142,7 @@
#include "lib/process/pidfile.h"
#include "lib/process/restrict.h"
#include "lib/process/setuid.h"
#include "lib/process/subprocess.h"
#include "lib/process/process.h"
#include "lib/net/gethostname.h"
#include "lib/thread/numcpus.h"

View File

@ -74,6 +74,7 @@
#include "lib/net/resolve.h"
#include "lib/process/waitpid.h"
#include "lib/process/process.h"
#include "lib/meminfo/meminfo.h"
#include "lib/osinfo/uname.h"
@ -558,6 +559,10 @@ tor_init(int argc, char *argv[])
rend_cache_init();
addressmap_init(); /* Init the client dns cache. Do it always, since it's
* cheap. */
/* Initialize Process subsystem. */
process_init();
/* Initialize the HS subsystem. */
hs_init();
@ -785,6 +790,7 @@ tor_free_all(int postfork)
circuitmux_ewma_free_all();
accounting_free_all();
protover_summary_cache_free_all();
process_free_all();
if (!postfork) {
config_free_all();

View File

@ -200,7 +200,6 @@ static int can_complete_circuits = 0;
#define LAZY_DESCRIPTOR_RETRY_INTERVAL (60)
static int conn_close_if_marked(int i);
static int run_main_loop_until_done(void);
static void connection_start_reading_from_linked_conn(connection_t *conn);
static int connection_should_read_from_linked_conn(connection_t *conn);
static void conn_read_callback(evutil_socket_t fd, short event, void *_conn);
@ -1853,10 +1852,6 @@ second_elapsed_callback(time_t now, const or_options_t *options)
run_connection_housekeeping(i, now);
}
/* 11b. check pending unconfigured managed proxies */
if (!net_is_disabled() && pt_proxies_configuration_pending())
pt_configure_remaining_proxies();
/* Run again in a second. */
return 1;
}
@ -2847,10 +2842,6 @@ do_main_loop(void)
}
}
#endif /* defined(HAVE_SYSTEMD_209) */
main_loop_should_exit = 0;
main_loop_exit_value = 0;
#ifdef ENABLE_RESTART_DEBUGGING
{
static int first_time = 1;
@ -2976,10 +2967,14 @@ run_main_loop_once(void)
*
* Shadow won't invoke this function, so don't fill it up with things.
*/
static int
STATIC int
run_main_loop_until_done(void)
{
int loop_result = 1;
main_loop_should_exit = 0;
main_loop_exit_value = 0;
do {
loop_result = run_main_loop_once();
} while (loop_result == 1);

View File

@ -100,6 +100,7 @@ extern struct token_bucket_rw_t global_bucket;
extern struct token_bucket_rw_t global_relayed_bucket;
#ifdef MAINLOOP_PRIVATE
STATIC int run_main_loop_until_done(void);
STATIC void close_closeable_connections(void);
STATIC void initialize_periodic_events(void);
STATIC void teardown_periodic_events(void);

View File

@ -102,10 +102,10 @@
#include "feature/relay/ext_orport.h"
#include "feature/control/control.h"
#include "lib/process/process.h"
#include "lib/process/env.h"
#include "lib/process/subprocess.h"
static process_environment_t *
static smartlist_t *
create_managed_proxy_environment(const managed_proxy_t *mp);
static inline int proxy_configuration_finished(const managed_proxy_t *mp);
@ -127,6 +127,7 @@ static void parse_method_error(const char *line, int is_server_method);
#define PROTO_SMETHODS_DONE "SMETHODS DONE"
#define PROTO_PROXY_DONE "PROXY DONE"
#define PROTO_PROXY_ERROR "PROXY-ERROR"
#define PROTO_LOG "LOG"
/** The first and only supported - at the moment - configuration
protocol version. */
@ -490,8 +491,8 @@ proxy_prepare_for_restart(managed_proxy_t *mp)
tor_assert(mp->conf_state == PT_PROTO_COMPLETED);
/* destroy the process handle and terminate the process. */
tor_process_handle_destroy(mp->process_handle, 1);
mp->process_handle = NULL;
process_set_data(mp->process, NULL);
process_terminate(mp->process);
/* destroy all its registered transports, since we will no longer
use them. */
@ -520,34 +521,35 @@ proxy_prepare_for_restart(managed_proxy_t *mp)
static int
launch_managed_proxy(managed_proxy_t *mp)
{
int retval;
tor_assert(mp);
process_environment_t *env = create_managed_proxy_environment(mp);
smartlist_t *env = create_managed_proxy_environment(mp);
#ifdef _WIN32
/* Passing NULL as lpApplicationName makes Windows search for the .exe */
retval = tor_spawn_background(NULL,
(const char **)mp->argv,
env,
&mp->process_handle);
#else /* !(defined(_WIN32)) */
retval = tor_spawn_background(mp->argv[0],
(const char **)mp->argv,
env,
&mp->process_handle);
#endif /* defined(_WIN32) */
/* Configure our process. */
process_set_data(mp->process, mp);
process_set_stdout_read_callback(mp->process, managed_proxy_stdout_callback);
process_set_stderr_read_callback(mp->process, managed_proxy_stderr_callback);
process_set_exit_callback(mp->process, managed_proxy_exit_callback);
process_set_protocol(mp->process, PROCESS_PROTOCOL_LINE);
process_reset_environment(mp->process, env);
process_environment_free(env);
/* Cleanup our env. */
SMARTLIST_FOREACH(env, char *, x, tor_free(x));
smartlist_free(env);
if (retval == PROCESS_STATUS_ERROR) {
log_warn(LD_GENERAL, "Managed proxy at '%s' failed at launch.",
/* Skip the argv[0] as we get that from process_new(argv[0]). */
for (int i = 1; mp->argv[i] != NULL; ++i)
process_append_argument(mp->process, mp->argv[i]);
if (process_exec(mp->process) != PROCESS_STATUS_RUNNING) {
log_warn(LD_CONFIG, "Managed proxy at '%s' failed at launch.",
mp->argv[0]);
return -1;
}
log_info(LD_CONFIG, "Managed proxy at '%s' has spawned with PID '%d'.",
mp->argv[0], tor_process_get_pid(mp->process_handle));
log_info(LD_CONFIG,
"Managed proxy at '%s' has spawned with PID '%" PRIu64 "'.",
mp->argv[0], process_get_pid(mp->process));
mp->conf_state = PT_PROTO_LAUNCHED;
return 0;
@ -615,10 +617,6 @@ pt_configure_remaining_proxies(void)
STATIC int
configure_proxy(managed_proxy_t *mp)
{
int configuration_finished = 0;
smartlist_t *proxy_output = NULL;
enum stream_status stream_status = 0;
/* if we haven't launched the proxy yet, do it now */
if (mp->conf_state == PT_PROTO_INFANT) {
if (launch_managed_proxy(mp) < 0) { /* launch fail */
@ -629,45 +627,8 @@ configure_proxy(managed_proxy_t *mp)
}
tor_assert(mp->conf_state != PT_PROTO_INFANT);
tor_assert(mp->process_handle);
proxy_output =
tor_get_lines_from_handle(tor_process_get_stdout_pipe(mp->process_handle),
&stream_status);
if (!proxy_output) { /* failed to get input from proxy */
if (stream_status != IO_STREAM_EAGAIN) { /* bad stream status! */
mp->conf_state = PT_PROTO_BROKEN;
log_warn(LD_GENERAL, "The communication stream of managed proxy '%s' "
"is '%s'. Most probably the managed proxy stopped running. "
"This might be a bug of the managed proxy, a bug of Tor, or "
"a misconfiguration. Please enable logging on your managed "
"proxy and check the logs for errors.",
mp->argv[0], stream_status_to_string(stream_status));
}
goto done;
}
/* Handle lines. */
SMARTLIST_FOREACH_BEGIN(proxy_output, const char *, line) {
handle_proxy_line(line, mp);
if (proxy_configuration_finished(mp))
goto done;
} SMARTLIST_FOREACH_END(line);
done:
/* if the proxy finished configuring, exit the loop. */
if (proxy_configuration_finished(mp)) {
handle_finished_proxy(mp);
configuration_finished = 1;
}
if (proxy_output) {
SMARTLIST_FOREACH(proxy_output, char *, cp, tor_free(cp));
smartlist_free(proxy_output);
}
return configuration_finished;
tor_assert(mp->process);
return mp->conf_state == PT_PROTO_COMPLETED;
}
/** Register server managed proxy <b>mp</b> transports to state */
@ -748,8 +709,11 @@ managed_proxy_destroy(managed_proxy_t *mp,
/* free the outgoing proxy URI */
tor_free(mp->proxy_uri);
tor_process_handle_destroy(mp->process_handle, also_terminate_process);
mp->process_handle = NULL;
/* do we want to terminate our process if it's still running? */
if (also_terminate_process && mp->process)
process_terminate(mp->process);
process_free(mp->process);
tor_free(mp);
}
@ -945,21 +909,12 @@ handle_proxy_line(const char *line, managed_proxy_t *mp)
parse_proxy_error(line);
goto err;
} else if (!strcmpstart(line, SPAWN_ERROR_MESSAGE)) {
/* managed proxy launch failed: parse error message to learn why. */
int retval, child_state, saved_errno;
retval = tor_sscanf(line, SPAWN_ERROR_MESSAGE "%x/%x",
&child_state, &saved_errno);
if (retval == 2) {
log_warn(LD_GENERAL,
"Could not launch managed proxy executable at '%s' ('%s').",
mp->argv[0], strerror(saved_errno));
} else { /* failed to parse error message */
log_warn(LD_GENERAL,"Could not launch managed proxy executable at '%s'.",
mp->argv[0]);
}
mp->conf_state = PT_PROTO_FAILED_LAUNCH;
/* We check for the additional " " after the PROTO_LOG string to make sure
* we can later extend this big if/else-if table with something that begins
* with "LOG" without having to get the order right. */
} else if (!strcmpstart(line, PROTO_LOG " ")) {
parse_log_line(line, mp);
return;
}
@ -1182,6 +1137,31 @@ parse_proxy_error(const char *line)
line+strlen(PROTO_PROXY_ERROR)+1);
}
/** Parses a LOG <b>line</b> and emit log events accordingly. */
STATIC void
parse_log_line(const char *line, managed_proxy_t *mp)
{
tor_assert(line);
tor_assert(mp);
if (strlen(line) < (strlen(PROTO_LOG) + 1)) {
log_warn(LD_PT, "Managed proxy sent us a %s line "
"with missing argument.", PROTO_LOG);
goto done;
}
const char *message = line + strlen(PROTO_LOG) + 1;
log_info(LD_PT, "Managed proxy \"%s\" says: %s",
mp->argv[0], message);
/* Emit control port event. */
control_event_pt_log(mp->argv[0], message);
done:
return;
}
/** Return a newly allocated string that tor should place in
* TOR_PT_SERVER_TRANSPORT_OPTIONS while configuring the server
* manged proxy in <b>mp</b>. Return NULL if no such options are found. */
@ -1257,7 +1237,7 @@ get_bindaddr_for_server_proxy(const managed_proxy_t *mp)
/** Return a newly allocated process_environment_t * for <b>mp</b>'s
* process. */
static process_environment_t *
static smartlist_t *
create_managed_proxy_environment(const managed_proxy_t *mp)
{
const or_options_t *options = get_options();
@ -1272,8 +1252,6 @@ create_managed_proxy_environment(const managed_proxy_t *mp)
/* The final environment to be passed to mp. */
smartlist_t *merged_env_vars = get_current_process_environment_variables();
process_environment_t *env;
{
char *state_tmp = get_datadir_fname("pt_state/"); /* XXX temp */
smartlist_add_asprintf(envs, "TOR_PT_STATE_LOCATION=%s", state_tmp);
@ -1366,14 +1344,9 @@ create_managed_proxy_environment(const managed_proxy_t *mp)
tor_free_, 1);
} SMARTLIST_FOREACH_END(env_var);
env = process_environment_make(merged_env_vars);
smartlist_free(envs);
SMARTLIST_FOREACH(merged_env_vars, void *, x, tor_free(x));
smartlist_free(merged_env_vars);
return env;
return merged_env_vars;
}
/** Create and return a new managed proxy for <b>transport</b> using
@ -1392,6 +1365,7 @@ managed_proxy_create(const smartlist_t *with_transport_list,
mp->argv = proxy_argv;
mp->transports = smartlist_new();
mp->proxy_uri = get_pt_proxy_uri();
mp->process = process_new(proxy_argv[0]);
mp->transports_to_launch = smartlist_new();
SMARTLIST_FOREACH(with_transport_list, const char *, transport,
@ -1736,3 +1710,72 @@ tor_escape_str_for_pt_args(const char *string, const char *chars_to_escape)
return new_string;
}
/** Callback function that is called when our PT process have data on its
* stdout. Our process can be found in <b>process</b>, the data can be found in
* <b>line</b> and the length of our line is given in <b>size</b>. */
STATIC void
managed_proxy_stdout_callback(process_t *process,
const char *line,
size_t size)
{
tor_assert(process);
tor_assert(line);
(void)size;
managed_proxy_t *mp = process_get_data(process);
handle_proxy_line(line, mp);
if (proxy_configuration_finished(mp)) {
handle_finished_proxy(mp);
tor_assert(mp->conf_state == PT_PROTO_COMPLETED);
}
}
/** Callback function that is called when our PT process have data on its
* stderr. Our process can be found in <b>process</b>, the data can be found in
* <b>line</b> and the length of our line is given in <b>size</b>. */
STATIC void
managed_proxy_stderr_callback(process_t *process,
const char *line,
size_t size)
{
tor_assert(process);
tor_assert(line);
(void)size;
managed_proxy_t *mp = process_get_data(process);
log_warn(LD_PT, "Managed proxy at '%s' reported: %s", mp->argv[0], line);
}
/** Callback function that is called when our PT process terminates. The
* process exit code can be found in <b>exit_code</b> and our process can be
* found in <b>process</b>. Returns true iff we want the process subsystem to
* free our process_t handle for us. */
STATIC bool
managed_proxy_exit_callback(process_t *process, process_exit_code_t exit_code)
{
tor_assert(process);
log_warn(LD_PT,
"Pluggable Transport process terminated with status code %" PRIu64,
exit_code);
/* We detach ourself from the MP (if we are attached) and free ourself. */
managed_proxy_t *mp = process_get_data(process);
/* If we are still attached to the process, it is probably because our PT
* process crashed before we got to call process_set_data(p, NULL); */
if (BUG(mp != NULL)) {
/* FIXME(ahf): Our process stopped without us having told it to stop
* (crashed). Should we restart it here? */
mp->process = NULL;
process_set_data(process, NULL);
}
return true;
}

View File

@ -11,6 +11,8 @@
#ifndef TOR_TRANSPORTS_H
#define TOR_TRANSPORTS_H
#include "lib/process/process.h"
/** Represents a pluggable transport used by a bridge. */
typedef struct transport_t {
/** SOCKS version: One of PROXY_SOCKS4, PROXY_SOCKS5. */
@ -81,7 +83,7 @@ enum pt_proto_state {
PT_PROTO_FAILED_LAUNCH /* failed while launching */
};
struct process_handle_t;
struct process_t;
/** Structure containing information of a managed proxy. */
typedef struct {
@ -94,10 +96,8 @@ typedef struct {
int is_server; /* is it a server proxy? */
/* A pointer to the process handle of this managed proxy. */
struct process_handle_t *process_handle;
int pid; /* The Process ID this managed proxy is using. */
/* A pointer to the process of this managed proxy. */
struct process_t *process;
/** Boolean: We are re-parsing our config, and we are going to
* remove this managed proxy if we don't find it any transport
@ -128,6 +128,7 @@ STATIC int parse_version(const char *line, managed_proxy_t *mp);
STATIC void parse_env_error(const char *line);
STATIC void parse_proxy_error(const char *line);
STATIC void handle_proxy_line(const char *line, managed_proxy_t *mp);
STATIC void parse_log_line(const char *line, managed_proxy_t *mp);
STATIC char *get_transport_options_for_server_proxy(const managed_proxy_t *mp);
STATIC void managed_proxy_destroy(managed_proxy_t *mp,
@ -142,6 +143,10 @@ STATIC char* get_pt_proxy_uri(void);
STATIC void free_execve_args(char **arg);
STATIC void managed_proxy_stdout_callback(process_t *, const char *, size_t);
STATIC void managed_proxy_stderr_callback(process_t *, const char *, size_t);
STATIC bool managed_proxy_exit_callback(process_t *, process_exit_code_t);
#endif /* defined(PT_PRIVATE) */
#endif /* !defined(TOR_TRANSPORTS_H) */

View File

@ -7033,6 +7033,17 @@ control_event_transport_launched(const char *mode, const char *transport_name,
mode, transport_name, fmt_addr(addr), port);
}
/** A pluggable transport called <b>pt_name</b> has emitted a log
* message found in <b>message</b>. */
void
control_event_pt_log(const char *pt_name, const char *message)
{
send_control_event(EVENT_PT_LOG,
"650 PT_LOG %s %s\r\n",
pt_name,
message);
}
/** Convert rendezvous auth type to string for HS_DESC control events
*/
const char *

View File

@ -207,6 +207,7 @@ void control_event_clients_seen(const char *controller_str);
void control_event_transport_launched(const char *mode,
const char *transport_name,
tor_addr_t *addr, uint16_t port);
void control_event_pt_log(const char *pt_name, const char *message);
const char *rend_auth_type_to_string(rend_auth_type_t auth_type);
MOCK_DECL(const char *, node_describe_longname_by_id,(const char *id_digest));
void control_event_hs_descriptor_requested(const char *onion_address,
@ -295,7 +296,8 @@ void control_free_all(void);
#define EVENT_HS_DESC 0x0021
#define EVENT_HS_DESC_CONTENT 0x0022
#define EVENT_NETWORK_LIVENESS 0x0023
#define EVENT_MAX_ 0x0023
#define EVENT_PT_LOG 0x0024
#define EVENT_MAX_ 0x0024
/* sizeof(control_connection_t.event_mask) in bits, currently a uint64_t */
#define EVENT_CAPACITY_ 0x0040

View File

@ -1268,7 +1268,7 @@ static const char *domain_list[] = {
"GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM",
"HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV",
"OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", "CHANNEL",
"SCHED", "GUARD", "CONSDIFF", "DOS", NULL
"SCHED", "GUARD", "CONSDIFF", "DOS", "PROCESS", "PT", NULL
};
/** Return a bitmask for the log domain for which <b>domain</b> is the name,

View File

@ -107,8 +107,12 @@
#define LD_CONSDIFF (1u<<24)
/** Denial of Service mitigation. */
#define LD_DOS (1u<<25)
/** Processes */
#define LD_PROCESS (1u<<26)
/** Pluggable Transports. */
#define LD_PT (1u<<27)
/** Number of logging domains in the code. */
#define N_LOGGING_DOMAINS 26
#define N_LOGGING_DOMAINS 28
/** This log message is not safe to send to a callback-based logger
* immediately. Used as a flag, not a log domain. */

View File

@ -21,6 +21,7 @@
#endif
#include <stdlib.h>
#include <unistd.h>
#ifdef PARANOIA
/** Helper: If PARANOIA is defined, assert that the buffer in local variable
@ -30,27 +31,36 @@
#define check() STMT_NIL
#endif /* defined(PARANOIA) */
/** Read up to <b>at_most</b> bytes from the socket <b>fd</b> into
/** Read up to <b>at_most</b> bytes from the file descriptor <b>fd</b> into
* <b>chunk</b> (which must be on <b>buf</b>). If we get an EOF, set
* *<b>reached_eof</b> to 1. Return -1 on error, 0 on eof or blocking,
* and the number of bytes read otherwise. */
* *<b>reached_eof</b> to 1. Uses <b>tor_socket_recv()</b> iff <b>is_socket</b>
* is true, otherwise it uses <b>read()</b>. Return -1 on error (and sets
* *<b>error</b> to errno), 0 on eof or blocking, and the number of bytes read
* otherwise. */
static inline int
read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most,
int *reached_eof, int *socket_error)
int *reached_eof, int *error, bool is_socket)
{
ssize_t read_result;
if (at_most > CHUNK_REMAINING_CAPACITY(chunk))
at_most = CHUNK_REMAINING_CAPACITY(chunk);
if (is_socket)
read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0);
else
read_result = read(fd, CHUNK_WRITE_PTR(chunk), at_most);
if (read_result < 0) {
int e = tor_socket_errno(fd);
int e = is_socket ? tor_socket_errno(fd) : errno;
if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */
#ifdef _WIN32
if (e == WSAENOBUFS)
log_warn(LD_NET,"recv() failed: WSAENOBUFS. Not enough ram?");
log_warn(LD_NET, "%s() failed: WSAENOBUFS. Not enough ram?",
is_socket ? "recv" : "read");
#endif
*socket_error = e;
if (error)
*error = e;
return -1;
}
return 0; /* would block. */
@ -68,16 +78,17 @@ read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most,
}
}
/** Read from socket <b>s</b>, writing onto end of <b>buf</b>. Read at most
* <b>at_most</b> bytes, growing the buffer as necessary. If recv() returns 0
* (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on
* error; else return the number of bytes read.
/** Read from file descriptor <b>fd</b>, writing onto end of <b>buf</b>. Read
* at most <b>at_most</b> bytes, growing the buffer as necessary. If recv()
* returns 0 (because of EOF), set *<b>reached_eof</b> to 1 and return 0.
* Return -1 on error; else return the number of bytes read.
*/
/* XXXX indicate "read blocked" somehow? */
int
buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most,
static int
buf_read_from_fd(buf_t *buf, int fd, size_t at_most,
int *reached_eof,
int *socket_error)
int *socket_error,
bool is_socket)
{
/* XXXX It's stupid to overload the return values for these functions:
* "error status" and "number of bytes read" are not mutually exclusive.
@ -87,7 +98,7 @@ buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most,
check();
tor_assert(reached_eof);
tor_assert(SOCKET_OK(s));
tor_assert(SOCKET_OK(fd));
if (BUG(buf->datalen >= INT_MAX))
return -1;
@ -108,7 +119,8 @@ buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most,
readlen = cap;
}
r = read_to_chunk(buf, chunk, s, readlen, reached_eof, socket_error);
r = read_to_chunk(buf, chunk, fd, readlen,
reached_eof, socket_error, is_socket);
check();
if (r < 0)
return r; /* Error */
@ -122,22 +134,27 @@ buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most,
}
/** Helper for buf_flush_to_socket(): try to write <b>sz</b> bytes from chunk
* <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. On success, deduct
* the bytes written from *<b>buf_flushlen</b>. Return the number of bytes
* written on success, 0 on blocking, -1 on failure.
* <b>chunk</b> of buffer <b>buf</b> onto file descriptor <b>fd</b>. On
* success, deduct the bytes written from *<b>buf_flushlen</b>. Return the
* number of bytes written on success, 0 on blocking, -1 on failure.
*/
static inline int
flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz,
size_t *buf_flushlen)
flush_chunk(tor_socket_t fd, buf_t *buf, chunk_t *chunk, size_t sz,
size_t *buf_flushlen, bool is_socket)
{
ssize_t write_result;
if (sz > chunk->datalen)
sz = chunk->datalen;
write_result = tor_socket_send(s, chunk->data, sz, 0);
if (is_socket)
write_result = tor_socket_send(fd, chunk->data, sz, 0);
else
write_result = write(fd, chunk->data, sz);
if (write_result < 0) {
int e = tor_socket_errno(s);
int e = is_socket ? tor_socket_errno(fd) : errno;
if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */
#ifdef _WIN32
if (e == WSAENOBUFS)
@ -155,15 +172,15 @@ flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz,
}
}
/** Write data from <b>buf</b> to the socket <b>s</b>. Write at most
/** Write data from <b>buf</b> to the file descriptor <b>fd</b>. Write at most
* <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by
* the number of bytes actually written, and remove the written bytes
* from the buffer. Return the number of bytes written on success,
* -1 on failure. Return 0 if write() would block.
*/
int
buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz,
size_t *buf_flushlen)
static int
buf_flush_to_fd(buf_t *buf, int fd, size_t sz,
size_t *buf_flushlen, bool is_socket)
{
/* XXXX It's stupid to overload the return values for these functions:
* "error status" and "number of bytes flushed" are not mutually exclusive.
@ -171,7 +188,7 @@ buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz,
int r;
size_t flushed = 0;
tor_assert(buf_flushlen);
tor_assert(SOCKET_OK(s));
tor_assert(SOCKET_OK(fd));
if (BUG(*buf_flushlen > buf->datalen)) {
*buf_flushlen = buf->datalen;
}
@ -188,7 +205,7 @@ buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz,
else
flushlen0 = buf->head->datalen;
r = flush_chunk(s, buf, buf->head, flushlen0, buf_flushlen);
r = flush_chunk(fd, buf, buf->head, flushlen0, buf_flushlen, is_socket);
check();
if (r < 0)
return r;
@ -200,3 +217,55 @@ buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz,
tor_assert(flushed < INT_MAX);
return (int)flushed;
}
/** Write data from <b>buf</b> to the socket <b>s</b>. Write at most
* <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by
* the number of bytes actually written, and remove the written bytes
* from the buffer. Return the number of bytes written on success,
* -1 on failure. Return 0 if write() would block.
*/
int
buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz,
size_t *buf_flushlen)
{
return buf_flush_to_fd(buf, s, sz, buf_flushlen, true);
}
/** Read from socket <b>s</b>, writing onto end of <b>buf</b>. Read at most
* <b>at_most</b> bytes, growing the buffer as necessary. If recv() returns 0
* (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on
* error; else return the number of bytes read.
*/
int
buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most,
int *reached_eof,
int *socket_error)
{
return buf_read_from_fd(buf, s, at_most, reached_eof, socket_error, true);
}
/** Write data from <b>buf</b> to the pipe <b>fd</b>. Write at most
* <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by
* the number of bytes actually written, and remove the written bytes
* from the buffer. Return the number of bytes written on success,
* -1 on failure. Return 0 if write() would block.
*/
int
buf_flush_to_pipe(buf_t *buf, int fd, size_t sz,
size_t *buf_flushlen)
{
return buf_flush_to_fd(buf, fd, sz, buf_flushlen, false);
}
/** Read from pipe <b>fd</b>, writing onto end of <b>buf</b>. Read at most
* <b>at_most</b> bytes, growing the buffer as necessary. If read() returns 0
* (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on
* error; else return the number of bytes read.
*/
int
buf_read_from_pipe(buf_t *buf, int fd, size_t at_most,
int *reached_eof,
int *socket_error)
{
return buf_read_from_fd(buf, fd, at_most, reached_eof, socket_error, false);
}

View File

@ -24,4 +24,11 @@ int buf_read_from_socket(struct buf_t *buf, tor_socket_t s, size_t at_most,
int buf_flush_to_socket(struct buf_t *buf, tor_socket_t s, size_t sz,
size_t *buf_flushlen);
int buf_read_from_pipe(struct buf_t *buf, int fd, size_t at_most,
int *reached_eof,
int *socket_error);
int buf_flush_to_pipe(struct buf_t *buf, int fd, size_t sz,
size_t *buf_flushlen);
#endif /* !defined(TOR_BUFFERS_H) */

View File

@ -4,8 +4,9 @@ lib/cc/*.h
lib/container/*.h
lib/ctime/*.h
lib/err/*.h
lib/intmath/*.h
lib/evloop/*.h
lib/fs/*.h
lib/intmath/*.h
lib/log/*.h
lib/malloc/*.h
lib/net/*.h

View File

@ -9,9 +9,11 @@ src_lib_libtor_process_a_SOURCES = \
src/lib/process/daemon.c \
src/lib/process/env.c \
src/lib/process/pidfile.c \
src/lib/process/process.c \
src/lib/process/process_unix.c \
src/lib/process/process_win32.c \
src/lib/process/restrict.c \
src/lib/process/setuid.c \
src/lib/process/subprocess.c \
src/lib/process/waitpid.c \
src/lib/process/winprocess_sys.c
@ -24,8 +26,10 @@ noinst_HEADERS += \
src/lib/process/daemon.h \
src/lib/process/env.h \
src/lib/process/pidfile.h \
src/lib/process/process.h \
src/lib/process/process_unix.h \
src/lib/process/process_win32.h \
src/lib/process/restrict.h \
src/lib/process/setuid.h \
src/lib/process/subprocess.h \
src/lib/process/waitpid.h \
src/lib/process/winprocess_sys.h

797
src/lib/process/process.c Normal file
View File

@ -0,0 +1,797 @@
/* 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.c
* \brief Module for working with other processes.
**/
#define PROCESS_PRIVATE
#include "lib/container/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
/** A list of all <b>process_t</b> instances currently allocated. */
static smartlist_t *processes;
/**
* Boolean. If true, then Tor may call execve or CreateProcess via
* tor_spawn_background.
**/
static int may_spawn_background_process = 1;
/** Structure to represent a child process. */
struct process_t {
/** Process status. */
process_status_t status;
/** Which protocol is the process using? */
process_protocol_t protocol;
/** Which function to call when we have data ready from stdout? */
process_read_callback_t stdout_read_callback;
/** Which function to call when we have data ready from stderr? */
process_read_callback_t stderr_read_callback;
/** Which function call when our process terminated? */
process_exit_callback_t exit_callback;
/** Our exit code when the process have terminated. */
process_exit_code_t exit_code;
/** Name of the command we want to execute (for example: /bin/ls). */
char *command;
/** The arguments used for the new process. The format here is one argument
* per element of the smartlist_t. On Windows these arguments are combined
* together using the <b>tor_join_win_cmdline</b> function. On Unix the
* process name (argv[0]) and the trailing NULL is added automatically before
* the process is executed. */
smartlist_t *arguments;
/** The environment used for the new process. */
smartlist_t *environment;
/** Buffer to store data from stdout when it is read. */
buf_t *stdout_buffer;
/** Buffer to store data from stderr when it is read. */
buf_t *stderr_buffer;
/** Buffer to store data to stdin before it is written. */
buf_t *stdin_buffer;
/** Do we need to store some custom data with the process? */
void *data;
#ifndef _WIN32
/** Our Unix process handle. */
process_unix_t *unix_process;
#else
/** Our Win32 process handle. */
process_win32_t *win32_process;
#endif
};
/** Convert a given process status in <b>status</b> to its string
* representation. */
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";
}
/* LCOV_EXCL_START */
tor_assert_unreached();
return NULL;
/* LCOV_EXCL_STOP */
}
/** Convert a given process protocol in <b>protocol</b> to its string
* representation. */
const char *
process_protocol_to_string(process_protocol_t protocol)
{
switch (protocol) {
case PROCESS_PROTOCOL_LINE:
return "Line";
case PROCESS_PROTOCOL_RAW:
return "Raw";
}
/* LCOV_EXCL_START */
tor_assert_unreached();
return NULL;
/* LCOV_EXCL_STOP */
}
/**
* Turn off may_spawn_background_process, so that all future calls to
* tor_spawn_background are guaranteed to fail.
**/
void
tor_disable_spawning_background_processes(void)
{
may_spawn_background_process = 0;
}
/** Initialize the Process subsystem. This function initializes the Process
* subsystem's global state. For cleaning up, <b>process_free_all()</b> should
* be called. */
void
process_init(void)
{
processes = smartlist_new();
#ifdef _WIN32
process_win32_init();
#endif
}
/** Free up all resources that is handled by the Process subsystem. Note that
* this call does not terminate already running processes. */
void
process_free_all(void)
{
#ifdef _WIN32
process_win32_deinit();
#endif
SMARTLIST_FOREACH(processes, process_t *, x, process_free(x));
smartlist_free(processes);
}
/** Get a list of all processes. This function returns a smartlist of
* <b>process_t</b> containing all the currently allocated processes. */
const smartlist_t *
process_get_all_processes(void)
{
return processes;
}
/** Allocate and initialize a new process. This function returns a newly
* allocated and initialized process data, which can be used to configure and
* later run a subprocess of Tor. Use the various <b>process_set_*()</b>
* methods to configure it and run the process using <b>process_exec()</b>. Use
* <b>command</b> to specify the path to the command to run. You can either
* specify an absolute path to the command or relative where Tor will use the
* underlying operating system's functionality for finding the command to run.
* */
process_t *
process_new(const char *command)
{
tor_assert(command);
process_t *process;
process = tor_malloc_zero(sizeof(process_t));
/* Set our command. */
process->command = tor_strdup(command);
/* By default we are not running. */
process->status = PROCESS_STATUS_NOT_RUNNING;
/* Prepare process environment. */
process->arguments = smartlist_new();
process->environment = smartlist_new();
/* Prepare the buffers. */
process->stdout_buffer = buf_new();
process->stderr_buffer = buf_new();
process->stdin_buffer = buf_new();
#ifndef _WIN32
/* Prepare our Unix process handle. */
process->unix_process = process_unix_new();
#else
/* Prepare our Win32 process handle. */
process->win32_process = process_win32_new();
#endif
smartlist_add(processes, process);
return process;
}
/** Deallocate the given process in <b>process</b>. */
void
process_free_(process_t *process)
{
if (! process)
return;
/* Cleanup parameters. */
tor_free(process->command);
/* Cleanup arguments and environment. */
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);
/* Cleanup the buffers. */
buf_free(process->stdout_buffer);
buf_free(process->stderr_buffer);
buf_free(process->stdin_buffer);
#ifndef _WIN32
/* Cleanup our Unix process handle. */
process_unix_free(process->unix_process);
#else
/* Cleanup our Win32 process handle. */
process_win32_free(process->win32_process);
#endif
smartlist_remove(processes, process);
tor_free(process);
}
/** Execute the given process. This function executes the given process as a
* subprocess of Tor. Returns <b>PROCESS_STATUS_RUNNING</b> upon success. */
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
/* Update our state. */
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;
}
/** Terminate the given process. Returns true on success,
* otherwise false. */
bool
process_terminate(process_t *process)
{
tor_assert(process);
/* Terminating a non-running process isn't going to work. */
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
}
/** Returns the unique process identifier for the given <b>process</b>. */
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
}
/** Set the callback function for output from the child process's standard out
* handle. This function sets the callback function which is called every time
* the child process have written output to its standard out file handle.
*
* Use <b>process_set_protocol(process, PROCESS_PROTOCOL_LINE)</b> if you want
* the callback to only contain complete "\n" or "\r\n" terminated lines. */
void
process_set_stdout_read_callback(process_t *process,
process_read_callback_t callback)
{
tor_assert(process);
process->stdout_read_callback = callback;
}
/** Set the callback function for output from the child process's standard
* error handle. This function sets the callback function which is called
* every time the child process have written output to its standard error file
* handle.
*
* Use <b>process_set_protocol(process, PROCESS_PROTOCOL_LINE)</b> if you want
* the callback to only contain complete "\n" or "\r\n" terminated lines. */
void
process_set_stderr_read_callback(process_t *process,
process_read_callback_t callback)
{
tor_assert(process);
process->stderr_read_callback = callback;
}
/** Set the callback function for process exit notification. The
* <b>callback</b> function will be called every time your child process have
* terminated. */
void
process_set_exit_callback(process_t *process,
process_exit_callback_t callback)
{
tor_assert(process);
process->exit_callback = callback;
}
/** Get the current command of the given process. */
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;
}
/** Get the currently used protocol of the given process. */
process_protocol_t
process_get_protocol(const process_t *process)
{
tor_assert(process);
return process->protocol;
}
/** Set opague pointer to data. This function allows you to store a pointer to
* your own data in the given process. Use <b>process_get_data()</b> in the
* various callback functions to retrieve the data again.
*
* Note that the given process does NOT take ownership of the data and you are
* responsible for freeing up any resources allocated by the given data.
* */
void
process_set_data(process_t *process, void *data)
{
tor_assert(process);
process->data = data;
}
/** Get the opaque pointer to callback data from the given process. This
* function allows you get the data you stored with <b>process_set_data()</b>
* in the different callback functions. */
void *
process_get_data(const process_t *process)
{
tor_assert(process);
return process->data;
}
/** Set the status of a given process. */
void
process_set_status(process_t *process, process_status_t status)
{
tor_assert(process);
process->status = status;
}
/** Get the status of the given process. */
process_status_t
process_get_status(const process_t *process)
{
tor_assert(process);
return process->status;
}
/** Append an argument to the list of arguments in the given process. */
void
process_append_argument(process_t *process, const char *argument)
{
tor_assert(process);
tor_assert(argument);
smartlist_add(process->arguments, tor_strdup(argument));
}
/** Returns a list of arguments (excluding the command itself) from the
* given process. */
const smartlist_t *
process_get_arguments(const process_t *process)
{
tor_assert(process);
return process->arguments;
}
/** Returns a newly allocated Unix style argument vector. Use <b>tor_free()</b>
* to deallocate it after use. */
char **
process_get_argv(const process_t *process)
{
tor_assert(process);
/** Generate a Unix style process argument vector from our process's
* arguments smartlist_t. */
char **argv = NULL;
char *filename = process->command;
const smartlist_t *arguments = process->arguments;
const size_t size = smartlist_len(arguments);
/* Make space for the process filename as argv[0] and a trailing NULL. */
argv = tor_malloc_zero(sizeof(char *) * (size + 2));
/* Set our filename as first argument. */
argv[0] = filename;
/* Put in the rest of the values from arguments. */
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;
}
/** This function clears the internal environment and copies over every string
* from <b>env</b> as the new environment. */
void
process_reset_environment(process_t *process, const smartlist_t *env)
{
tor_assert(process);
tor_assert(env);
/* Cleanup old environment. */
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)));
}
/** Set the given <b>key</b>/<b>value</b> pair as environment variable in the
* given process. */
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);
}
/** Returns a newly allocated <b>process_environment_t</b> containing the
* environment variables for the given process. */
process_environment_t *
process_get_environment(const process_t *process)
{
tor_assert(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;
}
#else
/** Get the internal handle for Windows backend. */
process_win32_t *
process_get_win32_process(const process_t *process)
{
tor_assert(process);
tor_assert(process->win32_process);
return process->win32_process;
}
#endif
/** Write <b>size</b> bytes of <b>data</b> to the given process's standard
* input. */
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);
}
/** As tor_vsnprintf(), but write the data to the given process's standard
* input. */
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);
process_write(process, (uint8_t *)data, size);
tor_free(data);
}
/** As tor_snprintf(), but write the data to the given process's standard
* input. */
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);
}
/** This function is called by the Process backend when a given process have
* data that is ready to be read from the child process's standard output
* handle. */
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);
}
/** This function is called by the Process backend when a given process have
* data that is ready to be read from the child process's standard error
* handle. */
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);
}
/** This function is called by the Process backend when a given process is
* allowed to begin writing data to the standard input of the child process. */
void
process_notify_event_stdin(process_t *process)
{
tor_assert(process);
process_write_stdin(process, process->stdin_buffer);
}
/** This function is called by the Process backend when a given process have
* terminated. The exit status code is passed in <b>exit_code</b>. We mark the
* process as no longer running and calls the <b>exit_callback</b> with
* information about the process termination. The given <b>process</b> is
* free'd iff the exit_callback returns true. */
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);
/* Update our state. */
process_set_status(process, PROCESS_STATUS_NOT_RUNNING);
process->exit_code = exit_code;
/* Call our exit callback, if it exists. */
bool free_process_handle = false;
/* The exit callback will tell us if we should process_free() our handle. */
if (process->exit_callback)
free_process_handle = process->exit_callback(process, exit_code);
if (free_process_handle)
process_free(process);
}
/** This function is called whenever the Process backend have notified us that
* there is data to be read from its standard out handle. Returns the number of
* bytes that have been put into the given buffer. */
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
}
/** This function is called whenever the Process backend have notified us that
* there is data to be read from its standard error handle. Returns the number
* of bytes that have been put into the given buffer. */
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
}
/** This function calls the backend function for the given process whenever
* there is data to be written to the backends' file handles. */
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
}
/** This function calls the protocol handlers based on the value of
* <b>process_get_protocol(process)</b>. Currently we call
* <b>process_read_buffer()</b> for <b>PROCESS_PROTOCOL_RAW</b> and
* <b>process_read_lines()</b> for <b>PROCESS_PROTOCOL_LINE</b>. */
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:
/* LCOV_EXCL_START */
tor_assert_unreached();
return;
/* LCOV_EXCL_STOP */
}
}
/** This function takes the content of the given <b>buffer</b> and passes it to
* the given <b>callback</b> function, but ensures that an additional zero byte
* is added to the end of the data such that the given callback implementation
* can threat the content as a ASCIIZ string. */
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);
/* We allocate an extra byte for the zero byte in the end. */
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);
}
/** This function tries to extract complete lines from the given <b>buffer</b>
* and calls the given <b>callback</b> function whenever it has a complete
* line. Before calling <b>callback</b> we remove the trailing "\n" or "\r\n"
* from the line. If we are unable to extract a complete line we leave the data
* in the buffer for next call. */
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);
/* A complete line should always be smaller than the size of our
* buffer. */
tor_assert(ret != -1);
/* Remove \n from the end of the line. */
if (line_size >= 1 && data[line_size - 1] == '\n') {
data[line_size - 1] = '\0';
--line_size;
}
/* Remove \r from the end of the line. */
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);
/* We have read a whole line, let's see if there is more lines to read.
* */
continue;
}
/* No complete line for us to read. We are done for now. */
tor_assert_nonfatal(ret == 0);
break;
}
tor_free(data);
}

142
src/lib/process/process.h Normal file
View File

@ -0,0 +1,142 @@
/* 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.h
* \brief Header for process.c
**/
#ifndef TOR_PROCESS_H
#define TOR_PROCESS_H
#include "orconfig.h"
#include "lib/malloc/malloc.h"
#include "lib/string/printf.h"
/** Maximum number of bytes to write to a process' stdin. */
#define PROCESS_MAX_WRITE (1024)
/** Maximum number of bytes to read from a process' stdout/stderr. */
#define PROCESS_MAX_READ (1024)
typedef enum {
/** The process is not running. */
PROCESS_STATUS_NOT_RUNNING,
/** The process is running. */
PROCESS_STATUS_RUNNING,
/** The process is in an erroneous state. */
PROCESS_STATUS_ERROR
} process_status_t;
const char *process_status_to_string(process_status_t status);
typedef enum {
/** Pass complete \n-terminated lines to the
* callback (with the \n or \r\n removed). */
PROCESS_PROTOCOL_LINE,
/** Pass the raw response from read() to the callback. */
PROCESS_PROTOCOL_RAW
} process_protocol_t;
const char *process_protocol_to_string(process_protocol_t protocol);
void tor_disable_spawning_background_processes(void);
struct process_t;
typedef struct process_t process_t;
typedef uint64_t process_exit_code_t;
typedef uint64_t process_pid_t;
typedef void (*process_read_callback_t)(process_t *,
const char *,
size_t);
typedef bool
(*process_exit_callback_t)(process_t *, process_exit_code_t);
void process_init(void);
void process_free_all(void);
const smartlist_t *process_get_all_processes(void);
process_t *process_new(const char *command);
void process_free_(process_t *process);
#define process_free(s) FREE_AND_NULL(process_t, process_free_, (s))
process_status_t process_exec(process_t *process);
bool process_terminate(process_t *process);
process_pid_t process_get_pid(process_t *process);
void process_set_stdout_read_callback(process_t *,
process_read_callback_t);
void process_set_stderr_read_callback(process_t *,
process_read_callback_t);
void process_set_exit_callback(process_t *,
process_exit_callback_t);
const char *process_get_command(const process_t *process);
void process_append_argument(process_t *process, const char *argument);
const smartlist_t *process_get_arguments(const process_t *process);
char **process_get_argv(const process_t *process);
void process_reset_environment(process_t *process, const smartlist_t *env);
void process_set_environment(process_t *process,
const char *key,
const char *value);
struct process_environment_t;
struct process_environment_t *process_get_environment(const process_t *);
void process_set_protocol(process_t *process, process_protocol_t protocol);
process_protocol_t process_get_protocol(const process_t *process);
void process_set_data(process_t *process, void *data);
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);
#else
struct process_win32_t;
struct process_win32_t *process_get_win32_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,
const char *format, va_list args) CHECK_PRINTF(2, 0);
void process_printf(process_t *process,
const char *format, ...) CHECK_PRINTF(2, 3);
void process_notify_event_stdout(process_t *process);
void process_notify_event_stderr(process_t *process);
void process_notify_event_stdin(process_t *process);
void process_notify_event_exit(process_t *process,
process_exit_code_t);
#ifdef PROCESS_PRIVATE
MOCK_DECL(STATIC int, process_read_stdout, (process_t *, buf_t *));
MOCK_DECL(STATIC int, process_read_stderr, (process_t *, buf_t *));
MOCK_DECL(STATIC void, process_write_stdin, (process_t *, buf_t *));
STATIC void process_read_data(process_t *process,
buf_t *buffer,
process_read_callback_t callback);
STATIC void process_read_buffer(process_t *process,
buf_t *buffer,
process_read_callback_t callback);
STATIC void process_read_lines(process_t *process,
buf_t *buffer,
process_read_callback_t callback);
#endif /* defined(PROCESS_PRIVATE). */
#endif /* defined(TOR_PROCESS_H). */

View File

@ -0,0 +1,704 @@
/* 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);
if (unix_process->stdin_handle.is_writing)
process_unix_stop_writing(&unix_process->stdin_handle);
/* Close all our file descriptors. */
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);
}
/** 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;
}
/** Terminate the given process. Returns true on success, otherwise false. */
bool
process_unix_terminate(process_t *process)
{
tor_assert(process);
process_unix_t *unix_process = process_get_unix_process(process);
/* All running processes should have a waitpid. */
if (BUG(unix_process->waitpid == NULL))
return false;
bool success = true;
/* Send a SIGTERM to our child process. */
int ret;
ret = kill(unix_process->pid, SIGTERM);
if (ret == -1) {
log_warn(LD_PROCESS, "Unable to terminate process: %s",
strerror(errno));
success = false;
}
/* Close all our FD's. */
if (! process_unix_close_file_descriptors(unix_process))
success = false;
return success;
}
/** Returns the unique process identifier for the given <b>process</b>. */
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;
}
/** 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);
/* Remove our waitpid callback. */
clear_waitpid_callback(unix_process->waitpid);
unix_process->waitpid = NULL;
/* Notify our process. */
process_notify_event_exit(process, status);
/* Make sure you don't modify the process after we have called
* process_notify_event_exit() on it, to allow users to process_free() it in
* the exit callback. */
}
/** 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;
}
/** Close the standard in, out, and error handles of the given
* <b>unix_process</b>. */
STATIC bool
process_unix_close_file_descriptors(process_unix_t *unix_process)
{
tor_assert(unix_process);
int ret;
bool success = true;
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 /* defined(_WIN32). */

View File

@ -0,0 +1,68 @@
/* 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);
bool process_unix_terminate(struct process_t *process);
process_pid_t process_unix_get_pid(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 *);
STATIC bool process_unix_close_file_descriptors(process_unix_t *);
#endif /* defined(PROCESS_UNIX_PRIVATE). */
#endif /* defined(_WIN32). */
#endif /* defined(TOR_PROCESS_UNIX_H). */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
/* 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_win32.h
* \brief Header for process_win32.c
**/
#ifndef TOR_PROCESS_WIN32_H
#define TOR_PROCESS_WIN32_H
#ifdef _WIN32
#include "orconfig.h"
#include "lib/malloc/malloc.h"
#include "lib/evloop/compat_libevent.h"
#include <windows.h>
struct process_t;
struct process_win32_t;
typedef struct process_win32_t process_win32_t;
process_win32_t *process_win32_new(void);
void process_win32_free_(process_win32_t *win32_process);
#define process_win32_free(s) \
FREE_AND_NULL(process_win32_t, process_win32_free_, (s))
void process_win32_init(void);
void process_win32_deinit(void);
process_status_t process_win32_exec(struct process_t *process);
bool process_win32_terminate(struct process_t *process);
process_pid_t process_win32_get_pid(struct process_t *process);
int process_win32_write(struct process_t *process, buf_t *buffer);
int process_win32_read_stdout(struct process_t *process, buf_t *buffer);
int process_win32_read_stderr(struct process_t *process, buf_t *buffer);
void process_win32_trigger_completion_callbacks(void);
/* Timer handling. */
void process_win32_timer_start(void);
void process_win32_timer_stop(void);
bool process_win32_timer_running(void);
#ifdef PROCESS_WIN32_PRIVATE
STATIC void process_win32_timer_callback(periodic_timer_t *, void *);
STATIC bool process_win32_timer_test_process(process_t *);
/* I/O pipe handling. */
struct process_win32_handle_t;
typedef struct process_win32_handle_t process_win32_handle_t;
typedef enum process_win32_pipe_type_t {
/** This pipe is used for reading. */
PROCESS_WIN32_PIPE_TYPE_READER,
/** This pipe is used for writing. */
PROCESS_WIN32_PIPE_TYPE_WRITER
} process_win32_pipe_type_t;
STATIC bool process_win32_create_pipe(HANDLE *,
HANDLE *,
SECURITY_ATTRIBUTES *,
process_win32_pipe_type_t);
STATIC void process_win32_cleanup_handle(process_win32_handle_t *handle);
STATIC VOID WINAPI process_win32_stdout_read_done(DWORD,
DWORD,
LPOVERLAPPED);
STATIC VOID WINAPI process_win32_stderr_read_done(DWORD,
DWORD,
LPOVERLAPPED);
STATIC VOID WINAPI process_win32_stdin_write_done(DWORD,
DWORD,
LPOVERLAPPED);
STATIC int process_win32_read_from_handle(process_win32_handle_t *,
buf_t *,
LPOVERLAPPED_COMPLETION_ROUTINE);
STATIC bool process_win32_handle_read_completion(process_win32_handle_t *,
DWORD,
DWORD);
STATIC char *format_win_cmdline_argument(const char *arg);
STATIC char *tor_join_win_cmdline(const char *argv[]);
#endif /* defined(PROCESS_WIN32_PRIVATE). */
#endif /* ! defined(_WIN32). */
#endif /* defined(TOR_PROCESS_WIN32_H). */

File diff suppressed because it is too large Load Diff

View File

@ -1,134 +0,0 @@
/* 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 subprocess.h
* \brief Header for subprocess.c
**/
#ifndef TOR_SUBPROCESS_H
#define TOR_SUBPROCESS_H
#include "lib/cc/torint.h"
#include "lib/testsupport/testsupport.h"
#include <stddef.h>
#ifdef _WIN32
#include <windows.h>
#endif
struct smartlist_t;
void tor_disable_spawning_background_processes(void);
typedef struct process_handle_t process_handle_t;
struct process_environment_t;
int tor_spawn_background(const char *const filename, const char **argv,
struct process_environment_t *env,
process_handle_t **process_handle_out);
#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
/** Status of an I/O stream. */
enum stream_status {
IO_STREAM_OKAY,
IO_STREAM_EAGAIN,
IO_STREAM_TERM,
IO_STREAM_CLOSED
};
const char *stream_status_to_string(enum stream_status stream_status);
enum stream_status get_string_from_pipe(int fd, char *buf, size_t count);
/* Values of process_handle_t.status. */
#define PROCESS_STATUS_NOTRUNNING 0
#define PROCESS_STATUS_RUNNING 1
#define PROCESS_STATUS_ERROR -1
#ifdef SUBPROCESS_PRIVATE
struct waitpid_callback_t;
/** Structure to represent the state of a process with which Tor is
* communicating. The contents of this structure are private to util.c */
struct process_handle_t {
/** One of the PROCESS_STATUS_* values */
int status;
#ifdef _WIN32
HANDLE stdin_pipe;
HANDLE stdout_pipe;
HANDLE stderr_pipe;
PROCESS_INFORMATION pid;
#else /* !(defined(_WIN32)) */
int stdin_pipe;
int stdout_pipe;
int stderr_pipe;
pid_t pid;
/** If the process has not given us a SIGCHLD yet, this has the
* waitpid_callback_t that gets invoked once it has. Otherwise this
* contains NULL. */
struct waitpid_callback_t *waitpid_cb;
/** The exit status reported by waitpid. */
int waitpid_exit_status;
#endif /* defined(_WIN32) */
};
#endif /* defined(SUBPROCESS_PRIVATE) */
/* Return values of tor_get_exit_code() */
#define PROCESS_EXIT_RUNNING 1
#define PROCESS_EXIT_EXITED 0
#define PROCESS_EXIT_ERROR -1
int tor_get_exit_code(process_handle_t *process_handle,
int block, int *exit_code);
int tor_split_lines(struct smartlist_t *sl, char *buf, int len);
#ifdef _WIN32
ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count,
const process_handle_t *process);
#else
ssize_t tor_read_all_handle(int fd, char *buf, size_t count,
const process_handle_t *process,
int *eof);
#endif /* defined(_WIN32) */
ssize_t tor_read_all_from_process_stdout(
const process_handle_t *process_handle, char *buf, size_t count);
ssize_t tor_read_all_from_process_stderr(
const process_handle_t *process_handle, char *buf, size_t count);
char *tor_join_win_cmdline(const char *argv[]);
int tor_process_get_pid(process_handle_t *process_handle);
#ifdef _WIN32
HANDLE tor_process_get_stdout_pipe(process_handle_t *process_handle);
#else
int tor_process_get_stdout_pipe(process_handle_t *process_handle);
#endif
#ifdef _WIN32
MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(HANDLE *handle,
enum stream_status *stream_status));
#else
MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(int fd,
enum stream_status *stream_status));
#endif /* defined(_WIN32) */
int tor_terminate_process(process_handle_t *process_handle);
MOCK_DECL(void, tor_process_handle_destroy,(process_handle_t *process_handle,
int also_terminate_process));
#ifdef SUBPROCESS_PRIVATE
/* Prototypes for private functions only used by util.c (and unit tests) */
#ifndef _WIN32
STATIC int format_helper_exit_status(unsigned char child_state,
int saved_errno, char *hex_errno);
/* Space for hex values of child state, a slash, saved_errno (with
leading minus) and newline (no null) */
#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \
1 + sizeof(int) * 2 + 1)
#endif /* !defined(_WIN32) */
#endif /* defined(SUBPROCESS_PRIVATE) */
#endif

View File

@ -1,4 +1,4 @@
all: test.exe test-child.exe bench.exe
all: test.exe bench.exe
CFLAGS = /I ..\win32 /I ..\..\..\build-alpha\include /I ..\common /I ..\or \
/I ..\ext
@ -30,8 +30,5 @@ test.exe: $(TEST_OBJECTS)
bench.exe: bench.obj
$(CC) $(CFLAGS) bench.obj $(LIBS) ..\common\*.lib /Fe$@
test-child.exe: test-child.obj
$(CC) $(CFLAGS) test-child.obj /Fe$@
clean:
del *.obj *.lib test.exe bench.exe test-child.exe
del *.obj *.lib test.exe bench.exe

View File

@ -65,7 +65,7 @@ noinst_PROGRAMS+= \
src/test/test \
src/test/test-slow \
src/test/test-memwipe \
src/test/test-child \
src/test/test-process \
src/test/test_workqueue \
src/test/test-switch-id \
src/test/test-timers
@ -153,6 +153,7 @@ src_test_test_SOURCES += \
src/test/test_pem.c \
src/test/test_periodic_event.c \
src/test/test_policy.c \
src/test/test_process.c \
src/test/test_procmon.c \
src/test/test_proto_http.c \
src/test/test_proto_misc.c \
@ -202,7 +203,7 @@ if UNITTESTS_ENABLED
src_test_test_slow_SOURCES += \
src/test/test_slow.c \
src/test/test_crypto_slow.c \
src/test/test_util_slow.c \
src/test/test_process_slow.c \
src/test/testing_common.c \
src/test/testing_rsakeys.c \
src/ext/tinytest.c

View File

@ -1,61 +0,0 @@
/* Copyright (c) 2011-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#include "orconfig.h"
#include <stdio.h>
#ifdef _WIN32
#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#else
#include <unistd.h>
#endif /* defined(_WIN32) */
#include <string.h>
#ifdef _WIN32
#define SLEEP(sec) Sleep((sec)*1000)
#else
#define SLEEP(sec) sleep(sec)
#endif
/** Trivial test program which prints out its command line arguments so we can
* check if tor_spawn_background() works */
int
main(int argc, char **argv)
{
int i;
int delay = 1;
int fast = 0;
if (argc > 1) {
if (!strcmp(argv[1], "--hang")) {
delay = 60;
} else if (!strcmp(argv[1], "--fast")) {
fast = 1;
delay = 0;
}
}
fprintf(stdout, "OUT\n");
fprintf(stderr, "ERR\n");
for (i = 1; i < argc; i++)
fprintf(stdout, "%s\n", argv[i]);
if (!fast)
fprintf(stdout, "SLEEPING\n");
/* We need to flush stdout so that test_util_spawn_background_partial_read()
succeed. Otherwise ReadFile() will get the entire output in one */
// XXX: Can we make stdio flush on newline?
fflush(stdout);
if (!fast)
SLEEP(1);
fprintf(stdout, "DONE\n");
fflush(stdout);
if (fast)
return 0;
while (--delay) {
SLEEP(1);
}
return 0;
}

85
src/test/test-process.c Normal file
View File

@ -0,0 +1,85 @@
/* Copyright (c) 2011-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#include "orconfig.h"
#include <stdio.h>
#ifdef _WIN32
#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#else
#include <unistd.h>
#endif /* defined(_WIN32) */
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
#define SLEEP(sec) Sleep((sec)*1000)
#else
#define SLEEP(sec) sleep(sec)
#endif
/* Trivial test program to test process_t. */
int
main(int argc, char **argv)
{
/* Does our process get the right arguments? */
for (int i = 0; i < argc; ++i) {
fprintf(stdout, "argv[%d] = '%s'\n", i, argv[i]);
fflush(stdout);
}
/* Make sure our process got our environment variable. */
fprintf(stdout, "Environment variable TOR_TEST_ENV = '%s'\n",
getenv("TOR_TEST_ENV"));
fflush(stdout);
/* Test line handling on stdout and stderr. */
fprintf(stdout, "Output on stdout\nThis is a new line\n");
fflush(stdout);
fprintf(stderr, "Output on stderr\nThis is a new line\n");
fflush(stderr);
fprintf(stdout, "Partial line on stdout ...");
fflush(stdout);
fprintf(stderr, "Partial line on stderr ...");
fflush(stderr);
SLEEP(2);
fprintf(stdout, "end of partial line on stdout\n");
fflush(stdout);
fprintf(stderr, "end of partial line on stderr\n");
fflush(stderr);
/* Echo input from stdin. */
char buffer[1024];
int count = 0;
while (fgets(buffer, sizeof(buffer), stdin)) {
/* Strip the newline. */
size_t size = strlen(buffer);
if (size >= 1 && buffer[size - 1] == '\n') {
buffer[size - 1] = '\0';
--size;
}
if (size >= 1 && buffer[size - 1] == '\r') {
buffer[size - 1] = '\0';
--size;
}
fprintf(stdout, "Read line from stdin: '%s'\n", buffer);
fflush(stdout);
if (++count == 3)
break;
}
fprintf(stdout, "We are done for here, thank you!\n");
return 0;
}

View File

@ -899,6 +899,7 @@ struct testgroup_t testgroups[] = {
{ "periodic-event/" , periodic_event_tests },
{ "policy/" , policy_tests },
{ "procmon/", procmon_tests },
{ "process/", process_tests },
{ "proto/http/", proto_http_tests },
{ "proto/misc/", proto_misc_tests },
{ "protover/", protover_tests },

View File

@ -241,6 +241,7 @@ extern struct testcase_t pem_tests[];
extern struct testcase_t periodic_event_tests[];
extern struct testcase_t policy_tests[];
extern struct testcase_t procmon_tests[];
extern struct testcase_t process_tests[];
extern struct testcase_t proto_http_tests[];
extern struct testcase_t proto_misc_tests[];
extern struct testcase_t protover_tests[];
@ -270,7 +271,7 @@ extern struct testcase_t voting_schedule_tests[];
extern struct testcase_t x509_tests[];
extern struct testcase_t slow_crypto_tests[];
extern struct testcase_t slow_util_tests[];
extern struct testcase_t slow_process_tests[];
extern struct testgroup_t testgroups[];

View File

@ -9,7 +9,6 @@
#include "lib/err/torerr.h"
#include "lib/log/log.h"
#include "test/test.h"
#include "lib/process/subprocess.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
@ -117,22 +116,27 @@ test_sigsafe_err(void *arg)
content = read_file_to_str(fn, 0, NULL);
tt_ptr_op(content, OP_NE, NULL);
tor_split_lines(lines, content, (int)strlen(content));
smartlist_split_string(lines, content, "\n", 0, 0);
tt_int_op(smartlist_len(lines), OP_GE, 5);
if (strstr(smartlist_get(lines, 0), "opening new log file"))
if (strstr(smartlist_get(lines, 0), "opening new log file")) {
void *item = smartlist_get(lines, 0);
smartlist_del_keeporder(lines, 0);
tor_free(item);
}
tt_assert(strstr(smartlist_get(lines, 0), "Say, this isn't too cool"));
/* Next line is blank. */
tt_assert(!strcmpstart(smartlist_get(lines, 1), "=============="));
tt_assert(!strcmpstart(smartlist_get(lines, 2), "Minimal."));
/* Next line is blank. */
tt_assert(!strcmpstart(smartlist_get(lines, 3), "=============="));
tt_str_op(smartlist_get(lines, 4), OP_EQ,
tt_str_op(smartlist_get(lines, 1), OP_EQ, "");
tt_assert(!strcmpstart(smartlist_get(lines, 2), "=============="));
tt_assert(!strcmpstart(smartlist_get(lines, 3), "Minimal."));
tt_str_op(smartlist_get(lines, 4), OP_EQ, "");
tt_assert(!strcmpstart(smartlist_get(lines, 5), "=============="));
tt_str_op(smartlist_get(lines, 6), OP_EQ,
"Testing any attempt to manually log from a signal.");
done:
tor_free(content);
SMARTLIST_FOREACH(lines, char *, x, tor_free(x));
smartlist_free(lines);
}

696
src/test/test_process.c Normal file
View File

@ -0,0 +1,696 @@
/* Copyright (c) 2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file test_process.c
* \brief Test cases for the Process API.
*/
#include "orconfig.h"
#include "core/or/or.h"
#include "test/test.h"
#include "lib/process/env.h"
#define PROCESS_PRIVATE
#include "lib/process/process.h"
#define PROCESS_UNIX_PRIVATE
#include "lib/process/process_unix.h"
#define PROCESS_WIN32_PRIVATE
#include "lib/process/process_win32.h"
static const char *stdout_read_buffer;
static const char *stderr_read_buffer;
struct process_data_t {
smartlist_t *stdout_data;
smartlist_t *stderr_data;
smartlist_t *stdin_data;
process_exit_code_t exit_code;
};
typedef struct process_data_t process_data_t;
static process_data_t *
process_data_new(void)
{
process_data_t *process_data = tor_malloc_zero(sizeof(process_data_t));
process_data->stdout_data = smartlist_new();
process_data->stderr_data = smartlist_new();
process_data->stdin_data = smartlist_new();
return process_data;
}
static void
process_data_free(process_data_t *process_data)
{
if (process_data == NULL)
return;
SMARTLIST_FOREACH(process_data->stdout_data, char *, x, tor_free(x));
SMARTLIST_FOREACH(process_data->stderr_data, char *, x, tor_free(x));
SMARTLIST_FOREACH(process_data->stdin_data, char *, x, tor_free(x));
smartlist_free(process_data->stdout_data);
smartlist_free(process_data->stderr_data);
smartlist_free(process_data->stdin_data);
tor_free(process_data);
}
static int
process_mocked_read_stdout(process_t *process, buf_t *buffer)
{
(void)process;
if (stdout_read_buffer != NULL) {
buf_add_string(buffer, stdout_read_buffer);
stdout_read_buffer = NULL;
}
return (int)buf_datalen(buffer);
}
static int
process_mocked_read_stderr(process_t *process, buf_t *buffer)
{
(void)process;
if (stderr_read_buffer != NULL) {
buf_add_string(buffer, stderr_read_buffer);
stderr_read_buffer = NULL;
}
return (int)buf_datalen(buffer);
}
static void
process_mocked_write_stdin(process_t *process, buf_t *buffer)
{
const size_t size = buf_datalen(buffer);
if (size == 0)
return;
char *data = tor_malloc_zero(size + 1);
process_data_t *process_data = process_get_data(process);
buf_get_bytes(buffer, data, size);
smartlist_add(process_data->stdin_data, data);
}
static void
process_stdout_callback(process_t *process, const char *data, size_t size)
{
tt_ptr_op(process, OP_NE, NULL);
tt_ptr_op(data, OP_NE, NULL);
tt_int_op(strlen(data), OP_EQ, size);
process_data_t *process_data = process_get_data(process);
smartlist_add(process_data->stdout_data, tor_strdup(data));
done:
return;
}
static void
process_stderr_callback(process_t *process, const char *data, size_t size)
{
tt_ptr_op(process, OP_NE, NULL);
tt_ptr_op(data, OP_NE, NULL);
tt_int_op(strlen(data), OP_EQ, size);
process_data_t *process_data = process_get_data(process);
smartlist_add(process_data->stderr_data, tor_strdup(data));
done:
return;
}
static bool
process_exit_callback(process_t *process, process_exit_code_t exit_code)
{
tt_ptr_op(process, OP_NE, NULL);
process_data_t *process_data = process_get_data(process);
process_data->exit_code = exit_code;
done:
/* Do not free up our process_t. */
return false;
}
static void
test_default_values(void *arg)
{
(void)arg;
process_init();
process_t *process = process_new("/path/to/nothing");
/* We are not running by default. */
tt_int_op(PROCESS_STATUS_NOT_RUNNING, OP_EQ, process_get_status(process));
/* We use the line protocol by default. */
tt_int_op(PROCESS_PROTOCOL_LINE, OP_EQ, process_get_protocol(process));
/* We don't set any custom data by default. */
tt_ptr_op(NULL, OP_EQ, process_get_data(process));
/* Our command was given to the process_t's constructor in process_new(). */
tt_str_op("/path/to/nothing", OP_EQ, process_get_command(process));
/* Make sure we are listed in the list of proccesses. */
tt_assert(smartlist_contains(process_get_all_processes(),
process));
/* Default PID is 0. */
tt_int_op(0, OP_EQ, process_get_pid(process));
/* Our arguments should be empty. */
tt_int_op(0, OP_EQ,
smartlist_len(process_get_arguments(process)));
done:
process_free(process);
process_free_all();
}
static void
test_environment(void *arg)
{
(void)arg;
process_init();
process_t *process = process_new("");
process_environment_t *env = NULL;
process_set_environment(process, "E", "F");
process_set_environment(process, "C", "D");
process_set_environment(process, "A", "B");
env = process_get_environment(process);
tt_mem_op(env->windows_environment_block, OP_EQ,
"A=B\0C=D\0E=F\0", 12);
tt_str_op(env->unixoid_environment_block[0], OP_EQ,
"A=B");
tt_str_op(env->unixoid_environment_block[1], OP_EQ,
"C=D");
tt_str_op(env->unixoid_environment_block[2], OP_EQ,
"E=F");
tt_ptr_op(env->unixoid_environment_block[3], OP_EQ,
NULL);
process_environment_free(env);
/* Reset our environment. */
smartlist_t *new_env = smartlist_new();
smartlist_add(new_env, (char *)"FOO=bar");
smartlist_add(new_env, (char *)"HELLO=world");
process_reset_environment(process, new_env);
smartlist_free(new_env);
env = process_get_environment(process);
tt_mem_op(env->windows_environment_block, OP_EQ,
"FOO=bar\0HELLO=world\0", 20);
tt_str_op(env->unixoid_environment_block[0], OP_EQ,
"FOO=bar");
tt_str_op(env->unixoid_environment_block[1], OP_EQ,
"HELLO=world");
tt_ptr_op(env->unixoid_environment_block[2], OP_EQ,
NULL);
done:
process_environment_free(env);
process_free(process);
process_free_all();
}
static void
test_stringified_types(void *arg)
{
(void)arg;
/* process_protocol_t values. */
tt_str_op("Raw", OP_EQ, process_protocol_to_string(PROCESS_PROTOCOL_RAW));
tt_str_op("Line", OP_EQ, process_protocol_to_string(PROCESS_PROTOCOL_LINE));
/* process_status_t values. */
tt_str_op("not running", OP_EQ,
process_status_to_string(PROCESS_STATUS_NOT_RUNNING));
tt_str_op("running", OP_EQ,
process_status_to_string(PROCESS_STATUS_RUNNING));
tt_str_op("error", OP_EQ,
process_status_to_string(PROCESS_STATUS_ERROR));
done:
return;
}
static void
test_line_protocol_simple(void *arg)
{
(void)arg;
process_init();
process_data_t *process_data = process_data_new();
process_t *process = process_new("");
process_set_data(process, process_data);
process_set_stdout_read_callback(process, process_stdout_callback);
process_set_stderr_read_callback(process, process_stderr_callback);
MOCK(process_read_stdout, process_mocked_read_stdout);
MOCK(process_read_stderr, process_mocked_read_stderr);
/* Make sure we are running with the line protocol. */
tt_int_op(PROCESS_PROTOCOL_LINE, OP_EQ, process_get_protocol(process));
tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
stdout_read_buffer = "Hello stdout\n";
process_notify_event_stdout(process);
tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
stderr_read_buffer = "Hello stderr\r\n";
process_notify_event_stderr(process);
tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
/* Data should be ready. */
tt_int_op(1, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(1, OP_EQ, smartlist_len(process_data->stderr_data));
/* Check if the data is correct. */
tt_str_op(smartlist_get(process_data->stdout_data, 0), OP_EQ,
"Hello stdout");
tt_str_op(smartlist_get(process_data->stderr_data, 0), OP_EQ,
"Hello stderr");
done:
process_data_free(process_data);
process_free(process);
process_free_all();
UNMOCK(process_read_stdout);
UNMOCK(process_read_stderr);
}
static void
test_line_protocol_multi(void *arg)
{
(void)arg;
process_init();
process_data_t *process_data = process_data_new();
process_t *process = process_new("");
process_set_data(process, process_data);
process_set_stdout_read_callback(process, process_stdout_callback);
process_set_stderr_read_callback(process, process_stderr_callback);
MOCK(process_read_stdout, process_mocked_read_stdout);
MOCK(process_read_stderr, process_mocked_read_stderr);
/* Make sure we are running with the line protocol. */
tt_int_op(PROCESS_PROTOCOL_LINE, OP_EQ, process_get_protocol(process));
tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
stdout_read_buffer = "Hello stdout\r\nOnion Onion Onion\nA B C D\r\n\r\n";
process_notify_event_stdout(process);
tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
stderr_read_buffer = "Hello stderr\nFoo bar baz\nOnion Onion Onion\n";
process_notify_event_stderr(process);
tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
/* Data should be ready. */
tt_int_op(4, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(3, OP_EQ, smartlist_len(process_data->stderr_data));
/* Check if the data is correct. */
tt_str_op(smartlist_get(process_data->stdout_data, 0), OP_EQ,
"Hello stdout");
tt_str_op(smartlist_get(process_data->stdout_data, 1), OP_EQ,
"Onion Onion Onion");
tt_str_op(smartlist_get(process_data->stdout_data, 2), OP_EQ,
"A B C D");
tt_str_op(smartlist_get(process_data->stdout_data, 3), OP_EQ,
"");
tt_str_op(smartlist_get(process_data->stderr_data, 0), OP_EQ,
"Hello stderr");
tt_str_op(smartlist_get(process_data->stderr_data, 1), OP_EQ,
"Foo bar baz");
tt_str_op(smartlist_get(process_data->stderr_data, 2), OP_EQ,
"Onion Onion Onion");
done:
process_data_free(process_data);
process_free(process);
process_free_all();
UNMOCK(process_read_stdout);
UNMOCK(process_read_stderr);
}
static void
test_line_protocol_partial(void *arg)
{
(void)arg;
process_init();
process_data_t *process_data = process_data_new();
process_t *process = process_new("");
process_set_data(process, process_data);
process_set_stdout_read_callback(process, process_stdout_callback);
process_set_stderr_read_callback(process, process_stderr_callback);
MOCK(process_read_stdout, process_mocked_read_stdout);
MOCK(process_read_stderr, process_mocked_read_stderr);
/* Make sure we are running with the line protocol. */
tt_int_op(PROCESS_PROTOCOL_LINE, OP_EQ, process_get_protocol(process));
tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
stdout_read_buffer = "Hello stdout this is a partial line ...";
process_notify_event_stdout(process);
tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
stderr_read_buffer = "Hello stderr this is a partial line ...";
process_notify_event_stderr(process);
tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
/* Data should NOT be ready. */
tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
stdout_read_buffer = " the end\nAnother partial string goes here ...";
process_notify_event_stdout(process);
tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
stderr_read_buffer = " the end\nAnother partial string goes here ...";
process_notify_event_stderr(process);
tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
/* Some data should be ready. */
tt_int_op(1, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(1, OP_EQ, smartlist_len(process_data->stderr_data));
stdout_read_buffer = " the end\nFoo bar baz\n";
process_notify_event_stdout(process);
tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
stderr_read_buffer = " the end\nFoo bar baz\n";
process_notify_event_stderr(process);
tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
/* Some data should be ready. */
tt_int_op(3, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(3, OP_EQ, smartlist_len(process_data->stderr_data));
/* Check if the data is correct. */
tt_str_op(smartlist_get(process_data->stdout_data, 0), OP_EQ,
"Hello stdout this is a partial line ... the end");
tt_str_op(smartlist_get(process_data->stdout_data, 1), OP_EQ,
"Another partial string goes here ... the end");
tt_str_op(smartlist_get(process_data->stdout_data, 2), OP_EQ,
"Foo bar baz");
tt_str_op(smartlist_get(process_data->stderr_data, 0), OP_EQ,
"Hello stderr this is a partial line ... the end");
tt_str_op(smartlist_get(process_data->stderr_data, 1), OP_EQ,
"Another partial string goes here ... the end");
tt_str_op(smartlist_get(process_data->stderr_data, 2), OP_EQ,
"Foo bar baz");
done:
process_data_free(process_data);
process_free(process);
process_free_all();
UNMOCK(process_read_stdout);
UNMOCK(process_read_stderr);
}
static void
test_raw_protocol_simple(void *arg)
{
(void)arg;
process_init();
process_data_t *process_data = process_data_new();
process_t *process = process_new("");
process_set_data(process, process_data);
process_set_protocol(process, PROCESS_PROTOCOL_RAW);
process_set_stdout_read_callback(process, process_stdout_callback);
process_set_stderr_read_callback(process, process_stderr_callback);
MOCK(process_read_stdout, process_mocked_read_stdout);
MOCK(process_read_stderr, process_mocked_read_stderr);
/* Make sure we are running with the raw protocol. */
tt_int_op(PROCESS_PROTOCOL_RAW, OP_EQ, process_get_protocol(process));
tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
stdout_read_buffer = "Hello stdout\n";
process_notify_event_stdout(process);
tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
stderr_read_buffer = "Hello stderr\n";
process_notify_event_stderr(process);
tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
/* Data should be ready. */
tt_int_op(1, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(1, OP_EQ, smartlist_len(process_data->stderr_data));
stdout_read_buffer = "Hello, again, stdout\nThis contains multiple lines";
process_notify_event_stdout(process);
tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
stderr_read_buffer = "Hello, again, stderr\nThis contains multiple lines";
process_notify_event_stderr(process);
tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
/* Data should be ready. */
tt_int_op(2, OP_EQ, smartlist_len(process_data->stdout_data));
tt_int_op(2, OP_EQ, smartlist_len(process_data->stderr_data));
/* Check if the data is correct. */
tt_str_op(smartlist_get(process_data->stdout_data, 0), OP_EQ,
"Hello stdout\n");
tt_str_op(smartlist_get(process_data->stdout_data, 1), OP_EQ,
"Hello, again, stdout\nThis contains multiple lines");
tt_str_op(smartlist_get(process_data->stderr_data, 0), OP_EQ,
"Hello stderr\n");
tt_str_op(smartlist_get(process_data->stderr_data, 1), OP_EQ,
"Hello, again, stderr\nThis contains multiple lines");
done:
process_data_free(process_data);
process_free(process);
process_free_all();
UNMOCK(process_read_stdout);
UNMOCK(process_read_stderr);
}
static void
test_write_simple(void *arg)
{
(void)arg;
process_init();
process_data_t *process_data = process_data_new();
process_t *process = process_new("");
process_set_data(process, process_data);
MOCK(process_write_stdin, process_mocked_write_stdin);
process_write(process, (uint8_t *)"Hello world\n", 12);
process_notify_event_stdin(process);
tt_int_op(1, OP_EQ, smartlist_len(process_data->stdin_data));
process_printf(process, "Hello %s !\n", "moon");
process_notify_event_stdin(process);
tt_int_op(2, OP_EQ, smartlist_len(process_data->stdin_data));
done:
process_data_free(process_data);
process_free(process);
process_free_all();
UNMOCK(process_write_stdin);
}
static void
test_exit_simple(void *arg)
{
(void)arg;
process_init();
process_data_t *process_data = process_data_new();
process_t *process = process_new("");
process_set_data(process, process_data);
process_set_exit_callback(process, process_exit_callback);
/* Our default is 0. */
tt_int_op(0, OP_EQ, process_data->exit_code);
/* Fake that we are a running process. */
process_set_status(process, PROCESS_STATUS_RUNNING);
tt_int_op(process_get_status(process), OP_EQ, PROCESS_STATUS_RUNNING);
/* Fake an exit. */
process_notify_event_exit(process, 1337);
/* Check if our state changed and if our callback fired. */
tt_int_op(process_get_status(process), OP_EQ, PROCESS_STATUS_NOT_RUNNING);
tt_int_op(1337, OP_EQ, process_data->exit_code);
done:
process_set_data(process, process_data);
process_data_free(process_data);
process_free(process);
process_free_all();
}
static void
test_argv_simple(void *arg)
{
(void)arg;
process_init();
process_t *process = process_new("/bin/cat");
char **argv = NULL;
/* Setup some arguments. */
process_append_argument(process, "foo");
process_append_argument(process, "bar");
process_append_argument(process, "baz");
/* Check the number of elements. */
tt_int_op(3, OP_EQ,
smartlist_len(process_get_arguments(process)));
/* Let's try to convert it into a Unix style char **argv. */
argv = process_get_argv(process);
/* Check our values. */
tt_str_op(argv[0], OP_EQ, "/bin/cat");
tt_str_op(argv[1], OP_EQ, "foo");
tt_str_op(argv[2], OP_EQ, "bar");
tt_str_op(argv[3], OP_EQ, "baz");
tt_ptr_op(argv[4], OP_EQ, NULL);
done:
tor_free(argv);
process_free(process);
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
}
static void
test_win32(void *arg)
{
(void)arg;
#ifdef _WIN32
process_init();
process_t *process = process_new("");
char *joined_argv = NULL;
/* On Win32 all processes should have a Win32 process handle. */
tt_ptr_op(NULL, OP_NE, process_get_win32_process(process));
/* Based on some test cases from "Parsing C++ Command-Line Arguments" in
* MSDN but we don't exercise all quoting rules because tor_join_win_cmdline
* will try to only generate simple cases for the child process to parse;
* i.e. we never embed quoted strings in arguments. */
const char *argvs[][4] = {
{"a", "bb", "CCC", NULL}, // Normal
{NULL, NULL, NULL, NULL}, // Empty argument list
{"", NULL, NULL, NULL}, // Empty argument
{"\"a", "b\"b", "CCC\"", NULL}, // Quotes
{"a\tbc", "dd dd", "E", NULL}, // Whitespace
{"a\\\\\\b", "de fg", "H", NULL}, // Backslashes
{"a\\\"b", "\\c", "D\\", NULL}, // Backslashes before quote
{"a\\\\b c", "d", "E", NULL}, // Backslashes not before quote
{ NULL } // Terminator
};
const char *cmdlines[] = {
"a bb CCC",
"",
"\"\"",
"\\\"a b\\\"b CCC\\\"",
"\"a\tbc\" \"dd dd\" E",
"a\\\\\\b \"de fg\" H",
"a\\\\\\\"b \\c D\\",
"\"a\\\\b c\" d E",
NULL // Terminator
};
int i;
for (i=0; cmdlines[i]!=NULL; i++) {
log_info(LD_GENERAL, "Joining argvs[%d], expecting <%s>", i, cmdlines[i]);
joined_argv = tor_join_win_cmdline(argvs[i]);
tt_str_op(cmdlines[i],OP_EQ, joined_argv);
tor_free(joined_argv);
}
done:
tor_free(joined_argv);
process_free(process);
process_free_all();
#endif
}
struct testcase_t process_tests[] = {
{ "default_values", test_default_values, TT_FORK, NULL, NULL },
{ "environment", test_environment, TT_FORK, NULL, NULL },
{ "stringified_types", test_stringified_types, TT_FORK, NULL, NULL },
{ "line_protocol_simple", test_line_protocol_simple, TT_FORK, NULL, NULL },
{ "line_protocol_multi", test_line_protocol_multi, TT_FORK, NULL, NULL },
{ "line_protocol_partial", test_line_protocol_partial, TT_FORK, NULL, NULL },
{ "raw_protocol_simple", test_raw_protocol_simple, TT_FORK, NULL, NULL },
{ "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 },
{ "win32", test_win32, TT_FORK, NULL, NULL },
END_OF_TESTCASES
};

View File

@ -0,0 +1,332 @@
/* Copyright (c) 2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file test_process_slow.c
* \brief Slow test cases for the Process API.
*/
#define MAINLOOP_PRIVATE
#include "orconfig.h"
#include "core/or/or.h"
#include "core/mainloop/mainloop.h"
#include "lib/evloop/compat_libevent.h"
#include "lib/process/process.h"
#include "lib/process/waitpid.h"
#include "test/test.h"
#ifndef BUILDDIR
#define BUILDDIR "."
#endif
#ifdef _WIN32
#define TEST_PROCESS "test-process.exe"
#else
#define TEST_PROCESS BUILDDIR "/src/test/test-process"
#endif /* defined(_WIN32) */
/** Timer that ticks once a second and stop the event loop after 5 ticks. */
static periodic_timer_t *main_loop_timeout_timer;
/** How many times have our timer ticked? */
static int timer_tick_count;
struct process_data_t {
smartlist_t *stdout_data;
smartlist_t *stderr_data;
smartlist_t *stdin_data;
process_exit_code_t exit_code;
};
typedef struct process_data_t process_data_t;
static process_data_t *
process_data_new(void)
{
process_data_t *process_data = tor_malloc_zero(sizeof(process_data_t));
process_data->stdout_data = smartlist_new();
process_data->stderr_data = smartlist_new();
process_data->stdin_data = smartlist_new();
return process_data;
}
static void
process_data_free(process_data_t *process_data)
{
if (process_data == NULL)
return;
SMARTLIST_FOREACH(process_data->stdout_data, char *, x, tor_free(x));
SMARTLIST_FOREACH(process_data->stderr_data, char *, x, tor_free(x));
SMARTLIST_FOREACH(process_data->stdin_data, char *, x, tor_free(x));
smartlist_free(process_data->stdout_data);
smartlist_free(process_data->stderr_data);
smartlist_free(process_data->stdin_data);
tor_free(process_data);
}
static void
process_stdout_callback(process_t *process, const char *data, size_t size)
{
tt_ptr_op(process, OP_NE, NULL);
tt_ptr_op(data, OP_NE, NULL);
tt_int_op(strlen(data), OP_EQ, size);
process_data_t *process_data = process_get_data(process);
smartlist_add(process_data->stdout_data, tor_strdup(data));
done:
return;
}
static void
process_stderr_callback(process_t *process, const char *data, size_t size)
{
tt_ptr_op(process, OP_NE, NULL);
tt_ptr_op(data, OP_NE, NULL);
tt_int_op(strlen(data), OP_EQ, size);
process_data_t *process_data = process_get_data(process);
smartlist_add(process_data->stderr_data, tor_strdup(data));
done:
return;
}
static bool
process_exit_callback(process_t *process, process_exit_code_t exit_code)
{
tt_ptr_op(process, OP_NE, NULL);
process_data_t *process_data = process_get_data(process);
process_data->exit_code = exit_code;
/* Our process died. Let's check the values it returned. */
tor_shutdown_event_loop_and_exit(0);
done:
/* Do not free up our process_t. */
return false;
}
#ifdef _WIN32
static const char *
get_win32_test_binary_path(void)
{
static char buffer[MAX_PATH];
/* Get the absolute path of our binary: \path\to\test-slow.exe. */
GetModuleFileNameA(GetModuleHandle(0), buffer, sizeof(buffer));
/* Find our process name. */
char *offset = strstr(buffer, "test-slow.exe");
tt_ptr_op(offset, OP_NE, NULL);
/* Change test-slow.exe to test-process.exe. */
memcpy(offset, TEST_PROCESS, strlen(TEST_PROCESS));
return buffer;
done:
return NULL;
}
#endif
static void
main_loop_timeout_cb(periodic_timer_t *timer, void *data)
{
/* Sanity check. */
tt_ptr_op(timer, OP_EQ, main_loop_timeout_timer);
tt_ptr_op(data, OP_EQ, NULL);
/* Have we been called 10 times we exit. */
timer_tick_count++;
tt_int_op(timer_tick_count, OP_LT, 10);
#ifndef _WIN32
/* Call waitpid callbacks. */
notify_pending_waitpid_callbacks();
#endif
return;
done:
/* Exit with an error. */
tor_shutdown_event_loop_and_exit(-1);
}
static void
run_main_loop(void)
{
int ret;
/* Wake up after 1 seconds. */
static const struct timeval interval = {1, 0};
timer_tick_count = 0;
main_loop_timeout_timer = periodic_timer_new(tor_libevent_get_base(),
&interval,
main_loop_timeout_cb,
NULL);
/* Run our main loop. */
ret = run_main_loop_until_done();
/* Clean up our main loop timeout timer. */
tt_int_op(ret, OP_EQ, 0);
done:
periodic_timer_free(main_loop_timeout_timer);
}
static void
test_callbacks(void *arg)
{
(void)arg;
const char *filename = NULL;
#ifdef _WIN32
filename = get_win32_test_binary_path();
#else
filename = TEST_PROCESS;
#endif
/* Initialize Process subsystem. */
process_init();
/* Process callback data. */
process_data_t *process_data = process_data_new();
/* Setup our process. */
process_t *process = process_new(filename);
process_set_data(process, process_data);
process_set_stdout_read_callback(process, process_stdout_callback);
process_set_stderr_read_callback(process, process_stderr_callback);
process_set_exit_callback(process, process_exit_callback);
/* Set environment variable. */
process_set_environment(process, "TOR_TEST_ENV", "Hello, from Tor!");
/* Add some arguments. */
process_append_argument(process, "This is the first one");
process_append_argument(process, "Second one");
process_append_argument(process, "Third: Foo bar baz");
/* Run our process. */
process_status_t status;
status = process_exec(process);
tt_int_op(status, OP_EQ, PROCESS_STATUS_RUNNING);
/* Write some lines to stdin. */
process_printf(process, "Hi process!\r\n");
process_printf(process, "Can you read more than one line?\n");
process_printf(process, "Can you read partial ...");
process_printf(process, " lines?\r\n");
/* Start our main loop. */
run_main_loop();
/* Check if our process is still running? */
status = process_get_status(process);
tt_int_op(status, OP_EQ, PROCESS_STATUS_NOT_RUNNING);
/* We returned. Let's see what our event loop said. */
tt_int_op(smartlist_len(process_data->stdout_data), OP_EQ, 12);
tt_int_op(smartlist_len(process_data->stderr_data), OP_EQ, 3);
tt_int_op(process_data->exit_code, OP_EQ, 0);
/* Check stdout output. */
char argv0_expected[256];
tor_snprintf(argv0_expected, sizeof(argv0_expected),
"argv[0] = '%s'", filename);
tt_str_op(smartlist_get(process_data->stdout_data, 0), OP_EQ,
argv0_expected);
tt_str_op(smartlist_get(process_data->stdout_data, 1), OP_EQ,
"argv[1] = 'This is the first one'");
tt_str_op(smartlist_get(process_data->stdout_data, 2), OP_EQ,
"argv[2] = 'Second one'");
tt_str_op(smartlist_get(process_data->stdout_data, 3), OP_EQ,
"argv[3] = 'Third: Foo bar baz'");
tt_str_op(smartlist_get(process_data->stdout_data, 4), OP_EQ,
"Environment variable TOR_TEST_ENV = 'Hello, from Tor!'");
tt_str_op(smartlist_get(process_data->stdout_data, 5), OP_EQ,
"Output on stdout");
tt_str_op(smartlist_get(process_data->stdout_data, 6), OP_EQ,
"This is a new line");
tt_str_op(smartlist_get(process_data->stdout_data, 7), OP_EQ,
"Partial line on stdout ...end of partial line on stdout");
tt_str_op(smartlist_get(process_data->stdout_data, 8), OP_EQ,
"Read line from stdin: 'Hi process!'");
tt_str_op(smartlist_get(process_data->stdout_data, 9), OP_EQ,
"Read line from stdin: 'Can you read more than one line?'");
tt_str_op(smartlist_get(process_data->stdout_data, 10), OP_EQ,
"Read line from stdin: 'Can you read partial ... lines?'");
tt_str_op(smartlist_get(process_data->stdout_data, 11), OP_EQ,
"We are done for here, thank you!");
/* Check stderr output. */
tt_str_op(smartlist_get(process_data->stderr_data, 0), OP_EQ,
"Output on stderr");
tt_str_op(smartlist_get(process_data->stderr_data, 1), OP_EQ,
"This is a new line");
tt_str_op(smartlist_get(process_data->stderr_data, 2), OP_EQ,
"Partial line on stderr ...end of partial line on stderr");
done:
process_data_free(process_data);
process_free(process);
process_free_all();
}
static void
test_callbacks_terminate(void *arg)
{
(void)arg;
const char *filename = NULL;
#ifdef _WIN32
filename = get_win32_test_binary_path();
#else
filename = TEST_PROCESS;
#endif
/* Initialize Process subsystem. */
process_init();
/* Process callback data. */
process_data_t *process_data = process_data_new();
/* Setup our process. */
process_t *process = process_new(filename);
process_set_data(process, process_data);
process_set_exit_callback(process, process_exit_callback);
/* Run our process. */
process_status_t status;
status = process_exec(process);
tt_int_op(status, OP_EQ, PROCESS_STATUS_RUNNING);
/* Zap our process. */
process_terminate(process);
/* Start our main loop. */
run_main_loop();
/* Check if our process is still running? */
status = process_get_status(process);
tt_int_op(status, OP_EQ, PROCESS_STATUS_NOT_RUNNING);
done:
process_data_free(process_data);
process_free(process);
process_free_all();
}
struct testcase_t slow_process_tests[] = {
{ "callbacks", test_callbacks, 0, NULL, NULL },
{ "callbacks_terminate", test_callbacks_terminate, 0, NULL, NULL },
END_OF_TESTCASES
};

View File

@ -8,7 +8,7 @@
#define UTIL_PRIVATE
#define STATEFILE_PRIVATE
#define CONTROL_PRIVATE
#define SUBPROCESS_PRIVATE
#define PROCESS_PRIVATE
#include "core/or/or.h"
#include "app/config/config.h"
#include "app/config/confparse.h"
@ -17,9 +17,9 @@
#include "core/or/circuitbuild.h"
#include "app/config/statefile.h"
#include "test/test.h"
#include "lib/process/subprocess.h"
#include "lib/encoding/confline.h"
#include "lib/net/resolve.h"
#include "lib/process/process.h"
#include "app/config/or_state_st.h"
@ -151,6 +151,8 @@ test_pt_get_transport_options(void *arg)
config_line_t *cl = NULL;
(void)arg;
process_init();
execve_args = tor_malloc(sizeof(char*)*2);
execve_args[0] = tor_strdup("cheeseshop");
execve_args[1] = NULL;
@ -190,6 +192,7 @@ test_pt_get_transport_options(void *arg)
config_free_lines(cl);
managed_proxy_destroy(mp, 0);
smartlist_free(transport_list);
process_free_all();
}
static void
@ -253,6 +256,8 @@ test_pt_get_extrainfo_string(void *arg)
char *s = NULL;
(void) arg;
process_init();
argv1 = tor_malloc_zero(sizeof(char*)*3);
argv1[0] = tor_strdup("ewige");
argv1[1] = tor_strdup("Blumenkraft");
@ -286,43 +291,30 @@ test_pt_get_extrainfo_string(void *arg)
smartlist_free(t1);
smartlist_free(t2);
tor_free(s);
process_free_all();
}
#ifdef _WIN32
#define STDIN_HANDLE HANDLE*
#else
#define STDIN_HANDLE int
#endif
static smartlist_t *
tor_get_lines_from_handle_replacement(STDIN_HANDLE handle,
enum stream_status *stream_status_out)
static int
process_read_stdout_replacement(process_t *process, buf_t *buffer)
{
(void)process;
static int times_called = 0;
smartlist_t *retval_sl = smartlist_new();
(void) handle;
(void) stream_status_out;
/* Generate some dummy CMETHOD lines the first 5 times. The 6th
time, send 'CMETHODS DONE' to finish configuring the proxy. */
if (times_called++ != 5) {
smartlist_add_asprintf(retval_sl, "SMETHOD mock%d 127.0.0.1:555%d",
times_called++;
if (times_called <= 5) {
buf_add_printf(buffer, "SMETHOD mock%d 127.0.0.1:555%d\n",
times_called, times_called);
} else {
smartlist_add_strdup(retval_sl, "SMETHODS DONE");
} else if (times_called <= 6) {
buf_add_string(buffer, "SMETHODS DONE\n");
} else if (times_called <= 7) {
buf_add_string(buffer, "LOG Oh noes, something bad happened. "
"What do we do!?\n");
}
return retval_sl;
}
/* NOP mock */
static void
tor_process_handle_destroy_replacement(process_handle_t *process_handle,
int also_terminate_process)
{
(void) process_handle;
(void) also_terminate_process;
return (int)buf_datalen(buffer);
}
static or_state_t *dummy_state = NULL;
@ -355,12 +347,11 @@ test_pt_configure_proxy(void *arg)
managed_proxy_t *mp = NULL;
(void) arg;
process_init();
dummy_state = tor_malloc_zero(sizeof(or_state_t));
MOCK(tor_get_lines_from_handle,
tor_get_lines_from_handle_replacement);
MOCK(tor_process_handle_destroy,
tor_process_handle_destroy_replacement);
MOCK(process_read_stdout, process_read_stdout_replacement);
MOCK(get_or_state,
get_or_state_replacement);
MOCK(queue_control_event_string,
@ -372,24 +363,34 @@ test_pt_configure_proxy(void *arg)
mp->conf_state = PT_PROTO_ACCEPTING_METHODS;
mp->transports = smartlist_new();
mp->transports_to_launch = smartlist_new();
mp->process_handle = tor_malloc_zero(sizeof(process_handle_t));
mp->argv = tor_malloc_zero(sizeof(char*)*2);
mp->argv[0] = tor_strdup("<testcase>");
mp->is_server = 1;
/* Configure the process. */
mp->process = process_new("");
process_set_stdout_read_callback(mp->process, managed_proxy_stdout_callback);
process_set_data(mp->process, mp);
/* Test the return value of configure_proxy() by calling it some
times while it is uninitialized and then finally finalizing its
configuration. */
for (i = 0 ; i < 5 ; i++) {
/* force a read from our mocked stdout reader. */
process_notify_event_stdout(mp->process);
/* try to configure our proxy. */
retval = configure_proxy(mp);
/* retval should be zero because proxy hasn't finished configuring yet */
tt_int_op(retval, OP_EQ, 0);
/* check the number of registered transports */
tt_assert(smartlist_len(mp->transports) == i+1);
tt_int_op(smartlist_len(mp->transports), OP_EQ, i+1);
/* check that the mp is still waiting for transports */
tt_assert(mp->conf_state == PT_PROTO_ACCEPTING_METHODS);
}
/* Get the SMETHOD DONE written to the process. */
process_notify_event_stdout(mp->process);
/* this last configure_proxy() should finalize the proxy configuration. */
retval = configure_proxy(mp);
/* retval should be 1 since the proxy finished configuring */
@ -412,6 +413,16 @@ test_pt_configure_proxy(void *arg)
tt_str_op(smartlist_get(controlevent_msgs, 4), OP_EQ,
"650 TRANSPORT_LAUNCHED server mock5 127.0.0.1 5555\r\n");
/* Get the log message out. */
process_notify_event_stdout(mp->process);
tt_int_op(controlevent_n, OP_EQ, 6);
tt_int_op(controlevent_event, OP_EQ, EVENT_PT_LOG);
tt_int_op(smartlist_len(controlevent_msgs), OP_EQ, 6);
tt_str_op(smartlist_get(controlevent_msgs, 5), OP_EQ,
"650 PT_LOG <testcase> Oh noes, something bad happened. "
"What do we do!?\r\n");
{ /* check that the transport info were saved properly in the tor state */
config_line_t *transport_in_state = NULL;
smartlist_t *transport_info_sl = smartlist_new();
@ -435,8 +446,7 @@ test_pt_configure_proxy(void *arg)
done:
or_state_free(dummy_state);
UNMOCK(tor_get_lines_from_handle);
UNMOCK(tor_process_handle_destroy);
UNMOCK(process_read_stdout);
UNMOCK(get_or_state);
UNMOCK(queue_control_event_string);
if (controlevent_msgs) {
@ -449,10 +459,11 @@ test_pt_configure_proxy(void *arg)
smartlist_free(mp->transports);
}
smartlist_free(mp->transports_to_launch);
tor_free(mp->process_handle);
process_free(mp->process);
tor_free(mp->argv[0]);
tor_free(mp->argv);
tor_free(mp);
process_free_all();
}
/* Test the get_pt_proxy_uri() function. */

View File

@ -20,7 +20,7 @@
struct testgroup_t testgroups[] = {
{ "slow/crypto/", slow_crypto_tests },
{ "slow/util/", slow_util_tests },
{ "slow/process/", slow_process_tests },
END_OF_GROUPS
};

View File

@ -10,7 +10,7 @@
#define UTIL_PRIVATE
#define UTIL_MALLOC_PRIVATE
#define SOCKET_PRIVATE
#define SUBPROCESS_PRIVATE
#define PROCESS_WIN32_PRIVATE
#include "lib/testsupport/testsupport.h"
#include "core/or/or.h"
#include "lib/buf/buffers.h"
@ -22,6 +22,7 @@
#include "test/test.h"
#include "lib/memarea/memarea.h"
#include "lib/process/waitpid.h"
#include "lib/process/process_win32.h"
#include "test/log_test_helpers.h"
#include "lib/compress/compress.h"
#include "lib/compress/compress_zstd.h"
@ -30,7 +31,6 @@
#include "lib/fs/winlib.h"
#include "lib/process/env.h"
#include "lib/process/pidfile.h"
#include "lib/process/subprocess.h"
#include "lib/intmath/weakrng.h"
#include "lib/thread/numcpus.h"
#include "lib/math/fp.h"
@ -4301,204 +4301,6 @@ test_util_load_win_lib(void *ptr)
}
#endif /* defined(_WIN32) */
#ifndef _WIN32
static void
clear_hex_errno(char *hex_errno)
{
memset(hex_errno, '\0', HEX_ERRNO_SIZE + 1);
}
static void
test_util_exit_status(void *ptr)
{
/* Leave an extra byte for a \0 so we can do string comparison */
char hex_errno[HEX_ERRNO_SIZE + 1];
int n;
(void)ptr;
clear_hex_errno(hex_errno);
tt_str_op("",OP_EQ, hex_errno);
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0, 0, hex_errno);
tt_str_op("0/0\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
#if SIZEOF_INT == 4
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0, 0x7FFFFFFF, hex_errno);
tt_str_op("0/7FFFFFFF\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0xFF, -0x80000000, hex_errno);
tt_str_op("FF/-80000000\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
tt_int_op(n,OP_EQ, HEX_ERRNO_SIZE);
#elif SIZEOF_INT == 8
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0, 0x7FFFFFFFFFFFFFFF, hex_errno);
tt_str_op("0/7FFFFFFFFFFFFFFF\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0xFF, -0x8000000000000000, hex_errno);
tt_str_op("FF/-8000000000000000\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
tt_int_op(n,OP_EQ, HEX_ERRNO_SIZE);
#endif /* SIZEOF_INT == 4 || ... */
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0x7F, 0, hex_errno);
tt_str_op("7F/0\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0x08, -0x242, hex_errno);
tt_str_op("8/-242\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
clear_hex_errno(hex_errno);
tt_str_op("",OP_EQ, hex_errno);
done:
;
}
#endif /* !defined(_WIN32) */
#ifndef _WIN32
static void
test_util_string_from_pipe(void *ptr)
{
int test_pipe[2] = {-1, -1};
int retval = 0;
enum stream_status status = IO_STREAM_TERM;
ssize_t retlen;
char buf[4] = { 0 };
(void)ptr;
errno = 0;
/* Set up a pipe to test on */
retval = pipe(test_pipe);
tt_int_op(retval, OP_EQ, 0);
/* Send in a string. */
retlen = write(test_pipe[1], "ABC", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "ABC");
errno = 0;
/* Send in a string that contains a nul. */
retlen = write(test_pipe[1], "AB\0", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "AB");
errno = 0;
/* Send in a string that contains a nul only. */
retlen = write(test_pipe[1], "\0", 1);
tt_int_op(retlen, OP_EQ, 1);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "");
errno = 0;
/* Send in a string that contains a trailing newline. */
retlen = write(test_pipe[1], "AB\n", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "AB");
errno = 0;
/* Send in a string that contains a newline only. */
retlen = write(test_pipe[1], "\n", 1);
tt_int_op(retlen, OP_EQ, 1);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "");
errno = 0;
/* Send in a string and check that we nul terminate return values. */
retlen = write(test_pipe[1], "AAA", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "AAA");
tt_mem_op(buf, OP_EQ, "AAA\0", sizeof(buf));
errno = 0;
retlen = write(test_pipe[1], "B", 1);
tt_int_op(retlen, OP_EQ, 1);
memset(buf, '\xff', sizeof(buf));
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "B");
tt_mem_op(buf, OP_EQ, "B\0\xff\xff", sizeof(buf));
errno = 0;
/* Send in multiple lines. */
retlen = write(test_pipe[1], "A\nB", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "A\nB");
errno = 0;
/* Send in a line and close */
retlen = write(test_pipe[1], "AB", 2);
tt_int_op(retlen, OP_EQ, 2);
retval = close(test_pipe[1]);
tt_int_op(retval, OP_EQ, 0);
test_pipe[1] = -1;
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "AB");
errno = 0;
/* Check for EOF */
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_CLOSED);
errno = 0;
done:
if (test_pipe[0] != -1)
close(test_pipe[0]);
if (test_pipe[1] != -1)
close(test_pipe[1]);
}
#endif /* !defined(_WIN32) */
/**
* Test for format_hex_number_sigsafe()
*/
@ -4593,57 +4395,6 @@ test_util_format_dec_number(void *ptr)
return;
}
/**
* Test that we can properly format a Windows command line
*/
static void
test_util_join_win_cmdline(void *ptr)
{
/* Based on some test cases from "Parsing C++ Command-Line Arguments" in
* MSDN but we don't exercise all quoting rules because tor_join_win_cmdline
* will try to only generate simple cases for the child process to parse;
* i.e. we never embed quoted strings in arguments. */
const char *argvs[][4] = {
{"a", "bb", "CCC", NULL}, // Normal
{NULL, NULL, NULL, NULL}, // Empty argument list
{"", NULL, NULL, NULL}, // Empty argument
{"\"a", "b\"b", "CCC\"", NULL}, // Quotes
{"a\tbc", "dd dd", "E", NULL}, // Whitespace
{"a\\\\\\b", "de fg", "H", NULL}, // Backslashes
{"a\\\"b", "\\c", "D\\", NULL}, // Backslashes before quote
{"a\\\\b c", "d", "E", NULL}, // Backslashes not before quote
{ NULL } // Terminator
};
const char *cmdlines[] = {
"a bb CCC",
"",
"\"\"",
"\\\"a b\\\"b CCC\\\"",
"\"a\tbc\" \"dd dd\" E",
"a\\\\\\b \"de fg\" H",
"a\\\\\\\"b \\c D\\",
"\"a\\\\b c\" d E",
NULL // Terminator
};
int i;
char *joined_argv = NULL;
(void)ptr;
for (i=0; cmdlines[i]!=NULL; i++) {
log_info(LD_GENERAL, "Joining argvs[%d], expecting <%s>", i, cmdlines[i]);
joined_argv = tor_join_win_cmdline(argvs[i]);
tt_str_op(cmdlines[i],OP_EQ, joined_argv);
tor_free(joined_argv);
}
done:
tor_free(joined_argv);
}
#define MAX_SPLIT_LINE_COUNT 4
struct split_lines_test_t {
const char *orig_line; // Line to be split (may contain \0's)
@ -4651,67 +4402,6 @@ struct split_lines_test_t {
const char *split_line[MAX_SPLIT_LINE_COUNT]; // Split lines
};
/**
* Test that we properly split a buffer into lines
*/
static void
test_util_split_lines(void *ptr)
{
/* Test cases. orig_line of last test case must be NULL.
* The last element of split_line[i] must be NULL. */
struct split_lines_test_t tests[] = {
{"", 0, {NULL}},
{"foo", 3, {"foo", NULL}},
{"\n\rfoo\n\rbar\r\n", 12, {"foo", "bar", NULL}},
{"fo o\r\nb\tar", 10, {"fo o", "b.ar", NULL}},
{"\x0f""f\0o\0\n\x01""b\0r\0\r", 12, {".f.o.", ".b.r.", NULL}},
{"line 1\r\nline 2", 14, {"line 1", "line 2", NULL}},
{"line 1\r\n\r\nline 2", 16, {"line 1", "line 2", NULL}},
{"line 1\r\n\r\r\r\nline 2", 18, {"line 1", "line 2", NULL}},
{"line 1\r\n\n\n\n\rline 2", 18, {"line 1", "line 2", NULL}},
{"line 1\r\n\r\t\r\nline 3", 18, {"line 1", ".", "line 3", NULL}},
{"\n\t\r\t\nline 3", 11, {".", ".", "line 3", NULL}},
{NULL, 0, { NULL }}
};
int i, j;
char *orig_line=NULL;
smartlist_t *sl=NULL;
(void)ptr;
for (i=0; tests[i].orig_line; i++) {
sl = smartlist_new();
/* Allocate space for string and trailing NULL */
orig_line = tor_memdup(tests[i].orig_line, tests[i].orig_length + 1);
tor_split_lines(sl, orig_line, tests[i].orig_length);
j = 0;
log_info(LD_GENERAL, "Splitting test %d of length %d",
i, tests[i].orig_length);
SMARTLIST_FOREACH_BEGIN(sl, const char *, line) {
/* Check we have not got too many lines */
tt_int_op(MAX_SPLIT_LINE_COUNT, OP_GT, j);
/* Check that there actually should be a line here */
tt_ptr_op(tests[i].split_line[j], OP_NE, NULL);
log_info(LD_GENERAL, "Line %d of test %d, should be <%s>",
j, i, tests[i].split_line[j]);
/* Check that the line is as expected */
tt_str_op(line,OP_EQ, tests[i].split_line[j]);
j++;
} SMARTLIST_FOREACH_END(line);
/* Check that we didn't miss some lines */
tt_ptr_op(NULL,OP_EQ, tests[i].split_line[j]);
tor_free(orig_line);
smartlist_free(sl);
sl = NULL;
}
done:
tor_free(orig_line);
smartlist_free(sl);
}
static void
test_util_di_ops(void *arg)
{
@ -6483,12 +6173,8 @@ struct testcase_t util_tests[] = {
UTIL_TEST(nowrap_math, 0),
UTIL_TEST(num_cpus, 0),
UTIL_TEST_WIN_ONLY(load_win_lib, 0),
UTIL_TEST_NO_WIN(exit_status, 0),
UTIL_TEST_NO_WIN(string_from_pipe, 0),
UTIL_TEST(format_hex_number, 0),
UTIL_TEST(format_dec_number, 0),
UTIL_TEST(join_win_cmdline, 0),
UTIL_TEST(split_lines, 0),
UTIL_TEST(n_bits_set, 0),
UTIL_TEST(eat_whitespace, 0),
UTIL_TEST(sl_new_from_text_lines, 0),

View File

@ -1,396 +0,0 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#include "orconfig.h"
#define UTIL_PRIVATE
#define SUBPROCESS_PRIVATE
#include "lib/crypt_ops/crypto_cipher.h"
#include "lib/log/log.h"
#include "lib/process/subprocess.h"
#include "lib/process/waitpid.h"
#include "lib/string/printf.h"
#include "lib/time/compat_time.h"
#include "test/test.h"
#include <errno.h>
#include <string.h>
#ifndef BUILDDIR
#define BUILDDIR "."
#endif
#ifdef _WIN32
#define notify_pending_waitpid_callbacks() STMT_NIL
#define TEST_CHILD "test-child.exe"
#define EOL "\r\n"
#else
#define TEST_CHILD (BUILDDIR "/src/test/test-child")
#define EOL "\n"
#endif /* defined(_WIN32) */
#ifdef _WIN32
/* I've assumed Windows doesn't have the gap between fork and exec
* that causes the race condition on unix-like platforms */
#define MATCH_PROCESS_STATUS(s1,s2) ((s1) == (s2))
#else /* !(defined(_WIN32)) */
/* work around a race condition of the timing of SIGCHLD handler updates
* to the process_handle's fields, and checks of those fields
*
* TODO: Once we can signal failure to exec, change PROCESS_STATUS_RUNNING to
* PROCESS_STATUS_ERROR (and similarly with *_OR_NOTRUNNING) */
#define PROCESS_STATUS_RUNNING_OR_NOTRUNNING (PROCESS_STATUS_RUNNING+1)
#define IS_RUNNING_OR_NOTRUNNING(s) \
((s) == PROCESS_STATUS_RUNNING || (s) == PROCESS_STATUS_NOTRUNNING)
/* well, this is ugly */
#define MATCH_PROCESS_STATUS(s1,s2) \
( (s1) == (s2) \
||((s1) == PROCESS_STATUS_RUNNING_OR_NOTRUNNING \
&& IS_RUNNING_OR_NOTRUNNING(s2)) \
||((s2) == PROCESS_STATUS_RUNNING_OR_NOTRUNNING \
&& IS_RUNNING_OR_NOTRUNNING(s1)))
#endif /* defined(_WIN32) */
/** Helper function for testing tor_spawn_background */
static void
run_util_spawn_background(const char *argv[], const char *expected_out,
const char *expected_err, int expected_exit,
int expected_status)
{
int retval, exit_code;
ssize_t pos;
process_handle_t *process_handle=NULL;
char stdout_buf[100], stderr_buf[100];
int status;
/* Start the program */
#ifdef _WIN32
status = tor_spawn_background(NULL, argv, NULL, &process_handle);
#else
status = tor_spawn_background(argv[0], argv, NULL, &process_handle);
#endif
notify_pending_waitpid_callbacks();
/* the race condition doesn't affect status,
* because status isn't updated by the SIGCHLD handler,
* but we still need to handle PROCESS_STATUS_RUNNING_OR_NOTRUNNING */
tt_assert(MATCH_PROCESS_STATUS(expected_status, status));
if (status == PROCESS_STATUS_ERROR) {
tt_ptr_op(process_handle, OP_EQ, NULL);
return;
}
tt_ptr_op(process_handle, OP_NE, NULL);
/* When a spawned process forks, fails, then exits very quickly,
* (this typically occurs when exec fails)
* there is a race condition between the SIGCHLD handler
* updating the process_handle's fields, and this test
* checking the process status in those fields.
* The SIGCHLD update can occur before or after the code below executes.
* This causes intermittent failures in spawn_background_fail(),
* typically when the machine is under load.
* We use PROCESS_STATUS_RUNNING_OR_NOTRUNNING to avoid this issue. */
/* the race condition affects the change in
* process_handle->status from RUNNING to NOTRUNNING */
tt_assert(MATCH_PROCESS_STATUS(expected_status, process_handle->status));
#ifndef _WIN32
notify_pending_waitpid_callbacks();
/* the race condition affects the change in
* process_handle->waitpid_cb to NULL,
* so we skip the check if expected_status is ambiguous,
* that is, PROCESS_STATUS_RUNNING_OR_NOTRUNNING */
tt_assert(process_handle->waitpid_cb != NULL
|| expected_status == PROCESS_STATUS_RUNNING_OR_NOTRUNNING);
#endif /* !defined(_WIN32) */
#ifdef _WIN32
tt_assert(process_handle->stdout_pipe != INVALID_HANDLE_VALUE);
tt_assert(process_handle->stderr_pipe != INVALID_HANDLE_VALUE);
tt_assert(process_handle->stdin_pipe != INVALID_HANDLE_VALUE);
#else
tt_assert(process_handle->stdout_pipe >= 0);
tt_assert(process_handle->stderr_pipe >= 0);
tt_assert(process_handle->stdin_pipe >= 0);
#endif /* defined(_WIN32) */
/* Check stdout */
pos = tor_read_all_from_process_stdout(process_handle, stdout_buf,
sizeof(stdout_buf) - 1);
tt_assert(pos >= 0);
stdout_buf[pos] = '\0';
tt_int_op(strlen(expected_out),OP_EQ, pos);
tt_str_op(expected_out,OP_EQ, stdout_buf);
notify_pending_waitpid_callbacks();
/* Check it terminated correctly */
retval = tor_get_exit_code(process_handle, 1, &exit_code);
tt_int_op(PROCESS_EXIT_EXITED,OP_EQ, retval);
tt_int_op(expected_exit,OP_EQ, exit_code);
// TODO: Make test-child exit with something other than 0
#ifndef _WIN32
notify_pending_waitpid_callbacks();
tt_ptr_op(process_handle->waitpid_cb, OP_EQ, NULL);
#endif
/* Check stderr */
pos = tor_read_all_from_process_stderr(process_handle, stderr_buf,
sizeof(stderr_buf) - 1);
tt_assert(pos >= 0);
stderr_buf[pos] = '\0';
tt_str_op(expected_err,OP_EQ, stderr_buf);
tt_int_op(strlen(expected_err),OP_EQ, pos);
notify_pending_waitpid_callbacks();
done:
if (process_handle)
tor_process_handle_destroy(process_handle, 1);
}
/** Check that we can launch a process and read the output */
static void
test_util_spawn_background_ok(void *ptr)
{
const char *argv[] = {TEST_CHILD, "--test", NULL};
const char *expected_out = "OUT"EOL "--test"EOL "SLEEPING"EOL "DONE" EOL;
const char *expected_err = "ERR"EOL;
(void)ptr;
run_util_spawn_background(argv, expected_out, expected_err, 0,
PROCESS_STATUS_RUNNING);
}
/** Check that failing to find the executable works as expected */
static void
test_util_spawn_background_fail(void *ptr)
{
const char *argv[] = {BUILDDIR "/src/test/no-such-file", "--test", NULL};
const char *expected_err = "";
char expected_out[1024];
char code[32];
#ifdef _WIN32
const int expected_status = PROCESS_STATUS_ERROR;
#else
/* TODO: Once we can signal failure to exec, set this to be
* PROCESS_STATUS_RUNNING_OR_ERROR */
const int expected_status = PROCESS_STATUS_RUNNING_OR_NOTRUNNING;
#endif /* defined(_WIN32) */
memset(expected_out, 0xf0, sizeof(expected_out));
memset(code, 0xf0, sizeof(code));
(void)ptr;
tor_snprintf(code, sizeof(code), "%x/%x",
9 /* CHILD_STATE_FAILEXEC */ , ENOENT);
tor_snprintf(expected_out, sizeof(expected_out),
"ERR: Failed to spawn background process - code %s\n", code);
run_util_spawn_background(argv, expected_out, expected_err, 255,
expected_status);
}
/** Test that reading from a handle returns a partial read rather than
* blocking */
static void
test_util_spawn_background_partial_read_impl(int exit_early)
{
const int expected_exit = 0;
const int expected_status = PROCESS_STATUS_RUNNING;
int retval, exit_code;
ssize_t pos = -1;
process_handle_t *process_handle=NULL;
int status;
char stdout_buf[100], stderr_buf[100];
const char *argv[] = {TEST_CHILD, "--test", NULL};
const char *expected_out[] = { "OUT" EOL "--test" EOL "SLEEPING" EOL,
"DONE" EOL,
NULL };
const char *expected_err = "ERR" EOL;
#ifndef _WIN32
int eof = 0;
#endif
int expected_out_ctr;
if (exit_early) {
argv[1] = "--hang";
expected_out[0] = "OUT"EOL "--hang"EOL "SLEEPING" EOL;
}
/* Start the program */
#ifdef _WIN32
status = tor_spawn_background(NULL, argv, NULL, &process_handle);
#else
status = tor_spawn_background(argv[0], argv, NULL, &process_handle);
#endif
tt_int_op(expected_status,OP_EQ, status);
tt_assert(process_handle);
tt_int_op(expected_status,OP_EQ, process_handle->status);
/* Check stdout */
for (expected_out_ctr = 0; expected_out[expected_out_ctr] != NULL;) {
#ifdef _WIN32
pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf,
sizeof(stdout_buf) - 1, NULL);
#else
/* Check that we didn't read the end of file last time */
tt_assert(!eof);
pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf,
sizeof(stdout_buf) - 1, NULL, &eof);
#endif /* defined(_WIN32) */
log_info(LD_GENERAL, "tor_read_all_handle() returned %d", (int)pos);
/* We would have blocked, keep on trying */
if (0 == pos)
continue;
tt_assert(pos > 0);
stdout_buf[pos] = '\0';
tt_str_op(expected_out[expected_out_ctr],OP_EQ, stdout_buf);
tt_int_op(strlen(expected_out[expected_out_ctr]),OP_EQ, pos);
expected_out_ctr++;
}
if (exit_early) {
tor_process_handle_destroy(process_handle, 1);
process_handle = NULL;
goto done;
}
/* The process should have exited without writing more */
#ifdef _WIN32
pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf,
sizeof(stdout_buf) - 1,
process_handle);
tt_int_op(0,OP_EQ, pos);
#else /* !(defined(_WIN32)) */
if (!eof) {
/* We should have got all the data, but maybe not the EOF flag */
pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf,
sizeof(stdout_buf) - 1,
process_handle, &eof);
tt_int_op(0,OP_EQ, pos);
tt_assert(eof);
}
/* Otherwise, we got the EOF on the last read */
#endif /* defined(_WIN32) */
/* Check it terminated correctly */
retval = tor_get_exit_code(process_handle, 1, &exit_code);
tt_int_op(PROCESS_EXIT_EXITED,OP_EQ, retval);
tt_int_op(expected_exit,OP_EQ, exit_code);
// TODO: Make test-child exit with something other than 0
/* Check stderr */
pos = tor_read_all_from_process_stderr(process_handle, stderr_buf,
sizeof(stderr_buf) - 1);
tt_assert(pos >= 0);
stderr_buf[pos] = '\0';
tt_str_op(expected_err,OP_EQ, stderr_buf);
tt_int_op(strlen(expected_err),OP_EQ, pos);
done:
tor_process_handle_destroy(process_handle, 1);
}
static void
test_util_spawn_background_partial_read(void *arg)
{
(void)arg;
test_util_spawn_background_partial_read_impl(0);
}
static void
test_util_spawn_background_exit_early(void *arg)
{
(void)arg;
test_util_spawn_background_partial_read_impl(1);
}
static void
test_util_spawn_background_waitpid_notify(void *arg)
{
int retval, exit_code;
process_handle_t *process_handle=NULL;
int status;
int ms_timer;
const char *argv[] = {TEST_CHILD, "--fast", NULL};
(void) arg;
#ifdef _WIN32
status = tor_spawn_background(NULL, argv, NULL, &process_handle);
#else
status = tor_spawn_background(argv[0], argv, NULL, &process_handle);
#endif
tt_int_op(status, OP_EQ, PROCESS_STATUS_RUNNING);
tt_ptr_op(process_handle, OP_NE, NULL);
/* We're not going to look at the stdout/stderr output this time. Instead,
* we're testing whether notify_pending_waitpid_calbacks() can report the
* process exit (on unix) and/or whether tor_get_exit_code() can notice it
* (on windows) */
#ifndef _WIN32
ms_timer = 30*1000;
tt_ptr_op(process_handle->waitpid_cb, OP_NE, NULL);
while (process_handle->waitpid_cb && ms_timer > 0) {
tor_sleep_msec(100);
ms_timer -= 100;
notify_pending_waitpid_callbacks();
}
tt_int_op(ms_timer, OP_GT, 0);
tt_ptr_op(process_handle->waitpid_cb, OP_EQ, NULL);
#endif /* !defined(_WIN32) */
ms_timer = 30*1000;
while (((retval = tor_get_exit_code(process_handle, 0, &exit_code))
== PROCESS_EXIT_RUNNING) && ms_timer > 0) {
tor_sleep_msec(100);
ms_timer -= 100;
}
tt_int_op(ms_timer, OP_GT, 0);
tt_int_op(retval, OP_EQ, PROCESS_EXIT_EXITED);
done:
tor_process_handle_destroy(process_handle, 1);
}
#undef TEST_CHILD
#undef EOL
#undef MATCH_PROCESS_STATUS
#ifndef _WIN32
#undef PROCESS_STATUS_RUNNING_OR_NOTRUNNING
#undef IS_RUNNING_OR_NOTRUNNING
#endif
#define UTIL_TEST(name, flags) \
{ #name, test_util_ ## name, flags, NULL, NULL }
struct testcase_t slow_util_tests[] = {
UTIL_TEST(spawn_background_ok, 0),
UTIL_TEST(spawn_background_fail, 0),
UTIL_TEST(spawn_background_partial_read, 0),
UTIL_TEST(spawn_background_exit_early, 0),
UTIL_TEST(spawn_background_waitpid_notify, 0),
END_OF_TESTCASES
};