]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
path-util: introduce path_find_first_component()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 30 Apr 2021 16:57:28 +0000 (01:57 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 28 May 2021 04:41:23 +0000 (13:41 +0900)
The function may be useful to iterate on each path component.

src/basic/path-util.c
src/basic/path-util.h
src/test/test-path-util.c

index 08dace775dcd176af60a51e35461d0e680547cd5..ed4fbcca2359b4dcaabf5d160193a1d453492f12 100644 (file)
@@ -808,6 +808,90 @@ char* dirname_malloc(const char *path) {
         return dir2;
 }
 
+static const char *skip_slash_or_dot(const char *p) {
+        for (; !isempty(p); p++) {
+                if (*p == '/')
+                        continue;
+                if (startswith(p, "./")) {
+                        p++;
+                        continue;
+                }
+                break;
+        }
+        return p;
+}
+
+int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret) {
+        const char *q, *first, *end_first, *next;
+        size_t len;
+
+        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 (isempty(first)) {
+                *p = first;
+                if (ret)
+                        *ret = NULL;
+                return 0;
+        }
+        if (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 + streq(next, ".");
+        if (ret)
+                *ret = first;
+        return len;
+}
+
 const char *last_path_component(const char *path) {
 
         /* Finds the last component of the path, preserving the optional trailing slash that signifies a directory.
index 238a76ea7334e0244de9c06ab53b192b7c37d4f7..d83a1effdae980d90759485136aaebc9ee6ddad5 100644 (file)
@@ -149,6 +149,7 @@ int fsck_exists(const char *fstype);
         })
 
 char* dirname_malloc(const char *path);
+int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret);
 const char *last_path_component(const char *path);
 int path_extract_filename(const char *p, char **ret);
 int path_extract_directory(const char *p, char **ret);
index c93a21a4754754b941214bd0c8e5424903017d5d..bb175145746c0eaab5c3bd6e1efa7ba750b5e191 100644 (file)
@@ -577,6 +577,90 @@ static void test_file_in_same_dir(void) {
         free(t);
 }
 
+static void test_path_find_first_component_one(
+                const char *path,
+                bool accept_dot_dot,
+                char **expected,
+                int ret) {
+
+        log_debug("/* %s(\"%s\", accept_dot_dot=%s) */", __func__, strnull(path), yes_no(accept_dot_dot));
+
+        for (const char *p = path;;) {
+                const char *e;
+                int r;
+
+                r = path_find_first_component(&p, accept_dot_dot, &e);
+                if (r <= 0) {
+                        if (r == 0) {
+                                if (path)
+                                        assert_se(p == path + strlen_ptr(path));
+                                else
+                                        assert_se(!p);
+                                assert_se(!e);
+                        }
+                        assert_se(r == ret);
+                        assert_se(strv_isempty(expected));
+                        return;
+                }
+
+                assert_se(e);
+                assert_se(strcspn(e, "/") == (size_t) r);
+                assert_se(strlen_ptr(*expected) == (size_t) r);
+                assert_se(strneq(e, *expected++, r));
+        }
+}
+
+static void test_path_find_first_component(void) {
+        _cleanup_free_ char *hoge = NULL;
+        char foo[NAME_MAX * 2];
+
+        log_info("/* %s */", __func__);
+
+        test_path_find_first_component_one(NULL, false, NULL, 0);
+        test_path_find_first_component_one("", false, NULL, 0);
+        test_path_find_first_component_one("/", false, NULL, 0);
+        test_path_find_first_component_one(".", false, NULL, 0);
+        test_path_find_first_component_one("./", false, NULL, 0);
+        test_path_find_first_component_one("./.", false, NULL, 0);
+        test_path_find_first_component_one("..", false, NULL, -EINVAL);
+        test_path_find_first_component_one("/..", false, NULL, -EINVAL);
+        test_path_find_first_component_one("./..", false, NULL, -EINVAL);
+        test_path_find_first_component_one("////./././//.", false, NULL, 0);
+        test_path_find_first_component_one("a/b/c", false, STRV_MAKE("a", "b", "c"), 0);
+        test_path_find_first_component_one("././//.///aa/bbb//./ccc", false, STRV_MAKE("aa", "bbb", "ccc"), 0);
+        test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", false, STRV_MAKE("aa", "..."), -EINVAL);
+        test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("aaa", ".bbb"), -EINVAL);
+        test_path_find_first_component_one("a/foo./b", false, STRV_MAKE("a", "foo.", "b"), 0);
+
+        test_path_find_first_component_one(NULL, true, NULL, 0);
+        test_path_find_first_component_one("", true, NULL, 0);
+        test_path_find_first_component_one("/", true, NULL, 0);
+        test_path_find_first_component_one(".", true, NULL, 0);
+        test_path_find_first_component_one("./", true, NULL, 0);
+        test_path_find_first_component_one("./.", true, NULL, 0);
+        test_path_find_first_component_one("..", true, STRV_MAKE(".."), 0);
+        test_path_find_first_component_one("/..", true, STRV_MAKE(".."), 0);
+        test_path_find_first_component_one("./..", true, STRV_MAKE(".."), 0);
+        test_path_find_first_component_one("////./././//.", true, NULL, 0);
+        test_path_find_first_component_one("a/b/c", true, STRV_MAKE("a", "b", "c"), 0);
+        test_path_find_first_component_one("././//.///aa/bbb//./ccc", true, STRV_MAKE("aa", "bbb", "ccc"), 0);
+        test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", true, STRV_MAKE("aa", "...", "..", "bbb", "ccc"), 0);
+        test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("aaa", ".bbb", "..", "c.", "d.dd", "..eeee"), 0);
+        test_path_find_first_component_one("a/foo./b", true, STRV_MAKE("a", "foo.", "b"), 0);
+
+        memset(foo, 'a', sizeof(foo) -1);
+        char_array_0(foo);
+
+        test_path_find_first_component_one(foo, false, NULL, -EINVAL);
+        test_path_find_first_component_one(foo, true, NULL, -EINVAL);
+
+        hoge = strjoin("a/b/c/", foo, "//d/e/.//f/");
+        assert_se(hoge);
+
+        test_path_find_first_component_one(hoge, false, STRV_MAKE("a", "b", "c"), -EINVAL);
+        test_path_find_first_component_one(hoge, true, STRV_MAKE("a", "b", "c"), -EINVAL);
+}
+
 static void test_last_path_component(void) {
         assert_se(last_path_component(NULL) == NULL);
         assert_se(streq(last_path_component("a/b/c"), "c"));
@@ -894,6 +978,7 @@ int main(int argc, char **argv) {
         test_path_startswith();
         test_prefix_root();
         test_file_in_same_dir();
+        test_path_find_first_component();
         test_last_path_component();
         test_path_extract_filename();
         test_path_extract_directory();