mirror of
https://github.com/rwinkhart/sys.git
synced 2026-08-28 21:06:47 -04:00
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>
705 lines
20 KiB
Go
705 lines
20 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build windows
|
|
|
|
package registry_test
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"errors"
|
|
"os"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows/registry"
|
|
)
|
|
|
|
func randKeyName(prefix string) string {
|
|
const numbers = "0123456789"
|
|
buf := make([]byte, 10)
|
|
rand.Read(buf)
|
|
for i, b := range buf {
|
|
buf[i] = numbers[b%byte(len(numbers))]
|
|
}
|
|
return prefix + string(buf)
|
|
}
|
|
|
|
func TestReadSubKeyNames(t *testing.T) {
|
|
k, err := registry.OpenKey(registry.CLASSES_ROOT, "TypeLib", registry.ENUMERATE_SUB_KEYS)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer k.Close()
|
|
|
|
names, err := k.ReadSubKeyNames(-1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var foundStdOle bool
|
|
for _, name := range names {
|
|
// Every PC has "stdole 2.0 OLE Automation" library installed.
|
|
if name == "{00020430-0000-0000-C000-000000000046}" {
|
|
foundStdOle = true
|
|
}
|
|
}
|
|
if !foundStdOle {
|
|
t.Fatal("could not find stdole 2.0 OLE Automation")
|
|
}
|
|
}
|
|
|
|
func TestCreateOpenDeleteKey(t *testing.T) {
|
|
k, err := registry.OpenKey(registry.CURRENT_USER, "Software", registry.QUERY_VALUE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer k.Close()
|
|
|
|
testKName := randKeyName("TestCreateOpenDeleteKey_")
|
|
|
|
testK, exist, err := registry.CreateKey(k, testKName, registry.CREATE_SUB_KEY)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer testK.Close()
|
|
|
|
if exist {
|
|
t.Fatalf("key %q already exists", testKName)
|
|
}
|
|
|
|
testKAgain, exist, err := registry.CreateKey(k, testKName, registry.CREATE_SUB_KEY)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer testKAgain.Close()
|
|
|
|
if !exist {
|
|
t.Fatalf("key %q should already exist", testKName)
|
|
}
|
|
|
|
testKOpened, err := registry.OpenKey(k, testKName, registry.ENUMERATE_SUB_KEYS)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer testKOpened.Close()
|
|
|
|
err = registry.DeleteKey(k, testKName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
testKOpenedAgain, err := registry.OpenKey(k, testKName, registry.ENUMERATE_SUB_KEYS)
|
|
if err == nil {
|
|
defer testKOpenedAgain.Close()
|
|
t.Fatalf("key %q should already been deleted", testKName)
|
|
}
|
|
if err != registry.ErrNotExist {
|
|
t.Fatalf(`unexpected error ("not exist" expected): %v`, err)
|
|
}
|
|
}
|
|
|
|
func equalStringSlice(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
if a == nil {
|
|
return true
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
type ValueTest struct {
|
|
Type uint32
|
|
Name string
|
|
Value interface{}
|
|
WillFail bool
|
|
}
|
|
|
|
var ValueTests = []ValueTest{
|
|
{Type: registry.SZ, Name: "String1", Value: ""},
|
|
{Type: registry.SZ, Name: "String2", Value: "\000", WillFail: true},
|
|
{Type: registry.SZ, Name: "String3", Value: "Hello World"},
|
|
{Type: registry.SZ, Name: "String4", Value: "Hello World\000", WillFail: true},
|
|
{Type: registry.EXPAND_SZ, Name: "ExpString1", Value: ""},
|
|
{Type: registry.EXPAND_SZ, Name: "ExpString2", Value: "\000", WillFail: true},
|
|
{Type: registry.EXPAND_SZ, Name: "ExpString3", Value: "Hello World"},
|
|
{Type: registry.EXPAND_SZ, Name: "ExpString4", Value: "Hello\000World", WillFail: true},
|
|
{Type: registry.EXPAND_SZ, Name: "ExpString5", Value: "%PATH%"},
|
|
{Type: registry.EXPAND_SZ, Name: "ExpString6", Value: "%NO_SUCH_VARIABLE%"},
|
|
{Type: registry.EXPAND_SZ, Name: "ExpString7", Value: "%PATH%;."},
|
|
{Type: registry.BINARY, Name: "Binary1", Value: []byte{}},
|
|
{Type: registry.BINARY, Name: "Binary2", Value: []byte{1, 2, 3}},
|
|
{Type: registry.BINARY, Name: "Binary3", Value: []byte{3, 2, 1, 0, 1, 2, 3}},
|
|
{Type: registry.DWORD, Name: "Dword1", Value: uint64(0)},
|
|
{Type: registry.DWORD, Name: "Dword2", Value: uint64(1)},
|
|
{Type: registry.DWORD, Name: "Dword3", Value: uint64(0xff)},
|
|
{Type: registry.DWORD, Name: "Dword4", Value: uint64(0xffff)},
|
|
{Type: registry.QWORD, Name: "Qword1", Value: uint64(0)},
|
|
{Type: registry.QWORD, Name: "Qword2", Value: uint64(1)},
|
|
{Type: registry.QWORD, Name: "Qword3", Value: uint64(0xff)},
|
|
{Type: registry.QWORD, Name: "Qword4", Value: uint64(0xffff)},
|
|
{Type: registry.QWORD, Name: "Qword5", Value: uint64(0xffffff)},
|
|
{Type: registry.QWORD, Name: "Qword6", Value: uint64(0xffffffff)},
|
|
{Type: registry.MULTI_SZ, Name: "MultiString1", Value: []string{"a", "b", "c"}},
|
|
{Type: registry.MULTI_SZ, Name: "MultiString2", Value: []string{"abc", "", "cba"}},
|
|
{Type: registry.MULTI_SZ, Name: "MultiString3", Value: []string{""}},
|
|
{Type: registry.MULTI_SZ, Name: "MultiString4", Value: []string{"abcdef"}},
|
|
{Type: registry.MULTI_SZ, Name: "MultiString5", Value: []string{"\000"}, WillFail: true},
|
|
{Type: registry.MULTI_SZ, Name: "MultiString6", Value: []string{"a\000b"}, WillFail: true},
|
|
{Type: registry.MULTI_SZ, Name: "MultiString7", Value: []string{"ab", "\000", "cd"}, WillFail: true},
|
|
{Type: registry.MULTI_SZ, Name: "MultiString8", Value: []string{"\000", "cd"}, WillFail: true},
|
|
{Type: registry.MULTI_SZ, Name: "MultiString9", Value: []string{"ab", "\000"}, WillFail: true},
|
|
}
|
|
|
|
func setValues(t *testing.T, k registry.Key) {
|
|
for _, test := range ValueTests {
|
|
var err error
|
|
switch test.Type {
|
|
case registry.SZ:
|
|
err = k.SetStringValue(test.Name, test.Value.(string))
|
|
case registry.EXPAND_SZ:
|
|
err = k.SetExpandStringValue(test.Name, test.Value.(string))
|
|
case registry.MULTI_SZ:
|
|
err = k.SetStringsValue(test.Name, test.Value.([]string))
|
|
case registry.BINARY:
|
|
err = k.SetBinaryValue(test.Name, test.Value.([]byte))
|
|
case registry.DWORD:
|
|
err = k.SetDWordValue(test.Name, uint32(test.Value.(uint64)))
|
|
case registry.QWORD:
|
|
err = k.SetQWordValue(test.Name, test.Value.(uint64))
|
|
default:
|
|
t.Fatalf("unsupported type %d for %s value", test.Type, test.Name)
|
|
}
|
|
if test.WillFail {
|
|
if err == nil {
|
|
t.Fatalf("setting %s value %q should fail, but succeeded", test.Name, test.Value)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func enumerateValues(t *testing.T, k registry.Key) {
|
|
names, err := k.ReadValueNames(-1)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
haveNames := make(map[string]bool)
|
|
for _, n := range names {
|
|
haveNames[n] = false
|
|
}
|
|
for _, test := range ValueTests {
|
|
wantFound := !test.WillFail
|
|
_, haveFound := haveNames[test.Name]
|
|
if wantFound && !haveFound {
|
|
t.Errorf("value %s is not found while enumerating", test.Name)
|
|
}
|
|
if haveFound && !wantFound {
|
|
t.Errorf("value %s is found while enumerating, but expected to fail", test.Name)
|
|
}
|
|
if haveFound {
|
|
delete(haveNames, test.Name)
|
|
}
|
|
}
|
|
for n, v := range haveNames {
|
|
t.Errorf("value %s (%v) is found while enumerating, but has not been created", n, v)
|
|
}
|
|
}
|
|
|
|
func testErrNotExist(t *testing.T, name string, err error) {
|
|
if err == nil {
|
|
t.Errorf("%s value should not exist", name)
|
|
return
|
|
}
|
|
if err != registry.ErrNotExist {
|
|
t.Errorf("reading %s value should return 'not exist' error, but got: %s", name, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testErrUnexpectedType(t *testing.T, test ValueTest, gottype uint32, err error) {
|
|
if err == nil {
|
|
t.Errorf("GetXValue(%q) should not succeed", test.Name)
|
|
return
|
|
}
|
|
if err != registry.ErrUnexpectedType {
|
|
t.Errorf("reading %s value should return 'unexpected key value type' error, but got: %s", test.Name, err)
|
|
return
|
|
}
|
|
if gottype != test.Type {
|
|
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testGetStringValue(t *testing.T, k registry.Key, test ValueTest) {
|
|
got, gottype, err := k.GetStringValue(test.Name)
|
|
if err != nil {
|
|
t.Errorf("GetStringValue(%s) failed: %v", test.Name, err)
|
|
return
|
|
}
|
|
if got != test.Value {
|
|
t.Errorf("want %s value %q, got %q", test.Name, test.Value, got)
|
|
return
|
|
}
|
|
if gottype != test.Type {
|
|
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
|
|
return
|
|
}
|
|
if gottype == registry.EXPAND_SZ {
|
|
_, err = registry.ExpandString(got)
|
|
if err != nil {
|
|
t.Errorf("ExpandString(%s) failed: %v", got, err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func testGetIntegerValue(t *testing.T, k registry.Key, test ValueTest) {
|
|
got, gottype, err := k.GetIntegerValue(test.Name)
|
|
if err != nil {
|
|
t.Errorf("GetIntegerValue(%s) failed: %v", test.Name, err)
|
|
return
|
|
}
|
|
if got != test.Value.(uint64) {
|
|
t.Errorf("want %s value %v, got %v", test.Name, test.Value, got)
|
|
return
|
|
}
|
|
if gottype != test.Type {
|
|
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testGetBinaryValue(t *testing.T, k registry.Key, test ValueTest) {
|
|
got, gottype, err := k.GetBinaryValue(test.Name)
|
|
if err != nil {
|
|
t.Errorf("GetBinaryValue(%s) failed: %v", test.Name, err)
|
|
return
|
|
}
|
|
if !bytes.Equal(got, test.Value.([]byte)) {
|
|
t.Errorf("want %s value %v, got %v", test.Name, test.Value, got)
|
|
return
|
|
}
|
|
if gottype != test.Type {
|
|
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testGetStringsValue(t *testing.T, k registry.Key, test ValueTest) {
|
|
got, gottype, err := k.GetStringsValue(test.Name)
|
|
if err != nil {
|
|
t.Errorf("GetStringsValue(%s) failed: %v", test.Name, err)
|
|
return
|
|
}
|
|
if !equalStringSlice(got, test.Value.([]string)) {
|
|
t.Errorf("want %s value %#v, got %#v", test.Name, test.Value, got)
|
|
return
|
|
}
|
|
if gottype != test.Type {
|
|
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testGetValue(t *testing.T, k registry.Key, test ValueTest, size int) {
|
|
if size <= 0 {
|
|
return
|
|
}
|
|
// read data with no buffer
|
|
gotsize, gottype, err := k.GetValue(test.Name, nil)
|
|
if err != nil {
|
|
t.Errorf("GetValue(%s, [%d]byte) failed: %v", test.Name, size, err)
|
|
return
|
|
}
|
|
if gotsize != size {
|
|
t.Errorf("want %s value size of %d, got %v", test.Name, size, gotsize)
|
|
return
|
|
}
|
|
if gottype != test.Type {
|
|
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
|
|
return
|
|
}
|
|
// read data with short buffer
|
|
gotsize, gottype, err = k.GetValue(test.Name, make([]byte, size-1))
|
|
if err == nil {
|
|
t.Errorf("GetValue(%s, [%d]byte) should fail, but succeeded", test.Name, size-1)
|
|
return
|
|
}
|
|
if err != registry.ErrShortBuffer {
|
|
t.Errorf("reading %s value should return 'short buffer' error, but got: %s", test.Name, err)
|
|
return
|
|
}
|
|
if gotsize != size {
|
|
t.Errorf("want %s value size of %d, got %v", test.Name, size, gotsize)
|
|
return
|
|
}
|
|
if gottype != test.Type {
|
|
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
|
|
return
|
|
}
|
|
// read full data
|
|
gotsize, gottype, err = k.GetValue(test.Name, make([]byte, size))
|
|
if err != nil {
|
|
t.Errorf("GetValue(%s, [%d]byte) failed: %v", test.Name, size, err)
|
|
return
|
|
}
|
|
if gotsize != size {
|
|
t.Errorf("want %s value size of %d, got %v", test.Name, size, gotsize)
|
|
return
|
|
}
|
|
if gottype != test.Type {
|
|
t.Errorf("want %s value type %v, got %v", test.Name, test.Type, gottype)
|
|
return
|
|
}
|
|
// check GetValue returns ErrNotExist as required
|
|
_, _, err = k.GetValue(test.Name+"_not_there", make([]byte, size))
|
|
if err == nil {
|
|
t.Errorf("GetValue(%q) should not succeed", test.Name)
|
|
return
|
|
}
|
|
if err != registry.ErrNotExist {
|
|
t.Errorf("GetValue(%q) should return 'not exist' error, but got: %s", test.Name, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func testValues(t *testing.T, k registry.Key) {
|
|
for _, test := range ValueTests {
|
|
switch test.Type {
|
|
case registry.SZ, registry.EXPAND_SZ:
|
|
if test.WillFail {
|
|
_, _, err := k.GetStringValue(test.Name)
|
|
testErrNotExist(t, test.Name, err)
|
|
} else {
|
|
testGetStringValue(t, k, test)
|
|
_, gottype, err := k.GetIntegerValue(test.Name)
|
|
testErrUnexpectedType(t, test, gottype, err)
|
|
// Size of utf16 string in bytes is not perfect,
|
|
// but correct for current test values.
|
|
// Size also includes terminating 0.
|
|
testGetValue(t, k, test, (len(test.Value.(string))+1)*2)
|
|
}
|
|
_, _, err := k.GetStringValue(test.Name + "_string_not_created")
|
|
testErrNotExist(t, test.Name+"_string_not_created", err)
|
|
case registry.DWORD, registry.QWORD:
|
|
testGetIntegerValue(t, k, test)
|
|
_, gottype, err := k.GetBinaryValue(test.Name)
|
|
testErrUnexpectedType(t, test, gottype, err)
|
|
_, _, err = k.GetIntegerValue(test.Name + "_int_not_created")
|
|
testErrNotExist(t, test.Name+"_int_not_created", err)
|
|
size := 8
|
|
if test.Type == registry.DWORD {
|
|
size = 4
|
|
}
|
|
testGetValue(t, k, test, size)
|
|
case registry.BINARY:
|
|
testGetBinaryValue(t, k, test)
|
|
_, gottype, err := k.GetStringsValue(test.Name)
|
|
testErrUnexpectedType(t, test, gottype, err)
|
|
_, _, err = k.GetBinaryValue(test.Name + "_byte_not_created")
|
|
testErrNotExist(t, test.Name+"_byte_not_created", err)
|
|
testGetValue(t, k, test, len(test.Value.([]byte)))
|
|
case registry.MULTI_SZ:
|
|
if test.WillFail {
|
|
_, _, err := k.GetStringsValue(test.Name)
|
|
testErrNotExist(t, test.Name, err)
|
|
} else {
|
|
testGetStringsValue(t, k, test)
|
|
_, gottype, err := k.GetStringValue(test.Name)
|
|
testErrUnexpectedType(t, test, gottype, err)
|
|
size := 0
|
|
for _, s := range test.Value.([]string) {
|
|
size += len(s) + 1 // nil terminated
|
|
}
|
|
size += 1 // extra nil at the end
|
|
size *= 2 // count bytes, not uint16
|
|
testGetValue(t, k, test, size)
|
|
}
|
|
_, _, err := k.GetStringsValue(test.Name + "_strings_not_created")
|
|
testErrNotExist(t, test.Name+"_strings_not_created", err)
|
|
default:
|
|
t.Errorf("unsupported type %d for %s value", test.Type, test.Name)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func testStat(t *testing.T, k registry.Key) {
|
|
subk, _, err := registry.CreateKey(k, "subkey", registry.CREATE_SUB_KEY)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer subk.Close()
|
|
|
|
defer registry.DeleteKey(k, "subkey")
|
|
|
|
ki, err := k.Stat()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if ki.SubKeyCount != 1 {
|
|
t.Error("key must have 1 subkey")
|
|
}
|
|
if ki.MaxSubKeyLen != 6 {
|
|
t.Error("key max subkey name length must be 6")
|
|
}
|
|
if ki.ValueCount != 24 {
|
|
t.Errorf("key must have 24 values, but is %d", ki.ValueCount)
|
|
}
|
|
if ki.MaxValueNameLen != 12 {
|
|
t.Errorf("key max value name length must be 10, but is %d", ki.MaxValueNameLen)
|
|
}
|
|
if ki.MaxValueLen != 38 {
|
|
t.Errorf("key max value length must be 38, but is %d", ki.MaxValueLen)
|
|
}
|
|
if mt, ct := ki.ModTime(), time.Now(); ct.Sub(mt) > 100*time.Millisecond {
|
|
t.Errorf("key mod time is not close to current time: mtime=%v current=%v delta=%v", mt, ct, ct.Sub(mt))
|
|
}
|
|
}
|
|
|
|
func deleteValues(t *testing.T, k registry.Key) {
|
|
for _, test := range ValueTests {
|
|
if test.WillFail {
|
|
continue
|
|
}
|
|
err := k.DeleteValue(test.Name)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
}
|
|
names, err := k.ReadValueNames(-1)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if len(names) != 0 {
|
|
t.Errorf("some values remain after deletion: %v", names)
|
|
}
|
|
}
|
|
|
|
func TestValues(t *testing.T) {
|
|
softwareK, err := registry.OpenKey(registry.CURRENT_USER, "Software", registry.QUERY_VALUE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer softwareK.Close()
|
|
|
|
testKName := randKeyName("TestValues_")
|
|
|
|
k, exist, err := registry.CreateKey(softwareK, testKName, registry.CREATE_SUB_KEY|registry.QUERY_VALUE|registry.SET_VALUE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer k.Close()
|
|
|
|
if exist {
|
|
t.Fatalf("key %q already exists", testKName)
|
|
}
|
|
|
|
defer registry.DeleteKey(softwareK, testKName)
|
|
|
|
setValues(t, k)
|
|
|
|
enumerateValues(t, k)
|
|
|
|
testValues(t, k)
|
|
|
|
testStat(t, k)
|
|
|
|
deleteValues(t, k)
|
|
}
|
|
|
|
func TestExpandString(t *testing.T) {
|
|
got, err := registry.ExpandString("%PATH%")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := os.Getenv("PATH")
|
|
if got != want {
|
|
t.Errorf("want %q string expanded, got %q", want, got)
|
|
}
|
|
}
|
|
|
|
func TestInvalidValues(t *testing.T) {
|
|
softwareK, err := registry.OpenKey(registry.CURRENT_USER, "Software", registry.QUERY_VALUE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer softwareK.Close()
|
|
|
|
testKName := randKeyName("TestInvalidValues_")
|
|
|
|
k, exist, err := registry.CreateKey(softwareK, testKName, registry.CREATE_SUB_KEY|registry.QUERY_VALUE|registry.SET_VALUE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer k.Close()
|
|
|
|
if exist {
|
|
t.Fatalf("key %q already exists", testKName)
|
|
}
|
|
|
|
defer registry.DeleteKey(softwareK, testKName)
|
|
|
|
var tests = []struct {
|
|
Type uint32
|
|
Name string
|
|
Data []byte
|
|
}{
|
|
{registry.DWORD, "Dword1", nil},
|
|
{registry.DWORD, "Dword2", []byte{1, 2, 3}},
|
|
{registry.QWORD, "Qword1", nil},
|
|
{registry.QWORD, "Qword2", []byte{1, 2, 3}},
|
|
{registry.QWORD, "Qword3", []byte{1, 2, 3, 4, 5, 6, 7}},
|
|
{registry.MULTI_SZ, "MultiString1", nil},
|
|
{registry.MULTI_SZ, "MultiString2", []byte{0}},
|
|
{registry.MULTI_SZ, "MultiString3", []byte{'a', 'b', 0}},
|
|
{registry.MULTI_SZ, "MultiString4", []byte{'a', 0, 0, 'b', 0}},
|
|
{registry.MULTI_SZ, "MultiString5", []byte{'a', 0, 0}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
err := k.SetValue(test.Name, test.Type, test.Data)
|
|
if err != nil {
|
|
t.Fatalf("SetValue for %q failed: %v", test.Name, err)
|
|
}
|
|
}
|
|
|
|
for _, test := range tests {
|
|
switch test.Type {
|
|
case registry.DWORD, registry.QWORD:
|
|
value, valType, err := k.GetIntegerValue(test.Name)
|
|
if err == nil {
|
|
t.Errorf("GetIntegerValue(%q) succeeded. Returns type=%d value=%v", test.Name, valType, value)
|
|
}
|
|
case registry.MULTI_SZ:
|
|
value, valType, err := k.GetStringsValue(test.Name)
|
|
if err == nil {
|
|
if len(value) != 0 {
|
|
t.Errorf("GetStringsValue(%q) succeeded. Returns type=%d value=%v", test.Name, valType, value)
|
|
}
|
|
}
|
|
default:
|
|
t.Errorf("unsupported type %d for %s value", test.Type, test.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetMUIStringValue(t *testing.T) {
|
|
if err := registry.LoadRegLoadMUIString(); err != nil {
|
|
t.Skip("regLoadMUIString not supported; skipping")
|
|
}
|
|
if err := procGetDynamicTimeZoneInformation.Find(); err != nil {
|
|
t.Skipf("%s not supported; skipping", procGetDynamicTimeZoneInformation.Name)
|
|
}
|
|
var dtzi DynamicTimezoneinformation
|
|
if _, err := GetDynamicTimeZoneInformation(&dtzi); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tzKeyName := syscall.UTF16ToString(dtzi.TimeZoneKeyName[:])
|
|
timezoneK, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
|
`SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\`+tzKeyName, registry.READ)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer timezoneK.Close()
|
|
|
|
type testType struct {
|
|
name string
|
|
want string
|
|
}
|
|
var tests = []testType{
|
|
{"MUI_Std", syscall.UTF16ToString(dtzi.StandardName[:])},
|
|
}
|
|
if dtzi.DynamicDaylightTimeDisabled == 0 {
|
|
tests = append(tests, testType{"MUI_Dlt", syscall.UTF16ToString(dtzi.DaylightName[:])})
|
|
}
|
|
|
|
for _, test := range tests {
|
|
got, err := timezoneK.GetMUIStringValue(test.name)
|
|
if err != nil {
|
|
t.Error("GetMUIStringValue:", err)
|
|
}
|
|
|
|
if got != test.want {
|
|
t.Errorf("GetMUIStringValue: %s: Got %q, want %q", test.name, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
type DynamicTimezoneinformation struct {
|
|
Bias int32
|
|
StandardName [32]uint16
|
|
StandardDate syscall.Systemtime
|
|
StandardBias int32
|
|
DaylightName [32]uint16
|
|
DaylightDate syscall.Systemtime
|
|
DaylightBias int32
|
|
TimeZoneKeyName [128]uint16
|
|
DynamicDaylightTimeDisabled uint8
|
|
}
|
|
|
|
var (
|
|
kernel32DLL = syscall.NewLazyDLL("kernel32")
|
|
|
|
procGetDynamicTimeZoneInformation = kernel32DLL.NewProc("GetDynamicTimeZoneInformation")
|
|
)
|
|
|
|
func GetDynamicTimeZoneInformation(dtzi *DynamicTimezoneinformation) (rc uint32, err error) {
|
|
r0, _, e1 := syscall.Syscall(procGetDynamicTimeZoneInformation.Addr(), 1, uintptr(unsafe.Pointer(dtzi)), 0, 0)
|
|
rc = uint32(r0)
|
|
if rc == 0xffffffff {
|
|
if e1 != 0 {
|
|
err = error(e1)
|
|
} else {
|
|
err = syscall.EINVAL
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
}
|