]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
path-util: add path_extract_directory(), to match path_extract_filename()
authorLennart Poettering <lennart@poettering.net>
Tue, 26 Jan 2021 11:28:23 +0000 (12:28 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 2 Mar 2021 14:07:11 +0000 (15:07 +0100)
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.

src/basic/path-util.c
src/basic/path-util.h
src/test/test-path-util.c

index f3398418c4f22b991b7fd0537ade711079c97a08..fe8321edce0349241292d708b8b3d06863dd9676 100644 (file)
@@ -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;
 
index ba12b03dbe6d70323c401f1a3e5be4b8f6c53512..74ee6362eacb9147c24ae5c6a012b46b9a139325 100644 (file)
@@ -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_;
index 59308473c1be8eed40d6caef803b2d24c248c098..db6c1a9efa8c537388021ca3a57480ba9adccf89 100644 (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();