Messages: add new messages to registry
[platform/upstream/libxkbcommon.git] / src / xkbcomp / include.c
index a7a8bbc..fc692fb 100644 (file)
  *
  ********************************************************/
 
+/*
+ * Copyright © 2012 Ran Benita <ran234@gmail.com>
+ *
+ * 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 <errno.h>
 #include <limits.h>
 #include <stdio.h>
 
 #include "xkbcomp-priv.h"
-#include "text.h"
 #include "include.h"
 
 /**
- * Extract the first token from an include statement.
- * @param str_inout Input statement, modified in-place. Can be passed in
+ * Parse an include statement. Each call returns a file name, along with
+ * (possibly) a specific map in the file, an explicit group designator, and
+ * the separator from the next file, used to determine the merge mode.
+ *
+ * @param str_inout Input statement, modified in-place. Should be passed in
  * repeatedly. If str_inout is NULL, the parsing has completed.
- * @param file_rtrn Set to the include file to be used.
- * @param map_rtrn Set to whatever comes after ), if any.
- * @param nextop_rtrn Set to the next operation in the complete statement.
- * @param extra_data Set to the string between ( and ), if any.
  *
- * @return true if parsing was succcessful, false for an illegal string.
+ * @param file_rtrn Set to the name of the include file to be used. Combined
+ * with an enum xkb_file_type, this determines which file to look for in the
+ * include path.
  *
- * Example: "evdev+aliases(qwerty)"
- *      str_inout = aliases(qwerty)
- *      nextop_retrn = +
- *      extra_data = NULL
- *      file_rtrn = evdev
+ * @param map_rtrn Set to the string between '(' and ')', if any. This will
+ * result in the compilation of a specific named map within the file (e.g.
+ * xkb_symbols "basic" { ... }) , as opposed to the default map of the file.
+ *
+ * @param nextop_rtrn Set to the next operation in the complete statement,
+ * which is '\0' if it's the last file or '+' or '|' if there are more.
+ * Separating the files with '+' sets the merge mode to MERGE_MODE_OVERRIDE,
+ * while '|' sets the merge mode to MERGE_MODE_AUGMENT.
+ *
+ * @param extra_data Set to the string after ':', if any. Currently the
+ * extra data is only used for setting an explicit group index for a symbols
+ * file.
+ *
+ * @return true if parsing was successful, false for an illegal string.
+ *
+ * Example: "evdev+aliases(qwerty):2"
+ *      str_inout = "aliases(qwerty):2"
+ *      file_rtrn = "evdev"
  *      map_rtrn = NULL
+ *      nextop_retrn = "+"
+ *      extra_data = NULL
  *
- * 2nd run with "aliases(qwerty)"
+ * 2nd run with "aliases(qwerty):2"
  *      str_inout = NULL
- *      file_rtrn = aliases
- *      map_rtrn = qwerty
- *      extra_data = NULL
+ *      file_rtrn = "aliases"
+ *      map_rtrn = "qwerty"
  *      nextop_retrn = ""
+ *      extra_data = "2"
  *
  */
 bool
@@ -66,19 +106,27 @@ ParseIncludeMap(char **str_inout, char **file_rtrn, char **map_rtrn,
 
     str = *str_inout;
 
-    /* search for tokens inside the string */
+    /*
+     * Find the position in the string where the next file is included,
+     * if there is more than one left in the statement.
+     */
     next = strpbrk(str, "|+");
     if (next) {
-        /* set nextop_rtrn to \0, next to next character */
+        /* Got more files, this function will be called again. */
         *nextop_rtrn = *next;
+        /* Separate the string, for strchr etc. to work on this file only. */
         *next++ = '\0';
     }
     else {
+        /* This is the last file in this statement, won't be called again. */
         *nextop_rtrn = '\0';
         next = NULL;
     }
 
-    /* search for :, store result in extra_data */
+    /*
+     * Search for the explicit group designator, if any. If it's there,
+     * it goes after the file name and map.
+     */
     tmp = strchr(str, ':');
     if (tmp != NULL) {
         *tmp++ = '\0';
@@ -88,21 +136,25 @@ ParseIncludeMap(char **str_inout, char **file_rtrn, char **map_rtrn,
         *extra_data = NULL;
     }
 
+    /* Look for a map, if any. */
     tmp = strchr(str, '(');
     if (tmp == NULL) {
+        /* No map. */
         *file_rtrn = strdup(str);
         *map_rtrn = NULL;
     }
     else if (str[0] == '(') {
+        /* Map without file - invalid. */
         free(*extra_data);
         return false;
     }
     else {
+        /* Got a map; separate the file and the map for the strdup's. */
         *tmp++ = '\0';
         *file_rtrn = strdup(str);
         str = tmp;
         tmp = strchr(str, ')');
-        if ((tmp == NULL) || (tmp[1] != '\0')) {
+        if (tmp == NULL || tmp[1] != '\0') {
             free(*file_rtrn);
             free(*extra_data);
             return false;
@@ -111,9 +163,10 @@ ParseIncludeMap(char **str_inout, char **file_rtrn, char **map_rtrn,
         *map_rtrn = strdup(str);
     }
 
+    /* Set up the next file for the next call, if any. */
     if (*nextop_rtrn == '\0')
         *str_inout = NULL;
-    else if ((*nextop_rtrn == '|') || (*nextop_rtrn == '+'))
+    else if (*nextop_rtrn == '|' || *nextop_rtrn == '+')
         *str_inout = next;
     else
         return false;
@@ -121,7 +174,15 @@ ParseIncludeMap(char **str_inout, char **file_rtrn, char **map_rtrn,
     return true;
 }
 
-/***====================================================================***/
+static const char *xkb_file_type_include_dirs[_FILE_TYPE_NUM_ENTRIES] = {
+    [FILE_TYPE_KEYCODES] = "keycodes",
+    [FILE_TYPE_TYPES] = "types",
+    [FILE_TYPE_COMPAT] = "compat",
+    [FILE_TYPE_SYMBOLS] = "symbols",
+    [FILE_TYPE_GEOMETRY] = "geometry",
+    [FILE_TYPE_KEYMAP] = "keymap",
+    [FILE_TYPE_RULES] = "rules",
+};
 
 /**
  * Return the xkb directory based on the type.
@@ -129,155 +190,153 @@ ParseIncludeMap(char **str_inout, char **file_rtrn, char **map_rtrn,
 static const char *
 DirectoryForInclude(enum xkb_file_type type)
 {
-    switch (type) {
-    case FILE_TYPE_KEYMAP:
-        return "keymap";
-
-    case FILE_TYPE_KEYCODES:
-        return "keycodes";
-
-    case FILE_TYPE_TYPES:
-        return "types";
-
-    case FILE_TYPE_SYMBOLS:
-        return "symbols";
-
-    case FILE_TYPE_COMPAT:
-        return "compat";
+    if (type >= _FILE_TYPE_NUM_ENTRIES)
+        return "";
+    return xkb_file_type_include_dirs[type];
+}
 
-    case FILE_TYPE_GEOMETRY:
-        return "geometry";
+static void
+LogIncludePaths(struct xkb_context *ctx)
+{
+    unsigned int i;
 
-    case FILE_TYPE_RULES:
-        return "rules";
+    if (xkb_context_num_include_paths(ctx) > 0) {
+        log_err_with_code(ctx,
+                XKB_ERROR_INCLUDED_FILE_NOT_FOUND,
+                "%d include paths searched:\n",
+                xkb_context_num_include_paths(ctx));
+        for (i = 0; i < xkb_context_num_include_paths(ctx); i++)
+            log_err_with_code(ctx,
+                    XKB_ERROR_INCLUDED_FILE_NOT_FOUND,
+                    "\t%s\n",
+                    xkb_context_include_path_get(ctx, i));
+    }
+    else {
+        log_err_with_code(ctx,
+                XKB_ERROR_INCLUDED_FILE_NOT_FOUND,
+                "There are no include paths to search\n");
+    }
 
-    default:
-        return "";
+    if (xkb_context_num_failed_include_paths(ctx) > 0) {
+        log_err_with_code(ctx,
+                XKB_ERROR_INCLUDED_FILE_NOT_FOUND,
+                "%d include paths could not be added:\n",
+                xkb_context_num_failed_include_paths(ctx));
+        for (i = 0; i < xkb_context_num_failed_include_paths(ctx); i++)
+            log_err_with_code(ctx,
+                    XKB_ERROR_INCLUDED_FILE_NOT_FOUND,
+                    "\t%s\n",
+                    xkb_context_failed_include_path_get(ctx, i));
     }
 }
 
-/***====================================================================***/
-
 /**
- * Search for the given file name in the include directories.
+ * Return an open file handle to the first file (counting from offset) with the
+ * given name in the include paths, starting at the offset.
  *
- * @param ctx the XKB ctx containing the include paths
- * @param type one of FILE_TYPE_TYPES, FILE_TYPE_COMPAT, ..., or
- *             FILE_TYPE_KEYMAP or FILE_TYPE_RULES
- * @param pathRtrn is set to the full path of the file if found.
+ * offset must be zero the first time this is called and is set to the index the
+ * file was found. Call again with offset+1 to keep searching through the
+ * include paths.
  *
- * @return an FD to the file or NULL. If NULL is returned, the value of
- * pathRtrn is undefined.
+ * If this function returns NULL, no more files are available.
  */
 FILE *
 FindFileInXkbPath(struct xkb_context *ctx, const char *name,
-                  enum xkb_file_type type, char **pathRtrn)
+                  enum xkb_file_type type, char **pathRtrn,
+                  unsigned int *offset)
 {
-    size_t i;
-    int ret;
+    unsigned int i;
     FILE *file = NULL;
-    char buf[PATH_MAX];
+    char *buf = NULL;
     const char *typeDir;
 
     typeDir = DirectoryForInclude(type);
-    for (i = 0; i < xkb_context_num_include_paths(ctx); i++) {
-        ret = snprintf(buf, sizeof(buf), "%s/%s/%s",
-                       xkb_context_include_path_get(ctx, i), typeDir, name);
-        if (ret >= (ssize_t) sizeof(buf)) {
-            log_err(ctx, "File name (%s/%s/%s) too long\n",
+
+    for (i = *offset; i < xkb_context_num_include_paths(ctx); i++) {
+        buf = asprintf_safe("%s/%s/%s", xkb_context_include_path_get(ctx, i),
+                            typeDir, name);
+        if (!buf) {
+            log_err_with_code(ctx,
+                    XKB_ERROR_ALLOCATION_ERROR,
+                    "Failed to alloc buffer for (%s/%s/%s)\n",
                     xkb_context_include_path_get(ctx, i), typeDir, name);
             continue;
         }
-        file = fopen(buf, "r");
-        if (file == NULL) {
-            log_err(ctx, "Couldn't open file (%s/%s/%s): %s\n",
-                    xkb_context_include_path_get(ctx, i), typeDir, name,
-                    strerror(errno));
-            continue;
+
+        file = fopen(buf, "rb");
+        if (file) {
+            if (pathRtrn) {
+                *pathRtrn = buf;
+                buf = NULL;
+            }
+            *offset = i;
+            goto out;
         }
-        break;
     }
 
-    if ((file != NULL) && (pathRtrn != NULL))
-        *pathRtrn = strdup(buf);
+    /* We only print warnings if we can't find the file on the first lookup */
+    if (*offset == 0) {
+        log_err_with_code(ctx,
+                XKB_ERROR_INCLUDED_FILE_NOT_FOUND,
+                "Couldn't find file \"%s/%s\" in include paths\n",
+                typeDir, name);
+        LogIncludePaths(ctx);
+    }
+
+out:
+    free(buf);
     return file;
 }
 
-/**
- * Open the file given in the include statement and parse it's content.
- * If the statement defines a specific map to use, this map is returned in
- * file_rtrn. Otherwise, the default map is returned.
- *
- * @param ctx The ctx containing include paths
- * @param stmt The include statement, specifying the file name to look for.
- * @param file_type Type of file (FILE_TYPE_KEYCODES, etc.)
- * @param file_rtrn Returns the key map to be used.
- * @param merge_rtrn Always returns stmt->merge.
- *
- * @return true on success or false otherwise.
- */
-bool
-ProcessIncludeFile(struct xkb_context *ctx,
-                   IncludeStmt * stmt,
-                   enum xkb_file_type file_type,
-                   XkbFile ** file_rtrn, enum merge_mode *merge_rtrn)
+XkbFile *
+ProcessIncludeFile(struct xkb_context *ctx, IncludeStmt *stmt,
+                   enum xkb_file_type file_type)
 {
     FILE *file;
-    XkbFile *rtrn, *mapToUse, *next;
+    XkbFile *xkb_file = NULL;
+    unsigned int offset = 0;
 
-    file = FindFileInXkbPath(ctx, stmt->file, file_type, NULL);
-    if (file == NULL) {
-        log_err(ctx, "Can't find file \"%s\" for %s include\n", stmt->file,
-                DirectoryForInclude(file_type));
-        return false;
-    }
+    file = FindFileInXkbPath(ctx, stmt->file, file_type, NULL, &offset);
+    if (!file)
+        return NULL;
 
-    if (!XkbParseFile(ctx, file, stmt->file, &rtrn)) {
-        log_err(ctx, "Error interpreting include file \"%s\"\n", stmt->file);
+    while (file) {
+        xkb_file = XkbParseFile(ctx, file, stmt->file, stmt->map);
         fclose(file);
-        return false;
-    }
-    fclose(file);
 
-    mapToUse = rtrn;
-    if (stmt->map != NULL) {
-        while (mapToUse)
-        {
-            next = (XkbFile *) mapToUse->common.next;
-            mapToUse->common.next = NULL;
-            if (streq(mapToUse->name, stmt->map) &&
-                mapToUse->file_type == file_type) {
-                FreeXkbFile(next);
+        if (xkb_file) {
+            if (xkb_file->file_type != file_type) {
+                log_err_with_code(ctx,
+                        XKB_ERROR_INVALID_INCLUDED_FILE,
+                        "Include file of wrong type (expected %s, got %s); "
+                        "Include file \"%s\" ignored\n",
+                        xkb_file_type_to_string(file_type),
+                        xkb_file_type_to_string(xkb_file->file_type), stmt->file);
+                FreeXkbFile(xkb_file);
+                xkb_file = NULL;
+            } else {
                 break;
             }
-            else {
-                FreeXkbFile(mapToUse);
-            }
-            mapToUse = next;
-        }
-        if (!mapToUse) {
-            log_err(ctx, "No %s named \"%s\" in the include file \"%s\"\n",
-                    FileTypeText(file_type), stmt->map, stmt->file);
-            return false;
         }
+
+        offset++;
+        file = FindFileInXkbPath(ctx, stmt->file, file_type, NULL, &offset);
     }
-    else if (rtrn->common.next) {
-        log_lvl(ctx, 5,
-                "No map in include statement, but \"%s\" contains several; "
-                "Using first defined map, \"%s\"\n",
-                stmt->file, rtrn->name);
-    }
-    if (mapToUse->file_type != file_type) {
-        log_err(ctx,
-                "Include file wrong type (expected %s, got %s); "
-                "Include file \"%s\" ignored\n",
-                FileTypeText(file_type), FileTypeText(mapToUse->file_type),
-                stmt->file);
-        return false;
+
+    if (!xkb_file) {
+        if (stmt->map)
+            log_err_with_code(ctx,
+                    XKB_ERROR_INVALID_INCLUDED_FILE,
+                    "Couldn't process include statement for '%s(%s)'\n",
+                    stmt->file, stmt->map);
+        else
+            log_err_with_code(ctx,
+                    XKB_ERROR_INVALID_INCLUDED_FILE,
+                    "Couldn't process include statement for '%s'\n",
+                    stmt->file);
     }
+
     /* FIXME: we have to check recursive includes here (or somewhere) */
 
-    *file_rtrn = mapToUse;
-    *merge_rtrn = stmt->merge;
-    return true;
+    return xkb_file;
 }