Load IANA PEN registry from a file

Previously, the OEM names dictionary was compiled in and
updating it required rebuilding of `ipmitool`, thus taking a
long time for newly registered OEMs to get supported by the tool.

Building also required a direct internet connection to succeed.

With this commit, the OEM enterprise dictionary is now loaded from
either ${HOME}/.local/usr/share/misc/enterprise-numbers or from
/usr/share/misc/enterprise-numbers (in that precedence).

Those files can be downloaded from iana.org at
http://www.iana.org/assignments/enterprise-numbers

Partially resolves ipmitool/ipmitool#11

Fixes: 9d41136c9b7c7d392f1a3f3adeb6d7fe3bd3135e
Signed-off-by: Alexander Amelkin <alexander@amelkin.msk.ru>
This commit is contained in:
Alexander Amelkin 2019-06-11 19:33:35 +03:00 committed by Alexander Amelkin
parent b397a07e9e
commit bd0475ce4a
6 changed files with 360 additions and 98 deletions

1
.gitignore vendored
View File

@ -29,6 +29,5 @@ control/prototype
control/rpmmacros control/rpmmacros
src/ipmievd src/ipmievd
src/ipmitool src/ipmitool
lib/ipmi_pen_list.inc.c
cscope.out cscope.out
tags tags

View File

@ -53,8 +53,10 @@ extern const struct valstr ipmi_chassis_power_control_vals[];
extern const struct valstr ipmi_auth_algorithms[]; extern const struct valstr ipmi_auth_algorithms[];
extern const struct valstr ipmi_integrity_algorithms[]; extern const struct valstr ipmi_integrity_algorithms[];
extern const struct valstr ipmi_encryption_algorithms[]; extern const struct valstr ipmi_encryption_algorithms[];
extern const struct valstr ipmi_oem_info[];
extern const struct valstr ipmi_user_enable_status_vals[]; extern const struct valstr ipmi_user_enable_status_vals[];
extern const struct valstr *ipmi_oem_info;
int ipmi_oem_info_init();
void ipmi_oem_info_free();
extern const struct valstr picmg_frucontrol_vals[]; extern const struct valstr picmg_frucontrol_vals[];
extern const struct valstr picmg_clk_family_vals[]; extern const struct valstr picmg_clk_family_vals[];

View File

@ -31,7 +31,6 @@
AUTOMAKE_OPTIONS = subdir-objects AUTOMAKE_OPTIONS = subdir-objects
AM_CPPFLAGS = -I$(top_srcdir)/include AM_CPPFLAGS = -I$(top_srcdir)/include
MAINTAINERCLEANFILES = Makefile.in MAINTAINERCLEANFILES = Makefile.in
PEN_LIST = $(srcdir)/ipmi_pen_list.inc.c
noinst_LTLIBRARIES = libipmitool.la noinst_LTLIBRARIES = libipmitool.la
libipmitool_la_SOURCES = helper.c ipmi_sdr.c ipmi_sel.c ipmi_sol.c ipmi_pef.c \ libipmitool_la_SOURCES = helper.c ipmi_sdr.c ipmi_sel.c ipmi_sol.c ipmi_pef.c \
@ -49,8 +48,3 @@ libipmitool_la_LDFLAGS = -export-dynamic
libipmitool_la_LIBADD = -lm libipmitool_la_LIBADD = -lm
libipmitool_la_DEPENDENCIES = libipmitool_la_DEPENDENCIES =
$(PEN_LIST):
$(srcdir)/create_pen_list $(PEN_LIST)
ipmi_strings.lo: $(PEN_LIST)

View File

@ -1,77 +0,0 @@
#!/bin/bash
# vi: set ts=2 sw=2 et :
#
# IANA PEN List generator
#
# This script takes the official IANA PEN registry and generates
# a C language output for inclusion into ipmi_strings.c
#
# Copyright (c) 2018 Alexander Amelkin <alexander@amelkin.msk.ru>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
OUTFILE=$1
PENLIST_URL=https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
if [ -z "$OUTFILE" ]; then
echo $0: Must specify output file
exit
fi
parse_pen_list() {
iconv -f utf8 -t ascii//TRANSLIT//IGNORE \
| awk '
/^[0-9]+/ {
if(PEN) {
print "{ " PEN ", \"" ENTERPRISE "\" },"
}
PEN=$1
}
/^ [[:alnum:]]/ {
# Remove leading spaces
sub(/^[[:space:]]+/,"")
# Remove question marks (Chinese characters after iconv)
gsub(/\?/,"");
# Remove non-printable characters
gsub(/[^[:print:]]/,"");
# Escape slashes and double quotes
gsub(/["\\]/,"\\\\&")
ENTERPRISE=$0;
}
END {
if(PEN) {
print "{ " PEN ", \"" ENTERPRISE "\" },"
}
}'
}
echo "Generating IANA PEN list..."
curl -# "$PENLIST_URL" | parse_pen_list > "$OUTFILE"

View File

@ -844,6 +844,12 @@ ipmi_main(int argc, char ** argv,
/* setup log */ /* setup log */
log_init(progname, 0, verbose); log_init(progname, 0, verbose);
/* load the IANA PEN registry */
if (ipmi_oem_info_init()) {
lprintf(LOG_ERR, "Failed to initialize the OEM info dictionary");
goto out_free;
}
/* run OEM setup if found */ /* run OEM setup if found */
if (oemtype && if (oemtype &&
ipmi_oem_setup(ipmi_main_intf, oemtype) < 0) { ipmi_oem_setup(ipmi_main_intf, oemtype) < 0) {
@ -1063,6 +1069,8 @@ ipmi_main(int argc, char ** argv,
devfile = NULL; devfile = NULL;
} }
ipmi_oem_info_free();
return rc; return rc;
} }

View File

@ -30,29 +30,52 @@
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*/ */
#include <ctype.h>
#include <limits.h>
#include <stddef.h> #include <stddef.h>
#include <ipmitool/ipmi_strings.h> #include <ipmitool/ipmi_strings.h>
#include <ipmitool/ipmi_constants.h> #include <ipmitool/ipmi_constants.h>
#include <ipmitool/ipmi_sensor.h> #include <ipmitool/ipmi_sensor.h>
#include <ipmitool/ipmi_sel.h> /* for IPMI_OEM */ #include <ipmitool/ipmi_sel.h> /* for IPMI_OEM */
#include <ipmitool/log.h>
const struct valstr ipmi_oem_info[] = {
/* These are at the top so they are found first */
{ IPMI_OEM_UNKNOWN, "Unknown" },
{ IPMI_OEM_RESERVED, "Unspecified" },
/* The included file is auto-generated from official IANA PEN list */
#include "ipmi_pen_list.inc.c"
/* /*
* This debug ID is at the bottom so that if IANA assigns it to * These are put at the head so they are found first because they
* any entity, that IANA's value is found first and reported. * may overlap with IANA specified numbers found in the registry.
*/ */
{ IPMI_OEM_DEBUG, "A Debug Assisting Company, Ltd." }, static const struct valstr ipmi_oem_info_head[] = {
{ -1 , NULL }, { IPMI_OEM_UNKNOWN, "Unknown" }, /* IPMI Unknown */
{ IPMI_OEM_RESERVED, "Unspecified" }, /* IPMI Reserved */
{ UINT32_MAX , NULL },
}; };
/*
* These are our own technical values. We don't want them to take precedence
* over IANA's defined values, so they go at the very end of the array.
*/
static const struct valstr ipmi_oem_info_tail[] = {
{ IPMI_OEM_DEBUG, "A Debug Assisting Company, Ltd." },
{ UINT32_MAX , NULL },
};
/*
* This is used when ipmi_oem_info couldn't be allocated.
* ipmitool would report all OEMs as unknown, but would be functional otherwise.
*/
static const struct valstr ipmi_oem_info_dummy[] = {
{ UINT32_MAX , NULL },
};
/* This will point to an array filled from IANA's enterprise numbers registry */
const struct valstr *ipmi_oem_info;
/* Single-linked list of OEM valstrs */
typedef struct oem_valstr_list_s {
struct valstr valstr;
struct oem_valstr_list_s *next;
} oem_valstr_list_t;
const struct oemvalstr ipmi_oem_product_info[] = { const struct oemvalstr ipmi_oem_product_info[] = {
/* Keep OEM grouped together */ /* Keep OEM grouped together */
@ -754,3 +777,316 @@ const struct oemvalstr picmg_busres_shmc_status_vals[] = {
{ 0xffffff, 0x00, NULL } { 0xffffff, 0x00, NULL }
}; };
/**
* A helper function to count repetitions of the same byte
* at the beginning of a string.
*/
static
size_t count_bytes(const char *s, unsigned char c)
{
size_t count;
for (count = 0; s && s[0] == c; ++s, ++count);
return count;
}
/**
* Parse the IANA PEN registry file.
*
* See https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
* The expected entry format is:
*
* Decimal
* | Organization
* | | Contact
* | | | Email
* | | | |
* 0
* Reserved
* Internet Assigned Numbers Authority
* iana&iana.org
*
* That is, IANA PEN at position 0, enterprise name at position 2.
*/
#define IANA_NAME_OFFSET 2
#define IANA_PEN_REGISTRY "/usr/share/misc/enterprise-numbers"
static
int oem_info_list_load(oem_valstr_list_t **list)
{
FILE *in = NULL;
char *home;
oem_valstr_list_t *oemlist = *list;
int count = 0;
/*
* First try to open user's local registry if HOME is set
*/
if ((home = getenv("HOME"))) {
char path[PATH_MAX + 1] = { 0 };
strncpy(path, home, sizeof(path));
strncat(path, "/.local" IANA_PEN_REGISTRY, PATH_MAX);
in = fopen(path, "r");
}
if (!in) {
/*
* Now open the system default file
*/
in = fopen(IANA_PEN_REGISTRY, "r");
if (!in) {
lperror(LOG_ERR, "IANA PEN registry open failed");
return -1;
}
}
/*
* Read the registry file line by line, fill in the linked list,
* and count the entries. No sorting is done, entries will come in
* reverse registry order.
*/
while (!feof(in)) {
oem_valstr_list_t *item;
char *line = NULL;
char *endptr = NULL;
size_t len = 0;
long iana;
if (!getline(&line, &len, in)) {
/* Either an EOF or an empty line. Start over. */
continue;
}
/*
* Check if the line starts with a digit. If so, take it as IANA PEN.
* Any numbers not starting at position 0 are discarded.
*/
iana = strtol(line, &endptr, 10);
if (!isdigit(line[0]) || endptr == line) {
free_n(&line);
continue;
}
free_n(&line);
/*
* Now as we have the enterprise number, we're expecting the
* organization name to immediately follow.
*/
len = 0;
if (!getline(&line, &len, in)) {
/*
* Either an EOF or an empty line. Neither one can happen in
* a valid registry entry. Start over.
*/
continue;
}
if (len < IANA_NAME_OFFSET + 1
|| count_bytes(line, ' ') != IANA_NAME_OFFSET)
{
/*
* This is not a valid line, it doesn't start with
* a correct-sized indentation or is too short.
* Start over.
*/
free_n(&line);
continue;
}
/* Adjust to real line length, don't count the indentation */
len = strnlen(line + IANA_NAME_OFFSET, len - IANA_NAME_OFFSET);
/* Don't count the trailing newline */
if (line[IANA_NAME_OFFSET + len - 1] == '\n') {
--len;
}
item = malloc(sizeof(oem_valstr_list_t));
if (!item) {
lperror(LOG_ERR, "IANA PEN registry entry allocation failed");
free_n(&line);
/* Just stop reading, and process what has already been read */
break;
}
/*
* Looks like we have a valid entry, store it in the list.
*/
item->valstr.val = iana;
item->valstr.str = malloc(len + 1); /* Add 1 for \0 terminator */
if (!item->valstr.str) {
lperror(LOG_ERR, "IANA PEN registry string allocation failed");
free_n(&line);
free_n(&item);
/* Just stop reading, and process what has already been read */
break;
}
strncpy((void *)item->valstr.str, line + IANA_NAME_OFFSET, len);
((char *)(item->valstr.str))[len] = 0;
free_n(&line);
item->next = oemlist;
oemlist = item;
++count;
}
fclose (in);
*list = oemlist;
return count;
}
/**
* Free the allocated list items and, if needed, strings.
*/
static
void
oem_info_list_free(oem_valstr_list_t **list, bool free_strings)
{
while ((*list)->next) {
oem_valstr_list_t *item = *list;
*list = item->next;
if (free_strings) {
free_n(&item->valstr.str);
}
free_n(&item);
}
}
/**
* Initialize the ipmi_oem_info array from a list
*/
static
bool
oem_info_init_from_list(oem_valstr_list_t *oemlist, size_t count)
{
/* Do not count terminators */
size_t head_entries = ARRAY_SIZE(ipmi_oem_info_head) - 1;
size_t tail_entries = ARRAY_SIZE(ipmi_oem_info_tail) - 1;
static oem_valstr_list_t *item;
bool rc = false;
/* Include static entries and the terminator */
count += head_entries + tail_entries + 1;
/*
* Allocate as much memory as needed to accomodata all the entries
* of the loaded linked list, plus the static head and tail, not including
* their terminating entries, plus the terminating entry for the new
* array.
*/
ipmi_oem_info = malloc(count * sizeof(*ipmi_oem_info));
if (!ipmi_oem_info) {
/*
* We can't identify OEMs without an allocated ipmi_oem_info.
* Report an error, set the pointer to dummy and clean up.
*/
lperror(LOG_ERR, "IANA PEN registry array allocation failed");
ipmi_oem_info = ipmi_oem_info_dummy;
goto out;
}
lprintf(LOG_DEBUG + 3, " Allocating %6zu entries", count);
/* Add a terminator at the very end */
--count;
((struct valstr *)ipmi_oem_info)[count].val = -1;
((struct valstr *)ipmi_oem_info)[count].str = NULL;
/* Add tail entries from the end */
while (count-- < SIZE_MAX && tail_entries--) {
((struct valstr *)ipmi_oem_info)[count] =
ipmi_oem_info_tail[tail_entries];
lprintf(LOG_DEBUG + 3, " [%6zu] %8d | %s", count,
ipmi_oem_info[count].val, ipmi_oem_info[count].str);
}
/* Now add the loaded entries */
item = oemlist;
while (count < SIZE_MAX && item->next) {
((struct valstr *)ipmi_oem_info)[count] =
item->valstr;
lprintf(LOG_DEBUG + 3, " [%6zu] %8d | %s", count,
ipmi_oem_info[count].val, ipmi_oem_info[count].str);
item = item->next;
--count;
}
/* Now add head entries */
while (count < SIZE_MAX && head_entries--) {
((struct valstr *)ipmi_oem_info)[count] =
ipmi_oem_info_head[head_entries];
lprintf(LOG_DEBUG + 3, " [%6zu] %8d | %s", count,
ipmi_oem_info[count].val, ipmi_oem_info[count].str);
--count;
}
rc = true;
out:
return rc;
}
int ipmi_oem_info_init()
{
oem_valstr_list_t terminator = { { -1, NULL}, NULL }; /* Terminator */
oem_valstr_list_t *oemlist = &terminator;
bool free_strings = true;
size_t count;
int rc = -4;
lprintf(LOG_INFO, "Loading IANA PEN Registry...");
if (ipmi_oem_info) {
lprintf(LOG_INFO, "IANA PEN Registry is already loaded");
rc = 0;
goto out;
}
if (!(count = oem_info_list_load(&oemlist))) {
/*
* We can't identify OEMs without a loaded registry.
* Set the pointer to dummy and return.
*/
ipmi_oem_info = ipmi_oem_info_dummy;
goto out;
}
/* In the array was allocated, don't free the strings at cleanup */
free_strings = !oem_info_init_from_list(oemlist, count);
rc = IPMI_CC_OK;
out:
oem_info_list_free(&oemlist, free_strings);
return rc;
}
void ipmi_oem_info_free()
{
/* Start with the dynamically allocated entries */
size_t i = ARRAY_SIZE(ipmi_oem_info_head) - 1;
if (ipmi_oem_info == ipmi_oem_info_dummy) {
return;
}
/*
* Proceed dynamically allocated entries until we hit the first
* entry of ipmi_oem_info_tail[], which is statically allocated.
*/
while (ipmi_oem_info
&& ipmi_oem_info[i].val < UINT32_MAX
&& ipmi_oem_info[i].str != ipmi_oem_info_tail[0].str)
{
free_n(&((struct valstr *)ipmi_oem_info)[i].str);
++i;
}
/* Free the array itself */
free_n(&ipmi_oem_info);
}