]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
path-util: introduce path_find_last_component()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 3 May 2021 14:47:57 +0000 (23:47 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 28 May 2021 04:44:38 +0000 (13:44 +0900)
src/basic/path-util.c
src/basic/path-util.h
src/test/test-path-util.c

index e91a17b398a404a12fc4e64887677e682c691c85..ab89623c5ab6d6633bea060ca1f33a105d59f647 100644 (file)
@@ -897,6 +897,109 @@ int path_find_first_component(const char **p, bool accept_dot_dot, const char **
         return len;
 }
 
+static const char *skip_slash_or_dot_backward(const char *path, const char *q) {
+        assert(path);
+
+        for (; q >= path; q--) {
+                if (*q == '/')
+                        continue;
+                if (q > path && strneq(q - 1, "/.", 2))
+                        continue;
+                break;
+        }
+        return q;
+}
+
+int path_find_last_component(const char *path, bool accept_dot_dot, const char **next, const char **ret) {
+        const char *q, *last_end, *last_begin;
+        size_t len;
+
+        /* Similar to path_find_first_component(), but search components from the end.
+        *
+        * Examples
+        *   Input:  path: "//.//aaa///bbbbb/cc//././"
+        *           next: NULL
+        *   Output: next: "/cc//././"
+        *           ret: "cc//././"
+        *           return value: 2 (== strlen("cc"))
+        *
+        *   Input:  path: "//.//aaa///bbbbb/cc//././"
+        *           next: "/cc//././"
+        *   Output: next: "///bbbbb/cc//././"
+        *           ret: "bbbbb/cc//././"
+        *           return value: 5 (== strlen("bbbbb"))
+        *
+        *   Input:  path: "/", ".", "", or NULL
+        *   Output: next: equivalent to path
+        *           ret: NULL
+        *           return value: 0
+        *
+        *   Input:  path: "(too long component)"
+        *   Output: return value: -EINVAL
+        *
+        *   (when accept_dot_dot is false)
+        *   Input:  path: "//..//aaa///bbbbb/cc/..//"
+        *   Output: return value: -EINVAL
+        */
+
+        if (isempty(path)) {
+                if (next)
+                        *next = path;
+                if (ret)
+                        *ret = NULL;
+                return 0;
+        }
+
+        if (next && *next) {
+                if (*next < path || *next > path + strlen(path))
+                        return -EINVAL;
+                if (*next == path) {
+                        if (ret)
+                                *ret = NULL;
+                        return 0;
+                }
+                if (!IN_SET(**next, '\0', '/'))
+                        return -EINVAL;
+                q = *next - 1;
+        } else
+                q = path + strlen(path) - 1;
+
+        q = skip_slash_or_dot_backward(path, q);
+        if ((q < path) || /* the root directory */
+            (q == path && *q == '.')) { /* path is "." or "./" */
+                if (next)
+                        *next = path;
+                if (ret)
+                        *ret = NULL;
+                return 0;
+        }
+
+        last_end = q + 1;
+
+        while (q >= path && *q != '/')
+                q--;
+
+        last_begin = q + 1;
+        len = last_end - last_begin;
+
+        if (len > NAME_MAX)
+                return -EINVAL;
+        if (!accept_dot_dot && len == 2 && strneq(last_begin, "..", 2))
+                return -EINVAL;
+
+        if (next) {
+                q = skip_slash_or_dot_backward(path, q);
+                if (q < path)
+                        *next = path;
+                else
+                        *next = q + 1;
+        }
+
+        if (ret)
+                *ret = last_begin;
+        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 9fa8b5b75cf39ea35c52230e5f24321c71870a18..4c4e7a1e77052a392c75e9a9792267eeeb88e18f 100644 (file)
@@ -153,6 +153,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);
+int path_find_last_component(const char *path, bool accept_dot_dot, const char **next, 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 72c18c1b387f44833f32d7d76137ac287061bfb8..4dcfcaabe17a912481693df1e5d52842b9a57f7a 100644 (file)
@@ -665,6 +665,85 @@ static void test_path_find_first_component(void) {
         test_path_find_first_component_one(hoge, true, STRV_MAKE("a", "b", "c"), -EINVAL);
 }
 
+static void test_path_find_last_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 *next = NULL;;) {
+                const char *e;
+                int r;
+
+                r = path_find_last_component(path, accept_dot_dot, &next, &e);
+                if (r <= 0) {
+                        if (r == 0) {
+                                assert_se(next == path);
+                                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_last_component(void) {
+        _cleanup_free_ char *hoge = NULL;
+        char foo[NAME_MAX * 2];
+
+        log_info("/* %s */", __func__);
+
+        test_path_find_last_component_one(NULL, false, NULL, 0);
+        test_path_find_last_component_one("", false, NULL, 0);
+        test_path_find_last_component_one("/", false, NULL, 0);
+        test_path_find_last_component_one(".", false, NULL, 0);
+        test_path_find_last_component_one("./", false, NULL, 0);
+        test_path_find_last_component_one("./.", false, NULL, 0);
+        test_path_find_last_component_one("..", false, NULL, -EINVAL);
+        test_path_find_last_component_one("/..", false, NULL, -EINVAL);
+        test_path_find_last_component_one("./..", false, NULL, -EINVAL);
+        test_path_find_last_component_one("////./././//.", false, NULL, 0);
+        test_path_find_last_component_one("a/b/c", false, STRV_MAKE("c", "b", "a"), 0);
+        test_path_find_last_component_one("././//.///aa./.bbb//./ccc/././/", false, STRV_MAKE("ccc", ".bbb", "aa."), 0);
+        test_path_find_last_component_one("././//.///aa/../.../bbb//./ccc/.", false, STRV_MAKE("ccc", "bbb", "..."), -EINVAL);
+        test_path_find_last_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("..eeee", "d.dd", "c."), -EINVAL);
+
+        test_path_find_last_component_one(NULL, true, NULL, 0);
+        test_path_find_last_component_one("", true, NULL, 0);
+        test_path_find_last_component_one("/", true, NULL, 0);
+        test_path_find_last_component_one(".", true, NULL, 0);
+        test_path_find_last_component_one("./", true, NULL, 0);
+        test_path_find_last_component_one("./.", true, NULL, 0);
+        test_path_find_last_component_one("..", true, STRV_MAKE(".."), 0);
+        test_path_find_last_component_one("/..", true, STRV_MAKE(".."), 0);
+        test_path_find_last_component_one("./..", true, STRV_MAKE(".."), 0);
+        test_path_find_last_component_one("////./././//.", true, NULL, 0);
+        test_path_find_last_component_one("a/b/c", true, STRV_MAKE("c", "b", "a"), 0);
+        test_path_find_last_component_one("././//.///aa./.bbb//./ccc/././/", true, STRV_MAKE("ccc", ".bbb", "aa."), 0);
+        test_path_find_last_component_one("././//.///aa/../.../bbb//./ccc/.", true, STRV_MAKE("ccc", "bbb", "...", "..", "aa"), 0);
+        test_path_find_last_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("..eeee", "d.dd", "c.", "..", ".bbb", "aaa"), 0);
+
+        memset(foo, 'a', sizeof(foo) -1);
+        char_array_0(foo);
+
+        test_path_find_last_component_one(foo, false, NULL, -EINVAL);
+        test_path_find_last_component_one(foo, true, NULL, -EINVAL);
+
+        hoge = strjoin(foo, "/a/b/c/");
+        assert_se(hoge);
+
+        test_path_find_last_component_one(hoge, false, STRV_MAKE("c", "b", "a"), -EINVAL);
+        test_path_find_last_component_one(hoge, true, STRV_MAKE("c", "b", "a"), -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"));
@@ -996,6 +1075,7 @@ int main(int argc, char **argv) {
         test_prefix_root();
         test_file_in_same_dir();
         test_path_find_first_component();
+        test_path_find_last_component();
         test_last_path_component();
         test_path_extract_filename();
         test_path_extract_directory();