path-util: add new path_join_many() API

This commit is contained in:
Lennart Poettering 2018-06-28 22:28:40 +02:00
parent de06c0cf77
commit cd8194a389
3 changed files with 105 additions and 0 deletions

View File

@ -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;

View File

@ -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) {

View File

@ -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 */