]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
path-util: add path_extend(), inspired by strextend(), but using path_join()
authorLennart Poettering <lennart@poettering.net>
Thu, 27 May 2021 14:01:20 +0000 (16:01 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 27 May 2021 14:02:18 +0000 (16:02 +0200)
src/basic/path-util.c
src/basic/path-util.h
src/test/test-path-util.c

index f98859939cddad1994d4a73504bbfcb8dcf9edb2..f06858da6cb63fa9d342e3da8f8ccb0701fb6981 100644 (file)
@@ -548,18 +548,22 @@ bool path_equal_filename(const char *a, const char *b) {
         return path_equal(a_basename, b_basename);
 }
 
-char* path_join_internal(const char *first, ...) {
-        char *joined, *q;
+char* path_extend_internal(char **x, ...) {
+        size_t sz, old_sz;
+        char *q, *nx;
         const char *p;
         va_list ap;
         bool slash;
-        size_t sz;
 
         /* Joins all listed strings until the sentinel and places a "/" between them unless the strings end/begin
          * already with one so that it is unnecessary. Note that slashes which are already duplicate won't be
          * removed. The string returned is hence always equal to or longer than the sum of the lengths of each
          * individual string.
          *
+         * The first argument may be an already allocated string that is extended via realloc() if
+         * non-NULL. path_extend() and path_join() are macro wrappers around this function, making use of the
+         * first parameter to distinguish the two operations.
+         *
          * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of which some
          * are optional.
          *
@@ -569,28 +573,39 @@ char* path_join_internal(const char *first, ...) {
          * path_join("foo/", "bar") → "foo/bar"
          * path_join("", "foo", "", "bar", "") → "foo/bar" */
 
-        sz = strlen_ptr(first);
-        va_start(ap, first);
-        while ((p = va_arg(ap, char*)) != POINTER_MAX)
-                if (!isempty(p))
-                        sz += 1 + strlen(p);
+        sz = old_sz = x ? strlen_ptr(*x) : 0;
+        va_start(ap, x);
+        while ((p = va_arg(ap, char*)) != POINTER_MAX) {
+                size_t add;
+
+                if (isempty(p))
+                        continue;
+
+                add = 1 + strlen(p);
+                if (sz > SIZE_MAX - add) /* overflow check */
+                        return NULL;
+
+                sz += add;
+        }
+
         va_end(ap);
 
-        joined = new(char, sz + 1);
-        if (!joined)
+        nx = realloc(x ? *x : NULL, GREEDY_ALLOC_ROUND_UP(sz+1));
+        if (!nx)
                 return NULL;
+        if (x)
+                *x = nx;
 
-        if (!isempty(first)) {
-                q = stpcpy(joined, first);
-                slash = endswith(first, "/");
-        } else {
-                /* Skip empty items */
-                joined[0] = 0;
-                q = joined;
+        if (old_sz > 0)
+                slash = nx[old_sz] == '/';
+        else {
+                nx[old_sz] = 0;
                 slash = true; /* no need to generate a slash anymore */
         }
 
-        va_start(ap, first);
+        q = nx + old_sz;
+
+        va_start(ap, x);
         while ((p = va_arg(ap, char*)) != POINTER_MAX) {
                 if (isempty(p))
                         continue;
@@ -603,7 +618,7 @@ char* path_join_internal(const char *first, ...) {
         }
         va_end(ap);
 
-        return joined;
+        return nx;
 }
 
 static int check_x_access(const char *path, int *ret_fd) {
index f82d935dc5c739d8d76afae6cbcaf6fee61ba568..238a76ea7334e0244de9c06ab53b192b7c37d4f7 100644 (file)
@@ -63,8 +63,10 @@ bool path_equal(const char *a, const char *b) _pure_;
 bool path_equal_or_files_same(const char *a, const char *b, int flags);
 /* Compares only the last portion of the input paths, ie: the filenames */
 bool path_equal_filename(const char *a, const char *b);
-char* path_join_internal(const char *first, ...);
-#define path_join(x, ...) path_join_internal(x, __VA_ARGS__, POINTER_MAX)
+
+char* path_extend_internal(char **x, ...);
+#define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX)
+#define path_join(...) path_extend_internal(NULL, __VA_ARGS__, POINTER_MAX)
 
 char* path_simplify(char *path, bool kill_dots);
 
index 1572483602c38dfcf21074b1964213b90f954deb..8091a301e38496c6e6ed2fee8549c0685cbe379a 100644 (file)
@@ -380,6 +380,27 @@ static void test_path_join(void) {
         test_join("//foo////bar////baz//", "//foo/", "///bar/", "///baz//");
 }
 
+static void test_path_extend(void) {
+        _cleanup_free_ char *p = NULL;
+
+        log_info("/* %s */", __func__);
+
+        assert_se(path_extend(&p, "foo", "bar", "baz") == p);
+        assert_se(streq(p, "foo/bar/baz"));
+
+        assert_se(path_extend(&p, "foo", "bar", "baz") == p);
+        assert_se(streq(p, "foo/bar/baz/foo/bar/baz"));
+
+        p = mfree(p);
+        assert_se(path_extend(&p, "foo") == p);
+        assert_se(streq(p, "foo"));
+
+        assert_se(path_extend(&p, "/foo") == p);
+        assert_se(streq(p, "foo/foo"));
+        assert_se(path_extend(&p, "waaaah/wahhh/") == p);
+        assert_se(streq(p, "foo/foo/waaaah/wahhh/"));
+}
+
 static void test_fsck_exists(void) {
         log_info("/* %s */", __func__);
 
@@ -860,6 +881,7 @@ int main(int argc, char **argv) {
         test_find_executable_exec();
         test_prefixes();
         test_path_join();
+        test_path_extend();
         test_fsck_exists();
         test_make_relative();
         test_strv_resolve();