#include <atomic.h>
#include <errno.h>
#include <execinfo.h>
#include <libuutil.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <sys/time.h>
#include <unistd.h>
#include "conditions.h"
#include "events.h"
#include "objects.h"
#include "util.h"
struct nwamd_event_source {
char *name;
void (*events_init)(void);
void (*events_fini)(void);
} event_sources[] = {
{ "routing_events",
nwamd_routing_events_init, nwamd_routing_events_fini },
{ "sysevent_events",
nwamd_sysevent_events_init, nwamd_sysevent_events_fini },
};
static uint64_t event_id_counter = 0;
static uu_list_pool_t *event_pool = NULL;
static uu_list_t *event_queue = NULL;
static pthread_mutex_t event_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t event_queue_cond = PTHREAD_COND_INITIALIZER;
static int nwamd_event_compare(const void *, const void *, void *);
static const char *
nwamd_event_name(int event_type)
{
if (event_type <= NWAM_EVENT_MAX)
return (nwam_event_type_to_string(event_type));
switch (event_type) {
case NWAM_EVENT_TYPE_OBJECT_INIT:
return ("OBJECT_INIT");
case NWAM_EVENT_TYPE_OBJECT_FINI:
return ("OBJECT_FINI");
case NWAM_EVENT_TYPE_TIMED_CHECK_CONDITIONS:
return ("TIMED_CHECK_CONDITIONS");
case NWAM_EVENT_TYPE_TRIGGERED_CHECK_CONDITIONS:
return ("TRIGGERED_CHECK_CONDITIONS");
case NWAM_EVENT_TYPE_NCU_CHECK:
return ("NCU_CHECK");
case NWAM_EVENT_TYPE_TIMER:
return ("TIMER");
case NWAM_EVENT_TYPE_UPGRADE:
return ("UPGRADE");
case NWAM_EVENT_TYPE_PERIODIC_SCAN:
return ("PERIODIC_SCAN");
case NWAM_EVENT_TYPE_QUEUE_QUIET:
return ("QUEUE_QUIET");
default:
return ("N/A");
}
}
void
nwamd_event_sources_init(void)
{
int i;
for (i = 0;
i < sizeof (event_sources) / sizeof (struct nwamd_event_source);
i++) {
if (event_sources[i].events_init != NULL)
event_sources[i].events_init();
}
}
void
nwamd_event_sources_fini(void)
{
int i;
for (i = 0;
i < sizeof (event_sources) / sizeof (struct nwamd_event_source);
i++) {
if (event_sources[i].events_init != NULL)
event_sources[i].events_fini();
}
}
static int
nwamd_event_compare(const void *l_arg, const void *r_arg, void *private)
{
nwamd_event_t l = (nwamd_event_t)l_arg;
nwamd_event_t r = (nwamd_event_t)r_arg;
int rv;
rv = l->event_time.tv_sec - r->event_time.tv_sec;
if (rv == 0)
rv = l->event_time.tv_nsec - r->event_time.tv_nsec;
return (rv);
}
void
nwamd_event_queue_init(void)
{
event_pool = uu_list_pool_create("event_queue_pool",
sizeof (struct nwamd_event),
offsetof(struct nwamd_event, event_node),
nwamd_event_compare, UU_LIST_POOL_DEBUG);
if (event_pool == NULL)
pfail("uu_list_pool_create failed with error %d", uu_error());
event_queue = uu_list_create(event_pool, NULL, UU_LIST_SORTED);
if (event_queue == NULL)
pfail("uu_list_create failed with error %d", uu_error());
}
void
nwamd_event_queue_fini(void)
{
void *cookie = NULL;
nwamd_event_t event;
while ((event = uu_list_teardown(event_queue, &cookie)) != NULL)
nwamd_event_fini(event);
uu_list_destroy(event_queue);
if (event_pool != NULL)
uu_list_pool_destroy(event_pool);
}
nwamd_event_t
nwamd_event_init(int32_t type, nwam_object_type_t object_type,
size_t size, const char *object_name)
{
nwamd_event_t event;
event = calloc(1, sizeof (struct nwamd_event));
if (event == NULL) {
nlog(LOG_ERR, "nwamd_event_init: could not create %s event for "
"object %s", nwamd_event_name(type),
object_name != NULL ? object_name : "<no object>");
return (NULL);
}
if (type <= NWAM_EVENT_MAX) {
event->event_send = B_TRUE;
event->event_msg = calloc(1, sizeof (struct nwam_event) + size);
if (event->event_msg == NULL) {
nlog(LOG_ERR,
"nwamd_event_init: could not create %s event",
nwamd_event_name(type));
free(event);
return (NULL);
}
event->event_msg->nwe_type = type;
event->event_msg->nwe_size = sizeof (struct nwam_event) + size;
} else {
event->event_send = B_FALSE;
event->event_msg = NULL;
}
event->event_type = type;
if (object_name != NULL) {
(void) strlcpy(event->event_object, object_name,
NWAM_MAX_NAME_LEN);
event->event_object_type = object_type;
} else {
event->event_object[0] = '\0';
}
event->event_id = atomic_add_64_nv(&event_id_counter, 1);
(void) clock_gettime(CLOCK_REALTIME, &event->event_time);
return (event);
}
void
nwamd_event_do_not_send(nwamd_event_t event)
{
nlog(LOG_DEBUG, "nwamd_event_do_not_send: cancelling delivery of "
"event %s for object %s", nwamd_event_name(event->event_type),
event->event_object[0] != '\0' ?
event->event_object : "<no object>");
event->event_send = B_FALSE;
}
void
nwamd_event_fini(nwamd_event_t event)
{
if (event != NULL) {
free(event->event_msg);
free(event);
}
}
nwamd_event_t
nwamd_event_init_object_action(nwam_object_type_t object_type,
const char *object_name, const char *parent_name,
nwam_action_t object_action)
{
nwamd_event_t event;
event = nwamd_event_init(NWAM_EVENT_TYPE_OBJECT_ACTION,
object_type, 0, object_name);
if (event == NULL)
return (NULL);
event->event_msg->nwe_data.nwe_object_action.nwe_action = object_action;
event->event_msg->nwe_data.nwe_object_action.nwe_object_type =
object_type;
(void) strlcpy(event->event_msg->nwe_data.nwe_object_action.nwe_name,
object_name,
sizeof (event->event_msg->nwe_data.nwe_object_action.nwe_name));
if (parent_name == NULL) {
event->event_msg->nwe_data.nwe_object_action.nwe_parent[0] =
'\0';
return (event);
}
(void) strlcpy
(event->event_msg->nwe_data.nwe_object_action.nwe_parent,
parent_name,
sizeof (event->event_msg->nwe_data.nwe_object_action.nwe_parent));
return (event);
}
nwamd_event_t
nwamd_event_init_object_state(nwam_object_type_t object_type,
const char *object_name, nwam_state_t state, nwam_aux_state_t aux_state)
{
nwamd_event_t event;
event = nwamd_event_init(NWAM_EVENT_TYPE_OBJECT_STATE,
object_type, 0, object_name);
if (event == NULL)
return (NULL);
event->event_msg->nwe_data.nwe_object_state.nwe_state = state;
event->event_msg->nwe_data.nwe_object_state.nwe_aux_state = aux_state;
event->event_msg->nwe_data.nwe_object_state.nwe_object_type =
object_type;
(void) strlcpy(event->event_msg->nwe_data.nwe_object_state.nwe_name,
object_name,
sizeof (event->event_msg->nwe_data.nwe_object_state.nwe_name));
return (event);
}
nwamd_event_t
nwamd_event_init_priority_group_change(int64_t priority)
{
nwamd_event_t event;
event = nwamd_event_init(NWAM_EVENT_TYPE_PRIORITY_GROUP,
NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL);
if (event == NULL)
return (NULL);
event->event_msg->nwe_data.nwe_priority_group_info.nwe_priority =
priority;
return (event);
}
nwamd_event_t
nwamd_event_init_link_action(const char *name, nwam_action_t link_action)
{
nwamd_event_t event;
nwam_error_t err;
char *object_name;
if ((err = nwam_ncu_name_to_typed_name(name, NWAM_NCU_TYPE_LINK,
&object_name)) != NWAM_SUCCESS) {
nlog(LOG_ERR, "nwamd_event_init_link_action: "
"nwam_ncu_name_to_typed_name: %s",
nwam_strerror(err));
return (NULL);
}
event = nwamd_event_init(NWAM_EVENT_TYPE_LINK_ACTION,
NWAM_OBJECT_TYPE_NCU, 0, object_name);
free(object_name);
if (event == NULL)
return (NULL);
(void) strlcpy(event->event_msg->nwe_data.nwe_link_action.nwe_name,
name,
sizeof (event->event_msg->nwe_data.nwe_link_action.nwe_name));
event->event_msg->nwe_data.nwe_link_action.nwe_action = link_action;
return (event);
}
nwamd_event_t
nwamd_event_init_link_state(const char *name, boolean_t up)
{
nwamd_event_t event;
nwam_error_t err;
char *object_name;
if ((err = nwam_ncu_name_to_typed_name(name, NWAM_NCU_TYPE_LINK,
&object_name)) != NWAM_SUCCESS) {
nlog(LOG_ERR, "nwamd_event_init_link_state: "
"nwam_ncu_name_to_typed_name: %s",
nwam_strerror(err));
return (NULL);
}
event = nwamd_event_init(NWAM_EVENT_TYPE_LINK_STATE,
NWAM_OBJECT_TYPE_NCU, 0, object_name);
free(object_name);
if (event == NULL)
return (NULL);
(void) strlcpy(event->event_msg->nwe_data.nwe_link_state.nwe_name, name,
sizeof (event->event_msg->nwe_data.nwe_link_state.nwe_name));
event->event_msg->nwe_data.nwe_link_state.nwe_link_up = up;
return (event);
}
nwamd_event_t
nwamd_event_init_if_state(const char *linkname, uint32_t flags,
uint32_t addr_added, struct sockaddr *addr, struct sockaddr *netmask)
{
nwamd_event_t event;
nwam_error_t err;
char *object_name;
if ((err = nwam_ncu_name_to_typed_name(linkname,
NWAM_NCU_TYPE_INTERFACE, &object_name)) != NWAM_SUCCESS) {
nlog(LOG_ERR, "nwamd_event_init_if_state: "
"nwam_ncu_name_to_typed_name: %s",
nwam_strerror(err));
return (NULL);
}
event = nwamd_event_init(NWAM_EVENT_TYPE_IF_STATE,
NWAM_OBJECT_TYPE_NCU, 0, object_name);
free(object_name);
if (event == NULL)
return (NULL);
(void) strlcpy(event->event_msg->nwe_data.nwe_if_state.nwe_name,
linkname,
sizeof (event->event_msg->nwe_data.nwe_if_state.nwe_name));
event->event_msg->nwe_data.nwe_if_state.nwe_flags = flags;
event->event_msg->nwe_data.nwe_if_state.nwe_addr_added = addr_added;
event->event_msg->nwe_data.nwe_if_state.nwe_addr_valid = (addr != NULL);
if (addr != NULL) {
bcopy(addr, &(event->event_msg->nwe_data.nwe_if_state.nwe_addr),
addr->sa_family == AF_INET ? sizeof (struct sockaddr_in) :
sizeof (struct sockaddr_in6));
}
if (netmask != NULL) {
bcopy(netmask,
&(event->event_msg->nwe_data.nwe_if_state.nwe_netmask),
netmask->sa_family == AF_INET ?
sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6));
}
return (event);
}
nwamd_event_t
nwamd_event_init_wlan(const char *name, int32_t type, boolean_t connected,
nwam_wlan_t *wlans, uint_t num_wlans)
{
size_t size = 0;
char *object_name;
nwamd_event_t event;
nwam_error_t err;
switch (type) {
case NWAM_EVENT_TYPE_WLAN_SCAN_REPORT:
case NWAM_EVENT_TYPE_WLAN_NEED_CHOICE:
size = sizeof (nwam_wlan_t) * (num_wlans - 1);
break;
case NWAM_EVENT_TYPE_WLAN_NEED_KEY:
case NWAM_EVENT_TYPE_WLAN_CONNECTION_REPORT:
break;
default:
nlog(LOG_ERR, "nwamd_event_init_wlan: unexpected "
"event type %s (%d)", nwamd_event_name(type), type);
return (NULL);
}
if ((err = nwam_ncu_name_to_typed_name(name, NWAM_NCU_TYPE_LINK,
&object_name)) != NWAM_SUCCESS) {
nlog(LOG_ERR, "nwamd_event_init_wlan: "
"nwam_ncu_name_to_typed_name: %s",
nwam_strerror(err));
return (NULL);
}
event = nwamd_event_init(type, NWAM_OBJECT_TYPE_NCU, size, object_name);
free(object_name);
if (event == NULL)
return (NULL);
(void) strlcpy(event->event_msg->nwe_data.nwe_wlan_info.nwe_name, name,
sizeof (event->event_msg->nwe_data.nwe_wlan_info.nwe_name));
event->event_msg->nwe_data.nwe_wlan_info.nwe_connected = connected;
event->event_msg->nwe_data.nwe_wlan_info.nwe_num_wlans = num_wlans;
(void) memcpy(event->event_msg->nwe_data.nwe_wlan_info.nwe_wlans, wlans,
num_wlans * sizeof (nwam_wlan_t));
return (event);
}
nwamd_event_t
nwamd_event_init_ncu_check(void)
{
return (nwamd_event_init(NWAM_EVENT_TYPE_NCU_CHECK,
NWAM_OBJECT_TYPE_NCP, 0, NULL));
}
nwamd_event_t
nwamd_event_init_init(void)
{
return (nwamd_event_init(NWAM_EVENT_TYPE_INIT,
NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL));
}
nwamd_event_t
nwamd_event_init_shutdown(void)
{
return (nwamd_event_init(NWAM_EVENT_TYPE_SHUTDOWN,
NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL));
}
void
nwamd_event_enqueue(nwamd_event_t event)
{
nwamd_event_enqueue_timed(event, 0);
}
void
nwamd_event_enqueue_timed(nwamd_event_t event, int delta_seconds)
{
uu_list_index_t idx;
nlog(LOG_DEBUG, "enqueueing event %lld %d (%s) for object %s in %ds",
event->event_id, event->event_type,
nwamd_event_name(event->event_type),
event->event_object[0] != 0 ? event->event_object : "none",
delta_seconds);
(void) clock_gettime(CLOCK_REALTIME, &event->event_time);
event->event_time.tv_sec += delta_seconds;
uu_list_node_init(event, &event->event_node, event_pool);
(void) pthread_mutex_lock(&event_queue_mutex);
(void) uu_list_find(event_queue, event, NULL, &idx);
(void) uu_list_insert(event_queue, event, idx);
(void) pthread_cond_signal(&event_queue_cond);
(void) pthread_mutex_unlock(&event_queue_mutex);
}
boolean_t
nwamd_event_enqueued(int32_t event_type, nwam_object_type_t object_type,
const char *object)
{
nwamd_event_t event;
(void) pthread_mutex_lock(&event_queue_mutex);
for (event = uu_list_first(event_queue);
event != NULL;
event = uu_list_next(event_queue, event)) {
if (event->event_type != event_type)
continue;
if (object_type != NWAM_OBJECT_TYPE_UNKNOWN &&
event->event_object_type != object_type)
continue;
if (object != NULL && strcmp(object, event->event_object) != 0)
continue;
(void) pthread_mutex_unlock(&event_queue_mutex);
return (B_TRUE);
}
(void) pthread_mutex_unlock(&event_queue_mutex);
return (B_FALSE);
}
static boolean_t
in_past(struct timespec t)
{
struct timespec now;
(void) clock_gettime(CLOCK_REALTIME, &now);
if (t.tv_sec < now.tv_sec)
return (B_TRUE);
if (t.tv_sec > now.tv_sec)
return (B_FALSE);
if (t.tv_nsec < now.tv_nsec)
return (B_TRUE);
return (B_FALSE);
}
static nwamd_event_t
nwamd_event_dequeue(long nsec)
{
nwamd_event_t event;
(void) pthread_mutex_lock(&event_queue_mutex);
event = uu_list_first(event_queue);
if (event == NULL && nsec == 0) {
do {
(void) pthread_cond_wait(&event_queue_cond,
&event_queue_mutex);
} while ((event = uu_list_first(event_queue)) == NULL);
} else {
struct timespec waitcap;
if (nsec != 0) {
(void) clock_gettime(CLOCK_REALTIME, &waitcap);
waitcap.tv_nsec += nsec;
waitcap.tv_sec += NSEC_TO_SEC(waitcap.tv_nsec);
waitcap.tv_nsec = NSEC_TO_FRACNSEC(waitcap.tv_nsec);
}
while ((event == NULL || !in_past(event->event_time)) &&
(nsec == 0 || !in_past(waitcap))) {
struct timespec eventwait;
if (nsec == 0) {
eventwait = event->event_time;
} else if (event != NULL) {
uint64_t diff;
diff = SEC_TO_NSEC(event->event_time.tv_sec -
waitcap.tv_sec) +
event->event_time.tv_nsec - waitcap.tv_nsec;
if (diff > 0)
eventwait = waitcap;
else
eventwait = event->event_time;
} else {
eventwait = waitcap;
}
(void) pthread_cond_timedwait(&event_queue_cond,
&event_queue_mutex, &eventwait);
event = uu_list_first(event_queue);
}
}
if (event != NULL && in_past(event->event_time)) {
uu_list_remove(event_queue, event);
uu_list_node_fini(event, &event->event_node, event_pool);
} else {
event = nwamd_event_init(NWAM_EVENT_TYPE_QUEUE_QUIET,
NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL);
}
if (event != NULL)
nlog(LOG_DEBUG,
"dequeueing event %lld of type %d (%s) for object %s",
event->event_id, event->event_type,
nwamd_event_name(event->event_type),
event->event_object[0] != 0 ? event->event_object :
"none");
(void) pthread_mutex_unlock(&event_queue_mutex);
return (event);
}
void
nwamd_event_send(nwam_event_t event_msg)
{
nwam_error_t err;
if (shutting_down && event_msg->nwe_type != NWAM_EVENT_TYPE_SHUTDOWN) {
nlog(LOG_DEBUG, "nwamd_event_send: tossing event as nwamd "
"is shutting down");
return;
}
err = nwam_event_send(event_msg);
if (err != NWAM_SUCCESS) {
nlog(LOG_ERR, "nwamd_event_send: nwam_event_send: %s",
nwam_strerror(err));
}
}
static void
nwamd_event_run_method(nwamd_event_t event)
{
nwamd_event_method_t *event_methods;
int i;
event_methods = nwamd_object_event_methods(event->event_object_type);
if (shutting_down && event->event_type != NWAM_EVENT_TYPE_OBJECT_FINI) {
nlog(LOG_DEBUG, "nwamd_event_run_method: tossing non-fini "
"event %s for object %s",
nwamd_event_name(event->event_type), event->event_object);
return;
}
for (i = 0;
event_methods[i].event_type != NWAM_EVENT_TYPE_NOOP;
i++) {
if (event_methods[i].event_type ==
event->event_type &&
event_methods[i].event_method != NULL) {
nlog(LOG_DEBUG,
"(%p) %s: running method for event %s",
(void *)event, event->event_object,
nwamd_event_name(event->event_type));
event_methods[i].event_method(event);
return;
}
}
nlog(LOG_DEBUG, "(%p) %s: no matching method for event %d (%s)",
(void *)event, event->event_object, event->event_type,
nwamd_event_name(event->event_type));
}
static void
nwamd_activate_ncus(void) {
int64_t prio = INVALID_PRIORITY_GROUP;
boolean_t selected;
nwamd_ncp_activate_manual_ncus();
selected = nwamd_ncp_check_priority_group(&prio);
if (selected) {
nwamd_ncp_activate_priority_group(prio);
nwamd_ncp_deactivate_priority_group_all(prio + 1);
} else {
int64_t oldprio = INVALID_PRIORITY_GROUP;
while (nwamd_ncp_find_next_priority_group(++oldprio, &prio)) {
nwamd_ncp_activate_priority_group(prio);
oldprio = prio;
}
}
}
void
nwamd_event_handler(void)
{
boolean_t got_shutdown_event = B_FALSE;
boolean_t check_conditions = B_FALSE;
boolean_t ncu_check = B_FALSE;
int queue_quiet_time = 0;
nwamd_event_t event;
while (!got_shutdown_event) {
event = nwamd_event_dequeue(queue_quiet_time);
queue_quiet_time = SEC_TO_NSEC(1)/10;
if (event->event_object[0] == '\0') {
switch (event->event_type) {
case NWAM_EVENT_TYPE_NOOP:
case NWAM_EVENT_TYPE_INIT:
break;
case NWAM_EVENT_TYPE_PRIORITY_GROUP:
(void) pthread_mutex_lock(&active_ncp_mutex);
current_ncu_priority_group =
event->event_msg->nwe_data.
nwe_priority_group_info.nwe_priority;
(void) pthread_mutex_unlock(&active_ncp_mutex);
break;
case NWAM_EVENT_TYPE_TIMED_CHECK_CONDITIONS:
if (!shutting_down) {
nwamd_set_timed_check_all_conditions();
check_conditions = B_TRUE;
}
break;
case NWAM_EVENT_TYPE_TRIGGERED_CHECK_CONDITIONS:
if (!shutting_down)
check_conditions = B_TRUE;
break;
case NWAM_EVENT_TYPE_NCU_CHECK:
if (!shutting_down)
ncu_check = B_TRUE;
break;
case NWAM_EVENT_TYPE_UPGRADE:
if (!shutting_down) {
nwamd_event_run_method(event);
}
break;
case NWAM_EVENT_TYPE_SHUTDOWN:
got_shutdown_event = B_TRUE;
break;
case NWAM_EVENT_TYPE_QUEUE_QUIET:
queue_quiet_time = 0;
if (!shutting_down && check_conditions) {
nwamd_check_all_conditions();
check_conditions = B_FALSE;
}
if (!shutting_down && ncu_check) {
nwamd_activate_ncus();
ncu_check = B_FALSE;
}
break;
default:
nlog(LOG_ERR,
"event %d (%s)had no object associated "
"with it", event->event_type,
nwamd_event_name(event->event_type));
break;
}
} else {
nwamd_event_run_method(event);
}
if (event->event_send)
nwamd_event_send(event->event_msg);
nwamd_event_fini(event);
}
nwamd_event_queue_fini();
nwamd_object_lists_fini();
}