diff options
Diffstat (limited to 'lib/cpluff/libcpluff/ploader.c')
-rw-r--r-- | lib/cpluff/libcpluff/ploader.c | 1186 |
1 files changed, 1186 insertions, 0 deletions
diff --git a/lib/cpluff/libcpluff/ploader.c b/lib/cpluff/libcpluff/ploader.c new file mode 100644 index 0000000000..648c2bb326 --- /dev/null +++ b/lib/cpluff/libcpluff/ploader.c @@ -0,0 +1,1186 @@ +/*------------------------------------------------------------------------- + * C-Pluff, a plug-in framework for C + * Copyright 2007 Johannes Lehtinen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + *-----------------------------------------------------------------------*/ + +/** @file + * Plug-in descriptor loader + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdarg.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <expat.h> +#include "cpluff.h" +#include "defines.h" +#include "util.h" +#include "internal.h" + +// Use XMLCALL if available +#ifdef XMLCALL +#define CP_XMLCALL XMLCALL +#else +#define CP_XMLCALL +#endif + + +/* ------------------------------------------------------------------------ + * Constants + * ----------------------------------------------------------------------*/ + +/// XML parser buffer size (in bytes) +#define CP_XML_PARSER_BUFFER_SIZE 4096 + +/// Initial configuration element value size +#define CP_CFG_ELEMENT_VALUE_INITSIZE 64 + +/// Plugin descriptor name +#define CP_PLUGIN_DESCRIPTOR "plugin.xml" + + +/* ------------------------------------------------------------------------ + * Internal data types + * ----------------------------------------------------------------------*/ + +typedef struct ploader_context_t ploader_context_t; + +/// Parser states +typedef enum parser_state_t { + PARSER_BEGIN, + PARSER_PLUGIN, + PARSER_REQUIRES, + PARSER_EXTENSION, + PARSER_END, + PARSER_UNKNOWN, + PARSER_ERROR +} parser_state_t; + +/// Plug-in loader context +struct ploader_context_t { + + /// The plug-in context, or NULL if none + cp_context_t *context; + + /// The XML parser being used + XML_Parser parser; + + /// The file being parsed + char *file; + + /// The plug-in being constructed + cp_plugin_info_t *plugin; + + /// The configuration element being constructed + cp_cfg_element_t *configuration; + + /// The current parser state + parser_state_t state; + + /// The saved parser state (used in PARSER_UNKNOWN) + parser_state_t saved_state; + + /** + * The current parser depth (used in PARSER_UNKNOWN and PARSER_EXTENSION) + */ + unsigned int depth; + + /// The number of skipped configuration elements + unsigned int skippedCEs; + + /// Size of allocated imports table + size_t imports_size; + + /// Size of allocated extension points table + size_t ext_points_size; + + /// Size of allocated extensions table + size_t extensions_size; + + /// Buffer for a value being read + char *value; + + /// Size of allocated value field + size_t value_size; + + /// Current length of value string + size_t value_length; + + /// The number of parsing errors that have occurred + unsigned int error_count; + + /// The number of resource errors that have occurred + unsigned int resource_error_count; +}; + + +/* ------------------------------------------------------------------------ + * Function definitions + * ----------------------------------------------------------------------*/ + +/** + * Reports a descriptor error. Does not set the parser to error state but + * increments the error count, unless this is merely a warning. + * + * @param context the parsing context + * @param warn whether this is only a warning + * @param error_msg the error message + * @param ... parameters for the error message + */ +static void descriptor_errorf(ploader_context_t *plcontext, int warn, + const char *error_msg, ...) { + va_list ap; + char message[128]; + + va_start(ap, error_msg); + vsnprintf(message, sizeof(message), error_msg, ap); + va_end(ap); + message[127] = '\0'; + if (warn) { + cpi_warnf(plcontext->context, + N_("Suspicious plug-in descriptor content in %s, line %d, column %d (%s)."), + plcontext->file, + XML_GetCurrentLineNumber(plcontext->parser), + XML_GetCurrentColumnNumber(plcontext->parser) + 1, + message); + } else { + cpi_errorf(plcontext->context, + N_("Invalid plug-in descriptor content in %s, line %d, column %d (%s)."), + plcontext->file, + XML_GetCurrentLineNumber(plcontext->parser), + XML_GetCurrentColumnNumber(plcontext->parser) + 1, + message); + } + if (!warn) { + plcontext->error_count++; + } +} + +/** + * Reports insufficient system resources while parsing and increments the + * resource error count. + * + * @param context the parsing context + */ +static void resource_error(ploader_context_t *plcontext) { + if (plcontext->resource_error_count == 0) { + cpi_errorf(plcontext->context, + N_("Insufficient system resources to parse plug-in descriptor content in %s, line %d, column %d."), + plcontext->file, + XML_GetCurrentLineNumber(plcontext->parser), + XML_GetCurrentColumnNumber(plcontext->parser) + 1); + } + plcontext->resource_error_count++; +} + +/** + * Returns whether the specified NULL-terminated list of strings includes + * the specified string. + * + * @param list the NULL-terminated list of strings, or NULL if none + * @param str the string + * @param step the stepping (1 to check every string or 2 to check every + * other string) + * @return pointer to the location of the string or NULL if not found + */ +static const XML_Char * const *contains_str(const XML_Char * const *list, + const XML_Char *str, int step) { + if (list != NULL) { + while (*list != NULL) { + if (!strcmp(*list, str)) { + return list; + } + list += step; + } + } + return NULL; +} + +/** + * Checks that an element has non-empty values for required attributes. + * Increments the error count for each missing attribute. + * + * @param context the parsing context + * @param elem the element being checked + * @param atts the attribute list for the element + * @param req_atts the required attributes (NULL terminated list, or NULL) + * @return whether the required attributes are present + */ +static int check_req_attributes(ploader_context_t *plcontext, + const XML_Char *elem, const XML_Char * const *atts, + const XML_Char * const *req_atts) { + const XML_Char * const *a; + int error = 0; + + // Check that required attributes have non-empty values + for (a = req_atts; a != NULL && *a != NULL; a++) { + const XML_Char * const *av; + + if ((av = contains_str(atts, *a, 2)) != NULL) { + if ((*(av + 1))[0] == '\0') { + descriptor_errorf(plcontext, 0, + _("required attribute %s for element %s has an empty value"), + *a, elem); + error = 1; + } + } else { + descriptor_errorf(plcontext, 0, + _("required attribute %s missing for element %s"), + *a, elem); + error = 1; + } + } + + return !error; +} + +/** + * Checks that an element has non-empty values for required attributes and + * warns if there are unknown attributes. Increments the error count for + * each missing required attribute. + * + * @param context the parsing context + * @param elem the element being checked + * @param atts the attribute list for the element + * @param req_atts the required attributes (NULL terminated list, or NULL) + * @param opt_atts the optional attributes (NULL terminated list, or NULL) + * @return whether the required attributes are present + */ +static int check_attributes(ploader_context_t *plcontext, + const XML_Char *elem, const XML_Char * const *atts, + const XML_Char * const *req_atts, const XML_Char * const *opt_atts) { + int error = 0; + + // Check required attributes + error = !check_req_attributes(plcontext, elem, atts, req_atts); + + // Warn if there are unknown attributes + for (; *atts != NULL; atts += 2) { + if (contains_str(req_atts, *atts, 1) == NULL + && contains_str(opt_atts, *atts, 1) == NULL) { + descriptor_errorf(plcontext, 1, + _("ignoring unknown attribute %s for element %s"), + *atts, elem); + } + } + + return !error; +} + +/** + * Allocates memory using malloc. Reports a resource error if there is not + * enough available memory. + * + * @param context the parsing context + * @param size the number of bytes to allocate + * @return pointer to the allocated memory, or NULL if memory allocation failed + */ +static void *parser_malloc(ploader_context_t *plcontext, size_t size) { + void *ptr; + + if ((ptr = malloc(size)) == NULL) { + resource_error(plcontext); + } + return ptr; +} + +/** + * Makes a copy of the specified string. The memory is allocated using malloc. + * Reports a resource error if there is not enough available memory. + * + * @param context the parsing context + * @param src the source string to be copied + * @return copy of the string, or NULL if memory allocation failed + */ +static char *parser_strdup(ploader_context_t *plcontext, const char *src) { + char *dup; + + if ((dup = strdup(src)) == NULL) { + resource_error(plcontext); + } + return dup; +} + +/** + * Concatenates the specified strings into a new string. The memory for the concatenated + * string is allocated using malloc. Reports a resource error if there is not + * enough available memory. + * + * @param context the parsing context + * @param ... the strings to be concatenated, terminated by NULL + * @return the concatenated string, or NULL if memory allocation failed + */ +static char *parser_strscat(ploader_context_t *plcontext, ...) { + va_list ap; + const char *str; + char *dst; + size_t len; + + // Calculate the length of the concatenated string + va_start(ap, plcontext); + len = 0; + while ((str = va_arg(ap, const char *)) != NULL) { + len += strlen(str); + } + va_end(ap); + + // Allocate space for the concatenated string + if ((dst = parser_malloc(plcontext, sizeof(char) * (len + 1))) == NULL) { + return NULL; + } + + // Copy the strings + len = 0; + va_start(ap, plcontext); + while ((str = va_arg(ap, const char *)) != NULL) { + strcpy(dst + len, str); + len += strlen(str); + } + va_end(ap); + dst[len] = '\0'; + return dst; +} + +/** + * Puts the parser to a state in which it skips an unknown element. + * Warns error handlers about the unknown element. + * + * @param context the parsing context + * @param elem the element name + */ +static void unexpected_element(ploader_context_t *plcontext, const XML_Char *elem) { + plcontext->saved_state = plcontext->state; + plcontext->state = PARSER_UNKNOWN; + plcontext->depth = 0; + descriptor_errorf(plcontext, 1, _("ignoring unexpected element %s and its contents"), elem); +} + +/** + * Creates a copy of the specified attributes. Reports failed memory + * allocation. + * + * @param context the parser context + * @param src the source attributes to be copied + * @param num pointer to the location where number of attributes is stored, + * or NULL for none + * @return the duplicated attribute array, or NULL if empty or failed + */ +static char **parser_attsdup(ploader_context_t *plcontext, const XML_Char * const *src, + unsigned int *num_atts) { + char **atts = NULL, *attr_data = NULL; + unsigned int i; + unsigned int num; + size_t attr_size; + + // Calculate the number of attributes and the amount of space required + for (num = 0, attr_size = 0; src[num] != NULL; num++) { + attr_size += strlen(src[num]) + 1; + } + assert((num & 1) == 0); + + // Allocate necessary memory and copy attribute data + if (num > 0) { + if ((atts = parser_malloc(plcontext, num * sizeof(char *))) != NULL) { + if ((attr_data = parser_malloc(plcontext, attr_size * sizeof(char))) != NULL) { + size_t offset; + + for (i = 0, offset = 0; i < num; i++) { + strcpy(attr_data + offset, src[i]); + atts[i] = attr_data + offset; + offset += strlen(src[i]) + 1; + } + } + } + } + + // If successful then return duplicates, otherwise free any allocations + if (num == 0 || (atts != NULL && attr_data != NULL)) { + if (num_atts != NULL) { + *num_atts = num / 2; + } + return atts; + } else { + free(attr_data); + free(atts); + return NULL; + } +} + +/** + * Initializes a configuration element. Reports an error if memory allocation fails. + * + * @param context the parser context + * @param ce the configuration element to be initialized + * @param name the element name + * @param atts the element attributes + * @param parent the parent element + */ +static void init_cfg_element(ploader_context_t *plcontext, cp_cfg_element_t *ce, + const XML_Char *name, const XML_Char * const *atts, cp_cfg_element_t *parent) { + + // Initialize the configuration element + memset(ce, 0, sizeof(cp_cfg_element_t)); + ce->name = parser_strdup(plcontext, name); + ce->atts = parser_attsdup(plcontext, atts, &(ce->num_atts)); + ce->value = NULL; + plcontext->value = NULL; + plcontext->value_size = 0; + plcontext->value_length = 0; + ce->parent = parent; + ce->children = NULL; +} + +/** + * Processes the character data while parsing. + * + * @param userData the parsing context + * @param str the string data + * @param len the string length + */ +static void CP_XMLCALL character_data_handler( + void *userData, const XML_Char *str, int len) { + ploader_context_t *plcontext = userData; + + // Ignore leading whitespace + if (plcontext->value == NULL) { + int i; + + for (i = 0; i < len; i++) { + if (str[i] != ' ' && str[i] != '\n' && str[i] != '\r' && str[i] != '\t') { + break; + } + } + str += i; + len -= i; + if (len == 0) { + return; + } + } + + // Allocate more memory for the character data if needed + if (plcontext->value_length + len >= plcontext->value_size) { + size_t ns; + char *nv; + + ns = plcontext->value_size; + while (plcontext->value_length + len >= ns) { + if (ns == 0) { + ns = CP_CFG_ELEMENT_VALUE_INITSIZE; + } else { + ns = 2 * ns; + } + } + if ((nv = realloc(plcontext->value, ns * sizeof(char))) != NULL) { + plcontext->value = nv; + plcontext->value_size = ns; + } else { + resource_error(plcontext); + return; + } + } + + // Copy character data + strncpy(plcontext->value + plcontext->value_length, str, len * sizeof(char)); + plcontext->value_length += len; +} + +/** + * Processes the start of element events while parsing. + * + * @param userData the parsing context + * @param name the element name + * @param atts the element attributes + */ +static void CP_XMLCALL start_element_handler( + void *userData, const XML_Char *name, const XML_Char **atts) { + static const XML_Char * const req_plugin_atts[] = { "id", NULL }; + static const XML_Char * const opt_plugin_atts[] = { "name", "version", "provider-name", NULL }; + static const XML_Char * const req_bwcompatibility_atts[] = { NULL }; + static const XML_Char * const opt_bwcompatibility_atts[] = { "abi", "api", NULL }; + static const XML_Char * const req_cpluff_atts[] = { "version", NULL }; + static const XML_Char * const opt_cpluff_atts[] = { NULL }; + static const XML_Char * const req_import_atts[] = { "plugin", NULL }; + static const XML_Char * const opt_import_atts[] = { "version", "optional", NULL }; + static const XML_Char * const req_runtime_atts[] = { "library", NULL }; + static const XML_Char * const opt_runtime_atts[] = { "funcs", NULL }; + static const XML_Char * const req_ext_point_atts[] = { "id", NULL }; + static const XML_Char * const opt_ext_point_atts[] = { "name", "schema", NULL }; + static const XML_Char * const req_extension_atts[] = { "point", NULL }; + //static const XML_Char * const opt_extension_atts[] = { "id", "name", NULL }; + ploader_context_t *plcontext = userData; + unsigned int i; + + // Process element start + switch (plcontext->state) { + + case PARSER_BEGIN: + if (!strcmp(name, "plugin")) { + plcontext->state = PARSER_PLUGIN; + if (!check_attributes(plcontext, name, atts, + req_plugin_atts, opt_plugin_atts)) { + break; + } + for (i = 0; atts[i] != NULL; i += 2) { + if (!strcmp(atts[i], "name")) { + plcontext->plugin->name + = parser_strdup(plcontext, atts[i+1]); + } else if (!strcmp(atts[i], "id")) { + plcontext->plugin->identifier + = parser_strdup(plcontext, atts[i+1]); + } else if (!strcmp(atts[i], "version")) { + plcontext->plugin->version + = parser_strdup(plcontext, atts[i+1]); + } else if (!strcmp(atts[i], "provider-name")) { + plcontext->plugin->provider_name + = parser_strdup(plcontext, atts[i+1]); + } + } + } else { + unexpected_element(plcontext, name); + } + break; + + case PARSER_PLUGIN: + if (!strcmp(name, "backwards-compatibility")) { + if (check_attributes(plcontext, name, atts, + req_bwcompatibility_atts, opt_bwcompatibility_atts)) { + for (i = 0; atts[i] != NULL; i += 2) { + if (!strcmp(atts[i], "abi")) { + plcontext->plugin->abi_bw_compatibility = parser_strdup(plcontext, atts[i+1]); + } else if (!strcmp(atts[i], "api")) { + plcontext->plugin->api_bw_compatibility = parser_strdup(plcontext, atts[i+1]); + } + } + } + } else if (!strcmp(name, "requires")) { + plcontext->state = PARSER_REQUIRES; + } else if (!strcmp(name, "runtime")) { + if (check_attributes(plcontext, name, atts, + req_runtime_atts, opt_runtime_atts)) { + for (i = 0; atts[i] != NULL; i += 2) { + if (!strcmp(atts[i], "library")) { + plcontext->plugin->runtime_lib_name + = parser_strdup(plcontext, atts[i+1]); + } else if (!strcmp(atts[i], "funcs")) { + plcontext->plugin->runtime_funcs_symbol + = parser_strdup(plcontext, atts[i+1]); + } + } + } + } else if (!strcmp(name, "extension-point")) { + if (check_attributes(plcontext, name, atts, + req_ext_point_atts, opt_ext_point_atts)) { + cp_ext_point_t *ext_point; + + // Allocate space for extension points, if necessary + if (plcontext->plugin->num_ext_points == plcontext->ext_points_size) { + cp_ext_point_t *nep; + size_t ns; + + if (plcontext->ext_points_size == 0) { + ns = 4; + } else { + ns = plcontext->ext_points_size * 2; + } + if ((nep = realloc(plcontext->plugin->ext_points, + ns * sizeof(cp_ext_point_t))) == NULL) { + resource_error(plcontext); + break; + } + plcontext->plugin->ext_points = nep; + plcontext->ext_points_size = ns; + } + + // Parse extension point specification + ext_point = plcontext->plugin->ext_points + + plcontext->plugin->num_ext_points; + memset(ext_point, 0, sizeof(cp_ext_point_t)); + ext_point->plugin = plcontext->plugin; + ext_point->name = NULL; + ext_point->local_id = NULL; + ext_point->identifier = NULL; + ext_point->schema_path = NULL; + for (i = 0; atts[i] != NULL; i += 2) { + if (!strcmp(atts[i], "name")) { + ext_point->name + = parser_strdup(plcontext, atts[i+1]); + } else if (!strcmp(atts[i], "id")) { + ext_point->local_id + = parser_strdup(plcontext, atts[i+1]); + ext_point->identifier + = parser_strscat(plcontext, + plcontext->plugin->identifier, ".", atts[i+1], NULL); + } else if (!strcmp(atts[i], "schema")) { + ext_point->schema_path + = parser_strdup(plcontext, atts[i+1]); + } + } + plcontext->plugin->num_ext_points++; + + } + } else if (!(strcmp(name, "extension"))) { + plcontext->state = PARSER_EXTENSION; + plcontext->depth = 0; + if (check_req_attributes( + plcontext, name, atts, req_extension_atts)) { + cp_extension_t *extension; + + // Allocate space for extensions, if necessary + if (plcontext->plugin->num_extensions == plcontext->extensions_size) { + cp_extension_t *ne; + size_t ns; + + if (plcontext->extensions_size == 0) { + ns = 16; + } else { + ns = plcontext->extensions_size * 2; + } + if ((ne = realloc(plcontext->plugin->extensions, + ns * sizeof(cp_extension_t))) == NULL) { + resource_error(plcontext); + break; + } + plcontext->plugin->extensions = ne; + plcontext->extensions_size = ns; + } + + // Parse extension attributes + extension = plcontext->plugin->extensions + + plcontext->plugin->num_extensions; + memset(extension, 0, sizeof(cp_extension_t)); + extension->plugin = plcontext->plugin; + extension->name = NULL; + extension->local_id = NULL; + extension->identifier = NULL; + extension->ext_point_id = NULL; + extension->configuration = NULL; + for (i = 0; atts[i] != NULL; i += 2) { + if (!strcmp(atts[i], "point")) { + extension->ext_point_id + = parser_strdup(plcontext, atts[i+1]); + } else if (!strcmp(atts[i], "id")) { + extension->local_id + = parser_strdup(plcontext, atts[i+1]); + extension->identifier + = parser_strscat(plcontext, + plcontext->plugin->identifier, ".", atts[i+1], NULL); + } else if (!strcmp(atts[i], "name")) { + extension->name + = parser_strdup(plcontext, atts[i+1]); + } + } + plcontext->plugin->num_extensions++; + + // Initialize configuration parsing + if ((extension->configuration = plcontext->configuration + = parser_malloc(plcontext, sizeof(cp_cfg_element_t))) != NULL) { + init_cfg_element(plcontext, plcontext->configuration, name, atts, NULL); + } + XML_SetCharacterDataHandler(plcontext->parser, character_data_handler); + } + } else { + unexpected_element(plcontext, name); + } + break; + + case PARSER_REQUIRES: + if (!strcmp(name, "c-pluff")) { + if (check_attributes(plcontext, name, atts, + req_cpluff_atts, opt_cpluff_atts)) { + for (i = 0; atts[i] != NULL; i += 2) { + if (!strcmp(atts[i], "version")) { + plcontext->plugin->req_cpluff_version = parser_strdup(plcontext, atts[i+1]); + } + } + } + } else if (!strcmp(name, "import")) { + if (check_attributes(plcontext, name, atts, + req_import_atts, opt_import_atts)) { + cp_plugin_import_t *import = NULL; + + // Allocate space for imports, if necessary + if (plcontext->plugin->num_imports == plcontext->imports_size) { + cp_plugin_import_t *ni; + size_t ns; + + if (plcontext->imports_size == 0) { + ns = 16; + } else { + ns = plcontext->imports_size * 2; + } + if ((ni = realloc(plcontext->plugin->imports, + ns * sizeof(cp_plugin_import_t))) == NULL) { + resource_error(plcontext); + break; + } + plcontext->plugin->imports = ni; + plcontext->imports_size = ns; + } + + // Parse import specification + import = plcontext->plugin->imports + + plcontext->plugin->num_imports; + memset(import, 0, sizeof(cp_plugin_import_t)); + import->plugin_id = NULL; + import->version = NULL; + for (i = 0; atts[i] != NULL; i += 2) { + if (!strcmp(atts[i], "plugin")) { + import->plugin_id + = parser_strdup(plcontext, atts[i+1]); + } else if (!strcmp(atts[i], "version")) { + import->version = parser_strdup(plcontext, atts[i+1]); + } else if (!strcmp(atts[i], "optional")) { + if (!strcmp(atts[i+1], "true") + || !strcmp(atts[i+1], "1")) { + import->optional = 1; + } else if (strcmp(atts[i+1], "false") + && strcmp(atts[i+1], "0")) { + descriptor_errorf(plcontext, 0, _("unknown boolean value: %s"), atts[i+1]); + } + } + } + plcontext->plugin->num_imports++; + } + } else { + unexpected_element(plcontext, name); + } + break; + + case PARSER_EXTENSION: + plcontext->depth++; + if (plcontext->configuration != NULL && plcontext->skippedCEs == 0) { + cp_cfg_element_t *ce; + + // Allocate more space for children, if necessary + if (plcontext->configuration->num_children == plcontext->configuration->index) { + cp_cfg_element_t *nce; + size_t ns; + + if (plcontext->configuration->index == 0) { + ns = 16; + } else { + ns = plcontext->configuration->index * 2; + } + if ((nce = realloc(plcontext->configuration->children, + ns * sizeof(cp_cfg_element_t))) == NULL) { + plcontext->skippedCEs++; + resource_error(plcontext); + break; + } + plcontext->configuration->children = nce; + plcontext->configuration->index = ns; + } + + // Save possible value + if (plcontext->value != NULL) { + plcontext->value[plcontext->value_length] = '\0'; + plcontext->configuration->value = plcontext->value; + } + + ce = plcontext->configuration->children + plcontext->configuration->num_children; + init_cfg_element(plcontext, ce, name, atts, plcontext->configuration); + plcontext->configuration->num_children++; + plcontext->configuration = ce; + } + break; + + case PARSER_UNKNOWN: + plcontext->depth++; + break; + default: + unexpected_element(plcontext, name); + break; + } +} + +/** + * Processes the end of element events while parsing. + * + * @param context the parsing context + * @param name the element name + */ +static void CP_XMLCALL end_element_handler( + void *userData, const XML_Char *name) { + ploader_context_t *plcontext = userData; + + // Process element end + switch (plcontext->state) { + + case PARSER_PLUGIN: + if (!strcmp(name, "plugin")) { + + // Readjust memory allocated for extension points, if necessary + if (plcontext->ext_points_size != plcontext->plugin->num_ext_points) { + cp_ext_point_t *nep; + + if ((nep = realloc(plcontext->plugin->ext_points, + plcontext->plugin->num_ext_points * + sizeof(cp_ext_point_t))) != NULL + || plcontext->plugin->num_ext_points == 0) { + plcontext->plugin->ext_points = nep; + plcontext->ext_points_size = plcontext->plugin->num_ext_points; + } + } + + // Readjust memory allocated for extensions, if necessary + if (plcontext->extensions_size != plcontext->plugin->num_extensions) { + cp_extension_t *ne; + + if ((ne = realloc(plcontext->plugin->extensions, + plcontext->plugin->num_extensions * + sizeof(cp_extension_t))) != NULL + || plcontext->plugin->num_extensions == 0) { + plcontext->plugin->extensions = ne; + plcontext->extensions_size = plcontext->plugin->num_extensions; + } + } + + plcontext->state = PARSER_END; + } + break; + + case PARSER_REQUIRES: + if (!strcmp(name, "requires")) { + + // Readjust memory allocated for imports, if necessary + if (plcontext->imports_size != plcontext->plugin->num_imports) { + cp_plugin_import_t *ni; + + if ((ni = realloc(plcontext->plugin->imports, + plcontext->plugin->num_imports * + sizeof(cp_plugin_import_t))) != NULL + || plcontext->plugin->num_imports == 0) { + plcontext->plugin->imports = ni; + plcontext->imports_size = plcontext->plugin->num_imports; + } + } + + plcontext->state = PARSER_PLUGIN; + } + break; + + case PARSER_UNKNOWN: + if (plcontext->depth-- == 0) { + plcontext->state = plcontext->saved_state; + } + break; + + case PARSER_EXTENSION: + if (plcontext->skippedCEs > 0) { + plcontext->skippedCEs--; + } else if (plcontext->configuration != NULL) { + + // Readjust memory allocated for children, if necessary + if (plcontext->configuration->index != plcontext->configuration->num_children) { + cp_cfg_element_t *nce; + + if ((nce = realloc(plcontext->configuration->children, + plcontext->configuration->num_children * + sizeof(cp_cfg_element_t))) != NULL + || plcontext->configuration->num_children == 0) { + plcontext->configuration->children = nce; + } + } + + if (plcontext->configuration->parent != NULL) { + plcontext->configuration->index = plcontext->configuration->parent->num_children - 1; + } else { + plcontext->configuration->index = 0; + } + if (plcontext->value != NULL) { + char *v = plcontext->value; + int i; + + // Ignore trailing whitespace + for (i = plcontext->value_length - 1; i >= 0; i--) { + if (v[i] != ' ' && v[i] != '\n' && v[i] != '\r' && v[i] != '\t') { + break; + } + } + if (i < 0) { + free(plcontext->value); + plcontext->value = NULL; + plcontext->value_length = 0; + plcontext->value_size = 0; + } else { + plcontext->value_length = i + 1; + } + } + if (plcontext->value != NULL) { + + // Readjust memory allocated for value, if necessary + if (plcontext->value_size > plcontext->value_length + 1) { + char *nv; + + if ((nv = realloc(plcontext->value, (plcontext->value_length + 1) * sizeof(char))) != NULL) { + plcontext->value = nv; + } + } + + plcontext->value[plcontext->value_length] = '\0'; + plcontext->configuration->value = plcontext->value; + plcontext->value = NULL; + plcontext->value_size = 0; + plcontext->value_length = 0; + } + plcontext->configuration = plcontext->configuration->parent; + + // Restore possible value + if (plcontext->configuration != NULL + && plcontext->configuration->value != NULL) { + plcontext->value = plcontext->configuration->value; + plcontext->value_length = strlen(plcontext->value); + plcontext->value_size = CP_CFG_ELEMENT_VALUE_INITSIZE; + while (plcontext->value_size < plcontext->value_length + 1) { + plcontext->value_size *= 2; + } + } + + } + if (plcontext->depth-- == 0) { + assert(!strcmp(name, "extension")); + plcontext->state = PARSER_PLUGIN; + XML_SetCharacterDataHandler(plcontext->parser, NULL); + } + break; + + default: + descriptor_errorf(plcontext, 0, _("unexpected closing tag for %s"), + name); + return; + } +} + +static void dealloc_plugin_info(cp_context_t *ctx, cp_plugin_info_t *plugin) { + cpi_free_plugin(plugin); +} + +CP_C_API cp_plugin_info_t * cp_load_plugin_descriptor(cp_context_t *context, const char *path, cp_status_t *error) { + char *file = NULL; + cp_status_t status = CP_OK; + FILE *fh = NULL; + XML_Parser parser = NULL; + ploader_context_t *plcontext = NULL; + cp_plugin_info_t *plugin = NULL; + + CHECK_NOT_NULL(context); + CHECK_NOT_NULL(path); + cpi_lock_context(context); + cpi_check_invocation(context, CPI_CF_ANY, __func__); + do { + int path_len; + + // Construct the file name for the plug-in descriptor + path_len = strlen(path); + if (path_len == 0) { + status = CP_ERR_IO; + break; + } + if (path[path_len - 1] == CP_FNAMESEP_CHAR) { + path_len--; + } + file = malloc((path_len + strlen(CP_PLUGIN_DESCRIPTOR) + 2) * sizeof(char)); + if (file == NULL) { + status = CP_ERR_RESOURCE; + break; + } + strcpy(file, path); + file[path_len] = CP_FNAMESEP_CHAR; + strcpy(file + path_len + 1, CP_PLUGIN_DESCRIPTOR); + + // Open the file + if ((fh = fopen(file, "rb")) == NULL) { + status = CP_ERR_IO; + break; + } + + // Initialize the XML parsing + parser = XML_ParserCreate(NULL); + if (parser == NULL) { + status = CP_ERR_RESOURCE; + break; + } + XML_SetElementHandler(parser, + start_element_handler, + end_element_handler); + + // Initialize the parsing context + if ((plcontext = malloc(sizeof(ploader_context_t))) == NULL) { + status = CP_ERR_RESOURCE; + break; + } + memset(plcontext, 0, sizeof(ploader_context_t)); + if ((plcontext->plugin = malloc(sizeof(cp_plugin_info_t))) == NULL) { + status = CP_ERR_RESOURCE; + break; + } + plcontext->context = context; + plcontext->configuration = NULL; + plcontext->value = NULL; + plcontext->parser = parser; + plcontext->file = file; + plcontext->state = PARSER_BEGIN; + memset(plcontext->plugin, 0, sizeof(cp_plugin_info_t)); + plcontext->plugin->name = NULL; + plcontext->plugin->identifier = NULL; + plcontext->plugin->version = NULL; + plcontext->plugin->provider_name = NULL; + plcontext->plugin->abi_bw_compatibility = NULL; + plcontext->plugin->api_bw_compatibility = NULL; + plcontext->plugin->plugin_path = NULL; + plcontext->plugin->req_cpluff_version = NULL; + plcontext->plugin->imports = NULL; + plcontext->plugin->runtime_lib_name = NULL; + plcontext->plugin->runtime_funcs_symbol = NULL; + plcontext->plugin->ext_points = NULL; + plcontext->plugin->extensions = NULL; + XML_SetUserData(parser, plcontext); + + // Parse the plug-in descriptor + while (1) { + int bytes_read; + void *xml_buffer; + int i; + + // Get buffer from Expat + if ((xml_buffer = XML_GetBuffer(parser, CP_XML_PARSER_BUFFER_SIZE)) + == NULL) { + status = CP_ERR_RESOURCE; + break; + } + + // Read data into buffer + bytes_read = fread(xml_buffer, 1, CP_XML_PARSER_BUFFER_SIZE, fh); + if (ferror(fh)) { + status = CP_ERR_IO; + break; + } + + // Parse the data + if (!(i = XML_ParseBuffer(parser, bytes_read, bytes_read == 0)) + && context != NULL) { + cpi_lock_context(context); + cpi_errorf(context, + N_("XML parsing error in %s, line %d, column %d (%s)."), + file, + XML_GetErrorLineNumber(parser), + XML_GetErrorColumnNumber(parser) + 1, + XML_ErrorString(XML_GetErrorCode(parser))); + cpi_unlock_context(context); + } + if (!i || plcontext->state == PARSER_ERROR) { + status = CP_ERR_MALFORMED; + break; + } + + if (bytes_read == 0) { + break; + } + } + if (status == CP_OK) { + if (plcontext->state != PARSER_END || plcontext->error_count > 0) { + status = CP_ERR_MALFORMED; + } + if (plcontext->resource_error_count > 0) { + status = CP_ERR_RESOURCE; + } + } + if (status != CP_OK) { + break; + } + + // Initialize the plug-in path + *(file + path_len) = '\0'; + plcontext->plugin->plugin_path = file; + file = NULL; + + // Increase plug-in usage count + if ((status = cpi_register_info(context, plcontext->plugin, (void (*)(cp_context_t *, void *)) dealloc_plugin_info)) != CP_OK) { + break; + } + + } while (0); + + // Report possible errors + if (status != CP_OK) { + switch (status) { + case CP_ERR_MALFORMED: + cpi_errorf(context, + N_("Plug-in descriptor in %s is invalid."), path); + break; + case CP_ERR_IO: + cpi_errorf(context, + N_("An I/O error occurred while loading a plug-in descriptor from %s."), path); + break; + case CP_ERR_RESOURCE: + cpi_errorf(context, + N_("Insufficient system resources to load a plug-in descriptor from %s."), path); + break; + default: + cpi_errorf(context, + N_("Failed to load a plug-in descriptor from %s."), path); + break; + } + } + cpi_unlock_context(context); + + // Release persistently allocated data on failure + if (status != CP_OK) { + if (file != NULL) { + free(file); + file = NULL; + } + if (plcontext != NULL && plcontext->plugin != NULL) { + cpi_free_plugin(plcontext->plugin); + plcontext->plugin = NULL; + } + } + + // Otherwise copy the plug-in pointer + else { + plugin = plcontext->plugin; + } + + // Release data allocated for parsing + if (parser != NULL) { + XML_ParserFree(parser); + } + if (fh != NULL) { + fclose(fh); + } + if (plcontext != NULL) { + if (plcontext->value != NULL) { + free(plcontext->value); + } + free(plcontext); + plcontext = NULL; + } + + // Return error code + if (error != NULL) { + *error = status; + } + + return plugin; +} |