patchelfcrc/src/xml.c

486 lines
13 KiB
C

#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xmlIO.h>
#include <libxml/xinclude.h>
#include <libxml/tree.h>
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>
#include <libxml/xmlreader.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include <patchelfcrc/reporting.h>
#include <patchelfcrc/xml.h>
#include <patchelfcrc/version.h>
#include <generated/schema-blob.h>
void xml_init(void)
{
LIBXML_TEST_VERSION;
}
int xml_write_crcs_to_file(const char *path, const struct crc_import_data *crc_data)
{
int ret = 0;
xmlTextWriter *writer;
SlList *entry_iter;
const struct crc_entry *entry;
size_t index;
if (!path || !crc_data) {
return -1000;
}
writer = xmlNewTextWriterFilename(path, 0);
if (!writer) {
print_err("Cannot create XML file %s\n", path);
ret = -1;
goto ret_none;
}
xmlTextWriterSetIndentString(writer, BAD_CAST "\t");
xmlTextWriterSetIndent(writer, 1);
xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL);
/* Generate the root node */
xmlTextWriterStartElement(writer, BAD_CAST "patchelfcrc");
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "version", "%s", version_string);
xmlTextWriterStartElement(writer, BAD_CAST "settings");
xmlTextWriterWriteFormatElement(writer, BAD_CAST "poly", "0x%" PRIx64, crc_data->crc_config.polynomial);
xmlTextWriterWriteFormatElement(writer, BAD_CAST "start", "0x%" PRIx32, crc_data->crc_config.start_value);
if (crc_data->crc_config.rev) {
xmlTextWriterStartElement(writer, BAD_CAST "rev");
xmlTextWriterEndElement(writer);
}
xmlTextWriterWriteFormatElement(writer, BAD_CAST "xor", "0x%" PRIx32, crc_data->crc_config.xor);
if (crc_data->elf_bits < 0) {
print_err("Cannot determine ELF class. Generated XML will be faulty.\n");
ret |= -1;
}
xmlTextWriterWriteFormatElement(writer, BAD_CAST "elfclass", "%d", crc_data->elf_bits);
xmlTextWriterEndElement(writer); /* End settings */
xmlTextWriterStartElement(writer, BAD_CAST "sections");
/* Output all section CRCs */
for (entry_iter = crc_data->crc_entries, index = 0u; entry_iter; entry_iter = sl_list_next(entry_iter), index++) {
entry = (const struct crc_entry *)entry_iter->data;
xmlTextWriterStartElement(writer, BAD_CAST "crc");
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "name", "%s", entry->name);
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "index", "%zu", index);
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "vma", "0x%" PRIx64, entry->vma);
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "lma", "0x%" PRIx64, entry->lma);
xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "size", "0x%" PRIx64, entry->size);
xmlTextWriterWriteFormatRaw(writer, "0x%" PRIx32, entry->crc);
xmlTextWriterEndElement(writer); /* End crc */
}
xmlTextWriterEndElement(writer); /* End sections */
xmlTextWriterEndElement(writer); /* End root node */
xmlTextWriterEndDocument(writer);
xmlFreeTextWriter(writer);
ret_none:
return ret;
}
struct crc_import_data *xml_crc_import_alloc(void)
{
struct crc_import_data *ret = NULL;
ret = (struct crc_import_data *)malloc(sizeof(struct crc_import_data));
if (ret)
ret->crc_entries = NULL;
else
print_err("Error. Out of memory. This should never happen\n");
return ret;
}
static bool validate_xml_doc(xmlDocPtr doc)
{
bool ret = false;
xmlSchemaParserCtxtPtr parser_ctx = NULL;
xmlSchemaPtr schema = NULL;
xmlSchemaValidCtxtPtr validation_ctx = NULL;
int res;
parser_ctx = xmlSchemaNewMemParserCtxt((const char *)schema_xsd, schema_xsd_len);
if (!parser_ctx) {
print_err("Cannot create parse context for built-in XSD. This is a bug. Report this.\n");
goto ret_none;
}
schema = xmlSchemaParse(parser_ctx);
if (!schema) {
print_err("Cannot parse built-in XSD. This is a bug. Report this.\n");
goto ret_none;
}
validation_ctx = xmlSchemaNewValidCtxt(schema);
if (!validation_ctx) {
print_err("Cannot create validation context. This is a bug. Report this.\n");
goto ret_none;
}
res = xmlSchemaValidateDoc(validation_ctx, doc);
ret = (res == 0 ? true : false);
ret_none:
/* Clean up */
if (validation_ctx)
xmlSchemaFreeValidCtxt(validation_ctx);
if (schema)
xmlSchemaFree(schema);
if (parser_ctx)
xmlSchemaFreeParserCtxt(parser_ctx);
return ret;
}
/**
* @brief Get the content of a node specified by the xpath \p path
* @param path Xpath to search for
* @param xpath_ctx Context
* @param required Print error if not found
* @return NULL in case of error
* @return pointer to newly allocated string data.
* @note Pointers returned from this function must be freed using xmlFree()
*/
static const char *get_node_content_from_xpath(const char *path, xmlXPathContextPtr xpath_ctx, bool required)
{
xmlXPathObjectPtr xpath_obj;
const char *ret = NULL;
xpath_obj = xmlXPathEvalExpression(BAD_CAST path, xpath_ctx);
if (xpath_obj) {
if (xmlXPathNodeSetIsEmpty(xpath_obj->nodesetval)) {
if (required)
print_err("Required XML path %s not found.\n", path);
} else {
ret = (const char *)xmlNodeGetContent(xpath_obj->nodesetval->nodeTab[0]);
}
xmlXPathFreeObject(xpath_obj);
} else {
/* Error */
print_err("Error searching for path %s in XML. This is an error. Report this.\n", path);
}
return ret;
}
/**
* @brief Convert a number string (either prefixed 0x hex or decimal) to a uint64
*
* In case of an error, the \p output remains untouched
*
* @param[in] data input data. 0 terminated
* @param[in] output Converted number.
* @return 0 if okay
* @return negative in case of error
*/
static int convert_number_string_to_uint(const char *data, uint64_t *output)
{
int ret = -1;
uint64_t num;
char *endptr;
if (!data || !output)
return -1000;
errno = 0;
num = strtoull(data, &endptr, 0);
if (endptr == data) {
/* Error finding number */
print_err("Data %s in XML is not a valid number\n", data);
} else if (errno == ERANGE) {
print_err("Data %s in XML overflowed\n", data);
} else if (errno == EINVAL) {
print_err("Unspecified error converting '%s' to a number\n", data);
} else if (errno == 0 && data && *endptr != '\0') {
print_err("Data '%s' could not be fully parsed to a number. Part '%s' is irritating\n", data, endptr);
} else if (errno == 0 && data && *endptr == '\0') {
ret = 0;
*output = num;
}
return ret;
}
/**
* @brief Get the content of an xpath and convert it to a uint64_t
* @param[in] xpath Path to get content from
* @param[in] xpath_ctx Xpath context
* @param[out] output Number output. Remains untouched in case of an error
* @param required This xpath is required. Will turn on error reporting if it is not found.
* @return 0 if successful
* @return negative in case of an error
*/
static int get_uint64_from_xpath_content(const char *xpath, xmlXPathContextPtr xpath_ctx, uint64_t *output, bool required)
{
const char *data;
int ret = -1;
data = get_node_content_from_xpath(xpath, xpath_ctx, required);
if (data) {
ret = convert_number_string_to_uint(data, output);
xmlFree((void *)data);
}
return ret;
}
/**
* @brief Get the content of an xpath and convert it to a uint64_t
* @param[in] xpath Path to get content from
* @param[in] xpath_ctx Xpath context
* @param[out] output Number output. Remains untouched in case of an error
* @param required This xpath is required. Will turn on error reporting if it is not found.
* @return 0 if successful
* @return negative in case of an error
*/
static int get_uint32_from_xpath_content(const char *xpath, xmlXPathContextPtr xpath_ctx, uint32_t *output, bool required)
{
const char *data;
uint64_t tmp;
int ret = -1;
data = get_node_content_from_xpath(xpath, xpath_ctx, required);
if (data) {
ret = convert_number_string_to_uint(data, &tmp);
xmlFree((void *)data);
if (ret == 0) {
if (tmp > UINT32_MAX) {
ret = -2;
print_err("Value in XML file at path '%s' is too large for uint32_t\n", xpath);
} else {
*output = (uint32_t)tmp;
}
}
}
return ret;
}
int get_uint64_from_node_attribute(xmlNodePtr node, const char *attr, uint64_t *output)
{
xmlChar *data;
uint64_t num;
int ret = -1;
data = xmlGetProp(node, BAD_CAST attr);
if (data) {
if (!convert_number_string_to_uint((const char *)data, &num)) {
ret = 0;
*output = num;
}
xmlFree(data);
}
return ret;
}
static int get_uint32_from_node_attribute(xmlNodePtr node, const char *attr, uint32_t *output)
{
int ret;
uint64_t tmp = 0;
ret = get_uint64_from_node_attribute(node, attr, &tmp);
if (tmp > UINT32_MAX || ret) {
print_err("Cannot convert attribute %s to 32 bit number\n", attr);
ret = -1;
} else {
*output = (uint32_t)tmp;
}
return ret;
}
static int get_uint64_from_node_content(xmlNodePtr node, uint64_t *output)
{
xmlChar *data;
int ret = -1;
data = xmlNodeGetContent(node);
if (data) {
ret = convert_number_string_to_uint((const char *)data, output);
xmlFree(data);
}
return ret;
}
static int get_uint32_from_node_content(xmlNodePtr node, uint32_t *output)
{
int ret;
uint64_t tmp = 0;
ret = get_uint64_from_node_content(node, &tmp);
if (tmp > UINT32_MAX || ret) {
print_err("Cannot convert content to 32 bit number\n");
ret = -1;
} else {
*output = (uint32_t)tmp;
}
return ret;
}
struct crc_import_data *xml_import_from_file(const char *path)
{
struct crc_import_data *ret = NULL;
struct crc_entry *crc;
xmlDocPtr doc;
xmlNodePtr root_node;
xmlNodePtr current_node;
xmlXPathContextPtr xpath_ctx = NULL;
xmlXPathObjectPtr xpath_obj = NULL;
uint64_t tmp_num64 = 0;
uint32_t tmp_num32 = 0;
int i;
const char *cptr;
if (!path)
return NULL;
doc = xmlReadFile(path, NULL, 0);
if (!doc) {
print_err("Error reading XML file: %s\n", path);
goto ret_none;
}
root_node = xmlDocGetRootElement(doc);
if (!root_node) {
goto ret_close_doc;
}
/* Validate the document */
if (!validate_xml_doc(doc)) {
print_err("XML does not match expected format. Cannot import.\n");
goto ret_close_doc;
}
/* Get xpath context */
xpath_ctx = xmlXPathNewContext(doc);
if (!xpath_ctx) {
goto ret_close_doc;
}
/* Get the version number and print error in case of incompatibility. Continue either way */
cptr = (char *)xmlGetProp(root_node, BAD_CAST "version");
if (cptr) {
if (strncmp(cptr, version_string, strlen(version_string)) != 0) {
print_err("XML file was generated with another version of patchelfcrc.\n");
print_err("\t XML shows: %s\n", cptr);
print_err("\t Program version: %s\n", version_string);
}
xmlFree((char *)cptr);
}
/* Allocate xml import structure */
ret = xml_crc_import_alloc();
if (!ret)
goto ret_close_doc;
/* Do not do extensive error handling. It is assured by the schema that the numbers are parsable */
(void)get_uint64_from_xpath_content("/patchelfcrc/settings/poly", xpath_ctx, &tmp_num64, true);
ret->crc_config.polynomial = tmp_num64;
(void)get_uint32_from_xpath_content("/patchelfcrc/settings/start", xpath_ctx, &tmp_num32, true);
ret->crc_config.start_value = tmp_num32;
(void)get_uint32_from_xpath_content("/patchelfcrc/settings/xor", xpath_ctx, &tmp_num32, true);
ret->crc_config.xor = tmp_num32;
cptr = get_node_content_from_xpath("/patchelfcrc/settings/rev", xpath_ctx, false);
if (cptr) {
xmlFree((void *)cptr);
ret->crc_config.rev = true;
} else {
ret->crc_config.rev = false;
}
(void)get_uint32_from_xpath_content("/patchelfcrc/settings/elfclass", xpath_ctx, &tmp_num32, true);
ret->elf_bits = (int)tmp_num32;
/* Get all CRCs */
xpath_obj = xmlXPathEvalExpression(BAD_CAST "/patchelfcrc/sections/crc", xpath_ctx);
if (xmlXPathNodeSetIsEmpty(xpath_obj->nodesetval)) {
print_err("Internal error during read\n");
xml_crc_import_free(ret);
ret = NULL;
goto ret_close_doc;
}
for (i = 0; i < xpath_obj->nodesetval->nodeNr; i++) {
current_node = xpath_obj->nodesetval->nodeTab[i];
crc = (struct crc_entry *)malloc(sizeof(struct crc_entry));
ret->crc_entries = sl_list_append(ret->crc_entries, crc);
get_uint64_from_node_attribute(current_node, "vma", &tmp_num64);
crc->vma = tmp_num64;
get_uint64_from_node_attribute(current_node, "size", &tmp_num64);
crc->size = tmp_num64;
get_uint64_from_node_attribute(current_node, "lma", &tmp_num64);
crc->lma = tmp_num64;
get_uint32_from_node_content(current_node, &tmp_num32);
crc->crc = tmp_num32;
crc->name = (char *)xmlGetProp(current_node, BAD_CAST "name");
}
ret_close_doc:
if (xpath_obj)
xmlXPathFreeObject(xpath_obj);
if (xpath_ctx)
xmlXPathFreeContext(xpath_ctx);
/* Free document and all of its children */
xmlFreeDoc(doc);
/* Cleanup global garbage */
xmlCleanupParser();
ret_none:
return ret;
}
static void free_crc_entry(void *entry)
{
struct crc_entry *e = (struct crc_entry *)entry;
if (entry) {
if (e->name)
xmlFree(e->name);
free(entry);
}
}
void xml_crc_import_free(struct crc_import_data *data)
{
if (!data)
return;
sl_list_free_full(data->crc_entries, free_crc_entry);
data->crc_entries = NULL;
free(data);
}
void xml_print_xsd(void)
{
printf("%.*s", schema_xsd_len, schema_xsd);
}