mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-24 04:13:28 +01:00
Merge branch 'ticket26063_squashed'
This commit is contained in:
commit
1eede00a4b
5
changes/ticket26063
Normal file
5
changes/ticket26063
Normal file
@ -0,0 +1,5 @@
|
||||
o Major features (CPU usage, mobile):
|
||||
- When Tor is disabled (via DisableNetwork or via hibernation), it
|
||||
no longer needs to run any per-second events. This change should
|
||||
make it easier for mobile applications to disable Tor while the
|
||||
device is sleeping, or Tor is not running. Closes ticket 26063.
|
@ -253,10 +253,39 @@ periodic_timer_new(struct event_base *base,
|
||||
}
|
||||
timer->cb = cb;
|
||||
timer->data = data;
|
||||
event_add(timer->ev, (struct timeval *)tv); /*drop const for old libevent*/
|
||||
periodic_timer_launch(timer, tv);
|
||||
return timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the timer <b>timer</b> to run at <b>tv</b> from now, and every
|
||||
* <b>tv</b> thereafter.
|
||||
*
|
||||
* If the timer is already enabled, this function does nothing.
|
||||
*/
|
||||
void
|
||||
periodic_timer_launch(periodic_timer_t *timer, const struct timeval *tv)
|
||||
{
|
||||
tor_assert(timer);
|
||||
if (event_pending(timer->ev, EV_TIMEOUT, NULL))
|
||||
return;
|
||||
event_add(timer->ev, tv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the provided <b>timer</b>, but do not free it.
|
||||
*
|
||||
* You can reenable the same timer later with periodic_timer_launch.
|
||||
*
|
||||
* If the timer is already disabled, this function does nothing.
|
||||
*/
|
||||
void
|
||||
periodic_timer_disable(periodic_timer_t *timer)
|
||||
{
|
||||
tor_assert(timer);
|
||||
event_del(timer->ev);
|
||||
}
|
||||
|
||||
/** Stop and free a periodic timer */
|
||||
void
|
||||
periodic_timer_free_(periodic_timer_t *timer)
|
||||
|
@ -31,6 +31,8 @@ periodic_timer_t *periodic_timer_new(struct event_base *base,
|
||||
void (*cb)(periodic_timer_t *timer, void *data),
|
||||
void *data);
|
||||
void periodic_timer_free_(periodic_timer_t *);
|
||||
void periodic_timer_launch(periodic_timer_t *, const struct timeval *tv);
|
||||
void periodic_timer_disable(periodic_timer_t *);
|
||||
#define periodic_timer_free(t) \
|
||||
FREE_AND_NULL(periodic_timer_t, periodic_timer_free_, (t))
|
||||
|
||||
|
@ -1449,9 +1449,9 @@ options_act_reversible(const or_options_t *old_options, char **msg)
|
||||
consider_hibernation(time(NULL));
|
||||
|
||||
/* Launch the listeners. (We do this before we setuid, so we can bind to
|
||||
* ports under 1024.) We don't want to rebind if we're hibernating. If
|
||||
* networking is disabled, this will close all but the control listeners,
|
||||
* but disable those. */
|
||||
* ports under 1024.) We don't want to rebind if we're hibernating or
|
||||
* shutting down. If networking is disabled, this will close all but the
|
||||
* control listeners, but disable those. */
|
||||
if (!we_are_hibernating()) {
|
||||
if (retry_all_listeners(replaced_listeners, new_listeners,
|
||||
options->DisableNetwork) < 0) {
|
||||
@ -2001,6 +2001,9 @@ options_act(const or_options_t *old_options)
|
||||
finish_daemon(options->DataDirectory);
|
||||
}
|
||||
|
||||
/* See whether we need to enable/disable our once-a-second timer. */
|
||||
reschedule_per_second_timer();
|
||||
|
||||
/* We want to reinit keys as needed before we do much of anything else:
|
||||
keys are important, and other things can depend on them. */
|
||||
if (transition_affects_workers ||
|
||||
|
@ -1762,13 +1762,13 @@ connection_connect_sockaddr,(connection_t *conn,
|
||||
tor_assert(sa);
|
||||
tor_assert(socket_error);
|
||||
|
||||
if (get_options()->DisableNetwork) {
|
||||
/* We should never even try to connect anyplace if DisableNetwork is set.
|
||||
* Warn if we do, and refuse to make the connection.
|
||||
if (net_is_completely_disabled()) {
|
||||
/* We should never even try to connect anyplace if the network is
|
||||
* completely shut off.
|
||||
*
|
||||
* We only check DisableNetwork here, not we_are_hibernating(), since
|
||||
* we'll still try to fulfill client requests sometimes in the latter case
|
||||
* (if it is soft hibernation) */
|
||||
* (We don't check net_is_disabled() here, since we still sometimes
|
||||
* want to open connections when we're in soft hibernation.)
|
||||
*/
|
||||
static ratelim_t disablenet_violated = RATELIM_INIT(30*60);
|
||||
*socket_error = SOCK_ERRNO(ENETUNREACH);
|
||||
log_fn_ratelim(&disablenet_violated, LOG_WARN, LD_BUG,
|
||||
|
@ -3537,6 +3537,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
|
||||
n_stream->base_.state = EXIT_CONN_STATE_RESOLVEFAILED;
|
||||
/* default to failed, change in dns_resolve if it turns out not to fail */
|
||||
|
||||
/* If we're hibernating or shutting down, we refuse to open new streams. */
|
||||
if (we_are_hibernating()) {
|
||||
relay_send_end_cell_from_edge(rh.stream_id, circ,
|
||||
END_STREAM_REASON_HIBERNATING, NULL);
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||
* Copyright (c) 2007-2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
@ -112,6 +113,10 @@ static int disable_log_messages = 0;
|
||||
#define EVENT_IS_INTERESTING(e) \
|
||||
(!! (global_event_mask & EVENT_MASK_(e)))
|
||||
|
||||
/** Macro: true if any event from the bitfield 'e' is interesting. */
|
||||
#define ANY_EVENT_IS_INTERESTING(e) \
|
||||
EVENT_IS_INTERESTING(e)
|
||||
|
||||
/** If we're using cookie-type authentication, how long should our cookies be?
|
||||
*/
|
||||
#define AUTHENTICATION_COOKIE_LEN 32
|
||||
@ -219,6 +224,7 @@ static void set_cached_network_liveness(int liveness);
|
||||
static void flush_queued_events_cb(mainloop_event_t *event, void *arg);
|
||||
|
||||
static char * download_status_to_string(const download_status_t *dl);
|
||||
static void control_get_bytes_rw_last_sec(uint64_t *r, uint64_t *w);
|
||||
|
||||
/** Given a control event code for a message event, return the corresponding
|
||||
* log severity. */
|
||||
@ -271,6 +277,7 @@ control_update_global_event_mask(void)
|
||||
smartlist_t *conns = get_connection_array();
|
||||
event_mask_t old_mask, new_mask;
|
||||
old_mask = global_event_mask;
|
||||
int any_old_per_sec_events = control_any_per_second_event_enabled();
|
||||
|
||||
global_event_mask = 0;
|
||||
SMARTLIST_FOREACH(conns, connection_t *, _conn,
|
||||
@ -288,10 +295,13 @@ control_update_global_event_mask(void)
|
||||
* we want to hear...*/
|
||||
control_adjust_event_log_severity();
|
||||
|
||||
/* Macro: true if ev was false before and is true now. */
|
||||
#define NEWLY_ENABLED(ev) \
|
||||
(! (old_mask & (ev)) && (new_mask & (ev)))
|
||||
|
||||
/* ...then, if we've started logging stream or circ bw, clear the
|
||||
* appropriate fields. */
|
||||
if (! (old_mask & EVENT_STREAM_BANDWIDTH_USED) &&
|
||||
(new_mask & EVENT_STREAM_BANDWIDTH_USED)) {
|
||||
if (NEWLY_ENABLED(EVENT_STREAM_BANDWIDTH_USED)) {
|
||||
SMARTLIST_FOREACH(conns, connection_t *, conn,
|
||||
{
|
||||
if (conn->type == CONN_TYPE_AP) {
|
||||
@ -300,10 +310,18 @@ control_update_global_event_mask(void)
|
||||
}
|
||||
});
|
||||
}
|
||||
if (! (old_mask & EVENT_CIRC_BANDWIDTH_USED) &&
|
||||
(new_mask & EVENT_CIRC_BANDWIDTH_USED)) {
|
||||
if (NEWLY_ENABLED(EVENT_CIRC_BANDWIDTH_USED)) {
|
||||
clear_circ_bw_fields();
|
||||
}
|
||||
if (NEWLY_ENABLED(EVENT_BANDWIDTH_USED)) {
|
||||
uint64_t r, w;
|
||||
control_get_bytes_rw_last_sec(&r, &w);
|
||||
}
|
||||
if (any_old_per_sec_events != control_any_per_second_event_enabled()) {
|
||||
reschedule_per_second_timer();
|
||||
}
|
||||
|
||||
#undef NEWLY_ENABLED
|
||||
}
|
||||
|
||||
/** Adjust the log severities that result in control_event_logmsg being called
|
||||
@ -352,6 +370,65 @@ control_event_is_interesting(int event)
|
||||
return EVENT_IS_INTERESTING(event);
|
||||
}
|
||||
|
||||
/** Return true if any event that needs to fire once a second is enabled. */
|
||||
int
|
||||
control_any_per_second_event_enabled(void)
|
||||
{
|
||||
return ANY_EVENT_IS_INTERESTING(
|
||||
EVENT_BANDWIDTH_USED |
|
||||
EVENT_CELL_STATS |
|
||||
EVENT_CIRC_BANDWIDTH_USED |
|
||||
EVENT_CONN_BW |
|
||||
EVENT_STREAM_BANDWIDTH_USED
|
||||
);
|
||||
}
|
||||
|
||||
/* The value of 'get_bytes_read()' the previous time that
|
||||
* control_get_bytes_rw_last_sec() as called. */
|
||||
static uint64_t stats_prev_n_read = 0;
|
||||
/* The value of 'get_bytes_written()' the previous time that
|
||||
* control_get_bytes_rw_last_sec() as called. */
|
||||
static uint64_t stats_prev_n_written = 0;
|
||||
|
||||
/**
|
||||
* Set <b>n_read</b> and <b>n_written</b> to the total number of bytes read
|
||||
* and written by Tor since the last call to this function.
|
||||
*
|
||||
* Call this only from the main thread.
|
||||
*/
|
||||
static void
|
||||
control_get_bytes_rw_last_sec(uint64_t *n_read,
|
||||
uint64_t *n_written)
|
||||
{
|
||||
const uint64_t stats_n_bytes_read = get_bytes_read();
|
||||
const uint64_t stats_n_bytes_written = get_bytes_written();
|
||||
|
||||
*n_read = stats_n_bytes_read - stats_prev_n_read;
|
||||
*n_written = stats_n_bytes_written - stats_prev_n_written;
|
||||
stats_prev_n_read = stats_n_bytes_read;
|
||||
stats_prev_n_written = stats_n_bytes_written;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all the controller events (if any) that are scheduled to trigger once
|
||||
* per second.
|
||||
*/
|
||||
void
|
||||
control_per_second_events(void)
|
||||
{
|
||||
if (!control_any_per_second_event_enabled())
|
||||
return;
|
||||
|
||||
uint64_t bytes_read, bytes_written;
|
||||
control_get_bytes_rw_last_sec(&bytes_read, &bytes_written);
|
||||
control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written);
|
||||
|
||||
control_event_stream_bandwidth_used();
|
||||
control_event_conn_bandwidth_used();
|
||||
control_event_circ_bandwidth_used();
|
||||
control_event_circuit_cell_stats();
|
||||
}
|
||||
|
||||
/** Append a NUL-terminated string <b>s</b> to the end of
|
||||
* <b>conn</b>-\>outbuf.
|
||||
*/
|
||||
@ -7035,6 +7112,8 @@ control_event_bootstrap_problem(const char *warn, const char *reason,
|
||||
if (bootstrap_problems >= BOOTSTRAP_PROBLEM_THRESHOLD)
|
||||
dowarn = 1;
|
||||
|
||||
/* Don't warn about our bootstrapping status if we are hibernating or
|
||||
* shutting down. */
|
||||
if (we_are_hibernating())
|
||||
dowarn = 0;
|
||||
|
||||
@ -7606,6 +7685,8 @@ control_free_all(void)
|
||||
{
|
||||
smartlist_t *queued_events = NULL;
|
||||
|
||||
stats_prev_n_read = stats_prev_n_written = 0;
|
||||
|
||||
if (authentication_cookie) /* Free the auth cookie */
|
||||
tor_free(authentication_cookie);
|
||||
if (detached_onion_services) { /* Free the detached onion services */
|
||||
|
@ -40,6 +40,9 @@ int connection_control_process_inbuf(control_connection_t *conn);
|
||||
#define EVENT_NS 0x000F
|
||||
int control_event_is_interesting(int event);
|
||||
|
||||
void control_per_second_events(void);
|
||||
int control_any_per_second_event_enabled(void);
|
||||
|
||||
int control_event_circuit_status(origin_circuit_t *circ,
|
||||
circuit_status_event_t e, int reason);
|
||||
int control_event_circuit_purpose_changed(origin_circuit_t *circ,
|
||||
|
@ -955,7 +955,7 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now)
|
||||
tor_assert(node);
|
||||
|
||||
if (router_is_me(router)) {
|
||||
/* We always know if we are down ourselves. */
|
||||
/* We always know if we are shutting down or hibernating ourselves. */
|
||||
answer = ! we_are_hibernating();
|
||||
} else if (router->is_hibernating &&
|
||||
(router->cache_info.published_on +
|
||||
|
@ -883,13 +883,26 @@ hibernate_begin_shutdown(void)
|
||||
hibernate_begin(HIBERNATE_STATE_EXITING, time(NULL));
|
||||
}
|
||||
|
||||
/** Return true iff we are currently hibernating. */
|
||||
/**
|
||||
* Return true iff we are currently hibernating -- that is, if we are in
|
||||
* any non-live state.
|
||||
*/
|
||||
MOCK_IMPL(int,
|
||||
we_are_hibernating,(void))
|
||||
{
|
||||
return hibernate_state != HIBERNATE_STATE_LIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true iff we are currently _fully_ hibernating -- that is, if we are
|
||||
* in a state where we expect to handle no network activity at all.
|
||||
*/
|
||||
MOCK_IMPL(int,
|
||||
we_are_fully_hibernating,(void))
|
||||
{
|
||||
return hibernate_state == HIBERNATE_STATE_DORMANT;
|
||||
}
|
||||
|
||||
/** If we aren't currently dormant, close all connections and become
|
||||
* dormant. */
|
||||
static void
|
||||
@ -1187,6 +1200,8 @@ on_hibernate_state_change(hibernate_state_t prev_state)
|
||||
if (prev_state != HIBERNATE_STATE_INITIAL) {
|
||||
rescan_periodic_events(get_options());
|
||||
}
|
||||
|
||||
reschedule_per_second_timer();
|
||||
}
|
||||
|
||||
/** Free all resources held by the accounting module */
|
||||
|
@ -25,6 +25,7 @@ void accounting_add_bytes(size_t n_read, size_t n_written, int seconds);
|
||||
int accounting_record_bandwidth_usage(time_t now, or_state_t *state);
|
||||
void hibernate_begin_shutdown(void);
|
||||
MOCK_DECL(int, we_are_hibernating, (void));
|
||||
MOCK_DECL(int, we_are_fully_hibernating,(void));
|
||||
void consider_hibernation(time_t now);
|
||||
int getinfo_helper_accounting(control_connection_t *conn,
|
||||
const char *question, char **answer,
|
||||
|
@ -163,11 +163,6 @@ token_bucket_rw_t global_bucket;
|
||||
/* Token bucket for relayed traffic. */
|
||||
token_bucket_rw_t global_relayed_bucket;
|
||||
|
||||
/* DOCDOC stats_prev_n_read */
|
||||
static uint64_t stats_prev_n_read = 0;
|
||||
/* DOCDOC stats_prev_n_written */
|
||||
static uint64_t stats_prev_n_written = 0;
|
||||
|
||||
/* XXX we might want to keep stats about global_relayed_*_bucket too. Or not.*/
|
||||
/** How many bytes have we read since we started the process? */
|
||||
static uint64_t stats_n_bytes_read = 0;
|
||||
@ -1258,7 +1253,8 @@ run_connection_housekeeping(int i, time_t now)
|
||||
} else if (we_are_hibernating() &&
|
||||
! have_any_circuits &&
|
||||
!connection_get_outbuf_len(conn)) {
|
||||
/* We're hibernating, there's no circuits, and nothing to flush.*/
|
||||
/* We're hibernating or shutting down, there's no circuits, and nothing to
|
||||
* flush.*/
|
||||
log_info(LD_OR,"Expiring non-used OR connection to fd %d (%s:%d) "
|
||||
"[Hibernating or exiting].",
|
||||
(int)conn->s,conn->address, conn->port);
|
||||
@ -2495,6 +2491,36 @@ hs_service_callback(time_t now, const or_options_t *options)
|
||||
|
||||
/** Timer: used to invoke second_elapsed_callback() once per second. */
|
||||
static periodic_timer_t *second_timer = NULL;
|
||||
|
||||
/**
|
||||
* Enable or disable the per-second timer as appropriate, creating it if
|
||||
* necessary.
|
||||
*/
|
||||
void
|
||||
reschedule_per_second_timer(void)
|
||||
{
|
||||
struct timeval one_second;
|
||||
one_second.tv_sec = 1;
|
||||
one_second.tv_usec = 0;
|
||||
|
||||
if (! second_timer) {
|
||||
second_timer = periodic_timer_new(tor_libevent_get_base(),
|
||||
&one_second,
|
||||
second_elapsed_callback,
|
||||
NULL);
|
||||
tor_assert(second_timer);
|
||||
}
|
||||
|
||||
const bool run_per_second_events =
|
||||
control_any_per_second_event_enabled() || ! net_is_completely_disabled();
|
||||
|
||||
if (run_per_second_events) {
|
||||
periodic_timer_launch(second_timer, &one_second);
|
||||
} else {
|
||||
periodic_timer_disable(second_timer);
|
||||
}
|
||||
}
|
||||
|
||||
/** Last time that update_current_time was called. */
|
||||
static time_t current_second = 0;
|
||||
/** Last time that update_current_time updated current_second. */
|
||||
@ -2568,8 +2594,6 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg)
|
||||
* could use Libevent's timers for this rather than checking the current
|
||||
* time against a bunch of timeouts every second. */
|
||||
time_t now;
|
||||
size_t bytes_written;
|
||||
size_t bytes_read;
|
||||
(void)timer;
|
||||
(void)arg;
|
||||
|
||||
@ -2581,18 +2605,8 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg)
|
||||
*/
|
||||
update_current_time(now);
|
||||
|
||||
/* the second has rolled over. check more stuff. */
|
||||
// remove this once it's unneeded
|
||||
bytes_read = (size_t)(stats_n_bytes_read - stats_prev_n_read);
|
||||
bytes_written = (size_t)(stats_n_bytes_written - stats_prev_n_written);
|
||||
stats_prev_n_read = stats_n_bytes_read;
|
||||
stats_prev_n_written = stats_n_bytes_written;
|
||||
|
||||
control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written);
|
||||
control_event_stream_bandwidth_used();
|
||||
control_event_conn_bandwidth_used();
|
||||
control_event_circ_bandwidth_used();
|
||||
control_event_circuit_cell_stats();
|
||||
/* Maybe some controller events are ready to fire */
|
||||
control_per_second_events();
|
||||
|
||||
run_scheduled_events(now);
|
||||
}
|
||||
@ -2872,17 +2886,7 @@ do_main_loop(void)
|
||||
}
|
||||
|
||||
/* set up once-a-second callback. */
|
||||
if (! second_timer) {
|
||||
struct timeval one_second;
|
||||
one_second.tv_sec = 1;
|
||||
one_second.tv_usec = 0;
|
||||
|
||||
second_timer = periodic_timer_new(tor_libevent_get_base(),
|
||||
&one_second,
|
||||
second_elapsed_callback,
|
||||
NULL);
|
||||
tor_assert(second_timer);
|
||||
}
|
||||
reschedule_per_second_timer();
|
||||
|
||||
#ifdef HAVE_SYSTEMD_209
|
||||
uint64_t watchdog_delay;
|
||||
@ -3697,7 +3701,6 @@ tor_free_all(int postfork)
|
||||
|
||||
memset(&global_bucket, 0, sizeof(global_bucket));
|
||||
memset(&global_relayed_bucket, 0, sizeof(global_relayed_bucket));
|
||||
stats_prev_n_read = stats_prev_n_written = 0;
|
||||
stats_n_bytes_read = stats_n_bytes_written = 0;
|
||||
time_of_process_start = 0;
|
||||
time_of_last_signewnym = 0;
|
||||
|
@ -94,6 +94,7 @@ uint64_t get_main_loop_error_count(void);
|
||||
uint64_t get_main_loop_idle_count(void);
|
||||
|
||||
void periodic_events_on_new_options(const or_options_t *options);
|
||||
void reschedule_per_second_timer(void);
|
||||
|
||||
extern time_t time_of_process_start;
|
||||
extern int quiet_level;
|
||||
|
@ -1599,15 +1599,24 @@ router_perform_bandwidth_test(int num_circs, time_t now)
|
||||
}
|
||||
}
|
||||
|
||||
/** Return true iff our network is in some sense disabled: either we're
|
||||
* hibernating, entering hibernation, or the network is turned off with
|
||||
* DisableNetwork. */
|
||||
/** Return true iff our network is in some sense disabled or shutting down:
|
||||
* either we're hibernating, entering hibernation, or the network is turned
|
||||
* off with DisableNetwork. */
|
||||
int
|
||||
net_is_disabled(void)
|
||||
{
|
||||
return get_options()->DisableNetwork || we_are_hibernating();
|
||||
}
|
||||
|
||||
/** Return true iff our network is in some sense "completely disabled" either
|
||||
* we're fully hibernating or the network is turned off with
|
||||
* DisableNetwork. */
|
||||
int
|
||||
net_is_completely_disabled(void)
|
||||
{
|
||||
return get_options()->DisableNetwork || we_are_fully_hibernating();
|
||||
}
|
||||
|
||||
/** Return true iff we believe ourselves to be an authoritative
|
||||
* directory server.
|
||||
*/
|
||||
@ -2268,6 +2277,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
|
||||
/* and compute ri->bandwidthburst similarly */
|
||||
ri->bandwidthburst = get_effective_bwburst(options);
|
||||
|
||||
/* Report bandwidth, unless we're hibernating or shutting down */
|
||||
ri->bandwidthcapacity = hibernating ? 0 : rep_hist_bandwidth_assess();
|
||||
|
||||
if (dns_seems_to_be_broken() || has_dns_init_failed()) {
|
||||
@ -2538,6 +2548,8 @@ check_descriptor_bandwidth_changed(time_t now)
|
||||
return;
|
||||
|
||||
prev = router_get_my_routerinfo()->bandwidthcapacity;
|
||||
/* Consider ourselves to have zero bandwidth if we're hibernating or
|
||||
* shutting down. */
|
||||
cur = we_are_hibernating() ? 0 : rep_hist_bandwidth_assess();
|
||||
if ((prev != cur && (!prev || !cur)) ||
|
||||
cur > prev*2 ||
|
||||
|
@ -53,6 +53,7 @@ void router_dirport_found_reachable(void);
|
||||
void router_perform_bandwidth_test(int num_circs, time_t now);
|
||||
|
||||
int net_is_disabled(void);
|
||||
int net_is_completely_disabled(void);
|
||||
|
||||
int authdir_mode(const or_options_t *options);
|
||||
int authdir_mode_handles_descs(const or_options_t *options, int purpose);
|
||||
|
Loading…
Reference in New Issue
Block a user