mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-27 05:43:30 +01:00
Merge branch 'ticket28179_squashed' into ticket28179_squashed_merged
This commit is contained in:
commit
e969d9c6b4
@ -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
4
.gitignore
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) */
|
||||
|
@ -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 *
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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. */
|
||||
|
@ -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);
|
||||
read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0);
|
||||
|
||||
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,
|
||||
int *reached_eof,
|
||||
int *socket_error)
|
||||
static int
|
||||
buf_read_from_fd(buf_t *buf, int fd, size_t at_most,
|
||||
int *reached_eof,
|
||||
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);
|
||||
}
|
||||
|
@ -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) */
|
||||
|
@ -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
|
||||
@ -15,4 +16,4 @@ lib/subsys/*.h
|
||||
lib/testsupport/*.h
|
||||
lib/thread/*.h
|
||||
|
||||
ext/ht.h
|
||||
ext/ht.h
|
||||
|
@ -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
797
src/lib/process/process.c
Normal 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
142
src/lib/process/process.h
Normal 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). */
|
704
src/lib/process/process_unix.c
Normal file
704
src/lib/process/process_unix.c
Normal 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). */
|
68
src/lib/process/process_unix.h
Normal file
68
src/lib/process/process_unix.h
Normal 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). */
|
1026
src/lib/process/process_win32.c
Normal file
1026
src/lib/process/process_win32.c
Normal file
File diff suppressed because it is too large
Load Diff
97
src/lib/process/process_win32.h
Normal file
97
src/lib/process/process_win32.h
Normal 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
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
85
src/test/test-process.c
Normal 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;
|
||||
}
|
@ -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 },
|
||||
|
@ -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[];
|
||||
|
||||
|
@ -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
696
src/test/test_process.c
Normal 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
|
||||
};
|
332
src/test/test_process_slow.c
Normal file
332
src/test/test_process_slow.c
Normal 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
|
||||
};
|
@ -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. */
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
};
|
Loading…
Reference in New Issue
Block a user