path-util: add path_extract_directory(), to match path_extract_filename()

These two together are a lot like dirname() + basename() but have the
benefit that they return clear errors when one passes a special case
path to them where the extraction doesn't make sense, i.e. "", "/",
"foo", "foo/" and so on.

Sooner or later we should probably port all our uses of
dirname()/basename() over to this, to catch these special cases more
safely.
This commit is contained in:
Lennart Poettering
2021-03-02 15:07:11 +01:00
parent 7e40042b55
commit 8dcb891c19
3 changed files with 102 additions and 0 deletions
+59
View File
@@ -606,6 +606,64 @@ static void test_path_extract_filename(void) {
test_path_extract_filename_one("./", NULL, -EINVAL);
}
static void test_path_extract_directory_one(const char *input, const char *output, int ret) {
_cleanup_free_ char *k = NULL;
int r;
r = path_extract_directory(input, &k);
log_info_errno(r, "%s → %s/%m [expected: %s/%s]",
strnull(input),
strnull(k), /* we output strerror_safe(r) via %m here, since otherwise the error buffer might be overwritten twice */
strnull(output), strerror_safe(ret));
assert_se(streq_ptr(k, output));
assert_se(r == ret);
/* Extra safety check: let's make sure that if we split out the filename too (and it works) the
* joined parts are identical to the original again */
if (r >= 0) {
_cleanup_free_ char *f = NULL;
r = path_extract_filename(input, &f);
if (r >= 0) {
_cleanup_free_ char *j = NULL;
assert_se(j = path_join(k, f));
assert_se(path_equal(input, j));
}
}
}
static void test_path_extract_directory(void) {
log_info("/* %s */", __func__);
test_path_extract_directory_one(NULL, NULL, -EINVAL);
test_path_extract_directory_one("a/b/c", "a/b", 0);
test_path_extract_directory_one("a/b/c/", "a/b", 0);
test_path_extract_directory_one("/", NULL, -EADDRNOTAVAIL);
test_path_extract_directory_one("//", NULL, -EADDRNOTAVAIL);
test_path_extract_directory_one("///", NULL, -EADDRNOTAVAIL);
test_path_extract_directory_one(".", NULL, -EDESTADDRREQ);
test_path_extract_directory_one("./.", ".", 0);
test_path_extract_directory_one("././", ".", 0);
test_path_extract_directory_one("././/", ".", 0);
test_path_extract_directory_one("/foo/a", "/foo", 0);
test_path_extract_directory_one("/foo/a/", "/foo", 0);
test_path_extract_directory_one("", NULL, -EINVAL);
test_path_extract_directory_one("a", NULL, -EDESTADDRREQ);
test_path_extract_directory_one("a/", NULL, -EDESTADDRREQ);
test_path_extract_directory_one("/a", "/", 0);
test_path_extract_directory_one("/a/", "/", 0);
test_path_extract_directory_one("/////////////a/////////////", "/", 0);
test_path_extract_directory_one("xx/.", "xx", 0);
test_path_extract_directory_one("xx/..", "xx", 0);
test_path_extract_directory_one("..", NULL, -EDESTADDRREQ);
test_path_extract_directory_one("/..", "/", 0);
test_path_extract_directory_one("../", NULL, -EDESTADDRREQ);
test_path_extract_directory_one(".", NULL, -EDESTADDRREQ);
test_path_extract_directory_one("/.", "/", 0);
test_path_extract_directory_one("./", NULL, -EDESTADDRREQ);
}
static void test_filename_is_valid(void) {
char foo[NAME_MAX+2];
@@ -793,6 +851,7 @@ int main(int argc, char **argv) {
test_file_in_same_dir();
test_last_path_component();
test_path_extract_filename();
test_path_extract_directory();
test_filename_is_valid();
test_path_is_valid();
test_hidden_or_backup_file();