Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hfs/hfsplus: use 64-bit inode timestamps

The interpretation of on-disk timestamps in HFS and HFS+ differs
between 32-bit and 64-bit kernels at the moment. Use 64-bit timestamps
consistently so apply the current 64-bit behavior everyhere.

According to the official documentation for HFS+ [1], inode timestamps
are supposed to cover the time range from 1904 to 2040 as originally
used in classic MacOS.

The traditional Linux usage is to convert the timestamps into an unsigned
32-bit number based on the Unix epoch and from there to a time_t. On
32-bit systems, that wraps the time from 2038 to 1902, so the last
two years of the valid time range become garbled. On 64-bit systems,
all times before 1970 get turned into timestamps between 2038 and 2106,
which is more convenient but also different from the documented behavior.

Looking at the Darwin sources [2], it seems that MacOS is inconsistent in
yet another way: all timestamps are wrapped around to a 32-bit unsigned
number when written to the disk, but when read back, all numeric values
lower than 2082844800U are assumed to be invalid, so we cannot represent
the times before 1970 or the times after 2040.

While all implementations seem to agree on the interpretation of values
between 1970 and 2038, they often differ on the exact range they support
when reading back values outside of the common range:

MacOS (traditional): 1904-2040
Apple Documentation: 1904-2040
MacOS X source comments: 1970-2040
MacOS X source code: 1970-2038
32-bit Linux: 1902-2038
64-bit Linux: 1970-2106
hfsfuse: 1970-2040
hfsutils (32 bit, old libc) 1902-2038
hfsutils (32 bit, new libc) 1970-2106
hfsutils (64 bit) 1904-2040
hfsplus-utils 1904-2040
hfsexplorer 1904-2040
7-zip 1904-2040

Out of the above, the range from 1970 to 2106 seems to be the most useful,
as it allows using HFS and HFS+ beyond year 2038, and this matches the
behavior that most users would see today on Linux, as few people run
32-bit kernels any more.

Link: [1] https://developer.apple.com/library/archive/technotes/tn/tn1150.html
Link: [2] https://opensource.apple.com/source/hfs/hfs-407.30.1/core/MacOSStubs.c.auto.html
Link: https://lore.kernel.org/lkml/20180711224625.airwna6gzyatoowe@eaf/
Suggested-by: "Ernesto A. Fernández" <ernesto.mnd.fernandez@gmail.com>
Reviewed-by: Vyacheslav Dubeyko <slava@dubeyko.com>
Reviewed-by: Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
v3: revert back to 1970-2106 time range
fix bugs found in review
merge both patches into one
drop cc:stable tag
v2: treat pre-1970 dates as invalid following MacOS X behavior,
reword and expand changelog text

+53 -19
+22 -6
fs/hfs/hfs_fs.h
··· 242 242 /* 243 243 * There are two time systems. Both are based on seconds since 244 244 * a particular time/date. 245 - * Unix: unsigned lil-endian since 00:00 GMT, Jan. 1, 1970 245 + * Unix: signed little-endian since 00:00 GMT, Jan. 1, 1970 246 246 * mac: unsigned big-endian since 00:00 GMT, Jan. 1, 1904 247 247 * 248 + * HFS implementations are highly inconsistent, this one matches the 249 + * traditional behavior of 64-bit Linux, giving the most useful 250 + * time range between 1970 and 2106, by treating any on-disk timestamp 251 + * under HFS_UTC_OFFSET (Jan 1 1970) as a time between 2040 and 2106. 248 252 */ 249 - #define __hfs_u_to_mtime(sec) cpu_to_be32(sec + 2082844800U - sys_tz.tz_minuteswest * 60) 250 - #define __hfs_m_to_utime(sec) (be32_to_cpu(sec) - 2082844800U + sys_tz.tz_minuteswest * 60) 253 + #define HFS_UTC_OFFSET 2082844800U 251 254 255 + static inline time64_t __hfs_m_to_utime(__be32 mt) 256 + { 257 + time64_t ut = (u32)(be32_to_cpu(mt) - HFS_UTC_OFFSET); 258 + 259 + return ut + sys_tz.tz_minuteswest * 60; 260 + } 261 + 262 + static inline __be32 __hfs_u_to_mtime(time64_t ut) 263 + { 264 + ut -= sys_tz.tz_minuteswest * 60; 265 + 266 + return cpu_to_be32(lower_32_bits(ut) + HFS_UTC_OFFSET); 267 + } 252 268 #define HFS_I(inode) (container_of(inode, struct hfs_inode_info, vfs_inode)) 253 269 #define HFS_SB(sb) ((struct hfs_sb_info *)(sb)->s_fs_info) 254 270 255 - #define hfs_m_to_utime(time) (struct timespec){ .tv_sec = __hfs_m_to_utime(time) } 256 - #define hfs_u_to_mtime(time) __hfs_u_to_mtime((time).tv_sec) 257 - #define hfs_mtime() __hfs_u_to_mtime(get_seconds()) 271 + #define hfs_m_to_utime(time) (struct timespec64){ .tv_sec = __hfs_m_to_utime(time) } 272 + #define hfs_u_to_mtime(time) __hfs_u_to_mtime((time).tv_sec) 273 + #define hfs_mtime() __hfs_u_to_mtime(ktime_get_real_seconds()) 258 274 259 275 static inline const char *hfs_mdb_name(struct super_block *sb) 260 276 {
+2 -2
fs/hfs/inode.c
··· 351 351 inode->i_mode &= ~hsb->s_file_umask; 352 352 inode->i_mode |= S_IFREG; 353 353 inode->i_ctime = inode->i_atime = inode->i_mtime = 354 - timespec_to_timespec64(hfs_m_to_utime(rec->file.MdDat)); 354 + hfs_m_to_utime(rec->file.MdDat); 355 355 inode->i_op = &hfs_file_inode_operations; 356 356 inode->i_fop = &hfs_file_operations; 357 357 inode->i_mapping->a_ops = &hfs_aops; ··· 362 362 HFS_I(inode)->fs_blocks = 0; 363 363 inode->i_mode = S_IFDIR | (S_IRWXUGO & ~hsb->s_dir_umask); 364 364 inode->i_ctime = inode->i_atime = inode->i_mtime = 365 - timespec_to_timespec64(hfs_m_to_utime(rec->dir.MdDat)); 365 + hfs_m_to_utime(rec->dir.MdDat); 366 366 inode->i_op = &hfs_dir_inode_operations; 367 367 inode->i_fop = &hfs_dir_operations; 368 368 break;
+23 -5
fs/hfsplus/hfsplus_fs.h
··· 533 533 void **data, int op, int op_flags); 534 534 int hfsplus_read_wrapper(struct super_block *sb); 535 535 536 - /* time macros */ 537 - #define __hfsp_mt2ut(t) (be32_to_cpu(t) - 2082844800U) 538 - #define __hfsp_ut2mt(t) (cpu_to_be32(t + 2082844800U)) 536 + /* 537 + * time helpers: convert between 1904-base and 1970-base timestamps 538 + * 539 + * HFS+ implementations are highly inconsistent, this one matches the 540 + * traditional behavior of 64-bit Linux, giving the most useful 541 + * time range between 1970 and 2106, by treating any on-disk timestamp 542 + * under HFSPLUS_UTC_OFFSET (Jan 1 1970) as a time between 2040 and 2106. 543 + */ 544 + #define HFSPLUS_UTC_OFFSET 2082844800U 545 + 546 + static inline time64_t __hfsp_mt2ut(__be32 mt) 547 + { 548 + time64_t ut = (u32)(be32_to_cpu(mt) - HFSPLUS_UTC_OFFSET); 549 + 550 + return ut; 551 + } 552 + 553 + static inline __be32 __hfsp_ut2mt(time64_t ut) 554 + { 555 + return cpu_to_be32(lower_32_bits(ut) + HFSPLUS_UTC_OFFSET); 556 + } 539 557 540 558 /* compatibility */ 541 - #define hfsp_mt2ut(t) (struct timespec){ .tv_sec = __hfsp_mt2ut(t) } 559 + #define hfsp_mt2ut(t) (struct timespec64){ .tv_sec = __hfsp_mt2ut(t) } 542 560 #define hfsp_ut2mt(t) __hfsp_ut2mt((t).tv_sec) 543 - #define hfsp_now2mt() __hfsp_ut2mt(get_seconds()) 561 + #define hfsp_now2mt() __hfsp_ut2mt(ktime_get_real_seconds()) 544 562 545 563 #endif
+6 -6
fs/hfsplus/inode.c
··· 504 504 hfsplus_get_perms(inode, &folder->permissions, 1); 505 505 set_nlink(inode, 1); 506 506 inode->i_size = 2 + be32_to_cpu(folder->valence); 507 - inode->i_atime = timespec_to_timespec64(hfsp_mt2ut(folder->access_date)); 508 - inode->i_mtime = timespec_to_timespec64(hfsp_mt2ut(folder->content_mod_date)); 509 - inode->i_ctime = timespec_to_timespec64(hfsp_mt2ut(folder->attribute_mod_date)); 507 + inode->i_atime = hfsp_mt2ut(folder->access_date); 508 + inode->i_mtime = hfsp_mt2ut(folder->content_mod_date); 509 + inode->i_ctime = hfsp_mt2ut(folder->attribute_mod_date); 510 510 HFSPLUS_I(inode)->create_date = folder->create_date; 511 511 HFSPLUS_I(inode)->fs_blocks = 0; 512 512 if (folder->flags & cpu_to_be16(HFSPLUS_HAS_FOLDER_COUNT)) { ··· 542 542 init_special_inode(inode, inode->i_mode, 543 543 be32_to_cpu(file->permissions.dev)); 544 544 } 545 - inode->i_atime = timespec_to_timespec64(hfsp_mt2ut(file->access_date)); 546 - inode->i_mtime = timespec_to_timespec64(hfsp_mt2ut(file->content_mod_date)); 547 - inode->i_ctime = timespec_to_timespec64(hfsp_mt2ut(file->attribute_mod_date)); 545 + inode->i_atime = hfsp_mt2ut(file->access_date); 546 + inode->i_mtime = hfsp_mt2ut(file->content_mod_date); 547 + inode->i_ctime = hfsp_mt2ut(file->attribute_mod_date); 548 548 HFSPLUS_I(inode)->create_date = file->create_date; 549 549 } else { 550 550 pr_err("bad catalog entry used to create inode\n");