From cd8194a38919c32f2ba1c606f1d2ac8983e08162 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 28 Jun 2018 22:28:40 +0200 Subject: [PATCH] path-util: add new path_join_many() API --- src/basic/path-util.c | 64 +++++++++++++++++++++++++++++++++++++++ src/basic/path-util.h | 3 ++ src/test/test-path-util.c | 38 +++++++++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/src/basic/path-util.c b/src/basic/path-util.c index b7f91ee3ae..ba31c858fd 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -495,6 +495,70 @@ char* path_join(const char *root, const char *path, const char *rest) { rest && rest[0] == '/' ? rest+1 : rest); } +char* path_join_many_internal(const char *first, ...) { + char *joined, *q; + const char *p; + va_list ap; + bool slash; + size_t sz; + + assert(first); + + /* Joins all listed strings until NULL and places an "/" between them unless the strings end/begin already with + * one so that it is unnecessary. Note that "/" which are already duplicate won't be removed. The string + * returned is hence always equal or longer than the sum of the lengths of each individual string. + * + * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of which some + * are optional. + * + * Examples: + * + * path_join_many("foo", "bar") → "foo/bar" + * path_join_many("foo/", "bar") → "foo/bar" + * path_join_many("", "foo", "", "bar", "") → "foo/bar" */ + + sz = strlen(first); + va_start(ap, first); + while ((p = va_arg(ap, char*))) { + + if (*p == 0) /* Skip empty items */ + continue; + + sz += 1 + strlen(p); + } + va_end(ap); + + joined = new(char, sz + 1); + if (!joined) + return NULL; + + if (first[0] != 0) { + q = stpcpy(joined, first); + slash = endswith(first, "/"); + } else { + /* Skip empty items */ + joined[0] = 0; + q = joined; + slash = true; /* no need to generate a slash anymore */ + } + + va_start(ap, first); + while ((p = va_arg(ap, char*))) { + + if (*p == 0) /* Skip empty items */ + continue; + + if (!slash && p[0] != '/') + *(q++) = '/'; + + q = stpcpy(q, p); + slash = endswith(p, "/"); + } + va_end(ap); + + return joined; +} + int find_binary(const char *name, char **ret) { int last_error, r; const char *p; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 53b980d3c1..868d64e17d 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -50,6 +50,9 @@ int path_compare(const char *a, const char *b) _pure_; bool path_equal(const char *a, const char *b) _pure_; bool path_equal_or_files_same(const char *a, const char *b, int flags); char* path_join(const char *root, const char *path, const char *rest); +char* path_join_many_internal(const char *first, ...) _sentinel_; +#define path_join_many(x, ...) path_join_many_internal(x, __VA_ARGS__, NULL) + char* path_simplify(char *path, bool kill_dots); static inline bool path_equal_ptr(const char *a, const char *b) { diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 7feae54068..e3bcfcd4bf 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -567,6 +567,43 @@ static void test_path_startswith_set(void) { assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "", "/zzz"), NULL)); } +static void test_path_join_many(void) { + char *j; + + assert_se(streq_ptr(j = path_join_many("", NULL), "")); + free(j); + + assert_se(streq_ptr(j = path_join_many("foo", NULL), "foo")); + free(j); + + assert_se(streq_ptr(j = path_join_many("foo", "bar"), "foo/bar")); + free(j); + + assert_se(streq_ptr(j = path_join_many("", "foo", "", "bar", ""), "foo/bar")); + free(j); + + assert_se(streq_ptr(j = path_join_many("", "", "", "", "foo", "", "", "", "bar", "", "", ""), "foo/bar")); + free(j); + + assert_se(streq_ptr(j = path_join_many("", "/", "", "/foo/", "", "/", "", "/bar/", "", "/", ""), "//foo///bar//")); + free(j); + + assert_se(streq_ptr(j = path_join_many("/", "foo", "/", "bar", "/"), "/foo/bar/")); + free(j); + + assert_se(streq_ptr(j = path_join_many("foo", "bar", "baz"), "foo/bar/baz")); + free(j); + + assert_se(streq_ptr(j = path_join_many("foo/", "bar", "/baz"), "foo/bar/baz")); + free(j); + + assert_se(streq_ptr(j = path_join_many("foo/", "/bar/", "/baz"), "foo//bar//baz")); + free(j); + + assert_se(streq_ptr(j = path_join_many("//foo/", "///bar/", "///baz//"), "//foo////bar////baz//")); + free(j); +} + int main(int argc, char **argv) { test_setup_logging(LOG_DEBUG); @@ -588,6 +625,7 @@ int main(int argc, char **argv) { test_skip_dev_prefix(); test_empty_or_root(); test_path_startswith_set(); + test_path_join_many(); test_systemd_installation_has_version(argv[1]); /* NULL is OK */