/*------------------------------------------------------------------------- * 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 #include #include #include #include #include #include #include #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 "addon.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[] = { "addon", 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, "addon")) { 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], "addon")) { 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, "addon")) { // 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_debugf(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; } CP_C_API cp_plugin_info_t * cp_load_plugin_descriptor_from_memory(cp_context_t *context, const char *buffer, unsigned int buffer_len, cp_status_t *error) { char *file = NULL; const char *path = "memory"; cp_status_t status = CP_OK; XML_Parser parser = NULL; ploader_context_t *plcontext = NULL; cp_plugin_info_t *plugin = NULL; CHECK_NOT_NULL(context); CHECK_NOT_NULL(buffer); cpi_lock_context(context); cpi_check_invocation(context, CPI_CF_ANY, __func__); do { int path_len = 6; file = malloc((path_len + 1) * sizeof(char)); if (file == NULL) { status = CP_ERR_RESOURCE; break; } strcpy(file, path); // 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 do { void *xml_buffer; int i; // Get buffer from Expat if ((xml_buffer = XML_GetBuffer(parser, buffer_len)) == NULL) { status = CP_ERR_RESOURCE; break; } // Read data into buffer memcpy(xml_buffer, buffer, buffer_len); // Parse the data if (!(i = XML_ParseBuffer(parser, buffer_len, 1)) && 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; } } while (0); 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_debugf(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 (plcontext != NULL) { if (plcontext->value != NULL) { free(plcontext->value); } free(plcontext); plcontext = NULL; } // Return error code if (error != NULL) { *error = status; } return plugin; }