From 3788ec1b4aaf559ad10623825d2afac98fc9d5aa Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Feb 2026 19:57:42 +0100 Subject: [PATCH 1/4] snapshot-util: include shared-forward.h --- src/shared/snapshot-util.c | 1 + src/shared/snapshot-util.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shared/snapshot-util.c b/src/shared/snapshot-util.c index a05cbcc8eab..93fd886d7ba 100644 --- a/src/shared/snapshot-util.c +++ b/src/shared/snapshot-util.c @@ -7,6 +7,7 @@ #include "discover-image.h" #include "log.h" #include "mountpoint-util.h" +#include "runtime-scope.h" #include "snapshot-util.h" #include "signal-util.h" #include "tmpfile-util.h" diff --git a/src/shared/snapshot-util.h b/src/shared/snapshot-util.h index 18a593f5c8d..aec83aee999 100644 --- a/src/shared/snapshot-util.h +++ b/src/shared/snapshot-util.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "runtime-scope.h" +#include "shared-forward.h" /* create_ephemeral_snapshot - create a snapshot of the given directory. * From 294855953cccddab0aab3155baccb55c66900d29 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 9 Feb 2026 00:29:18 +0100 Subject: [PATCH 2/4] path-util: drop redundant condition in path_find_last_component() Follow-up for 3a7ba9f6b9d9a80c7f909bfbf24b5fc8c99a3176 --- src/basic/path-util.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 1b231ec6377..6902246c972 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -1027,8 +1027,7 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char * q = path + strlen(path) - 1; q = skip_slash_or_dot_backward(path, q); - if (!q || /* the root directory */ - (q == path && *q == '.')) { /* path is "." or "./" */ + if (!q) { /* the root directory, "." or "./" */ if (next) *next = path; if (ret) From 18a8ea3377f6ae250cd2d75f451cbd518d0dd321 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 9 Feb 2026 01:25:39 +0100 Subject: [PATCH 3/4] path-util: unify path_extract_filename/directory into path_split_prefix_filename() --- src/basic/path-util.c | 92 +++++++++++++++++++------------------------ src/basic/path-util.h | 11 +++++- 2 files changed, 49 insertions(+), 54 deletions(-) diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 6902246c972..0394d26420c 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -1101,18 +1101,17 @@ const char* last_path_component(const char *path) { return path + k; } -int path_extract_filename(const char *path, char **ret) { - _cleanup_free_ char *a = NULL; +int path_split_prefix_filename(const char *path, char **ret_dir, char **ret_filename) { + _cleanup_free_ char *d = NULL; const char *c, *next = NULL; int r; - /* Extracts the filename part (i.e. right-most component) from a path, i.e. string that passes - * filename_is_valid(). A wrapper around last_path_component(), but eats up trailing - * slashes. Returns: + /* Split the path into dir prefix/filename pair. Returns: * * -EINVAL → if the path is not valid - * -EADDRNOTAVAIL → if only a directory was specified, but no filename, i.e. the root dir - * itself or "." is specified + * -EADDRNOTAVAIL → if the path refers to the uppermost directory in hierarchy (i.e. has neither + * dir prefix nor filename - the root dir itself or ".") + * -EDESTADDRREQ → if only a filename was passed, and caller only specifies ret_dir * -ENOMEM → no memory * * Returns >= 0 on success. If the input path has a trailing slash, returns O_DIRECTORY, to @@ -1121,63 +1120,52 @@ int path_extract_filename(const char *path, char **ret) { * This function guarantees to return a fully valid filename, i.e. one that passes * filename_is_valid() – this means "." and ".." are not accepted. */ - if (!path_is_valid(path)) + if (isempty(path)) return -EINVAL; - r = path_find_last_component(path, false, &next, &c); + r = path_find_last_component(path, /* accept_dot_dot = */ false, &next, &c); if (r < 0) return r; - if (r == 0) /* root directory */ + if (r == 0) /* root directory or "." */ return -EADDRNOTAVAIL; - a = strndup(c, r); - if (!a) - return -ENOMEM; + if (ret_dir) { + if (next == path) { + if (*path != '/') { /* filename only */ + if (!ret_filename) + return -EDESTADDRREQ; + } else { + d = strdup("/"); + if (!d) + return -ENOMEM; + } + } else { + d = strndup(path, next - path); + if (!d) + return -ENOMEM; - *ret = TAKE_PTR(a); - return strlen(c) > (size_t) r ? O_DIRECTORY : 0; -} + path_simplify(d); -int path_extract_directory(const char *path, char **ret) { - const char *c, *next = NULL; - int r; + if (!path_is_valid(d)) + return -EINVAL; + } - /* The inverse of path_extract_filename(), i.e. returns the directory path prefix. Returns: - * - * -EINVAL → if the path is not valid - * -EDESTADDRREQ → if no directory was specified in the passed in path, i.e. only a filename was passed - * -EADDRNOTAVAIL → if the passed in parameter had no filename but did have a directory, i.e. - * the root dir itself or "." was specified - * -ENOMEM → no memory (surprise!) - * - * This function guarantees to return a fully valid path, i.e. one that passes path_is_valid(). - */ - - r = path_find_last_component(path, false, &next, &c); - if (r < 0) - return r; - if (r == 0) /* empty or root */ - return isempty(path) ? -EINVAL : -EADDRNOTAVAIL; - if (next == path) { - if (*path != '/') /* filename only */ - return -EDESTADDRREQ; - - return strdup_to(ret, "/"); - } - - _cleanup_free_ char *a = strndup(path, next - path); - if (!a) - return -ENOMEM; - - path_simplify(a); - - if (!path_is_valid(a)) + } else if (!path_is_valid(path)) + /* We didn't validate the dir prefix, hence check if the whole path is valid now */ return -EINVAL; - if (ret) - *ret = TAKE_PTR(a); + if (ret_filename) { + char *fn = strndup(c, r); + if (!fn) + return -ENOMEM; - return 0; + *ret_filename = fn; + } + + if (ret_dir) + *ret_dir = TAKE_PTR(d); + + return strlen(c) > (size_t) r ? O_DIRECTORY : 0; } bool filename_part_is_valid(const char *p) { diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 5b2baef4e13..d70fc3b1bc6 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -135,8 +135,15 @@ int fsck_exists_for_fstype(const char *fstype); int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret); int path_find_last_component(const char *path, bool accept_dot_dot, const char **next, const char **ret); const char* last_path_component(const char *path); -int path_extract_filename(const char *path, char **ret); -int path_extract_directory(const char *path, char **ret); + +int path_split_prefix_filename(const char *path, char **ret_dir, char **ret_filename); +static inline int path_extract_filename(const char *path, char **ret) { + return path_split_prefix_filename(path, NULL, ret); +} +static inline int path_extract_directory(const char *path, char **ret) { + int r = path_split_prefix_filename(path, ret, NULL); + return r < 0 ? r : 0; /* suppress O_DIRECTORY */ +} bool filename_part_is_valid(const char *p) _pure_; bool filename_is_valid(const char *p) _pure_; From cc9a9d6a15a2e18713ca5809e9edd82979dac164 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 9 Feb 2026 02:30:38 +0100 Subject: [PATCH 4/4] tree-wide: use path_split_prefix_filename() where appropriate --- src/basic/lock-util.c | 11 +++++------ src/basic/tmpfile-util.c | 11 +++-------- src/shared/blockdev-util.c | 9 ++------- src/shared/creds-util.c | 12 ++++++------ src/shared/generator.c | 8 ++------ src/shared/unit-file.c | 10 ++++------ 6 files changed, 22 insertions(+), 39 deletions(-) diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c index 85c7dc0562f..bd267eabf99 100644 --- a/src/basic/lock-util.c +++ b/src/basic/lock-util.c @@ -69,15 +69,14 @@ int make_lock_file_for(const char *p, int operation, LockFile *ret) { assert(p); assert(ret); - r = path_extract_filename(p, &fn); + r = path_split_prefix_filename(p, &dn, &fn); if (r < 0) return r; - r = path_extract_directory(p, &dn); - if (r < 0) - return r; - - t = strjoin(dn, "/.#", fn, ".lck"); + if (dn) + t = strjoin(dn, "/.#", fn, ".lck"); + else + t = strjoin(".#", fn, ".lck"); if (!t) return -ENOMEM; diff --git a/src/basic/tmpfile-util.c b/src/basic/tmpfile-util.c index bbfd4f58ab6..2be44dcd777 100644 --- a/src/basic/tmpfile-util.c +++ b/src/basic/tmpfile-util.c @@ -161,17 +161,12 @@ static int tempfn_build(const char *p, const char *pre, const char *post, bool c if (!d) return -ENOMEM; } else { - r = path_extract_directory(p, &d); - if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */ - return r; - - r = path_extract_filename(p, &fn); + r = path_split_prefix_filename(p, &d, &fn); if (r < 0) return r; - if (strlen(fn) > NAME_MAX - len_add) - /* We cannot simply prepend and append strings to the filename. Let's truncate the filename. */ - fn[NAME_MAX - len_add] = '\0'; + /* Truncate the filename if it would become too long after mangling. */ + strshorten(fn, NAME_MAX - len_add); } nf = strjoin(".#", strempty(pre), strempty(fn), strempty(post)); diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 44feae5ab60..12a4f59c28a 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -922,6 +922,7 @@ int blockdev_get_root(int level, dev_t *ret) { } int partition_node_of(const char *node, unsigned nr, char **ret) { + _cleanup_free_ char *fn = NULL, *dn = NULL; int r; assert(node); @@ -931,18 +932,12 @@ int partition_node_of(const char *node, unsigned nr, char **ret) { /* Given a device node path to a block device returns the device node path to the partition block * device of the specified partition */ - _cleanup_free_ char *fn = NULL; - r = path_extract_filename(node, &fn); + r = path_split_prefix_filename(node, &dn, &fn); if (r < 0) return r; if (r == O_DIRECTORY) return -EISDIR; - _cleanup_free_ char *dn = NULL; - r = path_extract_directory(node, &dn); - if (r < 0 && r != -EDESTADDRREQ) /* allow if only filename is specified */ - return r; - size_t l = strlen(fn); assert(l > 0); /* underflow check for the subtraction below */ diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index a54883744fe..2aac4d253bb 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -480,14 +480,13 @@ int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) { if (!path_is_absolute(e)) return -EINVAL; - r = path_extract_directory(e, &_dirname); - if (r < 0) - return r; - - r = path_extract_filename(e, &_filename); + r = path_split_prefix_filename(e, &_dirname, &_filename); if (r < 0) return r; + if (r == O_DIRECTORY) + return -EINVAL; + /* We validate that the path is absolute above, hence dirname must be extractable. */ dirname = _dirname; filename = _filename; } else { @@ -498,7 +497,8 @@ int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) { assert(dirname); assert(filename); - mkdir_parents(dirname, 0755); + (void) mkdir_parents(dirname, 0755); + dfd = open_mkdir(dirname, O_CLOEXEC, 0755); if (dfd < 0) return log_debug_errno(dfd, "Failed to create or open directory '%s': %m", dirname); diff --git a/src/shared/generator.c b/src/shared/generator.c index a419fa4e187..603e969436e 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -109,13 +109,9 @@ int generator_add_symlink_full( * * If is specified, then must be a template unit name, and we'll instantiate it. */ - r = path_extract_directory(src, &dn); - if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */ - return log_error_errno(r, "Failed to extract directory name from '%s': %m", src); - - r = path_extract_filename(src, &fn); + r = path_split_prefix_filename(src, &dn, &fn); if (r < 0) - return log_error_errno(r, "Failed to extract file name from '%s': %m", src); + return log_error_errno(r, "Failed to split '%s' into directory prefix and filename: %m", src); if (r == O_DIRECTORY) return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Expected path to regular file name, but got '%s', refusing.", src); diff --git a/src/shared/unit-file.c b/src/shared/unit-file.c index c5f35dba169..f056b679a9c 100644 --- a/src/shared/unit-file.c +++ b/src/shared/unit-file.c @@ -291,17 +291,15 @@ int unit_file_resolve_symlink( dir, dir ? "/" : "", filename); if (!dir) { - r = path_extract_directory(filename, &_dir); - if (r < 0) - return r; - dir = _dir; - - r = path_extract_filename(filename, &_filename); + r = path_split_prefix_filename(filename, &_dir, &_filename); if (r < 0) return r; if (r == O_DIRECTORY) return log_warning_errno(SYNTHETIC_ERRNO(EISDIR), "Unexpected path to a directory \"%s\", refusing.", filename); + + /* We validate that the path is absolute above, hence dir must be extractable. */ + dir = ASSERT_PTR(_dir); filename = _filename; }