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;
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) {
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);
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 */