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 <X11/Xlib.h>
28#include <X11/XKBlib.h>
29
30#define	DEBUG_VAR debugFlags
31#include "utils.h"
32#include <stdlib.h>
33#include <X11/extensions/XKM.h>
34#include "xkbpath.h"
35
36#ifndef PATH_MAX
37#define	PATH_MAX 1024
38#endif
39
40#define	PATH_CHUNK	8       /* initial szPath */
41
42static Bool noDefaultPath = False;
43static int szPath;         /* number of entries allocated for includePath */
44static int nPathEntries;   /* number of actual entries in includePath */
45static char **includePath; /* Holds all directories we might be including data from */
46
47/**
48 * Extract the first token from an include statement.
49 * @param str_inout Input statement, modified in-place. Can be passed in
50 * repeatedly. If str_inout is NULL, the parsing has completed.
51 * @param file_rtrn Set to the include file to be used.
52 * @param map_rtrn Set to whatever comes after ), if any.
53 * @param nextop_rtrn Set to the next operation in the complete statement.
54 * @param extra_data Set to the string between ( and ), if any.
55 *
56 * @return True if parsing was successful, False for an illegal string.
57 *
58 * Example: "evdev+aliases(qwerty)"
59 *      str_inout = aliases(qwerty)
60 *      nextop_retrn = +
61 *      extra_data = NULL
62 *      file_rtrn = evdev
63 *      map_rtrn = NULL
64 *
65 * 2nd run with "aliases(qwerty)"
66 *      str_inout = NULL
67 *      file_rtrn = aliases
68 *      map_rtrn = qwerty
69 *      extra_data = NULL
70 *      nextop_retrn = ""
71 *
72 */
73Bool
74XkbParseIncludeMap(char **str_inout, char **file_rtrn, char **map_rtrn,
75                   char *nextop_rtrn, char **extra_data)
76{
77    char *tmp, *str, *next;
78
79    str = *str_inout;
80    if ((*str == '+') || (*str == '|'))
81    {
82        *file_rtrn = *map_rtrn = NULL;
83        *nextop_rtrn = *str;
84        next = str + 1;
85    }
86    else if (*str == '%')
87    {
88        *file_rtrn = *map_rtrn = NULL;
89        *nextop_rtrn = str[1];
90        next = str + 2;
91    }
92    else
93    {
94        /* search for tokens inside the string */
95        next = strpbrk(str, "|+");
96        if (next)
97        {
98            /* set nextop_rtrn to \0, next to next character */
99            *nextop_rtrn = *next;
100            *next++ = '\0';
101        }
102        else
103        {
104            *nextop_rtrn = '\0';
105            next = NULL;
106        }
107        /* search for :, store result in extra_data */
108        tmp = strchr(str, ':');
109        if (tmp != NULL)
110        {
111            *tmp++ = '\0';
112            *extra_data = uStringDup(tmp);
113        }
114        else
115        {
116            *extra_data = NULL;
117        }
118        tmp = strchr(str, '(');
119        if (tmp == NULL)
120        {
121            *file_rtrn = uStringDup(str);
122            *map_rtrn = NULL;
123        }
124        else if (str[0] == '(')
125        {
126            free(*extra_data);
127            return False;
128        }
129        else
130        {
131            *tmp++ = '\0';
132            *file_rtrn = uStringDup(str);
133            str = tmp;
134            tmp = strchr(str, ')');
135            if ((tmp == NULL) || (tmp[1] != '\0'))
136            {
137                free(*file_rtrn);
138                free(*extra_data);
139                return False;
140            }
141            *tmp++ = '\0';
142            *map_rtrn = uStringDup(str);
143        }
144    }
145    if (*nextop_rtrn == '\0')
146        *str_inout = NULL;
147    else if ((*nextop_rtrn == '|') || (*nextop_rtrn == '+'))
148        *str_inout = next;
149    else
150        return False;
151    return True;
152}
153
154/**
155 * Init memory for include paths.
156 */
157Bool
158XkbInitIncludePath(void)
159{
160    szPath = PATH_CHUNK;
161    includePath = (char **) calloc(szPath, sizeof(char *));
162    if (includePath == NULL)
163        return False;
164    return True;
165}
166
167void
168XkbAddDefaultDirectoriesToPath(void)
169{
170    if (noDefaultPath)
171        return;
172    XkbAddDirectoryToPath(DFLT_XKB_CONFIG_ROOT);
173}
174
175/**
176 * Remove all entries from the global includePath.
177 */
178static void
179XkbClearIncludePath(void)
180{
181    if (szPath > 0)
182    {
183        for (int i = 0; i < nPathEntries; i++)
184        {
185            free(includePath[i]);
186            includePath[i] = NULL;
187        }
188        nPathEntries = 0;
189    }
190    noDefaultPath = True;
191    return;
192}
193
194/**
195 * Add the given path to the global includePath variable.
196 * If dir is NULL, the includePath is emptied.
197 */
198Bool
199XkbAddDirectoryToPath(const char *dir)
200{
201    int len;
202    if ((dir == NULL) || (dir[0] == '\0'))
203    {
204        XkbClearIncludePath();
205        return True;
206    }
207    len = strlen(dir);
208    if (len + 2 >= PATH_MAX)
209    {                           /* allow for '/' and at least one character */
210        ERROR("Path entry (%s) too long (maximum length is %d)\n",
211               dir, PATH_MAX - 3);
212        return False;
213    }
214    if (nPathEntries >= szPath)
215    {
216        char **new;
217        szPath += PATH_CHUNK;
218        new = (char **) realloc(includePath, szPath * sizeof(char *));
219        if (new == NULL)
220        {
221            WSGO("Allocation failed (includePath)\n");
222            return False;
223        }
224        else
225            includePath = new;
226    }
227    includePath[nPathEntries] = strdup(dir);
228    if (includePath[nPathEntries] == NULL)
229    {
230        WSGO("Allocation failed (includePath[%d])\n", nPathEntries);
231        return False;
232    }
233    nPathEntries++;
234    return True;
235}
236
237/***====================================================================***/
238
239/**
240 * Return the xkb directory based on the type.
241 * Do not free the memory returned by this function.
242 */
243char *
244XkbDirectoryForInclude(unsigned type)
245{
246    static char buf[32];
247
248    switch (type)
249    {
250    case XkmSemanticsFile:
251        strcpy(buf, "semantics");
252        break;
253    case XkmLayoutFile:
254        strcpy(buf, "layout");
255        break;
256    case XkmKeymapFile:
257        strcpy(buf, "keymap");
258        break;
259    case XkmKeyNamesIndex:
260        strcpy(buf, "keycodes");
261        break;
262    case XkmTypesIndex:
263        strcpy(buf, "types");
264        break;
265    case XkmSymbolsIndex:
266        strcpy(buf, "symbols");
267        break;
268    case XkmCompatMapIndex:
269        strcpy(buf, "compat");
270        break;
271    case XkmGeometryFile:
272    case XkmGeometryIndex:
273        strcpy(buf, "geometry");
274        break;
275    default:
276        strcpy(buf, "");
277        break;
278    }
279    return buf;
280}
281
282/***====================================================================***/
283
284typedef struct _FileCacheEntry
285{
286    const char *name;
287    unsigned type;
288    char *path;
289    void *data;
290    struct _FileCacheEntry *next;
291} FileCacheEntry;
292static FileCacheEntry *fileCache;
293
294/**
295 * Add the file with the given name to the internal cache to avoid opening and
296 * parsing the file multiple times. If a cache entry for the same name + type
297 * is already present, the entry is overwritten and the data belonging to the
298 * previous entry is returned.
299 *
300 * @parameter name The name of the file (e.g. evdev).
301 * @parameter type Type of the file (XkbTypesIdx, ... or XkbSemanticsFile, ...)
302 * @parameter path The full path to the file.
303 * @parameter data Already parsed data.
304 *
305 * @return The data from the overwritten file or NULL.
306 */
307void *
308XkbAddFileToCache(const char *name, unsigned type, char *path, void *data)
309{
310    FileCacheEntry *entry;
311
312    for (entry = fileCache; entry != NULL; entry = entry->next)
313    {
314        if ((type == entry->type) && (uStringEqual(name, entry->name)))
315        {
316            void *old = entry->data;
317            WSGO("Replacing file cache entry (%s/%d)\n", name, type);
318            entry->path = path;
319            entry->data = data;
320            return old;
321        }
322    }
323    entry = malloc(sizeof(FileCacheEntry));
324    if (entry != NULL)
325    {
326        *entry = (FileCacheEntry) {
327            .name = name,
328            .type = type,
329            .path = path,
330            .data = data,
331            .next = fileCache
332        };
333        fileCache = entry;
334    }
335    return NULL;
336}
337
338/**
339 * Search for the given name + type in the cache.
340 *
341 * @parameter name The name of the file (e.g. evdev).
342 * @parameter type Type of the file (XkbTypesIdx, ... or XkbSemanticsFile, ...)
343 * @parameter pathRtrn Set to the full path of the given entry.
344 *
345 * @return the data from the cache entry or NULL if no matching entry was found.
346 */
347void *
348XkbFindFileInCache(const char *name, unsigned type, char **pathRtrn)
349{
350    FileCacheEntry *entry;
351
352    for (entry = fileCache; entry != NULL; entry = entry->next)
353    {
354        if ((type == entry->type) && (uStringEqual(name, entry->name)))
355        {
356            *pathRtrn = entry->path;
357            return entry->data;
358        }
359    }
360    return NULL;
361}
362
363/***====================================================================***/
364
365/**
366 * Search for the given file name in the include directories.
367 *
368 * @param type one of XkbTypesIndex, XkbCompatMapIndex, ..., or
369 * XkbSemanticsFile, XkmKeymapFile, ...
370 * @param pathReturn is set to the full path of the file if found.
371 *
372 * @return an FD to the file or NULL. If NULL is returned, the value of
373 * pathRtrn is undefined.
374 */
375FILE *
376XkbFindFileInPath(const char *name, unsigned type, char **pathRtrn)
377{
378    FILE *file = NULL;
379    int nameLen, typeLen;
380    char buf[PATH_MAX];
381    const char *typeDir;
382
383    typeDir = XkbDirectoryForInclude(type);
384    nameLen = strlen(name);
385    typeLen = strlen(typeDir);
386    for (int i = 0; i < nPathEntries; i++)
387    {
388        int pathLen = strlen(includePath[i]);
389        if (typeLen < 1)
390            continue;
391
392        if ((nameLen + typeLen + pathLen + 2) >= PATH_MAX)
393        {
394            ERROR("File name (%s/%s/%s) too long\n", includePath[i],
395                   typeDir, name);
396            ACTION("Ignored\n");
397            continue;
398        }
399        snprintf(buf, sizeof(buf), "%s/%s/%s", includePath[i], typeDir, name);
400        file = fopen(buf, "r");
401        if (file != NULL)
402            break;
403    }
404
405    if ((file != NULL) && (pathRtrn != NULL))
406    {
407        *pathRtrn = strdup(buf);
408    }
409    return file;
410}
411