static int
_sdr_cache_trailer_write (ipmi_sdr_ctx_t ctx,
                          ipmi_ctx_t ipmi_ctx,
                          int fd,
                          unsigned int total_bytes_written,
                          uint8_t trailer_checksum)
{
    char total_bytes_written_buf[4];
    ssize_t n;

    assert (ctx);
    assert (ctx->magic == IPMI_SDR_CTX_MAGIC);
    assert (ipmi_ctx);
    assert (fd);

    /* + 4 for this value, + 1 for checksum at end */
    total_bytes_written += 4;
    total_bytes_written += 1;

    total_bytes_written_buf[0] = (total_bytes_written & 0x000000FF);
    total_bytes_written_buf[1] = (total_bytes_written & 0x0000FF00) >> 8;
    total_bytes_written_buf[2] = (total_bytes_written & 0x00FF0000) >> 16;
    total_bytes_written_buf[3] = (total_bytes_written & 0xFF000000) >> 24;

    if ((n = fd_write_n (fd, total_bytes_written_buf, 4)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }
    if (n != 4)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }

    trailer_checksum = ipmi_checksum_final (total_bytes_written_buf, 4, trailer_checksum);

    if ((n = fd_write_n (fd, (char *)&trailer_checksum, 1)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }
    if (n != 1)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }

    return (0);
}
int
ipmi_sdr_cache_delete (ipmi_sdr_ctx_t ctx, const char *filename)
{
  int rv = -1;

  if (!ctx || ctx->magic != IPMI_SDR_CTX_MAGIC)
    {
      ERR_TRACE (ipmi_sdr_ctx_errormsg (ctx), ipmi_sdr_ctx_errnum (ctx));
      return (-1);
    }

  if (!filename)
    {
      SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_PARAMETERS);
      return (-1);
    }

  if (ctx->operation != IPMI_SDR_OPERATION_UNINITIALIZED)
    {
      if (ctx->operation == IPMI_SDR_OPERATION_READ_CACHE)
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CONTEXT_PERFORMING_OTHER_OPERATION);
      else
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_INTERNAL_ERROR);
      return (-1);
    }

  ctx->operation = IPMI_SDR_OPERATION_DELETE_CACHE;

  if (unlink (filename) < 0)
    {
      /* If there is no file (ENOENT), its ok */
      if (errno != ENOENT)
        {
          SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
	  goto cleanup;
        }
    }

  rv = 0;
  ctx->errnum = IPMI_SDR_ERR_SUCCESS;
 cleanup:
  ctx->operation = IPMI_SDR_OPERATION_UNINITIALIZED;
  return (rv);
}
static int
_sdr_cache_reservation_id (ipmi_sdr_ctx_t ctx,
                           ipmi_ctx_t ipmi_ctx,
                           uint16_t *reservation_id)
{
    fiid_obj_t obj_cmd_rs = NULL;
    uint64_t val;
    int rv = -1;

    assert (ctx);
    assert (ctx->magic == IPMI_SDR_CTX_MAGIC);
    assert (ipmi_ctx);
    assert (reservation_id);

    if (!(obj_cmd_rs = fiid_obj_create (tmpl_cmd_reserve_sdr_repository_rs)))
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        goto cleanup;
    }

    if (ipmi_cmd_reserve_sdr_repository (ipmi_ctx, obj_cmd_rs) < 0)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_IPMI_ERROR);
        goto cleanup;
    }

    *reservation_id = 0;
    if (FIID_OBJ_GET (obj_cmd_rs,
                      "reservation_id",
                      &val) < 0)
    {
        SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_cmd_rs);
        goto cleanup;
    }
    *reservation_id = val;

    rv = 0;
cleanup:
    fiid_obj_destroy (obj_cmd_rs);
    return (rv);
}
Exemplo n.º 4
0
int
ipmi_sdr_cache_open (ipmi_sdr_ctx_t ctx,
                     ipmi_ctx_t ipmi_ctx,
                     const char *filename)
{
  uint8_t sdr_version;
  uint16_t record_count;
  uint32_t most_recent_addition_timestamp, most_recent_erase_timestamp;
  char sdr_version_buf;
  char sdr_cache_magic_buf[4];
  char sdr_cache_version_buf[4];
  char record_count_buf[2];
  char most_recent_addition_timestamp_buf[4];
  char most_recent_erase_timestamp_buf[4];
  struct stat stat_buf;

  if (!ctx || ctx->magic != IPMI_SDR_CTX_MAGIC)
    {
      ERR_TRACE (ipmi_sdr_ctx_errormsg (ctx), ipmi_sdr_ctx_errnum (ctx));
      return (-1);
    }

  if (!filename)
    {
      SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_PARAMETERS);
      return (-1);
    }

  if (ctx->operation != IPMI_SDR_OPERATION_UNINITIALIZED)
    {
      if (ctx->operation == IPMI_SDR_OPERATION_READ_CACHE)
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_READ_ALREADY_INITIALIZED);
      else
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_INTERNAL_ERROR);
      return (-1);
    }

  if (stat (filename, &stat_buf) < 0)
    {
      SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
      goto cleanup;
    }

  /* File Size must be atleast magic_buf + file_version_buf +
   * sdr_version_buf + record_count_buf +
   * most_recent_addition_timestamp_buf +
   * most_recent_erase_timestamp-buf in size.
   */

  ctx->file_size = stat_buf.st_size;
  if (ctx->file_size < (4 + 4 + 1 + 2 + 4 + 4))
    {
      SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_INVALID);
      goto cleanup;
    }

  if ((ctx->fd = open (filename, O_RDONLY)) < 0)
    {
      SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
      goto cleanup;
    }

  ctx->sdr_cache = (uint8_t *)mmap (NULL,
                                    ctx->file_size,
                                    PROT_READ,
                                    MAP_PRIVATE,
                                    ctx->fd,
                                    0);
  if (!ctx->sdr_cache || ctx->sdr_cache == ((void *) -1))
    {
      ERRNO_TRACE (errno);
      SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
      goto cleanup;
    }

  memcpy (sdr_cache_magic_buf, ctx->sdr_cache + ctx->records_start_offset, 4);
  ctx->records_start_offset += 4;
  memcpy (sdr_cache_version_buf, ctx->sdr_cache + ctx->records_start_offset, 4);
  ctx->records_start_offset += 4;
  memcpy (&sdr_version_buf, ctx->sdr_cache + ctx->records_start_offset, 1);
  ctx->records_start_offset += 1;
  memcpy (record_count_buf, ctx->sdr_cache + ctx->records_start_offset, 2);
  ctx->records_start_offset += 2;
  memcpy (most_recent_addition_timestamp_buf, ctx->sdr_cache + ctx->records_start_offset, 4);
  ctx->records_start_offset += 4;
  memcpy (most_recent_erase_timestamp_buf, ctx->sdr_cache + ctx->records_start_offset, 4);
  ctx->records_start_offset += 4;

  if ((uint8_t)sdr_cache_magic_buf[0] != IPMI_SDR_CACHE_FILE_MAGIC_0
      || (uint8_t)sdr_cache_magic_buf[1] != IPMI_SDR_CACHE_FILE_MAGIC_1
      || (uint8_t)sdr_cache_magic_buf[2] != IPMI_SDR_CACHE_FILE_MAGIC_2
      || (uint8_t)sdr_cache_magic_buf[3] != IPMI_SDR_CACHE_FILE_MAGIC_3)
    {
      SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_INVALID);
      goto cleanup;
    }

  if (((uint8_t)sdr_cache_version_buf[0] != IPMI_SDR_CACHE_FILE_VERSION_1_0
       || (uint8_t)sdr_cache_version_buf[1] != IPMI_SDR_CACHE_FILE_VERSION_1_1
       || (uint8_t)sdr_cache_version_buf[2] != IPMI_SDR_CACHE_FILE_VERSION_1_2
       || (uint8_t)sdr_cache_version_buf[3] != IPMI_SDR_CACHE_FILE_VERSION_1_3)
      && ((uint8_t)sdr_cache_version_buf[0] != IPMI_SDR_CACHE_FILE_VERSION_1_2_0
	  || (uint8_t)sdr_cache_version_buf[1] != IPMI_SDR_CACHE_FILE_VERSION_1_2_1
	  || (uint8_t)sdr_cache_version_buf[2] != IPMI_SDR_CACHE_FILE_VERSION_1_2_2
	  || (uint8_t)sdr_cache_version_buf[3] != IPMI_SDR_CACHE_FILE_VERSION_1_2_3))
    {
      SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_INVALID);
      goto cleanup;
    }

  ctx->sdr_version = (uint8_t)sdr_version_buf;
  ctx->record_count = ((uint16_t)record_count_buf[0] & 0xFF);
  ctx->record_count |= ((uint16_t)record_count_buf[1] & 0xFF) << 8;
  ctx->most_recent_addition_timestamp = ((uint32_t)most_recent_addition_timestamp_buf[0] & 0xFF);
  ctx->most_recent_addition_timestamp |= ((uint32_t)most_recent_addition_timestamp_buf[1] & 0xFF) << 8;
  ctx->most_recent_addition_timestamp |= ((uint32_t)most_recent_addition_timestamp_buf[2] & 0xFF) << 16;
  ctx->most_recent_addition_timestamp |= ((uint32_t)most_recent_addition_timestamp_buf[3] & 0xFF) << 24;
  ctx->most_recent_erase_timestamp = ((uint32_t)most_recent_erase_timestamp_buf[0] & 0xFF);
  ctx->most_recent_erase_timestamp |= ((uint32_t)most_recent_erase_timestamp_buf[1] & 0xFF) << 8;
  ctx->most_recent_erase_timestamp |= ((uint32_t)most_recent_erase_timestamp_buf[2] & 0xFF) << 16;
  ctx->most_recent_erase_timestamp |= ((uint32_t)most_recent_erase_timestamp_buf[3] & 0xFF) << 24;

  if (ipmi_ctx)
    {
      if (sdr_info (ctx,
		    ipmi_ctx,
		    &sdr_version,
		    &record_count,
		    &most_recent_addition_timestamp,
		    &most_recent_erase_timestamp) < 0)
	goto cleanup;

      if (ctx->sdr_version != sdr_version
	  || ctx->most_recent_addition_timestamp != most_recent_addition_timestamp
	  || ctx->most_recent_erase_timestamp != most_recent_erase_timestamp)
	{
	  SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_OUT_OF_DATE);
	  goto cleanup;
	}
    }

  if ((uint8_t)sdr_cache_version_buf[0] == IPMI_SDR_CACHE_FILE_VERSION_1_2_0
      && (uint8_t)sdr_cache_version_buf[1] == IPMI_SDR_CACHE_FILE_VERSION_1_2_1
      && (uint8_t)sdr_cache_version_buf[2] == IPMI_SDR_CACHE_FILE_VERSION_1_2_2
      && (uint8_t)sdr_cache_version_buf[3] == IPMI_SDR_CACHE_FILE_VERSION_1_2_3)
    {
      uint8_t header_checksum_buf[512];
      unsigned int header_checksum_buf_len = 0;
      uint8_t header_checksum, header_checksum_cache;
      uint8_t trailer_checksum, trailer_checksum_cache;
      char total_bytes_written_buf[4];
      unsigned int total_bytes_written;
      unsigned int header_bytes_len;
      unsigned int trailer_bytes_len;

      /* File Size must be atleast magic_buf + file_version_buf +
       * sdr_version_buf + record_count_buf +
       * most_recent_addition_timestamp_buf +
       * most_recent_erase_timestamp-buf + header_checksum + trailer
       * bytes written + trailer records checksum.
       */
      
      header_bytes_len = 4 + 4 + 1 + 2 + 4 + 4 + 1;
      trailer_bytes_len = 4 + 1;

      if (ctx->file_size < (header_bytes_len + trailer_bytes_len))
	{
	  SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_INVALID);
	  goto cleanup;
	}

      memcpy (&header_checksum_cache, ctx->sdr_cache + ctx->records_start_offset, 1);
      ctx->records_start_offset += 1;

      memcpy(&header_checksum_buf[header_checksum_buf_len], sdr_cache_magic_buf, 4);
      header_checksum_buf_len += 4;
      memcpy(&header_checksum_buf[header_checksum_buf_len], sdr_cache_version_buf, 4);
      header_checksum_buf_len += 4;
      memcpy(&header_checksum_buf[header_checksum_buf_len], &sdr_version_buf, 1);
      header_checksum_buf_len += 1;
      memcpy(&header_checksum_buf[header_checksum_buf_len], record_count_buf, 2);
      header_checksum_buf_len += 2;
      memcpy(&header_checksum_buf[header_checksum_buf_len], most_recent_addition_timestamp_buf, 4);
      header_checksum_buf_len += 4;
      memcpy(&header_checksum_buf[header_checksum_buf_len], most_recent_erase_timestamp_buf, 4);
      header_checksum_buf_len += 4;
      
      header_checksum = ipmi_checksum (header_checksum_buf, header_checksum_buf_len);
      if (header_checksum != header_checksum_cache)
	{
	  SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_INVALID);
	  goto cleanup;
	}

      /* total_bytes_written is written before checksum */
      /* checksum of records is last byte written */
      memcpy (total_bytes_written_buf, ctx->sdr_cache + ctx->file_size - 5, 4);
      memcpy (&trailer_checksum_cache, ctx->sdr_cache + ctx->file_size - 1, 1);

      total_bytes_written = ((uint32_t)total_bytes_written_buf[0] & 0xFF);
      total_bytes_written |= ((uint32_t)total_bytes_written_buf[1] & 0xFF) << 8;
      total_bytes_written |= ((uint32_t)total_bytes_written_buf[2] & 0xFF) << 16;
      total_bytes_written |= ((uint32_t)total_bytes_written_buf[3] & 0xFF) << 24;

      if (total_bytes_written != ctx->file_size)
	{
	  SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_INVALID);
	  goto cleanup;
	}

      /* -1 for checksum */
      trailer_checksum = ipmi_checksum (ctx->sdr_cache + ctx->records_start_offset,
					total_bytes_written - ctx->records_start_offset - 1);

      if (trailer_checksum != trailer_checksum_cache)
	{
	  SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_INVALID);
	  goto cleanup;
	}

      ctx->records_end_offset = ctx->file_size - trailer_bytes_len;
    }
  else /* (uint8_t)sdr_cache_version_buf[0] == IPMI_SDR_CACHE_FILE_VERSION_1_0
	  && (uint8_t)sdr_cache_version_buf[1] == IPMI_SDR_CACHE_FILE_VERSION_1_1
	  && (uint8_t)sdr_cache_version_buf[2] == IPMI_SDR_CACHE_FILE_VERSION_1_2
	  && (uint8_t)sdr_cache_version_buf[3] == IPMI_SDR_CACHE_FILE_VERSION_1_3 */
    ctx->records_end_offset = ctx->file_size;

  _sdr_set_current_offset (ctx, ctx->records_start_offset);
  ctx->operation = IPMI_SDR_OPERATION_READ_CACHE;
  ctx->errnum = IPMI_SDR_ERR_SUCCESS;
  return (0);

 cleanup:
  /* ignore potential error, cleanup path */
  if (ctx->fd >= 0)
    close (ctx->fd);
  /* ignore potential error, cleanup path */
  if (ctx->sdr_cache)
    munmap ((void *)ctx->sdr_cache, ctx->file_size);
  sdr_init_ctx (ctx);
  return (-1);
}
/* return (1) - is oem intel node manager, fully parsed                                                                                     
 * return (0) - is not oem intel node manager                                                                                               
 * return (-1) - error                                                                                                                      
 */
int
ipmi_sdr_oem_parse_intel_node_manager (ipmi_sdr_ctx_t ctx,
                                       const void *sdr_record,
                                       unsigned int sdr_record_len,
                                       uint8_t *nm_device_slave_address,
                                       uint8_t *sensor_owner_lun,
                                       uint8_t *channel_number,
                                       uint8_t *nm_health_event_sensor_number,
                                       uint8_t *nm_exception_event_sensor_number,
                                       uint8_t *nm_operational_capabilities_sensor_number,
                                       uint8_t *nm_alert_threshold_exceeded_sensor_number)
{
  uint8_t sdr_record_buf[IPMI_SDR_MAX_RECORD_LENGTH];
  int sdr_record_buf_len;
  fiid_obj_t obj_oem_record = NULL;
  int expected_record_len;
  void *sdr_record_to_use;
  unsigned int sdr_record_len_to_use;
  uint64_t val;
  int rv = -1;

  if (!ctx || ctx->magic != IPMI_SDR_CTX_MAGIC)
    {
      ERR_TRACE (ipmi_sdr_ctx_errormsg (ctx), ipmi_sdr_ctx_errnum (ctx));
      return (-1);
    }

  if (!sdr_record || !sdr_record_len)
    {
      if (ctx->operation == IPMI_SDR_OPERATION_READ_CACHE
          && !sdr_record
          && !sdr_record_len)
        {
          if ((sdr_record_buf_len = ipmi_sdr_cache_record_read (ctx,
                                                                sdr_record_buf,
                                                                IPMI_SDR_MAX_RECORD_LENGTH)) < 0)
            {
              SDR_SET_INTERNAL_ERRNUM (ctx);
              return (-1);
            }
          sdr_record_to_use = sdr_record_buf;
          sdr_record_len_to_use = sdr_record_buf_len;
        }
      else
        {
          SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_PARAMETERS);
          return (-1);
        }
    }
  else
    {
      sdr_record_to_use = (void *)sdr_record;
      sdr_record_len_to_use = sdr_record_len;
    }

  if ((expected_record_len = fiid_template_len_bytes (tmpl_sdr_oem_intel_node_manager_record)) < 0)
    {
      SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
      goto cleanup;
    }
  
  if (sdr_record_len_to_use < expected_record_len)
    {
      rv = 0;
      goto cleanup;
    }

  if (!(obj_oem_record = fiid_obj_create (tmpl_sdr_oem_intel_node_manager_record)))
    {
      SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
      goto cleanup;
    }

  if (fiid_obj_set_all (obj_oem_record,
                        sdr_record_to_use,
                        expected_record_len) < 0)
    {
      SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
      goto cleanup;
    }

  /* achu: Node Manager documentation states that OEM ID in the
   * SDR record should be Intel's, but I've seen motherboards w/o
   * it, so don't bother checking.
   */

  if (FIID_OBJ_GET (obj_oem_record,
                    "record_subtype",
                    &val) < 0)
    {
      SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
      goto cleanup;
    }
  
  if (val != IPMI_SDR_OEM_INTEL_NODE_MANAGER_RECORD_SUBTYPE_NM_DISCOVERY)
    {
      rv = 0;
      goto cleanup;
    }

  if (FIID_OBJ_GET (obj_oem_record,
                    "version_number",
                    &val) < 0)
    {
      SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
      goto cleanup;
    }

  if (val != IPMI_SDR_OEM_INTEL_NODE_MANAGER_DISCOVERY_VERSION)
    {
      rv = 0;
      goto cleanup;
    }
     
  if (nm_device_slave_address)
    {
      if (FIID_OBJ_GET (obj_oem_record,
                        "nm_device_slave_address",
                        &val) < 0)
        {
          SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
          goto cleanup;
        }
      (*nm_device_slave_address) = val;
    }

  if (sensor_owner_lun)
    {
      if (FIID_OBJ_GET (obj_oem_record,
                        "sensor_owner_lun",
                        &val) < 0)
        {
          SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
          goto cleanup;
        }
      (*sensor_owner_lun) = val;
    }

  if (channel_number)
    {
      if (FIID_OBJ_GET (obj_oem_record,
                        "channel_number",
                        &val) < 0)
        {
          SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
          goto cleanup;
        }
      (*channel_number) = val;
    }

  if (nm_health_event_sensor_number)
    {
      if (FIID_OBJ_GET (obj_oem_record,
                        "nm_health_event_sensor_number",
                        &val) < 0)
        {
          SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
          goto cleanup;
        }
      (*nm_health_event_sensor_number) = val;
    }

  if (nm_exception_event_sensor_number)
    {
      if (FIID_OBJ_GET (obj_oem_record,
                        "nm_exception_event_sensor_number",
                        &val) < 0)
        {
          SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
          goto cleanup;
        }
      (*nm_exception_event_sensor_number) = val;
    }

  if (nm_operational_capabilities_sensor_number)
    {
      if (FIID_OBJ_GET (obj_oem_record,
                        "nm_operational_capabilities_sensor_number",
                        &val) < 0)
        {
          SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
          goto cleanup;
        }
      (*nm_operational_capabilities_sensor_number) = val;
    }
  
  if (nm_alert_threshold_exceeded_sensor_number)
    {
      if (FIID_OBJ_GET (obj_oem_record,
                        "nm_alert_threshold_exceeded_sensor_number",
                        &val) < 0)
        {
          SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_oem_record);
          goto cleanup;
        }
      (*nm_alert_threshold_exceeded_sensor_number) = val;
    }

  sdr_check_read_status (ctx);

  rv = 1;
  ctx->errnum = IPMI_SDR_ERR_SUCCESS;
 cleanup:
  fiid_obj_destroy (obj_oem_record);
  return (rv);
}
static int
_sdr_cache_header_write (ipmi_sdr_ctx_t ctx,
                         ipmi_ctx_t ipmi_ctx,
                         int fd,
                         unsigned int *total_bytes_written,
                         uint8_t sdr_version,
                         uint16_t record_count,
                         uint32_t most_recent_addition_timestamp,
                         uint32_t most_recent_erase_timestamp)
{
    char sdr_cache_magic_buf[4];
    char sdr_cache_version_buf[4];
    char record_count_buf[2];
    char most_recent_addition_timestamp_buf[4];
    char most_recent_erase_timestamp_buf[4];
    uint8_t header_checksum_buf[512];
    unsigned int header_checksum_buf_len = 0;
    uint8_t header_checksum;
    ssize_t n;

    assert (ctx);
    assert (ctx->magic == IPMI_SDR_CTX_MAGIC);
    assert (ipmi_ctx);
    assert (fd);
    assert (total_bytes_written);

    sdr_cache_magic_buf[0] = IPMI_SDR_CACHE_FILE_MAGIC_0;
    sdr_cache_magic_buf[1] = IPMI_SDR_CACHE_FILE_MAGIC_1;
    sdr_cache_magic_buf[2] = IPMI_SDR_CACHE_FILE_MAGIC_2;
    sdr_cache_magic_buf[3] = IPMI_SDR_CACHE_FILE_MAGIC_3;

    if ((n = fd_write_n (fd, sdr_cache_magic_buf, 4)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }
    if (n != 4)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }
    (*total_bytes_written) += 4;

    memcpy(&header_checksum_buf[header_checksum_buf_len], sdr_cache_magic_buf, 4);
    header_checksum_buf_len += 4;

    sdr_cache_version_buf[0] = IPMI_SDR_CACHE_FILE_VERSION_1_2_0;
    sdr_cache_version_buf[1] = IPMI_SDR_CACHE_FILE_VERSION_1_2_1;
    sdr_cache_version_buf[2] = IPMI_SDR_CACHE_FILE_VERSION_1_2_2;
    sdr_cache_version_buf[3] = IPMI_SDR_CACHE_FILE_VERSION_1_2_3;

    if ((n = fd_write_n (fd, sdr_cache_version_buf, 4)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }
    if (n != 4)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }
    (*total_bytes_written) += 4;

    memcpy(&header_checksum_buf[header_checksum_buf_len], sdr_cache_version_buf, 4);
    header_checksum_buf_len += 4;

    if ((n = fd_write_n (fd, (char *)&sdr_version, 1)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }
    if (n != 1)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }
    (*total_bytes_written) += 1;

    memcpy(&header_checksum_buf[header_checksum_buf_len], &sdr_version, 1);
    header_checksum_buf_len += 1;

    /* Store record count little-endian */
    record_count_buf[0] = (record_count & 0x00FF);
    record_count_buf[1] = (record_count & 0xFF00) >> 8;

    if ((n = fd_write_n (fd, record_count_buf, 2)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }
    if (n != 2)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }
    (*total_bytes_written) += 2;

    memcpy(&header_checksum_buf[header_checksum_buf_len], record_count_buf, 2);
    header_checksum_buf_len += 2;

    /* Store most recent addition timestamp little-endian */
    most_recent_addition_timestamp_buf[0] = (most_recent_addition_timestamp & 0x000000FF);
    most_recent_addition_timestamp_buf[1] = (most_recent_addition_timestamp & 0x0000FF00) >> 8;
    most_recent_addition_timestamp_buf[2] = (most_recent_addition_timestamp & 0x00FF0000) >> 16;
    most_recent_addition_timestamp_buf[3] = (most_recent_addition_timestamp & 0xFF000000) >> 24;

    if ((n = fd_write_n (fd, most_recent_addition_timestamp_buf, 4)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }
    if (n != 4)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }
    (*total_bytes_written) += 4;

    memcpy(&header_checksum_buf[header_checksum_buf_len], most_recent_addition_timestamp_buf, 4);
    header_checksum_buf_len += 4;

    /* Store most recent erase timestamp little-endian */
    most_recent_erase_timestamp_buf[0] = (most_recent_erase_timestamp & 0x000000FF);
    most_recent_erase_timestamp_buf[1] = (most_recent_erase_timestamp & 0x0000FF00) >> 8;
    most_recent_erase_timestamp_buf[2] = (most_recent_erase_timestamp & 0x00FF0000) >> 16;
    most_recent_erase_timestamp_buf[3] = (most_recent_erase_timestamp & 0xFF000000) >> 24;

    if ((n = fd_write_n (fd, most_recent_erase_timestamp_buf, 4)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }
    if (n != 4)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }
    (*total_bytes_written) += 4;

    memcpy(&header_checksum_buf[header_checksum_buf_len], most_recent_erase_timestamp_buf, 4);
    header_checksum_buf_len += 4;

    header_checksum = ipmi_checksum (header_checksum_buf, header_checksum_buf_len);

    if ((n = fd_write_n (fd, (char *)&header_checksum, 1)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }
    if (n != 1)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }
    (*total_bytes_written) += 1;

    return (0);
}
int
ipmi_sdr_cache_create (ipmi_sdr_ctx_t ctx,
                       ipmi_ctx_t ipmi_ctx,
                       const char *filename,
                       int cache_create_flags,
                       Ipmi_Sdr_Cache_Create_Callback create_callback,
                       void *create_callback_data)
{
    int open_flags;
    uint8_t sdr_version;
    uint16_t record_count, reservation_id, record_id, next_record_id;
    uint32_t most_recent_addition_timestamp, most_recent_erase_timestamp;
    unsigned int record_count_written = 0;
    unsigned int total_bytes_written = 0;
    uint16_t *record_ids = NULL;
    unsigned int record_ids_count = 0;
    unsigned int cache_create_flags_mask = (IPMI_SDR_CACHE_CREATE_FLAGS_OVERWRITE
                                            | IPMI_SDR_CACHE_CREATE_FLAGS_DUPLICATE_RECORD_ID
                                            | IPMI_SDR_CACHE_CREATE_FLAGS_ASSUME_MAX_SDR_RECORD_COUNT);
    uint8_t trailer_checksum = 0;
    int fd = -1;
    int rv = -1;

    if (!ctx || ctx->magic != IPMI_SDR_CTX_MAGIC)
    {
        ERR_TRACE (ipmi_sdr_ctx_errormsg (ctx), ipmi_sdr_ctx_errnum (ctx));
        return (-1);
    }

    /* Version cannot be 0h according to the IPMI spec */
    if (!ipmi_ctx
            || !filename
            || (strlen (filename) > MAXPATHLEN)
            || (cache_create_flags & ~cache_create_flags_mask))
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_PARAMETERS);
        return (-1);
    }

    if (ctx->operation != IPMI_SDR_OPERATION_UNINITIALIZED)
    {
        if (ctx->operation == IPMI_SDR_OPERATION_READ_CACHE)
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CONTEXT_PERFORMING_OTHER_OPERATION);
        else
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_INTERNAL_ERROR);
        return (-1);
    }

    ctx->operation = IPMI_SDR_OPERATION_CREATE_CACHE;

    if (cache_create_flags & IPMI_SDR_CACHE_CREATE_FLAGS_OVERWRITE)
        open_flags = O_CREAT | O_TRUNC | O_WRONLY;
    else
        open_flags = O_CREAT | O_EXCL | O_WRONLY;

    if ((fd = open (filename, open_flags, 0644)) < 0)
    {
        if (!(cache_create_flags & IPMI_SDR_CACHE_CREATE_FLAGS_OVERWRITE)
                && errno == EEXIST)
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_CREATE_CACHE_EXISTS);
        else if (errno == EPERM
                 || errno == EACCES
                 || errno == EISDIR
                 || errno == EROFS)
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_PERMISSION);
        else if (errno == ENAMETOOLONG
                 || errno == ENOENT
                 || errno == ELOOP)
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_FILENAME_INVALID);
        else if (errno == ENOSPC
                 || errno == EMFILE
                 || errno == ENFILE)
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_FILESYSTEM);
        else
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        goto cleanup;
    }

    if (sdr_info (ctx,
                  ipmi_ctx,
                  &sdr_version,
                  &record_count,
                  &most_recent_addition_timestamp,
                  &most_recent_erase_timestamp) < 0)
        goto cleanup;

    if (!record_count)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_CREATE_INVALID_RECORD_COUNT);
        goto cleanup;
    }

    if (_sdr_cache_header_write (ctx,
                                 ipmi_ctx,
                                 fd,
                                 &total_bytes_written,
                                 sdr_version,
                                 record_count,
                                 most_recent_addition_timestamp,
                                 most_recent_erase_timestamp) < 0)
        goto cleanup;

    /* Version cannot be 0h according to the IPMI spec, but we accept it regardless */
    ctx->sdr_version = sdr_version;
    ctx->record_count = record_count;
    ctx->most_recent_addition_timestamp = most_recent_addition_timestamp;
    ctx->most_recent_erase_timestamp = most_recent_erase_timestamp;

    if (cache_create_flags & IPMI_SDR_CACHE_CREATE_FLAGS_DUPLICATE_RECORD_ID)
    {
        if (!(record_ids = (uint16_t *)malloc (ctx->record_count * sizeof (uint16_t))))
        {
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_OUT_OF_MEMORY);
            goto cleanup;
        }
        record_ids_count = 0;
    }

    if (_sdr_cache_reservation_id (ctx,
                                   ipmi_ctx,
                                   &reservation_id) < 0)
        goto cleanup;

    next_record_id = IPMI_SDR_RECORD_ID_FIRST;
    while (next_record_id != IPMI_SDR_RECORD_ID_LAST)
    {
        uint8_t record_buf[IPMI_SDR_MAX_RECORD_LENGTH];
        int record_len;

        if (record_count_written >= ctx->record_count)
        {
            /* IPMI Workaround
             *
             * Discovered on unspecified Inspur motherboard
             *
             * SDR record reading is broken, the IPMI_SDR_RECORD_ID_LAST
             * record id never occurs.  So this workaround allows the
             * user to not error out and avoids this loop from looping
             * infinitely.
             *
             */

            if (cache_create_flags & IPMI_SDR_CACHE_CREATE_FLAGS_ASSUME_MAX_SDR_RECORD_COUNT)
                break;

            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_CREATE_INVALID_RECORD_COUNT);
            goto cleanup;
        }

        record_id = next_record_id;
        if ((record_len = _sdr_cache_get_record (ctx,
                          ipmi_ctx,
                          record_id,
                          record_buf,
                          IPMI_SDR_MAX_RECORD_LENGTH,
                          &reservation_id,
                          &next_record_id)) < 0)
            goto cleanup;

        if (record_len)
        {
            if (ctx->flags & IPMI_SDR_FLAGS_DEBUG_DUMP)
            {
                const char *record_str;

                if ((record_str = sdr_record_type_str (ctx,
                                                       record_buf,
                                                       record_len)))
                {
                    char hdrbuf[IPMI_SDR_CACHE_DEBUG_BUFLEN];

                    debug_hdr_str (DEBUG_UTIL_TYPE_NONE,
                                   DEBUG_UTIL_DIRECTION_NONE,
                                   DEBUG_UTIL_FLAGS_DEFAULT,
                                   record_str,
                                   hdrbuf,
                                   IPMI_SDR_CACHE_DEBUG_BUFLEN);

                    ipmi_dump_sdr_record (STDERR_FILENO,
                                          ctx->debug_prefix,
                                          hdrbuf,
                                          NULL,
                                          record_buf,
                                          record_len);
                }
            }

            if (_sdr_cache_record_write (ctx,
                                         fd,
                                         &total_bytes_written,
                                         record_ids,
                                         &record_ids_count,
                                         record_buf,
                                         record_len,
                                         &trailer_checksum) < 0)
                goto cleanup;

            record_count_written++;

            if (create_callback)
                (*create_callback)(ctx->sdr_version,
                                   ctx->record_count,
                                   ctx->most_recent_addition_timestamp,
                                   ctx->most_recent_erase_timestamp,
                                   record_id,
                                   create_callback_data);
        }
    }

    if (record_count_written != ctx->record_count)
    {
        /*
         * IPMI Workaround (achu)
         *
         * Discovered on Fujitsu RX 100
         * Discovered on Fujitsu RX300/200-S8
         *
         * The record_count listed from the Get SDR Repository Info command
         * is not consistent with the length of SDR records stored.
         *
         * We will assume that if we reached the end of the SDR record
         * list (i.e. next_record_id == 0xFFFF), a non-zero number of
         * records were written, it's ok and we can continue on.
         */
        if (next_record_id == IPMI_SDR_RECORD_ID_LAST
                && record_count_written)
        {
            unsigned int total_bytes_written_temp = 0;

            ctx->record_count = record_count_written;

            /* need to seek back to the beginning of the file and
             * re-write the header info with the correct number of
             * records
             */

            if (lseek (fd, 0, SEEK_SET) < 0)
            {
                SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
                goto cleanup;
            }

            if (_sdr_cache_header_write (ctx,
                                         ipmi_ctx,
                                         fd,
                                         &total_bytes_written_temp,
                                         ctx->sdr_version,
                                         ctx->record_count,
                                         ctx->most_recent_addition_timestamp,
                                         ctx->most_recent_erase_timestamp) < 0)
                goto cleanup;

            /* need to seek back to the end of the file to write the
             * trailer below
             */
            if (lseek (fd, 0, SEEK_END) < 0)
            {
                SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
                goto cleanup;
            }
        }
        else
        {
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_CREATE_INVALID_RECORD_COUNT);
            goto cleanup;
        }
    }

    if (_sdr_cache_trailer_write (ctx,
                                  ipmi_ctx,
                                  fd,
                                  total_bytes_written,
                                  trailer_checksum) < 0)
        goto cleanup;

    if (fsync (fd) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        goto cleanup;
    }

    if (close (fd) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        goto cleanup;
    }
    fd = -1;

    rv = 0;
    ctx->errnum = IPMI_SDR_ERR_SUCCESS;
cleanup:
    ctx->operation = IPMI_SDR_OPERATION_UNINITIALIZED;
    if (fd >= 0)
    {
        /* If the cache create never completed, try to remove the file */
        /* ignore potential error, cleanup path */
        unlink (filename);
        /* ignore potential error, cleanup path */
        close (fd);
    }
    free (record_ids);
    sdr_init_ctx (ctx);
    return (rv);
}
static int
_sdr_cache_record_write (ipmi_sdr_ctx_t ctx,
                         int fd,
                         unsigned int *total_bytes_written,
                         uint16_t *record_ids,
                         unsigned int *record_ids_count,
                         uint8_t *buf,
                         unsigned int buflen,
                         uint8_t *trailer_checksum)
{
    ssize_t n;

    assert (ctx);
    assert (ctx->magic == IPMI_SDR_CTX_MAGIC);
    assert (fd);
    assert (total_bytes_written);
    assert (!record_ids || (record_ids && record_ids_count));
    assert (buf);
    assert (buflen);
    assert (trailer_checksum);

    /* Record header bytes are 5 bytes */
    if (buflen < IPMI_SDR_RECORD_HEADER_LENGTH)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_CREATE_INVALID_RECORD_LENGTH);
        return (-1);
    }

    /* Record Length plus the header bytes should match buflen. */
    if ((((uint8_t)buf[IPMI_SDR_RECORD_LENGTH_INDEX]) + IPMI_SDR_RECORD_HEADER_LENGTH) != buflen)
    {
        /*
         * IPMI Workaround (achu)
         *
         * Discovered on HP Proliant DL585G7
         *
         * When reading an entire SDR record (using
         * IPMI_SDR_READ_ENTIRE_RECORD_BYTES_TO_READ), sometimes records
         * are returned with an excess of bytes.  The following
         * truncates the buffer length to the correct size.
         */
        if ((((uint8_t)buf[IPMI_SDR_RECORD_LENGTH_INDEX]) + IPMI_SDR_RECORD_HEADER_LENGTH) <= buflen)
            buflen = ((uint8_t)buf[IPMI_SDR_RECORD_LENGTH_INDEX]) + IPMI_SDR_RECORD_HEADER_LENGTH;
        else
        {
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_CREATE_INVALID_RECORD_LENGTH);
            return (-1);
        }
    }

    if (record_ids)
    {
        uint16_t record_id;
        unsigned int i;

        /* Record ID stored little endian */
        record_id = ((uint16_t)buf[IPMI_SDR_RECORD_ID_INDEX_LS] & 0xFF);
        record_id |= ((uint16_t)buf[IPMI_SDR_RECORD_ID_INDEX_MS] & 0xFF) << 8;

        for (i = 0; i < *record_ids_count; i++)
        {
            if (record_ids[i] == record_id)
            {
                SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_CACHE_CREATE_DUPLICATE_RECORD_ID);
                return (-1);
            }
        }
        record_ids[*record_ids_count] = record_id;
        (*record_ids_count)++;
    }

    if ((n = fd_write_n (fd, buf, buflen)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        return (-1);
    }

    if (n != buflen)
    {
        /* Try to lseek back to our original spot */
        lseek (fd, SEEK_SET, *total_bytes_written);
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_SYSTEM_ERROR);
        return (-1);
    }
    (*total_bytes_written) += buflen;

    (*trailer_checksum) = ipmi_checksum_incremental (buf, buflen, (*trailer_checksum));

    return (0);

}
static int
_sdr_cache_get_record (ipmi_sdr_ctx_t ctx,
                       ipmi_ctx_t ipmi_ctx,
                       uint16_t record_id,
                       void *record_buf,
                       unsigned int record_buf_len,
                       uint16_t *reservation_id,
                       uint16_t *next_record_id)
{
    fiid_obj_t obj_cmd_rs = NULL;
    fiid_obj_t obj_sdr_record_header = NULL;
    int sdr_record_header_length = 0;
    int sdr_record_len = 0;
    unsigned int record_length = 0;
    int rv = -1;
    unsigned int bytes_to_read = IPMI_SDR_CACHE_BYTES_TO_READ_START;
    unsigned int offset_into_record = 0;
    unsigned int reservation_id_retry_count = 0;
    uint8_t temp_record_buf[IPMI_SDR_MAX_RECORD_LENGTH];
    uint64_t val;

    assert (ctx);
    assert (ctx->magic == IPMI_SDR_CTX_MAGIC);
    assert (ipmi_ctx);
    assert (record_buf);
    assert (record_buf_len);
    assert (reservation_id);
    assert (next_record_id);

    if (!(obj_cmd_rs = fiid_obj_create (tmpl_cmd_get_sdr_rs)))
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        goto cleanup;
    }

    if (!(obj_sdr_record_header = fiid_obj_create (tmpl_sdr_record_header)))
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        goto cleanup;
    }

    if ((sdr_record_header_length = fiid_template_len_bytes (tmpl_sdr_record_header)) < 0)
    {
        SDR_ERRNO_TO_SDR_ERRNUM (ctx, errno);
        goto cleanup;
    }

    /* achu:
     *
     * Many motherboards now allow you to read the full SDR record, try
     * that first.  If it fails for any reason, bail and try to read via
     * partial reads.
     */

    reservation_id_retry_count = 0;
    while (!offset_into_record)
    {
        if (ipmi_cmd_get_sdr (ipmi_ctx,
                              *reservation_id,
                              record_id,
                              0,
                              IPMI_SDR_READ_ENTIRE_RECORD_BYTES_TO_READ,
                              obj_cmd_rs) < 0)
        {
            if (ipmi_ctx_errnum (ipmi_ctx) == IPMI_ERR_BAD_COMPLETION_CODE)
            {
                uint8_t comp_code;

                if (FIID_OBJ_GET (obj_cmd_rs,
                                  "comp_code",
                                  &val) < 0)
                {
                    SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_cmd_rs);
                    goto cleanup;
                }
                comp_code = val;

                if (comp_code == IPMI_COMP_CODE_RESERVATION_CANCELLED
                        && (reservation_id_retry_count < IPMI_SDR_CACHE_MAX_RESERVATION_ID_RETRY))
                {
                    if (_sdr_cache_reservation_id (ctx,
                                                   ipmi_ctx,
                                                   reservation_id) < 0)
                        goto cleanup;
                    reservation_id_retry_count++;
                    continue;
                }
            }

            goto partial_read;
        }

        if ((sdr_record_len = fiid_obj_get_data (obj_cmd_rs,
                              "record_data",
                              temp_record_buf,
                              IPMI_SDR_MAX_RECORD_LENGTH)) < 0)
        {
            SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_cmd_rs);
            goto cleanup;
        }

        /* Assume this is an "IPMI Error", fall through to partial reads */
        if (sdr_record_len < sdr_record_header_length)
            goto partial_read;

        /*
         * IPMI Workaround (achu)
         *
         * Discovered on Xyratex HB-F8-SRAY
         *
         * For some reason reading the entire SDR record (with
         * IPMI_SDR_READ_ENTIRE_RECORD_BYTES_TO_READ) the response
         * returns fewer bytes than the actual length of the record.
         * However, when reading with partial reads things ultimately
         * succeed.  If we notice the length is off, we fall out and do
         * a partial read.
         */
        if ((((uint8_t)temp_record_buf[IPMI_SDR_RECORD_LENGTH_INDEX]) + IPMI_SDR_RECORD_HEADER_LENGTH) > sdr_record_len)
            goto partial_read;

        if (sdr_record_len > record_buf_len)
        {
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_INTERNAL_ERROR);
            goto cleanup;
        }

        if (FIID_OBJ_GET (obj_cmd_rs,
                          "next_record_id",
                          &val) < 0)
        {
            SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_cmd_rs);
            goto cleanup;
        }
        *next_record_id = val;

        memcpy (record_buf, temp_record_buf, sdr_record_len);
        offset_into_record += sdr_record_len;
        goto out;
    }

partial_read:

    reservation_id_retry_count = 0;
    while (!record_length)
    {
        uint8_t record_header_buf[IPMI_SDR_MAX_RECORD_LENGTH];
        int sdr_record_header_len;

        if (ipmi_cmd_get_sdr (ipmi_ctx,
                              *reservation_id,
                              record_id,
                              0,
                              sdr_record_header_length,
                              obj_cmd_rs) < 0)
        {
            if (ipmi_ctx_errnum (ipmi_ctx) != IPMI_ERR_BAD_COMPLETION_CODE)
            {
                SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_IPMI_ERROR);
                goto cleanup;
            }
            else
            {
                uint8_t comp_code;

                if (FIID_OBJ_GET (obj_cmd_rs,
                                  "comp_code",
                                  &val) < 0)
                {
                    SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_cmd_rs);
                    goto cleanup;
                }
                comp_code = val;

                if (comp_code == IPMI_COMP_CODE_RESERVATION_CANCELLED
                        && (reservation_id_retry_count < IPMI_SDR_CACHE_MAX_RESERVATION_ID_RETRY))
                {
                    if (_sdr_cache_reservation_id (ctx,
                                                   ipmi_ctx,
                                                   reservation_id) < 0)
                        goto cleanup;
                    reservation_id_retry_count++;
                    continue;
                }

                SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_IPMI_ERROR);
                goto cleanup;
            }
        }

        if ((sdr_record_header_len = fiid_obj_get_data (obj_cmd_rs,
                                     "record_data",
                                     record_header_buf,
                                     IPMI_SDR_MAX_RECORD_LENGTH)) < 0)
        {
            SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_cmd_rs);
            goto cleanup;
        }

        if (sdr_record_header_len < sdr_record_header_length)
        {
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_IPMI_ERROR);
            goto cleanup;
        }

        if (fiid_obj_set_all (obj_sdr_record_header,
                              record_header_buf,
                              sdr_record_header_len) < 0)
        {
            SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_sdr_record_header);
            goto cleanup;
        }

        if (FIID_OBJ_GET (obj_sdr_record_header,
                          "record_length",
                          &val) < 0)
        {
            SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_sdr_record_header);
            goto cleanup;
        }

        if (sdr_record_header_len > record_buf_len)
        {
            SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_INTERNAL_ERROR);
            goto cleanup;
        }

        /* copy header into buf */
        memcpy (record_buf, record_header_buf, sdr_record_header_len);
        offset_into_record += sdr_record_header_len;
        record_length = val + sdr_record_header_length;
    }

    if (record_length > record_buf_len)
    {
        SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_INTERNAL_ERROR);
        goto cleanup;
    }

    if (FIID_OBJ_GET (obj_cmd_rs,
                      "next_record_id",
                      &val) < 0)
    {
        SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_cmd_rs);
        goto cleanup;
    }
    *next_record_id = val;

    reservation_id_retry_count = 0;
    while (offset_into_record < record_length)
    {
        int record_data_len;

        if ((record_length - offset_into_record) < bytes_to_read)
            bytes_to_read = record_length - offset_into_record;

        if (ipmi_cmd_get_sdr (ipmi_ctx,
                              *reservation_id,
                              record_id,
                              offset_into_record,
                              bytes_to_read,
                              obj_cmd_rs) < 0)
        {
            if (ipmi_ctx_errnum (ipmi_ctx) != IPMI_ERR_BAD_COMPLETION_CODE)
            {
                SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_IPMI_ERROR);
                goto cleanup;
            }
            else
            {
                uint8_t comp_code;

                if (FIID_OBJ_GET (obj_cmd_rs,
                                  "comp_code",
                                  &val) < 0)
                {
                    SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_cmd_rs);
                    goto cleanup;
                }
                comp_code = val;

                if (comp_code == IPMI_COMP_CODE_RESERVATION_CANCELLED
                        && (reservation_id_retry_count < IPMI_SDR_CACHE_MAX_RESERVATION_ID_RETRY))
                {
                    if (_sdr_cache_reservation_id (ctx,
                                                   ipmi_ctx,
                                                   reservation_id) < 0)
                        goto cleanup;
                    reservation_id_retry_count++;
                    continue;
                }
                else if  ((comp_code == IPMI_COMP_CODE_CANNOT_RETURN_REQUESTED_NUMBER_OF_BYTES
                           || comp_code == IPMI_COMP_CODE_UNSPECIFIED_ERROR)
                          && bytes_to_read > sdr_record_header_length)
                {
                    bytes_to_read -= IPMI_SDR_CACHE_BYTES_TO_READ_DECREMENT;
                    if (bytes_to_read < sdr_record_header_length)
                        bytes_to_read = sdr_record_header_length;
                    continue;
                }

                SDR_SET_ERRNUM (ctx, IPMI_SDR_ERR_IPMI_ERROR);
                goto cleanup;
            }
        }

        if ((record_data_len = fiid_obj_get_data (obj_cmd_rs,
                               "record_data",
                               record_buf + offset_into_record,
                               record_buf_len - offset_into_record)) < 0)
        {
            SDR_FIID_OBJECT_ERROR_TO_SDR_ERRNUM (ctx, obj_cmd_rs);
            goto cleanup;
        }

        offset_into_record += record_data_len;
    }

out:
    rv = offset_into_record;
cleanup:
    fiid_obj_destroy (obj_cmd_rs);
    fiid_obj_destroy (obj_sdr_record_header);
    return (rv);
}