diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/darray.h | 3 | ||||
-rw-r--r-- | src/registry.c | 1193 | ||||
-rw-r--r-- | src/util-list.c | 94 | ||||
-rw-r--r-- | src/util-list.h | 71 |
4 files changed, 1361 insertions, 0 deletions
diff --git a/src/darray.h b/src/darray.h index 8e87c94..de659cc 100644 --- a/src/darray.h +++ b/src/darray.h @@ -206,4 +206,7 @@ darray_next_alloc(unsigned alloc, unsigned need, unsigned itemSize) (idx) < (arr).size; \ (idx)++, (val)++) +#define darray_foreach_reverse(i, arr) \ + for ((i) = &(arr).item[(arr).size - 1]; (arr).size > 0 && (i) >= &(arr).item[0]; (i)--) + #endif /* CCAN_DARRAY_H */ diff --git a/src/registry.c b/src/registry.c new file mode 100644 index 0000000..19e004b --- /dev/null +++ b/src/registry.c @@ -0,0 +1,1193 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * 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 (including the next + * paragraph) 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. + */ + +#include "config.h" + +#include <sys/stat.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <libxml/parser.h> + +#include "xkbcommon/xkbregistry.h" +#include "utils.h" +#include "util-list.h" + +struct rxkb_object; + +typedef void (*destroy_func_t)(struct rxkb_object *object); + +/** + * All our objects are refcounted and are linked to iterate through them. + * Abstract those bits away into a shared parent class so we can generate + * most of the functions through macros. + */ +struct rxkb_object { + struct rxkb_object *parent; + uint32_t refcount; + struct list link; + destroy_func_t destroy; +}; + +struct rxkb_iso639_code { + struct rxkb_object base; + char *code; +}; + +struct rxkb_iso3166_code { + struct rxkb_object base; + char *code; +}; + +enum context_state { + CONTEXT_NEW, + CONTEXT_PARSED, + CONTEXT_FAILED, +}; + +struct rxkb_context { + struct rxkb_object base; + enum context_state context_state; + + bool load_extra_rules_files; + + struct list models; /* list of struct rxkb_models */ + struct list layouts; /* list of struct rxkb_layouts */ + struct list option_groups; /* list of struct rxkb_option_group */ + + darray(char *) includes; + + + ATTR_PRINTF(3, 0) void (*log_fn)(struct rxkb_context *ctx, + enum rxkb_log_level level, + const char *fmt, va_list args); + enum rxkb_log_level log_level; + + void *userdata; +}; + +struct rxkb_model { + struct rxkb_object base; + + char *name; + char *vendor; + char *description; + enum rxkb_popularity popularity; +}; + +struct rxkb_layout { + struct rxkb_object base; + + char *name; + char *brief; + char *description; + char *variant; + enum rxkb_popularity popularity; + + struct list iso639s; /* list of struct rxkb_iso639_code */ + struct list iso3166s; /* list of struct rxkb_iso3166_code */ +}; + +struct rxkb_option_group { + struct rxkb_object base; + + bool allow_multiple; + struct list options; /* list of struct rxkb_options */ + char *name; + char *description; + enum rxkb_popularity popularity; +}; + +struct rxkb_option { + struct rxkb_object base; + + char *name; + char *brief; + char *description; + enum rxkb_popularity popularity; +}; + +static bool +parse(struct rxkb_context *ctx, const char *path, + enum rxkb_popularity popularity); + +static void +rxkb_log(struct rxkb_context *ctx, enum rxkb_log_level level, + const char *fmt, ...) +{ + va_list args; + + if (ctx->log_level < level) + return; + + va_start(args, fmt); + ctx->log_fn(ctx, level, fmt, args); + va_end(args); +} + +/* + * The format is not part of the argument list in order to avoid the + * "ISO C99 requires rest arguments to be used" warning when only the + * format is supplied without arguments. Not supplying it would still + * result in an error, though. + */ +#define log_dbg(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_DEBUG, __VA_ARGS__) +#define log_info(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_INFO, __VA_ARGS__) +#define log_warn(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_WARNING, __VA_ARGS__) +#define log_err(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_ERROR, __VA_ARGS__) +#define log_wsgo(ctx, ...) \ + rxkb_log((ctx), RXKB_LOG_LEVEL_CRITICAL, __VA_ARGS__) + + +#define DECLARE_REF_UNREF_FOR_TYPE(type_) \ +XKB_EXPORT struct type_ * type_##_ref(struct type_ *object) { \ + rxkb_object_ref(&object->base); \ + return object; \ +} \ +XKB_EXPORT struct type_ * type_##_unref(struct type_ *object) { \ + if (!object) return NULL; \ + return rxkb_object_unref(&object->base); \ +} + +#define DECLARE_CREATE_FOR_TYPE(type_) \ +static inline struct type_ * type_##_create(struct rxkb_object *parent) { \ + struct type_ *t = calloc(1, sizeof *t); \ + if (t) \ + rxkb_object_init(&t->base, parent, (destroy_func_t)type_##_destroy); \ + return t; \ +} + +#define DECLARE_TYPED_GETTER_FOR_TYPE(type_, field_, rtype_) \ +XKB_EXPORT rtype_ type_##_get_##field_(struct type_ *object) { \ + return object->field_; \ +} + +#define DECLARE_GETTER_FOR_TYPE(type_, field_) \ + DECLARE_TYPED_GETTER_FOR_TYPE(type_, field_, const char*) + +#define DECLARE_FIRST_NEXT_FOR_TYPE(type_, parent_type_, parent_field_) \ +XKB_EXPORT struct type_ * type_##_first(struct parent_type_ *parent) { \ + struct type_ *o = NULL; \ + if (!list_empty(&parent->parent_field_)) \ + o = list_first_entry(&parent->parent_field_, o, base.link); \ + return o; \ +} \ +XKB_EXPORT struct type_ * \ +type_##_next(struct type_ *o) \ +{ \ + struct parent_type_ *parent; \ + struct type_ *next; \ + parent = container_of(o->base.parent, struct parent_type_, base); \ + next = list_first_entry(&o->base.link, o, base.link); \ + if (list_is_last(&parent->parent_field_, &o->base.link)) \ + return NULL; \ + return next; \ +} + +static void +rxkb_object_init(struct rxkb_object *object, struct rxkb_object *parent, destroy_func_t destroy) +{ + object->refcount = 1; + object->destroy = destroy; + object->parent = parent; + list_init(&object->link); +} + +static void +rxkb_object_destroy(struct rxkb_object *object) +{ + if (object->destroy) + object->destroy(object); + list_remove(&object->link); + free(object); +} + +static void * +rxkb_object_ref(struct rxkb_object *object) +{ + assert(object->refcount >= 1); + ++object->refcount; + return object; +} + +static void * +rxkb_object_unref(struct rxkb_object *object) +{ + assert(object->refcount >= 1); + if (--object->refcount == 0) + rxkb_object_destroy(object); + return NULL; +} + +static void +rxkb_iso639_code_destroy(struct rxkb_iso639_code *code) +{ + free(code->code); +} + +XKB_EXPORT struct rxkb_iso639_code * +rxkb_layout_get_iso639_first(struct rxkb_layout *layout) +{ + struct rxkb_iso639_code *code = NULL; + + if (!list_empty(&layout->iso639s)) + code = list_first_entry(&layout->iso639s, code, base.link); + + return code; +} + +XKB_EXPORT struct rxkb_iso639_code * +rxkb_iso639_code_next(struct rxkb_iso639_code *code) +{ + struct rxkb_iso639_code *next = NULL; + struct rxkb_layout *layout; + + layout = container_of(code->base.parent, struct rxkb_layout, base); + + if (list_is_last(&layout->iso639s, &code->base.link)) + return NULL; + + next = list_first_entry(&code->base.link, code, base.link); + + return next; +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_iso639_code); +DECLARE_CREATE_FOR_TYPE(rxkb_iso639_code); +DECLARE_GETTER_FOR_TYPE(rxkb_iso639_code, code); + +static void +rxkb_iso3166_code_destroy(struct rxkb_iso3166_code *code) +{ + free(code->code); +} + +XKB_EXPORT struct rxkb_iso3166_code * +rxkb_layout_get_iso3166_first(struct rxkb_layout *layout) +{ + struct rxkb_iso3166_code *code = NULL; + + if (!list_empty(&layout->iso3166s)) + code = list_first_entry(&layout->iso3166s, code, base.link); + + return code; +} + +XKB_EXPORT struct rxkb_iso3166_code * +rxkb_iso3166_code_next(struct rxkb_iso3166_code *code) +{ + struct rxkb_iso3166_code *next = NULL; + struct rxkb_layout *layout; + + layout = container_of(code->base.parent, struct rxkb_layout, base); + + if (list_is_last(&layout->iso3166s, &code->base.link)) + return NULL; + + next = list_first_entry(&code->base.link, code, base.link); + + return next; +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_iso3166_code); +DECLARE_CREATE_FOR_TYPE(rxkb_iso3166_code); +DECLARE_GETTER_FOR_TYPE(rxkb_iso3166_code, code); + +static void +rxkb_option_destroy(struct rxkb_option *o) +{ + free(o->name); + free(o->brief); + free(o->description); +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_option); +DECLARE_CREATE_FOR_TYPE(rxkb_option); +DECLARE_GETTER_FOR_TYPE(rxkb_option, name); +DECLARE_GETTER_FOR_TYPE(rxkb_option, brief); +DECLARE_GETTER_FOR_TYPE(rxkb_option, description); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_option, popularity, enum rxkb_popularity); +DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_option, rxkb_option_group, options); + +static void +rxkb_layout_destroy(struct rxkb_layout *l) +{ + struct rxkb_iso639_code *iso639, *tmp_639; + struct rxkb_iso3166_code *iso3166, *tmp_3166; + + free(l->name); + free(l->brief); + free(l->description); + free(l->variant); + + list_for_each_safe(iso639, tmp_639, &l->iso639s, base.link) { + rxkb_iso639_code_unref(iso639); + } + list_for_each_safe(iso3166, tmp_3166, &l->iso3166s, base.link) { + rxkb_iso3166_code_unref(iso3166); + } +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_layout); +DECLARE_CREATE_FOR_TYPE(rxkb_layout); +DECLARE_GETTER_FOR_TYPE(rxkb_layout, name); +DECLARE_GETTER_FOR_TYPE(rxkb_layout, brief); +DECLARE_GETTER_FOR_TYPE(rxkb_layout, description); +DECLARE_GETTER_FOR_TYPE(rxkb_layout, variant); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_layout, popularity, enum rxkb_popularity); +DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_layout, rxkb_context, layouts); + +static void +rxkb_model_destroy(struct rxkb_model *m) +{ + free(m->name); + free(m->vendor); + free(m->description); +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_model); +DECLARE_CREATE_FOR_TYPE(rxkb_model); +DECLARE_GETTER_FOR_TYPE(rxkb_model, name); +DECLARE_GETTER_FOR_TYPE(rxkb_model, vendor); +DECLARE_GETTER_FOR_TYPE(rxkb_model, description); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_model, popularity, enum rxkb_popularity); +DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_model, rxkb_context, models); + +static void +rxkb_option_group_destroy(struct rxkb_option_group *og) +{ + struct rxkb_option *o, *otmp; + + free(og->name); + free(og->description); + + list_for_each_safe(o, otmp, &og->options, base.link) { + rxkb_option_unref(o); + } +} + +XKB_EXPORT bool +rxkb_option_group_allows_multiple(struct rxkb_option_group *g) +{ + return g->allow_multiple; +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_option_group); +DECLARE_CREATE_FOR_TYPE(rxkb_option_group); +DECLARE_GETTER_FOR_TYPE(rxkb_option_group, name); +DECLARE_GETTER_FOR_TYPE(rxkb_option_group, description); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_option_group, popularity, enum rxkb_popularity); +DECLARE_FIRST_NEXT_FOR_TYPE(rxkb_option_group, rxkb_context, option_groups); + +static void +rxkb_context_destroy(struct rxkb_context *ctx) +{ + struct rxkb_model *m, *mtmp; + struct rxkb_layout *l, *ltmp; + struct rxkb_option_group *og, *ogtmp; + char **path; + + list_for_each_safe(m, mtmp, &ctx->models, base.link) + rxkb_model_unref(m); + assert(list_empty(&ctx->models)); + + list_for_each_safe(l, ltmp, &ctx->layouts, base.link) + rxkb_layout_unref(l); + assert(list_empty(&ctx->layouts)); + + list_for_each_safe(og, ogtmp, &ctx->option_groups, base.link) + rxkb_option_group_unref(og); + assert(list_empty(&ctx->option_groups)); + + darray_foreach(path, ctx->includes) + free(*path); + darray_free(ctx->includes); + + assert(darray_empty(ctx->includes)); +} + +DECLARE_REF_UNREF_FOR_TYPE(rxkb_context); +DECLARE_CREATE_FOR_TYPE(rxkb_context); +DECLARE_TYPED_GETTER_FOR_TYPE(rxkb_context, log_level, enum rxkb_log_level); + +XKB_EXPORT void +rxkb_context_set_log_level(struct rxkb_context *ctx, + enum rxkb_log_level level) +{ + ctx->log_level = level; +} + +static const char * +log_level_to_prefix(enum rxkb_log_level level) +{ + switch (level) { + case RXKB_LOG_LEVEL_DEBUG: + return "xkbregistry: DEBUG: "; + case RXKB_LOG_LEVEL_INFO: + return "xkbregistry: INFO: "; + case RXKB_LOG_LEVEL_WARNING: + return "xkbregistry: WARNING: "; + case RXKB_LOG_LEVEL_ERROR: + return "xkbregistry: ERROR: "; + case RXKB_LOG_LEVEL_CRITICAL: + return "xkbregistry: CRITICAL: "; + default: + return NULL; + } +} + +ATTR_PRINTF(3, 0) static void +default_log_fn(struct rxkb_context *ctx, enum rxkb_log_level level, + const char *fmt, va_list args) +{ + const char *prefix = log_level_to_prefix(level); + + if (prefix) + fprintf(stderr, "%s", prefix); + vfprintf(stderr, fmt, args); +} + +static enum rxkb_log_level +log_level(const char *level) { + char *endptr; + enum rxkb_log_level lvl; + + errno = 0; + lvl = strtol(level, &endptr, 10); + if (errno == 0 && (endptr[0] == '\0' || is_space(endptr[0]))) + return lvl; + if (istreq_prefix("crit", level)) + return RXKB_LOG_LEVEL_CRITICAL; + if (istreq_prefix("err", level)) + return RXKB_LOG_LEVEL_ERROR; + if (istreq_prefix("warn", level)) + return RXKB_LOG_LEVEL_WARNING; + if (istreq_prefix("info", level)) + return RXKB_LOG_LEVEL_INFO; + if (istreq_prefix("debug", level) || istreq_prefix("dbg", level)) + return RXKB_LOG_LEVEL_DEBUG; + + return RXKB_LOG_LEVEL_ERROR; +} + +XKB_EXPORT struct rxkb_context * +rxkb_context_new(enum rxkb_context_flags flags) +{ + struct rxkb_context *ctx = rxkb_context_create(NULL); + const char *env; + + if (!ctx) + return NULL; + + ctx->context_state = CONTEXT_NEW; + ctx->load_extra_rules_files = flags & RXKB_CONTEXT_LOAD_EXOTIC_RULES; + ctx->log_fn = default_log_fn; + ctx->log_level = RXKB_LOG_LEVEL_ERROR; + + /* Environment overwrites defaults. */ + env = secure_getenv("RXKB_LOG_LEVEL"); + if (env) + rxkb_context_set_log_level(ctx, log_level(env)); + + list_init(&ctx->models); + list_init(&ctx->layouts); + list_init(&ctx->option_groups); + + if (!(flags & RXKB_CONTEXT_NO_DEFAULT_INCLUDES) && + !rxkb_context_include_path_append_default(ctx)) { + rxkb_context_unref(ctx); + return NULL; + } + + return ctx; +} + +XKB_EXPORT void +rxkb_context_set_log_fn(struct rxkb_context *ctx, + void (*log_fn)(struct rxkb_context *ctx, + enum rxkb_log_level level, + const char *fmt, va_list args)) +{ + ctx->log_fn = (log_fn ? log_fn : default_log_fn); +} + +XKB_EXPORT bool +rxkb_context_include_path_append(struct rxkb_context *ctx, const char *path) +{ + struct stat stat_buf; + int err; + char *tmp = NULL; + char rules[PATH_MAX]; + + if (ctx->context_state != CONTEXT_NEW) { + log_err(ctx, "include paths can only be appended to a new context\n"); + return false; + } + + tmp = strdup(path); + if (!tmp) + goto err; + + err = stat(path, &stat_buf); + if (err != 0) + goto err; + if (!S_ISDIR(stat_buf.st_mode)) + goto err; + + if (!check_eaccess(path, R_OK | X_OK)) + goto err; + + /* Pre-filter for the 99.9% case - if we can't assemble the default ruleset + * path, complain here instead of during parsing later. The niche cases + * where this is the wrong behaviour aren't worth worrying about. + */ + if (!snprintf_safe(rules, sizeof(rules), "%s/rules/%s.xml", + path, DEFAULT_XKB_RULES)) + goto err; + + darray_append(ctx->includes, tmp); + + return true; + +err: + free(tmp); + return false; +} + +XKB_EXPORT bool +rxkb_context_include_path_append_default(struct rxkb_context *ctx) +{ + const char *home, *xdg, *root; + char *user_path; + int err; + bool ret = false; + + if (ctx->context_state != CONTEXT_NEW) { + log_err(ctx, "include paths can only be appended to a new context\n"); + return false; + } + + home = secure_getenv("HOME"); + + xdg = secure_getenv("XDG_CONFIG_HOME"); + if (xdg != NULL) { + err = asprintf(&user_path, "%s/xkb", xdg); + if (err >= 0) { + ret |= rxkb_context_include_path_append(ctx, user_path); + free(user_path); + } + } else if (home != NULL) { + /* XDG_CONFIG_HOME fallback is $HOME/.config/ */ + err = asprintf(&user_path, "%s/.config/xkb", home); + if (err >= 0) { + ret |= rxkb_context_include_path_append(ctx, user_path); + free(user_path); + } + } + + if (home != NULL) { + err = asprintf(&user_path, "%s/.xkb", home); + if (err >= 0) { + ret |= rxkb_context_include_path_append(ctx, user_path); + free(user_path); + } + } + + root = secure_getenv("XKB_CONFIG_ROOT"); + if (root != NULL) + ret |= rxkb_context_include_path_append(ctx, root); + else + ret |= rxkb_context_include_path_append(ctx, DFLT_XKB_CONFIG_ROOT); + + return ret; +} + +XKB_EXPORT bool +rxkb_context_parse_default_ruleset(struct rxkb_context *ctx) +{ + return rxkb_context_parse(ctx, DEFAULT_XKB_RULES); +} + +XKB_EXPORT bool +rxkb_context_parse(struct rxkb_context *ctx, const char *ruleset) +{ + char **path; + bool success = false; + + if (ctx->context_state != CONTEXT_NEW) { + log_err(ctx, "parse must only be called on a new context\n"); + return false; + } + + darray_foreach_reverse(path, ctx->includes) { + char rules[PATH_MAX]; + + if (snprintf_safe(rules, sizeof(rules), "%s/rules/%s.xml", + *path, ruleset)) { + log_dbg(ctx, "Parsing %s\n", rules); + if (parse(ctx, rules, RXKB_POPULARITY_STANDARD)) + success = true; + } + + if (ctx->load_extra_rules_files && + snprintf_safe(rules, sizeof(rules), "%s/rules/%s.extras.xml", + *path, ruleset)) { + log_dbg(ctx, "Parsing %s\n", rules); + if (parse(ctx, rules, RXKB_POPULARITY_EXOTIC)) + success = true; + } + } + + ctx->context_state = success ? CONTEXT_PARSED : CONTEXT_FAILED; + + return success; +} + + +XKB_EXPORT void +rxkb_context_set_user_data(struct rxkb_context *ctx, void *userdata) +{ + ctx->userdata = userdata; +} + +XKB_EXPORT void * +rxkb_context_get_user_data(struct rxkb_context *ctx) +{ + return ctx->userdata; +} + +static inline bool +is_node(xmlNode *node, const char *name) +{ + return node->type == XML_ELEMENT_NODE && + xmlStrEqual(node->name, (const xmlChar*)name); +} + +/* return a copy of the text content from the first text node of this node */ +static char * +extract_text(xmlNode *node) +{ + xmlNode *n; + + for (n = node->children; n; n = n->next) { + if (n->type == XML_TEXT_NODE) + return (char *)xmlStrdup(n->content); + } + return NULL; +} + +static bool +parse_config_item(struct rxkb_context *ctx, + xmlNode *parent, + char **name, + char **description, + char **brief, + char **vendor) +{ + xmlNode *node = NULL; + xmlNode *ci = NULL; + + for (ci = parent->children; ci; ci = ci->next) { + if (is_node(ci, "configItem")) { + *name = NULL; + *description = NULL; + *brief = NULL; + *vendor = NULL; + + for (node = ci->children; node; node = node->next) { + if (is_node(node, "name")) + *name = extract_text(node); + else if (is_node(node, "description")) + *description = extract_text(node); + else if (is_node(node, "shortDescription")) + *brief = extract_text(node); + else if (is_node(node, "vendor")) + *vendor = extract_text(node); + /* Note: the DTD allows for vendor + brief but models only use + * vendor and everything else only uses shortDescription */ + } + + if (!*name || !strlen(*name)) { + log_err(ctx, "xml:%d: missing required element 'name'\n", + ci->line); + return false; + } + + return true; /* only one configItem allowed in the dtd */ + } + } + + return false; +} + +static void +parse_model(struct rxkb_context *ctx, xmlNode *model, + enum rxkb_popularity popularity) +{ + char *name, *description, *brief, *vendor; + + if (parse_config_item(ctx, model, &name, &description, &brief, &vendor)) { + struct rxkb_model *m; + + list_for_each(m, &ctx->models, base.link) { + if (streq(m->name, name)) { + free(name); + free(description); + free(brief); + free(vendor); + return; + } + } + + /* new model */ + m = rxkb_model_create(&ctx->base); + m->name = name; + m->description = description; + m->vendor = vendor; + m->popularity = popularity; + list_append(&ctx->models, &m->base.link); + } +} + +static void +parse_model_list(struct rxkb_context *ctx, xmlNode *model_list, + enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = model_list->children; node; node = node->next) { + if (is_node(node, "model")) + parse_model(ctx, node, popularity); + } +} + +static void +parse_language_list(xmlNode *language_list, struct rxkb_layout *layout) +{ + xmlNode *node = NULL; + struct rxkb_iso639_code *code; + + for (node = language_list->children; node; node = node->next) { + if (is_node(node, "iso639Id")) { + char *str = extract_text(node); + struct rxkb_object *parent; + + parent = &layout->base; + code = rxkb_iso639_code_create(parent); + code->code = str; + list_append(&layout->iso639s, &code->base.link); + } + } +} + +static void +parse_country_list(xmlNode *country_list, struct rxkb_layout *layout) +{ + xmlNode *node = NULL; + struct rxkb_iso3166_code *code; + + for (node = country_list->children; node; node = node->next) { + if (is_node(node, "iso3166Id")) { + char *str = extract_text(node); + struct rxkb_object *parent; + + parent = &layout->base; + code = rxkb_iso3166_code_create(parent); + code->code = str; + list_append(&layout->iso3166s, &code->base.link); + } + } +} + +static void +parse_variant(struct rxkb_context *ctx, struct rxkb_layout *l, + xmlNode *variant, enum rxkb_popularity popularity) +{ + xmlNode *ci; + char *name, *description, *brief, *vendor; + + if (parse_config_item(ctx, variant, &name, &description, &brief, &vendor)) { + struct rxkb_layout *v; + bool exists = false; + + list_for_each(v, &ctx->layouts, base.link) { + if (streq(v->name, name) && streq(v->name, l->name)) { + exists = true; + break; + } + } + + if (!exists) { + v = rxkb_layout_create(&ctx->base); + list_init(&v->iso639s); + list_init(&v->iso3166s); + v->name = strdup(l->name); + v->variant = name; + v->description = description; + v->brief = brief; + v->popularity = popularity; + list_append(&ctx->layouts, &v->base.link); + + for (ci = variant->children; ci; ci = ci->next) { + xmlNode *node; + + if (!is_node(ci, "configItem")) + continue; + + for (node = ci->children; node; node = node->next) { + if (is_node(node, "languageList")) + parse_language_list(node, v); + if (is_node(node, "countryList")) + parse_country_list(node, v); + } + } + } else { + free(name); + free(description); + free(brief); + free(vendor); + } + } +} + +static void +parse_variant_list(struct rxkb_context *ctx, struct rxkb_layout *l, + xmlNode *variant_list, enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = variant_list->children; node; node = node->next) { + if (is_node(node, "variant")) + parse_variant(ctx, l, node, popularity); + } +} + +static void +parse_layout(struct rxkb_context *ctx, xmlNode *layout, + enum rxkb_popularity popularity) +{ + char *name, *description, *brief, *vendor; + struct rxkb_layout *l; + xmlNode *node = NULL; + bool exists = false; + + if (!parse_config_item(ctx, layout, &name, &description, &brief, &vendor)) + return; + + list_for_each(l, &ctx->layouts, base.link) { + if (streq(l->name, name) && l->variant == NULL) { + exists = true; + break; + } + } + + if (!exists) { + l = rxkb_layout_create(&ctx->base); + list_init(&l->iso639s); + list_init(&l->iso3166s); + l->name = name; + l->variant = NULL; + l->description = description; + l->brief = brief; + l->popularity = popularity; + list_append(&ctx->layouts, &l->base.link); + } else { + free(name); + free(description); + free(brief); + free(vendor); + } + + for (node = layout->children; node; node = node->next) { + if (is_node(node, "variantList")) { + parse_variant_list(ctx, l, node, popularity); + } + if (!exists && is_node(node, "configItem")) { + xmlNode *ll; + for (ll = node->children; ll; ll = ll->next) { + if (is_node(ll, "languageList")) + parse_language_list(ll, l); + if (is_node(ll, "countryList")) + parse_country_list(ll, l); + } + } + } +} + +static void +parse_layout_list(struct rxkb_context *ctx, xmlNode *layout_list, + enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = layout_list->children; node; node = node->next) { + if (is_node(node, "layout")) + parse_layout(ctx, node, popularity); + } +} + +static void +parse_option(struct rxkb_context *ctx, struct rxkb_option_group *group, + xmlNode *option, enum rxkb_popularity popularity) +{ + char *name, *description, *brief, *vendor; + + if (parse_config_item(ctx, option, &name, &description, &brief, &vendor)) { + struct rxkb_option *o; + + list_for_each(o, &group->options, base.link) { + if (streq(o->name, name)) { + free(name); + free(description); + free(brief); + free(vendor); + return; + } + } + + o = rxkb_option_create(&group->base); + o->name = name; + o->description = description; + o->popularity = popularity; + list_append(&group->options, &o->base.link); + } +} + +static void +parse_group(struct rxkb_context *ctx, xmlNode *group, + enum rxkb_popularity popularity) +{ + char *name, *description, *brief, *vendor; + struct rxkb_option_group *g; + xmlNode *node = NULL; + xmlChar *multiple; + bool exists = false; + + if (!parse_config_item(ctx, group, &name, &description, &brief, &vendor)) + return; + + list_for_each(g, &ctx->option_groups, base.link) { + if (streq(g->name, name)) { + exists = true; + break; + } + } + + if (!exists) { + g = rxkb_option_group_create(&ctx->base); + g->name = name; + g->description = description; + g->popularity = popularity; + + multiple = xmlGetProp(group, (const xmlChar*)"allowMultipleSelection"); + if (multiple && xmlStrEqual(multiple, (const xmlChar*)"true")) + g->allow_multiple = true; + xmlFree(multiple); + + list_init(&g->options); + list_append(&ctx->option_groups, &g->base.link); + } else { + free(name); + free(description); + free(brief); + free(vendor); + } + + for (node = group->children; node; node = node->next) { + if (is_node(node, "option")) + parse_option(ctx, g, node, popularity); + } +} + +static void +parse_option_list(struct rxkb_context *ctx, xmlNode *option_list, + enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = option_list->children; node; node = node->next) { + if (is_node(node, "group")) + parse_group(ctx, node, popularity); + } +} + +static void +parse_rules_xml(struct rxkb_context *ctx, xmlNode *root, + enum rxkb_popularity popularity) +{ + xmlNode *node = NULL; + + for (node = root->children; node; node = node->next) { + if (is_node(node, "modelList")) + parse_model_list(ctx, node, popularity); + else if (is_node(node, "layoutList")) + parse_layout_list(ctx, node, popularity); + else if (is_node(node, "optionList")) + parse_option_list(ctx, node, popularity); + } +} + +static void +ATTR_PRINTF(2, 0) +xml_error_func(void *ctx, const char *msg, ...) +{ + static char buf[PATH_MAX]; + static int slen = 0; + va_list args; + int rc; + + /* libxml2 prints IO errors from bad includes paths by + * calling the error function once per word. So we get to + * re-assemble the message here and print it when we get + * the line break. My enthusiasm about this is indescribable. + */ + va_start(args, msg); + rc = vsnprintf(&buf[slen], sizeof(buf) - slen, msg, args); + va_end(args); + + /* This shouldn't really happen */ + if (rc < 0) { + log_err(ctx, "+++ out of cheese error. redo from start +++\n"); + slen = 0; + memset(buf, 0, sizeof(buf)); + return; + } + + slen += rc; + if (slen >= (int)sizeof(buf)) { + /* truncated, let's flush this */ + buf[sizeof(buf) - 1] = '\n'; + slen = sizeof(buf); + } + + /* We're assuming here that the last character is \n. */ + if (buf[slen - 1] == '\n') { + log_err(ctx, "%s", buf); + memset(buf, 0, sizeof(buf)); + slen = 0; + } +} + +static bool +validate(struct rxkb_context *ctx, xmlDoc *doc) +{ + bool success = false; + xmlValidCtxt *dtdvalid = NULL; + xmlDtd *dtd = NULL; + xmlParserInputBufferPtr buf = NULL; + /* This is a modified version of the xkeyboard-config xkb.dtd. That one + * requires modelList, layoutList and optionList, we + * allow for any of those to be missing. + */ + const char dtdstr[] = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!ELEMENT xkbConfigRegistry (modelList?, layoutList?, optionList?)>\n" + "<!ATTLIST xkbConfigRegistry version CDATA \"1.1\">\n" + "<!ELEMENT modelList (model*)>\n" + "<!ELEMENT model (configItem)>\n" + "<!ELEMENT layoutList (layout*)>\n" + "<!ELEMENT layout (configItem, variantList?)>\n" + "<!ELEMENT optionList (group*)>\n" + "<!ELEMENT variantList (variant*)>\n" + "<!ELEMENT variant (configItem)>\n" + "<!ELEMENT group (configItem, option*)>\n" + "<!ATTLIST group allowMultipleSelection (true|false) \"false\">\n" + "<!ELEMENT option (configItem)>\n" + "<!ELEMENT configItem (name, shortDescription?, description?, vendor?, countryList?, languageList?, hwList?)>\n" + "<!ATTLIST configItem popularity (standard|exotic) \"standard\">\n" + "<!ELEMENT name (#PCDATA)>\n" + "<!ELEMENT shortDescription (#PCDATA)>\n" + "<!ELEMENT description (#PCDATA)>\n" + "<!ELEMENT vendor (#PCDATA)>\n" + "<!ELEMENT countryList (iso3166Id+)>\n" + "<!ELEMENT iso3166Id (#PCDATA)>\n" + "<!ELEMENT languageList (iso639Id+)>\n" + "<!ELEMENT iso639Id (#PCDATA)>\n" + "<!ELEMENT hwList (hwId+)>\n" + "<!ELEMENT hwId (#PCDATA)>\n"; + + /* Note: do not use xmlParserInputBufferCreateStatic, it generates random + * DTD validity errors for unknown reasons */ + buf = xmlParserInputBufferCreateMem(dtdstr, sizeof(dtdstr), + XML_CHAR_ENCODING_UTF8); + if (!buf) + return false; + + dtd = xmlIOParseDTD(NULL, buf, XML_CHAR_ENCODING_UTF8); + if (!dtd) { + log_err(ctx, "Failed to load DTD\n"); + return false; + } + + dtdvalid = xmlNewValidCtxt(); + if (xmlValidateDtd(dtdvalid, doc, dtd)) + success = true; + + if (dtd) + xmlFreeDtd(dtd); + if (dtdvalid) + xmlFreeValidCtxt(dtdvalid); + + return success; +} + +static bool +parse(struct rxkb_context *ctx, const char *path, + enum rxkb_popularity popularity) +{ + bool success = false; + xmlDoc *doc = NULL; + xmlNode *root = NULL; + + if (!check_eaccess(path, R_OK)) + return false; + + LIBXML_TEST_VERSION + + xmlSetGenericErrorFunc(ctx, xml_error_func); + + doc = xmlParseFile(path); + if (!doc) + return false; + + if (!validate(ctx, doc)) { + log_err(ctx, "XML error: failed to validate document at %s\n", path); + goto error; + } + + root = xmlDocGetRootElement(doc); + parse_rules_xml(ctx, root, popularity); + + success = true; +error: + xmlFreeDoc(doc); + xmlCleanupParser(); + + return success; +} diff --git a/src/util-list.c b/src/util-list.c new file mode 100644 index 0000000..b6f5069 --- /dev/null +++ b/src/util-list.c @@ -0,0 +1,94 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2013-2015 Red Hat, Inc. + * + * 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 (including the next + * paragraph) 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. + */ + +#include "config.h" + +#include <assert.h> +#include <stddef.h> +#include <stdbool.h> + +#include "util-list.h" + +void +list_init(struct list *list) +{ + list->prev = list; + list->next = list; +} + +void +list_insert(struct list *list, struct list *elm) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || + !"elm->next|prev is not NULL, list node used twice?"); + + elm->prev = list; + elm->next = list->next; + list->next = elm; + elm->next->prev = elm; +} + +void +list_append(struct list *list, struct list *elm) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || + !"elm->next|prev is not NULL, list node used twice?"); + + elm->next = list; + elm->prev = list->prev; + list->prev = elm; + elm->prev->next = elm; +} + +void +list_remove(struct list *elm) +{ + assert((elm->next != NULL && elm->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + + elm->prev->next = elm->next; + elm->next->prev = elm->prev; + elm->next = NULL; + elm->prev = NULL; +} + +bool +list_empty(const struct list *list) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + + return list->next == list; +} + +bool +list_is_last(const struct list *list, const struct list *elm) +{ + return elm->next == list; +} diff --git a/src/util-list.h b/src/util-list.h new file mode 100644 index 0000000..573dff7 --- /dev/null +++ b/src/util-list.h @@ -0,0 +1,71 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2013-2015 Red Hat, Inc. + * + * 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 (including the next + * paragraph) 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. + */ + +#pragma once + +#include "config.h" + +#include <stdbool.h> +#include <stddef.h> + +/* + * This list data structure is a verbatim copy from wayland-util.h from the + * Wayland project; except that wl_ prefix has been removed. + */ + +struct list { + struct list *prev; + struct list *next; +}; + +void list_init(struct list *list); +void list_insert(struct list *list, struct list *elm); +void list_append(struct list *list, struct list *elm); +void list_remove(struct list *elm); +bool list_empty(const struct list *list); +bool list_is_last(const struct list *list, const struct list *elm); + +#define container_of(ptr, type, member) \ + (__typeof__(type) *)((char *)(ptr) - \ + offsetof(__typeof__(type), member)) + +#define list_first_entry(head, pos, member) \ + container_of((head)->next, __typeof__(*pos), member) + +#define list_last_entry(head, pos, member) \ + container_of((head)->prev, __typeof__(*pos), member) + +#define list_for_each(pos, head, member) \ + for (pos = 0, pos = list_first_entry(head, pos, member); \ + &pos->member != (head); \ + pos = list_first_entry(&pos->member, pos, member)) + +#define list_for_each_safe(pos, tmp, head, member) \ + for (pos = 0, tmp = 0, \ + pos = list_first_entry(head, pos, member), \ + tmp = list_first_entry(&pos->member, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = list_first_entry(&pos->member, tmp, member)) |