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.
This commit is contained in:
Thomas Haller 2022-04-12 15:32:38 +02:00
parent 82cac62fe2
commit b5f3d88e6f
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
3 changed files with 585 additions and 0 deletions

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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();
}