diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 243771d9e4..b7f91ee3ae 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -769,6 +769,37 @@ const char *last_path_component(const char *path) { return path + k; } +int path_extract_filename(const char *p, char **ret) { + _cleanup_free_ char *a = NULL; + const char *c, *e = NULL, *q; + + /* Extracts the filename part (i.e. right-most component) from a path, i.e. string that passes + * filename_is_valid(). A wrapper around last_path_component(), but eats up trailing slashes. */ + + if (!p) + return -EINVAL; + + c = last_path_component(p); + + for (q = c; *q != 0; q++) + if (*q != '/') + e = q + 1; + + if (!e) /* no valid character? */ + return -EINVAL; + + a = strndup(c, e - c); + if (!a) + return -ENOMEM; + + if (!filename_is_valid(a)) + return -EINVAL; + + *ret = TAKE_PTR(a); + + return 0; +} + bool filename_is_valid(const char *p) { const char *e; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index e2a51ff33a..53b980d3c1 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -133,6 +133,7 @@ int parse_path_argument_and_warn(const char *path, bool suppress_root, char **ar char* dirname_malloc(const char *path); const char *last_path_component(const char *path); +int path_extract_filename(const char *p, char **ret); bool filename_is_valid(const char *p) _pure_; bool path_is_valid(const char *p) _pure_; diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index dd00f13905..7feae54068 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -425,6 +425,45 @@ static void test_last_path_component(void) { assert_se(streq(last_path_component("/a/"), "a/")); } +static void test_path_extract_filename_one(const char *input, const char *output, int ret) { + _cleanup_free_ char *k = NULL; + int r; + + r = path_extract_filename(input, &k); + log_info("%s → %s/%s [expected: %s/%s]", strnull(input), strnull(k), strerror(-r), strnull(output), strerror(-ret)); + assert_se(streq_ptr(k, output)); + assert_se(r == ret); +} + +static void test_path_extract_filename(void) { + test_path_extract_filename_one(NULL, NULL, -EINVAL); + test_path_extract_filename_one("a/b/c", "c", 0); + test_path_extract_filename_one("a/b/c/", "c", 0); + test_path_extract_filename_one("/", NULL, -EINVAL); + test_path_extract_filename_one("//", NULL, -EINVAL); + test_path_extract_filename_one("///", NULL, -EINVAL); + test_path_extract_filename_one(".", NULL, -EINVAL); + test_path_extract_filename_one("./.", NULL, -EINVAL); + test_path_extract_filename_one("././", NULL, -EINVAL); + test_path_extract_filename_one("././/", NULL, -EINVAL); + test_path_extract_filename_one("/foo/a", "a", 0); + test_path_extract_filename_one("/foo/a/", "a", 0); + test_path_extract_filename_one("", NULL, -EINVAL); + test_path_extract_filename_one("a", "a", 0); + test_path_extract_filename_one("a/", "a", 0); + test_path_extract_filename_one("/a", "a", 0); + test_path_extract_filename_one("/a/", "a", 0); + test_path_extract_filename_one("/////////////a/////////////", "a", 0); + test_path_extract_filename_one("xx/.", NULL, -EINVAL); + test_path_extract_filename_one("xx/..", NULL, -EINVAL); + test_path_extract_filename_one("..", NULL, -EINVAL); + test_path_extract_filename_one("/..", NULL, -EINVAL); + test_path_extract_filename_one("../", NULL, -EINVAL); + test_path_extract_filename_one(".", NULL, -EINVAL); + test_path_extract_filename_one("/.", NULL, -EINVAL); + test_path_extract_filename_one("./", NULL, -EINVAL); +} + static void test_filename_is_valid(void) { char foo[FILENAME_MAX+2]; int i; @@ -543,6 +582,7 @@ int main(int argc, char **argv) { test_prefix_root(); test_file_in_same_dir(); test_last_path_component(); + test_path_extract_filename(); test_filename_is_valid(); test_hidden_or_backup_file(); test_skip_dev_prefix();