From 006ba4e5579c8948a1a445ca84c5d3d972831e6d Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Mon, 16 Sep 2024 16:09:11 +0200 Subject: [PATCH] rules: Resolve relative include statements using XKB paths Contrary to keymap files, the `! include` statement in rules does not lookup include paths added to `xkb_context`. So it is not possible e.g. to import another file in the same folder without using an absolute path. - Added path utils: `is_absolute(path)`. - Added XKB paths lookup to enable e.g. `! include evdev` to work. - Added test. --- changes/api/501.rules-includes.feature.md | 1 + meson.build | 4 +- src/utils-paths.c | 50 +++++++++++++++++++++++ src/utils-paths.h | 16 ++++++++ src/xkbcomp/rules.c | 34 +++++++++++---- test/data/rules/inc-src-relative-path | 4 ++ test/rules-file-includes.c | 10 +++++ test/utils.c | 38 +++++++++++++++-- 8 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 changes/api/501.rules-includes.feature.md create mode 100644 src/utils-paths.c create mode 100644 src/utils-paths.h create mode 100644 test/data/rules/inc-src-relative-path diff --git a/changes/api/501.rules-includes.feature.md b/changes/api/501.rules-includes.feature.md new file mode 100644 index 000000000..9499b6a67 --- /dev/null +++ b/changes/api/501.rules-includes.feature.md @@ -0,0 +1 @@ +rules: Use XKB paths to resolve relative paths in include statements. diff --git a/meson.build b/meson.build index ff3a731d7..d97962908 100644 --- a/meson.build +++ b/meson.build @@ -255,9 +255,11 @@ libxkbcommon_sources = [ 'src/text.h', 'src/utf8.c', 'src/utf8.h', - 'src/util-mem.h', 'src/utils.c', 'src/utils.h', + 'src/util-mem.h', + 'src/utils-paths.c', + 'src/utils-paths.h', ] libxkbcommon_link_args = [] libxkbcommon_link_deps = [] diff --git a/src/utils-paths.c b/src/utils-paths.c new file mode 100644 index 000000000..d443c8a9d --- /dev/null +++ b/src/utils-paths.c @@ -0,0 +1,50 @@ +/* + * Copyright © 2024 Pierre Le Marre + * + * 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 "utils.h" +#include "utils-paths.h" + + +bool +is_absolute(const char *path) +{ + const size_t len = strlen_safe(path); +#ifdef _WIN32 + /* + * A file name is relative to the current directory if it does not begin with + * one of the following: + * - A UNC name of any format, which always start with two backslash characters ("\\"). + * - A disk designator with a backslash, for example "C:\" or "d:\". + * - A single backslash, for example, "\directory" or "\file.txt". + * See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file + */ + return len >= 1 && + ((path[0] == PATH_SEPARATOR || path[0] == ALT_PATH_SEPARATOR) || + (len >= 3 && path[1] == ':' && + (path[2] == PATH_SEPARATOR || path[2] == ALT_PATH_SEPARATOR))); +#else + return len >= 1 && path[0] == PATH_SEPARATOR; +#endif +} diff --git a/src/utils-paths.h b/src/utils-paths.h new file mode 100644 index 000000000..060da7513 --- /dev/null +++ b/src/utils-paths.h @@ -0,0 +1,16 @@ +#ifndef UTILS_PATHS_H +#define UTILS_PATHS_H + +#include + +#ifdef _WIN32 +#define PATH_SEPARATOR '\\' +#define ALT_PATH_SEPARATOR '/' +#else +#define PATH_SEPARATOR '/' +#endif + +bool +is_absolute(const char *path); + +#endif diff --git a/src/xkbcomp/rules.c b/src/xkbcomp/rules.c index 4c5cb2802..cbdffb8b7 100644 --- a/src/xkbcomp/rules.c +++ b/src/xkbcomp/rules.c @@ -53,6 +53,7 @@ #include "rules.h" #include "include.h" #include "scanner-utils.h" +#include "utils-paths.h" #define MAX_INCLUDE_DEPTH 5 @@ -369,7 +370,6 @@ matcher_include(struct matcher *m, struct scanner *parent_scanner, struct sval inc) { struct scanner s; /* parses the !include value */ - FILE *file; scanner_init(&s, m->ctx, inc.start, inc.len, parent_scanner->file_name, NULL); @@ -383,6 +383,7 @@ matcher_include(struct matcher *m, struct scanner *parent_scanner, return; } + /* Proceed to %-expansion */ while (!scanner_eof(&s) && !scanner_eol(&s)) { if (scanner_chr(&s, '%')) { if (scanner_chr(&s, '%')) { @@ -427,19 +428,34 @@ matcher_include(struct matcher *m, struct scanner *parent_scanner, return; } - file = fopen(s.buf, "rb"); - if (file) { + /* Lookup rules file in XKB paths only if the include path is relative */ + unsigned int offset = 0; + FILE *file; + bool absolute_path = is_absolute(s.buf); + if (absolute_path) + file = fopen(s.buf, "rb"); + else + file = FindFileInXkbPath(m->ctx, s.buf, FILE_TYPE_RULES, NULL, &offset); + + while (file) { bool ret = read_rules_file(m->ctx, m, include_depth + 1, file, s.buf); - if (!ret) - log_err(m->ctx, XKB_LOG_MESSAGE_NO_ID, - "No components returned from included XKB rules \"%s\"\n", - s.buf); fclose(file); - } else { + if (ret) + return; + /* Failed to parse rules or get all the components */ log_err(m->ctx, XKB_LOG_MESSAGE_NO_ID, - "Failed to open included XKB rules \"%s\"\n", + "No components returned from included XKB rules \"%s\"\n", s.buf); + if (absolute_path) + break; + /* Try next XKB path */ + offset++; + file = FindFileInXkbPath(m->ctx, s.buf, FILE_TYPE_RULES, NULL, &offset); } + + log_err(m->ctx, XKB_LOG_MESSAGE_NO_ID, + "Failed to open included XKB rules \"%s\"\n", + s.buf); } static void diff --git a/test/data/rules/inc-src-relative-path b/test/data/rules/inc-src-relative-path new file mode 100644 index 000000000..9d142676e --- /dev/null +++ b/test/data/rules/inc-src-relative-path @@ -0,0 +1,4 @@ +! layout = symbols + my_layout = my_symbols + +! include inc-dst-simple diff --git a/test/rules-file-includes.c b/test/rules-file-includes.c index 86750aeb2..743b40f4a 100644 --- a/test/rules-file-includes.c +++ b/test/rules-file-includes.c @@ -167,6 +167,16 @@ main(int argc, char *argv[]) }; assert(test_rules(ctx, &test7)); + struct test_data test8 = { + .rules = "inc-src-relative-path", + + .model = "my_model", .layout = "my_layout", .variant = "", .options = "", + + .keycodes = "my_keycodes", .types = "default_types", + .compat = "default_compat", .symbols = "my_symbols", + }; + assert(test_rules(ctx, &test8)); + xkb_context_unref(ctx); return 0; } diff --git a/test/utils.c b/test/utils.c index 444422002..362ff20fc 100644 --- a/test/utils.c +++ b/test/utils.c @@ -30,14 +30,13 @@ #include "test.h" #include "utils.h" +#include "utils-paths.h" -int -main(void) +static void +test_string_functions(void) { char buffer[10]; - test_init(); - assert(!snprintf_safe(buffer, 0, "foo")); assert(!snprintf_safe(buffer, 1, "foo")); assert(!snprintf_safe(buffer, 3, "foo")); @@ -53,6 +52,37 @@ main(void) assert(!streq_null("foobar", NULL)); assert(!streq_null(NULL, "foobar")); assert(streq_null(NULL, NULL)); +} + +static void +test_path_functions(void) +{ + /* Absolute paths */ +#ifdef _WIN32 + assert(!is_absolute("path\\test")); + assert(is_absolute("c:\\test")); + assert(!is_absolute("c:test")); + assert(is_absolute("c:\\")); + assert(is_absolute("c:/")); + assert(!is_absolute("c:")); + assert(is_absolute("\\\\foo")); + assert(is_absolute("\\\\?\\foo")); + assert(is_absolute("\\\\?\\UNC\\foo")); + assert(is_absolute("/foo")); + assert(is_absolute("\\foo")); +#else + assert(!is_absolute("test/path")); + assert(is_absolute("/test" )); + assert(is_absolute("/" )); +#endif +} + +int +main(void) +{ + test_init(); + test_string_functions(); + test_path_functions(); return 0; }