hfs/hfsplus: use documented official timestamp range

Message ID 20180622141758.229589-1-arnd@arndb.de
State New
Headers show
Series
  • hfs/hfsplus: use documented official timestamp range
Related show

Commit Message

Arnd Bergmann June 22, 2018, 2:17 p.m.
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.

The same behavior is used in Darwin and presumaby all versions of MacOS X,
as seen in the to_hfs_time() function in [2]. It is unclear whether this
is a bug in the file system code, or intentional but undocumented behavior.

This changes Linux over to the traditional MacOS (pre MacOS X)
behavior. This means all files that are created on MacOS X or Linux
with future timestamps between 2040 and 2106 will now show up as past
dates. Timestamps between 2038 and 2040 will still be represented
incorrectly on 32-bit architectures as times between 1902 and 1904,
but that will be fixed once we have user space with 64-bit time_t.

Cc: stable@vger.kernel.org
Link: [1] https://developer.apple.com/library/archive/technotes/tn/tn1150.html
Link: [2] https://opensource.apple.com/source/xnu/xnu-344/bsd/hfs/MacOSStubs.c
Suggested-by: Viacheslav Dubeyko <slava@dubeyko.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>

---
Note: This is the patch that Viacheslav asked for, but given how
MacOS X behaves, I'm increasingly thinking this is a bad idea.
---
 fs/hfs/hfs_fs.h         | 2 +-
 fs/hfsplus/hfsplus_fs.h | 5 +++--
 2 files changed, 4 insertions(+), 3 deletions(-)

-- 
2.9.0

Comments

Ernesto A. Fernández June 23, 2018, 6 a.m. | #1
On Fri, Jun 22, 2018 at 04:17:32PM +0200, Arnd Bergmann wrote:
> 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.

> 

> The same behavior is used in Darwin and presumaby all versions of MacOS X,

> as seen in the to_hfs_time() function in [2]. It is unclear whether this

> is a bug in the file system code, or intentional but undocumented behavior.


But the to_bsd_time() function considers wrapped timestamps as invalid,
doesn't it? So it seems they simply don't care about the post-2040 (or
pre-1970) case?

> 

> This changes Linux over to the traditional MacOS (pre MacOS X)

> behavior. This means all files that are created on MacOS X or Linux

> with future timestamps between 2040 and 2106 will now show up as past

> dates. Timestamps between 2038 and 2040 will still be represented

> incorrectly on 32-bit architectures as times between 1902 and 1904,

> but that will be fixed once we have user space with 64-bit time_t.

> 

> Cc: stable@vger.kernel.org

> Link: [1] https://developer.apple.com/library/archive/technotes/tn/tn1150.html

> Link: [2] https://opensource.apple.com/source/xnu/xnu-344/bsd/hfs/MacOSStubs.c

> Suggested-by: Viacheslav Dubeyko <slava@dubeyko.com>

> Signed-off-by: Arnd Bergmann <arnd@arndb.de>

> ---

> Note: This is the patch that Viacheslav asked for, but given how

> MacOS X behaves, I'm increasingly thinking this is a bad idea.


I agree that it made more sense before.

> ---

>  fs/hfs/hfs_fs.h         | 2 +-

>  fs/hfsplus/hfsplus_fs.h | 5 +++--

>  2 files changed, 4 insertions(+), 3 deletions(-)

> 

> diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h

> index 6d0783e2e276..39c1f3a43ed8 100644

> --- a/fs/hfs/hfs_fs.h

> +++ b/fs/hfs/hfs_fs.h

> @@ -247,7 +247,7 @@ extern void hfs_mark_mdb_dirty(struct super_block *sb);

>   *

>   */

>  #define __hfs_u_to_mtime(sec)	cpu_to_be32(sec + 2082844800U - sys_tz.tz_minuteswest * 60)

> -#define __hfs_m_to_utime(sec)	(be32_to_cpu(sec) - 2082844800U  + sys_tz.tz_minuteswest * 60)

> +#define __hfs_m_to_utime(sec)	((time64_t)be32_to_cpu(sec) - 2082844800U  + sys_tz.tz_minuteswest * 60)

>  

>  #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/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h

> index d9255abafb81..57838ef4dcdc 100644

> --- a/fs/hfsplus/hfsplus_fs.h

> +++ b/fs/hfsplus/hfsplus_fs.h

> @@ -530,8 +530,9 @@ int hfsplus_submit_bio(struct super_block *sb, sector_t sector, void *buf,

>  		       void **data, int op, int op_flags);

>  int hfsplus_read_wrapper(struct super_block *sb);

>  

> -/* time macros */

> -#define __hfsp_mt2ut(t)		(be32_to_cpu(t) - 2082844800U)

> +/* time macros: convert between 1904-2040 and 1970-2106 range,

> + * pre-1970 timestamps are interpreted as post-2038 times after wrap-around */


This comment seems to be from the original series, maybe you forgot to
change it?

> +#define __hfsp_mt2ut(t)		((time64_t)be32_to_cpu(t) - 2082844800U)

>  #define __hfsp_ut2mt(t)		(cpu_to_be32(t + 2082844800U))

>  

>  /* compatibility */

> -- 

> 2.9.0

>
Arnd Bergmann July 10, 2018, 3:26 p.m. | #2
On Sat, Jun 23, 2018 at 8:00 AM, Ernesto A. Fernández
<ernesto.mnd.fernandez@gmail.com> wrote:
> On Fri, Jun 22, 2018 at 04:17:32PM +0200, Arnd Bergmann wrote:

>> 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.

>>

>> The same behavior is used in Darwin and presumaby all versions of MacOS X,

>> as seen in the to_hfs_time() function in [2]. It is unclear whether this

>> is a bug in the file system code, or intentional but undocumented behavior.

>

> But the to_bsd_time() function considers wrapped timestamps as invalid,

> doesn't it? So it seems they simply don't care about the post-2040 (or

> pre-1970) case?


Sorry for the late reply. Just got back to looking at what remains for the
file systems.

You are of course right, my mistake: Apple writes a wrapped date when
converting from bsd time to on-disk format, but does treat the wrapped
dates as invalid when reading
>> diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h

>> index d9255abafb81..57838ef4dcdc 100644

>> --- a/fs/hfsplus/hfsplus_fs.h

>> +++ b/fs/hfsplus/hfsplus_fs.h

>> @@ -530,8 +530,9 @@ int hfsplus_submit_bio(struct super_block *sb, sector_t sector, void *buf,

>>                      void **data, int op, int op_flags);

>>  int hfsplus_read_wrapper(struct super_block *sb);

>>

>> -/* time macros */

>> -#define __hfsp_mt2ut(t)              (be32_to_cpu(t) - 2082844800U)

>> +/* time macros: convert between 1904-2040 and 1970-2106 range,

>> + * pre-1970 timestamps are interpreted as post-2038 times after wrap-around */

>

> This comment seems to be from the original series, maybe you forgot to

> change it?


Correct. I'll change it and resend it with a fixed changelog.

      Arnd

Patch

diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h
index 6d0783e2e276..39c1f3a43ed8 100644
--- a/fs/hfs/hfs_fs.h
+++ b/fs/hfs/hfs_fs.h
@@ -247,7 +247,7 @@  extern void hfs_mark_mdb_dirty(struct super_block *sb);
  *
  */
 #define __hfs_u_to_mtime(sec)	cpu_to_be32(sec + 2082844800U - sys_tz.tz_minuteswest * 60)
-#define __hfs_m_to_utime(sec)	(be32_to_cpu(sec) - 2082844800U  + sys_tz.tz_minuteswest * 60)
+#define __hfs_m_to_utime(sec)	((time64_t)be32_to_cpu(sec) - 2082844800U  + sys_tz.tz_minuteswest * 60)
 
 #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/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h
index d9255abafb81..57838ef4dcdc 100644
--- a/fs/hfsplus/hfsplus_fs.h
+++ b/fs/hfsplus/hfsplus_fs.h
@@ -530,8 +530,9 @@  int hfsplus_submit_bio(struct super_block *sb, sector_t sector, void *buf,
 		       void **data, int op, int op_flags);
 int hfsplus_read_wrapper(struct super_block *sb);
 
-/* time macros */
-#define __hfsp_mt2ut(t)		(be32_to_cpu(t) - 2082844800U)
+/* time macros: convert between 1904-2040 and 1970-2106 range,
+ * pre-1970 timestamps are interpreted as post-2038 times after wrap-around */
+#define __hfsp_mt2ut(t)		((time64_t)be32_to_cpu(t) - 2082844800U)
 #define __hfsp_ut2mt(t)		(cpu_to_be32(t + 2082844800U))
 
 /* compatibility */