/*
 * sdr.c
 *
 * MontaVista IPMI code for handling SDRs
 *
 * Author: MontaVista Software, Inc.
 *         Corey Minyard <minyard@mvista.com>
 *         source@mvista.com
 *
 * Copyright 2002,2003 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_sdr.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_domain.h>
#include <OpenIPMI/internal/ipmi_mc.h>
#include <OpenIPMI/internal/ipmi_int.h>

/* Max bytes to try to get at a time, the minimum allowed, and the
   amount to decrement between tries. */
#define MAX_SDR_FETCH_BYTES 28
#define STD_SDR_FETCH_BYTES 16
#define MIN_SDR_FETCH_BYTES 10
#define SDR_FETCH_BYTES_DECR 6

/* Do up to this many retries when the reservation is lost. */
#define MAX_SDR_FETCH_RETRIES 10

/* Number of outstanding fetch requests we can have out. */
#define MAX_SDR_FETCH_OUTSTANDING 3

typedef struct sdr_fetch_handler_s
{
    ipmi_sdr_info_t     *sdrs;
    ipmi_sdrs_fetched_t handler;
    void                *cb_data;
} sdr_fetch_handler_t;

enum fetch_state_e { IDLE, FETCHING, HANDLERS };

typedef struct fetch_info_s
{
    unsigned int fetch_retry_num;

    ipmi_sdr_info_t *sdrs;

    unsigned int sdr_rec;
    unsigned int idx;
    unsigned int offset;
    unsigned int read_len;
    unsigned char data[MAX_SDR_FETCH_BYTES+2];

    ilist_item_t link;
} fetch_info_t;

#undef DEBUG_INFO_TRACKING

struct ipmi_sdr_info_s
{
    char name[IPMI_MC_NAME_LEN+1+20];

    /* The thing holding the SDR repository. */
    ipmi_mcid_t mc;

    /* The OS handler for this SDR. */
    os_handler_t *os_hnd;

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

    /* Is this for sensor SDRs or main SDRs. */
    int         sensor;

    /* A lock, primarily for handling race conditions fetching the data. */
    ipmi_lock_t *sdr_lock;

    opq_t *sdr_wait_q;
    int   wait_err;

    /* Information from the SDR Repository Info command, non-sensor
       mode only. */
    uint8_t  major_version;
    uint8_t  minor_version;
    uint32_t last_addition_timestamp;
    uint32_t last_erase_timestamp;
    unsigned int overflow : 1;
    unsigned int update_mode : 2;
    unsigned int supports_delete_sdr : 1;
    unsigned int supports_partial_add_sdr : 1;
    unsigned int supports_reserve_sdr : 1;
    unsigned int supports_get_sdr_repository_allocation : 1;
    unsigned int use_cache : 1;

    /* Information from the GET DEVICE SDR INFO command, sensor mode
       only. */
    unsigned int dynamic_population : 1;
    char lun_has_sensors[4];

    /* Have the SDRs previously been fetched? */
    unsigned int fetched : 1;

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

    /* Are we currently fetching SDRs? */
    enum fetch_state_e fetch_state;

    /* Are we currently fetching database data? */
    int                db_fetching;

    /* 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           read_offset; /* Next data to read */

    unsigned int           fetch_size;

    unsigned int           curr_read_rec_id;
    unsigned int           next_read_rec_id;
    int                    curr_read_idx;
    int                    next_read_offset; /* -1 if header */
    int                    read_size;

    unsigned int           reservation;
    unsigned int           working_num_sdrs;
    ipmi_sdr_t             *working_sdrs;
    int                    sdrs_changed;
    unsigned int           fetch_retry_count;
    unsigned int           sdr_retry_count;
    int                    fetch_err;

    unsigned int           sdr_data_write;
    unsigned int           write_sdr_num;

    /* List of fetch info items for an in-progress fetch.  The free
       list holds fetch structures that are not currently in use, the
       outstanding list holds ones that have been sent but have not
       received a response, and the process queue holds one received
       out of order. */
    ilist_t *free_fetch;
    ilist_t *outstanding_fetch;
    ilist_t *process_fetch;

    /* This is used so that start_fetch will only start when nothing
       is outstanding from other fetches.  This avoids getting
       messages that are not valid, running out of buffers, and other
       confusing things. */
    int waiting_start_fetch;

    /* This timer is used to restart the SDR fetch operation.  If it
       fails due to a lost reservation, wait a random amount of time
       and restart it. */
    os_hnd_timer_id_t *restart_timer;
    int               restart_timer_running;

    /* The actual current copy of the SDR repository. */
    unsigned int num_sdrs;
    unsigned int sdr_array_size;
    ipmi_sdr_t *sdrs;

    char db_key[32+5];
    int  db_key_set;

#ifdef DEBUG_INFO_TRACKING
    struct {
	int            line;
	const char     *filename;
	const char     *function;
	long           time;
    } last[1000];
#define DEBUG_INFO(info) do { struct timeval _tv; gettimeofday(&_tv, NULL); \
			      memcpy(info->last, info->last+1,		\
			             sizeof(info->last[0]) * 999);	\
			      info->last[999].time = _tv.tv_sec;	\
			      info->last[999].filename = __FILE__;	\
			      info->last[999].line = __LINE__;		\
			      info->last[999].function = __FUNCTION__; }\
			 while(0)
#else
#define DEBUG_INFO(info)
#endif
};

static void internal_destroy_sdr_info(ipmi_sdr_info_t *sdrs);
static void restart_timer_cb(void *cb_data, os_hnd_timer_id_t *id);


static inline void sdr_lock(ipmi_sdr_info_t *sdrs)
{
    ipmi_lock(sdrs->sdr_lock);
}

static inline void sdr_unlock(ipmi_sdr_info_t *sdrs)
{
    ipmi_unlock(sdrs->sdr_lock);
}

static void
free_fetch(ilist_iter_t *iter, void *item, void *cb_data)
{
    ilist_delete(iter);
    ipmi_mem_free(item);
}

static void
cancel_fetch(ilist_iter_t *iter, void *item, void *cb_data)
{
    fetch_info_t *info = item;

    info->fetch_retry_num = -1;
    ilist_delete(iter);
}

static void
cleanup_fetch_items(ipmi_sdr_info_t *sdrs)
{
    ilist_iter(sdrs->free_fetch, free_fetch, NULL);
    ilist_iter(sdrs->process_fetch, free_fetch, NULL);
    ilist_iter(sdrs->outstanding_fetch, cancel_fetch, NULL);
}

static void
process_db_data(ipmi_sdr_info_t *sdrs,
		unsigned char   *db_data,
		unsigned int    len)
{
    int           num;
    unsigned char *d;
    ipmi_sdr_t    *to_free;

    if (len < 9)
	goto no_db;

    /* Format# is the last byte. */
    d = db_data + len - 1;
    if (*d != 1)
	goto no_db;

    /* timestamps are the 8 bytes before the format#. */
    d -= 8;
    sdrs->last_addition_timestamp = ipmi_get_uint32(d);
    d += 4;
    sdrs->last_erase_timestamp = ipmi_get_uint32(d);
    d += 4;
    len -= 9;
    num = len / sizeof(ipmi_sdr_t);
    /* Allocate 9 extra bytes for storing the timestamps and
     * format#. */
    to_free = sdrs->sdrs;
    sdrs->sdrs = ipmi_mem_alloc((sizeof(ipmi_sdr_t) * num) + 9);
    if (!sdrs->sdrs)
	goto no_db;
    memcpy(sdrs->sdrs, db_data, sizeof(ipmi_sdr_t) * num);
    sdrs->num_sdrs = num;
    sdrs->sdr_array_size = num;
    sdrs->fetched = 1;
    if (to_free)
	ipmi_mem_free(to_free);

 no_db:
    sdrs->os_hnd->database_free(sdrs->os_hnd, db_data);
}

static void
db_fetched(void          *cb_data,
	   int           err,
	   unsigned char *db_data,
	   unsigned int  db_data_len)
{
    ipmi_sdr_info_t *sdrs = cb_data;

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	internal_destroy_sdr_info(sdrs);
	return;
    }

    /* Note that since this is run from the opq, there is no reason to
       check to see if another fetch is going on and has finished.  We
       are guaranteed that this works. */
    if (!err)
	process_db_data(sdrs, db_data, db_data_len);

    sdrs->db_fetching = 0;
    sdr_unlock(sdrs);
    if (!err)
	sdrs->os_hnd->database_free(sdrs->os_hnd, db_data);
    opq_op_done(sdrs->sdr_wait_q);
}

static int
start_db_fetch(void *cb_data, int shutdown)
{
    ipmi_sdr_info_t *sdrs = cb_data;
    int             rv = ENOSYS;

    if (shutdown)
	return OPQ_HANDLER_STARTED;

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	internal_destroy_sdr_info(sdrs);
	return OPQ_HANDLER_ABORTED;
    }

    /* Go ahead and do the database fetch here if we have support. */
    if (sdrs->os_hnd->database_find && sdrs->db_key_set)
    {
	unsigned char *db_data;
	unsigned int  db_data_len;
	unsigned int  data_fetched = 0;

	rv = sdrs->os_hnd->database_find(sdrs->os_hnd,
					 sdrs->db_key,
					 &data_fetched,
					 &db_data,
					 &db_data_len,
					 db_fetched,
					 sdrs);
	/* If the above fails, no problem, the db_data will be NULL. */
	if (!rv) {
	    if (data_fetched) {
		process_db_data(sdrs, db_data, db_data_len);
		rv = -1; /* Just mark it as done */
	    }
	}
    } else {
	rv = -1; /* Just mark it as done */
    }

    if (rv) {
	sdrs->db_fetching = 0;
	sdr_unlock(sdrs);
	return OPQ_HANDLER_ABORTED;
    } else {
	sdr_unlock(sdrs);
	return OPQ_HANDLER_STARTED;
    }
}

int
ipmi_sdr_info_alloc(ipmi_domain_t   *domain,
		    ipmi_mc_t       *mc,
		    unsigned int    lun,
		    int             sensor,
		    ipmi_sdr_info_t **new_sdrs)
{
    ipmi_sdr_info_t *sdrs = NULL;
    int             rv;
    fetch_info_t    *info;
    int             i;
    os_handler_t    *os_hnd = ipmi_domain_get_os_hnd(domain);

    CHECK_MC_LOCK(mc);

    if (lun >= 4)
	return EINVAL;

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

    i = ipmi_mc_get_name(mc, sdrs->name, sizeof(sdrs->name));
    snprintf(sdrs->name+i, sizeof(sdrs->name)-i, "(%c,%d) ",
	     sensor ? 's' : 'm', lun);

    sdrs->mc = ipmi_mc_convert_to_id(mc);
    sdrs->os_hnd = os_hnd;
    sdrs->destroyed = 0;
    sdrs->sdr_lock = NULL;
    sdrs->fetched = 0;
    sdrs->fetch_state = IDLE;
    sdrs->sdrs = NULL;
    sdrs->num_sdrs = 0;
    sdrs->sdr_array_size = 0;
    sdrs->destroy_handler = NULL;
    sdrs->lun = lun;
    sdrs->sensor = sensor;
    sdrs->sdr_wait_q = NULL;
    /* use guaranteed size */
    sdrs->fetch_size = STD_SDR_FETCH_BYTES;

    /* Assume we have a dynamic population until told otherwise. */
    sdrs->dynamic_population = 1;

    sdrs->use_cache = ipmi_option_use_cache(domain);

    rv = ipmi_create_lock(domain, &sdrs->sdr_lock);
    if (rv)
	goto out_done;

    rv = os_hnd->alloc_timer(os_hnd, &(sdrs->restart_timer));
    if (rv)
	goto out_done;

    sdrs->free_fetch = alloc_ilist();
    if (!sdrs->free_fetch) {
	rv = ENOMEM;
	goto out_done;
    }

    sdrs->outstanding_fetch = alloc_ilist();
    if (!sdrs->outstanding_fetch) {
	rv = ENOMEM;
	goto out_done;
    }

    for (i=0; i<MAX_SDR_FETCH_OUTSTANDING; i++) {
	info = ipmi_mem_alloc(sizeof(*info));
	if (!info) {
	    rv = ENOMEM;
	    goto out_done;
	}
	info->sdrs = sdrs;
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
    }

    sdrs->process_fetch = alloc_ilist();
    if (!sdrs->process_fetch) {
	rv = ENOMEM;
	goto out_done;
    }

    sdrs->sdr_wait_q = opq_alloc(os_hnd);
    if (! sdrs->sdr_wait_q) {
	rv = ENOMEM;
	goto out_done;
    }

 out_done:
    if (rv) {
	if (sdrs) {
	    if (sdrs->free_fetch) {
		ilist_iter(sdrs->free_fetch, free_fetch, NULL);
		free_ilist(sdrs->free_fetch);
	    }
	    if (sdrs->outstanding_fetch)
		free_ilist(sdrs->outstanding_fetch);
	    if (sdrs->process_fetch)
		free_ilist(sdrs->process_fetch);
	    if (sdrs->sdr_lock)
		ipmi_destroy_lock(sdrs->sdr_lock);
	    ipmi_mem_free(sdrs);
	}
    } else {
	*new_sdrs = sdrs;
    }
 out:
    return rv;
}

void
ipmi_sdr_set_mc(ipmi_sdr_info_t *sdrs, ipmi_mc_t *mc)
{
    sdrs->mc = ipmi_mc_convert_to_id(mc);
}

static void
internal_destroy_sdr_info(ipmi_sdr_info_t *sdrs)
{
    /* We don't have to have a valid ipmi to destroy an SDR, they are
       designed to live after the ipmi has been destroyed. */

    cleanup_fetch_items(sdrs);

    sdr_unlock(sdrs);

    free_ilist(sdrs->free_fetch);
    free_ilist(sdrs->outstanding_fetch);
    free_ilist(sdrs->process_fetch);

    /* We don't have to worry about stopping the timer, this can't be
       called if the timer is running, because a fetch operation would
       be in progress if that was the case. */
    sdrs->os_hnd->free_timer(sdrs->os_hnd, sdrs->restart_timer);

    opq_destroy(sdrs->sdr_wait_q);

    ipmi_destroy_lock(sdrs->sdr_lock);

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

    if (sdrs->sdrs)
	ipmi_mem_free(sdrs->sdrs);
    ipmi_mem_free(sdrs);
}

void
ipmi_sdr_clean_out_sdrs(ipmi_sdr_info_t *sdrs)
{
    if (sdrs->sdrs)
	ipmi_mem_free(sdrs->sdrs);
    sdrs->sdrs = NULL;
    sdrs->dynamic_population = 1;
    sdrs->fetched = 0;
}

void
ipmi_sdr_cleanout_timer(ipmi_sdr_info_t *sdrs)
{
    sdr_lock(sdrs);
    DEBUG_INFO(sdrs);
    if (sdrs->restart_timer_running) {
	/* Stop the timer.  If we fail, the timer handler is
	   running (error is returned from the stop), just let it
	   handle the stop.  Otherwise, we handle the stop. */
	int rv;

	rv = sdrs->os_hnd->stop_timer(sdrs->os_hnd, sdrs->restart_timer);
	if (!rv) {
	    DEBUG_INFO(sdrs);
	    sdr_unlock(sdrs);
	    restart_timer_cb(sdrs, sdrs->restart_timer);
	    goto out;
	}
    }
    sdr_unlock(sdrs);
 out:
    return;
}

int
ipmi_sdr_info_destroy(ipmi_sdr_info_t      *sdrs,
		      ipmi_sdr_destroyed_t handler,
		      void                 *cb_data)
{
    /* We don't need the read lock, because the sdrs are stand-alone
       after they are created (except for fetching SDRs, of course). */
    sdr_lock(sdrs);
    DEBUG_INFO(sdrs);
    if (sdrs->destroyed) {
	sdr_unlock(sdrs);
	return EINVAL;
    }
    sdrs->destroyed = 1;
    sdrs->destroy_handler = handler;
    sdrs->destroy_cb_data = cb_data;
    if ((sdrs->fetch_state != IDLE) || sdrs->db_fetching) {
	/* It's currently in fetch state, so let it be destroyed in
           the handler, since we can't cancel the handler or
           operation. */
	DEBUG_INFO(sdrs);
	if (sdrs->restart_timer_running) {
	    /* Stop the timer.  If we fail, the timer handler is
	       running (error is returned from the stop), just let it
	       handle the stop.  Otherwise, we handle the stop. */
	    int rv;

	    rv = sdrs->os_hnd->stop_timer(sdrs->os_hnd, sdrs->restart_timer);
	    if (!rv) {
		DEBUG_INFO(sdrs);
		sdr_unlock(sdrs);
		restart_timer_cb(sdrs, sdrs->restart_timer);
		goto out1;
	    }
	}
	sdr_unlock(sdrs);
    out1:
	return 0;
    }

    /* This unlocks the lock. */
    internal_destroy_sdr_info(sdrs);
    return 0;
}

/* Must be called with the SDR locked.  This will unlock the SDR
   before calling the callback, and will return with the sdr unlocked. */
static void
fetch_complete(ipmi_sdr_info_t *sdrs, int err)
{
    DEBUG_INFO(sdrs);
    sdrs->wait_err = err;
    if (err) {
	DEBUG_INFO(sdrs);
	if (sdrs->working_sdrs) {
	    ipmi_mem_free(sdrs->working_sdrs);
	    sdrs->working_sdrs = NULL;
	}
    } else {
	/* The wierd to_free business is because at some points we put
	   the sdrs into the working_sdrs so they will be restored
	   properly. */
	ipmi_sdr_t *to_free = NULL;

	DEBUG_INFO(sdrs);
	sdrs->fetched = 1;
	sdrs->num_sdrs = sdrs->curr_read_idx+1;
	sdrs->sdr_array_size = sdrs->num_sdrs;
	if (sdrs->sdrs != sdrs->working_sdrs)
	    to_free = sdrs->sdrs;
	sdrs->sdrs = sdrs->working_sdrs;
	sdrs->working_sdrs = NULL;
	if (to_free)
	    ipmi_mem_free(to_free);

	if (sdrs->sdrs && sdrs->db_key_set && sdrs->os_hnd->database_store) {
	    unsigned int  len = sdrs->num_sdrs * sizeof(ipmi_sdr_t);
	    unsigned char *d = ((unsigned char *) sdrs->sdrs) + len;

	    /* We always allocate 9 extra bytes in the SDR data to put
	       the timestamps and format at the end. */
	    ipmi_set_uint32(d, sdrs->last_addition_timestamp);
	    d += 4;
	    ipmi_set_uint32(d, sdrs->last_erase_timestamp);
	    d += 4;
	    *d = 1; /* format # */
	    len += 9;
	    sdrs->os_hnd->database_store(sdrs->os_hnd,
					 sdrs->db_key,
					 (unsigned char *) sdrs->sdrs,
					 len);
	}
    }
    sdrs->fetch_state = HANDLERS;
    sdr_unlock(sdrs);

    opq_op_done(sdrs->sdr_wait_q);

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	DEBUG_INFO(sdrs);
	internal_destroy_sdr_info(sdrs);
	/* The previous call unlocks the lock. */
	return;
    }

    if (sdrs->fetch_state == HANDLERS)
	/* The fetch process wasn't restarted, so go to IDLE. */
	sdrs->fetch_state = IDLE;

    sdr_unlock(sdrs);
}

static int start_fetch(ipmi_sdr_info_t *sdrs, ipmi_mc_t *mc, int delay);

static void
handle_reservation_check(ipmi_mc_t  *mc,
			 ipmi_msg_t *rsp,
			 void       *rsp_data)
{
    int             rv;
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) rsp_data;

    sdr_lock(sdrs);
    DEBUG_INFO(sdrs);
    if (sdrs->destroyed) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_reservation_check): "
		 "SDR info was destroyed while an operation was in"
		 " progress(1)", sdrs->name);
	fetch_complete(sdrs, ECANCELED);
	goto out;
    }

    if (!mc) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_reservation_check): "
		 "MC went away while SDR fetch was in progress(1)",
		 sdrs->name);
	fetch_complete(sdrs, ECANCELED);
	goto out;
    }
	
    if (rsp->data[0] == IPMI_INVALID_RESERVATION_CC) {
	DEBUG_INFO(sdrs);
	/* We lost our reservation, restart the operation.  Only do
           this so many times, in order to guarantee that this
           completes. */
	sdrs->fetch_retry_count++;
	if (sdrs->fetch_retry_count > MAX_SDR_FETCH_RETRIES) {
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_reservation_check): "
		     "Lost reservation too many times trying to"
		     " fetch the SDRs", sdrs->name);
	    fetch_complete(sdrs, EAGAIN);
	    goto out;
	} else {
	    if (sdrs->working_sdrs) {
		ipmi_mem_free(sdrs->working_sdrs);
		sdrs->working_sdrs = NULL;
	    }
	    rv = start_fetch(sdrs, mc, 1);
	    if (rv) {
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%ssdr.c(handle_reservation_check): "
			 "Could not start the SDR fetch: %x", sdrs->name, rv);
		fetch_complete(sdrs, rv);
		goto out;
	    }
	}
	sdr_unlock(sdrs);
	goto out;
    }

    if (rsp->data[0] != 0) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_reservation_check): "
		 "IPMI error from SDR fetch reservation check: %x",
		 sdrs->name, rsp->data[0]);
	fetch_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	goto out;
    }

    fetch_complete(sdrs, 0);
 out:
    return;
}

/* Must be called with the sdr lock held, it will release it and
   return with the lock not held. */
static void
start_reservation_check(ipmi_sdr_info_t *sdrs, ipmi_mc_t *mc)
{
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;
    int             rv;

    DEBUG_INFO(sdrs);
    /* We block the wait queue so any new members after this will be
       always fetch all the SDRs.  Then we do one final fetch to check
       our reservation.  There are possible race conditions where an
       event comes in right at the end of an SDR fetch, if we didn't
       do this, it's possible that we would not re-fetch the SDRs when
       they have changed due to an event. */
    opq_add_block(sdrs->sdr_wait_q);
    
    cmd_msg.data = cmd_data;
    if (sdrs->sensor) {
	DEBUG_INFO(sdrs);
	cmd_msg.netfn = IPMI_SENSOR_EVENT_NETFN;
	cmd_msg.cmd = IPMI_GET_DEVICE_SDR_CMD;
    } else {
	DEBUG_INFO(sdrs);
	cmd_msg.netfn = IPMI_STORAGE_NETFN;
	cmd_msg.cmd = IPMI_GET_SDR_CMD;
    }
    cmd_msg.data_len = 6;
    ipmi_set_uint16(cmd_msg.data, sdrs->reservation);
    ipmi_set_uint16(cmd_msg.data+2, 0);
    cmd_msg.data[4] = 0;
    cmd_msg.data[5] = 1; /* Only care about the reservation */
    rv = ipmi_mc_send_command(mc, sdrs->lun, &cmd_msg,
			      handle_reservation_check, sdrs);
    if (rv) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(start_reservation_check): "
		 "Could not send command to get an SDR: %x", sdrs->name, rv);
	fetch_complete(sdrs, rv);
	return;
    }
    sdr_unlock(sdrs);
}

#define SDR_HEADER_SIZE 5
static void
process_sdr_info(ipmi_sdr_info_t *sdrs, fetch_info_t *info)
{
    ipmi_sdr_t *sdr;

    sdr = &sdrs->working_sdrs[info->idx];
    if (info->offset == 0) {
	sdr->record_id = ipmi_get_uint16(info->data+2);
	sdr->major_version = info->data[4] & 0xf;
	sdr->minor_version = (info->data[4] >> 4) & 0xf;
	sdr->type = info->data[5];
	sdr->length = info->data[6];
    } else {
	memcpy(&sdr->data[info->offset-SDR_HEADER_SIZE],
	       info->data+2, info->read_len);
    }
    if (info->offset+info->read_len == (uint32_t)sdr->length+SDR_HEADER_SIZE) {
	sdrs->curr_rec_id = ipmi_get_uint16(info->data);
	sdrs->read_offset = 0;
    } else {
	sdrs->read_offset += info->read_len;
    }
}

typedef struct process_info_s
{
    ipmi_sdr_info_t *sdrs;
    int processed;
} process_info_t;

static void
check_and_process_info(ilist_iter_t *iter, void *item, void *cb_data)
{
    process_info_t  *pinfo = cb_data;
    ipmi_sdr_info_t *sdrs = pinfo->sdrs;
    fetch_info_t    *info = item;

    if ((info->sdr_rec == sdrs->curr_rec_id)
	&& (info->offset == sdrs->read_offset))
    {
	if (iter)
	    ilist_delete(iter);
	pinfo->processed = 1;
	process_sdr_info(sdrs, info);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
    }
}

typedef struct cancel_same_or_newer_s
{
    ipmi_sdr_info_t *sdrs;
    unsigned int    idx;
} cancel_same_or_newer_t;

static void
cancel_if_same_or_newer(ilist_iter_t *iter, void *item, void *cb_data)
{
    cancel_same_or_newer_t *info = cb_data;
    fetch_info_t           *finfo = item;

    if (finfo->idx >= info->idx)
	finfo->fetch_retry_num = -1;
}

static void
free_if_same_or_newer(ilist_iter_t *iter, void *item, void *cb_data)
{
    cancel_same_or_newer_t *info = cb_data;
    fetch_info_t           *finfo = item;

    if (finfo->idx >= info->idx) {
	ilist_delete(iter);
	ilist_add_tail(info->sdrs->free_fetch, finfo, &finfo->link);
    }
}

static void
cancel_same_or_newer(ipmi_sdr_info_t *sdrs, int idx)
{
    cancel_same_or_newer_t info;

    info.sdrs = sdrs;
    info.idx = idx;
    ilist_iter(sdrs->outstanding_fetch, cancel_if_same_or_newer, &info);
    ilist_iter(sdrs->process_fetch, free_if_same_or_newer, &info);
}

static void handle_sdr_data(ipmi_mc_t  *mc,
			    ipmi_msg_t *rsp,
			    void       *rsp_data);

static int
info_send(ipmi_sdr_info_t *sdrs, fetch_info_t *info, ipmi_mc_t *mc)
{
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;
    int             rv;

    cmd_msg.data = cmd_data;
    if (sdrs->sensor) {
	DEBUG_INFO(sdrs);
	cmd_msg.netfn = IPMI_SENSOR_EVENT_NETFN;
	cmd_msg.cmd = IPMI_GET_DEVICE_SDR_CMD;
    } else {
	DEBUG_INFO(sdrs);
	cmd_msg.netfn = IPMI_STORAGE_NETFN;
	cmd_msg.cmd = IPMI_GET_SDR_CMD;
    }
    cmd_msg.data_len = 6;
    ipmi_set_uint16(cmd_msg.data, sdrs->reservation);
    ipmi_set_uint16(cmd_msg.data+2, info->sdr_rec);
    cmd_msg.data[4] = info->offset;
    cmd_msg.data[5] = info->read_len;

    rv = ipmi_mc_send_command(mc, sdrs->lun, &cmd_msg,
			      handle_sdr_data, info);
    if (rv) {
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(info_send): "
		 "initial_sdr_fetch: Couldn't send first SDR fetch: %x",
		 sdrs->name, rv);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
	fetch_complete(sdrs, rv);
    } else {
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->outstanding_fetch, info, &info->link);
    }

    return rv;
}

static void
handle_sdr_data(ipmi_mc_t  *mc,
		ipmi_msg_t *rsp,
		void       *rsp_data)
{
    fetch_info_t    *info = rsp_data;
    ipmi_sdr_info_t *sdrs = info->sdrs;
    process_info_t  pinfo;
    int             rv;

    sdr_lock(sdrs);
    DEBUG_INFO(sdrs);
    if (! ilist_remove_item_from_list(sdrs->outstanding_fetch, info)) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_SEVERE,
		 "%ssdr.c(handle_sdr_data): "
		 "Got SDR data but the info was not in the"
		 " outstanding operation list", sdrs->name);
	goto out_unlock;
    }

    if (sdrs->destroyed) {
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
	if (!ilist_empty(sdrs->outstanding_fetch)) {
	    DEBUG_INFO(sdrs);
	    goto out_unlock;
	}

	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_data): "
		 "SDR info was destroyed while an operation was in"
		 " progress(2)", sdrs->name);
	fetch_complete(sdrs, ECANCELED);
	goto out;
    }

    if (!mc) {
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
	if (!ilist_empty(sdrs->outstanding_fetch)) {
	    DEBUG_INFO(sdrs);
	    goto out_unlock;
	}

	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_data): "
		 "MC went away while SDR fetch was in progress(2)",
		 sdrs->name);
	fetch_complete(sdrs, ECANCELED);
	goto out;
    }

    if (sdrs->waiting_start_fetch) {
	/* A start fetch operation is waiting for the outstanding
           queue to clear, so free this and try again. */
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);

	rv = start_fetch(sdrs, mc, 1);
	if (rv) {
	    DEBUG_INFO(sdrs);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_data): "
		     "Could not start the SDR fetch: %x", sdrs->name, rv);
	    fetch_complete(sdrs, rv);
	    goto out;
	}
	goto out_unlock;
    }
	
    if (info->fetch_retry_num != sdrs->fetch_retry_count) {
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);

	if (sdrs->fetch_retry_count > MAX_SDR_FETCH_RETRIES) {
	    DEBUG_INFO(sdrs);
	    if (!ilist_empty(sdrs->outstanding_fetch)) {
		DEBUG_INFO(sdrs);
		goto out_unlock;
	    }

	    fetch_complete(sdrs, sdrs->fetch_err);
	    goto out;
	}

	DEBUG_INFO(sdrs);
	goto out_nextmsg;
    }

    if (rsp->data[0] == 0x80) {
	/* Data changed during fetch, retry.  Only do this so many
           times before giving up. */
	DEBUG_INFO(sdrs);
	sdrs->sdr_retry_count++;
	if (sdrs->sdr_retry_count > MAX_SDR_FETCH_RETRIES) {
	    /* Cause the operation to be terminated. */
	    DEBUG_INFO(sdrs);
	    sdrs->fetch_retry_count = MAX_SDR_FETCH_RETRIES+1;
	    ilist_add_tail(sdrs->free_fetch, info, &info->link);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_data): "
		     "To many retries trying to fetch SDRs", sdrs->name);

	    sdrs->fetch_err = EAGAIN;

	    if (!ilist_empty(sdrs->outstanding_fetch)) {
		DEBUG_INFO(sdrs);
		goto out_unlock;
	    }

	    fetch_complete(sdrs, EAGAIN);
	    DEBUG_INFO(sdrs);
	    goto out;
	}

	/* Cancel any current or newer pending operations. */
	cancel_same_or_newer(sdrs, info->idx);

	/* Re-start the fetch on the SDR. */
	sdrs->next_read_offset = -1;
	sdrs->read_size = -1;
	sdrs->next_read_rec_id = info->sdr_rec;
	sdrs->curr_read_idx = info->idx-1;

	ilist_add_tail(sdrs->free_fetch, info, &info->link);
	goto out_nextmsg;
    }

    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. */
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
	sdrs->fetch_retry_count++;
	if (sdrs->fetch_retry_count > MAX_SDR_FETCH_RETRIES) {
	    DEBUG_INFO(sdrs);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_data): "
		     "Lost reservation too many times trying to fetch SDRs",
		     sdrs->name);

	    sdrs->fetch_err = EAGAIN;

	    if (!ilist_empty(sdrs->outstanding_fetch)) {
		DEBUG_INFO(sdrs);
		goto out_unlock;
	    }

	    fetch_complete(sdrs, EAGAIN);
	    goto out;
	} else {
	    DEBUG_INFO(sdrs);
	    if (sdrs->working_sdrs) {
		DEBUG_INFO(sdrs);
		ipmi_mem_free(sdrs->working_sdrs);
		sdrs->working_sdrs = NULL;
	    }
	    rv = start_fetch(sdrs, mc, 1);
	    if (rv) {
		DEBUG_INFO(sdrs);
		/* Cause the fetch to be aborted. */
		sdrs->fetch_retry_count = MAX_SDR_FETCH_RETRIES+1;
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%ssdr.c(handle_sdr_data): "
			 "Could not start SDR fetch: %x", sdrs->name, rv);

		sdrs->fetch_err = rv;

		if (!ilist_empty(sdrs->outstanding_fetch)) {
		    DEBUG_INFO(sdrs);
		    goto out_unlock;
		}

		fetch_complete(sdrs, rv);
		goto out;
	    }
	}
	goto out_unlock;
    }

    if ((info->sdr_rec == 0)
	&& ((rsp->data[0] == IPMI_UNKNOWN_ERR_CC)
	    || (rsp->data[0] == IPMI_NOT_PRESENT_CC)))
    {
	/* We got an error fetching the first SDR, so the repository is
	   probably empty.  Just go on. */
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
	start_reservation_check(sdrs, mc);
	goto out;
    }

    if (rsp->data[0] == IPMI_CANNOT_RETURN_REQ_LENGTH_CC) {
	/* It's more than the system can return in a single messages,
	   decrease the size. */
	ilist_add_tail(sdrs->free_fetch, info, &info->link);

	sdrs->fetch_size -= SDR_FETCH_BYTES_DECR;
	if (sdrs->fetch_size < MIN_SDR_FETCH_BYTES) {
	    DEBUG_INFO(sdrs);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_data): "
		     "SDR target chould not support the minimum fetch size",
		     sdrs->name);

	    sdrs->fetch_err = IPMI_IPMI_ERR_VAL(rsp->data[0]);

	    if (!ilist_empty(sdrs->outstanding_fetch)) {
		DEBUG_INFO(sdrs);
		goto out_unlock;
	    }

	    fetch_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	    goto out;
	} else {
	    /* Cancel any current or newer pending operations. */
	    DEBUG_INFO(sdrs);
	    cancel_same_or_newer(sdrs, info->idx);

	    /* Re-start the fetch on this SDR. */
	    sdrs->next_read_offset = -1;
	    sdrs->read_size = -1;
	    sdrs->next_read_rec_id = info->sdr_rec;
	    sdrs->curr_read_idx = info->idx-1;

	    goto out_nextmsg;
	}
    }

    if (rsp->data[0] != 0) {
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
	sdrs->fetch_retry_count = MAX_SDR_FETCH_RETRIES+1;

	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_data): "
		 "SDR fetch error getting sdr 0x%x: %x",
		 sdrs->name, info->sdr_rec, rsp->data[0]);

	sdrs->fetch_err = IPMI_IPMI_ERR_VAL(rsp->data[0]);

	if (!ilist_empty(sdrs->outstanding_fetch)) {
	    DEBUG_INFO(sdrs);
	    goto out_unlock;
	}

	fetch_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	goto out;
    }

    if (rsp->data_len < info->read_len+3) {
	/* We got back an invalid amount of data, abort */
	DEBUG_INFO(sdrs);
	ilist_add_tail(sdrs->free_fetch, info, &info->link);
	sdrs->fetch_retry_count = MAX_SDR_FETCH_RETRIES+1;
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_data): "
		 "Got an invalid amount of SDR data: %d, expected %d",
		 sdrs->name, rsp->data_len, info->read_len+3);

	sdrs->fetch_err = EINVAL;

	if (!ilist_empty(sdrs->outstanding_fetch)) {
	    DEBUG_INFO(sdrs);
	    goto out_unlock;
	}

	fetch_complete(sdrs, EINVAL);
	goto out;
    }

    /* We have a good response */

    /* First handle the info for fetching data. */
    if (info->offset == 0) {
	/* We read a header. */
	DEBUG_INFO(sdrs);
	sdrs->read_size = rsp->data[7] + SDR_HEADER_SIZE;
	sdrs->next_read_rec_id = ipmi_get_uint16(rsp->data+1);
	sdrs->next_read_offset = info->read_len;
    }

    /* Now process it for the user. */
    memcpy(info->data, rsp->data+1, rsp->data_len-1);

    pinfo.processed = 0;
    pinfo.sdrs = sdrs;
    check_and_process_info(NULL, info, &pinfo);
    if (pinfo.processed) {
	/* Since we may have processed a previous one, check the ones
	   we have already received that were received out of
	   order. */
	DEBUG_INFO(sdrs);
	ilist_iter(sdrs->process_fetch, check_and_process_info, &pinfo);
    } else {
	ilist_iter_t iter;
	int          pos;
	fetch_info_t *ninfo;
	int          found = 0;

	DEBUG_INFO(sdrs);
	/* It is not the reponse we are expecting, just throw it onto
           the queue in order to be handled later. */
	ilist_init_iter(&iter, sdrs->process_fetch);
	pos = ilist_last(&iter);
	while (pos) {
	    ninfo = ilist_get(&iter);
	    if ((info->idx > ninfo->idx) || (info->offset > ninfo->offset)) {
		found = 1;
		break;
	    }
	    pos = ilist_prev(&iter);
	}

	if (found) {
	    DEBUG_INFO(sdrs);
	    ilist_add_after(&iter, info, &info->link);
	} else {
	    DEBUG_INFO(sdrs);
	    ilist_add_before(&iter, info, &info->link);
	}
    }

 out_nextmsg:
    while (!ilist_empty(sdrs->free_fetch)) {
	/* We have some free buffers, see what we can do with them. */

	if (sdrs->next_read_offset == 0)
	    /* We need to get the SDR header before we can go on. */
	    break;

	if (sdrs->next_read_offset == sdrs->read_size) {
	    /* Done with this SDR, time to go to the next. */
	    if (sdrs->next_read_rec_id == 0xffff) {
		/* This is the last SDR.  However, we don't go to the
		   next stage until all the outstanding fetches are
		   complete. */
		if (ilist_empty(sdrs->outstanding_fetch)) {
		    start_reservation_check(sdrs, mc);
		    goto out;
		}
		break;
	    }

	    if ((unsigned int) (sdrs->curr_read_idx+1)
		>= sdrs->working_num_sdrs)
	    {
		if (sdrs->sensor && (sdrs->working_num_sdrs < 512)) {
		    /* The get device SDR command (stupidly) only
		       reports the number of sensors, not the number
		       of SDRs.  So we have to be able to expand, but
		       keep it within reason (thus the "512" check
		       above). */
		    unsigned int new_num_sdrs = sdrs->working_num_sdrs + 10;
		    ipmi_sdr_t *new_sdrs;

		    /* Allocate 9 extra bytes for the db info. */
		    new_sdrs = ipmi_mem_alloc((sizeof(ipmi_sdr_t)
					       * new_num_sdrs) + 9);
		    if (!new_sdrs) {
			ipmi_log(IPMI_LOG_ERR_INFO,
				 "%ssdr.c(handle_sdr_data): "
				 "SDR respository had more SDRs than"
				 " originally thougt, but could not expand"
				 " the SDR array because out of memory",
				 sdrs->name);
			fetch_complete(sdrs, ENOMEM);
			goto out;
		    }
		    memcpy(new_sdrs, sdrs->working_sdrs,
			   sdrs->working_num_sdrs * sizeof(ipmi_sdr_t));
		    ipmi_mem_free(sdrs->working_sdrs);
		    sdrs->working_sdrs = new_sdrs;
		    sdrs->working_num_sdrs = new_num_sdrs;
		} else {
		    ipmi_log(IPMI_LOG_ERR_INFO,
			     "%ssdr.c(handle_sdr_data): "
			     "Fetched more SDRs than the info said there were",
			     sdrs->name);
		
		    sdrs->fetch_err = EINVAL;
	    
		    if (!ilist_empty(sdrs->outstanding_fetch))
			goto out_unlock;
	    
		    fetch_complete(sdrs, EINVAL);
		    goto out;
		}
	    }
	}

	info = ilist_remove_first(sdrs->free_fetch);
	info->fetch_retry_num = sdrs->fetch_retry_count;

	if (sdrs->next_read_offset == sdrs->read_size) {
	    /* header is the next read. */
	    DEBUG_INFO(sdrs);
	    sdrs->curr_read_rec_id = sdrs->next_read_rec_id;
	    sdrs->curr_read_idx++;
	    sdrs->next_read_offset = 0;
	    info->offset = sdrs->next_read_offset;
	    info->read_len = SDR_HEADER_SIZE;
	} else {
	    DEBUG_INFO(sdrs);
	    info->read_len = sdrs->read_size - sdrs->next_read_offset;
	    if (info->read_len > sdrs->fetch_size)
		info->read_len = sdrs->fetch_size;
	    info->offset = sdrs->next_read_offset;
	    sdrs->next_read_offset += info->read_len;
	}
	
	info->sdr_rec = sdrs->curr_read_rec_id;
	info->idx = sdrs->curr_read_idx;
	rv = info_send(sdrs, info, mc);
	if (rv) {
	    DEBUG_INFO(sdrs);
	    sdrs->fetch_retry_count = MAX_SDR_FETCH_RETRIES+1;
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_data): "
		     "Could not send SDR fetch: %x", sdrs->name, rv);
	    
	    sdrs->fetch_err = rv;
	    
	    if (!ilist_empty(sdrs->outstanding_fetch)) {
		DEBUG_INFO(sdrs);
		goto out_unlock;
	    }
	    
	    fetch_complete(sdrs, rv);
	    goto out;
	}
    }

 out_unlock:
    DEBUG_INFO(sdrs);
    sdr_unlock(sdrs);
 out:
    DEBUG_INFO(sdrs);
    return;
}

static int
initial_sdr_fetch(ipmi_sdr_info_t *sdrs, ipmi_mc_t *mc)
{
    fetch_info_t    *info;

    DEBUG_INFO(sdrs);
    info = ilist_remove_first(sdrs->free_fetch);
    if (!info) {
	/* Technically this cannot fail, but just in case... */
	DEBUG_INFO(sdrs);
	return ENOMEM;
    }
    info->sdr_rec = sdrs->curr_rec_id;
    info->offset = 0;
    /* If all systems were implemented correctly, we could do a big
       fetch here and if it was too big then they would just return
       what was available.  Some systems, though, are picky about the
       sizes being exactly right.  So we fetch the header first so we
       can get the size. */
    info->read_len = SDR_HEADER_SIZE;
    info->fetch_retry_num = sdrs->fetch_retry_count;
    info->idx = sdrs->curr_read_idx;
    return info_send(sdrs, info, mc);
}

static void
handle_reservation(ipmi_mc_t  *mc,
		   ipmi_msg_t *rsp,
		   void       *rsp_data)
{
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) rsp_data;
    int             rv;


    sdr_lock(sdrs);
    DEBUG_INFO(sdrs);
    if (sdrs->destroyed) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_reservation): "
		 "SDR info was destroyed while an operation was in"
		 " progress(3)", sdrs->name);
	fetch_complete(sdrs, ECANCELED);
	goto out;
    }

    if (!mc) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_reservation): "
		 "MC went away while SDR fetch was in progress(3)",
		 sdrs->name);
	fetch_complete(sdrs, ECANCELED);
	goto out;
    }
	
    if (rsp->data[0] != 0) {
	DEBUG_INFO(sdrs);
	if (sdrs->sensor && (rsp->data[0] == IPMI_INVALID_CMD_CC)) {
	    DEBUG_INFO(sdrs);
	    /* This is a special case.  We always attempt a
               reservation with a device SDR (since there is nothing
               telling us if this is supported), if it fails then we
               just go on without the reservation. */
	    sdrs->supports_reserve_sdr = 0;
	    sdrs->reservation = 0;
	    goto reservation_set;
	}
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_reservation): "
		 "Error getting SDR fetch reservation: %x",
		 sdrs->name, rsp->data[0]);
	fetch_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	goto out;
    }
    if (rsp->data_len < 3) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_reservation): "
		 "SDR Reservation data not long enough", sdrs->name);
	fetch_complete(sdrs, EINVAL);
	goto out;
    }

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

 reservation_set:
    /* Fetch the first part of the SDR. */
    DEBUG_INFO(sdrs);
    rv = initial_sdr_fetch(sdrs, mc);
    if (rv) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_reservation): "
		 "initial SDR fetch failed: %x", sdrs->name, rv);
	fetch_complete(sdrs, EINVAL);
	goto out;
    }

    sdr_unlock(sdrs);
 out:
    return;
}

static void
handle_sdr_info(ipmi_mc_t  *mc,
		ipmi_msg_t *rsp,
		void       *rsp_data)
{
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) rsp_data;
    ipmi_msg_t      cmd_msg;
    int             rv;
    uint32_t        add_timestamp;
    uint32_t        erase_timestamp;


    sdr_lock(sdrs);
    DEBUG_INFO(sdrs);
    if (sdrs->destroyed) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_info): "
		 "SDR info was destroyed while an operation was in"
		 " progress(4)", sdrs->name);
	fetch_complete(sdrs, ECANCELED);
	goto out;
    }

    if (!mc) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_info): "
		 "MC went away while SDR fetch was in progress(4)",
		 sdrs->name);
	fetch_complete(sdrs, ECANCELED);
	goto out;
    }
	
    if (rsp->data[0] != 0) {
	if (sdrs->sensor) {
	    DEBUG_INFO(sdrs);
	    /* The device doesn't support the get device SDR info
               command, so just assume some defaults. */
	    sdrs->working_num_sdrs = 256;
	    sdrs->dynamic_population = 0;

	    /* Assume it uses reservations, if the reservation returns
               an error, then say that it doesn't. */
	    sdrs->supports_reserve_sdr = 1;

	    (sdrs->lun_has_sensors)[0] = 1;
	    (sdrs->lun_has_sensors)[1] = 0;
	    (sdrs->lun_has_sensors)[2] = 0;
	    (sdrs->lun_has_sensors)[3] = 0;

	    add_timestamp = 0;
	    erase_timestamp = 0;
	} else {
	    DEBUG_INFO(sdrs);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_info): "
		     "IPMI Error getting SDR info: %x",
		     sdrs->name, rsp->data[0]);
	    fetch_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	    goto out;
	}
    } else if (sdrs->sensor) {
	if (rsp->data_len < 3) {
	    DEBUG_INFO(sdrs);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_info): "
		     "SDR info is not long enough", sdrs->name);
	    fetch_complete(sdrs, EINVAL);
	    goto out;
	}

	sdrs->working_num_sdrs = rsp->data[1];
	sdrs->dynamic_population = (rsp->data[2] & 0x80) == 0x80;

	/* Assume it uses reservations, if the reservation returns
	   an error, then say that it doesn't. */
	sdrs->supports_reserve_sdr = 1;

	(sdrs->lun_has_sensors)[0] = (rsp->data[2] & 0x01) == 0x01;
	(sdrs->lun_has_sensors)[1] = (rsp->data[2] & 0x01) == 0x02;
	(sdrs->lun_has_sensors)[2] = (rsp->data[2] & 0x01) == 0x04;
	(sdrs->lun_has_sensors)[3] = (rsp->data[2] & 0x01) == 0x08;

	if (sdrs->dynamic_population) {
	    if (rsp->data_len < 7) {
		DEBUG_INFO(sdrs);
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%ssdr.c(handle_sdr_info): "
			 "SDR info is not long enough", sdrs->name);
		fetch_complete(sdrs, EINVAL);
		goto out;
	    }
	    DEBUG_INFO(sdrs);
	    add_timestamp = ipmi_get_uint32(rsp->data + 3);
	} else {
	    DEBUG_INFO(sdrs);
	    add_timestamp = 0;
	}
	erase_timestamp = 0;
    } else {
	DEBUG_INFO(sdrs);
	if (rsp->data_len < 15) {
	    DEBUG_INFO(sdrs);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_info): "
		     "SDR info is not long enough", sdrs->name);
	    fetch_complete(sdrs, EINVAL);
	    goto out;
	}

	/* Pull pertinant info from the response. */
	sdrs->major_version = rsp->data[1] & 0xf;
	sdrs->minor_version = (rsp->data[1] >> 4) & 0xf;
	sdrs->working_num_sdrs = ipmi_get_uint16(rsp->data+2);
	sdrs->overflow = (rsp->data[14] & 0x80) == 0x80;
	sdrs->update_mode = (rsp->data[14] >> 5) & 0x3;
	sdrs->supports_delete_sdr = (rsp->data[14] & 0x08) == 0x08;
	sdrs->supports_partial_add_sdr = (rsp->data[14] & 0x04) == 0x04;
	sdrs->supports_reserve_sdr = (rsp->data[14] & 0x02) == 0x02;
	sdrs->supports_get_sdr_repository_allocation
	    = (rsp->data[14] & 0x01) == 0x01;

	add_timestamp = ipmi_get_uint32(rsp->data + 6);
	erase_timestamp = ipmi_get_uint32(rsp->data + 10);
    }

    /* If the timestamps still match, no need to re-fetch the repository */
    if (sdrs->fetched
	&& (add_timestamp == sdrs->last_addition_timestamp)
	&& (erase_timestamp == sdrs->last_erase_timestamp))
    {
	DEBUG_INFO(sdrs);
	/* Set these so the fetch complete handler will put them back. */
	sdrs->curr_read_idx = sdrs->num_sdrs-1;
	sdrs->working_sdrs = sdrs->sdrs;
	fetch_complete(sdrs, 0);
	goto out;
    }

    sdrs->last_addition_timestamp = add_timestamp;
    sdrs->last_erase_timestamp = erase_timestamp;

    sdrs->sdrs_changed = 1;

    if (sdrs->working_num_sdrs == 0) {
	/* No sdrs, so there's nothing to do. */
	if (sdrs->sdrs) {
	    DEBUG_INFO(sdrs);
	    ipmi_mem_free(sdrs->sdrs);
	    sdrs->sdrs = NULL;
	}
	DEBUG_INFO(sdrs);
	sdrs->curr_read_idx = -1;
	fetch_complete(sdrs, 0);
	goto out;
    }

    /* Allocate 9 extra bytes for the db info. */
    sdrs->working_sdrs = ipmi_mem_alloc((sizeof(ipmi_sdr_t)
					* sdrs->working_num_sdrs) + 9);
    if (!sdrs->working_sdrs) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_info): "
		 "Could not allocate working SDR information",
		 sdrs->name);
	fetch_complete(sdrs, ENOMEM);
	goto out;
    }

    sdrs->curr_rec_id = 0;
    sdrs->read_offset = 0; /* First thing is to read the header. */

    sdrs->next_read_rec_id = 0;
    sdrs->curr_read_rec_id = 0;
    sdrs->curr_read_idx = 0;
    sdrs->next_read_offset = 0; /* First thing is to read the header. */

    if (sdrs->supports_reserve_sdr) {
	/* Now get the reservation. */
	if (sdrs->sensor) {
	    DEBUG_INFO(sdrs);
	    cmd_msg.netfn = IPMI_SENSOR_EVENT_NETFN;
	    cmd_msg.cmd = IPMI_RESERVE_DEVICE_SDR_REPOSITORY_CMD;
	} else {
	    DEBUG_INFO(sdrs);
	    cmd_msg.netfn = IPMI_STORAGE_NETFN;
	    cmd_msg.cmd = IPMI_RESERVE_SDR_REPOSITORY_CMD;
	}
	cmd_msg.data = NULL;
	cmd_msg.data_len = 0;
	rv = ipmi_mc_send_command_sideeff(mc, sdrs->lun, &cmd_msg,
					  handle_reservation, sdrs);
	if (rv) {
	    DEBUG_INFO(sdrs);
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_info): "
		     "handle_sdr_info: Couldn't send SDR reservation: %x",
		     sdrs->name, rv);
	    fetch_complete(sdrs, rv);
	    goto out;
	}
	DEBUG_INFO(sdrs);
    } else {
	/* No reservation support, just go on and start fetching. */
	DEBUG_INFO(sdrs);
	sdrs->reservation = 0;

	/* Fetch the first part of the SDR. */
	rv = initial_sdr_fetch(sdrs, mc);
	if (rv)
	    goto out;
    }
    sdr_unlock(sdrs);
 out:
    return;
}

static int
start_fetch(ipmi_sdr_info_t *sdrs, ipmi_mc_t *mc, int delay)
{
    unsigned char       cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t          cmd_msg;

    DEBUG_INFO(sdrs);
    sdrs->working_sdrs = NULL;
    sdrs->fetch_state = FETCHING;
    sdrs->sdrs_changed = 0;

    if (!ilist_empty(sdrs->outstanding_fetch)) {
	DEBUG_INFO(sdrs);
	sdrs->waiting_start_fetch = 1;
	return 0;
    }

    sdrs->waiting_start_fetch = 0;

    if (delay) {
	/* Start the fetch operation after a random delay. */
	struct timeval tv;

	DEBUG_INFO(sdrs);
	sdrs->os_hnd->get_random(sdrs->os_hnd,
				 &tv.tv_sec,
				 sizeof(tv.tv_sec));
	/* Wait a random value between 10 and 30 seconds */
	if (tv.tv_sec < 0)
	    tv.tv_sec = -tv.tv_sec;
	tv.tv_sec = (tv.tv_sec % 20) + 10;
	tv.tv_usec = 0;
	sdrs->restart_timer_running = 1;
	sdrs->os_hnd->start_timer(sdrs->os_hnd,
				  sdrs->restart_timer,
				  &tv,
				  restart_timer_cb,
				  sdrs);
	return 0;
    } else {
	/* Get the SDR repository information first. */
	cmd_msg.data = cmd_data;
	if (sdrs->sensor) {
	    DEBUG_INFO(sdrs);
	    cmd_msg.netfn = IPMI_SENSOR_EVENT_NETFN;
	    cmd_msg.cmd = IPMI_GET_DEVICE_SDR_INFO_CMD;
	} else {
	    DEBUG_INFO(sdrs);
	    cmd_msg.netfn = IPMI_STORAGE_NETFN;
	    cmd_msg.cmd = IPMI_GET_SDR_REPOSITORY_INFO_CMD;
	}
	cmd_msg.data_len = 0;
	return ipmi_mc_send_command(mc, sdrs->lun, &cmd_msg,
				    handle_sdr_info, sdrs);
    }
}

static void
handle_start_fetch_cb(ipmi_mc_t *mc, void *cb_data)
{
    int             rv;
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) cb_data;

    DEBUG_INFO(sdrs);
    sdrs->wait_err = 0;
    sdrs->sdr_retry_count = 0;
    sdr_lock(sdrs);
    rv = start_fetch(sdrs, mc, 0);
    if (rv) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_start_fetch_cb): "
		 "handle_start_fetch: error requesting SDR reserveration: %x",
		 sdrs->name, rv);
	sdrs->wait_err = rv;
	fetch_complete(sdrs, rv);
    } else {
	sdr_unlock(sdrs);
    }
}

static void
handle_start_fetch(ipmi_sdr_info_t *sdrs)
{
    int rv;

    DEBUG_INFO(sdrs);
    rv = ipmi_mc_pointer_cb(sdrs->mc, handle_start_fetch_cb, sdrs);
    if (rv) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_start_fetch): "
		 "handle_start_fetch: error finding MC: %x",
		 sdrs->name, rv);
	sdrs->wait_err = rv;
	sdr_lock(sdrs);
	fetch_complete(sdrs, rv);
    }
}

static void
restart_timer_cb(void *cb_data, os_hnd_timer_id_t *id)
{
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) cb_data;

    sdr_lock(sdrs);
    DEBUG_INFO(sdrs);
    sdrs->restart_timer_running = 0;
    if (sdrs->destroyed) {
	DEBUG_INFO(sdrs);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(restart_timer_cb): "
		 "SDR info was destroyed while an operation was in"
		 " progress(1)", sdrs->name);
	fetch_complete(sdrs, ECANCELED);
	return;
    }
    sdr_unlock(sdrs);

    handle_start_fetch(sdrs);
}

static int
initial_start_fetch(void *cb_data, int shutdown)
{
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) cb_data;

    DEBUG_INFO(sdrs);
    if (shutdown)
	return OPQ_HANDLER_STARTED;

    sdrs->fetch_retry_count = 0;
    handle_start_fetch(sdrs);

    return OPQ_HANDLER_STARTED;
}

static void
handle_fetch_done(void *cb_data, int shutdown)
{
    sdr_fetch_handler_t *elem = (sdr_fetch_handler_t *) cb_data;

    DEBUG_INFO(elem->sdrs);
    elem->handler(elem->sdrs,
		  elem->sdrs->wait_err,
		  elem->sdrs->sdrs_changed,
		  elem->sdrs->num_sdrs,
		  elem->cb_data);
    ipmi_mem_free(elem);
}

typedef struct sdr_fetch_info_s
{
    ipmi_sdr_info_t     *sdrs;
    ipmi_sdrs_fetched_t handler;
    void                *cb_data;
    int                 rv;
} sdr_fetch_info_t;

static void
sdr_fetch_cb(ipmi_mc_t *mc, void *cb_data)
{
    sdr_fetch_info_t    *info = cb_data;
    ipmi_sdr_info_t     *sdrs = info->sdrs;
    sdr_fetch_handler_t *elem;
    unsigned char       guid[16];

    DEBUG_INFO(sdrs);
    elem = ipmi_mem_alloc(sizeof(*elem));
    if (!elem) {
	DEBUG_INFO(sdrs);
	info->rv = ENOMEM;
	return;
    }
    memset(elem, 0, sizeof(*elem));

    elem->sdrs = sdrs;
    elem->handler = info->handler;
    elem->cb_data = info->cb_data;

    if (sdrs->sensor) {
	DEBUG_INFO(sdrs);
	if (! ipmi_mc_provides_device_sdrs(mc)) {
	    info->rv = ENOSYS;
	    goto out;
	}
    } else {
	DEBUG_INFO(sdrs);
	if (! ipmi_mc_sdr_repository_support(mc)) {
	    info->rv = ENOSYS;
	    goto out;
	}
    }
    DEBUG_INFO(sdrs);

    sdr_lock(sdrs);
    if (!sdrs->fetched && (sdrs->fetch_state == IDLE) && sdrs->use_cache) {
	/* Look in the database before the first fetch. */
	if (ipmi_mc_get_guid(mc, guid) == 0) {
	    char *s;
	    int  i;

	    DEBUG_INFO(sdrs);
	    s = sdrs->db_key;
	    s += sprintf(s, "sdr-");
	    for (i=0; i<16; i++)
		s += sprintf(s, "%2.2x", guid[i]);
	    sdrs->db_key_set = 1;
	}

	sdrs->db_fetching = 1;
	sdr_unlock(sdrs);
	if (!opq_new_op(sdrs->sdr_wait_q, start_db_fetch, sdrs, 0)) {
	    DEBUG_INFO(sdrs);
	    sdrs->db_fetching = 0;
	}
	/*
	 * Note that we go ahead and do a fetch, anyway, even if we
	 * find a database item, in case the data has changed.  We
	 * should detect the data change via timestamp before really
	 * fetching the data.
	 */
    } else
	sdr_unlock(sdrs);

    DEBUG_INFO(sdrs);
    if (! opq_new_op_with_done(sdrs->sdr_wait_q,
			       initial_start_fetch,
			       sdrs,
			       handle_fetch_done,
			       elem))
    {
	DEBUG_INFO(sdrs);
	info->rv = ENOMEM;
    }

 out:
    if (info->rv)
	ipmi_mem_free(elem);
}

int
ipmi_sdr_fetch(ipmi_sdr_info_t     *sdrs,
	       ipmi_sdrs_fetched_t handler,
	       void                *cb_data)
{
    int              rv;
    sdr_fetch_info_t info;

    if (! sdrs->dynamic_population)
	return ENOSYS;

    DEBUG_INFO(sdrs);
    info.sdrs = sdrs;
    info.handler = handler;
    info.cb_data = cb_data;
    info.rv = 0;

    /* Convert the mc id to an mc. */
    rv = ipmi_mc_pointer_cb(sdrs->mc, sdr_fetch_cb, &info);
    if (rv) {
	DEBUG_INFO(sdrs);
	return rv;
    }
    return info.rv;
}

int
ipmi_get_sdr_count(ipmi_sdr_info_t *sdrs,
		   unsigned int    *count)
{
    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *count = sdrs->num_sdrs;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_get_sdr_by_recid(ipmi_sdr_info_t *sdrs,
		      int             recid,
		      ipmi_sdr_t      *return_sdr)
{
    unsigned int i;
    int          rv = ENOENT;

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    for (i=0; i<sdrs->num_sdrs; i++) {
	if (sdrs->sdrs[i].record_id == recid) {
	    rv = 0;
	    *return_sdr = sdrs->sdrs[i];
	    break;
	}
    }

    sdr_unlock(sdrs);
    return rv;
}

int
ipmi_get_sdr_by_type(ipmi_sdr_info_t *sdrs,
		     int             type,
		     ipmi_sdr_t      *return_sdr)
{
    unsigned int i;
    int          rv = ENOENT;

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    for (i=0; i<sdrs->num_sdrs; i++) {
	if (sdrs->sdrs[i].type == type) {
	    rv = 0;
	    *return_sdr = sdrs->sdrs[i];
	    break;
	}
    }

    sdr_unlock(sdrs);
    return rv;
}

int
ipmi_get_sdr_by_index(ipmi_sdr_info_t *sdrs,
		      int             index,
		      ipmi_sdr_t      *return_sdr)
{
    int rv = 0;

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    if ((unsigned int)index >= sdrs->num_sdrs)
	rv = ENOENT;
    else
	*return_sdr = sdrs->sdrs[index];

    sdr_unlock(sdrs);
    return rv;
}

int
ipmi_set_sdr_by_index(ipmi_sdr_info_t *sdrs,
		      int             index,
		      ipmi_sdr_t      *sdr)
{
    int rv = 0;

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    if ((unsigned int)index >= sdrs->num_sdrs)
	rv = ENOENT;
    else
	sdrs->sdrs[index] = *sdr;

    sdr_unlock(sdrs);
    return rv;
}

int ipmi_get_all_sdrs(ipmi_sdr_info_t *sdrs,
		      int             *array_size,
		      ipmi_sdr_t      *array)
{
    unsigned int i;
    int          rv = 0;

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    if ((unsigned int)*array_size < sdrs->num_sdrs) {
	rv = E2BIG;
    } else {
	for (i=0; i<sdrs->num_sdrs; i++) {
	    *array = sdrs->sdrs[i];
	    array++;
	}
	*array_size = sdrs->num_sdrs;
    }

    sdr_unlock(sdrs);
    return rv;
}

int
ipmi_sdr_get_major_version(ipmi_sdr_info_t *sdrs, int *val)
{
    sdr_lock(sdrs);
    if (sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->major_version;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_get_minor_version(ipmi_sdr_info_t *sdrs, int *val)
{
    sdr_lock(sdrs);
    if (sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->minor_version;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_get_overflow(ipmi_sdr_info_t *sdrs, int *val)
{
    sdr_lock(sdrs);
    if (sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->overflow;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_get_update_mode(ipmi_sdr_info_t *sdrs, int *val)
{
    sdr_lock(sdrs);
    if (sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->update_mode;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_get_supports_delete_sdr(ipmi_sdr_info_t *sdrs, int *val)
{
    sdr_lock(sdrs);
    if (sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->supports_delete_sdr;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_get_supports_partial_add_sdr(ipmi_sdr_info_t *sdrs, int *val)
{
    sdr_lock(sdrs);
    if (sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->supports_partial_add_sdr;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_get_supports_reserve_sdr(ipmi_sdr_info_t *sdrs, int *val)
{
    sdr_lock(sdrs);
    if (sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->supports_reserve_sdr;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_get_supports_get_sdr_repository_allocation(ipmi_sdr_info_t *sdrs,
						    int             *val)
{
    sdr_lock(sdrs);
    if (sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->supports_get_sdr_repository_allocation;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_get_dynamic_population(ipmi_sdr_info_t *sdrs, int *val)
{
    sdr_lock(sdrs);
    if (!sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->dynamic_population;

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_get_lun_has_sensors(ipmi_sdr_info_t *sdrs, unsigned int lun, int *val)
{
    if (lun >= 4)
	return EINVAL;

    sdr_lock(sdrs);
    if (!sdrs->sensor) {
	sdr_unlock(sdrs);
	return EINVAL;
    }

    *val = sdrs->lun_has_sensors[lun];

    sdr_unlock(sdrs);
    return 0;
}

int
ipmi_sdr_add(ipmi_sdr_info_t *sdrs,
	     ipmi_sdr_t      *sdr)
{
    int rv = 0;
    int pos;

    sdr_lock(sdrs);
    if (sdrs->num_sdrs >= sdrs->sdr_array_size) {
	ipmi_sdr_t *new_array;
	/* Allocate 9 extra bytes for the db info. */
	new_array = ipmi_mem_alloc((sizeof(ipmi_sdr_t)
				    * (sdrs->sdr_array_size + 10)) + 9);
	if (!new_array) {
	    rv = ENOMEM;
	    goto out_unlock;
	}
	memcpy(new_array, sdrs->sdrs, sizeof(ipmi_sdr_t)*sdrs->sdr_array_size);
	ipmi_mem_free(sdrs->sdrs);
	sdrs->sdrs = new_array;
	sdrs->sdr_array_size += 10;
    }

    pos = sdrs->num_sdrs;
    (sdrs->num_sdrs)++;

    memcpy(&((sdrs->sdrs)[pos]), sdr, sizeof(*sdr));

 out_unlock:
    sdr_unlock(sdrs);
    return rv;
}


typedef struct sdr_save_handler_s
{
    ipmi_sdr_info_t  *sdrs;
    ipmi_sdr_save_cb handler;
    void             *cb_data;
} sdr_save_handler_t;

/* Must be called with the sdr lock held.  This will release and
   reaquire the sdr lock. */
static void
save_complete(ipmi_sdr_info_t *sdrs, int err)
{
    sdrs->wait_err = err;
    sdrs->fetch_state = HANDLERS;
    sdr_unlock(sdrs);

    opq_op_done(sdrs->sdr_wait_q);

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(save_complete): "
		 "SDR info was destroyed while an operation was in"
		 " progress(5)", sdrs->name);
	internal_destroy_sdr_info(sdrs);
	/* The previous call unlocks the lock. */
	return;
    }

    if (sdrs->fetch_state == HANDLERS)
	/* The fetch process wasn't restarted, so go to IDLE. */
	sdrs->fetch_state = IDLE;

    sdr_unlock(sdrs);
}

static int start_save(ipmi_sdr_info_t *sdrs, ipmi_mc_t *mc);

static void handle_sdr_write(ipmi_mc_t  *mc,
			     ipmi_msg_t *rsp,
			     void       *rsp_data);

static void handle_sdr_write_done(ipmi_mc_t  *mc,
				  ipmi_msg_t *rsp,
				  void       *rsp_data);

static int
start_sdr_write(ipmi_sdr_info_t *sdrs,
		ipmi_sdr_t      *sdr,
		ipmi_mc_t       *mc)
{
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;

    /* Save the first part of the SDR. */
    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_PARTIAL_ADD_SDR_CMD;
    ipmi_set_uint16(cmd_msg.data, sdrs->reservation);
    ipmi_set_uint16(cmd_msg.data+2, sdrs->curr_rec_id);
    cmd_msg.data[4] = 0;
    cmd_msg.data[6] = 0;
    cmd_msg.data[7] = 0;
    cmd_msg.data[8] = sdr->major_version | (sdr->minor_version << 4);
    cmd_msg.data[9] = sdr->type;
    cmd_msg.data[10] = sdr->length;
    if (sdr->length <= (sdrs->fetch_size - 5)) {
	cmd_msg.data[5] = 1;
	memcpy(cmd_msg.data+11, sdr->data, sdr->length);
	cmd_msg.data_len = 11 + sdr->length;
	return ipmi_mc_send_command(mc, sdrs->lun, &cmd_msg,
				    handle_sdr_write_done, sdrs);
    } else {
	cmd_msg.data[5] = 0;
	memcpy(cmd_msg.data+11, sdr->data, (sdrs->fetch_size - 5));
	cmd_msg.data_len = 11 + (sdrs->fetch_size - 5);
	sdrs->sdr_data_write = sdrs->fetch_size - 5;
	return ipmi_mc_send_command(mc, sdrs->lun, &cmd_msg,
				    handle_sdr_write, sdrs);
    }
}

static void
handle_sdr_write(ipmi_mc_t  *mc,
		 ipmi_msg_t *rsp,
		 void       *rsp_data)
{
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) rsp_data;
    ipmi_sdr_t      *sdr = &(sdrs->sdrs[sdrs->write_sdr_num]);
    int             rv;
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;
    unsigned int    wleft;

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_write): "
		 "SDR info was destroyed while an operation was in"
		 " progress(6)", sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_write): "
		 "MC went away while SDR fetch was in progress(5)",
		 sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }
	
    if (rsp->data[0] == IPMI_INVALID_RESERVATION_CC) {
	/* Arg, lost my reservation, start over. */
	sdrs->fetch_retry_count++;
	if (sdrs->fetch_retry_count > MAX_SDR_FETCH_RETRIES) {
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_write): "
		     "Lost reservation too many times", sdrs->name);
	    save_complete(sdrs, EAGAIN);
	    goto out;
	} else {
	    rv = start_save(sdrs, mc);
	    if (rv) {
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%ssdr.c(handle_sdr_write): "
			 "Could not restart save operation", sdrs->name);
		save_complete(sdrs, rv);
		goto out;
	    }
	}
	goto out_unlock;
    }

    if (rsp->data[0] != 0) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_write): "
		 "Error from write operation: %x",
		 sdrs->name, rsp->data[0]);
	save_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	goto out;
    }

    /* use the returned record id */
    sdrs->curr_rec_id = ipmi_get_uint16(rsp->data+1);

    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_PARTIAL_ADD_SDR_CMD;
    ipmi_set_uint16(cmd_msg.data, sdrs->reservation);
    ipmi_set_uint16(cmd_msg.data+2, sdrs->curr_rec_id);
    /* offset = 5 more bytes for sensor record header from start_sdr_write */
    cmd_msg.data[4] = 5 + sdrs->sdr_data_write;
    wleft = sdr->length - sdrs->sdr_data_write;
    if (wleft <= sdrs->fetch_size) {
	cmd_msg.data[5] = 1;
	memcpy(cmd_msg.data+6, sdr->data+sdrs->sdr_data_write, wleft);
	cmd_msg.data_len = 6 + wleft;
	rv = ipmi_mc_send_command(mc, sdrs->lun, &cmd_msg,
				  handle_sdr_write_done, sdrs);
    } else {
	cmd_msg.data[5] = 0;
	memcpy(cmd_msg.data+6, sdr->data+sdrs->sdr_data_write,
	       sdrs->fetch_size);
	cmd_msg.data_len = 6 + sdrs->fetch_size;
	sdrs->sdr_data_write += sdrs->fetch_size;
	rv = ipmi_mc_send_command(mc, sdrs->lun, &cmd_msg,
				  handle_sdr_write, sdrs);
    }

    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_write): "
		 "handle_sdr_write: Could not send next write: %x",
		 sdrs->name, rv);
	save_complete(sdrs, rv);
	goto out;
    }
 out_unlock:
    sdr_unlock(sdrs);
 out:
    return;
}

static void
handle_sdr_write_done(ipmi_mc_t  *mc,
		      ipmi_msg_t *rsp,
		      void       *rsp_data)
{
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) rsp_data;
    int             rv;


    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_write_done): "
		 "SDR info was destroyed while an operation was in"
		 " progress(7)", sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_write_done): "
		 "MC went away while SDR fetch was in progress(6)",
		 sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }
	
    if (rsp->data[0] == IPMI_INVALID_RESERVATION_CC) {
	/* Arg, lost my reservation, start over. */
	sdrs->fetch_retry_count++;
	if (sdrs->fetch_retry_count > MAX_SDR_FETCH_RETRIES) {
	    ipmi_log(IPMI_LOG_ERR_INFO,
		     "%ssdr.c(handle_sdr_write_done): "
		     "Lost reservation too many times", sdrs->name);
	    save_complete(sdrs, EAGAIN);
	    goto out;
	} else {
	    rv = start_save(sdrs, mc);
	    if (rv) {
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%ssdr.c(handle_sdr_write_done): "
			 " Could not restart save operation", sdrs->name);
		save_complete(sdrs, rv);
		goto out;
	    }
	}
	goto out_unlock;
    }

    if (rsp->data[0] != 0) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_write_done): "
		 "Error from write operation: %x",
		 sdrs->name, rsp->data[0]);
	save_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	goto out;
    }

    (sdrs->write_sdr_num)++;
    if (sdrs->write_sdr_num >= sdrs->num_sdrs) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_write_done): "
		 "Error from write operation: %x",
		 sdrs->name, rsp->data[0]);
	save_complete(sdrs, 0);
	goto out;
    }

    rv = start_sdr_write(sdrs, &(sdrs->sdrs[sdrs->write_sdr_num]), mc);
    if (rv) {
	save_complete(sdrs, rv);
	goto out;
    }
 out_unlock:
    sdr_unlock(sdrs);
 out:
    return;
}

static void
handle_write_reservation(ipmi_mc_t  *mc,
                         ipmi_msg_t *rsp,
                         void       *rsp_data)
{
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) rsp_data;
    int 	    rv;


    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_write_reservation): "
		 "SDR info was destroyed while an operation was in"
		 " progress(9)", sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_write_reservation): "
		 "MC went away while SDR fetch was in progress(8)",
		 sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }

    if (rsp->data[0] != 0) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_write_reservation): "
		 "Error getting reservation: %x",
		 sdrs->name, rsp->data[0]);
	save_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	goto out;
    }
    if (rsp->data_len < 3) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_write_reservation): "
		 "Reservation data not long enough", sdrs->name);
	save_complete(sdrs, EINVAL);
	goto out;
    }

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

    sdrs->curr_rec_id = 0;
    sdrs->write_sdr_num = 0;
    sdrs->sdr_data_write = 0;

    /* Save the first part of the SDR. */
    rv = start_sdr_write(sdrs, &(sdrs->sdrs[0]), mc);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_clear): "
		 "Could not send next write: %x", sdrs->name, rv);
	save_complete(sdrs, rv);
	goto out;
    }
    sdr_unlock(sdrs);
 out:
    return;
}

static void
handle_sdr_clear(ipmi_mc_t  *mc,
		 ipmi_msg_t *rsp,
		 void       *rsp_data)
{
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) rsp_data;
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;
    int             rv;

    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_clear): "
		 "SDR info was destroyed while an operation was in"
		 " progress(8)", sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_clear): "
		 "MC went away while SDR fetch was in progress(7)",
		 sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }
	
    if (rsp->data[0] != 0) {
	save_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	goto out;
    }

    if ((rsp->data[1] & 0x0F) != 1) {
	/* Check clear progress. */
	cmd_msg.data = cmd_data;
	cmd_msg.netfn = IPMI_STORAGE_NETFN;
	cmd_msg.cmd = IPMI_CLEAR_SDR_REPOSITORY_CMD;
	ipmi_set_uint16(cmd_data, sdrs->reservation);
	cmd_data[2] = 'C';
	cmd_data[3] = 'L';
	cmd_data[4] = 'R';
	cmd_data[5] = 0x00;
	cmd_msg.data_len = 6;
	rv = ipmi_mc_send_command(mc, sdrs->lun, &cmd_msg,
				  handle_sdr_clear, sdrs);
	if (rv) {
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%ssdr.c(handle_sdr_clear): "
			 "Couldn't check SDR clear status: %x",
			 sdrs->name, rv);
		save_complete(sdrs, rv);
		goto out;
	}
	goto out_unlock;
    }

    if (sdrs->num_sdrs == 0) {
	save_complete(sdrs, 0);
	goto out;
    }

    /* Get a reservation again -- reservation is lost after clear. */
    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_RESERVE_SDR_REPOSITORY_CMD;
    cmd_msg.data_len = 0;
    rv = ipmi_mc_send_command_sideeff(mc, sdrs->lun, &cmd_msg,
				      handle_write_reservation, sdrs);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_sdr_clear): "
		 "Could not send next write: %x", sdrs->name, rv);
	save_complete(sdrs, rv);
	goto out;
    }
 out_unlock:
    sdr_unlock(sdrs);
 out:
    return;
}

static void
handle_save_reservation(ipmi_mc_t  *mc,
			ipmi_msg_t *rsp,
			void       *rsp_data)
{
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) rsp_data;
    unsigned char   cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t      cmd_msg;
    int             rv;


    sdr_lock(sdrs);
    if (sdrs->destroyed) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_save_reservation): "
		 "SDR info was destroyed while an operation was in"
		 " progress(9)", sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }

    if (!mc) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_save_reservation): "
		 "MC went away while SDR fetch was in progress(8)",
		 sdrs->name);
	save_complete(sdrs, ECANCELED);
	goto out;
    }
	
    if (rsp->data[0] != 0) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_save_reservation): "
		 "Error getting reservation: %x",
		 sdrs->name, rsp->data[0]);
	save_complete(sdrs, IPMI_IPMI_ERR_VAL(rsp->data[0]));
	goto out;
    }
    if (rsp->data_len < 3) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_save_reservation): "
		 "Reservation data not long enough", sdrs->name);
	save_complete(sdrs, EINVAL);
	goto out;
    }

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

    /* Clear the repository. */
    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_CLEAR_SDR_REPOSITORY_CMD;
    cmd_data[0] = rsp->data[1];
    cmd_data[1] = rsp->data[2];
    cmd_data[2] = 'C';
    cmd_data[3] = 'L';
    cmd_data[4] = 'R';
    cmd_data[5] = 0xaa;
    cmd_msg.data_len = 6;
    rv = ipmi_mc_send_command(mc, sdrs->lun, &cmd_msg,
			      handle_sdr_clear, sdrs);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_save_reservation): "
		 "Couldn't send SDR clear: %x", sdrs->name, rv);
	save_complete(sdrs, rv);
	goto out;
    }
    sdr_unlock(sdrs);
 out:
    return;
}

static int
start_save(ipmi_sdr_info_t *sdrs, ipmi_mc_t *mc)
{
    unsigned char cmd_data[MAX_IPMI_DATA_SIZE];
    ipmi_msg_t    cmd_msg;

    sdrs->fetch_state = FETCHING;

    /* Get a reservation first. */
    cmd_msg.data = cmd_data;
    cmd_msg.netfn = IPMI_STORAGE_NETFN;
    cmd_msg.cmd = IPMI_RESERVE_SDR_REPOSITORY_CMD;
    cmd_msg.data_len = 0;
    return ipmi_mc_send_command_sideeff(mc, sdrs->lun, &cmd_msg,
					handle_save_reservation, sdrs);
}

static void
handle_start_save_cb(ipmi_mc_t *mc, void *cb_data)
{
    int             rv;
    ipmi_sdr_info_t *sdrs = (ipmi_sdr_info_t *) cb_data;

    sdrs->wait_err = 0;
    sdr_lock(sdrs);
    rv = start_save(sdrs, mc);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_start_save_cb): "
		 "error requesting reserveration: %x", sdrs->name, rv);
	sdrs->wait_err = rv;
	save_complete(sdrs, rv);
    } else {
	sdr_unlock(sdrs);
    }
}

static int
handle_start_save(void *cb_data, int shutdown)
{
    int             rv;
    ipmi_sdr_info_t *sdrs = cb_data;

    if (shutdown)
	return OPQ_HANDLER_STARTED;

    rv = ipmi_mc_pointer_cb(sdrs->mc, handle_start_save_cb, sdrs);
    if (rv) {
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%ssdr.c(handle_start_save): "
		 "error finding MC: %x",
		 sdrs->name, rv);
	sdrs->wait_err = rv;
	fetch_complete(sdrs, rv);
    }
    return OPQ_HANDLER_STARTED;
}

static void
handle_save_done(void *cb_data, int shutdown)
{
    sdr_save_handler_t *elem = cb_data;

    elem->handler(elem->sdrs,
		  elem->sdrs->wait_err,
		  elem->cb_data);
    ipmi_mem_free(elem);
}

typedef struct sdr_save_info_s
{
    ipmi_sdr_info_t  *sdrs;
    ipmi_sdr_save_cb done;
    void             *cb_data;
    int              rv;
} sdr_save_info_t;

static void
sdr_save_cb(ipmi_mc_t *mc, void *cb_data)
{
    sdr_save_info_t    *info = cb_data;
    ipmi_sdr_info_t    *sdrs = info->sdrs;
    sdr_save_handler_t *elem;


    elem = ipmi_mem_alloc(sizeof(*elem));
    if (!elem) {
	info->rv = ENOMEM;
	return;
    }

    elem->sdrs = sdrs;
    elem->handler = info->done;
    elem->cb_data = info->cb_data;

    if (!ipmi_mc_sdr_repository_support(mc)) {
	info->rv = ENOSYS;
	goto out;
    }

    sdr_lock(sdrs);
    if (! opq_new_op_with_done(sdrs->sdr_wait_q,
			       handle_start_save,
			       sdrs,
			       handle_save_done,
			       elem))
    {
	info->rv = ENOMEM;
    }
    sdr_unlock(sdrs);

 out:
    if (info->rv)
	ipmi_mem_free(elem);
}

int
ipmi_sdr_save(ipmi_sdr_info_t  *sdrs,
	      ipmi_sdr_save_cb done,
	      void             *cb_data)
{
    int             rv;
    sdr_save_info_t info;

    info.sdrs = sdrs;
    info.done = done;
    info.cb_data = cb_data;
    info.rv = 0;

    /* Convert the mc id to an mc. */
    rv = ipmi_mc_pointer_cb(sdrs->mc, sdr_save_cb, &info);
    if (rv)
	return rv;
    return info.rv;
}
