From de0da85d41b207b850aa0f68bb2436525389cf2b Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Feb 2026 19:32:51 +0100 Subject: [PATCH 01/11] boot/efi-string: use QUOTES macro, shorten the code a bit Follow-up for a8f2f5d71786c2cf36e32f856cc329413a76cd93 --- src/boot/efi-string.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/efi-string.c b/src/boot/efi-string.c index ca077a5096..ee430c1b36 100644 --- a/src/boot/efi-string.c +++ b/src/boot/efi-string.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "efi-string.h" +#include "string-util-fundamental.h" #if SD_BOOT # include "proto/simple-text-io.h" @@ -497,8 +498,7 @@ char* line_get_key_value(char *s, const char *sep, size_t *pos, char **ret_key, value++; /* unquote */ - if ((value[0] == '"' && line[linelen - 1] == '"') || - (value[0] == '\'' && line[linelen - 1] == '\'')) { + if (strchr8(QUOTES, value[0]) && line[linelen - 1] == value[0]) { value++; line[linelen - 1] = '\0'; } From 7dc7cc02fb40e50f63a63451a27c75e7ea8bf1d5 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 2 Feb 2026 10:50:40 +0100 Subject: [PATCH 02/11] portable-util: drop spurious -ENXIO check for xdg_user_config_dir() Unlike $XDG_RUNTIME_DIR the user config dir has a default value, hence -ENXIO is not really expected. --- src/shared/portable-util.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/shared/portable-util.c b/src/shared/portable-util.c index 078d311dda..d2c03aecb9 100644 --- a/src/shared/portable-util.c +++ b/src/shared/portable-util.c @@ -29,13 +29,12 @@ int portable_profile_dirs(RuntimeScope scope, char ***ret) { _cleanup_free_ char *d = NULL; r = xdg_user_config_dir("systemd/portable/profile", &d); - if (r < 0 && r != -ENXIO) + if (r < 0) + return r; + + r = strv_consume(&dirs, TAKE_PTR(d)); + if (r < 0) return r; - if (r >= 0) { - r = strv_consume(&dirs, TAKE_PTR(d)); - if (r < 0) - return r; - } r = xdg_user_runtime_dir("systemd/portable/profile", &d); if (r < 0 && r != -ENXIO) From 3065bc80cb5d30961af256bc6158a69fce8a5030 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 29 Jan 2026 10:49:56 +0100 Subject: [PATCH 03/11] shared/find-esp: STATX_MNT_ID is no longer needed as we rely on _ATTR_MOUNT_ROOT Follow-up for a98a6eb95cc980edab4b0f9c59e6573edc7ffe0c --- src/shared/find-esp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index cf4eddbbfa..f2df84cd48 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -287,7 +287,7 @@ static int verify_fsroot_dir( if (statx(dir_fd, strempty(f), AT_SYMLINK_NOFOLLOW|(isempty(f) ? AT_EMPTY_PATH : 0), - STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx) < 0) + STATX_TYPE|STATX_INO, &sx) < 0) return log_full_errno((searching && errno == ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(errno)) ? LOG_DEBUG : LOG_ERR, errno, "Failed to determine block device node of \"%s\": %m", path); From 539e2b75aabe8daf0baea65a346ee44584954914 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 29 Jan 2026 11:27:40 +0100 Subject: [PATCH 04/11] fd-util: add generic resolve_xat_fdroot() As suggested in https://github.com/systemd/systemd/pull/40500#discussion_r2740921215 --- src/basic/fd-util.c | 26 ++++++++++++++++++++++++++ src/basic/fd-util.h | 2 ++ src/basic/stat-util.c | 16 +++------------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index f80ce0ae47..197d015f37 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1105,6 +1105,32 @@ int fds_are_same_mount(int fd1, int fd2) { return statx_inode_same(&sx1, &sx2) && statx_mount_same(&sx1, &sx2); } +int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer) { + assert(fd); + assert(path); + assert(ret_buffer); + + if (*fd != XAT_FDROOT) { + *ret_buffer = NULL; + return 0; + } + + if (isempty(*path)) { + *path = "/"; + *ret_buffer = NULL; + } else if (!path_is_absolute(*path)) { + char *p = strjoin("/", *path); + if (!p) + return -ENOMEM; + + *path = *ret_buffer = p; + } + + *fd = AT_FDCWD; + + return 1; +} + char* format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int fd) { assert(buf); assert(fd >= 0); diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 707445d73d..ee1dc870df 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -181,6 +181,8 @@ static inline int dir_fd_is_root_or_cwd(int dir_fd) { int fds_are_same_mount(int fd1, int fd2); +int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer); + /* The maximum length a buffer for a /proc/self/fd/ path needs */ #define PROC_FD_PATH_MAX \ (STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 6d1796c315..2e2f929fc0 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -36,19 +36,9 @@ static int verify_stat_at( assert(verify_func); _cleanup_free_ char *p = NULL; - if (fd == XAT_FDROOT) { - fd = AT_FDCWD; - - if (isempty(path)) - path = "/"; - else if (!path_is_absolute(path)) { - p = strjoin("/", path); - if (!p) - return -ENOMEM; - - path = p; - } - } + r = resolve_xat_fdroot(&fd, &path, &p); + if (r < 0) + return r; if (fstatat(fd, strempty(path), &st, (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0) From 6b039272e95682dcbd17974119acdbfd92842e07 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Feb 2026 23:30:42 +0100 Subject: [PATCH 05/11] basic: add generated statx_mask_one_to_name()/statx_attribute_to_name() --- src/basic/generate-statx-attribute-list.sh | 10 +++++ src/basic/generate-statx-mask-list.sh | 11 +++++ src/basic/meson.build | 52 ++++++++++++---------- src/basic/statx-attribute-to-name.awk | 14 ++++++ src/basic/statx-mask-to-name.awk | 14 ++++++ src/include/meson.build | 5 +++ 6 files changed, 83 insertions(+), 23 deletions(-) create mode 100755 src/basic/generate-statx-attribute-list.sh create mode 100755 src/basic/generate-statx-mask-list.sh create mode 100644 src/basic/statx-attribute-to-name.awk create mode 100644 src/basic/statx-mask-to-name.awk diff --git a/src/basic/generate-statx-attribute-list.sh b/src/basic/generate-statx-attribute-list.sh new file mode 100755 index 0000000000..9e0ba45045 --- /dev/null +++ b/src/basic/generate-statx-attribute-list.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eu +set -o pipefail + +CC=${1:?} +shift + +$CC -E -dM -include linux/stat.h "$@" - '], ], - ['arphrd', arphrd_sources, 'ARPHRD_', [''], ], - ['capability', capability_sources, '', [''], ], - ['errno', [], '', [''], ], + ['af', af_sources, '', [''], ], + ['arphrd', arphrd_sources, 'ARPHRD_', [''], ], + ['capability', capability_sources, '', [''], ], + ['errno', [], '', [''], ], + ['statx-mask', statx_sources ], + ['statx-attribute', statx_sources ], ] generate_list = files('generate-@0@-list.sh'.format(item[0])) @@ -138,31 +140,35 @@ foreach item : [ command : [env, 'bash', generate_list, cpp, system_include_args], capture : true) - gperf_file = custom_target( - input : list_txt, - output : '@0@-from-name.gperf'.format(item[0]), - command : [generate_gperfs, item[0], item[2], '@INPUT@'] + item[3], - capture : true) - - target1 = custom_target( - input : gperf_file, - output : '@0@-from-name.inc'.format(item[0]), - command : [gperf, - '-L', 'ANSI-C', '-t', '--ignore-case', - '-N', 'lookup_@0@'.format(item[0]), - '-H', 'hash_@0@_name'.format(item[0]), - '-p', '-C', - '@INPUT@'], - capture : true) - awkscript = '@0@-to-name.awk'.format(item[0]) - target2 = custom_target( + target = custom_target( input : [awkscript, list_txt], output : '@0@-to-name.inc'.format(item[0]), command : [awk, '-f', '@INPUT0@', '@INPUT1@'], capture : true) - generated_gperf_headers += [target1, target2] + generated_gperf_headers += [target] + + if item.length() > 2 + gperf_file = custom_target( + input : list_txt, + output : '@0@-from-name.gperf'.format(item[0]), + command : [generate_gperfs, item[0], item[2], '@INPUT@'] + item[3], + capture : true) + + target = custom_target( + input : gperf_file, + output : '@0@-from-name.inc'.format(item[0]), + command : [gperf, + '-L', 'ANSI-C', '-t', '--ignore-case', + '-N', 'lookup_@0@'.format(item[0]), + '-H', 'hash_@0@_name'.format(item[0]), + '-p', '-C', + '@INPUT@'], + capture : true) + + generated_gperf_headers += [target] + endif endforeach generated_sources += generated_gperf_headers diff --git a/src/basic/statx-attribute-to-name.awk b/src/basic/statx-attribute-to-name.awk new file mode 100644 index 0000000000..807b57df74 --- /dev/null +++ b/src/basic/statx-attribute-to-name.awk @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +BEGIN{ + print "const char* statx_attribute_to_name(uint64_t attr) {" + print " switch (attr) {" +} +{ + printf " case %s: return \"%s\";\n", $1, $1 +} +END{ + print " default: return NULL;" + print " }" + print "}" +} diff --git a/src/basic/statx-mask-to-name.awk b/src/basic/statx-mask-to-name.awk new file mode 100644 index 0000000000..77ebb76faa --- /dev/null +++ b/src/basic/statx-mask-to-name.awk @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +BEGIN{ + print "const char* statx_mask_one_to_name(unsigned mask) {" + print " switch (mask) {" +} +{ + printf " case %s: return \"%s\";\n", $1, $1 +} +END{ + print " default: return NULL;" + print " }" + print "}" +} diff --git a/src/include/meson.build b/src/include/meson.build index 90c8e0fdf8..9cf7e4dbc8 100644 --- a/src/include/meson.build +++ b/src/include/meson.build @@ -43,3 +43,8 @@ keyboard_sources = files( 'uapi/linux/input.h', 'uapi/linux/input-event-codes.h', ) + +# Source files that provides STATX_* +statx_sources = files( + 'uapi/linux/stat.h', +) From 1cf84c56429873813944704a1cf6056351d7c1de Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 29 Jan 2026 09:23:35 +0100 Subject: [PATCH 06/11] stat-util: introduce xstatx() --- src/basic/stat-util.c | 92 +++++++++++++++++++++++++++++++++++++++++++ src/basic/stat-util.h | 18 +++++++++ 2 files changed, 110 insertions(+) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 2e2f929fc0..7413f51505 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -6,6 +6,7 @@ #include #include "alloc-util.h" +#include "bitfield.h" #include "chase.h" #include "dirent-util.h" #include "errno-util.h" @@ -235,6 +236,97 @@ int null_or_empty_path_with_root(const char *fn, const char *root) { return null_or_empty(&st); } +static const char* statx_mask_one_to_name(unsigned mask); +static const char* statx_attribute_to_name(uint64_t attr); + +#include "statx-attribute-to-name.inc" +#include "statx-mask-to-name.inc" + +#define DEFINE_STATX_BITS_TO_STRING(prefix, type, func, format_str) \ + static char* prefix##_to_string(type v) { \ + if (v == 0) \ + return strdup(""); \ + \ + _cleanup_free_ char *s = NULL; \ + \ + BIT_FOREACH(i, v) { \ + type f = 1 << i; \ + \ + const char *n = func(f); \ + if (!n) \ + continue; \ + \ + if (!strextend_with_separator(&s, "|", n)) \ + return NULL; \ + v &= ~f; \ + } \ + \ + if (v != 0 && strextendf_with_separator(&s, "|", format_str, v) < 0) \ + return NULL; \ + \ + return TAKE_PTR(s); \ + } + +DEFINE_STATX_BITS_TO_STRING(statx_mask, unsigned, statx_mask_one_to_name, "0x%x"); +DEFINE_STATX_BITS_TO_STRING(statx_attributes, uint64_t, statx_attribute_to_name, "0x%" PRIx64); + +int xstatx_full(int fd, + const char *path, + int flags, + unsigned mandatory_mask, + unsigned optional_mask, + uint64_t mandatory_attributes, + struct statx *ret) { + + struct statx sx = {}; /* explicitly initialize the struct to make msan silent. */ + int r; + + /* Wrapper around statx(), with additional bells and whistles: + * + * 1. AT_EMPTY_PATH is implied on empty path + * 2. Supports XAT_FDROOT + * 3. Takes separate mandatory and optional mask params, plus mandatory attributes. + * Returns -EUNATCH if statx() does not return all masks specified as mandatory, + * > 0 if all optional masks are supported, 0 otherwise. + */ + + assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); + assert((mandatory_mask & optional_mask) == 0); + assert(ret); + + _cleanup_free_ char *p = NULL; + r = resolve_xat_fdroot(&fd, &path, &p); + if (r < 0) + return r; + + if (statx(fd, strempty(path), + flags|(isempty(path) ? AT_EMPTY_PATH : 0), + mandatory_mask|optional_mask, + &sx) < 0) + return negative_errno(); + + if (!FLAGS_SET(sx.stx_mask, mandatory_mask)) { + if (DEBUG_LOGGING) { + _cleanup_free_ char *mask_str = statx_mask_to_string(mandatory_mask & ~sx.stx_mask); + log_debug("statx() does not support '%s' mask (running on an old kernel?)", strnull(mask_str)); + } + + return -EUNATCH; + } + + if (!FLAGS_SET(sx.stx_attributes_mask, mandatory_attributes)) { + if (DEBUG_LOGGING) { + _cleanup_free_ char *attr_str = statx_attributes_to_string(mandatory_attributes & ~sx.stx_attributes_mask); + log_debug("statx() does not support '%s' attribute (running on an old kernel?)", strnull(attr_str)); + } + + return -EUNATCH; + } + + *ret = sx; + return FLAGS_SET(sx.stx_mask, optional_mask); +} + static int xfstatfs(int fd, struct statfs *ret) { assert(ret); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 11d0fdc7ff..77d9f55c63 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -41,6 +41,24 @@ static inline int null_or_empty_path(const char *fn) { return null_or_empty_path_with_root(fn, NULL); } +int xstatx_full(int fd, + const char *path, + int flags, + unsigned mandatory_mask, + unsigned optional_mask, + uint64_t mandatory_attributes, + struct statx *ret); + +static inline int xstatx( + int fd, + const char *path, + int flags, + unsigned mandatory_mask, + struct statx *ret) { + + return xstatx_full(fd, path, flags, mandatory_mask, 0, 0, ret); +} + int fd_is_read_only_fs(int fd); int path_is_read_only_fs(const char *path); From 56adac11935455e00a3127cf0d7676bae7bcc9fe Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 29 Jan 2026 10:20:00 +0100 Subject: [PATCH 07/11] mountpoint-util: port to xstatx(), support XAT_FDROOT --- src/basic/mountpoint-util.c | 80 +++++++++++++++---------------------- 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index d8f57c6b7a..1f90217dc4 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -246,31 +246,24 @@ struct file_handle* file_handle_dup(const struct file_handle *fh) { int is_mount_point_at(int dir_fd, const char *path, int flags) { int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert((flags & ~AT_SYMLINK_FOLLOW) == 0); if (path_equal(path, "/")) return true; - if (isempty(path)) { - if (dir_fd == AT_FDCWD) - path = "."; - else { - flags |= AT_EMPTY_PATH; - path = ""; - } - } + if (dir_fd == XAT_FDROOT && isempty(path)) + return true; - struct statx sx = {}; /* explicitly initialize the struct to make msan silent. */ - if (statx(dir_fd, path, - at_flags_normalize_nofollow(flags) | - AT_NO_AUTOMOUNT | /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */ - AT_STATX_DONT_SYNC, /* don't go to the network for this – for similar reasons */ - STATX_TYPE|STATX_INO, - &sx) < 0) - return -errno; - - r = statx_warn_mount_root(&sx, LOG_DEBUG); + struct statx sx; + r = xstatx_full(dir_fd, path, + at_flags_normalize_nofollow(flags) | + AT_NO_AUTOMOUNT | /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */ + AT_STATX_DONT_SYNC, /* don't go to the network for this – for similar reasons */ + STATX_TYPE|STATX_INO, + /* optional_mask = */ 0, + STATX_ATTR_MOUNT_ROOT, + &sx); if (r < 0) return r; @@ -307,23 +300,19 @@ int path_is_mount_point_full(const char *path, const char *root, int flags) { return is_mount_point_at(dir_fd, /* path= */ NULL, flags); } -int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) { +static int path_get_mnt_id_at_internal(int dir_fd, const char *path, bool unique, uint64_t *ret) { struct statx sx; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(ret); - if (statx(dir_fd, - strempty(path), - (isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW) | - AT_NO_AUTOMOUNT | /* don't trigger automounts, mnt_id is a local concept */ - AT_STATX_DONT_SYNC, /* don't go to the network, mnt_id is a local concept */ - STATX_MNT_ID, - &sx) < 0) - return -errno; - - r = statx_warn_mount_id(&sx, LOG_DEBUG); + r = xstatx(dir_fd, path, + AT_SYMLINK_NOFOLLOW | + AT_NO_AUTOMOUNT | /* don't trigger automounts, mnt_id is a local concept */ + AT_STATX_DONT_SYNC, /* don't go to the network, mnt_id is a local concept */ + unique ? STATX_MNT_ID_UNIQUE : STATX_MNT_ID, + &sx); if (r < 0) return r; @@ -331,28 +320,23 @@ int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) { return 0; } -int path_get_unique_mnt_id_at(int dir_fd, const char *path, uint64_t *ret) { - struct statx sx; +int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) { + uint64_t mnt_id; + int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(ret); + r = path_get_mnt_id_at_internal(dir_fd, path, /* unique = */ false, &mnt_id); + if (r < 0) + return r; - if (statx(dir_fd, - strempty(path), - (isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW) | - AT_NO_AUTOMOUNT | /* don't trigger automounts, mnt_id is a local concept */ - AT_STATX_DONT_SYNC, /* don't go to the network, mnt_id is a local concept */ - STATX_MNT_ID_UNIQUE, - &sx) < 0) - return -errno; - - if (!FLAGS_SET(sx.stx_mask, STATX_MNT_ID_UNIQUE)) - return -EOPNOTSUPP; - - *ret = sx.stx_mnt_id; + assert(mnt_id <= INT_MAX); + *ret = (int) mnt_id; return 0; } +int path_get_unique_mnt_id_at(int dir_fd, const char *path, uint64_t *ret) { + return path_get_mnt_id_at_internal(dir_fd, path, /* unique = */ true, ret); +} + bool fstype_is_network(const char *fstype) { const char *x; From 5817c73391b5f3599c50df2c0873b26ea426f848 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 5 Feb 2026 01:32:59 +0100 Subject: [PATCH 08/11] mountpoint-util: rework name_to_handle_at() unique mount id handling name_to_handle_at_try_unique_mntid_fid() in its current form is ill-designed for various reasons: * AT_HANDLE_FID requires file system support, while unique mount id is a VFS concept hence is always available if supported. Hence the fallback for AT_HANDLE_MNT_ID_UNIQUE should be independent of fid. * The request for AT_HANDLE_MNT_ID_UNIQUE can be identified via specifying ret_unique_mnt_id, no need for opening up the control to caller (and currently the function simply doesn't handle mismatch between ret params and flags). * The caller cannot realistically differentiate whether the returned mount id is actually unique. * The path_get_unique_mnt_id() fallback did not handle AT_SYMLINK_FOLLOW. Let's instead move the statx() fallback into name_to_handle_at_loop() directly, and revamp interaction of ret_mnt_id/ret_unique_mnt_id: if both are set, it indicates that the caller can handle both, hence set what we have and return 0/1 for whether we managed to acquire the unique one. The !ret_handle && ret_mnt_id logic is removed. Let's not rely on undocumented bizaare behavior and it's unused anyways. path_get_mnt_id_at() exists for a reason... --- src/basic/mountpoint-util.c | 132 +++++++++++++---------------- src/basic/mountpoint-util.h | 3 +- src/basic/stat-util.c | 11 ++- src/libsystemd/sd-json/json-util.c | 2 +- src/shared/discover-image.c | 26 ++---- src/shared/tar-util.c | 20 ++++- 6 files changed, 97 insertions(+), 97 deletions(-) diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 1f90217dc4..5f62e383a4 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -53,27 +53,29 @@ int name_to_handle_at_loop( uint64_t *ret_unique_mnt_id, int flags) { - size_t n = ORIGINAL_MAX_HANDLE_SZ; + int r; assert(fd >= 0 || fd == AT_FDCWD); - assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH|AT_HANDLE_FID|AT_HANDLE_MNT_ID_UNIQUE)) == 0); + assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH|AT_HANDLE_FID)) == 0); /* We need to invoke name_to_handle_at() in a loop, given that it might return EOVERFLOW when the specified * buffer is too small. Note that in contrast to what the docs might suggest, MAX_HANDLE_SZ is only good as a * start value, it is not an upper bound on the buffer size required. * * This improves on raw name_to_handle_at() also in one other regard: ret_handle and ret_mnt_id can be passed - * as NULL if there's no interest in either. */ + * as NULL if there's no interest in either. + * + * If unique mount id is requested via ret_unique_mnt_id, try AT_HANDLE_MNT_ID_UNIQUE flag first + * (needs kernel v6.12), and fall back to statx() if not supported. If neither worked, and caller + * also specifies ret_mnt_id, then the old-style mount id is returned, -EUNATCH otherwise. */ if (isempty(path)) { flags |= AT_EMPTY_PATH; path = ""; } - for (;;) { + for (size_t n = ORIGINAL_MAX_HANDLE_SZ;;) { _cleanup_free_ struct file_handle *h = NULL; - int mnt_id = -1, r; - uint64_t unique_mnt_id = 0; h = malloc0(offsetof(struct file_handle, f_handle) + n); if (!h) @@ -81,18 +83,61 @@ int name_to_handle_at_loop( h->handle_bytes = n; - if (FLAGS_SET(flags, AT_HANDLE_MNT_ID_UNIQUE)) + if (ret_unique_mnt_id) { + uint64_t mnt_id; + /* The kernel will still use this as uint64_t pointer */ - r = name_to_handle_at(fd, path, h, (int *) &unique_mnt_id, flags); - else - r = name_to_handle_at(fd, path, h, &mnt_id, flags); + r = name_to_handle_at(fd, path, h, (int *) &mnt_id, flags|AT_HANDLE_MNT_ID_UNIQUE); + if (r >= 0) { + if (ret_handle) + *ret_handle = TAKE_PTR(h); + + *ret_unique_mnt_id = mnt_id; + + if (ret_mnt_id) + *ret_mnt_id = -1; + + return 1; + } + if (errno == EOVERFLOW) + goto grow; + if (errno != EINVAL) + return -errno; + } + + int mnt_id; + r = name_to_handle_at(fd, path, h, &mnt_id, flags); if (r >= 0) { + if (ret_unique_mnt_id) { + /* Hmm, AT_HANDLE_MNT_ID_UNIQUE is not supported? Let's try to acquire + * the unique mount id from statx() then, which has a slightly lower + * kernel version requirement (6.8 vs 6.12). */ + + struct statx sx; + r = xstatx(fd, path, + at_flags_normalize_nofollow(flags & (AT_SYMLINK_FOLLOW|AT_EMPTY_PATH))|AT_STATX_DONT_SYNC, + STATX_MNT_ID_UNIQUE, + &sx); + if (r >= 0) { + if (ret_handle) + *ret_handle = TAKE_PTR(h); + + *ret_unique_mnt_id = sx.stx_mnt_id; + + if (ret_mnt_id) + *ret_mnt_id = -1; + + return 1; + } + if (r != -EUNATCH || !ret_mnt_id) + return r; + + *ret_unique_mnt_id = 0; + } if (ret_handle) *ret_handle = TAKE_PTR(h); - if (ret_unique_mnt_id) - *ret_unique_mnt_id = unique_mnt_id; if (ret_mnt_id) *ret_mnt_id = mnt_id; @@ -101,19 +146,7 @@ int name_to_handle_at_loop( if (errno != EOVERFLOW) return -errno; - if (!ret_handle && ((ret_mnt_id && mnt_id >= 0) || (ret_unique_mnt_id && unique_mnt_id > 0))) { - - /* As it appears, name_to_handle_at() fills in mnt_id even when it returns EOVERFLOW when the - * buffer is too small, but that's undocumented. Hence, let's make use of this if it appears to - * be filled in, and the caller was interested in only the mount ID an nothing else. */ - - if (ret_unique_mnt_id) - *ret_unique_mnt_id = unique_mnt_id; - if (ret_mnt_id) - *ret_mnt_id = mnt_id; - return 0; - } - + grow: /* If name_to_handle_at() didn't increase the byte size, then this EOVERFLOW is caused by * something else (apparently EOVERFLOW is returned for untriggered nfs4 autofs mounts * sometimes), not by the too small buffer. In that case propagate EOVERFLOW */ @@ -134,6 +167,7 @@ int name_to_handle_at_try_fid( const char *path, struct file_handle **ret_handle, int *ret_mnt_id, + uint64_t *ret_unique_mnt_id, int flags) { int r; @@ -144,55 +178,11 @@ int name_to_handle_at_try_fid( * we'll try without the flag, in order to support older kernels that didn't have AT_HANDLE_FID * (i.e. older than Linux 6.5). */ - r = name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, /* ret_unique_mnt_id= */ NULL, flags | AT_HANDLE_FID); + r = name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, ret_unique_mnt_id, flags | AT_HANDLE_FID); if (r >= 0 || is_name_to_handle_at_fatal_error(r)) return r; - return name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, /* ret_unique_mnt_id= */ NULL, flags & ~AT_HANDLE_FID); -} - -int name_to_handle_at_try_unique_mntid_fid( - int fd, - const char *path, - struct file_handle **ret_handle, - uint64_t *ret_mnt_id, - int flags) { - - int mnt_id = -1, r; - - assert(fd >= 0 || fd == AT_FDCWD); - - /* First issues name_to_handle_at() with AT_HANDLE_MNT_ID_UNIQUE and AT_HANDLE_FID. - * If this fails and this is not a fatal error we'll try without the - * AT_HANDLE_MNT_ID_UNIQUE flag because it's only available from Linux 6.12 onwards. */ - r = name_to_handle_at_loop(fd, path, ret_handle, /* ret_mnt_id= */ NULL, ret_mnt_id, flags | AT_HANDLE_MNT_ID_UNIQUE | AT_HANDLE_FID); - if (r >= 0 || is_name_to_handle_at_fatal_error(r)) - return r; - - flags &= ~AT_HANDLE_MNT_ID_UNIQUE; - - /* Then issues name_to_handle_at() with AT_HANDLE_FID. If this fails and this is not a fatal error - * we'll try without the flag, in order to support older kernels that didn't have AT_HANDLE_FID - * (i.e. older than Linux 6.5). */ - - r = name_to_handle_at_loop(fd, path, ret_handle, &mnt_id, /* ret_unique_mnt_id= */ NULL, flags | AT_HANDLE_FID); - if (r < 0 && is_name_to_handle_at_fatal_error(r)) - return r; - if (r >= 0) { - if (ret_mnt_id && mnt_id >= 0) { - /* See if we can do better because statx can do unique mount IDs since Linux 6.8 - * and only if this doesn't work we use the non-unique mnt_id as returned. */ - if (path_get_unique_mnt_id_at(fd, path, ret_mnt_id) < 0) - *ret_mnt_id = mnt_id; - } - - return r; - } - - r = name_to_handle_at_loop(fd, path, ret_handle, &mnt_id, /* ret_unique_mnt_id= */ NULL, flags & ~AT_HANDLE_FID); - if (ret_mnt_id && mnt_id >= 0) - *ret_mnt_id = mnt_id; - return r; + return name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, ret_unique_mnt_id, flags & ~AT_HANDLE_FID); } int name_to_handle_at_u64(int fd, const char *path, uint64_t *ret) { diff --git a/src/basic/mountpoint-util.h b/src/basic/mountpoint-util.h index 82eb1516c3..1381f3d8ab 100644 --- a/src/basic/mountpoint-util.h +++ b/src/basic/mountpoint-util.h @@ -35,8 +35,7 @@ bool is_name_to_handle_at_fatal_error(int err); int name_to_handle_at_loop(int fd, const char *path, struct file_handle **ret_handle, int *ret_mnt_id, uint64_t *ret_unique_mnt_id, int flags); -int name_to_handle_at_try_fid(int fd, const char *path, struct file_handle **ret_handle, int *ret_mnt_id, int flags); -int name_to_handle_at_try_unique_mntid_fid(int fd, const char *path, struct file_handle **ret_handle, uint64_t *ret_mnt_id, int flags); +int name_to_handle_at_try_fid(int fd, const char *path, struct file_handle **ret_handle, int *ret_mnt_id, uint64_t *ret_unique_mnt_id, int flags); int name_to_handle_at_u64(int fd, const char *path, uint64_t *ret); static inline int path_to_handle_u64(const char *path, uint64_t *ret) { return name_to_handle_at_u64(AT_FDCWD, path, ret); diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 7413f51505..703cf4fc92 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -431,12 +431,14 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl int ntha_flags = at_flags_normalize_follow(flags) & (AT_EMPTY_PATH|AT_SYMLINK_FOLLOW); _cleanup_free_ struct file_handle *ha = NULL, *hb = NULL; - int mntida = -1, mntidb = -1; + uint64_t mntida, mntidb; + int _mntida, _mntidb; r = name_to_handle_at_try_fid( fda, filea, &ha, + &_mntida, &mntida, ntha_flags); if (r < 0) { @@ -445,12 +447,15 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl goto fallback; } + if (r == 0) + mntida = _mntida; r = name_to_handle_at_try_fid( fdb, fileb, &hb, - &mntidb, + r > 0 ? NULL : &_mntidb, /* if we managed to get unique mnt id for a, insist on that for b */ + r > 0 ? &mntidb : NULL, ntha_flags); if (r < 0) { if (is_name_to_handle_at_fatal_error(r)) @@ -458,6 +463,8 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl goto fallback; } + if (r == 0) + mntidb = _mntidb; /* Now compare the two file handles */ if (!file_handle_equal(ha, hb)) diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index 30dbfde37b..41ac8c4097 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -685,7 +685,7 @@ int json_variant_new_fd_info(sd_json_variant **ret, int fd) { if (r < 0) return r; - r = name_to_handle_at_try_fid(fd, "", &fid, &mntid, AT_EMPTY_PATH); + r = name_to_handle_at_try_fid(fd, "", &fid, &mntid, /* ret_unique_mnt_id = */ NULL, AT_EMPTY_PATH); if (r < 0 && is_name_to_handle_at_fatal_error(r)) return r; diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index d80ecdaf7f..885b896de4 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -451,27 +451,15 @@ static int image_make( path_startswith(path, "/usr") || (faccessat(fd, "", W_OK, AT_EACCESS|AT_EMPTY_PATH) < 0 && errno == EROFS); - uint64_t on_mount_id = 0; _cleanup_free_ struct file_handle *fh = NULL; + uint64_t on_mount_id; + int _mnt_id; - r = name_to_handle_at_try_unique_mntid_fid(fd, /* path= */ NULL, &fh, &on_mount_id, /* flags= */ 0); - if (r < 0) { - if (is_name_to_handle_at_fatal_error(r)) - return r; - - r = path_get_unique_mnt_id_at(fd, /* path= */ NULL, &on_mount_id); - if (r < 0) { - if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) - return r; - - int on_mount_id_fallback = -1; - r = path_get_mnt_id_at(fd, /* path= */ NULL, &on_mount_id_fallback); - if (r < 0) - return r; - - on_mount_id = on_mount_id_fallback; - } - } + r = name_to_handle_at_try_fid(fd, /* path= */ NULL, &fh, &_mnt_id, &on_mount_id, AT_EMPTY_PATH); + if (r < 0) + return r; + if (r == 0) + on_mount_id = _mnt_id; if (S_ISDIR(st->st_mode)) { unsigned file_attr = 0; diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 9436eff868..7fe4544c3c 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -1015,8 +1015,10 @@ static int make_tmpfs(void) { struct make_archive_data { struct archive *archive; TarFlags flags; + int hardlink_db_fd; char *hardlink_db_path; + int have_unique_mount_id; }; static int hardlink_lookup( @@ -1042,16 +1044,29 @@ static int hardlink_lookup( if (FLAGS_SET(sx->stx_mask, STATX_TYPE) && !inode_type_can_hardlink(sx->stx_mode)) goto bypass; + uint64_t unique_mnt_id; int mnt_id; - r = name_to_handle_at_try_fid(inode_fd, /* path= */ NULL, &handle, &mnt_id, /* flags= */ AT_EMPTY_PATH); + r = name_to_handle_at_try_fid(inode_fd, /* path= */ NULL, + &handle, + d->have_unique_mount_id <= 0 ? &mnt_id : NULL, + d->have_unique_mount_id != 0 ? &unique_mnt_id : NULL, + /* flags= */ AT_EMPTY_PATH); if (r < 0) return log_error_errno(r, "Failed to get file handle of file: %m"); + if (d->have_unique_mount_id < 0) + d->have_unique_mount_id = r > 0; + else + assert(d->have_unique_mount_id == (r > 0)); m = hexmem(SHA256_DIRECT(handle->f_handle, handle->handle_bytes), SHA256_DIGEST_SIZE); if (!m) return log_oom(); - if (asprintf(&n, "%i:%i:%s", mnt_id, handle->handle_type, m) < 0) + if (d->have_unique_mount_id) + r = asprintf(&n, "%" PRIu64 ":%i:%s", unique_mnt_id, handle->handle_type, m); + else + r = asprintf(&n, "%i:%i:%s", mnt_id, handle->handle_type, m); + if (r < 0) return log_oom(); if (d->hardlink_db_fd < 0) { @@ -1467,6 +1482,7 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) { .archive = a, .flags = flags, .hardlink_db_fd = -EBADF, + .have_unique_mount_id = -1, }; r = recurse_dir(tree_fd, From 60464c951439dbf85c3b52bced7c98600dea0b8c Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 5 Feb 2026 05:37:29 +0100 Subject: [PATCH 09/11] README: list STATX_MNT_ID_UNIQUE and AT_HANDLE_MNT_ID_UNIQUE --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index a5e6163721..c9ab090746 100644 --- a/README +++ b/README @@ -72,9 +72,11 @@ REQUIREMENTS: and MOVE_MOUNT_BENEATH ≥ 6.6 for quota support on tmpfs ≥ 6.7 for cgroup2fs memory_hugetlb_accounting option + ≥ 6.8 for STATX_MNT_ID_UNIQUE ≥ 6.9 for pidfs ≥ 6.10 for fcntl(F_DUPFD_QUERY), unprivileged linkat(AT_EMPTY_PATH), and block device 'partscan' sysfs attribute + ≥ 6.12 for AT_HANDLE_MNT_ID_UNIQUE ≥ 6.13 for PIDFD_GET_INFO and {set,remove}xattrat() ≥ 6.16 for coredump pattern '%F' (pidfd) specifier and SO_PASSRIGHTS From 10072d8be44a3687221adc042cf9b9b45b4c9689 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 29 Jan 2026 10:48:43 +0100 Subject: [PATCH 10/11] various: use xstatx() --- src/basic/fd-util.c | 31 +++++++++++------------------ src/basic/xattr-util.c | 15 +++++++------- src/shared/find-esp.c | 18 ++++++++--------- src/tmpfiles/tmpfiles.c | 44 +++++++++++++++-------------------------- 4 files changed, 44 insertions(+), 64 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 197d015f37..99eff99874 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1077,30 +1077,23 @@ int path_is_root_at(int dir_fd, const char *path) { } int fds_are_same_mount(int fd1, int fd2) { - struct statx sx1 = {}, sx2 = {}; /* explicitly initialize the struct to make msan silent. */ + struct statx sx1, sx2; + int r; assert(fd1 >= 0 || IN_SET(fd1, AT_FDCWD, XAT_FDROOT)); assert(fd2 >= 0 || IN_SET(fd2, AT_FDCWD, XAT_FDROOT)); - const char *fn1; - if (fd1 == XAT_FDROOT) { - fd1 = AT_FDCWD; - fn1 = "/"; - } else - fn1 = ""; + r = xstatx(fd1, /* path = */ NULL, AT_EMPTY_PATH, + STATX_TYPE|STATX_INO|STATX_MNT_ID, + &sx1); + if (r < 0) + return r; - if (statx(fd1, fn1, AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx1) < 0) - return -errno; - - const char *fn2; - if (fd2 == XAT_FDROOT) { - fd2 = AT_FDCWD; - fn2 = "/"; - } else - fn2 = ""; - - if (statx(fd2, fn2, AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx2) < 0) - return -errno; + r = xstatx(fd2, /* path = */ NULL, AT_EMPTY_PATH, + STATX_TYPE|STATX_INO|STATX_MNT_ID, + &sx2); + if (r < 0) + return r; return statx_inode_same(&sx1, &sx2) && statx_mount_same(&sx1, &sx2); } diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c index 2d0577979e..68ee83d899 100644 --- a/src/basic/xattr-util.c +++ b/src/basic/xattr-util.c @@ -419,9 +419,6 @@ int getcrtime_at( assert(fd >= 0 || fd == AT_FDCWD); assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); - if (isempty(path)) - at_flags |= AT_EMPTY_PATH; - /* So here's the deal: the creation/birth time (crtime/btime) of a file is a relatively newly supported concept * on Linux (or more strictly speaking: a concept that only recently got supported in the API, it was * implemented on various file systems on the lower level since a while, but never was accessible). However, we @@ -432,11 +429,13 @@ int getcrtime_at( * concept is useful for determining how "old" a file really is, and hence using the older of the two makes * most sense. */ - if (statx(fd, strempty(path), - at_flags_normalize_nofollow(at_flags)|AT_STATX_DONT_SYNC, - STATX_BTIME, - &sx) >= 0 && - FLAGS_SET(sx.stx_mask, STATX_BTIME) && sx.stx_btime.tv_sec != 0) + r = xstatx_full(fd, path, + at_flags_normalize_nofollow(at_flags)|AT_STATX_DONT_SYNC, + /* mandatory_mask = */ 0, + STATX_BTIME, + /* mandatory_attributes = */ 0, + &sx); + if (r > 0 && sx.stx_btime.tv_sec != 0) /* > 0: all optional masks are supported */ a = statx_timestamp_load(&sx.stx_btime); else a = USEC_INFINITY; diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index f2df84cd48..3ba9b8a4b6 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -285,20 +285,20 @@ static int verify_fsroot_dir( if (r < 0 && r != -EADDRNOTAVAIL) return log_error_errno(r, "Failed to extract filename of \"%s\": %m", path); - if (statx(dir_fd, strempty(f), - AT_SYMLINK_NOFOLLOW|(isempty(f) ? AT_EMPTY_PATH : 0), - STATX_TYPE|STATX_INO, &sx) < 0) - return log_full_errno((searching && errno == ENOENT) || - (unprivileged_mode && ERRNO_IS_PRIVILEGE(errno)) ? LOG_DEBUG : LOG_ERR, errno, + r = xstatx_full(dir_fd, f, + AT_SYMLINK_NOFOLLOW, + STATX_TYPE|STATX_INO, + /* optional_mask = */ 0, + STATX_ATTR_MOUNT_ROOT, + &sx); + if (r < 0) + return log_full_errno((searching && r == -ENOENT) || + (unprivileged_mode && ERRNO_IS_NEG_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, r, "Failed to determine block device node of \"%s\": %m", path); if (!S_ISDIR(sx.stx_mode)) return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Path \"%s\" is not a directory", path); - r = statx_warn_mount_root(&sx, LOG_ERR); - if (r < 0) - return r; - if (!FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index a2ab8ded9b..59adcc61ca 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -586,12 +586,13 @@ static int opendir_and_stat( return 0; } - if (statx(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &sx) < 0) - return log_error_errno(errno, "statx(%s) failed: %m", path); - - r = statx_warn_mount_root(&sx, LOG_ERR); + r = xstatx_full(dirfd(d), /* path = */ NULL, AT_EMPTY_PATH, + STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, + /* optional_mask = */ 0, + STATX_ATTR_MOUNT_ROOT, + &sx); if (r < 0) - return r; + return log_error_errno(r, "statx(%s) failed: %m", path); *ret_mountpoint = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); *ret = TAKE_PTR(d); @@ -683,35 +684,22 @@ static int dir_cleanup( if (dot_or_dot_dot(de->d_name)) continue; - /* If statx() is supported, use it. It's preferable over fstatat() since it tells us - * explicitly where we are looking at a mount point, for free as side information. Determining - * the same information without statx() is hard, see the complexity of path_is_mount_point(), - * and also much slower as it requires a number of syscalls instead of just one. Hence, when - * we have modern statx() we use it instead of fstat() and do proper mount point checks, - * while on older kernels's well do traditional st_dev based detection of mount points. - * - * Using statx() for detecting mount points also has the benefit that we handle weird file - * systems such as overlayfs better where each file is originating from a different - * st_dev. */ - struct statx sx; - if (statx(dirfd(d), de->d_name, - AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT, - STATX_TYPE|STATX_MODE|STATX_UID|STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME, - &sx) < 0) { - if (errno == ENOENT) - continue; - + r = xstatx_full(dirfd(d), de->d_name, + AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT, + STATX_TYPE|STATX_MODE|STATX_UID, + STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME, + STATX_ATTR_MOUNT_ROOT, + &sx); + if (r == -ENOENT) + continue; + if (r < 0) { /* FUSE, NFS mounts, SELinux might return EACCES */ - log_full_errno(errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, + log_full_errno(r == -EACCES ? LOG_DEBUG : LOG_ERR, r, "statx(%s/%s) failed: %m", p, de->d_name); continue; } - r = statx_warn_mount_root(&sx, LOG_ERR); - if (r < 0) - return r; - if (FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) { log_debug("Ignoring \"%s/%s\": different mount points.", p, de->d_name); continue; From 9feb490633a2e8e38bc5828625ff116b80050f74 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 29 Jan 2026 10:59:42 +0100 Subject: [PATCH 11/11] stat-util: drop now unused statx_warn_mount_{id,root}() --- src/basic/stat-util.c | 22 ---------------------- src/basic/stat-util.h | 3 --- 2 files changed, 25 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 703cf4fc92..9eaf17624a 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -701,25 +701,3 @@ mode_t inode_type_from_string(const char *s) { return MODE_INVALID; } - -int statx_warn_mount_root(const struct statx *sx, int log_level) { - assert(sx); - - /* The STATX_ATTR_MOUNT_ROOT flag is supported since kernel v5.8. */ - if (!FLAGS_SET(sx->stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) - return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOSYS), - "statx() did not set STATX_ATTR_MOUNT_ROOT, running on an old kernel?"); - - return 0; -} - -int statx_warn_mount_id(const struct statx *sx, int log_level) { - assert(sx); - - /* The STATX_MNT_ID flag is supported since kernel v5.10. */ - if (!FLAGS_SET(sx->stx_mask, STATX_MNT_ID)) - return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOSYS), - "statx() does not support STATX_MNT_ID, running on an old kernel?"); - - return 0; -} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 77d9f55c63..97dedd4b80 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -135,6 +135,3 @@ static inline bool inode_type_can_hardlink(mode_t m) { * type). */ return IN_SET(m & S_IFMT, S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFCHR, S_IFIFO); } - -int statx_warn_mount_root(const struct statx *sx, int log_level); -int statx_warn_mount_id(const struct statx *sx, int log_level);