From b5f3d88e6fdd42424aaa96ff91c0588b721d3915 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 12 Apr 2022 15:32:38 +0200 Subject: [PATCH] glib-aux: add path-utils from systemd We use these functions, currently from our systemd fork. One day we want to stop importing systemd code, so we need them ourselves. Copy them, and adjust for NM style. --- src/libnm-glib-aux/nm-shared-utils.c | 242 ++++++++++++++ src/libnm-glib-aux/nm-shared-utils.h | 33 ++ .../tests/test-shared-general.c | 310 ++++++++++++++++++ 3 files changed, 585 insertions(+) diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c index 310609d2ac..9446cce80c 100644 --- a/src/libnm-glib-aux/nm-shared-utils.c +++ b/src/libnm-glib-aux/nm-shared-utils.c @@ -6933,3 +6933,245 @@ on_failure: nm_explicit_bzero(buf, len); return r; } + +/*****************************************************************************/ + +static const char * +skip_slash_or_dot(const char *p) +{ + for (; !nm_str_is_empty(p);) { + if (p[0] == '/') { + p += 1; + continue; + } + if (p[0] == '.' && p[1] == '/') { + p += 2; + continue; + } + break; + } + return p; +} + +int +nm_path_find_first_component(const char **p, gboolean accept_dot_dot, const char **ret) +{ + const char *q, *first, *end_first, *next; + size_t len; + + /* Copied from systemd's path_compare() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L809 */ + + nm_assert(p); + + /* When a path is input, then returns the pointer to the first component and its length, and + * move the input pointer to the next component or nul. This skips both over any '/' + * immediately *before* and *after* the first component before returning. + * + * Examples + * Input: p: "//.//aaa///bbbbb/cc" + * Output: p: "bbbbb///cc" + * ret: "aaa///bbbbb/cc" + * return value: 3 (== strlen("aaa")) + * + * Input: p: "aaa//" + * Output: p: (pointer to NUL) + * ret: "aaa//" + * return value: 3 (== strlen("aaa")) + * + * Input: p: "/", ".", "" + * Output: p: (pointer to NUL) + * ret: NULL + * return value: 0 + * + * Input: p: NULL + * Output: p: NULL + * ret: NULL + * return value: 0 + * + * Input: p: "(too long component)" + * Output: return value: -EINVAL + * + * (when accept_dot_dot is false) + * Input: p: "//..//aaa///bbbbb/cc" + * Output: return value: -EINVAL + */ + + q = *p; + + first = skip_slash_or_dot(q); + if (nm_str_is_empty(first)) { + *p = first; + if (ret) + *ret = NULL; + return 0; + } + if (nm_streq(first, ".")) { + *p = first + 1; + if (ret) + *ret = NULL; + return 0; + } + + end_first = strchrnul(first, '/'); + len = end_first - first; + + if (len > NAME_MAX) + return -EINVAL; + if (!accept_dot_dot && len == 2 && first[0] == '.' && first[1] == '.') + return -EINVAL; + + next = skip_slash_or_dot(end_first); + + *p = next + (nm_streq(next, ".") ? 1 : 0); + if (ret) + *ret = first; + return len; +} + +int +nm_path_compare(const char *a, const char *b) +{ + /* Copied from systemd's path_compare() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L415 */ + + /* Order NULL before non-NULL */ + NM_CMP_SELF(a, b); + + /* A relative path and an absolute path must not compare as equal. + * Which one is sorted before the other does not really matter. + * Here a relative path is ordered before an absolute path. */ + NM_CMP_DIRECT(nm_path_is_absolute(a), nm_path_is_absolute(b)); + + for (;;) { + const char *aa, *bb; + int j, k; + + j = nm_path_find_first_component(&a, TRUE, &aa); + k = nm_path_find_first_component(&b, TRUE, &bb); + + if (j < 0 || k < 0) { + /* When one of paths is invalid, order invalid path after valid one. */ + NM_CMP_DIRECT(j < 0, k < 0); + + /* fallback to use strcmp() if both paths are invalid. */ + NM_CMP_DIRECT_STRCMP(a, b); + return 0; + } + + /* Order prefixes first: "/foo" before "/foo/bar" */ + if (j == 0) { + if (k == 0) + return 0; + return -1; + } + if (k == 0) + return 1; + + /* Alphabetical sort: "/foo/aaa" before "/foo/b" */ + NM_CMP_DIRECT_MEMCMP(aa, bb, NM_MIN(j, k)); + + /* Sort "/foo/a" before "/foo/aaa" */ + NM_CMP_DIRECT(j, k); + } +} + +char * +nm_path_startswith_full(const char *path, const char *prefix, gboolean accept_dot_dot) +{ + /* Copied from systemd's path_startswith_full() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L375 */ + + nm_assert(path); + nm_assert(prefix); + + /* Returns a pointer to the start of the first component after the parts matched by + * the prefix, iff + * - both paths are absolute or both paths are relative, + * and + * - each component in prefix in turn matches a component in path at the same position. + * An empty string will be returned when the prefix and path are equivalent. + * + * Returns NULL otherwise. + */ + + if ((path[0] == '/') != (prefix[0] == '/')) + return NULL; + + for (;;) { + const char *p, *q; + int r, k; + + r = nm_path_find_first_component(&path, accept_dot_dot, &p); + if (r < 0) + return NULL; + + k = nm_path_find_first_component(&prefix, accept_dot_dot, &q); + if (k < 0) + return NULL; + + if (k == 0) + return (char *) (p ?: path); + + if (r != k) + return NULL; + + if (strncmp(p, q, r) != 0) + return NULL; + } +} + +char * +nm_path_simplify(char *path) +{ + bool add_slash = false; + char *f = path; + int r; + + /* Copied from systemd's path_simplify() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.c#L325 */ + + nm_assert(path); + + /* Removes redundant inner and trailing slashes. Also removes unnecessary dots. + * Modifies the passed string in-place. + * + * ///foo//./bar/. becomes /foo/bar + * .//./foo//./bar/. becomes foo/bar + */ + + if (path[0] == '\0') + return path; + + if (nm_path_is_absolute(path)) + f++; + + for (const char *p = f;;) { + const char *e; + + r = nm_path_find_first_component(&p, TRUE, &e); + if (r == 0) + break; + + if (add_slash) + *f++ = '/'; + + if (r < 0) { + /* if path is invalid, then refuse to simplify remaining part. */ + memmove(f, p, strlen(p) + 1); + return path; + } + + memmove(f, e, r); + f += r; + + add_slash = TRUE; + } + + /* Special rule, if we stripped everything, we need a "." for the current directory. */ + if (f == path) + *f++ = '.'; + + *f = '\0'; + return path; +} diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index 959a2b5d24..34936e0a36 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -3313,4 +3313,37 @@ void nm_utils_thread_local_register_destroy(gpointer tls_data, GDestroyNotify de int nm_unbase64char(char c); int nm_unbase64mem_full(const char *p, gsize l, gboolean secure, guint8 **ret, gsize *ret_size); +/*****************************************************************************/ + +static inline gboolean +nm_path_is_absolute(const char *p) +{ + /* Copied from systemd's path_is_absolute() + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.h#L50 */ + + nm_assert(p); + return p[0] == '/'; +} + +int nm_path_find_first_component(const char **p, gboolean accept_dot_dot, const char **ret); + +int nm_path_compare(const char *a, const char *b); + +static inline gboolean +nm_path_equal(const char *a, const char *b) +{ + return nm_path_compare(a, b) == 0; +} + +char *nm_path_simplify(char *path); + +char * +nm_path_startswith_full(const char *path, const char *prefix, gboolean accept_dot_dot) _nm_pure; + +static inline char * +nm_path_startswith(const char *path, const char *prefix) +{ + return nm_path_startswith_full(path, prefix, TRUE); +} + #endif /* __NM_SHARED_UTILS_H__ */ diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c index 783b771a0d..c3fa2559ed 100644 --- a/src/libnm-glib-aux/tests/test-shared-general.c +++ b/src/libnm-glib-aux/tests/test-shared-general.c @@ -1796,6 +1796,311 @@ test_unbase64mem3(void) /*****************************************************************************/ +static void +assert_path_compare(const char *a, const char *b, int expected) +{ + int r; + + g_assert(NM_IN_SET(expected, -1, 0, 1)); + + g_assert_cmpint(nm_path_compare(a, a), ==, 0); + g_assert_cmpint(nm_path_compare(b, b), ==, 0); + + r = nm_path_compare(a, b); + g_assert_cmpint(r, ==, expected); + r = nm_path_compare(b, a); + g_assert_cmpint(r, ==, -expected); + + g_assert(nm_path_equal(a, a) == 1); + g_assert(nm_path_equal(b, b) == 1); + g_assert(nm_path_equal(a, b) == (expected == 0)); + g_assert(nm_path_equal(b, a) == (expected == 0)); +} + +static void +test_path_compare(void) +{ + /* Copied from systemd. + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/test/test-path-util.c#L126 */ + + assert_path_compare("/goo", "/goo", 0); + assert_path_compare("/goo", "/goo", 0); + assert_path_compare("//goo", "/goo", 0); + assert_path_compare("//goo/////", "/goo", 0); + assert_path_compare("goo/////", "goo", 0); + assert_path_compare("/goo/boo", "/goo//boo", 0); + assert_path_compare("//goo/boo", "/goo/boo//", 0); + assert_path_compare("//goo/././//./boo//././//", "/goo/boo//.", 0); + assert_path_compare("/.", "//.///", 0); + assert_path_compare("/x", "x/", 1); + assert_path_compare("x/", "/", -1); + assert_path_compare("/x/./y", "x/y", 1); + assert_path_compare("/x/./y", "/x/y", 0); + assert_path_compare("/x/./././y", "/x/y/././.", 0); + assert_path_compare("./x/./././y", "./x/y/././.", 0); + assert_path_compare(".", "./.", 0); + assert_path_compare(".", "././.", 0); + assert_path_compare("./..", ".", 1); + assert_path_compare("x/.y", "x/y", -1); + assert_path_compare("foo", "/foo", -1); + assert_path_compare("/foo", "/foo/bar", -1); + assert_path_compare("/foo/aaa", "/foo/b", -1); + assert_path_compare("/foo/aaa", "/foo/b/a", -1); + assert_path_compare("/foo/a", "/foo/aaa", -1); + assert_path_compare("/foo/a/b", "/foo/aaa", -1); +} + +/*****************************************************************************/ + +static void +test_path_equal(void) +{ +#define _path_equal_check(path, expected) \ + G_STMT_START \ + { \ + const char *_path0 = (path); \ + const char *_expected = (expected); \ + gs_free char *_path = g_strdup(_path0); \ + const char *_path_result; \ + \ + _path_result = nm_path_simplify(_path); \ + g_assert(_path_result == _path); \ + g_assert_cmpstr(_path, ==, _expected); \ + } \ + G_STMT_END + + _path_equal_check("", ""); + _path_equal_check(".", "."); + _path_equal_check("..", ".."); + _path_equal_check("/..", "/.."); + _path_equal_check("//..", "/.."); + _path_equal_check("/.", "/"); + _path_equal_check("./", "."); + _path_equal_check("./.", "."); + _path_equal_check(".///.", "."); + _path_equal_check(".///./", "."); + _path_equal_check(".////", "."); + _path_equal_check("//..//foo/", "/../foo"); + _path_equal_check("///foo//./bar/.", "/foo/bar"); + _path_equal_check(".//./foo//./bar/.", "foo/bar"); +} + +/*****************************************************************************/ + +static void +assert_path_find_first_component(const char *path, + gboolean accept_dot_dot, + const char *const *expected, + int ret) +{ + const char *p; + + for (p = path;;) { + const char *e; + int r; + + r = nm_path_find_first_component(&p, accept_dot_dot, &e); + if (r <= 0) { + if (r == 0) { + if (path) + g_assert(p == path + strlen(path)); + else + g_assert(!p); + g_assert(!e); + } + g_assert(r == ret); + g_assert(!expected || !*expected); + return; + } + + g_assert(e); + g_assert(strcspn(e, "/") == (size_t) r); + g_assert(strlen(*expected) == (size_t) r); + g_assert(strncmp(e, *expected++, r) == 0); + } +} + +static void +test_path_find_first_component(void) +{ + gs_free char *hoge = NULL; + char foo[NAME_MAX * 2]; + + /* Copied from systemd. + * https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/test/test-path-util.c#L631 */ + + assert_path_find_first_component(NULL, false, NULL, 0); + assert_path_find_first_component("", false, NULL, 0); + assert_path_find_first_component("/", false, NULL, 0); + assert_path_find_first_component(".", false, NULL, 0); + assert_path_find_first_component("./", false, NULL, 0); + assert_path_find_first_component("./.", false, NULL, 0); + assert_path_find_first_component("..", false, NULL, -EINVAL); + assert_path_find_first_component("/..", false, NULL, -EINVAL); + assert_path_find_first_component("./..", false, NULL, -EINVAL); + assert_path_find_first_component("////./././//.", false, NULL, 0); + assert_path_find_first_component("a/b/c", false, NM_MAKE_STRV("a", "b", "c"), 0); + assert_path_find_first_component("././//.///aa/bbb//./ccc", + false, + NM_MAKE_STRV("aa", "bbb", "ccc"), + 0); + assert_path_find_first_component("././//.///aa/.../../bbb//./ccc/.", + false, + NM_MAKE_STRV("aa", "..."), + -EINVAL); + assert_path_find_first_component("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", + false, + NM_MAKE_STRV("aaa", ".bbb"), + -EINVAL); + assert_path_find_first_component("a/foo./b", false, NM_MAKE_STRV("a", "foo.", "b"), 0); + + assert_path_find_first_component(NULL, true, NULL, 0); + assert_path_find_first_component("", true, NULL, 0); + assert_path_find_first_component("/", true, NULL, 0); + assert_path_find_first_component(".", true, NULL, 0); + assert_path_find_first_component("./", true, NULL, 0); + assert_path_find_first_component("./.", true, NULL, 0); + assert_path_find_first_component("..", true, NM_MAKE_STRV(".."), 0); + assert_path_find_first_component("/..", true, NM_MAKE_STRV(".."), 0); + assert_path_find_first_component("./..", true, NM_MAKE_STRV(".."), 0); + assert_path_find_first_component("////./././//.", true, NULL, 0); + assert_path_find_first_component("a/b/c", true, NM_MAKE_STRV("a", "b", "c"), 0); + assert_path_find_first_component("././//.///aa/bbb//./ccc", + true, + NM_MAKE_STRV("aa", "bbb", "ccc"), + 0); + assert_path_find_first_component("././//.///aa/.../../bbb//./ccc/.", + true, + NM_MAKE_STRV("aa", "...", "..", "bbb", "ccc"), + 0); + assert_path_find_first_component("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", + true, + NM_MAKE_STRV("aaa", ".bbb", "..", "c.", "d.dd", "..eeee"), + 0); + assert_path_find_first_component("a/foo./b", true, NM_MAKE_STRV("a", "foo.", "b"), 0); + + memset(foo, 'a', sizeof(foo) - 1); + foo[sizeof(foo) - 1] = '\0'; + + assert_path_find_first_component(foo, false, NULL, -EINVAL); + assert_path_find_first_component(foo, true, NULL, -EINVAL); + + hoge = g_strjoin("", "a/b/c/", foo, "//d/e/.//f/", NULL); + g_assert(hoge); + + assert_path_find_first_component(hoge, false, NM_MAKE_STRV("a", "b", "c"), -EINVAL); + assert_path_find_first_component(hoge, true, NM_MAKE_STRV("a", "b", "c"), -EINVAL); +} + +/*****************************************************************************/ + +static void +assert_path_startswith(const char *path, + const char *prefix, + const char *skipped, + const char *expected) +{ + const char *p; + + p = nm_path_startswith(path, prefix); + g_assert_cmpstr(p, ==, expected); + if (p) { + gs_free char *q = NULL; + + g_assert(skipped); + q = g_strjoin("", skipped, p, NULL); + g_assert_cmpstr(q, ==, path); + g_assert(p == path + strlen(skipped)); + } else + g_assert(!skipped); +} + +static void +test_path_startswith(void) +{ + assert_path_startswith("/foo/bar/barfoo/", "/foo", "/foo/", "bar/barfoo/"); + assert_path_startswith("/foo/bar/barfoo/", "/foo/", "/foo/", "bar/barfoo/"); + assert_path_startswith("/foo/bar/barfoo/", "/", "/", "foo/bar/barfoo/"); + assert_path_startswith("/foo/bar/barfoo/", "////", "/", "foo/bar/barfoo/"); + assert_path_startswith("/foo/bar/barfoo/", "/foo//bar/////barfoo///", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo////", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar///barfoo/", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "/foo////bar/barfoo/", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "////foo/bar/barfoo/", "/foo/bar/barfoo/", ""); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo", "/foo/bar/barfoo/", ""); + + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfooa/", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfooa", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/bar/foo", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/f/b/b/", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/barfo", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/foo/bar/bar", NULL, NULL); + assert_path_startswith("/foo/bar/barfoo/", "/fo", NULL, NULL); +} + +/*****************************************************************************/ + +static void +assert_path_simplify(const char *in, const char *out) +{ + gs_free char *p = NULL; + + g_assert(in); + p = g_strdup(in); + nm_path_simplify(p); + g_assert_cmpstr(p, ==, out); +} + +static void +test_path_simplify(void) +{ + gs_free char *hoge = NULL; + gs_free char *hoge_out = NULL; + char foo[NAME_MAX * 2]; + + assert_path_simplify("", ""); + assert_path_simplify("aaa/bbb////ccc", "aaa/bbb/ccc"); + assert_path_simplify("//aaa/.////ccc", "/aaa/ccc"); + assert_path_simplify("///", "/"); + assert_path_simplify("///.//", "/"); + assert_path_simplify("///.//.///", "/"); + assert_path_simplify("////.././///../.", "/../.."); + assert_path_simplify(".", "."); + assert_path_simplify("./", "."); + assert_path_simplify(".///.//./.", "."); + assert_path_simplify(".///.//././/", "."); + assert_path_simplify("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", + "/aaa/.bbb/../c./d.dd/..eeee"); + assert_path_simplify("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "/aaa/.bbb/../c./d.dd/..eeee/.."); + assert_path_simplify(".//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "aaa/.bbb/../c./d.dd/..eeee/.."); + assert_path_simplify("..//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "../aaa/.bbb/../c./d.dd/..eeee/.."); + + memset(foo, 'a', sizeof(foo) - 1); + foo[sizeof(foo) - 1] = '\0'; + + assert_path_simplify(foo, foo); + + hoge = g_strjoin("", "/", foo, NULL); + g_assert(hoge); + assert_path_simplify(hoge, hoge); + nm_clear_g_free(&hoge); + + hoge = + g_strjoin("", "a////.//././//./b///././/./c/////././//./", foo, "//.//////d/e/.//f/", NULL); + g_assert(hoge); + + hoge_out = g_strjoin("", "a/b/c/", foo, "//.//////d/e/.//f/", NULL); + g_assert(hoge_out); + + assert_path_simplify(hoge, hoge_out); +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -1834,6 +2139,11 @@ main(int argc, char **argv) g_test_add_func("/general/test_unbase64mem1", test_unbase64mem1); g_test_add_func("/general/test_unbase64mem2", test_unbase64mem2); g_test_add_func("/general/test_unbase64mem3", test_unbase64mem3); + g_test_add_func("/general/test_path_compare", test_path_compare); + g_test_add_func("/general/test_path_equal", test_path_equal); + g_test_add_func("/general/test_path_find_first_component", test_path_find_first_component); + g_test_add_func("/general/test_path_startswith", test_path_startswith); + g_test_add_func("/general/test_path_simplify", test_path_simplify); return g_test_run(); }