From: Lennart Poettering Date: Tue, 24 Mar 2026 16:06:09 +0000 (+0100) Subject: stat-util: introduce inode_unmodified_hash_ops X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=172ae47e659c76c66a2e00ff89f277cf9ce2b5f4;p=thirdparty%2Fsystemd.git stat-util: introduce inode_unmodified_hash_ops This is almost the same as inode_hash_ops, but also hashes + compares all attributes that could affect the contents of a file. It ignores "superficial"/"external" attributes such as ownership or access mode however. --- diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 98dfa8c5a97..ac218d15520 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -731,6 +731,59 @@ int inode_compare_func(const struct stat *a, const struct stat *b) { DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free); +void inode_unmodified_hash_func(const struct stat *q, struct siphash *state) { + inode_hash_func(q, state); + + siphash24_compress_typesafe(q->st_mtim.tv_sec, state); + siphash24_compress_typesafe(q->st_mtim.tv_nsec, state); + + if (S_ISREG(q->st_mode)) + siphash24_compress_typesafe(q->st_size, state); + else { + uint64_t invalid = UINT64_MAX; + siphash24_compress_typesafe(invalid, state); + } + + if (S_ISCHR(q->st_mode) || S_ISBLK(q->st_mode)) + siphash24_compress_typesafe(q->st_rdev, state); + else { + dev_t invalid = (dev_t) -1; + siphash24_compress_typesafe(invalid, state); + } +} + +int inode_unmodified_compare_func(const struct stat *a, const struct stat *b) { + int r; + + r = inode_compare_func(a, b); + if (r != 0) + return r; + + r = CMP(a->st_mtim.tv_sec, b->st_mtim.tv_sec); + if (r != 0) + return r; + + r = CMP(a->st_mtim.tv_nsec, b->st_mtim.tv_nsec); + if (r != 0) + return r; + + if (S_ISREG(a->st_mode)) { + r = CMP(a->st_size, b->st_size); + if (r != 0) + return r; + } + + if (S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) { + r = CMP(a->st_rdev, b->st_rdev); + if (r != 0) + return r; + } + + return 0; +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_unmodified_hash_ops, struct stat, inode_unmodified_hash_func, inode_unmodified_compare_func, free); + const char* inode_type_to_string(mode_t m) { /* Returns a short string for the inode type. We use the same name as the underlying macros for each diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 939a0fc398d..2b50c9c5588 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -127,6 +127,12 @@ void inode_hash_func(const struct stat *q, struct siphash *state); int inode_compare_func(const struct stat *a, const struct stat *b); extern const struct hash_ops inode_hash_ops; +/* This is a more thorough version of the above, and also checks the mtimes, the size, and the rdev. It does + * not check "external" attributes such as access mode or ownership. */ +void inode_unmodified_hash_func(const struct stat *q, struct siphash *state); +int inode_unmodified_compare_func(const struct stat *a, const struct stat *b); +extern const struct hash_ops inode_unmodified_hash_ops; + DECLARE_STRING_TABLE_LOOKUP(inode_type, mode_t); /* Macros that check whether the stat/statx structures have been initialized already. For "struct stat" we