windows/registry: correct KeyInfo.ModTime calculation

Filetime.Nanoseconds() has a major drawback: the returned int64 is too small to
represent Filetime's zero value (January 1, 1601, [1]) in terms of nanoseconds
since Epoch (00:00:00 UTC, January 1, 1970); MinInt64 [2] 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 (see note below). 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.

A note on last write time values: `lastWriteTime` is not exposed in the UI
(say, `regedit`) or in PowerShell (`Get-ItemProperty`), you need to query
`RegQueryInfoKeyA` [3] explicitly in some way [4] or another [5]. The source of
the latter is offline by now but can be found elsewhere [6] and provides a
quick way to show the value.

[1] https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
[2] https://pkg.go.dev/math#pkg-constants
[3] https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryinfokeya?redirectedfrom=MSDN
[4] https://learn.microsoft.com/en-us/windows/win32/sysinfo/retrieving-the-last-write-time
[5] https://learn-powershell.net/2014/12/18/retrieving-a-registry-key-lastwritetime-using-powershell/
[6] https://github.com/wxrdnx/GetRegistryKeyLastWriteTimeAndClassName

Fixes golang/go#74335.

Change-Id: I83dc1d6b5e0c581bfb53d58ee08a21ed9c166b0f
GitHub-Last-Rev: 0073d1d313b2c986360f3a06ffeceb6b331c0d9a
GitHub-Pull-Request: golang/sys#251
Reviewed-on: https://go-review.googlesource.com/c/sys/+/682816
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: David Chase <drchase@google.com>
diff --git a/windows/registry/export_test.go b/windows/registry/export_test.go
index 7f1ac70..f3a30e2 100644
--- a/windows/registry/export_test.go
+++ b/windows/registry/export_test.go
@@ -9,3 +9,7 @@
 func (k Key) SetValue(name string, valtype uint32, data []byte) error {
 	return k.setValue(name, valtype, data)
 }
+
+func (ki *KeyInfo) ModTimeZero() bool {
+	return ki.modTimeZero()
+}
diff --git a/windows/registry/key.go b/windows/registry/key.go
index 39aeeb6..7cc6ff3 100644
--- a/windows/registry/key.go
+++ b/windows/registry/key.go
@@ -198,7 +198,20 @@
 
 // ModTime returns the key's last write time.
 func (ki *KeyInfo) ModTime() time.Time {
-	return time.Unix(0, ki.lastWriteTime.Nanoseconds())
+	lastHigh, lastLow := ki.lastWriteTime.HighDateTime, ki.lastWriteTime.LowDateTime
+	// 100-nanosecond intervals since January 1, 1601
+	hsec := uint64(lastHigh)<<32 + uint64(lastLow)
+	// Convert _before_ gauging; the nanosecond difference between Epoch (00:00:00
+	// UTC, January 1, 1970) and Filetime's zero offset (January 1, 1601) is out
+	// of bounds for int64: -11644473600*1e7*1e2 < math.MinInt64
+	sec := int64(hsec/1e7) - 11644473600
+	nsec := int64(hsec%1e7) * 100
+	return time.Unix(sec, nsec)
+}
+
+// modTimeZero reports whether the key's last write time is zero.
+func (ki *KeyInfo) modTimeZero() bool {
+	return ki.lastWriteTime.LowDateTime == 0 && ki.lastWriteTime.HighDateTime == 0
 }
 
 // Stat retrieves information about the open key k.
diff --git a/windows/registry/registry_test.go b/windows/registry/registry_test.go
index 6e7bec5..995acc9 100644
--- a/windows/registry/registry_test.go
+++ b/windows/registry/registry_test.go
@@ -9,6 +9,7 @@
 import (
 	"bytes"
 	"crypto/rand"
+	"errors"
 	"os"
 	"syscall"
 	"testing"
@@ -674,3 +675,30 @@
 	}
 	return
 }
+
+func TestModTimeZeroValue(t *testing.T) {
+	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009`, registry.READ)
+	if err != nil {
+		if errors.Is(err, syscall.ERROR_FILE_NOT_FOUND) {
+			t.Skip("Perflib key not found; skipping")
+		}
+		t.Fatal(err)
+	}
+	defer k.Close()
+
+	// Modification time of Perflib keys is known to be set to
+	// Filetime's zero value: get stats and check.
+	stats, err := k.Stat()
+	if err != nil {
+		t.Fatal(err)
+	}
+	// First verify input is zero (assume ModTimeZero uses it directly).
+	if !stats.ModTimeZero() {
+		t.Error("Modification time of Perflib key should be zero")
+	}
+	// Then check ModTime directly thus conversion implicitly.
+	modTime := stats.ModTime()
+	if !modTime.Equal(time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC)) {
+		t.Errorf("ModTime should be 1601-01-01, but is %v", modTime)
+	}
+}