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.
*
* 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;
}
va_end(ap);
- return joined;
+ return nx;
}
static int check_x_access(const char *path, int *ret_fd) {
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);
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__);
test_find_executable_exec();
test_prefixes();
test_path_join();
+ test_path_extend();
test_fsck_exists();
test_make_relative();
test_strv_resolve();