diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 70d5b74224..ab45eda0ed 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -364,8 +364,7 @@ bool stat_inode_same(const struct stat *a, const struct stat *b) { /* Returns if the specified stat structure references the same (though possibly modified) inode. Does * a thorough check, comparing inode nr, backing device and if the inode is still of the same type. */ - return a && b && - a->st_dev != 0 && /* is the structure ever initialized? */ + return stat_is_set(a) && stat_is_set(b) && ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 && /* same inode type */ a->st_dev == b->st_dev && a->st_ino == b->st_ino; @@ -393,7 +392,7 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { /* Same as stat_inode_same() but for struct statx */ - return a && b && + return statx_is_set(a) && statx_is_set(b) && FLAGS_SET(a->stx_mask, STATX_TYPE|STATX_INO) && FLAGS_SET(b->stx_mask, STATX_TYPE|STATX_INO) && ((a->stx_mode ^ b->stx_mode) & S_IFMT) == 0 && a->stx_dev_major == b->stx_dev_major && @@ -402,7 +401,7 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { } bool statx_mount_same(const struct new_statx *a, const struct new_statx *b) { - if (!a || !b) + if (!new_statx_is_set(a) || !new_statx_is_set(b)) return false; /* if we have the mount ID, that's all we need */ @@ -536,6 +535,8 @@ const char* inode_type_to_string(mode_t m) { return "sock"; } + /* Note anonmyous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will + * return an .st_mode where we'll return NULL here! */ return NULL; } diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index c5dfd2db16..06bd9fdd97 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -9,6 +9,7 @@ #include #include +#include "fs-util.h" #include "macro.h" #include "missing_stat.h" #include "siphash24.h" @@ -126,3 +127,18 @@ extern const struct hash_ops inode_hash_ops; const char* inode_type_to_string(mode_t m); mode_t inode_type_from_string(const char *s); + +/* Macros that check whether the stat/statx structures have been initialized already. For "struct stat" we + * use a check for .st_dev being non-zero, since the kernel unconditionally fills that in, mapping the file + * to its originating superblock, regardless if the fs is block based or virtual (we also check for .st_mode + * being MODE_INVALID, since we use that as an invalid marker for separate mode_t fields). For "struct statx" + * we use the .stx_mask field, which must be non-zero if any of the fields have already been initialized. */ +static inline bool stat_is_set(const struct stat *st) { + return st && st->st_dev != 0 && st->st_mode != MODE_INVALID; +} +static inline bool statx_is_set(const struct statx *sx) { + return sx && sx->stx_mask != 0; +} +static inline bool new_statx_is_set(const struct new_statx *sx) { + return sx && sx->stx_mask != 0; +} diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index 2d334d38dd..7b05386213 100644 --- a/src/resolve/resolved-etc-hosts.c +++ b/src/resolve/resolved-etc-hosts.c @@ -342,7 +342,7 @@ static int manager_etc_hosts_read(Manager *m) { m->etc_hosts_last = ts; - if (m->etc_hosts_stat.st_mode != 0) { + if (stat_is_set(&m->etc_hosts_stat)) { if (stat("/etc/hosts", &st) < 0) { if (errno != ENOENT) return log_error_errno(errno, "Failed to stat /etc/hosts: %m"); diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index 272cd4c0d3..df37dcb528 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -195,6 +196,21 @@ TEST(inode_type_from_string) { assert_se(inode_type_from_string(inode_type_to_string(*m)) == *m); } +TEST(anonymous_inode) { + _cleanup_close_ int fd = -EBADF; + + fd = eventfd(0, EFD_CLOEXEC); + assert_se(fd >= 0); + + /* Verify that we handle anonymous inodes correctly, i.e. those which have no file type */ + + struct stat st; + assert_se(fstat(fd, &st) >= 0); + assert_se((st.st_mode & S_IFMT) == 0); + + assert_se(!inode_type_to_string(st.st_mode)); +} + TEST(fd_verify_linked) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF;