mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-24 21:34:08 +03:00
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:
parent
7e40042b55
commit
8dcb891c19
@ -846,6 +846,48 @@ int path_extract_filename(const char *p, char **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int path_extract_directory(const char *p, char **ret) {
|
||||
_cleanup_free_ char *a = NULL;
|
||||
const char *c;
|
||||
|
||||
/* The inverse of path_extract_filename(), i.e. returns the directory path prefix. Returns:
|
||||
*
|
||||
* -EINVAL → if the passed in path is not a valid path
|
||||
* -EDESTADDRREQ → if no directory was specified in the passed in path, i.e. only a filename was passed
|
||||
* -EADDRNOTAVAIL → if the passed in parameter had no filename but did have a directory, i.e. the root dir itself was specified
|
||||
* -ENOMEM → no memory (surprise!)
|
||||
*
|
||||
* This function guarantees to return a fully valid path, i.e. one that passes path_is_valid().
|
||||
*/
|
||||
|
||||
if (!path_is_valid(p))
|
||||
return -EINVAL;
|
||||
|
||||
/* Special case the root dir, because otherwise for an input of "///" last_path_component() returns
|
||||
* the pointer to the last slash only, which might be seen as a valid path below. */
|
||||
if (path_equal(p, "/"))
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
c = last_path_component(p);
|
||||
|
||||
/* Delete trailing slashes, but keep one */
|
||||
while (c > p+1 && c[-1] == '/')
|
||||
c--;
|
||||
|
||||
if (p == c) /* No path whatsoever? Then return a recognizable error */
|
||||
return -EDESTADDRREQ;
|
||||
|
||||
a = strndup(p, c - p);
|
||||
if (!a)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!path_is_valid(a))
|
||||
return -EINVAL;
|
||||
|
||||
*ret = TAKE_PTR(a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool filename_is_valid(const char *p) {
|
||||
const char *e;
|
||||
|
||||
|
@ -147,6 +147,7 @@ int fsck_exists(const char *fstype);
|
||||
char* dirname_malloc(const char *path);
|
||||
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);
|
||||
|
||||
bool filename_is_valid(const char *p) _pure_;
|
||||
bool path_is_valid(const char *p) _pure_;
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user