Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs.Stat fails on pre-epoch mtime (<1970-01-01T00:00:00Z) #32369

Closed
daneroo opened this issue Mar 19, 2020 · 3 comments · Fixed by libuv/libuv#2747
Closed

fs.Stat fails on pre-epoch mtime (<1970-01-01T00:00:00Z) #32369

daneroo opened this issue Mar 19, 2020 · 3 comments · Fixed by libuv/libuv#2747
Labels
confirmed-bug Issues with confirmed bugs. fs Issues and PRs related to the fs subsystem / file system. libuv Issues and PRs related to the libuv dependency or the uv binding.

Comments

@daneroo
Copy link

daneroo commented Mar 19, 2020

  • Version: v13.10.1
  • Platform: Darwin dirac.imetrical.com 18.7.0 Darwin Kernel Version 18.7.0: Sun Dec 1 18:59:03 PST 2019; root:xnu-4903.278.19~1/RELEASE_X86_64 x86_64
  • Subsystem: fs.stat

What steps will reproduce the bug?

// statPreEpoch.js
const fs = require('fs')
const path = 'coco.txt'
const { mtime } = fs.statSync(path)
console.log(`mtime of ${path}: ${mtime}`)

This is on Darwin (macOS)

# This is correct
$ touch -mt 197001010000.00 coco.txt
$ stat coco.txt 
16777220 104232499 -rw-r--r-- 1 daniel staff 0 0 "Mar 19 13:26:22 2020" "Jan  1 00:00:00 1970" "Mar 19 15:01:21 2020" "Dec 31 19:00:00 1969" 4096 0 0 coco.txt
$ node statPreEpoch.js 
mtime of coco.txt: Thu Jan 01 1970 00:00:00 GMT-0500 (GMT-05:00)

# This is the bug:
touch -mt 196805160000.00 coco.txt 
stat coco.txt 
16777220 104232499 -rw-r--r-- 1 daniel staff 0 0 "Mar 19 13:26:22 2020" "May 16 00:00:00 1968" "Mar 19 15:00:23 2020" "Dec 31 19:00:00 1969" 4096 0 0 coco.txt
$ node statPreEpoch.js 
mtime of coco.txt: Invalid Date

This is on Linux (in Docker

$ docker run --rm -it -v $(pwd)/statPreEpoch.js:/src/statPreEpoch.js node:13.10 bash
$ uname -a
Linux 38f95bbffb38 4.19.76-linuxkit #1 SMP Thu Oct 17 19:31:58 UTC 2019 x86_64 GNU/Linux
$ touch -mt 197001010000.00 coco.txt
$ stat coco.txt 
  File: coco.txt
  Size: 0         	Blocks: 0          IO Block: 4096   regular empty file
Device: abh/171d	Inode: 2910905     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-03-19 19:10:48.080137057 +0000
Modify: 1970-01-01 00:00:00.000000000 +0000
Change: 2020-03-19 19:10:48.080137057 +0000
$ node  /src/statPreEpoch.js
mtime of coco.txt: Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time)

# This is the bug
$ touch -mt 196805160000.00 coco.txt 
$ stat coco.txt 
  File: coco.txt
  Size: 0         	Blocks: 0          IO Block: 4096   regular empty file
Device: abh/171d	Inode: 2910905     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-03-19 19:10:48.080137057 +0000
Modify: 1968-05-16 00:00:00.000000000 +0000
Change: 2020-03-19 19:12:24.343012135 +0000
 Birth: -
$ node  /src/statPreEpoch.js
mtime of coco.txt: Invalid Date

How often does it reproduce? Is there a required condition?

Every time that mtime < unix epoch (1970-01-01T00:00:00Z)

What is the expected behavior?

Return a valid Date Object, for example

$ node  /src/statPreEpoch.js
mtime of coco.txt: Thu May 16 1968 00:00:00 GMT+0000 (Coordinated Universal Time)
> new Date("1968-05-16T00:00:00-04:00")
1968-05-16T04:00:00.000Z
> new Date("1968-05-16T00:00:00-04:00").getTime()
-51393600000

What do you see instead?

$ node  /src/statPreEpoch.js
mtime of coco.txt: Invalid Date

Additional information

I discovered this behavior by trying to use fs.utimes which is also not able to correctly handle dates before unix epoch, although fs.utimes seems to have a workaround by using the string representation of unix time.

@addaleax addaleax added fs Issues and PRs related to the fs subsystem / file system. confirmed-bug Issues with confirmed bugs. labels Mar 20, 2020
@bnoordhuis
Copy link
Member

FWIW, the fix is trivial (see below) but I'm still working on a reliable, portable regression test. Not all file systems and operating systems let you set the date that far back.

diff --git a/src/node_file-inl.h b/src/node_file-inl.h
index e9ed18a75f..346a557f86 100644
--- a/src/node_file-inl.h
+++ b/src/node_file-inl.h
@@ -86,7 +86,7 @@ void FillStatsArray(AliasedBufferBase<NativeT, V8T>* fields,
 
 #define SET_FIELD_WITH_TIME_STAT(stat_offset, stat)                          \
   /* NOLINTNEXTLINE(runtime/int) */                                          \
-  SET_FIELD_WITH_STAT(stat_offset, static_cast<unsigned long>(stat))
+  SET_FIELD_WITH_STAT(stat_offset, static_cast<double>(stat))
 
   SET_FIELD_WITH_STAT(kDev, s->st_dev);
   SET_FIELD_WITH_STAT(kMode, s->st_mode);

@bnoordhuis
Copy link
Member

There's also a secondary libuv bug: conversion from double to struct timespec exhibits rounding errors for large values.

E.g. 1969-07-20T02:56:00.00 round-trips as 1969-07-20T02:56:00.55161.

@bnoordhuis bnoordhuis added the libuv Issues and PRs related to the libuv dependency or the uv binding. label Mar 21, 2020
bnoordhuis added a commit to bnoordhuis/libuv that referenced this issue Mar 21, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second.

Fixes: nodejs/node#32369 (partially)
bnoordhuis added a commit to bnoordhuis/io.js that referenced this issue Mar 21, 2020
Introduced in commit 9836cf5 ("lib: lazy instantiation of fs.Stats
dates") without providing any rationale - that wasn't added until later,
by someone else - and it doesn't look the least bit correct to me.

It certainly makes an upcoming regression test fail because a timestamp
in the deep past doesn't round-trip correctly, it's off by a fraction of
a second.

Refs: nodejs#32369
bnoordhuis added a commit to bnoordhuis/io.js that referenced this issue Mar 21, 2020
A wrong type cast prevented timestamps before 1970-01-01 from working
with functions like `fs.stat()`.

Fixes: nodejs#32369
@daneroo
Copy link
Author

daneroo commented Mar 22, 2020

Very happy you caught the rounding error too. Great work.

bnoordhuis added a commit to bnoordhuis/libuv that referenced this issue Mar 24, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second.

Fixes: nodejs/node#32369 (partially)
bnoordhuis added a commit to bnoordhuis/libuv that referenced this issue Mar 24, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second.

Fixes: nodejs/node#32369 (partially)
bnoordhuis added a commit to bnoordhuis/libuv that referenced this issue Mar 27, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second.

Fixes: nodejs/node#32369 (partially)
vtjnash pushed a commit to vtjnash/libuv that referenced this issue Nov 6, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second.

Fixes: nodejs/node#32369 (partially)
PR-URL: libuv#2747
vtjnash added a commit to bnoordhuis/libuv that referenced this issue Nov 6, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second on
unix, or to be reinterpreted as unsigned and end up off by more than
just sign but many also decades.

Fixes: nodejs/node#32369 (partially)
PR-URL: libuv#2747
Co-authored-by: Jameson Nash <vtjnash@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
vtjnash added a commit to bnoordhuis/libuv that referenced this issue Nov 10, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second on
unix, or to be reinterpreted as unsigned and end up off by more than
just sign but many also decades.

Fixes: nodejs/node#32369 (partially)
PR-URL: libuv#2747
Co-authored-by: Jameson Nash <vtjnash@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
vtjnash added a commit to bnoordhuis/libuv that referenced this issue Nov 18, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second on
unix, or to be reinterpreted as unsigned and end up off by more than
just sign but many also decades.

Fixes: nodejs/node#32369 (partially)
PR-URL: libuv#2747
Co-authored-by: Jameson Nash <vtjnash@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
vtjnash added a commit to vtjnash/libuv that referenced this issue Nov 18, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second on
unix, or to be reinterpreted as unsigned and end up off by more than
just sign but many also decades.

Fixes: nodejs/node#32369 (partially)
PR-URL: libuv#2747
Co-authored-by: Jameson Nash <vtjnash@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
vtjnash added a commit to bnoordhuis/libuv that referenced this issue Nov 19, 2020
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second on
unix, or to be reinterpreted as unsigned and end up off by more than
just sign but many also decades.

Fixes: nodejs/node#32369 (partially)
PR-URL: libuv#2747
Co-authored-by: Jameson Nash <vtjnash@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
JeffroMF pushed a commit to JeffroMF/libuv that referenced this issue May 16, 2022
`uv_fs_utime()` and `uv_fs_futime()` receive the timestamp as
a `double` and then convert it to `struct timeval` or `struct timespec`
where necessary but the calculation for the sub-second part exhibited
rounding errors for dates in the deep past or the far-flung future,
causing the timestamps to be off by sometimes over half a second on
unix, or to be reinterpreted as unsigned and end up off by more than
just sign but many also decades.

Fixes: nodejs/node#32369 (partially)
PR-URL: libuv#2747
Co-authored-by: Jameson Nash <vtjnash@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug Issues with confirmed bugs. fs Issues and PRs related to the fs subsystem / file system. libuv Issues and PRs related to the libuv dependency or the uv binding.
Projects
None yet
3 participants