From 484cd43cae536d952cd84abfda3260235dc893cc Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 3 May 2021 23:47:57 +0900 Subject: [PATCH] path-util: introduce path_find_last_component() --- src/basic/path-util.c | 103 ++++++++++++++++++++++++++++++++++++++ src/basic/path-util.h | 1 + src/test/test-path-util.c | 80 +++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) diff --git a/src/basic/path-util.c b/src/basic/path-util.c index e91a17b398a..ab89623c5ab 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -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. diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 9fa8b5b75cf..4c4e7a1e770 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -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); diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 72c18c1b387..4dcfcaabe17 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -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(); -- 2.47.3