root/usr/src/lib/sun_fc/common/FCSyseventBridge.cc
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "FCSyseventBridge.h"
#include "Exceptions.h"
#include "Trace.h"
#include "AdapterAddEvent.h"
#include "AdapterEvent.h"
#include "AdapterPortEvent.h"
#include "AdapterDeviceEvent.h"
#include "TargetEvent.h"
#include "sun_fc.h"
#include <libnvpair.h>
#include <iostream>
#include <climits>

using namespace std;

FCSyseventBridge* FCSyseventBridge::_instance = NULL;

FCSyseventBridge* FCSyseventBridge::getInstance() {
    Trace log("FCSyseventBridge::getInstance");
    if (_instance == NULL) {
        _instance = new FCSyseventBridge();
    }
    return (_instance);

}


void FCSyseventBridge::addListener(AdapterAddEventListener *listener) {
    lock();
    try {
        adapterAddEventListeners.insert(adapterAddEventListeners.begin(),
                listener);
        validateRegistration();
        unlock();
    } catch (...) {
        unlock();
        throw;
    }
}
void FCSyseventBridge::addListener(AdapterEventListener *listener, HBA *hba) {
    lock();
    try {
        adapterEventListeners.insert(adapterEventListeners.begin(), listener);
        validateRegistration();
        unlock();
    } catch (...) {
        unlock();
        throw;
    }
}
void FCSyseventBridge::addListener(AdapterPortEventListener *listener,
            HBAPort *port) {
    lock();
    try {
        adapterPortEventListeners.insert(adapterPortEventListeners.begin(),
                listener);
        validateRegistration();
        unlock();
    } catch (...) {
        unlock();
        throw;
    }
}
void FCSyseventBridge::addListener(AdapterDeviceEventListener *listener,
    HBAPort *port) {
        lock();
        try {
                adapterDeviceEventListeners.insert(adapterDeviceEventListeners.begin(),
                    listener);
                validateRegistration();
                unlock();
        } catch (...) {
                unlock();
                throw;
        }
}
void FCSyseventBridge::addListener(TargetEventListener *listener,
            HBAPort *port, uint64_t targetWWN, bool filter) {
    lock();
    try {
        targetEventListeners.insert(targetEventListeners.begin(), listener);
        validateRegistration();
        unlock();
    } catch (...) {
        unlock();
        throw;
    }
}

void FCSyseventBridge::removeListener(AdapterAddEventListener *listener) {
    lock();
    try {
        typedef vector<AdapterAddEventListener *>::iterator Iter;
        for (Iter tmp = adapterAddEventListeners.begin();
                tmp != adapterAddEventListeners.end(); tmp++) {
            if (*tmp == listener) {
                adapterAddEventListeners.erase(tmp);
                unlock();
                return;
            }
        }
        throw InvalidHandleException();
    } catch (...) {
        unlock();
        throw;
    }
}

void FCSyseventBridge::removeListener(AdapterEventListener *listener) {
    lock();
    try {
        typedef vector<AdapterEventListener *>::iterator Iter;
        for (Iter tmp = adapterEventListeners.begin();
                tmp != adapterEventListeners.end(); tmp++) {
            if (*tmp == listener) {
                adapterEventListeners.erase(tmp);
                unlock();
                return;
            }
        }
        throw InvalidHandleException();
    } catch (...) {
        unlock();
        throw;
    }
}

void FCSyseventBridge::removeListener(AdapterPortEventListener *listener) {
    lock();
    try {
        typedef vector<AdapterPortEventListener *>::iterator Iter;
        for (Iter tmp = adapterPortEventListeners.begin();
                tmp != adapterPortEventListeners.end(); tmp++) {
            if (*tmp == listener) {
                adapterPortEventListeners.erase(tmp);
                unlock();
                return;
            }
        }
        throw InvalidHandleException();
    } catch (...) {
        unlock();
        throw;
    }
}

void FCSyseventBridge::removeListener(AdapterDeviceEventListener *listener) {
        lock();
        try {
                typedef vector<AdapterDeviceEventListener *>::iterator Iter;
                for (Iter tmp = adapterDeviceEventListeners.begin();
                    tmp != adapterDeviceEventListeners.end(); tmp++) {
                        if (*tmp == listener) {
                                adapterDeviceEventListeners.erase(tmp);
                                unlock();
                                return;
                        }
                }
                throw InvalidHandleException();
        } catch (...) {
                unlock();
                throw;
        }
}
 
void FCSyseventBridge::removeListener(TargetEventListener *listener) {
    lock();
    try {
        typedef vector<TargetEventListener *>::iterator Iter;
        for (Iter tmp = targetEventListeners.begin();
                tmp != targetEventListeners.end(); tmp++) {
            if (*tmp == listener) {
                targetEventListeners.erase(tmp);
                unlock();
                return;
            }
        }
        throw InvalidHandleException();
    } catch (...) {
        unlock();
        throw;
    }
}

extern "C" void static_dispatch(sysevent_t *ev) {
    Trace log("static_dispatch");
    FCSyseventBridge::getInstance()->dispatch(ev);
}

void FCSyseventBridge::dispatch(sysevent_t *ev) {
    Trace log("FCSyseventBridge::dispatch");
    nvlist_t                *list = NULL;
    hrtime_t                    when;

    if (ev == NULL) {
        log.debug("Null event.");
        return;
    }

    if (sysevent_get_attr_list(ev, &list) || list == NULL) {
        log.debug("Empty event.");
        return;
    }

    string eventVendor = sysevent_get_vendor_name(ev);
    string eventPublisher = sysevent_get_pub_name(ev);
    string eventClass = sysevent_get_class_name(ev);
    string eventSubClass = sysevent_get_subclass_name(ev);

    sysevent_get_time(ev, &when);

    // Now that we know what type of event it is, handle it accordingly
    if (eventClass == "EC_sunfc") {

        // All events of this class type have instance and port-wwn for
        // the HBA port.
        uint32_t        instance;
        if (nvlist_lookup_uint32(list, (char *)"instance",
                &instance)) {
            log.genericIOError(
                "Improperly formed event: no instance field.");
            nvlist_free(list);
            return;
        }
        uchar_t         *rawPortWWN;
        uint32_t        rawPortWWNLength;

        if (nvlist_lookup_byte_array(list, (char *)"port-wwn",
                &rawPortWWN, &rawPortWWNLength)) {
            log.genericIOError(
                "Improperly formed event: no port-wwn field.");
            nvlist_free(list);
            return;
        }

        // Now deal with the specific details of each subclass type
        if (eventSubClass == "ESC_sunfc_port_offline") {

            // Create event instance
            AdapterPortEvent event(
                wwnConversion(rawPortWWN),
                AdapterPortEvent::OFFLINE,
                0);

            // Dispatch to interested parties.
            lock();
            try {
                typedef vector<AdapterPortEventListener *>::iterator Iter;
                for (Iter tmp = adapterPortEventListeners.begin();
                        tmp != adapterPortEventListeners.end(); tmp++) {
                    (*tmp)->dispatch(event);
                }
            } catch (...) {
                unlock();
                nvlist_free(list);
                throw;
            }
            unlock();

        } else if (eventSubClass == "ESC_sunfc_port_online") {

            // Create event instance
            AdapterPortEvent event(
                wwnConversion(rawPortWWN),
                AdapterPortEvent::ONLINE,
                0);

            // Dispatch to interested parties.
            lock();
            try {
                typedef vector<AdapterPortEventListener *>::iterator Iter;
                for (Iter tmp = adapterPortEventListeners.begin();
                        tmp != adapterPortEventListeners.end(); tmp++) {
                    (*tmp)->dispatch(event);
                }
            } catch (...) {
                unlock();
                nvlist_free(list);
                throw;
            }
            unlock();

        } else if (eventSubClass == "ESC_sunfc_device_online") {
                AdapterDeviceEvent event(
                    wwnConversion(rawPortWWN),
                    AdapterDeviceEvent::ONLINE,
                    0);
                lock();
                try {
                        typedef vector<AdapterDeviceEventListener *>::iterator Iter;
                        for (Iter tmp = adapterDeviceEventListeners.begin();
                            tmp != adapterDeviceEventListeners.end(); tmp++) {
                                (*tmp)->dispatch(event);
                        }
                } catch (...) {
                        unlock();
                        nvlist_free(list);
                        throw;
                }
                unlock();

        } else if (eventSubClass == "ESC_sunfc_device_offline") {
                AdapterDeviceEvent event(
                    wwnConversion(rawPortWWN),
                    AdapterDeviceEvent::OFFLINE,
                    0);
                lock();
                try {
                        typedef vector<AdapterDeviceEventListener *>::iterator Iter;
                        for (Iter tmp = adapterDeviceEventListeners.begin();
                            tmp != adapterDeviceEventListeners.end(); tmp++) {
                                (*tmp)->dispatch(event);
                        }
                } catch (...) {
                        unlock();
                        nvlist_free(list);
                        throw;
                }
                unlock();

        } else if (eventSubClass == "ESC_sunfc_port_rscn") {
            /*
             * RSCNs are a little tricky.  There can be multiple
             * affected page properties, each numbered.  To make sure
             * we get them all, we loop through all properties
             * in the nvlist and if their name begins with "affected_page_"
             * then we send an event for them.
             */
            uint32_t    affected_page;
            nvpair_t    *attr = NULL;
            for (attr = nvlist_next_nvpair(list, NULL);
                    attr != NULL;
                    attr = nvlist_next_nvpair(list, attr)) {
                string name = nvpair_name(attr);
                if (name.find("affected_page_") != name.npos) {

                    if (nvpair_value_uint32(attr, &affected_page)) {
                        log.genericIOError(
                            "Improperly formed event: "
                            "corrupt affected_page field");
                        continue;
                    }
                    // Create event instance
                    AdapterPortEvent event(
                        wwnConversion(rawPortWWN),
                        AdapterPortEvent::FABRIC,
                        affected_page);

                    // Dispatch to interested parties.
                    lock();
                    typedef vector<AdapterPortEventListener *>::iterator Iter;
                    try {
                        for (Iter tmp = adapterPortEventListeners.begin();
                                tmp != adapterPortEventListeners.end(); tmp++) {
                            (*tmp)->dispatch(event);
                        }
                    } catch (...) {
                        unlock();
                        nvlist_free(list);
                        throw;
                    }
                    unlock();
                }
            }
        } else if (eventSubClass == "ESC_sunfc_target_add") {
            uchar_t     *rawTargetPortWWN;
            uint32_t    rawTargetPortWWNLength;

            if (nvlist_lookup_byte_array(list, (char *)"target-port-wwn",
                    &rawTargetPortWWN, &rawTargetPortWWNLength)) {
                log.genericIOError(
                    "Improperly formed event: no target-port-wwn field.");
                nvlist_free(list);
                return;
            }

            // Create event instance
            AdapterPortEvent event(
                wwnConversion(rawPortWWN),
                AdapterPortEvent::NEW_TARGETS,
                0);

            // Dispatch to interested parties.
            lock();
            try {
                typedef vector<AdapterPortEventListener *>::iterator Iter;
                for (Iter tmp = adapterPortEventListeners.begin();
                        tmp != adapterPortEventListeners.end(); tmp++) {
                    (*tmp)->dispatch(event);
                }
            } catch (...) {
                unlock();
                nvlist_free(list);
                throw;
            }
            unlock();
        } else if (eventSubClass == "ESC_sunfc_target_remove") {
            uchar_t     *rawTargetPortWWN;
            uint32_t    rawTargetPortWWNLength;

            if (nvlist_lookup_byte_array(list, (char *)"target-port-wwn",
                    &rawTargetPortWWN, &rawTargetPortWWNLength)) {
                log.genericIOError(
                    "Improperly formed event: no target-port-wwn field.");
                nvlist_free(list);
                return;
            }
            // Create event instance
            TargetEvent event(
                wwnConversion(rawPortWWN),
                wwnConversion(rawTargetPortWWN),
                TargetEvent::REMOVED);

            // Dispatch to interested parties.
            lock();
            try {
                typedef vector<TargetEventListener *>::iterator Iter;
                for (Iter tmp = targetEventListeners.begin();
                        tmp != targetEventListeners.end(); tmp++) {
                    (*tmp)->dispatch(event);
                }
            } catch (...) {
                unlock();
                nvlist_free(list);
                throw;
            }
            unlock();
        } else if (eventSubClass == "ESC_sunfc_port_attach") {
            // Create event instance
            AdapterAddEvent event(wwnConversion(rawPortWWN));
            // Dispatch to interested parties.
            lock();
            try {
                typedef vector<AdapterAddEventListener *>::iterator Iter;
                for (Iter tmp = adapterAddEventListeners.begin();
                        tmp != adapterAddEventListeners.end(); tmp++) {
                    (*tmp)->dispatch(event);
                }
            } catch (...) {
                unlock();
                nvlist_free(list);
                throw;
            }
            unlock();
        } else if (eventSubClass == "ESC_sunfc_port_detach") {
            // Technically, we should probably try to coalesce
            // all detach events for the same multi-ported adapter
            // and only send one event to the client, but for now,
            // we'll just blindly send duplicates.

            // Create event instance
            AdapterEvent event(
                wwnConversion(rawPortWWN),
                AdapterEvent::REMOVE);

            // Dispatch to interested parties.
            lock();
            try {
                typedef vector<AdapterEventListener *>::iterator Iter;
                for (Iter tmp = adapterEventListeners.begin();
                        tmp != adapterEventListeners.end(); tmp++) {
                    (*tmp)->dispatch(event);
                }
            } catch (...) {
                unlock();
                nvlist_free(list);
                throw;
            }
            unlock();

        } else {
            log.genericIOError(
                    "Unrecognized subclass \"%s\": Ignoring event",
                    eventSubClass.c_str());
        }
    } else {
        // This should not happen, as we only asked for specific classes.
        log.genericIOError(
                "Unrecognized class \"%s\": Ignoring event",
                eventClass.c_str());
    }
    nvlist_free(list);
}

void FCSyseventBridge::validateRegistration() {
    Trace log("FCSyseventBridge::validateRegistration");
    uint64_t count = 0;
    count = adapterAddEventListeners.size() +
            adapterEventListeners.size() +
            adapterPortEventListeners.size() +
            targetEventListeners.size();
    if (count == 1) {
        handle = sysevent_bind_handle(static_dispatch);
        if (handle == NULL) {
            log.genericIOError(
                "Unable to bind sysevent handle.");
            return;
        }
        const char *subclass_list[9] = {
                "ESC_sunfc_port_attach",
                "ESC_sunfc_port_detach",
                "ESC_sunfc_port_offline",
                "ESC_sunfc_port_online",
                "ESC_sunfc_port_rscn",
                "ESC_sunfc_target_add",
                "ESC_sunfc_target_remove",
                "ESC_sunfc_device_online",
                "ESC_sunfc_device_offline"
            };
        if (sysevent_subscribe_event(handle,
                "EC_sunfc", (const char **)subclass_list, 9)) {
            log.genericIOError(
                "Unable to subscribe to sun_fc events.");
            sysevent_unbind_handle(handle);
            handle = NULL;
        }
    } else if (count == 0 && handle != NULL) {
        // Remove subscription
        sysevent_unbind_handle(handle);
        handle == NULL;
    } // Else do nothing
}

int32_t FCSyseventBridge::getMaxListener() {
    return (INT_MAX);
}