Skip to content

Commit 933af5e

Browse files
committed
windows: fix ModTime to cover Filetime zero value
Filetime.Nanoseconds() has a major drawback: the returned int64 is too small to represent Filetime's zero value (January 1, 1601) in terms of nanoseconds since Epoch (00:00:00 UTC, January 1, 1970); MinInt64 only dates back to year 1677. This has real-life implications, e.g., some Windows sub systems (Perflib, to name one) create registry keys with the last write time property set to zero. In this case, ModTime() reports an underflow-affected value of 2185-07-22T00:34:33.709551+01:00. This commit drops usage of Nanoseconds() in favor of a conversion that converts first to seconds and nanoseconds before gauging thus is capable to cover the full range of Filetime values. Additionally, ModTimeZero() provides a convenient way to check for a last write time value of zero in analogy to time.Time.IsZero(); no need to specify January 1, 1601 manually. Fixes golang/go#74335
1 parent 5e63aa5 commit 933af5e

File tree

2 files changed

+42
-1
lines changed

2 files changed

+42
-1
lines changed

windows/registry/key.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,20 @@ type KeyInfo struct {
198198

199199
// ModTime returns the key's last write time.
200200
func (ki *KeyInfo) ModTime() time.Time {
201-
return time.Unix(0, ki.lastWriteTime.Nanoseconds())
201+
lastHigh, lastLow := ki.lastWriteTime.HighDateTime, ki.lastWriteTime.LowDateTime
202+
// 100-nanosecond intervals since January 1, 1601
203+
hsec := uint64(lastHigh)<<32 + uint64(lastLow)
204+
// Convert _before_ gauging; the nanosecond difference between Epoch (00:00:00
205+
// UTC, January 1, 1970) and Filetime's zero offset (January 1, 1601) is out
206+
// of bounds for int64: -11644473600*1e7*1e2 < math.MinInt64
207+
sec := int64(hsec/1e7) - 11644473600
208+
nsec := int64(hsec%1e7) * 100
209+
return time.Unix(sec, nsec)
210+
}
211+
212+
// ModTimeZero reports whether the key's last write time is zero.
213+
func (ki *KeyInfo) ModTimeZero() bool {
214+
return ki.lastWriteTime.LowDateTime == 0 && ki.lastWriteTime.HighDateTime == 0
202215
}
203216

204217
// Stat retrieves information about the open key k.

windows/registry/registry_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package registry_test
99
import (
1010
"bytes"
1111
"crypto/rand"
12+
"errors"
1213
"os"
1314
"syscall"
1415
"testing"
@@ -674,3 +675,30 @@ func GetDynamicTimeZoneInformation(dtzi *DynamicTimezoneinformation) (rc uint32,
674675
}
675676
return
676677
}
678+
679+
func TestModTimeZeroValue(t *testing.T) {
680+
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009`, registry.READ)
681+
if err != nil {
682+
if errors.Is(err, syscall.ERROR_FILE_NOT_FOUND) {
683+
t.Skip("Perflib key not found; skipping")
684+
}
685+
t.Fatal(err)
686+
}
687+
defer k.Close()
688+
689+
// Modification time of Perflib keys is known to be set to
690+
// Filetime's zero value: get stats and check.
691+
stats, err := k.Stat()
692+
if err != nil {
693+
t.Fatal(err)
694+
}
695+
// First verify input is zero (assume ModTimeZero uses it directly).
696+
if !stats.ModTimeZero() {
697+
t.Error("Modification time of Perflib key should be zero")
698+
}
699+
// Then check ModTime directly thus conversion implicitly.
700+
modTime := stats.ModTime()
701+
if !modTime.Equal(time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC)) {
702+
t.Errorf("ModTime should be 1601-01-01, but is %v", modTime)
703+
}
704+
}

0 commit comments

Comments
 (0)