/*
 * Copyright (C) 2003-2015 FreeIPMI Core Team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>
#if STDC_HEADERS
#include <string.h>
#include <stdarg.h>
#endif /* STDC_HEADERS */
#include <assert.h>
#include <errno.h>

#include "freeipmi/sel/ipmi-sel.h"

#include "freeipmi/cmds/ipmi-sel-cmds.h"
#include "freeipmi/interpret/ipmi-interpret.h"
#include "freeipmi/record-format/ipmi-sdr-record-format.h"
#include "freeipmi/record-format/ipmi-sel-record-format.h"
#include "freeipmi/record-format/oem/ipmi-sel-oem-linux-kernel-record-format.h"
#include "freeipmi/sdr/ipmi-sdr.h"
#include "freeipmi/spec/ipmi-event-reading-type-code-spec.h"
#include "freeipmi/spec/ipmi-iana-enterprise-numbers-spec.h"
#include "freeipmi/spec/ipmi-product-id-spec.h"
#include "freeipmi/spec/ipmi-sensor-and-event-code-tables-spec.h"
#include "freeipmi/spec/ipmi-sensor-and-event-code-tables-oem-spec.h"
#include "freeipmi/spec/ipmi-sensor-types-spec.h"
#include "freeipmi/spec/ipmi-sensor-units-spec.h"
#include "freeipmi/spec/ipmi-slave-address-spec.h"
#include "freeipmi/spec/ipmi-netfn-spec.h"
#include "freeipmi/spec/ipmi-comp-code-spec.h"
#include "freeipmi/spec/oem/ipmi-slave-address-oem-linux-kernel-spec.h"
#include "freeipmi/util/ipmi-iana-enterprise-numbers-util.h"
#include "freeipmi/util/ipmi-sensor-and-event-code-tables-util.h"
#include "freeipmi/util/ipmi-sensor-util.h"
#include "freeipmi/util/ipmi-timestamp-util.h"
#include "freeipmi/util/ipmi-util.h"

#include "ipmi-sel-common.h"
#include "ipmi-sel-defs.h"
#include "ipmi-sel-string.h"
#include "ipmi-sel-string-linux-kernel.h"
#include "ipmi-sel-trace.h"
#include "ipmi-sel-util.h"

#include "freeipmi-portability.h"

#define NA_STRING         "N/A"
#define ASSERTION_EVENT   "Assertion Event"
#define DEASSERTION_EVENT "Deassertion Event"

#define EVENT_BUFFER_LENGTH     4096
#define SEL_BUFFER_LENGTH       256
#define SENSOR_NAME_LENGTH      256
#define IANA_LENGTH             1024
#define UNITS_BUFFER_LENGTH     1024

extern struct sel_string_oem sel_string_oem_dell_poweredge;
extern struct sel_string_oem sel_string_oem_fujitsu_irmc_common;
extern struct sel_string_oem sel_string_oem_gigabyte_md90fs0zb;
extern struct sel_string_oem sel_string_oem_gigabyte_mg20op0zb;
extern struct sel_string_oem sel_string_oem_intel_quanta_qssc_s4r;
extern struct sel_string_oem sel_string_oem_intel_s2600jf;
extern struct sel_string_oem sel_string_oem_intel_s2600wp;
extern struct sel_string_oem sel_string_oem_intel_s2600kp;
extern struct sel_string_oem sel_string_oem_intel_s2600gz;
extern struct sel_string_oem sel_string_oem_intel_s2600wt_common;
extern struct sel_string_oem sel_string_oem_intel_s5500wb;
extern struct sel_string_oem sel_string_oem_intel_windmill;
extern struct sel_string_oem sel_string_oem_inventec_5441;
extern struct sel_string_oem sel_string_oem_inventec_5442;
extern struct sel_string_oem sel_string_oem_linux_kernel;
extern struct sel_string_oem sel_string_oem_quanta_s99q;
extern struct sel_string_oem sel_string_oem_sun_x4140;
extern struct sel_string_oem sel_string_oem_supermicro_overheat_common;
extern struct sel_string_oem sel_string_oem_supermicro_magnum_technologies_x8dtl;
extern struct sel_string_oem sel_string_oem_supermicro_peppercon_common;
extern struct sel_string_oem sel_string_oem_supermicro_x10dimm;
extern struct sel_string_oem sel_string_oem_wistron_c6220;

/* returns 0 on success, 1 on success but w/ truncation */
int
sel_string_snprintf (char *buf,
                     unsigned int buflen,
                     unsigned int *wlen,
                     const char *fmt,
                     ...)
{
  va_list ap;
  int ret;

  assert (buf);
  assert (buflen);
  assert (wlen);
  assert (fmt);

  va_start (ap, fmt);
  ret = vsnprintf (buf + *wlen, buflen - *wlen, fmt, ap);
  va_end (ap);
  if (ret >= (buflen - *wlen))
    {
      (*wlen) = buflen;
      return (1);
    }
  (*wlen) += ret;
  return (0);
}

int
sel_string_strcat_comma_separate (char *buf,
                                  unsigned int buflen,
                                  unsigned int *wlen,
                                  const char *str)
{
  assert (buf);
  assert (buflen);
  assert (wlen);
  assert (str);

  if (strlen (buf) > 0)
    {
      if (sel_string_snprintf (buf, buflen, wlen, ", "))
        return (1);
    }

  return (sel_string_snprintf (buf, buflen, wlen, "%s", str));
}

static int
_find_motherboard_oem_support (ipmi_sel_ctx_t ctx,
                               unsigned int flags,
                               struct sel_string_oem **sel_string_oem)
{
  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA);
  assert (sel_string_oem);

  if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_DELL)
    {
      assert (IPMI_DELL_PRODUCT_ID_POWEREDGE_2900 == IPMI_DELL_PRODUCT_ID_POWEREDGE_2950);
      assert (IPMI_DELL_PRODUCT_ID_POWEREDGE_2900 == IPMI_DELL_PRODUCT_ID_POWEREDGE_R610);
      assert (IPMI_DELL_PRODUCT_ID_POWEREDGE_2900 == IPMI_DELL_PRODUCT_ID_POWEREDGE_R710);
      assert (IPMI_DELL_PRODUCT_ID_POWEREDGE_2900 == IPMI_DELL_PRODUCT_ID_POWEREDGE_R720);

      if (ctx->product_id == IPMI_DELL_PRODUCT_ID_POWEREDGE_2900
          || ctx->product_id == IPMI_DELL_PRODUCT_ID_POWEREDGE_2950
          || ctx->product_id == IPMI_DELL_PRODUCT_ID_POWEREDGE_R610
          || ctx->product_id == IPMI_DELL_PRODUCT_ID_POWEREDGE_R710
          || ctx->product_id == IPMI_DELL_PRODUCT_ID_POWEREDGE_R720)
        (*sel_string_oem) = &sel_string_oem_dell_poweredge;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_FUJITSU)
    {
      if (ctx->product_id >= IPMI_FUJITSU_PRODUCT_ID_MIN
          && ctx->product_id <= IPMI_FUJITSU_PRODUCT_ID_MAX)
        (*sel_string_oem) = &sel_string_oem_fujitsu_irmc_common;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_GIGABYTE)
    {
      if (ctx->product_id == IPMI_GIGABYTE_PRODUCT_ID_MD90_FS0_ZB)
        (*sel_string_oem) = &sel_string_oem_gigabyte_md90fs0zb;
      else if (ctx->product_id == IPMI_GIGABYTE_PRODUCT_ID_MG20_OP0_ZB)
        (*sel_string_oem) = &sel_string_oem_gigabyte_mg20op0zb;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_INTEL)
    {
      /* achu:
       *
       * ARGH! Intel gave the S2600KPR an identical product ID number as the
       * S2600WT2 and S2600WTT.
       *
       * That's why IPMI_INTEL_PRODUCT_ID_S2600KPR returns
       * sel_string_oem_intel_s2600wt_common, even though it seemingly
       * should return sel_string_oem_intel_s2600kp.
       */

      assert (IPMI_INTEL_PRODUCT_ID_S2600WT2 == IPMI_INTEL_PRODUCT_ID_S2600WTT);
      assert (IPMI_INTEL_PRODUCT_ID_S2600WT2 == IPMI_INTEL_PRODUCT_ID_S2600KPR);

      if (ctx->product_id == IPMI_INTEL_PRODUCT_ID_QUANTA_QSSC_S4R)
        (*sel_string_oem) = &sel_string_oem_intel_quanta_qssc_s4r;
      else if (ctx->product_id == IPMI_INTEL_PRODUCT_ID_S2600JF)
        (*sel_string_oem) = &sel_string_oem_intel_s2600jf;
      else if (ctx->product_id == IPMI_INTEL_PRODUCT_ID_S2600WP)
        (*sel_string_oem) = &sel_string_oem_intel_s2600wp;
      else if (ctx->product_id == IPMI_INTEL_PRODUCT_ID_S2600KP)
        (*sel_string_oem) = &sel_string_oem_intel_s2600kp;
      else if (ctx->product_id == IPMI_INTEL_PRODUCT_ID_S2600GZ)
        (*sel_string_oem) = &sel_string_oem_intel_s2600gz;
      else if (ctx->product_id == IPMI_INTEL_PRODUCT_ID_S2600WT2
               || ctx->product_id == IPMI_INTEL_PRODUCT_ID_S2600WTT
               || ctx->product_id == IPMI_INTEL_PRODUCT_ID_S2600KPR)
        (*sel_string_oem) = &sel_string_oem_intel_s2600wt_common;
      else if (ctx->product_id == IPMI_INTEL_PRODUCT_ID_S5500WB)
        (*sel_string_oem) = &sel_string_oem_intel_s5500wb;
      else if (ctx->product_id == IPMI_INTEL_PRODUCT_ID_WINDMILL)
        (*sel_string_oem) = &sel_string_oem_intel_windmill;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_INVENTEC)
    {
      if (ctx->product_id == IPMI_INVENTEC_PRODUCT_ID_5441)
        (*sel_string_oem) = &sel_string_oem_inventec_5441;
      else if (ctx->product_id == IPMI_INVENTEC_PRODUCT_ID_5442)
        (*sel_string_oem) = &sel_string_oem_inventec_5442;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_QUANTA)
    {
      if (ctx->product_id == IPMI_QUANTA_PRODUCT_ID_S99Q)
        (*sel_string_oem) = &sel_string_oem_quanta_s99q;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_SUN_MICROSYSTEMS)
    {
      if (ctx->product_id == IPMI_SUN_MICROSYSTEMS_PRODUCT_ID_X4140)
        (*sel_string_oem) = &sel_string_oem_sun_x4140;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_PEPPERCON) /* Supermicro variant */
    {
      assert (IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X7DBR_3 == IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X7DB8);
      assert (IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X7DBR_3 == IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X8DTN);
      assert (IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X7DBR_3 == IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X7SBI_LN4);

      if (ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X7DBR_3
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X7DB8
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X8DTN
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_PEPPERCON_X7SBI_LN4)
        (*sel_string_oem) = &sel_string_oem_supermicro_peppercon_common;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_SUPERMICRO
           || ctx->manufacturer_id ==  IPMI_IANA_ENTERPRISE_ID_SUPERMICRO_WORKAROUND)
    {
      assert (IPMI_SUPERMICRO_PRODUCT_ID_X8DTH == IPMI_SUPERMICRO_PRODUCT_ID_X8DTG);
      assert (IPMI_SUPERMICRO_PRODUCT_ID_X8DTH == IPMI_SUPERMICRO_PRODUCT_ID_X8DTU);
      assert (IPMI_SUPERMICRO_PRODUCT_ID_X8DTH == IPMI_SUPERMICRO_PRODUCT_ID_X8DT3_LN4F);

      assert (IPMI_SUPERMICRO_PRODUCT_ID_X8DTL == IPMI_SUPERMICRO_PRODUCT_ID_X8DTL_3F);

      assert (IPMI_SUPERMICRO_PRODUCT_ID_X9SCL == IPMI_SUPERMICRO_PRODUCT_ID_X9SCM);

      if (ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8DTH
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8DTG
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8DTU
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8DT3_LN4F
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8DTU_6PLUS
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8DTL
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8DTL_3F
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8SIL_F
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X9SCL
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X9SCM
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8DTNPLUS_F
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X8SIE
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X9SCA_F_O
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_H8DGU_F
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_H8DGU
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_H8DG6
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X9DRI_F
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X9DRI_LN4F_PLUS
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X9SPU_F_O
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X9SCM_IIF
          || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_H8SGL_F)
        (*sel_string_oem) = &sel_string_oem_supermicro_overheat_common;
      else if (ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10DRH_BASE
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10DRW_BASE
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10DRI_BASE
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10SL_BASE
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10SLL_F
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10DRL_I
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10SLM_F
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10SRW_F
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10SRI_F
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10DRW_I
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10SRL_F
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10DDW_I
               || ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_X10DRG_HT)
        (*sel_string_oem) = &sel_string_oem_supermicro_x10dimm;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_MAGNUM_TECHNOLOGIES) /* Supermicro variant */
    {
      if (ctx->product_id == IPMI_SUPERMICRO_PRODUCT_ID_MAGNUM_TECHNOLOGIES_X8DTL)
        (*sel_string_oem) = &sel_string_oem_supermicro_magnum_technologies_x8dtl;
    }
  else if (ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_WISTRON)
    {
      if (ctx->product_id == IPMI_WISTRON_PRODUCT_ID_C6220)
        (*sel_string_oem) = &sel_string_oem_wistron_c6220;
    }

  return (0);
}

static int
_invalid_sel_entry_common (ipmi_sel_ctx_t ctx,
                           char *buf,
                           unsigned int buflen,
                           unsigned int flags,
                           unsigned int *wlen)
{
  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (flags & IPMI_SEL_STRING_FLAGS_IGNORE_UNAVAILABLE_FIELD)
    {
      if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
        {
          if (sel_string_snprintf (buf, buflen, wlen, "%s", NA_STRING))
            return (1);
          return (0);
        }
      return (0);
    }
  ctx->errnum = IPMI_SEL_ERR_INVALID_SEL_ENTRY;
  return (-1);
}

/*
 * return (1) - found record
 * return (0) - can't find record
 * return (-1) - error
 */
static int
_find_and_seek_record (ipmi_sel_ctx_t ctx,
                       struct ipmi_sel_system_event_record_data *system_event_record_data)
{
  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (ctx->sdr_ctx);   /* must be checked earlier */
  assert (system_event_record_data);

  if (ipmi_sdr_cache_search_sensor (ctx->sdr_ctx,
                                    system_event_record_data->sensor_number,
                                    system_event_record_data->generator_id) < 0)
    {
      /* IPMI Workaround (achu)
       *
       * Discovered on Supermicro H8QME with SIMSO daughter card.
       *
       * The slave address is reportedly incorrectly by having the
       * generator_id be shifted over by one.  This is a special
       * "try again" corner case.
       */
      if (ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_NOT_FOUND
          && (system_event_record_data->generator_id == (IPMI_SLAVE_ADDRESS_BMC << 1)))
        {
          if (!ipmi_sdr_cache_search_sensor (ctx->sdr_ctx,
                                             system_event_record_data->sensor_number,
                                             (system_event_record_data->generator_id >> 1)))
            return (1);
          /* else fall through to normal error path */
        }

      if (ipmi_sdr_ctx_errnum (ctx->sdr_ctx) != IPMI_SDR_ERR_NOT_FOUND)
        {
          SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_SDR_CACHE_ERROR);
          return (-1);
        }
      /* else can't find it */
      return (0);
    }

  return (1);
}

/*
 * return (1) - parsed fine
 * return (0) - can't parse info/non-decodable
 * return (-1) - error
 */
static int
_get_sensor_name (ipmi_sel_ctx_t ctx,
                  struct ipmi_sel_system_event_record_data *system_event_record_data,
                  unsigned int flags,
                  char *sensor_name,
                  unsigned int sensor_name_len)
{
  int rv = -1;
  int ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (system_event_record_data);
  assert (sensor_name);
  assert (sensor_name_len);

  if (!ctx->sdr_ctx)
    return (0);

  if ((ret = _find_and_seek_record (ctx, system_event_record_data)) < 0)
    return (-1);

  if (!ret)
    {
      rv = 0;
      goto cleanup;
    }

  if (flags & IPMI_SEL_STRING_FLAGS_ENTITY_SENSOR_NAMES)
    {
      if (ipmi_sdr_parse_entity_sensor_name (ctx->sdr_ctx,
                                             NULL,
                                             0,
                                             system_event_record_data->sensor_number,
                                             0, /* flags */
                                             sensor_name,
                                             sensor_name_len) < 0)
        {
          if (ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INVALID_SDR_RECORD
              || ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INCOMPLETE_SDR_RECORD)
            rv = 0;
          else
            SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
          goto cleanup;
        }
    }
  else
    {
      if (ipmi_sdr_parse_sensor_name (ctx->sdr_ctx,
                                      NULL,
                                      0,
                                      system_event_record_data->sensor_number,
                                      0, /* flags */
                                      sensor_name,
                                      sensor_name_len) < 0)
        {
          if (ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INVALID_SDR_RECORD
              || ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INCOMPLETE_SDR_RECORD)
            rv = 0;
          else
            SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
          goto cleanup;
        }
    }

  rv = 1;
 cleanup:
  return (rv);
}

/*
 * return (1) - parsed fine
 * return (0) - can't parse info/non-decodable
 * return (-1) - error
 */
static int
_get_sensor_reading (ipmi_sel_ctx_t ctx,
                     struct ipmi_sel_system_event_record_data *system_event_record_data,
                     unsigned int flags,
                     uint8_t raw_data,
                     double *reading,
                     char *sensor_units_buf,
                     unsigned int sensor_units_buflen)
{
  uint8_t sdr_event_reading_type_code;
  int8_t r_exponent;
  int8_t b_exponent;
  int16_t m;
  int16_t b;
  uint8_t linearization;
  uint8_t analog_data_format;
  uint8_t sensor_units_percentage;
  uint8_t sensor_units_modifier;
  uint8_t sensor_units_rate;
  uint8_t sensor_base_unit_type;
  uint8_t sensor_modifier_unit_type;
  int abbreviated_units;
  int sensor_units_ret;
  int rv = -1;
  int ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (system_event_record_data);
  assert (ipmi_event_reading_type_code_class (system_event_record_data->event_type_code) == IPMI_EVENT_READING_TYPE_CODE_CLASS_THRESHOLD);
  assert (reading);
  assert (sensor_units_buf);
  assert (sensor_units_buflen);

  if (!ctx->sdr_ctx)
    return (0);

  if ((ret = _find_and_seek_record (ctx, system_event_record_data)) < 0)
    return (-1);

  if (!ret)
    {
      rv = 0;
      goto cleanup;
    }

  if (ipmi_sdr_parse_event_reading_type_code (ctx->sdr_ctx,
                                              NULL,
                                              0,
                                              &sdr_event_reading_type_code) < 0)
    {
      if (ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INVALID_SDR_RECORD
          || ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INCOMPLETE_SDR_RECORD)
        rv = 0;
      else
        SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
      goto cleanup;
    }

  if (ipmi_event_reading_type_code_class (sdr_event_reading_type_code) != IPMI_EVENT_READING_TYPE_CODE_CLASS_THRESHOLD)
    {
      rv = 0;
      goto cleanup;
    }

  if (ipmi_sdr_parse_sensor_decoding_data (ctx->sdr_ctx,
                                           NULL,
                                           0,
                                           &r_exponent,
                                           &b_exponent,
                                           &m,
                                           &b,
                                           &linearization,
                                           &analog_data_format) < 0)
    {
      if (ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INVALID_SDR_RECORD
          || ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INCOMPLETE_SDR_RECORD)
        rv = 0;
      else
        SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
      goto cleanup;
    }

  if (ipmi_sdr_parse_sensor_units (ctx->sdr_ctx,
                                   NULL,
                                   0,
                                   &sensor_units_percentage,
                                   &sensor_units_modifier,
                                   &sensor_units_rate,
                                   &sensor_base_unit_type,
                                   &sensor_modifier_unit_type) < 0)
    {
      if (ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INVALID_SDR_RECORD
          || ipmi_sdr_ctx_errnum (ctx->sdr_ctx) == IPMI_SDR_ERR_PARSE_INCOMPLETE_SDR_RECORD)
        rv = 0;
      else
        SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
      goto cleanup;
    }

  if (flags & IPMI_SEL_STRING_FLAGS_NON_ABBREVIATED_UNITS)
    abbreviated_units = 0;
  else
    abbreviated_units = 1;

  sensor_units_ret = ipmi_sensor_units_string (sensor_units_percentage,
                                               sensor_units_modifier,
                                               sensor_units_rate,
                                               sensor_base_unit_type,
                                               sensor_modifier_unit_type,
                                               sensor_units_buf,
                                               sensor_units_buflen,
                                               abbreviated_units);

  if (sensor_units_ret <= 0)
    snprintf (sensor_units_buf,
              sensor_units_buflen,
              "%s",
              ipmi_sensor_units[IPMI_SENSOR_UNIT_UNSPECIFIED]);

  /* if the sensor is not analog, this is most likely a bug in the
   * SDR
   */
  if (!IPMI_SDR_ANALOG_DATA_FORMAT_VALID (analog_data_format))
    {
      rv = 0;
      goto cleanup;
    }

  /* We don't currently handle non-linear sensors */
  if (!IPMI_SDR_LINEARIZATION_IS_LINEAR (linearization))
    {
      rv = 0;
      goto cleanup;
    }

  if (ipmi_sensor_decode_value (r_exponent,
                                b_exponent,
                                m,
                                b,
                                linearization,
                                analog_data_format,
                                raw_data,
                                reading) < 0)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
      goto cleanup;
    }

  rv = 1;
 cleanup:
  return (rv);
}

static int
_get_previous_state_or_severity (ipmi_sel_ctx_t ctx,
                                 struct ipmi_sel_entry *sel_entry,
                                 uint8_t *previous_offset_from_event_reading_type_code,
                                 uint8_t *offset_from_severity_event_reading_type_code)
{
  fiid_obj_t obj_sel_system_event_record = NULL;
  uint64_t val;
  int rv = -1;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (previous_offset_from_event_reading_type_code);
  assert (offset_from_severity_event_reading_type_code);

  if (!(obj_sel_system_event_record = fiid_obj_create (tmpl_sel_system_event_record_discrete_previous_state_severity)))
    {
      SEL_ERRNO_TO_SEL_ERRNUM (ctx, errno);
      goto cleanup;
    }

  if (fiid_obj_set_all (obj_sel_system_event_record,
                        sel_entry->sel_event_record,
                        sel_entry->sel_event_record_len) < 0)
    {
      SEL_FIID_OBJECT_ERROR_TO_SEL_ERRNUM (ctx, obj_sel_system_event_record);
      goto cleanup;
    }

  if (FIID_OBJ_GET (obj_sel_system_event_record,
                    "previous_offset_from_event_reading_type_code",
                    &val) < 0)
    {
      SEL_FIID_OBJECT_ERROR_TO_SEL_ERRNUM (ctx, obj_sel_system_event_record);
      goto cleanup;
    }
  *previous_offset_from_event_reading_type_code = val;

  if (FIID_OBJ_GET (obj_sel_system_event_record,
                    "offset_from_severity_event_reading_type_code",
                    &val) < 0)
    {
      SEL_FIID_OBJECT_ERROR_TO_SEL_ERRNUM (ctx, obj_sel_system_event_record);
      goto cleanup;
    }
  *offset_from_severity_event_reading_type_code = val;

  rv = 0;
 cleanup:
  fiid_obj_destroy (obj_sel_system_event_record);
  return (rv);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_interpretation (ipmi_sel_ctx_t ctx,
                              struct ipmi_sel_entry *sel_entry,
                              uint8_t sel_record_type,
                              char *buf,
                              unsigned int buflen,
                              unsigned int flags,
                              unsigned int *wlen)
{
  unsigned int interpret_flags_save;
  unsigned int interpret_flags;
  unsigned int sel_state;
  char *sel_state_str = NULL;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD
      && ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_TIMESTAMPED_OEM_RECORD
      && ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_NON_TIMESTAMPED_OEM_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (!ctx->interpret_ctx)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERPRET_ERROR);
      return (-1);
    }

  if (ipmi_interpret_ctx_get_flags (ctx->interpret_ctx, &interpret_flags_save) < 0)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERPRET_ERROR);
      return (-1);
    }

  interpret_flags = interpret_flags_save;

  if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
    interpret_flags |= IPMI_INTERPRET_FLAGS_INTERPRET_OEM_DATA;

  if (ipmi_interpret_ctx_set_flags (ctx->interpret_ctx, interpret_flags) < 0)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERPRET_ERROR);
      return (-1);
    }

  if (ipmi_interpret_sel (ctx->interpret_ctx,
                          sel_entry->sel_event_record,
                          sel_entry->sel_event_record_len,
                          &sel_state) < 0)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERPRET_ERROR);
      return (-1);
    }

  if (ipmi_interpret_ctx_set_flags (ctx->interpret_ctx, interpret_flags_save) < 0)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERPRET_ERROR);
      return (-1);
    }

  /* achu
   *
   * NOT_AVAILABLE and IGNORE_UNAVAILABLE_FIELD not applicable here.
   * This output is always possible, the N/A indicates that there is
   * no interpretation available for the current event.
   */
  if (sel_state == IPMI_INTERPRET_STATE_NOMINAL)
    sel_state_str = "Nominal";
  else if (sel_state == IPMI_INTERPRET_STATE_WARNING)
    sel_state_str = "Warning";
  else if (sel_state == IPMI_INTERPRET_STATE_CRITICAL)
    sel_state_str = "Critical";
  else
    sel_state_str = NA_STRING;

  if (sel_string_snprintf (buf, buflen, wlen, "%s", sel_state_str))
    return (1);

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_time (ipmi_sel_ctx_t ctx,
              struct ipmi_sel_entry *sel_entry,
              uint8_t sel_record_type,
              char *buf,
              unsigned int buflen,
              unsigned int flags,
              unsigned int *wlen)
{
  char tmpbuf[SEL_BUFFER_LENGTH + 1];
  unsigned int timestamp_flags;
  uint32_t timestamp;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD
      && ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_TIMESTAMPED_OEM_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_timestamp (ctx, sel_entry, &timestamp) < 0)
    return (-1);

  memset (tmpbuf, '\0', SEL_BUFFER_LENGTH + 1);

  timestamp_flags = IPMI_TIMESTAMP_FLAG_ABBREVIATE;
  if (flags & IPMI_SEL_STRING_FLAGS_UTC_TO_LOCALTIME)
    timestamp_flags |= IPMI_TIMESTAMP_FLAG_UTC_TO_LOCALTIME;
  if (flags & IPMI_SEL_STRING_FLAGS_LOCALTIME_TO_UTC)
    timestamp_flags |= IPMI_TIMESTAMP_FLAG_LOCALTIME_TO_UTC;

  if (ipmi_timestamp_string (timestamp,
                             ctx->utc_offset,
                             timestamp_flags,
                             "%H:%M:%S",
                             tmpbuf,
                             SEL_BUFFER_LENGTH) < 0)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
      return (-1);
    }

  if (sel_string_snprintf (buf, buflen, wlen, "%s", tmpbuf))
    return (1);

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_date (ipmi_sel_ctx_t ctx,
              struct ipmi_sel_entry *sel_entry,
              uint8_t sel_record_type,
              char *buf,
              unsigned int buflen,
              unsigned int flags,
              unsigned int *wlen)
{
  char tmpbuf[SEL_BUFFER_LENGTH + 1];
  unsigned int timestamp_flags;
  uint32_t timestamp;
  char *date_format = NULL;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD
      && ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_TIMESTAMPED_OEM_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_timestamp (ctx, sel_entry, &timestamp) < 0)
    return (-1);

  if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
    date_format = "%d-%b-%Y";
  else
    {
      if (flags & IPMI_SEL_STRING_FLAGS_DATE_MONTH_STRING)
        {
          if (flags & IPMI_SEL_STRING_FLAGS_DATE_USE_SLASH)
            date_format = "%b/%d/%Y";
          else
            date_format = "%b-%d-%Y";
        }
      else
        {
          if (flags & IPMI_SEL_STRING_FLAGS_DATE_USE_SLASH)
            date_format = "%m/%d/%Y";
          else
            date_format = "%m-%d-%Y";
        }
    }

  memset (tmpbuf, '\0', SEL_BUFFER_LENGTH + 1);

  timestamp_flags = IPMI_TIMESTAMP_FLAG_ABBREVIATE;
  if (flags & IPMI_SEL_STRING_FLAGS_UTC_TO_LOCALTIME)
    timestamp_flags |= IPMI_TIMESTAMP_FLAG_UTC_TO_LOCALTIME;
  if (flags & IPMI_SEL_STRING_FLAGS_LOCALTIME_TO_UTC)
    timestamp_flags |= IPMI_TIMESTAMP_FLAG_LOCALTIME_TO_UTC;

  if (ipmi_timestamp_string (timestamp,
                             ctx->utc_offset,
                             timestamp_flags,
                             date_format,
                             tmpbuf,
                             SEL_BUFFER_LENGTH) < 0)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
      return (-1);
    }

  if (sel_string_snprintf (buf, buflen, wlen, "%s", tmpbuf))
    return (1);
  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_sensor_type (ipmi_sel_ctx_t ctx,
                     struct ipmi_sel_entry *sel_entry,
                     uint8_t sel_record_type,
                     char *buf,
                     unsigned int buflen,
                     unsigned int flags,
                     unsigned int *wlen)
{
  struct ipmi_sel_system_event_record_data system_event_record_data;
  const char *sensor_type_str = NULL;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_system_event_record (ctx, sel_entry, &system_event_record_data) < 0)
    return (-1);

  if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
    sensor_type_str = ipmi_get_oem_sensor_type_string (system_event_record_data.sensor_type,
                                                       (system_event_record_data.event_type_code & 0x7F),
                                                       ctx->manufacturer_id,
                                                       ctx->product_id);
  else
    sensor_type_str = ipmi_get_sensor_type_string (system_event_record_data.sensor_type);

  if (sensor_type_str)
    {
      if (sel_string_snprintf (buf, buflen, wlen, "%s", sensor_type_str))
        return (1);
    }

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_sensor_name (ipmi_sel_ctx_t ctx,
                     struct ipmi_sel_entry *sel_entry,
                     uint8_t sel_record_type,
                     char *buf,
                     unsigned int buflen,
                     unsigned int flags,
                     unsigned int *wlen,
                     struct sel_string_oem *sel_string_oem)
{
  struct ipmi_sel_system_event_record_data system_event_record_data;
  char sensor_name_buf[SENSOR_NAME_LENGTH + 1];
  int ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_system_event_record (ctx, sel_entry, &system_event_record_data) < 0)
    return (-1);

  memset (sensor_name_buf, '\0', SENSOR_NAME_LENGTH + 1);
  if ((ret = _get_sensor_name (ctx,
                               &system_event_record_data,
                               flags,
                               sensor_name_buf,
                               SENSOR_NAME_LENGTH)) < 0)
    return (-1);

  if (ret)
    {
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s",
                               sensor_name_buf))
        return (1);
      return (0);
    }
  /* else fall through */

  if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
    {
      int oem_rv = 0;

      if (sel_string_oem && sel_string_oem->output_sensor_name)
        {
          if ((ret = sel_string_oem->output_sensor_name (ctx,
                                                         sel_entry,
                                                         sel_record_type,
                                                         buf,
                                                         buflen,
                                                         flags,
                                                         wlen,
                                                         &system_event_record_data,
                                                         &oem_rv)) < 0)
            return (-1);

          if (ret)
            return (oem_rv);
        }
    }

  if (flags & IPMI_SEL_STRING_FLAGS_VERBOSE)
    {
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "Sensor #%d (Generator ID %02Xh)",
                               system_event_record_data.sensor_number,
                               system_event_record_data.generator_id))
        return (1);
    }
  else
    {
      if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "#%d",
                                   system_event_record_data.sensor_number))
            return (1);
        }
      else
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Sensor #%d",
                                   system_event_record_data.sensor_number))
            return (1);
        }
    }

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_data1 (ipmi_sel_ctx_t ctx,
                     struct ipmi_sel_entry *sel_entry,
                     uint8_t sel_record_type,
                     char *buf,
                     unsigned int buflen,
                     unsigned int flags,
                     unsigned int *wlen,
                     struct sel_string_oem *sel_string_oem)
{
  struct ipmi_sel_system_event_record_data system_event_record_data;
  char tmpbuf[EVENT_BUFFER_LENGTH + 1];
  int output_flag = 0;
  int ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_system_event_record (ctx, sel_entry, &system_event_record_data) < 0)
    return (-1);

  memset (tmpbuf, '\0', EVENT_BUFFER_LENGTH + 1);

  switch (ipmi_event_reading_type_code_class (system_event_record_data.event_type_code))
    {
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_THRESHOLD:
      /* Don't use ipmi_get_threshold_message, b/c we didn't call
       * get_sensor_reading.  Fall through to below.
       */
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_GENERIC_DISCRETE:
      ret = ipmi_get_generic_event_message_short (system_event_record_data.event_type_code,
                                                  system_event_record_data.offset_from_event_reading_type_code,
                                                  tmpbuf,
                                                  EVENT_BUFFER_LENGTH);
      if (ret > 0)
        output_flag++;
      break;
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_SENSOR_SPECIFIC_DISCRETE:

      if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
        {
          if (sel_string_oem && sel_string_oem->output_event_data1_class_sensor_specific_discrete)
            {
              if ((ret = sel_string_oem->output_event_data1_class_sensor_specific_discrete (ctx,
                                                                                            sel_entry,
                                                                                            sel_record_type,
                                                                                            tmpbuf,
                                                                                            EVENT_BUFFER_LENGTH,
                                                                                            flags,
                                                                                            wlen,
                                                                                            &system_event_record_data)) < 0)
                return (-1);

              if (ret)
                {
                  output_flag++;
                  break;
                }
            }
        }

      /*
       * From Dell Spec and Dell Code
       *
       * Dell Poweredge R610
       * Dell Poweredge R710
       *
       * achu: This is a special case, event reading type code and
       * sensor type are non-OEM, Dell added an additional offset not
       * defined by the IPMI spec.
       */
      if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA
          && ctx->manufacturer_id == IPMI_IANA_ENTERPRISE_ID_DELL
          && (ctx->product_id == IPMI_DELL_PRODUCT_ID_POWEREDGE_R610
              || ctx->product_id == IPMI_DELL_PRODUCT_ID_POWEREDGE_R710)
          && system_event_record_data.sensor_type == IPMI_SENSOR_TYPE_SYSTEM_FIRMWARE_PROGRESS
          && system_event_record_data.offset_from_event_reading_type_code == IPMI_SENSOR_TYPE_SYSTEM_FIRMWARE_PROGRESS_OEM_DELL_POST_FATAL_ERROR)
        {
          snprintf (tmpbuf,
                    EVENT_BUFFER_LENGTH,
                    "POST Fatal Error");
          output_flag++;
          break;
        }

      ret = ipmi_get_sensor_type_message_short (system_event_record_data.sensor_type,
                                                system_event_record_data.offset_from_event_reading_type_code,
                                                tmpbuf,
                                                EVENT_BUFFER_LENGTH);
      if (ret > 0)
        output_flag++;
      break;
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_OEM:

      if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
        {
          if (sel_string_oem && sel_string_oem->output_event_data1_class_oem)
            {
              if ((ret = sel_string_oem->output_event_data1_class_oem (ctx,
                                                                       sel_entry,
                                                                       sel_record_type,
                                                                       tmpbuf,
                                                                       EVENT_BUFFER_LENGTH,
                                                                       flags,
                                                                       wlen,
                                                                       &system_event_record_data)) < 0)
                return (-1);

              if (ret)
                {
                  output_flag++;
                  break;
                }
            }
        }

      if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
        snprintf (tmpbuf,
                  EVENT_BUFFER_LENGTH,
                  "Event Type Code = %02Xh",
                  system_event_record_data.event_type_code);
      else
        {
          if (flags & IPMI_SEL_STRING_FLAGS_VERBOSE)
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM Event Offset = %02Xh (Event Type Code = %02Xh)",
                      system_event_record_data.offset_from_event_reading_type_code,
                      system_event_record_data.event_type_code);
          else
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM Event Offset = %02Xh",
                      system_event_record_data.offset_from_event_reading_type_code);
        }
      output_flag++;
      break;
    default:
      /* fall through to output default output */
      break;
    }

  if (output_flag)
    {
      if (sel_string_snprintf (buf, buflen, wlen, "%s", tmpbuf))
        return (1);
    }
  else
    {
      if (flags & IPMI_SEL_STRING_FLAGS_VERBOSE)
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Event Offset = %02Xh (Event Type Code = %02Xh)",
                                   system_event_record_data.offset_from_event_reading_type_code,
                                   system_event_record_data.event_type_code))
            return (1);
        }
      else
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Event Offset = %02Xh",
                                   system_event_record_data.offset_from_event_reading_type_code))
            return (1);
        }
    }

  return (0);
}

static double
_round_double2 (double d)
{
  double r = 0.0;

  r = (d - (long) d) * 100.0;

  if ((r - (long) r) > 0.5)
    return ((long) d + (((long) r + 1) / 100.0));

  return ((long) d + ((long) r / 100.0));
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_data2 (ipmi_sel_ctx_t ctx,
                     struct ipmi_sel_entry *sel_entry,
                     uint8_t sel_record_type,
                     char *buf,
                     unsigned int buflen,
                     unsigned int flags,
                     unsigned int *wlen,
                     struct sel_string_oem *sel_string_oem)
{
  struct ipmi_sel_system_event_record_data system_event_record_data;
  char tmpbuf[EVENT_BUFFER_LENGTH + 1];
  int no_output_flag = 0;
  int output_flag = 0;
  int ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_system_event_record (ctx, sel_entry, &system_event_record_data) < 0)
    return (-1);

  memset (tmpbuf, '\0', EVENT_BUFFER_LENGTH + 1);

  switch (ipmi_event_reading_type_code_class (system_event_record_data.event_type_code))
    {
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_THRESHOLD:
      switch (system_event_record_data.event_data2_flag)
        {
        case IPMI_SEL_EVENT_DATA_TRIGGER_READING:
          {
            double reading;
            char sensor_units_buf[UNITS_BUFFER_LENGTH+1];

            memset (sensor_units_buf, '\0', UNITS_BUFFER_LENGTH+1);
            if ((ret = _get_sensor_reading (ctx,
                                            &system_event_record_data,
                                            flags,
                                            system_event_record_data.event_data2,
                                            &reading,
                                            sensor_units_buf,
                                            UNITS_BUFFER_LENGTH)) < 0)
              return (-1);

            if (ret)
              {
                if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
                  snprintf (tmpbuf,
                            EVENT_BUFFER_LENGTH,
                            "Reading = %.2f %s",
                            _round_double2 (reading),
                            sensor_units_buf);
                else
                  snprintf (tmpbuf,
                            EVENT_BUFFER_LENGTH,
                            "Sensor Reading = %.2f %s",
                            _round_double2 (reading),
                            sensor_units_buf);
              }
            else
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "Sensor Reading = %02Xh",
                        system_event_record_data.event_data2);
            output_flag++;
          }
          break;
        case IPMI_SEL_EVENT_DATA_SENSOR_SPECIFIC_EVENT_EXTENSION_CODE:

          if (system_event_record_data.event_data2 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
            {
              no_output_flag++;
              break;
            }

          ret = ipmi_get_event_data2_message (system_event_record_data.sensor_type,
                                              system_event_record_data.offset_from_event_reading_type_code,
                                              system_event_record_data.event_data2,
                                              tmpbuf,
                                              EVENT_BUFFER_LENGTH);
          if (ret > 0)
            output_flag++;
          break;
        case IPMI_SEL_EVENT_DATA_OEM_CODE:

          if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
            {
              if (sel_string_oem && sel_string_oem->output_event_data2_threshold_oem)
                {
                  if ((ret = sel_string_oem->output_event_data2_threshold_oem (ctx,
                                                                               sel_entry,
                                                                               sel_record_type,
                                                                               tmpbuf,
                                                                               EVENT_BUFFER_LENGTH,
                                                                               flags,
                                                                               wlen,
                                                                               &system_event_record_data)) < 0)
                    return (-1);

                  if (ret)
                    {
                      output_flag++;
                      break;
                    }
                }
            }

          if (system_event_record_data.event_data2 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
            {
              no_output_flag++;
              break;
            }

          if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM code = %02Xh",
                      system_event_record_data.event_data2);
          else
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM Event Data2 code = %02Xh",
                      system_event_record_data.event_data2);
          output_flag++;
          break;
        default:
          if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
            {
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "%s",
                        NA_STRING);
              output_flag++;
            }
          else
            /* nothing to output */
            return (0);
          break;
        }
      break;
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_GENERIC_DISCRETE:
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_SENSOR_SPECIFIC_DISCRETE:
      switch (system_event_record_data.event_data2_flag)
        {
        case IPMI_SEL_EVENT_DATA_PREVIOUS_STATE_OR_SEVERITY:
          {
            uint8_t previous_offset_from_event_reading_type_code;
            uint8_t offset_from_severity_event_reading_type_code;
            char tmppreviousbuf[EVENT_BUFFER_LENGTH];
            char tmpseveritybuf[EVENT_BUFFER_LENGTH];
            int previous_output_flag = 0;
            int severity_output_flag = 0;

            if (system_event_record_data.event_data2 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
              {
                no_output_flag++;
                break;
              }

            if (_get_previous_state_or_severity (ctx,
                                                 sel_entry,
                                                 &previous_offset_from_event_reading_type_code,
                                                 &offset_from_severity_event_reading_type_code) < 0)
              return (-1);

            if (previous_offset_from_event_reading_type_code != IPMI_SEL_RECORD_UNSPECIFIED_OFFSET)
              {
                ret = ipmi_get_event_data2_message (system_event_record_data.sensor_type,
                                                    system_event_record_data.offset_from_event_reading_type_code,
                                                    previous_offset_from_event_reading_type_code,
                                                    tmppreviousbuf,
                                                    EVENT_BUFFER_LENGTH);
                if (ret > 0)
                  {
                    snprintf (tmpbuf,
                              EVENT_BUFFER_LENGTH,
                              "Previous State = %s",
                              tmppreviousbuf);
                    previous_output_flag++;
                  }
              }
            if (offset_from_severity_event_reading_type_code != IPMI_SEL_RECORD_UNSPECIFIED_OFFSET)
              {
                ret = ipmi_get_generic_event_message_short (IPMI_EVENT_READING_TYPE_CODE_TRANSITION_SEVERITY,
                                                            offset_from_severity_event_reading_type_code,
                                                            tmpseveritybuf,
                                                            EVENT_BUFFER_LENGTH);
                if (ret > 0)
                  {
                    snprintf (tmpbuf,
                              EVENT_BUFFER_LENGTH,
                              "Severity State = %s",
                              tmpseveritybuf);
                    severity_output_flag++;
                  }
              }
            /* achu: special case, we need to combine the outputs into one */
            if (previous_output_flag && severity_output_flag)
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "Previous State = %s%sSeverity State = %s",
                        tmppreviousbuf,
                        ctx->separator ? ctx->separator : IPMI_SEL_SEPARATOR_STRING,
                        tmpseveritybuf);
            if (previous_output_flag || severity_output_flag)
              output_flag++;
          }
          break;
        case IPMI_SEL_EVENT_DATA_SENSOR_SPECIFIC_EVENT_EXTENSION_CODE:

          if (system_event_record_data.event_data2 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
            {
              no_output_flag++;
              break;
            }

          ret = ipmi_get_event_data2_message (system_event_record_data.sensor_type,
                                              system_event_record_data.offset_from_event_reading_type_code,
                                              system_event_record_data.event_data2,
                                              tmpbuf,
                                              EVENT_BUFFER_LENGTH);
          if (ret > 0)
            output_flag++;
          break;
        case IPMI_SEL_EVENT_DATA_OEM_CODE:

          if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
            {
              if (sel_string_oem && sel_string_oem->output_event_data2_discrete_oem)
                {
                  if ((ret = sel_string_oem->output_event_data2_discrete_oem (ctx,
                                                                              sel_entry,
                                                                              sel_record_type,
                                                                              tmpbuf,
                                                                              EVENT_BUFFER_LENGTH,
                                                                              flags,
                                                                              wlen,
                                                                              &system_event_record_data)) < 0)
                    return (-1);

                  if (ret)
                    {
                      output_flag++;
                      break;
                    }
                }
            }

          if (system_event_record_data.event_data2 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
            {
              no_output_flag++;
              break;
            }

          if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM code = %02Xh",
                      system_event_record_data.event_data2);
          else
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM Event Data2 code = %02Xh",
                      system_event_record_data.event_data2);
          output_flag++;
          break;
        default:
          if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
            {
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "%s",
                        NA_STRING);
              output_flag++;
            }
          else
            /* nothing to output */
            return (0);
          break;
        }
      break;
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_OEM:

      if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
        {
          if (sel_string_oem && sel_string_oem->output_event_data2_class_oem)
            {
              if ((ret = sel_string_oem->output_event_data2_class_oem (ctx,
                                                                       sel_entry,
                                                                       sel_record_type,
                                                                       tmpbuf,
                                                                       EVENT_BUFFER_LENGTH,
                                                                       flags,
                                                                       wlen,
                                                                       &system_event_record_data)) < 0)
                return (-1);

              if (ret)
                {
                  output_flag++;
                  break;
                }
            }
        }

      if (system_event_record_data.event_data2 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT
          || system_event_record_data.event_data2_flag == IPMI_SEL_EVENT_DATA_UNSPECIFIED_BYTE)
        {
          no_output_flag++;
          break;
        }

      if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
        snprintf (tmpbuf,
                  EVENT_BUFFER_LENGTH,
                  "Event data2 = %02Xh",
                  system_event_record_data.event_data2);
      else
        snprintf (tmpbuf,
                  EVENT_BUFFER_LENGTH,
                  "OEM Event Data2 code = %02Xh",
                  system_event_record_data.event_data2);
      output_flag++;
      break;
    default:
      /* fall through to output default output */
      break;
    }

  if (output_flag)
    {
      if (sel_string_snprintf (buf, buflen, wlen, "%s", tmpbuf))
        return (1);
    }
  else
    {
      if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
        return (0);

      if (no_output_flag)
        {
          if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
            {
              if (sel_string_snprintf (buf, buflen, wlen, "%s", NA_STRING))
                return (1);
            }
          return (0);
        }

      if (flags & IPMI_SEL_STRING_FLAGS_VERBOSE)
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Event Data2 = %02Xh (Event Type Code = %02Xh)",
                                   system_event_record_data.event_data2,
                                   system_event_record_data.event_type_code))
            return (1);
        }
      else
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Event Data2 = %02Xh",
                                   system_event_record_data.event_data2))
            return (1);
        }
    }

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_data3 (ipmi_sel_ctx_t ctx,
                     struct ipmi_sel_entry *sel_entry,
                     uint8_t sel_record_type,
                     char *buf,
                     unsigned int buflen,
                     unsigned int flags,
                     unsigned int *wlen,
                     struct sel_string_oem *sel_string_oem)
{
  struct ipmi_sel_system_event_record_data system_event_record_data;
  char tmpbuf[EVENT_BUFFER_LENGTH + 1];
  int no_output_flag = 0;
  int output_flag = 0;
  int ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_system_event_record (ctx, sel_entry, &system_event_record_data) < 0)
    return (-1);

  memset (tmpbuf, '\0', EVENT_BUFFER_LENGTH + 1);

  switch (ipmi_event_reading_type_code_class (system_event_record_data.event_type_code))
    {
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_THRESHOLD:
      switch (system_event_record_data.event_data3_flag)
        {
        case IPMI_SEL_EVENT_DATA_TRIGGER_THRESHOLD_VALUE:
          {
            double reading;
            char sensor_units_buf[UNITS_BUFFER_LENGTH+1];

            memset (sensor_units_buf, '\0', UNITS_BUFFER_LENGTH+1);
            if ((ret = _get_sensor_reading (ctx,
                                            &system_event_record_data,
                                            flags,
                                            system_event_record_data.event_data3,
                                            &reading,
                                            sensor_units_buf,
                                            UNITS_BUFFER_LENGTH)) < 0)
              return (-1);

            if (ret)
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "Threshold = %.2f %s",
                        _round_double2 (reading),
                        sensor_units_buf);
            else
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "Threshold = %02Xh",
                        system_event_record_data.event_data3);
            output_flag++;
          }
          break;
        case IPMI_SEL_EVENT_DATA_SENSOR_SPECIFIC_EVENT_EXTENSION_CODE:

          if (system_event_record_data.event_data3 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
            {
              no_output_flag++;
              break;
            }

          ret = ipmi_get_event_data3_message (system_event_record_data.sensor_type,
                                              system_event_record_data.offset_from_event_reading_type_code,
                                              system_event_record_data.event_data2,
                                              system_event_record_data.event_data3,
                                              tmpbuf,
                                              EVENT_BUFFER_LENGTH);
          if (ret > 0)
            output_flag++;
          break;
        case IPMI_SEL_EVENT_DATA_OEM_CODE:

          if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
            {
              if (sel_string_oem && sel_string_oem->output_event_data3_threshold_oem)
                {
                  if ((ret = sel_string_oem->output_event_data3_threshold_oem (ctx,
                                                                               sel_entry,
                                                                               sel_record_type,
                                                                               tmpbuf,
                                                                               EVENT_BUFFER_LENGTH,
                                                                               flags,
                                                                               wlen,
                                                                               &system_event_record_data)) < 0)
                    return (-1);

                  if (ret)
                    {
                      output_flag++;
                      break;
                    }
                }
            }

          if (system_event_record_data.event_data3 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
            {
              no_output_flag++;
              break;
            }

          if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM code = %02Xh",
                      system_event_record_data.event_data3);
          else
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM Event Data3 code = %02Xh",
                      system_event_record_data.event_data3);
          output_flag++;
          break;
        default:
          if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
            {
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "%s",
                        NA_STRING);
              output_flag++;
            }
          else
            /* nothing to output */
            return (0);
          break;
        }
      break;
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_GENERIC_DISCRETE:
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_SENSOR_SPECIFIC_DISCRETE:
      switch (system_event_record_data.event_data3_flag)
        {
        case IPMI_SEL_EVENT_DATA_SENSOR_SPECIFIC_EVENT_EXTENSION_CODE:
          if (system_event_record_data.event_data3 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
            {
              no_output_flag++;
              break;
            }
          ret = ipmi_get_event_data3_message (system_event_record_data.sensor_type,
                                              system_event_record_data.offset_from_event_reading_type_code,
                                              system_event_record_data.event_data2,
                                              system_event_record_data.event_data3,
                                              tmpbuf,
                                              EVENT_BUFFER_LENGTH);
          if (ret > 0)
            output_flag++;
          break;
        case IPMI_SEL_EVENT_DATA_OEM_CODE:

          if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
            {
              if (sel_string_oem && sel_string_oem->output_event_data3_discrete_oem)
                {
                  if ((ret = sel_string_oem->output_event_data3_discrete_oem (ctx,
                                                                              sel_entry,
                                                                              sel_record_type,
                                                                              tmpbuf,
                                                                              EVENT_BUFFER_LENGTH,
                                                                              flags,
                                                                              wlen,
                                                                              &system_event_record_data)) < 0)
                    return (-1);

                  if (ret)
                    {
                      output_flag++;
                      break;
                    }
                }
            }

          if (system_event_record_data.event_data3 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
            {
              no_output_flag++;
              break;
            }

          if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM code = %02Xh",
                      system_event_record_data.event_data3);
          else
            snprintf (tmpbuf,
                      EVENT_BUFFER_LENGTH,
                      "OEM Event Data3 code = %02Xh",
                      system_event_record_data.event_data3);
          output_flag++;
          break;
        default:
          if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
            {
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "%s",
                        NA_STRING);
              output_flag++;
            }
          else
            /* nothing to output */
            return (0);
          break;
        }
      break;
    case IPMI_EVENT_READING_TYPE_CODE_CLASS_OEM:

      if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
        {
          if (sel_string_oem && sel_string_oem->output_event_data3_class_oem)
            {
              if ((ret = sel_string_oem->output_event_data3_class_oem (ctx,
                                                                       sel_entry,
                                                                       sel_record_type,
                                                                       tmpbuf,
                                                                       EVENT_BUFFER_LENGTH,
                                                                       flags,
                                                                       wlen,
                                                                       &system_event_record_data)) < 0)
                return (-1);

              if (ret)
                {
                  output_flag++;
                  break;
                }
            }
        }

      if (system_event_record_data.event_data3 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT
          || system_event_record_data.event_data3_flag == IPMI_SEL_EVENT_DATA_UNSPECIFIED_BYTE)
        {
          no_output_flag++;
          break;
        }

      if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
        snprintf (tmpbuf,
                  EVENT_BUFFER_LENGTH,
                  "Event data3 = %02Xh",
                  system_event_record_data.event_data3);
      else
        snprintf (tmpbuf,
                  EVENT_BUFFER_LENGTH,
                  "OEM Event Data3 code = %02Xh",
                  system_event_record_data.event_data3);
      output_flag++;
      break;
    default:
      /* fall through to output default output */
      break;
    }

  if (output_flag)
    {
      if (sel_string_snprintf (buf, buflen, wlen, "%s", tmpbuf))
        return (1);
    }
  else
    {
      if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
        return (0);

      if (no_output_flag)
        {
          if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
            {
              if (sel_string_snprintf (buf, buflen, wlen, "%s", NA_STRING))
                return (1);
            }
          return (0);
        }

      if (flags & IPMI_SEL_STRING_FLAGS_VERBOSE)
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Event Data3 = %02Xh (Event Type Code = %02Xh)",
                                   system_event_record_data.event_data3,
                                   system_event_record_data.event_type_code))
            return (1);
        }
      else
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Event Data3 = %02Xh",
                                   system_event_record_data.event_data3))
            return (1);
        }
    }

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_data2_event_data3 (ipmi_sel_ctx_t ctx,
                                 struct ipmi_sel_entry *sel_entry,
                                 uint8_t sel_record_type,
                                 char *buf,
                                 unsigned int buflen,
                                 unsigned int flags,
                                 unsigned int *wlen,
                                 struct sel_string_oem *sel_string_oem)
{
  struct ipmi_sel_system_event_record_data system_event_record_data;
  char tmpbufdata2[EVENT_BUFFER_LENGTH + 1];
  char tmpbufdata3[EVENT_BUFFER_LENGTH + 1];
  unsigned int tmpbufdata2_wlen = 0;
  unsigned int tmpbufdata3_wlen = 0;
  int data2_ret;
  int data3_ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_system_event_record (ctx, sel_entry, &system_event_record_data) < 0)
    return (-1);

  memset (tmpbufdata2, '\0', EVENT_BUFFER_LENGTH+1);
  memset (tmpbufdata3, '\0', EVENT_BUFFER_LENGTH+1);

  /* Special case
   *
   * The event_data3 indicates what "type" event_data2 is, and
   * subsequently what should be output.  So we only need to output
   * event_data3.
   */
  if (ipmi_event_reading_type_code_class (system_event_record_data.event_type_code) == IPMI_EVENT_READING_TYPE_CODE_CLASS_SENSOR_SPECIFIC_DISCRETE
      && system_event_record_data.sensor_type == IPMI_SENSOR_TYPE_EVENT_LOGGING_DISABLED
      && system_event_record_data.offset_from_event_reading_type_code == IPMI_SENSOR_TYPE_EVENT_LOGGING_DISABLED_CORRECTABLE_MACHINE_CHECK_ERROR_LOGGING_DISABLED
      && system_event_record_data.event_data2_flag == IPMI_SEL_EVENT_DATA_SENSOR_SPECIFIC_EVENT_EXTENSION_CODE
      && system_event_record_data.event_data3_flag == IPMI_SEL_EVENT_DATA_SENSOR_SPECIFIC_EVENT_EXTENSION_CODE)
    {
      if ((data3_ret = _output_event_data3 (ctx,
                                            sel_entry,
                                            sel_record_type,
                                            tmpbufdata3,
                                            EVENT_BUFFER_LENGTH,
                                            flags,
                                            &tmpbufdata3_wlen,
                                            sel_string_oem)) < 0)
        return (-1);

      if (data3_ret)
        {
          SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
          return (-1);
        }

      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s",
                               tmpbufdata3))
        return (1);
      return (0);
    }

  if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
    {
      int ret;
      int oem_rv = 0;

      /* Linux kernel case is special, not vendor specific */
      if (system_event_record_data.generator_id == IPMI_SLAVE_ADDRESS_OEM_LINUX_KERNEL
          && system_event_record_data.event_type_code == IPMI_EVENT_READING_TYPE_CODE_SENSOR_SPECIFIC
          && system_event_record_data.sensor_type == IPMI_SENSOR_TYPE_OS_CRITICAL_STOP
          && system_event_record_data.event_data2_flag == IPMI_SEL_EVENT_DATA_OEM_CODE
          && system_event_record_data.event_data3_flag == IPMI_SEL_EVENT_DATA_OEM_CODE)
        {
          if ((ret = sel_string_output_linux_kernel_event_data2_event_data3 (ctx,
                                                                             sel_entry,
                                                                             sel_record_type,
                                                                             buf,
                                                                             buflen,
                                                                             flags,
                                                                             wlen,
                                                                             &system_event_record_data,
                                                                             &oem_rv)) < 0)
            return (-1);

          if (ret)
            return (oem_rv);
        }

      if (sel_string_oem && sel_string_oem->output_event_data2_event_data3)
        {
          if ((ret = sel_string_oem->output_event_data2_event_data3 (ctx,
                                                                     sel_entry,
                                                                     sel_record_type,
                                                                     buf,
                                                                     buflen,
                                                                     flags,
                                                                     wlen,
                                                                     &system_event_record_data,
                                                                     &oem_rv)) < 0)
            return (-1);

          if (ret)
            return (oem_rv);
        }
    }

  if ((data2_ret = _output_event_data2 (ctx,
                                        sel_entry,
                                        sel_record_type,
                                        tmpbufdata2,
                                        EVENT_BUFFER_LENGTH,
                                        flags,
                                        &tmpbufdata2_wlen,
                                        sel_string_oem)) < 0)
    return (-1);

  if ((data3_ret = _output_event_data3 (ctx,
                                        sel_entry,
                                        sel_record_type,
                                        tmpbufdata3,
                                        EVENT_BUFFER_LENGTH,
                                        flags,
                                        &tmpbufdata3_wlen,
                                        sel_string_oem)) < 0)
    return (-1);

  if (data2_ret || data3_ret)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
      return (-1);
    }

  if ((strlen (tmpbufdata2)
       && strcasecmp (tmpbufdata2, NA_STRING))
      && (strlen (tmpbufdata3)
          && strcasecmp (tmpbufdata3, NA_STRING)))
    {
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s%s%s",
                               tmpbufdata2,
                               ctx->separator ? ctx->separator : IPMI_SEL_SEPARATOR_STRING,
                               tmpbufdata3))
        return (1);
    }
  else if (strlen (tmpbufdata2)
           && strcasecmp (tmpbufdata2, NA_STRING))
    {
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s",
                               tmpbufdata2))
        return (1);
    }
  else if (strlen (tmpbufdata3)
           && strcasecmp (tmpbufdata3, NA_STRING))
    {
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s",
                               tmpbufdata3))
        return (1);
    }
  else if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
    {
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s",
                               NA_STRING))
        return (1);
    }

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_data2_previous_state_or_severity (ipmi_sel_ctx_t ctx,
                                                struct ipmi_sel_entry *sel_entry,
                                                uint8_t sel_record_type,
                                                char *buf,
                                                unsigned int buflen,
                                                unsigned int flags,
                                                unsigned int *wlen,
                                                int previous_state_flag)
{
  struct ipmi_sel_system_event_record_data system_event_record_data;
  uint8_t previous_offset_from_event_reading_type_code;
  uint8_t offset_from_severity_event_reading_type_code;
  char tmpbuf[EVENT_BUFFER_LENGTH + 1];
  char tmpstatebuf[EVENT_BUFFER_LENGTH];
  int no_output_flag = 0;
  int output_flag = 0;
  int ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_system_event_record (ctx, sel_entry, &system_event_record_data) < 0)
    return (-1);

  if (_get_previous_state_or_severity (ctx,
                                       sel_entry,
                                       &previous_offset_from_event_reading_type_code,
                                       &offset_from_severity_event_reading_type_code) < 0)
    return (-1);

  memset (tmpbuf, '\0', EVENT_BUFFER_LENGTH + 1);

  if ((ipmi_event_reading_type_code_class (system_event_record_data.event_type_code) == IPMI_EVENT_READING_TYPE_CODE_CLASS_GENERIC_DISCRETE
       || ipmi_event_reading_type_code_class (system_event_record_data.event_type_code) == IPMI_EVENT_READING_TYPE_CODE_CLASS_SENSOR_SPECIFIC_DISCRETE)
      && system_event_record_data.event_data2_flag == IPMI_SEL_EVENT_DATA_PREVIOUS_STATE_OR_SEVERITY
      && ((previous_state_flag && previous_offset_from_event_reading_type_code != IPMI_SEL_RECORD_UNSPECIFIED_OFFSET)
          || (!previous_state_flag && offset_from_severity_event_reading_type_code != IPMI_SEL_RECORD_UNSPECIFIED_OFFSET)))
    {
      if (system_event_record_data.event_data2 == IPMI_SEL_RECORD_UNSPECIFIED_EVENT)
        {
          no_output_flag++;
          goto out;
        }

      if (previous_state_flag)
        {
          ret = ipmi_get_event_data2_message (system_event_record_data.sensor_type,
                                              system_event_record_data.offset_from_event_reading_type_code,
                                              previous_offset_from_event_reading_type_code,
                                              tmpstatebuf,
                                              EVENT_BUFFER_LENGTH);
          if (ret > 0)
            {
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "Previous State = %s",
                        tmpstatebuf);
              output_flag++;
            }
        }
      else
        {
          ret = ipmi_get_generic_event_message_short (IPMI_EVENT_READING_TYPE_CODE_TRANSITION_SEVERITY,
                                                      offset_from_severity_event_reading_type_code,
                                                      tmpstatebuf,
                                                      EVENT_BUFFER_LENGTH);
          if (ret > 0)
            {
              snprintf (tmpbuf,
                        EVENT_BUFFER_LENGTH,
                        "Severity State = %s",
                        tmpstatebuf);
              output_flag++;
            }
        }
    }
  else
    {
      if (flags & IPMI_SEL_STRING_FLAGS_IGNORE_UNAVAILABLE_FIELD)
        {
          if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
            {
              if (sel_string_snprintf (buf, buflen, wlen, "%s", NA_STRING))
                return (1);
              return (0);
            }
          return (0);
        }
      ctx->errnum = IPMI_SEL_ERR_INVALID_SEL_ENTRY;
      return (-1);
    }

 out:
  if (output_flag)
    {
      if (sel_string_snprintf (buf, buflen, wlen, "%s", tmpbuf))
        return (1);
    }
  else
    {
      if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
        return (0);

      if (no_output_flag)
        {
          if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
            {
              if (sel_string_snprintf (buf, buflen, wlen, "%s", NA_STRING))
                return (1);
            }
          return (0);
        }

      if (flags & IPMI_SEL_STRING_FLAGS_VERBOSE)
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Event Data2 = %02Xh (Event Type Code = %02Xh)",
                                   system_event_record_data.event_data2,
                                   system_event_record_data.event_type_code))
            return (1);
        }
      else
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Event Data2 = %02Xh",
                                   system_event_record_data.event_data2))
            return (1);
        }
    }

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_data2_previous_state (ipmi_sel_ctx_t ctx,
                                    struct ipmi_sel_entry *sel_entry,
                                    uint8_t sel_record_type,
                                    char *buf,
                                    unsigned int buflen,
                                    unsigned int flags,
                                    unsigned int *wlen)
{
  return (_output_event_data2_previous_state_or_severity (ctx,
                                                          sel_entry,
                                                          sel_record_type,
                                                          buf,
                                                          buflen,
                                                          flags,
                                                          wlen,
                                                          1));
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_data2_severity (ipmi_sel_ctx_t ctx,
                              struct ipmi_sel_entry *sel_entry,
                              uint8_t sel_record_type,
                              char *buf,
                              unsigned int buflen,
                              unsigned int flags,
                              unsigned int *wlen)
{
  return (_output_event_data2_previous_state_or_severity (ctx,
                                                          sel_entry,
                                                          sel_record_type,
                                                          buf,
                                                          buflen,
                                                          flags,
                                                          wlen,
                                                          0));
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_data1_event_data2_event_data3 (ipmi_sel_ctx_t ctx,
                                             struct ipmi_sel_entry *sel_entry,
                                             uint8_t sel_record_type,
                                             char *buf,
                                             unsigned int buflen,
                                             unsigned int flags,
                                             unsigned int *wlen,
                                             struct sel_string_oem *sel_string_oem)
{
  struct ipmi_sel_system_event_record_data system_event_record_data;
  char tmpbufeventdata1[EVENT_BUFFER_LENGTH + 1];
  char tmpbufeventdata23[EVENT_BUFFER_LENGTH + 1];
  unsigned int tmpbufeventdata1_wlen = 0;
  unsigned int tmpbufeventdata23_wlen = 0;
  int data1_ret;
  int data23_ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_system_event_record (ctx, sel_entry, &system_event_record_data) < 0)
    return (-1);

  memset (tmpbufeventdata1, '\0', EVENT_BUFFER_LENGTH+1);
  memset (tmpbufeventdata23, '\0', EVENT_BUFFER_LENGTH+1);

  if ((data1_ret = _output_event_data1 (ctx,
                                        sel_entry,
                                        sel_record_type,
                                        tmpbufeventdata1,
                                        EVENT_BUFFER_LENGTH,
                                        flags,
                                        &tmpbufeventdata1_wlen,
                                        sel_string_oem)) < 0)
    return (-1);

  if ((data23_ret = _output_event_data2_event_data3 (ctx,
                                                     sel_entry,
                                                     sel_record_type,
                                                     tmpbufeventdata23,
                                                     EVENT_BUFFER_LENGTH,
                                                     flags,
                                                     &tmpbufeventdata23_wlen,
                                                     sel_string_oem)) < 0)
    return (-1);

  if (data1_ret || data23_ret)
    {
      SEL_SET_ERRNUM (ctx, IPMI_SEL_ERR_INTERNAL_ERROR);
      return (-1);
    }

  if ((strlen (tmpbufeventdata1)
       && strcasecmp (tmpbufeventdata1, NA_STRING))
      && (strlen (tmpbufeventdata23)
          && strcasecmp (tmpbufeventdata23, NA_STRING)))
    {
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s%s%s",
                               tmpbufeventdata1,
                               ctx->separator ? ctx->separator : IPMI_SEL_SEPARATOR_STRING,
                               tmpbufeventdata23))
        return (1);
    }
  else if (strlen (tmpbufeventdata1)
           && strcasecmp (tmpbufeventdata1, NA_STRING))
    {
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s",
                               tmpbufeventdata1))
        return (1);
    }
  else if (strlen (tmpbufeventdata23)
           && strcasecmp (tmpbufeventdata23, NA_STRING))
    {
      /* Having event_data1 as N/A and event data 2+3 not N/A is
       * probably impossible, but handle it anyway.
       */
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s",
                               tmpbufeventdata23))
        return (1);
    }
  else if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
    {
      if (sel_string_snprintf (buf,
                               buflen,
                               wlen,
                               "%s",
                               NA_STRING))
        return (1);
    }

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_event_direction (ipmi_sel_ctx_t ctx,
                         struct ipmi_sel_entry *sel_entry,
                         uint8_t sel_record_type,
                         char *buf,
                         unsigned int buflen,
                         unsigned int flags,
                         unsigned int *wlen)
{
  struct ipmi_sel_system_event_record_data system_event_record_data;
  char *str = NULL;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_system_event_record (ctx, sel_entry, &system_event_record_data) < 0)
    return (-1);

  if (system_event_record_data.event_direction)
    str = DEASSERTION_EVENT;
  else
    str = ASSERTION_EVENT;

  if (sel_string_snprintf (buf, buflen, wlen, "%s", str))
    return (1);

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_manufacturer_id (ipmi_sel_ctx_t ctx,
                         struct ipmi_sel_entry *sel_entry,
                         uint8_t sel_record_type,
                         char *buf,
                         unsigned int buflen,
                         unsigned int flags,
                         unsigned int *wlen)
{
  uint32_t manufacturer_id;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_TIMESTAMPED_OEM_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (sel_get_manufacturer_id (ctx, sel_entry, &manufacturer_id) < 0)
    return (-1);

  if (flags & IPMI_SEL_STRING_FLAGS_LEGACY)
    {
      if (sel_string_snprintf (buf, buflen, wlen, "Manufacturer ID = %02Xh", manufacturer_id))
        return (1);
    }
  else
    {
      char iana_buf[IANA_LENGTH + 1];
      int ret;

      memset (iana_buf, '\0', IANA_LENGTH + 1);

      /* if ret == 0 means no string, < 0 means bad manufacturer id
       * either way, output just the number
       */
      ret = ipmi_iana_enterprise_numbers_string (manufacturer_id,
                                                 iana_buf,
                                                 IANA_LENGTH);
      if (ret > 0)
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Manufacturer ID = %s (%02Xh)",
                                   iana_buf,
                                   manufacturer_id))
            return (1);

        }
      else
        {
          if (sel_string_snprintf (buf,
                                   buflen,
                                   wlen,
                                   "Manufacturer ID = %02Xh",
                                   manufacturer_id))
            return (1);
        }
    }

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_oem_record_data (ipmi_sel_ctx_t ctx,
                         struct ipmi_sel_entry *sel_entry,
                         uint8_t sel_record_type,
                         char *buf,
                         unsigned int buflen,
                         unsigned int flags,
                         unsigned int *wlen,
                         struct sel_string_oem *sel_string_oem)
{
  uint8_t oem_data[SEL_BUFFER_LENGTH];
  int oem_len;
  int oem_index;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_TIMESTAMPED_OEM_RECORD
      && ipmi_sel_record_type_class (sel_record_type) != IPMI_SEL_RECORD_TYPE_CLASS_NON_TIMESTAMPED_OEM_RECORD)
    return (_invalid_sel_entry_common (ctx, buf, buflen, flags, wlen));

  if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
    {
      int ret;
      int oem_rv = 0;

      /* Linux kernel case is special, not vendor specific
       *
       * achu: hoping no vendor violates this
       */
      if (sel_record_type == IPMI_SEL_RECORD_TYPE_NON_TIMESTAMPED_OEM_LINUX_KERNEL_PANIC)
        {
          if ((ret = sel_string_output_linux_kernel_oem_record_data (ctx,
                                                                     sel_entry,
                                                                     sel_record_type,
                                                                     buf,
                                                                     buflen,
                                                                     flags,
                                                                     wlen,
                                                                     &oem_rv)) < 0)
            return (-1);

          if (ret)
            return (oem_rv);
        }

      if (sel_string_oem && sel_string_oem->output_oem_record_data)
        {
          if ((ret = sel_string_oem->output_oem_record_data (ctx,
                                                             sel_entry,
                                                             sel_record_type,
                                                             buf,
                                                             buflen,
                                                             flags,
                                                             wlen,
                                                             &oem_rv)) < 0)
            return (-1);

          if (ret)
            return (oem_rv);
        }
    }

  if ((oem_len = sel_get_oem (ctx, sel_entry, oem_data, SEL_BUFFER_LENGTH)) < 0)
    return (-1);

  if (flags & IPMI_SEL_STRING_FLAGS_VERBOSE)
    {
      if (sel_string_snprintf (buf, buflen, wlen, "OEM defined (Record Type = %02Xh) = ", sel_record_type))
        return (1);
    }
  else
    {
      if (sel_string_snprintf (buf, buflen, wlen, "OEM defined = "))
        return (1);
    }

  for (oem_index = 0; oem_index < oem_len; oem_index++)
    {
      if (oem_index)
        {
          if (sel_string_snprintf (buf, buflen, wlen, " "))
            return (1);
        }
      if (sel_string_snprintf (buf, buflen, wlen, "%02Xh", oem_data[oem_index]))
        return (1);
    }

  return (0);
}

/* return (0) - continue on
 * return (1) - buffer full, return full buffer to user
 * return (-1) - error, cleanup and return error
 */
static int
_output_oem_string (ipmi_sel_ctx_t ctx,
                    struct ipmi_sel_entry *sel_entry,
                    uint8_t sel_record_type,
                    char *buf,
                    unsigned int buflen,
                    unsigned int flags,
                    unsigned int *wlen,
                    struct sel_string_oem *sel_string_oem)
{
  int ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (sel_entry);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));
  assert (wlen);

  if (sel_string_oem && sel_string_oem->output_oem_string)
    {
      int oem_rv = 0;

      if ((ret = sel_string_oem->output_oem_string (ctx,
                                                    sel_entry,
                                                    sel_record_type,
                                                    buf,
                                                    buflen,
                                                    flags,
                                                    wlen,
                                                    &oem_rv)) < 0)
        return (-1);

      if (ret)
        return (oem_rv);
    }

  if (flags & IPMI_SEL_STRING_FLAGS_OUTPUT_NOT_AVAILABLE)
    {
      if (sel_string_snprintf (buf, buflen, wlen, "%s", NA_STRING))
        return (1);
    }

  return (0);
}

int
sel_format_record_string (ipmi_sel_ctx_t ctx,
                          const char *fmt,
                          const void *sel_record,
                          unsigned int sel_record_len,
                          char *buf,
                          unsigned int buflen,
                          unsigned int flags)
{
  struct ipmi_sel_entry sel_entry;
  uint16_t record_id;
  uint8_t sel_record_type;
  int percent_flag = 0;
  unsigned int wlen = 0;
  struct sel_string_oem *sel_string_oem = NULL;
  int rv = -1;
  int ret;

  assert (ctx);
  assert (ctx->magic == IPMI_SEL_CTX_MAGIC);
  assert (fmt);
  assert (sel_record);
  assert (sel_record_len >= IPMI_SEL_RECORD_LENGTH);
  assert (buf);
  assert (buflen);
  assert (!(flags & ~IPMI_SEL_STRING_FLAGS_MASK));

  memcpy (sel_entry.sel_event_record, sel_record, IPMI_SEL_RECORD_LENGTH);
  sel_entry.sel_event_record_len = IPMI_SEL_RECORD_LENGTH;

  if (sel_get_record_header_info (ctx,
                                  &sel_entry,
                                  &record_id,
                                  &sel_record_type) < 0)
    goto cleanup;

  if (flags & IPMI_SEL_STRING_FLAGS_INTERPRET_OEM_DATA)
    {
      if (_find_motherboard_oem_support (ctx,
                                         flags,
                                         &sel_string_oem) < 0)
        goto cleanup;
    }

  while (*fmt)
    {
      if (*fmt == '%')
        {
          if (percent_flag)
            {
              if (sel_string_snprintf (buf, buflen, &wlen, "%%"))
                goto out;
              percent_flag = 0;
            }
          else
            percent_flag = 1;
          goto end_loop;
        }
      else if (percent_flag && *fmt == 'i') /* record id */
        {
          if (sel_string_snprintf (buf, buflen, &wlen, "%u", record_id))
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'I') /* event interpretation */
        {
          if ((ret = _output_event_interpretation (ctx,
                                                   &sel_entry,
                                                   sel_record_type,
                                                   buf,
                                                   buflen,
                                                   flags,
                                                   &wlen)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 't') /* time */
        {
          if ((ret = _output_time (ctx,
                                   &sel_entry,
                                   sel_record_type,
                                   buf,
                                   buflen,
                                   flags,
                                   &wlen)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'd') /* date */
        {
          if ((ret = _output_date (ctx,
                                   &sel_entry,
                                   sel_record_type,
                                   buf,
                                   buflen,
                                   flags,
                                   &wlen)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'T') /* sensor type */
        {
          if ((ret = _output_sensor_type (ctx,
                                          &sel_entry,
                                          sel_record_type,
                                          buf,
                                          buflen,
                                          flags,
                                          &wlen)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 's') /* sensor name */
        {
          if ((ret = _output_sensor_name (ctx,
                                          &sel_entry,
                                          sel_record_type,
                                          buf,
                                          buflen,
                                          flags,
                                          &wlen,
                                          sel_string_oem)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'e') /* event data1 */
        {
          if ((ret = _output_event_data1 (ctx,
                                          &sel_entry,
                                          sel_record_type,
                                          buf,
                                          buflen,
                                          flags,
                                          &wlen,
                                          sel_string_oem)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'f') /* event data2  */
        {
          if ((ret = _output_event_data2 (ctx,
                                          &sel_entry,
                                          sel_record_type,
                                          buf,
                                          buflen,
                                          flags,
                                          &wlen,
                                          sel_string_oem)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'h') /* event data3 */
        {
          if ((ret = _output_event_data3 (ctx,
                                          &sel_entry,
                                          sel_record_type,
                                          buf,
                                          buflen,
                                          flags,
                                          &wlen,
                                          sel_string_oem)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'c') /* combined event data 2 and event data 3 string */
        {
          if ((ret = _output_event_data2_event_data3 (ctx,
                                                      &sel_entry,
                                                      sel_record_type,
                                                      buf,
                                                      buflen,
                                                      flags,
                                                      &wlen,
                                                      sel_string_oem)) < 0)
            goto cleanup;
          if (ret)
            goto out;
        }
      else if (percent_flag && *fmt == 'p') /* event data2 previous state */
        {
          if ((ret = _output_event_data2_previous_state (ctx,
                                                         &sel_entry,
                                                         sel_record_type,
                                                         buf,
                                                         buflen,
                                                         flags,
                                                         &wlen)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'S') /* event data3 severity */
        {
          if ((ret = _output_event_data2_severity (ctx,
                                                   &sel_entry,
                                                   sel_record_type,
                                                   buf,
                                                   buflen,
                                                   flags,
                                                   &wlen)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'E') /* combined event data 1, 2, and 3 string */
        {
          if ((ret = _output_event_data1_event_data2_event_data3 (ctx,
                                                                  &sel_entry,
                                                                  sel_record_type,
                                                                  buf,
                                                                  buflen,
                                                                  flags,
                                                                  &wlen,
                                                                  sel_string_oem)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'k') /* event direction */
        {
          if ((ret = _output_event_direction (ctx,
                                              &sel_entry,
                                              sel_record_type,
                                              buf,
                                              buflen,
                                              flags,
                                              &wlen)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'm') /* manufacturer id */
        {
          if ((ret = _output_manufacturer_id (ctx,
                                              &sel_entry,
                                              sel_record_type,
                                              buf,
                                              buflen,
                                              flags,
                                              &wlen)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'o') /* oem data */
        {
          if ((ret = _output_oem_record_data (ctx,
                                              &sel_entry,
                                              sel_record_type,
                                              buf,
                                              buflen,
                                              flags,
                                              &wlen,
                                              sel_string_oem)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else if (percent_flag && *fmt == 'O') /* OEM string */
        {
          if ((ret = _output_oem_string (ctx,
                                         &sel_entry,
                                         sel_record_type,
                                         buf,
                                         buflen,
                                         flags,
                                         &wlen,
                                         sel_string_oem)) < 0)
            goto cleanup;
          if (ret)
            goto out;
          percent_flag = 0;
        }
      else
        {
          if (percent_flag)
            {
              if (sel_string_snprintf (buf, buflen, &wlen, "%%%c", *fmt))
                goto out;
              percent_flag = 0;
            }
          else
            {
              if (sel_string_snprintf (buf, buflen, &wlen, "%c", *fmt))
                goto out;
            }
        }

    end_loop:
      fmt++;
    }

 out:
  rv = wlen;
  ctx->errnum = IPMI_SEL_ERR_SUCCESS;
 cleanup:
  return (rv);
}
