#include <sys/types.h>
#include <sys/sysevent/dr.h>
#include <sys/sysevent/eventdefs.h>
#include <sys/sunddi.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <libsysevent.h>
#include <sys/sysevent_impl.h>
#include <libnvpair.h>
#include <config_admin.h>
#include "disk_monitor.h"
#include "hotplug_mgr.h"
#include "schg_mgr.h"
#include "dm_platform.h"
typedef struct sysevent_event {
sysevent_t *evp;
} sysevent_event_t;
static pthread_t g_sysev_tid;
static pthread_mutex_t g_event_handler_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_event_handler_cond = PTHREAD_COND_INITIALIZER;
static qu_t *g_sysev_queue = NULL;
static thread_state_t g_sysev_thread_state = TS_NOT_RUNNING;
static sysevent_handle_t *sysevent_handle = NULL;
static void free_sysevent_event(void *p);
static int
nsleep(int seconds)
{
struct timespec tspec;
tspec.tv_sec = seconds;
tspec.tv_nsec = 0;
return (nanosleep(&tspec, NULL));
}
static int
config_list_ext_poll(int num, char * const *path,
cfga_list_data_t **list_array, int *nlist, int flag)
{
boolean_t done = B_FALSE;
boolean_t timedout = B_FALSE;
boolean_t interrupted = B_FALSE;
int timeout = 0;
int e;
#define TIMEOUT_MAX 60
do {
switch ((e = config_list_ext(num, path, list_array,
nlist, NULL, NULL, NULL, flag))) {
case CFGA_OK:
return (CFGA_OK);
case CFGA_BUSY:
case CFGA_SYSTEM_BUSY:
if (timeout++ >= TIMEOUT_MAX)
timedout = B_TRUE;
else {
if (nsleep(1) < 0)
interrupted = (errno == EINTR);
}
break;
default:
done = B_TRUE;
break;
}
} while (!done && !timedout && !interrupted);
return (e);
}
void
adjust_dynamic_ap(const char *apid, char *adjusted)
{
cfga_list_data_t *list_array = NULL;
int nlist;
char *ap_path[1];
char phys[MAXPATHLEN];
char dev_phys[MAXPATHLEN];
char *dyn;
int c, t, d;
dm_assert((strlen(apid) + 8 ) < MAXPATHLEN);
(void) strcpy(adjusted, apid);
dyn = strstr(apid, "::");
if ((dyn == NULL) || (dyn == apid) ||
(sscanf(dyn, "::dsk/c%dt%dd%d", &c, &t, &d) != 3))
return;
(void) strcpy(phys, apid);
*strstr(phys, "::") = '\0';
(void) snprintf(dev_phys, MAXPATHLEN, "/devices%s", phys);
ap_path[0] = dev_phys;
if (config_list_ext_poll(1, ap_path, &list_array, &nlist, 0)
!= CFGA_OK)
return;
dm_assert(nlist == 1);
if (sscanf(list_array[0].ap_log_id, "c%d", &c) == 1)
(void) snprintf(adjusted, MAXPATHLEN, "%s::dsk/c%dt%dd%d",
phys, c, t, d);
free(list_array);
}
static int
disk_ap_is_scsi(const char *ap_path)
{
return (strstr(ap_path, ":scsi:") != NULL);
}
hotplug_state_t
disk_ap_state_to_hotplug_state(diskmon_t *diskp)
{
hotplug_state_t state = HPS_UNKNOWN;
cfga_list_data_t *list_array = NULL;
int rv, nlist;
char *app = (char *)dm_prop_lookup(diskp->app_props,
DISK_AP_PROP_APID);
char adj_app[MAXPATHLEN];
char *ap_path[1];
char *devices_app;
int len;
boolean_t list_valid = B_FALSE;
dm_assert(app != NULL);
adjust_dynamic_ap(app, adj_app);
ap_path[0] = adj_app;
devices_app = NULL;
rv = config_list_ext_poll(1, ap_path, &list_array, &nlist,
CFGA_FLAG_LIST_ALL);
if (rv != CFGA_OK) {
len = 8 + strlen(adj_app) + 1;
devices_app = dmalloc(len);
(void) snprintf(devices_app, len, "/devices%s",
adj_app);
ap_path[0] = devices_app;
rv = config_list_ext_poll(1, ap_path, &list_array, &nlist,
CFGA_FLAG_LIST_ALL);
}
if (rv == CFGA_OK) {
dm_assert(nlist == 1);
list_valid = B_TRUE;
} else if (disk_ap_is_scsi(ap_path[0]))
state = HPS_ABSENT;
if (devices_app != NULL)
dfree(devices_app, len);
if (list_valid) {
if (list_array[0].ap_r_state == CFGA_STAT_EMPTY ||
list_array[0].ap_r_state == CFGA_STAT_DISCONNECTED)
state = HPS_ABSENT;
else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
list_array[0].ap_cond == CFGA_COND_UNKNOWN)
state = HPS_PRESENT;
else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
list_array[0].ap_cond != CFGA_COND_UNKNOWN)
state = HPS_UNCONFIGURED;
else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
list_array[0].ap_o_state == CFGA_STAT_CONFIGURED)
state = HPS_CONFIGURED;
free(list_array);
}
return (state);
}
static hotplug_state_t
disk_sysev_to_state(diskmon_t *diskp, sysevent_t *evp)
{
const char *class_name, *subclass;
hotplug_state_t state = HPS_UNKNOWN;
sysevent_value_t se_val;
class_name = sysevent_get_class_name(evp);
subclass = sysevent_get_subclass_name(evp);
if (strcmp(class_name, EC_DEVFS) == 0) {
if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0) {
state = HPS_CONFIGURED;
} else if (strcmp(subclass, ESC_DEVFS_DEVI_REMOVE) == 0) {
state = HPS_UNCONFIGURED;
}
} else if (strcmp(class_name, EC_DR) == 0 &&
((strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) ||
(strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0))) {
if (sysevent_lookup_attr(evp, DR_HINT, SE_DATA_TYPE_STRING,
&se_val) == 0 && se_val.value.sv_string != NULL) {
if (strcmp(se_val.value.sv_string, DR_HINT_INSERT)
== 0) {
state = HPS_PRESENT;
} else if (strcmp(se_val.value.sv_string,
DR_HINT_REMOVE) == 0) {
state = HPS_ABSENT;
}
}
if ((state == HPS_UNKNOWN) || (state = HPS_PRESENT))
state = disk_ap_state_to_hotplug_state(diskp);
}
return (state);
}
static void
disk_split_ap_path_sata(const char *ap_path, char *device, int *target)
{
char *p;
int n;
(void) strncpy(device, ap_path, MAXPATHLEN);
p = strrchr(device, ':');
dm_assert(p != NULL);
n = sscanf(p, ":%d", target);
dm_assert(n == 1);
*p = '\0';
}
static void
disk_split_ap_path_scsi(const char *ap_path, char *device, int *target)
{
char *p;
int n;
(void) strncpy(device, ap_path, MAXPATHLEN);
p = strrchr(device, ':');
dm_assert(p != NULL);
n = sscanf(p, ":dsk/c%*dt%dd%*d", target);
dm_assert(n == 1);
*strchr(device, ':') = '\0';
}
static void
disk_split_ap_path(const char *ap_path, char *device, int *target)
{
if (disk_ap_is_scsi(ap_path))
disk_split_ap_path_scsi(ap_path, device, target);
else
disk_split_ap_path_sata(ap_path, device, target);
}
static void
disk_split_device_path(const char *dev_path, char *device, int *target)
{
char *t, *p, *e;
(void) strncpy(device, dev_path, MAXPATHLEN);
e = t = strrchr(device, '/');
dm_assert(t != NULL);
t = strchr(t, '@');
dm_assert(t != NULL);
t += 1;
if ((p = strchr(t, ',')) != NULL)
*p = '\0';
*target = strtol(t, 0, 16);
*e = '\0';
}
static diskmon_t *
disk_match_by_device_path(diskmon_t *disklistp, const char *dev_path)
{
char dev_device[MAXPATHLEN];
int dev_target;
char ap_device[MAXPATHLEN];
int ap_target;
dm_assert(disklistp != NULL);
dm_assert(dev_path != NULL);
if (strncmp(dev_path, DEVICES_PREFIX, 8) == 0)
dev_path += 8;
disk_split_device_path(dev_path, (char *)&dev_device, &dev_target);
while (disklistp != NULL) {
char *app = (char *)dm_prop_lookup(disklistp->app_props,
DISK_AP_PROP_APID);
dm_assert(app != NULL);
if (strncmp(app, DEVICES_PREFIX, 8) == 0)
app += 8;
disk_split_ap_path(app, (char *)&ap_device, &ap_target);
if ((strcmp(dev_device, ap_device) == 0) &&
(dev_target == ap_target))
return (disklistp);
disklistp = disklistp->next;
}
return (NULL);
}
static diskmon_t *
disk_match_by_ap_id(diskmon_t *disklistp, const char *ap_id)
{
const char *disk_ap_id;
dm_assert(disklistp != NULL);
dm_assert(ap_id != NULL);
if (strncmp(ap_id, DEVICES_PREFIX, 8 ) == 0)
ap_id += 8;
while (disklistp != NULL) {
disk_ap_id = dm_prop_lookup(disklistp->app_props,
DISK_AP_PROP_APID);
dm_assert(disk_ap_id != NULL);
if (strcmp(disk_ap_id, ap_id) == 0)
return (disklistp);
disklistp = disklistp->next;
}
return (NULL);
}
static diskmon_t *
disk_match_by_target_id(diskmon_t *disklistp, const char *target_path)
{
const char *disk_ap_id;
char match_device[MAXPATHLEN];
int match_target;
char ap_device[MAXPATHLEN];
int ap_target;
if (strncmp(target_path, DEVICES_PREFIX, 8) == 0)
target_path += 8;
disk_split_ap_path(target_path, (char *)&match_device, &match_target);
while (disklistp != NULL) {
disk_ap_id = dm_prop_lookup(disklistp->app_props,
DISK_AP_PROP_APID);
dm_assert(disk_ap_id != NULL);
disk_split_ap_path(disk_ap_id, (char *)&ap_device, &ap_target);
if ((match_target == ap_target) &&
(strcmp(match_device, ap_device) == 0))
return (disklistp);
disklistp = disklistp->next;
}
return (NULL);
}
static diskmon_t *
match_sysevent_to_disk(diskmon_t *disklistp, sysevent_t *evp)
{
diskmon_t *dmp = NULL;
sysevent_value_t se_val;
char *class_name = sysevent_get_class_name(evp);
char *subclass = sysevent_get_subclass_name(evp);
se_val.value.sv_string = NULL;
if (strcmp(class_name, EC_DEVFS) == 0) {
if (sysevent_lookup_attr(evp, DEVFS_PATHNAME,
SE_DATA_TYPE_STRING, &se_val) == 0 &&
se_val.value.sv_string != NULL) {
dmp = disk_match_by_device_path(disklistp,
se_val.value.sv_string);
}
} else if (strcmp(class_name, EC_DR) == 0 &&
strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) {
if (sysevent_lookup_attr(evp, DR_AP_ID, SE_DATA_TYPE_STRING,
&se_val) == 0 && se_val.value.sv_string != NULL) {
dmp = disk_match_by_ap_id(disklistp,
se_val.value.sv_string);
}
} else if (strcmp(class_name, EC_DR) == 0 &&
strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0) {
if (sysevent_lookup_attr(evp, DR_TARGET_ID,
SE_DATA_TYPE_STRING, &se_val) == 0 &&
se_val.value.sv_string != NULL) {
dmp = disk_match_by_target_id(disklistp,
se_val.value.sv_string);
}
}
if (se_val.value.sv_string)
log_msg(MM_HPMGR, "match_sysevent_to_disk: device/ap: %s\n",
se_val.value.sv_string);
return (dmp);
}
static void
dm_process_sysevent(sysevent_t *dupev)
{
char *class_name;
char *pub;
char *subclass = sysevent_get_subclass_name(dupev);
diskmon_t *diskp;
class_name = sysevent_get_class_name(dupev);
log_msg(MM_HPMGR, "****EVENT: %s %s (by %s)\n", class_name,
subclass,
((pub = sysevent_get_pub_name(dupev)) != NULL) ? pub : "UNKNOWN");
if (pub)
free(pub);
if (strcmp(class_name, EC_PLATFORM) == 0 &&
strcmp(subclass, ESC_PLATFORM_SP_RESET) == 0) {
if (dm_platform_resync() != 0)
log_warn("failed to resync SP platform\n");
sysevent_free(dupev);
return;
}
if ((diskp = match_sysevent_to_disk(config_data->disk_list, dupev))
!= NULL) {
dm_state_change(diskp, disk_sysev_to_state(diskp, dupev));
}
sysevent_free(dupev);
}
static void
dm_fmd_sysevent_thread(void *queuep)
{
qu_t *qp = (qu_t *)queuep;
sysevent_event_t *sevevp;
dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
if (g_sysev_thread_state != TS_EXIT_REQUESTED)
g_sysev_thread_state = TS_RUNNING;
(void) pthread_cond_broadcast(&g_event_handler_cond);
dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
while (g_sysev_thread_state != TS_EXIT_REQUESTED) {
if ((sevevp = (sysevent_event_t *)queue_remove(qp)) == NULL)
continue;
dm_process_sysevent(sevevp->evp);
free_sysevent_event(sevevp);
}
dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
g_sysev_thread_state = TS_EXITED;
(void) pthread_cond_broadcast(&g_event_handler_cond);
dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
log_msg(MM_HPMGR, "FMD sysevent handler thread exiting...");
}
static sysevent_event_t *
new_sysevent_event(sysevent_t *ev)
{
sysevent_event_t *sevevp = malloc(sizeof (sysevent_event_t));
sevevp->evp = ev;
return (sevevp);
}
static void
free_sysevent_event(void *p)
{
free(p);
}
static void
event_handler(sysevent_t *ev)
{
sysevent_t *dupev = sysevent_dup(ev);
queue_add(g_sysev_queue, new_sysevent_event(dupev));
}
static void
fini_sysevents(void)
{
sysevent_unsubscribe_event(sysevent_handle, EC_ALL);
}
static int
init_sysevents(void)
{
int rv = 0;
const char *devfs_subclasses[] = {
ESC_DEVFS_DEVI_ADD,
ESC_DEVFS_DEVI_REMOVE
};
const char *dr_subclasses[] = {
ESC_DR_AP_STATE_CHANGE,
ESC_DR_TARGET_STATE_CHANGE
};
const char *platform_subclasses[] = {
ESC_PLATFORM_SP_RESET
};
if ((sysevent_handle = sysevent_bind_handle(event_handler)) == NULL) {
rv = errno;
log_err("Could not initialize the hotplug manager ("
"sysevent_bind_handle failure");
}
if (sysevent_subscribe_event(sysevent_handle, EC_DEVFS,
devfs_subclasses,
sizeof (devfs_subclasses)/sizeof (devfs_subclasses[0])) != 0) {
log_err("Could not initialize the hotplug manager "
"sysevent_subscribe_event(event class = EC_DEVFS) "
"failure");
rv = -1;
} else if (sysevent_subscribe_event(sysevent_handle, EC_DR,
dr_subclasses,
sizeof (dr_subclasses)/sizeof (dr_subclasses[0])) != 0) {
log_err("Could not initialize the hotplug manager "
"sysevent_subscribe_event(event class = EC_DR) "
"failure");
fini_sysevents();
rv = -1;
} else if (sysevent_subscribe_event(sysevent_handle, EC_PLATFORM,
platform_subclasses,
sizeof (platform_subclasses)/sizeof (platform_subclasses[0]))
!= 0) {
log_err("Could not initialize the hotplug manager "
"sysevent_subscribe_event(event class = EC_PLATFORM) "
"failure");
fini_sysevents();
rv = -1;
}
return (rv);
}
static void
stdfree(void *p, size_t sz)
{
free(p);
}
hotplug_mgr_init_err_t
init_hotplug_manager()
{
g_sysev_queue = new_queue(B_TRUE, malloc, stdfree, free_sysevent_event);
dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
g_sysev_tid = fmd_thr_create(g_fm_hdl, dm_fmd_sysevent_thread,
g_sysev_queue);
while (g_sysev_thread_state != TS_RUNNING)
(void) pthread_cond_wait(&g_event_handler_cond,
&g_event_handler_lock);
dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
if (init_sysevents() != 0) {
log_warn_e("Error initializing sysevents");
return (HPM_ERR_SYSEVENT_INIT);
}
return (0);
}
void
cleanup_hotplug_manager()
{
fini_sysevents();
dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
g_sysev_thread_state = TS_EXIT_REQUESTED;
queue_add(g_sysev_queue, NULL);
while (g_sysev_thread_state != TS_EXITED)
(void) pthread_cond_wait(&g_event_handler_cond,
&g_event_handler_lock);
dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
(void) pthread_join(g_sysev_tid, NULL);
fmd_thr_destroy(g_fm_hdl, g_sysev_tid);
queue_free(&g_sysev_queue);
g_sysev_thread_state = TS_NOT_RUNNING;
}