/*
 * sel.c
 *
 * MontaVista IPMI code for handling the system event log
 *
 * Author: MontaVista Software, Inc.
 *         Corey Minyard <minyard@mvista.com>
 *         source@mvista.com
 *
 * Copyright 2002,2003,2004,2005 MontaVista Software Inc.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 *
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <string.h>
#include <stdio.h>

#include <OpenIPMI/ipmiif.h>
#include <OpenIPMI/ipmi_msgbits.h>
#include <OpenIPMI/ipmi_err.h>

#include <OpenIPMI/internal/opq.h>
#include <OpenIPMI/internal/ilist.h>
#include <OpenIPMI/internal/ipmi_int.h>
#include <OpenIPMI/internal/ipmi_event.h>
#include <OpenIPMI/internal/ipmi_sel.h>
#include <OpenIPMI/internal/ipmi_domain.h>
#include <OpenIPMI/internal/ipmi_mc.h>

#define MAX_SEL_FETCH_RETRIES 10

typedef struct sel_fetch_handler_s
{
    ipmi_sel_info_t     *sel;
    ipmi_sels_fetched_t handler;
    void                *cb_data;
    int                 rv;

    struct sel_fetch_handler_s *next;
} sel_fetch_handler_t;

/* Holds an event in the list of events. */
typedef struct sel_event_holder_s
{
    unsigned int deleted : 1;
    unsigned int cancelled : 1;
    unsigned int refcount;
    ipmi_event_t *event;
} sel_event_holder_t;

static sel_event_holder_t *
sel_event_holder_alloc(void)
{
    sel_event_holder_t *holder = ipmi_mem_alloc(sizeof(*holder));

    if (!holder)
	return NULL;
    holder->deleted = 0;
    holder->cancelled = 0;
    holder->refcount = 1;
    holder->event = NULL;
    return holder;
}

static void
sel_event_holder_get(sel_event_holder_t *holder)
{
    holder->refcount++;
}

static void
sel_event_holder_put(sel_event_holder_t *holder)
{
    holder->refcount--;
    if (holder->refcount == 0) {
	ipmi_event_free(holder->event);
	ipmi_mem_free(holder);
    }
}

typedef struct sel_clear_req_s
{
    ipmi_event_t *last_event;
    struct sel_clear_req_s *next;
} sel_clear_req_t;

#define SEL_NAME_LEN (IPMI_MC_NAME_LEN + 32)

struct ipmi_sel_info_s
{
    ipmi_mcid_t mc;

    /* LUN we are attached with. */
    int         lun;

    uint8_t  major_version;
    uint8_t  minor_version;
    uint16_t entries;
    uint32_t last_addition_timestamp;
    uint32_t last_erase_timestamp;

    /* These are here to store the timestamps until the operation
       completes.  Successfully.  Otherwise, if we restart the fetch,
       it will have the timestamps set wrong and won't do the
       fetch. */
    uint32_t curr_addition_timestamp;
    uint32_t curr_erase_timestamp;

    uint16_t free_bytes;
    unsigned int overflow : 1;
    unsigned int supports_delete_sel : 1;
    unsigned int supports_partial_add_sel : 1;
    unsigned int supports_reserve_sel : 1;
    unsigned int supports_get_sel_allocation : 1;

    unsigned int fetched : 1;

    /* Has the SEL been destroyed?  This is here because of race
       conditions in shutdown.  If we are currently in the process of
       fetching SELs, we will allow a destroy operation to complete,
       but we don't actually destroy the data until the SEL fetch
       reaches a point were it can be stopped safely. */
    unsigned int destroyed : 1;
    unsigned int in_destroy : 1;

    /* Is a fetch in the queue or currently running? */
    unsigned int in_fetch : 1;

    /* Something to call when the destroy is complete. */
    ipmi_sel_destroyed_t destroy_handler;
    void                 *destroy_cb_data;


    /* When fetching the data in event-driven mode, these are the
       variables that track what is going on. */
    unsigned int           curr_rec_id;
    unsigned int           next_rec_id;
    unsigned int           reservation;
    int                    sels_changed;
    unsigned int           fetch_retry_count;
    sel_fetch_handler_t    *fetch_handlers;

    /* When we start a fetch, we start with this id.  This is the last
       one we successfully fetches (or 0 if it is not valid) so we can
       find the next valid id to fetch. */
    unsigned int           start_rec_id;
    unsigned char          start_rec_id_data[14];

    /* A lock, primarily for handling race conditions fetching the data. */
    os_hnd_lock_t *sel_lock;

    os_handler_t *os_hnd;

    /* This is the actual list of events and the number of non-deleted
       events and the number of deleted events.  Note that events may
       contain more items than num_sels, num_sels only counts the
       number of non-deleted events in the list.  del_sels+num_sels
       should be the number of events. */
    ilist_t      *events;
    unsigned int num_sels;
    unsigned int del_sels;

    /* We serialize operations through here, since we are dealing with
       a locked resource. */
    opq_t *opq;

    ipmi_sel_new_event_handler_cb new_event_handler;
    void                          *new_event_cb_data;

    char name[SEL_NAME_LEN];

    ipmi_domain_stat_t *sel_good_scans;
    ipmi_domain_stat_t *sel_scan_lost_reservation;
    ipmi_domain_stat_t *sel_fail_scan_lost_reservation;
    ipmi_domain_stat_t *sel_received_events;
    ipmi_domain_stat_t *sel_fetch_errors;

    ipmi_domain_stat_t *sel_good_clears;
    ipmi_domain_stat_t *sel_clear_lost_reservation;
    ipmi_domain_stat_t *sel_clear_errors;

    ipmi_domain_stat_t *sel_good_deletes;
    ipmi_domain_stat_t *sel_delete_lost_reservation;
    ipmi_domain_stat_t *sel_fail_delete_lost_reservation;
    ipmi_domain_stat_t *sel_delete_errors;
};

static inline void sel_lock(ipmi_sel_info_t *sel)
{
    if (sel->os_hnd->lock)
	sel->os_hnd->lock(sel->os_hnd, sel->sel_lock);
}

static inline void sel_unlock(ipmi_sel_info_t *sel)
{
    if (sel->os_hnd->lock)
	sel->os_hnd->unlock(sel->os_hnd, sel->sel_lock);
}

static void
free_event(ilist_iter_t *iter, void *item, void *cb_data)
{
    sel_event_holder_t *holder = item;
    sel_event_holder_put(holder);
}

static void
free_events(ilist_t *events)
{
    ilist_iter(events, free_event, NULL);
}

static int
recid_search_cmp(void *item, void *cb_data)
{
    sel_event_holder_t *holder = item;
    unsigned int       recid = *((unsigned int *) cb_data);

    return ipmi_event_get_record_id(holder->event) == recid;
}
static sel_event_holder_t *
find_event(ilist_t *list, unsigned int recid)
{
    return ilist_search(list, recid_search_cmp, &recid);
}

static int
event_cmp(ipmi_event_t *event1, ipmi_event_t *event2)
{
    int rv;
    unsigned int        record_id1, record_id2;
    unsigned int        type1, type2;
    unsigned int        data_len1, data_len2;
    const unsigned char *ptr1, *ptr2;

    rv = ipmi_cmp_mc_id(ipmi_event_get_mcid(event1),
			ipmi_event_get_mcid(event2));
    if (rv)
	return rv;
    record_id1 = ipmi_event_get_record_id(event1);
    record_id2 = ipmi_event_get_record_id(event2);
    if (record_id1 > record_id2)
	return 1;
    if (record_id1 < record_id2)
	return -1;
    type1 = ipmi_event_get_type(event1);
    type2 = ipmi_event_get_type(event2);
    if (type1 > type2)
	return 1;
    if (type1 < type2)
	return -1;
    data_len1 = ipmi_event_get_data_len(event1);
    data_len2 = ipmi_event_get_data_len(event2);
    if (data_len1 > data_len2)
	return 1;
    if (data_len1 < data_len2)
	return -1;
    ptr1 = ipmi_event_get_data_ptr(event1);
    ptr2 = ipmi_event_get_data_ptr(event2);
    return memcmp(ptr1, ptr2, data_len1);
}

int
ipmi_sel_alloc(ipmi_mc_t       *mc,
	       unsigned int    lun,
	       ipmi_sel_info_t **new_sel)
{
    ipmi_sel_info_t *sel = NULL;
    int             rv = 0;
    ipmi_domain_t   *domain;
    int             i;

    CHECK_MC_LOCK(mc);

    domain = ipmi_mc_get_domain(mc);

    if (lun >= 4)
	return EINVAL;

    sel = ipmi_mem_alloc(sizeof(*sel));
    if (!sel) {
	rv = ENOMEM;
	goto out;
    }
    memset(sel, 0, sizeof(*sel));

    i = ipmi_mc_get_name(mc, sel->name, sizeof(sel->name));
    snprintf(sel->name+i, sizeof(sel->name)-i, "(sel)");

    sel->events = alloc_ilist();
    if (!sel->events) {
	rv = ENOMEM;
	goto out;
    }

    sel->mc = ipmi_mc_convert_to_id(mc);
    sel->destroyed = 0;
    sel->in_destroy = 0;
    sel->os_hnd = ipmi_domain_get_os_hnd(domain);
    sel->sel_lock = NULL;
    sel->fetched = 0;
    sel->in_fetch = 0;
    sel->num_sels = 0;
    sel->del_sels = 0;
    sel->destroy_handler = NULL;
    sel->lun = lun;
    sel->fetch_handlers = NULL;
    sel->new_event_handler = NULL;

    sel->opq = opq_alloc(sel->os_hnd);
    if (!sel->opq) {
	rv = ENOMEM;
	goto out;
    }

    if (sel->os_hnd->create_lock) {
	rv = sel->os_hnd->create_lock(sel->os_hnd, &sel->sel_lock);
	if (rv)
	    goto out;
    }

 out:
    if (rv) {
	if (sel) {
	    if (sel->events)
		free_ilist(sel->events);
	    if (sel->opq)
		opq_destroy(sel->opq);
	    if (sel->sel_lock)
		sel->os_hnd->destroy_lock(sel->os_hnd, sel->sel_lock);
	    ipmi_mem_free(sel);
	}
    } else {
	ipmi_domain_stat_register(domain, "sel_good_scans",
				  i_ipmi_mc_name(mc),
				  &sel->sel_good_scans);
	ipmi_domain_stat_register(domain, "sel_scan_lost_reservation",
				  i_ipmi_mc_name(mc),
				  &sel->sel_scan_lost_reservation);
	ipmi_domain_stat_register(domain, "sel_fail_scan_lost_reservation",
				  i_ipmi_mc_name(mc),
				  &sel->sel_fail_scan_lost_reservation);
	ipmi_domain_stat_register(domain, "sel_received_events",
				  i_ipmi_mc_name(mc),
				  &sel->sel_received_events);
	ipmi_domain_stat_register(domain, "sel_fetch_errors",
				  i_ipmi_mc_name(mc),
				  &sel->sel_fetch_errors);
	ipmi_domain_stat_register(domain, "sel_good_clears",
				  i_ipmi_mc_name(mc),
				  &sel->sel_good_clears);
	ipmi_domain_stat_register(domain, "sel_clear_lost_reservation",
				  i_ipmi_mc_name(mc),
				  &sel->sel_clear_lost_reservation);
	ipmi_domain_stat_register(domain, "sel_clear_errors",
				  i_ipmi_mc_name(mc),
				  &sel->sel_clear_errors);
	ipmi_domain_stat_register(domain, "sel_good_deletes",
				  i_ipmi_mc_name(mc),
				  &sel->sel_good_deletes);
	ipmi_domain_stat_register(domain, "sel_delete_lost_reservation",
				  i_ipmi_mc_name(mc),
				  &sel->sel_delete_lost_reservation);
	ipmi_domain_stat_register(domain, "sel_fail_delete_lost_reservation",
				  i_ipmi_mc_name(mc),
				  &sel->sel_fail_delete_lost_reservation);
	ipmi_domain_stat_register(domain, "sel_delete_errors",
				  i_ipmi_mc_name(mc),
				  &sel->sel_delete_errors);
	*new_sel = sel;
    }
    return rv;
}

static void
internal_destroy_sel(ipmi_sel_info_t *sel)
{
    sel->in_destroy = 1;

    /* We don't have to have a valid ipmi to destroy an SEL, the are
       designed to live after the ipmi has been destroyed. */

    if (sel->events) {
	free_events(sel->events);
	free_ilist(sel->events);
    }
    sel_unlock(sel);

    if (sel->opq)
	opq_destroy(sel->opq);

    if (sel->sel_lock)
	sel->os_hnd->destroy_lock(sel->os_hnd, sel->sel_lock);

    if (sel->sel_good_scans)
	ipmi_domain_stat_put(sel->sel_good_scans);
    if (sel->sel_scan_lost_reservation)
	ipmi_domain_stat_put(sel->sel_scan_lost_reservation);
    if (sel->sel_fail_scan_lost_reservation)
	ipmi_domain_stat_put(sel->sel_fail_scan_lost_reservation);
    if (sel->sel_received_events)
	ipmi_domain_stat_put(sel->sel_received_events);
    if (sel->sel_fetch_errors)
	ipmi_domain_stat_put(sel->sel_fetch_errors);
    if (sel->sel_good_clears)
	ipmi_domain_stat_put(sel->sel_good_clears);
    if (sel->sel_clear_lost_reservation)
	ipmi_domain_stat_put(sel->sel_clear_lost_reservation);
    if (sel->sel_clear_errors)
	ipmi_domain_stat_put(sel->sel_clear_errors);
    if (sel->sel_good_deletes)
	ipmi_domain_stat_put(sel->sel_good_deletes);
    if (sel->sel_delete_lost_reservation)
	ipmi_domain_stat_put(sel->sel_delete_lost_reservation);
    if (sel->sel_fail_delete_lost_reservation)
	ipmi_domain_stat_put(sel->sel_fail_delete_lost_reservation);
    if (sel->sel_delete_errors)
	ipmi_domain_stat_put(sel->sel_delete_errors);

    /* Do this after we have gotten rid of all external dependencies,
       but before it is free. */
    if (sel->destroy_handler)
	sel->destroy_handler(sel, sel->destroy_cb_data);

    ipmi_mem_free(sel);
}

int
ipmi_sel_destroy(ipmi_sel_info_t      *sel,
		 ipmi_sel_destroyed_t handler,
		 void                 *cb_data)
{
    /* We don't need the read lock, because the sels are stand-alone
       after they are created (except for fetching SELs, of course). */
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }
    sel->destroyed = 1;
    sel->destroy_handler = handler;
    sel->destroy_cb_data = cb_data;
    if (opq_stuff_in_progress(sel->opq)) {
	/* It's currently doing something with callbacks, so let it be
           destroyed in the handler, since we can't cancel the handler
           or operation. */
	sel_unlock(sel);
	return 0;
    }

    /* This unlocks the lock. */
    internal_destroy_sel(sel);
    return 0;
}

/* This should be called with the sel locked.  It will unlock the sel
   before returning. */
static void
fetch_complete(ipmi_sel_info_t *sel, int err, int do_opq_done)
{
    sel_fetch_handler_t *elem, *next;
    int                 sels_changed;
    unsigned int        num_sels;

    if (sel->in_destroy)
	goto out;

    sels_changed = sel->sels_changed;
    num_sels = sel->num_sels;

    elem = sel->fetch_handlers;
    sel->fetch_handlers = NULL;
    sel->fetched = 1;
    sel->in_fetch = 0;
    sel_unlock(sel);

    while (elem) {
	next = elem->next;
	elem->next = NULL;
	if (elem->handler)
	    elem->handler(sel,
		          err,
		          sels_changed,
		          num_sels,
		          elem->cb_data);
	ipmi_mem_free(elem);
	elem = next;
    }

    if (sel->destroyed) {
	sel_lock(sel);
	internal_destroy_sel(sel);
	/* Previous call releases lock. */
	return;
    }

    if (do_opq_done)
	opq_op_done(sel->opq);
    return;

 out:
    sel_unlock(sel);
}

static void
free_deleted_event(ilist_iter_t *iter, void *item, void *cb_data)
{
    sel_event_holder_t *holder = item;
    ipmi_sel_info_t    *sel = cb_data;

    if (holder->deleted) {
	ilist_delete(iter);
	holder->cancelled = 1;
	sel->del_sels--;
	sel_event_holder_put(holder);
    }
}

static void
free_deleted_events(ipmi_sel_info_t *sel)
{
    ilist_iter(sel->events, free_deleted_event, sel);
}

static void
handle_sel_clear(ipmi_mc_t  *mc,
		 ipmi_msg_t *rsp,
		 void       *rsp_data)
{
    sel_fetch_handler_t *elem = rsp_data;
    ipmi_sel_info_t     *sel = elem->sel;

    sel_lock(sel);
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_clear): "
		 "SEL info was destroyed while an operation was in"
		 " progress(1)", sel->name);
	fetch_complete(sel, ECANCELED, 1);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_clear): "
		 "MC went away while SEL op was in progress",
		 sel->name);
        fetch_complete(sel, ECANCELED, 1);
	goto out;
    }

    if (rsp->data[0] == 0) {
	if (sel->sel_good_clears)
	    ipmi_domain_stat_add(sel->sel_good_clears, 1);

	/* Success!  We can free the data. */
	free_deleted_events(sel);
	sel->del_sels = 0;
    } else if (rsp->data[0] == IPMI_INVALID_RESERVATION_CC) {
	if (sel->sel_clear_lost_reservation)
	    ipmi_domain_stat_add(sel->sel_clear_lost_reservation, 1);
    } else {
	if (sel->sel_clear_errors)
	    ipmi_domain_stat_add(sel->sel_clear_errors, 1);
    }

    fetch_complete(sel, 0, 1);
 out:
    return;
}

static int
send_sel_clear(sel_fetch_handler_t *elem, ipmi_mc_t *mc)
{
    ipmi_sel_info_t *sel = elem->sel;
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;

    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_CLEAR_SEL_CMD;
    cmd_msg.data_len = 6;
    ipmi_set_uint16(cmd_msg.data, sel->reservation);
    cmd_msg.data[2] = 'C';
    cmd_msg.data[3] = 'L';
    cmd_msg.data[4] = 'R';
    cmd_msg.data[5] = 0xaa;

    return ipmi_mc_send_command(mc, sel->lun, &cmd_msg,
				handle_sel_clear, elem);
}

static int start_fetch(void *cb_data, int shutdown);

static void
handle_sel_data(ipmi_mc_t  *mc,
		ipmi_msg_t *rsp,
		void       *rsp_data)
{
    sel_fetch_handler_t *elem = rsp_data;
    ipmi_sel_info_t     *sel = elem->sel;
    unsigned char       cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t          cmd_msg;
    int                 rv;
    int                 event_is_new = 0;
    ipmi_event_t        *del_event;
    unsigned int        record_id;
    ipmi_time_t         timestamp;
    sel_event_holder_t  *holder;


    sel_lock(sel);
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_data): "
		 "SEL info was destroyed while an operation was in"
		 " progress(2)", sel->name);
	fetch_complete(sel, ECANCELED, 1);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_data): "
		 "handle_sel_data: MC went away while SEL op was in progress",
		 sel->name);
        fetch_complete(sel, ECANCELED, 1);
	goto out;
    }
	
    if (rsp->data[0] == IPMI_INVALID_RESERVATION_CC) {
	/* We lost our reservation, restart the operation.  Only do
           this so many times, in order to guarantee that this
           completes.  Note that we do this so many times *per fetch*,
           we do not reset the counter if we get a successful
           operation.  This is because if this happens a lot during a
           fetch, there is heavy contention for the SEL and someone
           needs to drop out to allow everyone else to continue. */
	sel->fetch_retry_count++;
	if (sel->sel_scan_lost_reservation)
	    ipmi_domain_stat_add(sel->sel_scan_lost_reservation, 1);
	if (sel->fetch_retry_count > MAX_SEL_FETCH_RETRIES) {
	    if (sel->sel_fail_scan_lost_reservation)
		ipmi_domain_stat_add(sel->sel_fail_scan_lost_reservation, 1);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssel.c(handle_sel_data): "
		     "Too many lost reservations in SEL fetch",
		     sel->name);
	    fetch_complete(sel, EAGAIN, 1);
	    goto out;
	} else {
	    sel_unlock(sel);
	    start_fetch(elem, 0);
	    goto out;
	}
    }
    if ((rsp->data[0] == 0) && (rsp->data_len < 19)) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_data): "
		 "Received a short SEL data message",
		 sel->name);
	fetch_complete(sel, EINVAL, 1);
	goto out;
    }

    if ((rsp->data[0] != 0)
	|| ((sel->start_rec_id != 0) && (sel->start_rec_id == sel->curr_rec_id)
	    && (memcmp(sel->start_rec_id_data, rsp->data+5, 14) != 0)))
    {
	/* We got an error fetching the current id, or the current
	   id's data was for our "start" record and it doesn't match
	   the one we fetched, so it has changed.  We have to start
	   over or handle the error. */
	if (sel->start_rec_id != 0) {
	    /* If we get a fetch error and it is not a lost
	       reservation, it may be that another system deleted our
	       "current" record.  Start over from the beginning of the
	       SEL. */
	    sel->start_rec_id = 0;
	    sel->curr_rec_id = 0;
	    del_event = NULL;
	    goto start_request_sel_data;
	}
	if (sel->sel_fetch_errors)
	    ipmi_domain_stat_add(sel->sel_fetch_errors, 1);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_data): "
		 "IPMI error from SEL fetch: %x",
		 sel->name, rsp->data[0]);
	fetch_complete(sel, IPMI_IPMI_ERR_VAL(rsp->data[0]), 1);
	goto out;
    }

    sel->next_rec_id = ipmi_get_uint16(rsp->data+1);

    record_id = ipmi_get_uint16(rsp->data+3);

    if (rsp->data[5] < 0xe0)
	timestamp = ipmi_seconds_to_time(ipmi_get_uint32(rsp->data+6));
    else
	timestamp = -1;
    del_event = ipmi_event_alloc(ipmi_mc_convert_to_id(mc),
				 record_id,
				 rsp->data[5],
				 timestamp,
				 rsp->data+6,
				 13);
    if (!del_event) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_data): "
		 "Could not allocate event for SEL",
		 sel->name);
	fetch_complete(sel, ENOMEM, 1);
	goto out;
    }

    if ((timestamp > 0) && (timestamp < ipmi_mc_get_startup_SEL_time(mc)))
	ipmi_event_set_is_old(del_event, 1);

    holder = find_event(sel->events, record_id);
    if (!holder) {
	holder = sel_event_holder_alloc();
	if (!holder) {
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssel.c(handle_sel_data): "
		     "Could not allocate log information for SEL",
		     sel->name);
	    fetch_complete(sel, ENOMEM, 1);
	    goto out;
	}
	if (!ilist_add_tail(sel->events, holder, NULL)) {
	    ipmi_mem_free(holder);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssel.c(handle_sel_data): "
		     "Could not link log onto the log linked list",
		     sel->name);
	    fetch_complete(sel, ENOMEM, 1);
	    goto out;
	}
	holder->event = del_event;
	holder->deleted = 0;
	event_is_new = 1;
	sel->num_sels++;
	if (sel->sel_received_events)
	    ipmi_domain_stat_add(sel->sel_received_events, 1);
    } else if (event_cmp(del_event, holder->event) != 0) {
	/* It's a new event in an old slot, so overwrite the old
           event. */
	
	ipmi_event_free(holder->event);
	holder->event = del_event;
	if (holder->deleted) {
	    holder->deleted = 0;
	    sel->num_sels++;
	    sel->del_sels--;
	}
	event_is_new = 1;
	if (sel->sel_received_events)
	    ipmi_domain_stat_add(sel->sel_received_events, 1);
    } else {
	ipmi_event_free(del_event);
    }

    if (sel->next_rec_id == 0xFFFF) {
	/* Only set the timestamps if the SEL fetch completed
	   successfully.  If we were unsuccessful, we want to redo the
	   operation so don't set the timestamps. */
	sel->last_addition_timestamp = sel->curr_addition_timestamp;
	sel->last_erase_timestamp = sel->curr_erase_timestamp;

	/* To avoid confusion, deliver the event before we deliver fetch
           complete. */
	if (event_is_new && sel->new_event_handler) {
	    ipmi_sel_new_event_handler_cb handler = sel->new_event_handler;
	    void                          *cb_data = sel->new_event_cb_data;
	    sel_unlock(sel);
	    handler(sel, mc, del_event, cb_data);
	    sel_lock(sel);
	}

	if (sel->sel_good_scans)
	    ipmi_domain_stat_add(sel->sel_good_scans, 1);

	/* If the operation completed successfully and everything in
	   our SEL is deleted, then clear it with our old reservation.
	   We also do the clear if the overflow flag is set; on some
	   systems this operation clears the overflow flag. */
	if ((sel->num_sels == 0)
	    && ((!ilist_empty(sel->events)) || sel->overflow))
	{
	    /* We don't care if this fails, because it will just
	       happen again later if it does. */
	    rv = send_sel_clear(elem, mc);
	    if (rv) {
		fetch_complete(sel, 0, 1);
		goto out;
	    }
	    rv = 0;
	    goto out_unlock;
	} else {
	    fetch_complete(sel, 0, 1);
	    goto out;
	}
    }
    sel->start_rec_id = sel->curr_rec_id;
    memcpy(sel->start_rec_id_data, rsp->data+5, 14);
    sel->curr_rec_id = sel->next_rec_id;

 start_request_sel_data:
    /* Request some more data. */
    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_GET_SEL_ENTRY_CMD;
    cmd_msg.data_len = 6;
    ipmi_set_uint16(cmd_msg.data, sel->reservation);
    ipmi_set_uint16(cmd_msg.data+2, sel->curr_rec_id);
    cmd_msg.data[4] = 0;
    cmd_msg.data[5] = 0xff;
    rv = ipmi_mc_send_command(mc, sel->lun, &cmd_msg, handle_sel_data, elem);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_clear): "
		 "Could not send SEL fetch command: %x", sel->name, rv);
	fetch_complete(sel, rv, 1);
	goto out;
    }

    if (event_is_new && sel->new_event_handler) {
	ipmi_sel_new_event_handler_cb handler = sel->new_event_handler;
	void                          *cb_data = sel->new_event_cb_data;
	sel_unlock(sel);
	handler(sel, mc, del_event, cb_data);
	sel_lock(sel);
    }
 out_unlock:
    sel_unlock(sel);
 out:
    return;
}

/* Cheap hacks for broken hardware. */
static void
sel_fixups(ipmi_mc_t *mc, ipmi_sel_info_t *sel)
{
    unsigned int mfg_id, product_id;

    /* Fixups */
    mfg_id = ipmi_mc_manufacturer_id(mc);
    product_id = ipmi_mc_product_id(mc);
    if ((mfg_id == 0x157) && (product_id == 0x841))
	/* Intel ATCA CMM mistakenly reports that it supports delete SEL */
	sel->supports_delete_sel = 0;
}

static void
handle_sel_info(ipmi_mc_t  *mc,
		ipmi_msg_t *rsp,
		void       *rsp_data)
{
    sel_fetch_handler_t *elem = rsp_data;
    ipmi_sel_info_t     *sel = elem->sel;
    unsigned char       cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t          cmd_msg;
    int                 rv;
    uint32_t            add_timestamp;
    uint32_t            erase_timestamp;
    int                 fetched_num_sels;

    sel_lock(sel);
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_info): "
		 "SEL info was destroyed while an operation was in progress",
		 sel->name);
	fetch_complete(sel, ECANCELED, 1);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_info): "
		 "MC went away while SEL op was in progress",
		 sel->name);
        fetch_complete(sel, ECANCELED, 1);
	goto out;
    }
	
    if (rsp->data[0] != 0) {
	if (sel->sel_fetch_errors)
	    ipmi_domain_stat_add(sel->sel_fetch_errors, 1);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_info): "
		 "IPMI error from SEL info fetch: %x",
		 sel->name, rsp->data[0]);
	fetch_complete(sel, IPMI_IPMI_ERR_VAL(rsp->data[0]), 1);
	goto out;
    }

    if (rsp->data_len < 15) {
	if (sel->sel_fetch_errors)
	    ipmi_domain_stat_add(sel->sel_fetch_errors, 1);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_info): SEL info too short", sel->name);
	fetch_complete(sel, EINVAL, 1);
	goto out;
    }

    /* Pull pertinant info from the response. */
    sel->major_version = rsp->data[1] & 0xf;
    sel->minor_version = (rsp->data[1] >> 4) & 0xf;
    fetched_num_sels = ipmi_get_uint16(rsp->data+2);
    sel->entries = fetched_num_sels;
    sel->free_bytes = ipmi_get_uint16(rsp->data+4);
    sel->overflow = (rsp->data[14] & 0x80) == 0x80;
    sel->supports_delete_sel = (rsp->data[14] & 0x08) == 0x08;
    sel->supports_partial_add_sel = (rsp->data[14] & 0x04) == 0x04;
    sel->supports_reserve_sel = (rsp->data[14] & 0x02) == 0x02;
    sel->supports_get_sel_allocation = (rsp->data[14] & 0x01) == 0x01;
    
    add_timestamp = ipmi_get_uint32(rsp->data + 6);
    erase_timestamp = ipmi_get_uint32(rsp->data + 10);

    sel_fixups(mc, sel);

    /* If the timestamps still match, no need to re-fetch the
       repository.  Note that we only check the add timestamp.  We
       don't care if things were deleted. */
    if (sel->fetched && (add_timestamp == sel->last_addition_timestamp)) {
	/* If the operation completed successfully and everything in
	   our SEL is deleted, then clear it with our old reservation.
	   We also do the clear if the overflow flag is set; on some
	   systems this operation clears the overflow flag. */
	if ((sel->num_sels == 0)
	    && ((!ilist_empty(sel->events)) || sel->overflow))
	{
	    /* We don't care if this fails, because it will just
	       happen again later if it does. */
	    rv = send_sel_clear(elem, mc);
	    if (rv) {
		fetch_complete(sel, 0, 1);
		goto out;
	    }
	    goto out_unlock;
	} else {
	    fetch_complete(sel, 0, 1);
	    goto out;
	}
    }

    sel->curr_addition_timestamp = add_timestamp;
    sel->curr_erase_timestamp = erase_timestamp;

    sel->sels_changed = 1;
    sel->next_rec_id = 0;

    if (fetched_num_sels == 0) {
	/* No sels, so there's nothing to do. */

	/* Set the timestamps here, because they are not the same, but
	   there was nothing to do. */
	sel->last_addition_timestamp = sel->curr_addition_timestamp;
	sel->last_erase_timestamp = sel->curr_erase_timestamp;
	sel->start_rec_id = 0;
	sel->curr_rec_id = 0;

	fetch_complete(sel, 0, 1);
	goto out;
    }

    /* Fetch the first SEL entry. */
    sel->curr_rec_id = sel->start_rec_id;
    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_GET_SEL_ENTRY_CMD;
    cmd_msg.data_len = 6;
    ipmi_set_uint16(cmd_msg.data, sel->reservation);
    ipmi_set_uint16(cmd_msg.data+2, sel->curr_rec_id);
    cmd_msg.data[4] = 0;
    cmd_msg.data[5] = 0xff;
    rv = ipmi_mc_send_command(mc, sel->lun, &cmd_msg, handle_sel_data, elem);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_info): "
		 "Could not send first SEL fetch command: %x",
		 sel->name, rv);
	fetch_complete(sel, rv, 1);
	goto out;
    }
 out_unlock:
    sel_unlock(sel);
 out:
    return;
}

static int
send_get_sel_info(sel_fetch_handler_t *elem, ipmi_mc_t *mc)
{
    ipmi_sel_info_t *sel = elem->sel;
    ipmi_msg_t      cmd_msg;

    /* Fetch the repository info. */
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_GET_SEL_INFO_CMD;
    cmd_msg.data = NULL;
    cmd_msg.data_len = 0;
    return ipmi_mc_send_command(mc, sel->lun, &cmd_msg, handle_sel_info, elem);
}

static void
sel_handle_reservation(ipmi_mc_t  *mc,
		       ipmi_msg_t *rsp,
		       void       *rsp_data)
{
    sel_fetch_handler_t *elem = rsp_data;
    ipmi_sel_info_t     *sel = elem->sel;
    int                 rv;

    sel_lock(sel);
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_handle_reservation): "
		 "SEL info was destroyed while an operation was in progress",
		 sel->name);
	fetch_complete(sel, ECANCELED, 1);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_handle_reservation): "
		 "MC went away while SEL op was in progress",
		 sel->name);
        fetch_complete(sel, ECANCELED, 1);
	goto out;
    }
	
    if (rsp->data[0] != 0) {
	if (sel->sel_fetch_errors)
	    ipmi_domain_stat_add(sel->sel_fetch_errors, 1);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_handle_reservation): "
		 "Failed getting reservation", sel->name);
	fetch_complete(sel, ENOSYS, 1);
	goto out;
    } else if (rsp->data_len < 3) {
	if (sel->sel_fetch_errors)
	    ipmi_domain_stat_add(sel->sel_fetch_errors, 1);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_handle_reservation): "
		 "got invalid reservation length", sel->name);
	fetch_complete(sel, EINVAL, 1);
	goto out;
    }

    sel->reservation = ipmi_get_uint16(rsp->data+1);

    rv = send_get_sel_info(elem, mc);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_handle_reservation): "
		 "Could not send SEL info command: %x", sel->name, rv);
	fetch_complete(sel, rv, 1);
	goto out;
    }
    sel_unlock(sel);
 out:
    return;
}

static void
start_fetch_cb(ipmi_mc_t *mc, void *cb_data)
{
    sel_fetch_handler_t *elem = cb_data;
    ipmi_sel_info_t     *sel = elem->sel;
    unsigned char       cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t          cmd_msg;
    int                 rv;

    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(start_fetch_cb): "
		 "SEL info was destroyed while an operation was in progress",
		 sel->name);
	elem->rv = ECANCELED;
	goto out;
    }

    if (sel->supports_reserve_sel) {
	/* Get a reservation first. */
	cmd_msg.data = cmd_data;
	cmd_msg.netfn = IPMI_STORAGE_NETFN;
	cmd_msg.cmd = IPMI_RESERVE_SEL_CMD;
	cmd_msg.data_len = 0;
	rv = ipmi_mc_send_command_sideeff(mc, sel->lun, &cmd_msg,
					  sel_handle_reservation, elem);
    } else {
	/* Bypass the reservation, it's not supported. */
	sel->reservation = 0;

	/* Fetch the repository info. */
	cmd_msg.data = cmd_data;
	cmd_msg.netfn = IPMI_STORAGE_NETFN;
	cmd_msg.cmd = IPMI_GET_SEL_INFO_CMD;
	cmd_msg.data_len = 0;
	rv = ipmi_mc_send_command(mc, sel->lun,
				  &cmd_msg, handle_sel_info, elem);
    }

    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(start_fetch_cb): could not send cmd: %x",
		 sel->name, rv);
	elem->rv = rv;
	goto out;
    }

 out:
    return;
}

static int
start_fetch(void *cb_data, int shutdown)
{
    sel_fetch_handler_t *elem = cb_data;
    int                 rv;

    sel_lock(elem->sel);
    if (shutdown) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(start_fetch): "
		 "SEL info was destroyed while an operation was in progress",
		 elem->sel->name);
	fetch_complete(elem->sel, ECANCELED, 0);
	return OPQ_HANDLER_ABORTED;
    }

    /* The read lock must be claimed before the sel lock to avoid
       deadlock. */
    rv = ipmi_mc_pointer_cb(elem->sel->mc, start_fetch_cb, elem);
    if (rv)
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(start_fetch): MC is not valid", elem->sel->name);
    else
	rv = elem->rv;

    if (rv) {
	fetch_complete(elem->sel, rv, 0);
	return OPQ_HANDLER_ABORTED;
    }

    sel_unlock(elem->sel);

    return OPQ_HANDLER_STARTED;
}

/* We have to have this because the allocate element can go away (the
   operation can complete) before returning to the user. */
typedef struct sel_get_cb_s
{
    sel_fetch_handler_t *elem;
    int                 rv;
} sel_get_cb_t;

static void
ipmi_sel_get_cb(ipmi_mc_t *mc, void *cb_data)
{
    sel_get_cb_t        *info = cb_data;
    sel_fetch_handler_t *elem = info->elem;
    ipmi_sel_info_t     *sel = elem->sel;

    if (!ipmi_mc_sel_device_support(mc)) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(ipmi_sel_get_cb): "
		 "No support for the system event log", sel->name);
	info->rv = ENOSYS;
	return;
    }

    sel_lock(sel);
    if (! sel->in_fetch) {
	/* If we are not currently fetching sels, then start the
	   process.  If we are already fetching sels, then the current
	   fetch process will handle it. */
	sel->fetch_retry_count = 0;
	sel->in_fetch = 1;
	sel->sels_changed = 0;

	elem->next = NULL;
	sel->fetch_handlers = elem;
	sel_unlock(sel);
	/* Always put a fetch ahead of everything else.  If there are
	   deletes in progress and a clear gets done, we can complete
	   all the deletes. */
	if (!opq_new_op_prio(sel->opq, start_fetch, elem, 0, OPQ_ADD_HEAD,
			    NULL))
	{
	    sel->fetch_handlers = NULL;
	    info->rv = ENOMEM;
	}
	goto out;
    } else if (elem->handler) {
	/* Add it to the list of waiting fetch handlers, if it has a
	   handler. */
	elem->next = sel->fetch_handlers;
	sel->fetch_handlers = elem;
    } else {
	/* No handler and fetch was already in progress.  Return an
	   error so the caller knows what happened. */
	info->rv = EEXIST;
    }

    sel_unlock(sel);
 out:
    return;
}

int
ipmi_sel_get(ipmi_sel_info_t     *sel,
	     ipmi_sels_fetched_t handler,
	     void                *cb_data)
{
    sel_get_cb_t        info;
    sel_fetch_handler_t *elem;
    int                 rv;

    elem = ipmi_mem_alloc(sizeof(*elem));
    if (!elem) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(ipmi_sel_get): "
		 "could not allocate the sel element", sel->name);
	return ENOMEM;
    }

    elem->handler = handler;
    elem->cb_data = cb_data;
    elem->sel = sel;
    elem->rv = 0;
    info.elem = elem;
    info.rv = 0;

    rv = ipmi_mc_pointer_cb(sel->mc, ipmi_sel_get_cb, &info);
    if (!rv)
	rv = info.rv;
    if (rv)
	ipmi_mem_free(elem);
    if (rv == EEXIST)
	/* EEXIST means that a operation was already running, and no
	   handler was given.  We want to free the element, but we
	   still want to return success. */
	rv = 0;

    return rv;
}

/* Don't do this forever. */
#define MAX_DEL_RESERVE_RETRIES		10

typedef struct sel_del_handler_data_s
{
    ipmi_sel_info_t       *sel;
    ipmi_sel_op_done_cb_t handler;
    void                  *cb_data;
    unsigned int          reservation;
    unsigned int          record_id;
    unsigned int          lun;
    unsigned int          count;
    ipmi_event_t          *event;
    sel_event_holder_t    *holder;

    /* If true, we do a clear operation if the given record is the
       last record in the SEL. */
    int                   do_clear;
} sel_del_handler_data_t;

static int send_reserve_sel_for_delete(sel_del_handler_data_t *data,
				       ipmi_mc_t *mc);

static void
sel_op_done(sel_del_handler_data_t *data,
	    int                    rv,
	    int                    do_op_done)
{
    ipmi_sel_info_t *sel = data->sel;

    if (data->holder)
	sel_event_holder_put(data->holder);

    sel_unlock(sel);

    if (data->handler)
	data->handler(sel, data->cb_data, rv);

    sel_lock(sel);

    if (sel->in_destroy) {
	/* Nothing to do */
	sel_unlock(sel);
    } else if (sel->destroyed) {
	/* This will unlock the lock. */
	internal_destroy_sel(sel);
    } else {
	sel_unlock(sel);
	if (do_op_done)
	    opq_op_done(sel->opq);
    }
    if (data->event)
	ipmi_event_free(data->event);
    ipmi_mem_free(data);
}

static void
free_all_event(ilist_iter_t *iter, void *item, void *cb_data)
{
    sel_event_holder_t *holder = item;
    ipmi_sel_info_t    *sel = cb_data;

    if (holder->deleted) {
	sel->del_sels--;
	holder->cancelled = 1;
    }
    ilist_delete(iter);
    sel_event_holder_put(holder);
}

static void
free_all_events(ipmi_sel_info_t *sel)
{
    ilist_iter(sel->events, free_all_event, sel);
}

static void
handle_del_sel_clear(ipmi_mc_t  *mc,
		     ipmi_msg_t *rsp,
		     void       *rsp_data)
{
    sel_del_handler_data_t *data = rsp_data;
    ipmi_sel_info_t        *sel = data->sel;

    sel_lock(sel);
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_del_sel_clear): "
		 "SEL info was destroyed while an operation was in progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 1);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_del_sel_clear): "
		 "MC went away while SEL fetch was in progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 1);
	goto out;
    }

    if (rsp->data[0]) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_del_sel_clear): "
		 "IPMI error clearing SEL: 0x%x",
		 sel->name, rsp->data[0]);
	sel_op_done(data, IPMI_IPMI_ERR_VAL(rsp->data[0]), 1);
	goto out;
    }

    free_all_events(sel);
    sel->num_sels = 0;

    sel_op_done(data, 0, 1);

 out:
    return;
}

static int
send_del_clear(sel_del_handler_data_t *data, ipmi_mc_t *mc)
{
    ipmi_sel_info_t *sel = data->sel;
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;

    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_CLEAR_SEL_CMD;
    cmd_msg.data_len = 6;
    ipmi_set_uint16(cmd_msg.data, data->reservation);
    cmd_msg.data[2] = 'C';
    cmd_msg.data[3] = 'L';
    cmd_msg.data[4] = 'R';
    cmd_msg.data[5] = 0xaa;

    return ipmi_mc_send_command(mc, sel->lun, &cmd_msg,
				handle_del_sel_clear, data);
}

static void
handle_sel_delete(ipmi_mc_t  *mc,
		  ipmi_msg_t *rsp,
		  void       *rsp_data)
{
    sel_del_handler_data_t *data = rsp_data;
    ipmi_sel_info_t        *sel = data->sel;
    int                    rv = 0;
    
    sel_lock(sel);
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_delete): "
		 "SEL info was destroyed while an operation was in progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 1);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_delete): "
		 "MC went away while SEL fetch was in progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 1);
	goto out;
    }

    /* Special return codes. */
    if (rsp->data[0] == 0x80) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_delete): "
		 "Operation not supported on SEL delete",
		 sel->name);
	rv = ENOSYS;
    } else if (rsp->data[0] == 0x81) {
	/* The SEL is being erased, so by definition the log will be
           gone. */
	rv = 0;
    } else if (rsp->data[0] == IPMI_NOT_PRESENT_CC) {
	/* The entry is already gone, so just return no error. */
	rv = 0;
    } else if ((data->count < MAX_DEL_RESERVE_RETRIES)
	       && (rsp->data[0] == IPMI_INVALID_RESERVATION_CC))
    {
	if (sel->sel_delete_lost_reservation)
	    ipmi_domain_stat_add(sel->sel_delete_lost_reservation, 1);
	/* Lost our reservation, retry the operation. */
	data->count++;
	rv = send_reserve_sel_for_delete(data, mc);
	if (!rv)
	    goto out_unlock;
    } else if (rsp->data[0]) {
	if (rsp->data[0] == IPMI_INVALID_RESERVATION_CC) {
	    if (sel->sel_fail_delete_lost_reservation)
		ipmi_domain_stat_add(sel->sel_fail_delete_lost_reservation, 1);
	} else {
	    if (sel->sel_delete_errors)
		ipmi_domain_stat_add(sel->sel_delete_errors, 1);
	}
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_delete): "
		 "IPMI error from SEL delete: %x", sel->name, rsp->data[0]);
	rv = IPMI_IPMI_ERR_VAL(rsp->data[0]);
    } else {	
	/* We deleted the entry, so remove it from our database. */
	sel_event_holder_t *real_holder;
	ilist_iter_t       iter;

	ilist_init_iter(&iter, sel->events);
	ilist_unpositioned(&iter);
	real_holder = ilist_search_iter(&iter, recid_search_cmp,
					&(data->record_id));
	if (real_holder) {
	    ilist_delete(&iter);
	    sel_event_holder_put(real_holder);
	    sel->del_sels--;
	}
    }

    sel_op_done(data, rv, 1);

 out:
    return;

 out_unlock:
    sel_unlock(sel);
}

static int
send_del_sel(sel_del_handler_data_t *data, ipmi_mc_t *mc)
{
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;
    int             rv;
    
    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_DELETE_SEL_ENTRY_CMD;
    cmd_msg.data_len = 4;
    ipmi_set_uint16(cmd_msg.data, data->reservation);
    ipmi_set_uint16(cmd_msg.data+2, data->record_id);
    rv = ipmi_mc_send_command(mc, data->lun,
			      &cmd_msg, handle_sel_delete, data);

    return rv;
}

static void
handle_sel_check(ipmi_mc_t  *mc,
		 ipmi_msg_t *rsp,
		 void       *rsp_data)
{
    sel_del_handler_data_t *data = rsp_data;
    ipmi_sel_info_t        *sel = data->sel;
    int                    rv = 0;
    
    sel_lock(sel);
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_check): "
		 "SEL info was destroyed while SEL delete element was in"
		 " progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 1);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_check): "
		 "MC went away while SEL delete element was in progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 1);
	goto out;
    }

    /* Special return codes. */
    if (rsp->data[0] == IPMI_NOT_PRESENT_CC) {
	/* The entry is already gone, so just return no error. */
	sel_op_done(data, 0, 1);
	goto out;
    } else if (rsp->data[0]) {
	if (sel->sel_delete_errors)
	    ipmi_domain_stat_add(sel->sel_delete_errors, 1);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(handle_sel_check): IPMI error from SEL check: %x",
		 sel->name, rsp->data[0]);
	sel_op_done(data, IPMI_IPMI_ERR_VAL(rsp->data[0]), 1);
	goto out;
    } else {
	ipmi_event_t *ch_event;
	ipmi_time_t  timestamp;

	if (rsp->data[5] < 0xe0)
	    timestamp = ipmi_get_uint32(rsp->data+6);
	else
	    timestamp = -1;
	ch_event = ipmi_event_alloc(ipmi_mc_convert_to_id(mc),
				    ipmi_get_uint16(rsp->data+3),
				    rsp->data[5],
				    timestamp,
				    rsp->data+6,
				    13);
	if (!ch_event) {
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssel.c(handle_sel_check): Could not allocate memory",
		     sel->name);
	    sel_op_done(data, ENOMEM, 1);
	    goto out;
	}

	if (data->event && (event_cmp(ch_event, data->event) != 0)) {
	    /* The event's don't match, so just finish. */
	    ipmi_event_free(ch_event);
	    sel_op_done(data, 0, 1);
	    goto out;
	}
	ipmi_event_free(ch_event);

	if (data->do_clear) {
	    /* Make sure that there is no next event. */
	    uint16_t next_ev = ipmi_get_uint16(rsp->data+1);

	    if (next_ev != 0xffff) {
		/* A new event was added after this one.  Fail the op. */
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%ssel.c(handle_sel_check): "
			 "Clear SEL failed, new events in SEL",
			 sel->name);
		sel_op_done(data, EAGAIN, 1);
		goto out;
	    }

	    rv = send_del_clear(data, mc);
	    if (rv) {
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%ssel.c(handle_sel_check): "
			 "Could not send SEL clear command: %x",
			 sel->name, rv);
		sel_op_done(data, rv, 1);
		goto out;
	    }
	} else {
	    rv = send_del_sel(data, mc);
	    if (rv) {
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%ssel.c(handle_sel_check): "
			 "Could not send SEL delete command: %x",
			 sel->name, rv);
		sel_op_done(data, rv, 1);
		goto out;
	    } else if (data->record_id == sel->start_rec_id)
		/* We are deleting our "current" record (used for finding
		   the next record), make sure we start again from
		   scratch on the next fetch. */
		sel->start_rec_id = 0;
	}
    }
	

    sel_unlock(sel);

 out:
    return;
}

/* First get the entry, to make sure we are deleting the right one. */
static int
send_check_sel(sel_del_handler_data_t *data, ipmi_mc_t *mc)
{
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;
    int             rv;
    
    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_GET_SEL_ENTRY_CMD;
    cmd_msg.data_len = 6;
    ipmi_set_uint16(cmd_msg.data, 0);
    ipmi_set_uint16(cmd_msg.data+2, data->record_id);
    cmd_msg.data[4] = 0;
    cmd_msg.data[5] = 0xff;
    rv = ipmi_mc_send_command(mc, data->lun,
			      &cmd_msg, handle_sel_check, data);

    return rv;
}

static void
sel_reserved_for_delete(ipmi_mc_t  *mc,
			ipmi_msg_t *rsp,
			void       *rsp_data)
{
    sel_del_handler_data_t *data = rsp_data;
    ipmi_sel_info_t        *sel = data->sel;
    int                    rv;

    sel_lock(sel);
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_reserved_for_delete): "
		 "SEL info was destroyed while SEL delete element was in"
		 " progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 1);
	goto out;
    }
    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_reserved_for_delete): "
		 "MC went away while SEL delete element was in progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 1);
	goto out;
    }

    if (rsp->data[0] != 0) {
	if (sel->sel_delete_errors)
	    ipmi_domain_stat_add(sel->sel_delete_errors, 1);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_reserved_for_delete): "
		 "IPMI error from SEL delete reservation: %x",
		 sel->name, rsp->data[0]);
	sel_op_done(data, IPMI_IPMI_ERR_VAL(rsp->data[0]), 1);
	goto out;
    }

    data->reservation = ipmi_get_uint16(rsp->data+1);
    if (!data->do_clear || data->event) {
	rv = send_check_sel(data, mc);
	if (rv) {
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssel.c(sel_reserved_for_delete): "
		     "Could not send SEL get command: %x", sel->name, rv);
	    sel_op_done(data, rv, 1);
	    goto out;
	}
    } else {
	/* We are clearing the SEL and the user didn't supply an
	   event.  Don't worry about checking anything. */
	rv = send_del_clear(data, mc);
	if (rv) {
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssel.c(sel_reserved_for_delete): "
		     "Could not send SEL clear command: %x", sel->name, rv);
	    sel_op_done(data, rv, 1);
	    goto out;
	}
    }

    sel_unlock(sel);
 out:
    return;
}

static int
send_reserve_sel_for_delete(sel_del_handler_data_t *data, ipmi_mc_t *mc)
{
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;
    int             rv;

    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_RESERVE_SEL_CMD;
    cmd_msg.data_len = 0;
    rv = ipmi_mc_send_command_sideeff(mc, data->lun,
				      &cmd_msg, sel_reserved_for_delete, data);

    return rv;
}

static void
start_del_sel_cb(ipmi_mc_t *mc, void *cb_data)
{
    sel_del_handler_data_t *data = cb_data;
    ipmi_sel_info_t        *sel = data->sel;
    int                    rv;

    /* Called with SEL lock held. */
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(start_del_sel_cb): "
		 "SEL info was destroyed while an operation was in progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 1);
	goto out;
    }

    if (data->sel->supports_reserve_sel)
	rv = send_reserve_sel_for_delete(data, mc);
    else
	rv = send_check_sel(data, mc);

    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(start_del_sel_cb): could not send cmd: %x",
		 sel->name, rv);
	sel_op_done(data, rv, 1);
	goto out;
    }

    sel_unlock(sel);
 out:
    return;
}

static int
start_del_sel(void *cb_data, int shutdown)
{
    sel_del_handler_data_t *data = cb_data;
    ipmi_sel_info_t        *sel = data->sel;
    int                    rv;

    sel_lock(sel);
    if (shutdown) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(start_del_sel): "
		 "SEL info was destroyed while an operation was in progress",
		 sel->name);
	sel_op_done(data, ECANCELED, 0);
	return OPQ_HANDLER_ABORTED;
    }

    if (data->holder && data->holder->cancelled) {
	/* Deleted by a clear, everything is ok. */
	sel_op_done(data, 0, 0);
	return OPQ_HANDLER_ABORTED;
    }

    rv = ipmi_mc_pointer_cb(sel->mc, start_del_sel_cb, data);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(start_del_sel_cb): MC went away during delete",
		 sel->name);
	sel_op_done(data, ECANCELED, 0);
	return OPQ_HANDLER_ABORTED;
    }

    return OPQ_HANDLER_STARTED;
}

typedef struct sel_del_event_info_s
{
    ipmi_sel_info_t       *sel;
    ipmi_event_t          *event;
    unsigned int          record_id;
    ipmi_sel_op_done_cb_t handler;
    void                  *cb_data;
    int                   cmp_event;
    int                   rv;
    int                   do_clear;
} sel_del_event_info_t;

static void
sel_del_event_cb(ipmi_mc_t *mc, void *cb_data)
{
    sel_del_event_info_t  *info = cb_data;
    ipmi_sel_info_t       *sel = info->sel;
    ipmi_event_t          *event = info->event;
    int                   cmp_event = info->cmp_event;
    sel_event_holder_t    *real_holder = NULL;
    ilist_iter_t          iter;
    int                   start_fetch = 0;

    sel_lock(sel);
    if (sel->destroyed) {
	info->rv = EINVAL;
	goto out_unlock;
    }

    if (event) {
	ilist_init_iter(&iter, sel->events);
	ilist_unpositioned(&iter);
	real_holder = ilist_search_iter(&iter, recid_search_cmp,
				    &info->record_id);
	if (!real_holder) {
	    info->rv = EINVAL;
	    goto out_unlock;
	}

	if (cmp_event && (event_cmp(event, real_holder->event) != 0)) {
	    info->rv = EINVAL;
	    goto out_unlock;
	}

	if (! info->do_clear) {
	    if (real_holder->deleted) {
		info->rv = EINVAL;
		goto out_unlock;
	    }

	    real_holder->deleted = 1;
	    sel->num_sels--;
	    sel->del_sels++;

	    start_fetch = (sel->num_sels == 0) && (sel->del_sels > 1);
	}
    }

    /* Note that at this point we cannot check num_sels to see if it
       is zero and do a bulk clear.  A new event might be added to the
       SEL after this point, thus failing the bulk clear, and that
       would prevent the individual delete from happening.  But we can
       start a fetch if the value reaches zero, which is just as
       good. */

    if (sel->supports_delete_sel || info->do_clear) {
	sel_del_handler_data_t *data;
	opq_elem_t             *elem;

	/* We can delete the entry immediately, just do it. */
	data = ipmi_mem_alloc(sizeof(*data));
	elem = opq_alloc_elem();
	if (!data || !elem) {
	    if (! info->do_clear) {
		real_holder->deleted = 0;
		sel->num_sels++;
		sel->del_sels--;
	    }
	    info->rv = ENOMEM;
	    if (data)
		ipmi_mem_free(data);
	    if (elem)
		opq_free_elem(elem);
	    goto out_unlock;
	}

	data->sel = sel;
	data->handler = info->handler;
	data->cb_data = info->cb_data;
	data->lun = sel->lun;
	data->record_id = info->record_id;
	data->count = 0;
	data->event = event;
	data->holder = real_holder;
	if (real_holder)
	    sel_event_holder_get(real_holder);
	data->do_clear = info->do_clear;
	event = NULL;

	sel_unlock(sel);
	opq_new_op_prio(sel->opq, start_del_sel, data, 0, OPQ_ADD_TAIL, elem);
    } else {
	sel_unlock(sel);
	/* Don't really delete the event, but report is as done. */
	info->handler(sel, info->cb_data, 0);
	ipmi_event_free(event);
    }

    if (start_fetch)
	ipmi_sel_get(sel, NULL, NULL);
    return;

 out_unlock:
    sel_unlock(sel);
}

static int
sel_del_event(ipmi_sel_info_t       *sel,
	      ipmi_event_t          *event,
	      unsigned int          record_id,
	      ipmi_sel_op_done_cb_t handler,
	      void                  *cb_data,
	      int                   cmp_event,
	      int                   do_clear)
{
    sel_del_event_info_t info;
    int                  rv;

    info.sel = sel;
    info.event = ipmi_event_dup(event);
    info.record_id = record_id;
    info.handler = handler;
    info.cb_data = cb_data;
    info.cmp_event = cmp_event;
    info.rv = 0;
    info.do_clear = do_clear;
    rv = ipmi_mc_pointer_cb(sel->mc, sel_del_event_cb, &info);
    if (!rv)
	rv = info.rv;
    if (rv)
	ipmi_event_free(info.event);
    return rv;
}

int
ipmi_sel_del_event(ipmi_sel_info_t       *sel,
		   ipmi_event_t          *event,
		   ipmi_sel_op_done_cb_t handler,
		   void                  *cb_data)
{
    return sel_del_event(sel, event, ipmi_event_get_record_id(event),
			 handler, cb_data, 1, 0);
}

int
ipmi_sel_del_event_by_recid(ipmi_sel_info_t       *sel,
			    unsigned int          record_id,
			    ipmi_sel_op_done_cb_t handler,
			    void                  *cb_data)
{
    return sel_del_event(sel, NULL, record_id, handler, cb_data, 0, 0);
}

int
ipmi_sel_clear(ipmi_sel_info_t       *sel,
	       ipmi_event_t          *last_event,
	       ipmi_sel_op_done_cb_t handler,
	       void                  *cb_data)
{
    int cmp_event = (last_event != NULL);
    unsigned int record_id = 0;
    if (last_event)
	record_id = ipmi_event_get_record_id(last_event);
    return sel_del_event(sel, last_event, record_id, handler, cb_data,
			 cmp_event, 1);
}

int
ipmi_get_sel_count(ipmi_sel_info_t *sel,
		   unsigned int    *count)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *count = sel->num_sels;

    sel_unlock(sel);
    return 0;
}

int
ipmi_get_sel_entries_used(ipmi_sel_info_t *sel,
			  unsigned int    *count)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *count = sel->num_sels + sel->del_sels;

    sel_unlock(sel);
    return 0;
}

ipmi_event_t *
ipmi_sel_get_first_event(ipmi_sel_info_t *sel)
{
    ilist_iter_t iter;
    ipmi_event_t *rv = NULL;

    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return NULL;
    }
    ilist_init_iter(&iter, sel->events);
    if (ilist_first(&iter)) {
	sel_event_holder_t *holder = ilist_get(&iter);

	while (holder->deleted) {
	    if (! ilist_next(&iter))
		goto out;
	    holder = ilist_get(&iter);
	}
	rv = ipmi_event_dup(holder->event);
    }
 out:
    sel_unlock(sel);
    return rv;
}

ipmi_event_t *
ipmi_sel_get_last_event(ipmi_sel_info_t *sel)
{
    ilist_iter_t iter;
    ipmi_event_t *rv = NULL;

    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return NULL;
    }
    ilist_init_iter(&iter, sel->events);
    if (ilist_last(&iter)) {
	sel_event_holder_t *holder = ilist_get(&iter);

	while (holder->deleted) {
	    if (! ilist_prev(&iter))
		goto out;
	    holder = ilist_get(&iter);
	}
	rv = ipmi_event_dup(holder->event);
    }
 out:
    sel_unlock(sel);
    return rv;
}

ipmi_event_t *
ipmi_sel_get_next_event(ipmi_sel_info_t *sel, const ipmi_event_t *event)
{
    ilist_iter_t iter;
    ipmi_event_t *rv = NULL;
    unsigned int record_id;

    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return NULL;
    }
    ilist_init_iter(&iter, sel->events);
    ilist_unpositioned(&iter);
    record_id = ipmi_event_get_record_id(event);
    if (ilist_search_iter(&iter, recid_search_cmp, &record_id)) {
	if (ilist_next(&iter)) {
	    sel_event_holder_t *holder = ilist_get(&iter);

	    while (holder->deleted) {
		if (! ilist_next(&iter))
		    goto out;
		holder = ilist_get(&iter);
	    }
	    rv = ipmi_event_dup(holder->event);
	}
    }
 out:
    sel_unlock(sel);
    return rv;
}

ipmi_event_t *
ipmi_sel_get_prev_event(ipmi_sel_info_t *sel, const ipmi_event_t *event)
{
    ilist_iter_t iter;
    ipmi_event_t *rv = NULL;
    unsigned int record_id;

    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return NULL;
    }
    ilist_init_iter(&iter, sel->events);
    ilist_unpositioned(&iter);
    record_id = ipmi_event_get_record_id(event);
    if (ilist_search_iter(&iter, recid_search_cmp, &record_id)) {
	if (ilist_prev(&iter)) {
	    sel_event_holder_t *holder = ilist_get(&iter);

	    while (holder->deleted) {
		if (! ilist_prev(&iter))
		    goto out;
		holder = ilist_get(&iter);
	    }
	    rv = ipmi_event_dup(holder->event);
	}
    }
 out:
    sel_unlock(sel);
    return rv;
}

ipmi_event_t *
ipmi_sel_get_event_by_recid(ipmi_sel_info_t *sel,
                            unsigned int    record_id)
{
    ipmi_event_t       *rv = NULL;
    sel_event_holder_t *holder;

    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return NULL;
    }

    holder = find_event(sel->events, record_id);
    if (!holder)
	goto out_unlock;

    if (holder->deleted)
        goto out_unlock;

    rv = ipmi_event_dup(holder->event);

 out_unlock:
    sel_unlock(sel);
    return rv;
}

int
ipmi_get_all_sels(ipmi_sel_info_t *sel,
		  int             *array_size,
		  ipmi_event_t    **array)
{
    unsigned int i;
    int          rv = 0;

    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    if (*array_size < (int) sel->num_sels) {
	rv = E2BIG;
    } else if (sel->num_sels == 0) {
	rv = 0;
    } else {
	ilist_iter_t iter;

	ilist_init_iter(&iter, sel->events);
	if (! ilist_first(&iter)) {
	    rv = EINVAL;
	    goto out_unlock;
	}
	for (i = 0; ; ) {
	    sel_event_holder_t *holder = ilist_get(&iter);

	    if (!holder->deleted)
		array[i++] = ipmi_event_dup(holder->event);
	    if (i < sel->num_sels) {
		if (! ilist_next(&iter)) {
		    rv = EINVAL;
		    while (i > 0)
			ipmi_event_free(array[--i]);
		    goto out_unlock;
		}
	    } else
		break;
	}
	*array_size = i;
    }

 out_unlock:
    sel_unlock(sel);
    return rv;
}

int
ipmi_sel_get_major_version(ipmi_sel_info_t *sel, int *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->major_version;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_get_minor_version(ipmi_sel_info_t *sel, int *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->minor_version;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_get_num_entries(ipmi_sel_info_t *sel, int *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->entries;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_get_free_bytes(ipmi_sel_info_t *sel, int *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->free_bytes;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_get_overflow(ipmi_sel_info_t *sel, int *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->overflow;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_get_supports_delete_sel(ipmi_sel_info_t *sel, int *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->supports_delete_sel;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_get_supports_partial_add_sel(ipmi_sel_info_t *sel, int *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->supports_partial_add_sel;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_get_supports_reserve_sel(ipmi_sel_info_t *sel, int *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->supports_reserve_sel;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_get_supports_get_sel_allocation(ipmi_sel_info_t *sel,
					 int             *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->supports_get_sel_allocation;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_get_last_addition_timestamp(ipmi_sel_info_t *sel,
                                     int             *val)
{
    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    *val = sel->last_addition_timestamp;

    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_set_new_event_handler(ipmi_sel_info_t               *sel,
			       ipmi_sel_new_event_handler_cb handler,
			       void                          *cb_data)
{
    sel_lock(sel);
    sel->new_event_handler = handler;
    sel->new_event_cb_data = cb_data;
    sel_unlock(sel);
    return 0;
}

int
ipmi_sel_event_add(ipmi_sel_info_t *sel,
		   ipmi_event_t    *new_event)
{
    int                rv = 0;
    sel_event_holder_t *holder;
    unsigned int       record_id;

    sel_lock(sel);
    if (sel->destroyed) {
	sel_unlock(sel);
	return EINVAL;
    }

    record_id = ipmi_event_get_record_id(new_event);
    holder = find_event(sel->events, record_id);
    if (!holder) {
	holder = sel_event_holder_alloc();
	if (!holder) {
	    rv = ENOMEM;
	    goto out_unlock;
	}
	if (!ilist_add_tail(sel->events, holder, NULL)) {
	    rv = ENOMEM;
	    goto out_unlock;
	}
	holder->event = ipmi_event_dup(new_event);
	sel->num_sels++;
    } else if (event_cmp(holder->event, new_event) == 0) {
	/* A duplicate event, just ignore it and return the right
	   error. */
	rv = EEXIST;
    } else {
	ipmi_event_free(holder->event);
	holder->event = ipmi_event_dup(new_event);
	if (holder->deleted) {
	    holder->deleted = 0;
	    sel->num_sels++;
	    sel->del_sels--;
	}
    }

 out_unlock:
    sel_unlock(sel);
    return rv;
}

typedef struct sel_add_cb_handler_data_s
{
    ipmi_sel_info_t           *sel;
    ipmi_sel_add_op_done_cb_t handler;
    void                      *cb_data;
    unsigned int              record_id;
    ipmi_event_t              *event;
    int                       rv;
} sel_add_cb_handler_data_t;

static void
sel_add_op_done(sel_add_cb_handler_data_t *data,
		int                       rv)
{
    ipmi_sel_info_t *sel = data->sel;

    if (data->handler)
	data->handler(sel, data->cb_data, rv, data->record_id);

    if (sel->in_destroy) {
	/* Nothing to do */
	sel_unlock(sel);
    } else if (sel->destroyed) {
	/* This will unlock the lock. */
	internal_destroy_sel(sel);
    } else {
	sel_unlock(sel);
	opq_op_done(sel->opq);
    }
    if (data->event)
	ipmi_event_free(data->event);
    ipmi_mem_free(data);
}

static void
sel_add_event_done(ipmi_mc_t  *mc,
		   ipmi_msg_t *rsp,
		   void       *rsp_data)
{
    sel_add_cb_handler_data_t *info = rsp_data;
    ipmi_sel_info_t           *sel = info->sel;

    sel_lock(sel);
    if (sel->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_add_event_done): "
		 "SEL info was destroyed while an operation was in progress",
		 sel->name);
	sel_add_op_done(info, ECANCELED);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_add_event_done): "
		 "MC went away while SEL op was in progress", sel->name);
        sel_add_op_done(info, ECANCELED);
	goto out;
    }
	
    if (rsp->data[0] != 0) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_add_event_done): "
		 "IPMI error from SEL info fetch: %x",
		 sel->name, rsp->data[0]);
	sel_add_op_done(info, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	goto out;
    }

    if (rsp->data_len < 3) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_add_event_done): SEL add response too short",
		 sel->name);
	sel_add_op_done(info, EINVAL);
	goto out;
    }

    info->record_id = ipmi_get_uint16(rsp->data+1);
    sel_add_op_done(info, 0);
 out:
    return;
}

static void
sel_add_event_cb(ipmi_mc_t *mc, void *cb_data)
{
    sel_add_cb_handler_data_t *info = cb_data;
    ipmi_sel_info_t           *sel = info->sel;
    ipmi_event_t              *event = info->event;
    unsigned char             data[16];
    ipmi_msg_t                msg;

    msg.netfn = IPMI_STORAGE_NETFN;
    msg.cmd = IPMI_ADD_SEL_ENTRY_CMD;
    msg.data = data;
    msg.data_len = 16;

    ipmi_set_uint16(data, ipmi_event_get_record_id(event));
    data[2] = ipmi_event_get_type(event);
    memcpy(data+3, ipmi_event_get_data_ptr(event), 13);

    info->rv = ipmi_mc_send_command(mc, sel->lun, &msg, sel_add_event_done,
				    info);
}

static int
sel_add_event_op(void *cb_data, int shutdown)
{
    sel_add_cb_handler_data_t *info = cb_data;
    ipmi_sel_info_t           *sel = info->sel;
    int                       rv;

    sel_lock(sel);
    if (shutdown) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_add_event_op): "
		 "SEL info was destroyed while an operation was in progress",
		 sel->name);
	sel_add_op_done(info, ECANCELED);
	goto out;
    }

    rv = ipmi_mc_pointer_cb(sel->mc, sel_add_event_cb, info);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_add_event_op): MC went away during delete",
		 sel->name);
	sel_add_op_done(info, ECANCELED);
	goto out;
    } else if (info->rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssel.c(sel_add_event_cb): could not send cmd: %x",
		 sel->name, rv);
	sel_add_op_done(info, info->rv);
	goto out;
    }

    sel_unlock(sel);
 out:
    return OPQ_HANDLER_STARTED;
}

int
ipmi_sel_add_event_to_sel(ipmi_sel_info_t           *sel,
			  ipmi_event_t              *event_to_add,
			  ipmi_sel_add_op_done_cb_t done,
			  void                      *cb_data)
{
    sel_add_cb_handler_data_t *info = cb_data;
    int                       rv = 0;

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info)
	return ENOMEM;

    info->sel = sel;
    info->event = ipmi_event_dup(event_to_add);
    info->handler = done;
    info->cb_data = cb_data;
    info->record_id = 0;

    sel_lock(sel);
    if (sel->destroyed) {
	rv = EINVAL;
	goto out_unlock;
    }

    sel_unlock(sel);

    /* Schedule this to run at the end of the queue. */
    if (!opq_new_op(sel->opq, sel_add_event_op, info, 0)) {
	rv = ENOMEM;
	goto out_unlock;
    }
    goto out;

 out_unlock:
    sel_unlock(sel);

 out:
    if (rv)
	ipmi_mem_free(info);
    return rv;
}
