From: Charalampos Mitrodimas <charmitro@posteo.net>
To: Viacheslav Dubeyko <slava@dubeyko.com>
Cc: glaubitz@physik.fu-berlin.de, linux-fsdevel@vger.kernel.org,
frank.li@vivo.com, Slava.Dubeyko@ibm.com
Subject: Re: [PATCH] hfs/hfsplus: fix timestamp wrapped issue
Date: Tue, 17 Feb 2026 02:39:39 +0000 [thread overview]
Message-ID: <87a4x8f5zq.fsf@posteo.net> (raw)
In-Reply-To: <20260216233556.4005400-1-slava@dubeyko.com>
Viacheslav Dubeyko <slava@dubeyko.com> writes:
> The xfstests' test-case generic/258 fails to execute
> correctly:
>
> FSTYP -- hfsplus
> PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.15.0-rc4+ #8 SMP PREEMPT_DYNAMIC Thu May 1 16:43:22 PDT 2025
> MKFS_OPTIONS -- /dev/loop51
> MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch
>
> generic/258 [failed, exit status 1]- output mismatch (see xfstests-dev/results//generic/258.out.bad)
>
> The main reason of the issue is the logic:
>
> cpu_to_be32(lower_32_bits(ut) + HFSPLUS_UTC_OFFSET)
>
> At first, we take the lower 32 bits of the value and, then
> we add the time offset. However, if we have negative value
> then we make completely wrong calculation.
>
> This patch corrects the logic of __hfsp_mt2ut() and
> __hfsp_ut2mt (HFS+ case), __hfs_m_to_utime() and
> __hfs_u_to_mtime (HFS case). The HFS_MIN_TIMESTAMP_SECS and
> HFS_MAX_TIMESTAMP_SECS have been introduced in
> include/linux/hfs_common.h. Also, HFS_UTC_OFFSET constant
> has been moved to include/linux/hfs_common.h. The hfs_fill_super()
> and hfsplus_fill_super() logic defines sb->s_time_min,
> sb->s_time_max, and sb->s_time_gran.
>
> sudo ./check generic/258
> FSTYP -- hfsplus
> PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.19.0-rc1+ #87 SMP PREEMPT_DYNAMIC Mon Feb 16 14:48:57 PST 2026
> MKFS_OPTIONS -- /dev/loop51
> MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch
>
> generic/258 29s ... 39s
> Ran: generic/258
> Passed all 1 tests
>
> [1] https://github.com/hfs-linux-kernel/hfs-linux-kernel/issues/133
>
> Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
> cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
> cc: Yangtao Li <frank.li@vivo.com>
> cc: linux-fsdevel@vger.kernel.org
> ---
> fs/hfs/hfs_fs.h | 17 ++++-------------
> fs/hfs/super.c | 4 ++++
> fs/hfsplus/hfsplus_fs.h | 13 ++++---------
> fs/hfsplus/super.c | 4 ++++
> include/linux/hfs_common.h | 18 ++++++++++++++++++
> 5 files changed, 34 insertions(+), 22 deletions(-)
>
> diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h
> index ac0e83f77a0f..7d529e6789b8 100644
> --- a/fs/hfs/hfs_fs.h
> +++ b/fs/hfs/hfs_fs.h
> @@ -229,21 +229,11 @@ extern int hfs_mac2asc(struct super_block *sb,
> extern void hfs_mark_mdb_dirty(struct super_block *sb);
>
> /*
> - * There are two time systems. Both are based on seconds since
> - * a particular time/date.
> - * Unix: signed little-endian since 00:00 GMT, Jan. 1, 1970
> - * mac: unsigned big-endian since 00:00 GMT, Jan. 1, 1904
> - *
> - * HFS implementations are highly inconsistent, this one matches the
> - * traditional behavior of 64-bit Linux, giving the most useful
> - * time range between 1970 and 2106, by treating any on-disk timestamp
> - * under HFS_UTC_OFFSET (Jan 1 1970) as a time between 2040 and 2106.
> + * time helpers: convert between 1904-base and 1970-base timestamps
> */
> -#define HFS_UTC_OFFSET 2082844800U
> -
> static inline time64_t __hfs_m_to_utime(__be32 mt)
> {
> - time64_t ut = (u32)(be32_to_cpu(mt) - HFS_UTC_OFFSET);
> + time64_t ut = (time64_t)be32_to_cpu(mt) - HFS_UTC_OFFSET;
>
> return ut + sys_tz.tz_minuteswest * 60;
> }
> @@ -251,8 +241,9 @@ static inline time64_t __hfs_m_to_utime(__be32 mt)
> static inline __be32 __hfs_u_to_mtime(time64_t ut)
> {
> ut -= sys_tz.tz_minuteswest * 60;
> + ut += HFS_UTC_OFFSET;
>
> - return cpu_to_be32(lower_32_bits(ut) + HFS_UTC_OFFSET);
> + return cpu_to_be32(lower_32_bits(ut));
> }
> #define HFS_I(inode) (container_of(inode, struct hfs_inode_info, vfs_inode))
> #define HFS_SB(sb) ((struct hfs_sb_info *)(sb)->s_fs_info)
> diff --git a/fs/hfs/super.c b/fs/hfs/super.c
> index 97546d6b41f4..6b6c138812b7 100644
> --- a/fs/hfs/super.c
> +++ b/fs/hfs/super.c
> @@ -341,6 +341,10 @@ static int hfs_fill_super(struct super_block *sb, struct fs_context *fc)
> sb->s_flags |= SB_NODIRATIME;
> mutex_init(&sbi->bitmap_lock);
>
> + sb->s_time_gran = NSEC_PER_SEC;
> + sb->s_time_min = HFS_MIN_TIMESTAMP_SECS;
> + sb->s_time_max = HFS_MAX_TIMESTAMP_SECS;
> +
> res = hfs_mdb_get(sb);
> if (res) {
> if (!silent)
> diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h
> index 5f891b73a646..3554faf84c15 100644
> --- a/fs/hfsplus/hfsplus_fs.h
> +++ b/fs/hfsplus/hfsplus_fs.h
> @@ -511,24 +511,19 @@ int hfsplus_read_wrapper(struct super_block *sb);
>
> /*
> * time helpers: convert between 1904-base and 1970-base timestamps
> - *
> - * HFS+ implementations are highly inconsistent, this one matches the
> - * traditional behavior of 64-bit Linux, giving the most useful
> - * time range between 1970 and 2106, by treating any on-disk timestamp
> - * under HFSPLUS_UTC_OFFSET (Jan 1 1970) as a time between 2040 and 2106.
> */
> -#define HFSPLUS_UTC_OFFSET 2082844800U
> -
> static inline time64_t __hfsp_mt2ut(__be32 mt)
> {
> - time64_t ut = (u32)(be32_to_cpu(mt) - HFSPLUS_UTC_OFFSET);
> + time64_t ut = (time64_t)be32_to_cpu(mt) - HFS_UTC_OFFSET;
>
> return ut;
> }
>
> static inline __be32 __hfsp_ut2mt(time64_t ut)
> {
> - return cpu_to_be32(lower_32_bits(ut) + HFSPLUS_UTC_OFFSET);
> + ut += HFS_UTC_OFFSET;
> +
> + return cpu_to_be32(lower_32_bits(ut));
> }
>
> static inline enum hfsplus_btree_mutex_classes
> diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c
> index 592d8fbb748c..dcd61868d199 100644
> --- a/fs/hfsplus/super.c
> +++ b/fs/hfsplus/super.c
> @@ -487,6 +487,10 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)
> if (!sbi->rsrc_clump_blocks)
> sbi->rsrc_clump_blocks = 1;
>
> + sb->s_time_gran = NSEC_PER_SEC;
> + sb->s_time_min = HFS_MIN_TIMESTAMP_SECS;
> + sb->s_time_max = HFS_MAX_TIMESTAMP_SECS;
> +
> err = -EFBIG;
> last_fs_block = sbi->total_blocks - 1;
> last_fs_page = (last_fs_block << sbi->alloc_blksz_shift) >>
> diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h
> index dadb5e0aa8a3..816ac2f0996d 100644
> --- a/include/linux/hfs_common.h
> +++ b/include/linux/hfs_common.h
> @@ -650,4 +650,22 @@ typedef union {
> struct hfsplus_attr_key attr;
> } __packed hfsplus_btree_key;
>
> +/*
> + * There are two time systems. Both are based on seconds since
> + * a particular time/date.
> + * Unix: signed little-endian since 00:00 GMT, Jan. 1, 1970
> + * mac: unsigned big-endian since 00:00 GMT, Jan. 1, 1904
> + *
> + * HFS/HFS+ implementations are highly inconsistent, this one matches the
> + * traditional behavior of 64-bit Linux, giving the most useful
> + * time range between 1970 and 2106, by treating any on-disk timestamp
> + * under HFS_UTC_OFFSET (Jan 1 1970) as a time between 2040 and 2106.
> + */
Since this is replacing the wrapping behavior with a linear 1904-2040
mapping, should we update this comment to match? It still describes the
old "2040 to 2106" wrapping semantics.
Cheers,
C. Mitrodimas
> +#define HFS_UTC_OFFSET 2082844800U
> +
> +/* January 1, 1904, 00:00:00 UTC */
> +#define HFS_MIN_TIMESTAMP_SECS -2082844800LL
> +/* February 6, 2040, 06:28:15 UTC */
> +#define HFS_MAX_TIMESTAMP_SECS 2212122495LL
> +
> #endif /* _HFS_COMMON_H_ */
next prev parent reply other threads:[~2026-02-17 2:39 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-16 23:35 [PATCH] hfs/hfsplus: fix timestamp wrapped issue Viacheslav Dubeyko
2026-02-17 2:39 ` Charalampos Mitrodimas [this message]
2026-02-17 18:11 ` Viacheslav Dubeyko
2026-02-18 2:00 ` Charalampos Mitrodimas
2026-02-18 20:56 ` Viacheslav Dubeyko
2026-02-19 0:44 ` Charalampos Mitrodimas
2026-02-19 23:39 ` Viacheslav Dubeyko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87a4x8f5zq.fsf@posteo.net \
--to=charmitro@posteo.net \
--cc=Slava.Dubeyko@ibm.com \
--cc=frank.li@vivo.com \
--cc=glaubitz@physik.fu-berlin.de \
--cc=linux-fsdevel@vger.kernel.org \
--cc=slava@dubeyko.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.