include: properly use the default map if none is found
[platform/upstream/libxkbcommon.git] / src / xkbcomp / include.c
1 /************************************************************
2  * Copyright (c) 1994 by Silicon Graphics Computer Systems, Inc.
3  *
4  * Permission to use, copy, modify, and distribute this
5  * software and its documentation for any purpose and without
6  * fee is hereby granted, provided that the above copyright
7  * notice appear in all copies and that both that copyright
8  * notice and this permission notice appear in supporting
9  * documentation, and that the name of Silicon Graphics not be
10  * used in advertising or publicity pertaining to distribution
11  * of the software without specific prior written permission.
12  * Silicon Graphics makes no representation about the suitability
13  * of this software for any purpose. It is provided "as is"
14  * without any express or implied warranty.
15  *
16  * SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
17  * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
18  * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
19  * GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
20  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
22  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
23  * THE USE OR PERFORMANCE OF THIS SOFTWARE.
24  *
25  ********************************************************/
26
27 #include <errno.h>
28 #include <limits.h>
29 #include <stdio.h>
30
31 #include "xkbcomp-priv.h"
32 #include "include.h"
33
34 /**
35  * Extract the first token from an include statement.
36  * @param str_inout Input statement, modified in-place. Can be passed in
37  * repeatedly. If str_inout is NULL, the parsing has completed.
38  * @param file_rtrn Set to the include file to be used.
39  * @param map_rtrn Set to whatever comes after ), if any.
40  * @param nextop_rtrn Set to the next operation in the complete statement.
41  * @param extra_data Set to the string between ( and ), if any.
42  *
43  * @return true if parsing was succcessful, false for an illegal string.
44  *
45  * Example: "evdev+aliases(qwerty)"
46  *      str_inout = aliases(qwerty)
47  *      nextop_retrn = +
48  *      extra_data = NULL
49  *      file_rtrn = evdev
50  *      map_rtrn = NULL
51  *
52  * 2nd run with "aliases(qwerty)"
53  *      str_inout = NULL
54  *      file_rtrn = aliases
55  *      map_rtrn = qwerty
56  *      extra_data = NULL
57  *      nextop_retrn = ""
58  *
59  */
60 bool
61 ParseIncludeMap(char **str_inout, char **file_rtrn, char **map_rtrn,
62                 char *nextop_rtrn, char **extra_data)
63 {
64     char *tmp, *str, *next;
65
66     str = *str_inout;
67
68     /* search for tokens inside the string */
69     next = strpbrk(str, "|+");
70     if (next) {
71         /* set nextop_rtrn to \0, next to next character */
72         *nextop_rtrn = *next;
73         *next++ = '\0';
74     }
75     else {
76         *nextop_rtrn = '\0';
77         next = NULL;
78     }
79
80     /* search for :, store result in extra_data */
81     tmp = strchr(str, ':');
82     if (tmp != NULL) {
83         *tmp++ = '\0';
84         *extra_data = strdup(tmp);
85     }
86     else {
87         *extra_data = NULL;
88     }
89
90     tmp = strchr(str, '(');
91     if (tmp == NULL) {
92         *file_rtrn = strdup(str);
93         *map_rtrn = NULL;
94     }
95     else if (str[0] == '(') {
96         free(*extra_data);
97         return false;
98     }
99     else {
100         *tmp++ = '\0';
101         *file_rtrn = strdup(str);
102         str = tmp;
103         tmp = strchr(str, ')');
104         if ((tmp == NULL) || (tmp[1] != '\0')) {
105             free(*file_rtrn);
106             free(*extra_data);
107             return false;
108         }
109         *tmp++ = '\0';
110         *map_rtrn = strdup(str);
111     }
112
113     if (*nextop_rtrn == '\0')
114         *str_inout = NULL;
115     else if ((*nextop_rtrn == '|') || (*nextop_rtrn == '+'))
116         *str_inout = next;
117     else
118         return false;
119
120     return true;
121 }
122
123 /***====================================================================***/
124
125 static const char *xkb_file_type_include_dirs[_FILE_TYPE_NUM_ENTRIES] = {
126     [FILE_TYPE_KEYCODES] = "keycodes",
127     [FILE_TYPE_TYPES] = "types",
128     [FILE_TYPE_COMPAT] = "compat",
129     [FILE_TYPE_SYMBOLS] = "symbols",
130     [FILE_TYPE_GEOMETRY] = "geometry",
131     [FILE_TYPE_KEYMAP] = "keymap",
132     [FILE_TYPE_RULES] = "rules",
133 };
134
135 /**
136  * Return the xkb directory based on the type.
137  */
138 static const char *
139 DirectoryForInclude(enum xkb_file_type type)
140 {
141     if (type >= _FILE_TYPE_NUM_ENTRIES)
142         return "";
143     return xkb_file_type_include_dirs[type];
144 }
145
146 /***====================================================================***/
147
148 /**
149  * Search for the given file name in the include directories.
150  *
151  * @param ctx the XKB ctx containing the include paths
152  * @param type one of FILE_TYPE_TYPES, FILE_TYPE_COMPAT, ..., or
153  *             FILE_TYPE_KEYMAP or FILE_TYPE_RULES
154  * @param pathRtrn is set to the full path of the file if found.
155  *
156  * @return an FD to the file or NULL. If NULL is returned, the value of
157  * pathRtrn is undefined.
158  */
159 FILE *
160 FindFileInXkbPath(struct xkb_context *ctx, const char *name,
161                   enum xkb_file_type type, char **pathRtrn)
162 {
163     size_t i;
164     int ret;
165     FILE *file = NULL;
166     char buf[PATH_MAX];
167     const char *typeDir;
168
169     typeDir = DirectoryForInclude(type);
170     for (i = 0; i < xkb_context_num_include_paths(ctx); i++) {
171         ret = snprintf(buf, sizeof(buf), "%s/%s/%s",
172                        xkb_context_include_path_get(ctx, i), typeDir, name);
173         if (ret >= (ssize_t) sizeof(buf)) {
174             log_err(ctx, "File name (%s/%s/%s) too long\n",
175                     xkb_context_include_path_get(ctx, i), typeDir, name);
176             continue;
177         }
178         file = fopen(buf, "r");
179         if (file == NULL) {
180             log_err(ctx, "Couldn't open file (%s/%s/%s): %s\n",
181                     xkb_context_include_path_get(ctx, i), typeDir, name,
182                     strerror(errno));
183             continue;
184         }
185         break;
186     }
187
188     if ((file != NULL) && (pathRtrn != NULL))
189         *pathRtrn = strdup(buf);
190     return file;
191 }
192
193 /**
194  * Open the file given in the include statement and parse it's content.
195  * If the statement defines a specific map to use, this map is returned in
196  * file_rtrn. Otherwise, the default map is returned.
197  *
198  * @param ctx The ctx containing include paths
199  * @param stmt The include statement, specifying the file name to look for.
200  * @param file_type Type of file (FILE_TYPE_KEYCODES, etc.)
201  * @param file_rtrn Returns the key map to be used.
202  * @param merge_rtrn Always returns stmt->merge.
203  *
204  * @return true on success or false otherwise.
205  */
206 bool
207 ProcessIncludeFile(struct xkb_context *ctx,
208                    IncludeStmt * stmt,
209                    enum xkb_file_type file_type,
210                    XkbFile ** file_rtrn, enum merge_mode *merge_rtrn)
211 {
212     FILE *file;
213     XkbFile *rtrn, *mapToUse, *next;
214
215     file = FindFileInXkbPath(ctx, stmt->file, file_type, NULL);
216     if (file == NULL) {
217         log_err(ctx, "Can't find file \"%s\" for %s include\n", stmt->file,
218                 DirectoryForInclude(file_type));
219         return false;
220     }
221
222     if (!XkbParseFile(ctx, file, stmt->file, &rtrn)) {
223         log_err(ctx, "Error interpreting include file \"%s\"\n", stmt->file);
224         fclose(file);
225         return false;
226     }
227     fclose(file);
228
229     mapToUse = rtrn;
230     if (stmt->map != NULL) {
231         while (mapToUse)
232         {
233             next = (XkbFile *) mapToUse->common.next;
234             mapToUse->common.next = NULL;
235             if (streq(mapToUse->name, stmt->map) &&
236                 mapToUse->file_type == file_type) {
237                 FreeXkbFile(next);
238                 break;
239             }
240             else {
241                 FreeXkbFile(mapToUse);
242             }
243             mapToUse = next;
244         }
245         if (!mapToUse) {
246             log_err(ctx, "No %s named \"%s\" in the include file \"%s\"\n",
247                     xkb_file_type_to_string(file_type), stmt->map,
248                     stmt->file);
249             return false;
250         }
251     }
252     else if (rtrn->common.next) {
253         for (; mapToUse; mapToUse = (XkbFile *) mapToUse->common.next)
254             if (mapToUse->flags & XkbLC_Default)
255                 break;
256
257         if (!mapToUse) {
258             mapToUse = rtrn;
259             log_vrb(ctx, 5,
260                     "No map in include statement, but \"%s\" contains several; "
261                     "Using first defined map, \"%s\"\n",
262                     stmt->file, rtrn->name);
263         }
264     }
265
266     if (mapToUse->file_type != file_type) {
267         log_err(ctx,
268                 "Include file wrong type (expected %s, got %s); "
269                 "Include file \"%s\" ignored\n",
270                 xkb_file_type_to_string(file_type),
271                 xkb_file_type_to_string(mapToUse->file_type), stmt->file);
272         return false;
273     }
274
275     /* FIXME: we have to check recursive includes here (or somewhere) */
276
277     *file_rtrn = mapToUse;
278     *merge_rtrn = stmt->merge;
279     return true;
280 }