#include <stdio.h>
#include <stdlib.h>
#include <stropts.h>
#include <synch.h>
#include <thread.h>
#include <libsysevent.h>
#include <sys/sysevent/eventdefs.h>
#include <sys/sysevent/dev.h>
#include <errno.h>
#include <libgen.h>
#include <unistd.h>
#include "libdiskmgt.h"
#include "disks_private.h"
#pragma fini(libdiskmgt_fini)
struct event_list {
struct event_list *next;
nvlist_t *event;
};
static mutex_t shp_lock = ERRORCHECKMUTEX;
static sysevent_handle_t *shp = NULL;
static struct event_list *events = NULL;
static int event_error = 0;
static int event_break = 0;
static mutex_t queue_lock;
static sema_t semaphore;
#define WALK_NONE 0
#define WALK_WAITING 1
#define WALK_RUNNING 2
#define WALK_WAIT_TIME 60
static mutex_t walker_lock = ERRORCHECKMUTEX;
static cond_t walker_cv = DEFAULTCV;
static int walker_state = WALK_NONE;
static int events_pending = 0;
static int sendevents = 0;
static void add_event_to_queue(nvlist_t *event);
static void *cb_watch_events(void *);
static void event_handler(sysevent_t *ev);
static void print_nvlist(char *prefix, nvlist_t *list);
static void walk_devtree(void);
static void *walker(void *arg);
static void(*callback)(nvlist_t *, int) = NULL;
static boolean_t shutting_down = B_FALSE;
static void
libdiskmgt_fini(void)
{
mutex_enter(&shp_lock);
if (shp != NULL) {
sysevent_unsubscribe_event(shp, EC_ALL);
sysevent_unbind_handle(shp);
shp = NULL;
}
mutex_enter(&walker_lock);
shutting_down = B_TRUE;
while (walker_state != WALK_NONE)
(void) cond_wait(&walker_cv, &walker_lock);
mutex_exit(&walker_lock);
}
nvlist_t *
dm_get_event(int *errp)
{
nvlist_t *event = NULL;
*errp = 0;
while (1) {
(void) sema_wait(&semaphore);
if (event_break) {
event_break = 0;
*errp = EINTR;
break;
}
(void) mutex_lock(&queue_lock);
if (event_error != 0) {
*errp = event_error;
event_error = 0;
} else if (events != NULL) {
struct event_list *tmpp;
event = events->event;
tmpp = events->next;
free(events);
events = tmpp;
}
(void) mutex_unlock(&queue_lock);
if (*errp != 0 || event != NULL) {
break;
}
}
return (event);
}
void
dm_init_event_queue(void (*cb)(nvlist_t *, int), int *errp)
{
if (sendevents == 1) {
*errp = 0;
if (cb != callback) {
callback = cb;
if (cb == NULL) {
event_break = 1;
(void) sema_post(&semaphore);
} else {
thread_t watch_thread;
*errp = thr_create(NULL, 0, cb_watch_events, NULL,
THR_DAEMON, &watch_thread);
}
}
} else {
sendevents = 1;
*errp = sema_init(&semaphore, 0, USYNC_THREAD, NULL);
if (*errp != 0) {
return;
}
if (cb != NULL) {
thread_t watch_thread;
callback = cb;
*errp = thr_create(NULL, 0, cb_watch_events, NULL, THR_DAEMON,
&watch_thread);
}
}
}
void
events_new_event(char *name, int dtype, char *etype)
{
nvlist_t *event = NULL;
if (!sendevents) {
return;
}
if (nvlist_alloc(&event, NVATTRS, 0) != 0) {
event = NULL;
} else {
int error = 0;
if (name != NULL &&
nvlist_add_string(event, DM_EV_NAME, name) != 0) {
error = ENOMEM;
}
if (dtype != -1 &&
nvlist_add_uint32(event, DM_EV_DTYPE, dtype) != 0) {
error = ENOMEM;
}
if (nvlist_add_string(event, DM_EV_TYPE, etype) != 0) {
error = ENOMEM;
}
if (error != 0) {
nvlist_free(event);
event = NULL;
}
}
add_event_to_queue(event);
}
void
events_new_slice_event(char *dev, char *type)
{
events_new_event(basename(dev), DM_SLICE, type);
}
int
events_start_event_watcher()
{
const char *subclass_list[1];
int ret = -1;
mutex_enter(&shp_lock);
if (shp != NULL) {
ret = 0;
goto out;
}
shp = sysevent_bind_handle(event_handler);
if (shp == NULL) {
if (dm_debug) {
(void) fprintf(stderr, "ERROR: sysevent bind failed: "
"%d\n", errno);
}
goto out;
}
subclass_list[0] = ESC_DISK;
if (sysevent_subscribe_event(shp, EC_DEV_ADD, subclass_list, 1) != 0 ||
sysevent_subscribe_event(shp, EC_DEV_REMOVE, subclass_list, 1) !=
0) {
sysevent_unsubscribe_event(shp, EC_ALL);
sysevent_unbind_handle(shp);
shp = NULL;
if (dm_debug) {
(void) fprintf(stderr, "ERROR: sysevent subscribe "
"failed: %d\n", errno);
}
goto out;
}
ret = 0;
out:
mutex_exit(&shp_lock);
return (ret);
}
static void
add_event_to_queue(nvlist_t *event)
{
(void) mutex_lock(&queue_lock);
if (event == NULL) {
event_error = ENOMEM;
(void) mutex_unlock(&queue_lock);
return;
}
if (events == NULL) {
events = (struct event_list *)malloc(sizeof (struct event_list));
if (events == NULL) {
event_error = ENOMEM;
nvlist_free(event);
} else {
events->next = NULL;
events->event = event;
}
} else {
struct event_list *ep;
struct event_list *new_event;
for (ep = events; ep->next != NULL; ep = ep->next);
new_event = (struct event_list *)malloc(sizeof (struct event_list));
if (new_event == NULL) {
event_error = ENOMEM;
nvlist_free(event);
} else {
new_event->next = NULL;
new_event->event = event;
ep->next = new_event;
}
}
(void) mutex_unlock(&queue_lock);
(void) sema_post(&semaphore);
}
static void *
cb_watch_events(void *arg __unused)
{
nvlist_t *event;
int error;
while (1) {
event = dm_get_event(&error);
if (callback == NULL) {
return (NULL);
}
callback(event, error);
}
}
static void
event_handler(sysevent_t *ev)
{
char *class_name;
char *pub;
class_name = sysevent_get_class_name(ev);
if (dm_debug) {
(void) fprintf(stderr, "****EVENT: %s %s ", class_name,
sysevent_get_subclass_name(ev));
if ((pub = sysevent_get_pub_name(ev)) != NULL) {
(void) fprintf(stderr, "%s\n", pub);
free(pub);
} else {
(void) fprintf(stderr, "\n");
}
}
if (libdiskmgt_str_eq(class_name, EC_DEV_ADD)) {
walk_devtree();
} else if (libdiskmgt_str_eq(class_name, EC_DEV_REMOVE)) {
nvlist_t *nvlist = NULL;
char *dev_name = NULL;
(void) sysevent_get_attr_list(ev, &nvlist);
if (nvlist != NULL) {
(void) nvlist_lookup_string(nvlist, DEV_NAME, &dev_name);
if (dm_debug) {
print_nvlist("**** ", nvlist);
}
}
if (dev_name != NULL) {
cache_update(DM_EV_DISK_DELETE, dev_name);
}
if (nvlist != NULL) {
nvlist_free(nvlist);
}
}
}
static void
print_nvlist(char *prefix, nvlist_t *list)
{
nvpair_t *nvp;
nvp = nvlist_next_nvpair(list, NULL);
while (nvp != NULL) {
char *attrname;
char *str;
uint32_t ui32;
uint64_t ui64;
char **str_array;
uint_t cnt;
int i;
attrname = nvpair_name(nvp);
switch (nvpair_type(nvp)) {
case DATA_TYPE_STRING:
(void) nvpair_value_string(nvp, &str);
(void) fprintf(stderr, "%s%s: %s\n", prefix, attrname, str);
break;
case DATA_TYPE_STRING_ARRAY:
(void) nvpair_value_string_array(nvp, &str_array, &cnt);
(void) fprintf(stderr, "%s%s:\n", prefix, attrname);
for (i = 0; i < cnt; i++) {
(void) fprintf(stderr, "%s %s\n", prefix, str_array[i]);
}
break;
case DATA_TYPE_UINT32:
(void) nvpair_value_uint32(nvp, &ui32);
(void) fprintf(stderr, "%s%s: %u\n", prefix, attrname, ui32);
break;
case DATA_TYPE_UINT64:
(void) nvpair_value_uint64(nvp, &ui64);
#ifdef _LP64
(void) fprintf(stderr, "%s%s: %lu\n", prefix, attrname, ui64);
#else
(void) fprintf(stderr, "%s%s: %llu\n", prefix, attrname, ui64);
#endif
break;
case DATA_TYPE_BOOLEAN:
(void) fprintf(stderr, "%s%s: true\n", prefix, attrname);
break;
default:
(void) fprintf(stderr, "%s%s: UNSUPPORTED TYPE\n", prefix,
attrname);
break;
}
nvp = nvlist_next_nvpair(list, nvp);
}
}
static void
walk_devtree(void)
{
thread_t walk_thread;
mutex_enter(&walker_lock);
switch (walker_state) {
case WALK_NONE:
if (thr_create(NULL, 0, walker, NULL,
THR_DAEMON, &walk_thread) == 0) {
walker_state = WALK_WAITING;
}
break;
case WALK_WAITING:
break;
case WALK_RUNNING:
events_pending = 1;
break;
}
mutex_exit(&walker_lock);
}
static void *
walker(void *arg __unused)
{
int walk_again = 0;
do {
(void) sleep(WALK_WAIT_TIME);
mutex_enter(&walker_lock);
if (shutting_down) {
walker_state = WALK_NONE;
(void) cond_broadcast(&walker_cv);
mutex_exit(&walker_lock);
return (NULL);
}
walker_state = WALK_RUNNING;
mutex_exit(&walker_lock);
cache_update(DM_EV_DISK_ADD, NULL);
mutex_enter(&walker_lock);
if (shutting_down) {
walker_state = WALK_NONE;
(void) cond_broadcast(&walker_cv);
mutex_exit(&walker_lock);
return (NULL);
}
if (events_pending) {
events_pending = 0;
walker_state = WALK_WAITING;
walk_again = 1;
} else {
walker_state = WALK_NONE;
walk_again = 0;
}
mutex_exit(&walker_lock);
} while (walk_again);
return (NULL);
}