int path_extract_filename(const char *p, char **ret) {
_cleanup_free_ char *a = NULL;
const char *c;
+ size_t n;
/* 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. Returns
- * -EADDRNOTAVAIL if specified parameter includes no filename (i.e. is "/" or so). Returns -EINVAL if
- * not a valid path in the first place. */
+ * filename_is_valid(). A wrapper around last_path_component(), but eats up trailing
+ * slashes. Returns:
+ *
+ * -EINVAL → if the passed in path is not a valid path
+ * -EADDRNOTAVAIL → if only a directory was specified, but no filename, i.e. the root dir itself is specified
+ * -ENOMEM → no memory
+ *
+ * Returns >= 0 on success. If the input path has a trailing slash, returns O_DIRECTORY, to indicate
+ * the referenced file must be a directory.
+ *
+ * This function guarantees to return a fully valid filename, i.e. one that passes
+ * filename_is_valid() – this means "." and ".." are not accepted. */
if (!path_is_valid(p))
return -EINVAL;
return -EADDRNOTAVAIL;
c = last_path_component(p);
+ n = strcspn(c, "/");
- a = strndup(c, strcspn(c, "/"));
+ a = strndup(c, n);
if (!a)
return -ENOMEM;
return -EINVAL;
*ret = TAKE_PTR(a);
- return 0;
+ return c[n] == '/' ? O_DIRECTORY : 0;
}
int path_extract_directory(const char *p, char **ret) {
int r;
r = path_extract_filename(input, &k);
- log_info("%s → %s/%s [expected: %s/%s]", strnull(input), strnull(k), strerror_safe(r), strnull(output), strerror_safe(ret));
+ log_info_errno(r, "%s → %s/%m [expected: %s/%s]",
+ strnull(input),
+ strnull(k), /* strerror(r) is printed via %m, to avoid that the two strerror()'s overwrite each other's buffers */
+ strnull(output), ret < 0 ? strerror_safe(ret) : "-");
assert_se(streq_ptr(k, output));
assert_se(r == ret);
}
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("a/b/c/", "c", O_DIRECTORY);
test_path_extract_filename_one("/", NULL, -EADDRNOTAVAIL);
test_path_extract_filename_one("//", NULL, -EADDRNOTAVAIL);
test_path_extract_filename_one("///", NULL, -EADDRNOTAVAIL);
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("/foo/a/", "a", O_DIRECTORY);
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", O_DIRECTORY);
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", O_DIRECTORY);
+ test_path_extract_filename_one("/////////////a/////////////", "a", O_DIRECTORY);
test_path_extract_filename_one("xx/.", NULL, -EINVAL);
test_path_extract_filename_one("xx/..", NULL, -EINVAL);
test_path_extract_filename_one("..", NULL, -EINVAL);