2019-05-09 10:11:15 +02:00
/ * SPDX - License - Identifier : MIT
*
2020-05-02 10:08:26 +02:00
* Copyright ( C ) 2020 WireGuard LLC . All Rights Reserved .
2019-05-09 10:11:15 +02:00
* /
package registry
import (
"errors"
"fmt"
"runtime"
"strings"
"time"
2019-05-11 06:21:02 +02:00
"unsafe"
2019-05-09 10:11:15 +02:00
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
const (
// REG_NOTIFY_CHANGE_NAME notifies the caller if a subkey is added or deleted.
REG_NOTIFY_CHANGE_NAME uint32 = 0x00000001
// REG_NOTIFY_CHANGE_ATTRIBUTES notifies the caller of changes to the attributes of the key, such as the security descriptor information.
REG_NOTIFY_CHANGE_ATTRIBUTES uint32 = 0x00000002
// REG_NOTIFY_CHANGE_LAST_SET notifies the caller of changes to a value of the key. This can include adding or deleting a value, or changing an existing value.
REG_NOTIFY_CHANGE_LAST_SET uint32 = 0x00000004
// REG_NOTIFY_CHANGE_SECURITY notifies the caller of changes to the security descriptor of the key.
REG_NOTIFY_CHANGE_SECURITY uint32 = 0x00000008
// REG_NOTIFY_THREAD_AGNOSTIC indicates that the lifetime of the registration must not be tied to the lifetime of the thread issuing the RegNotifyChangeKeyValue call. Note: This flag value is only supported in Windows 8 and later.
REG_NOTIFY_THREAD_AGNOSTIC uint32 = 0x10000000
)
//sys regNotifyChangeKeyValue(key windows.Handle, watchSubtree bool, notifyFilter uint32, event windows.Handle, asynchronous bool) (regerrno error) = advapi32.RegNotifyChangeKeyValue
func OpenKeyWait ( k registry . Key , path string , access uint32 , timeout time . Duration ) ( registry . Key , error ) {
runtime . LockOSThread ( )
defer runtime . UnlockOSThread ( )
deadline := time . Now ( ) . Add ( timeout )
pathSpl := strings . Split ( path , "\\" )
for i := 0 ; ; i ++ {
keyName := pathSpl [ i ]
isLast := i + 1 == len ( pathSpl )
event , err := windows . CreateEvent ( nil , 0 , 0 , nil )
if err != nil {
return 0 , fmt . Errorf ( "Error creating event: %v" , err )
}
defer windows . CloseHandle ( event )
var key registry . Key
for {
err = regNotifyChangeKeyValue ( windows . Handle ( k ) , false , REG_NOTIFY_CHANGE_NAME , windows . Handle ( event ) , true )
if err != nil {
return 0 , fmt . Errorf ( "Setting up change notification on registry key failed: %v" , err )
}
var accessFlags uint32
if isLast {
accessFlags = access
} else {
2019-05-10 17:37:03 +02:00
accessFlags = registry . NOTIFY
2019-05-09 10:11:15 +02:00
}
key , err = registry . OpenKey ( k , keyName , accessFlags )
if err == windows . ERROR_FILE_NOT_FOUND || err == windows . ERROR_PATH_NOT_FOUND {
timeout := time . Until ( deadline ) / time . Millisecond
if timeout < 0 {
timeout = 0
}
s , err := windows . WaitForSingleObject ( event , uint32 ( timeout ) )
if err != nil {
return 0 , fmt . Errorf ( "Unable to wait on registry key: %v" , err )
}
if s == uint32 ( windows . WAIT_TIMEOUT ) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows
return 0 , errors . New ( "Timeout waiting for registry key" )
}
} else if err != nil {
return 0 , fmt . Errorf ( "Error opening registry key %v: %v" , path , err )
} else {
if isLast {
return key , nil
}
defer key . Close ( )
break
}
}
k = key
}
}
func WaitForKey ( k registry . Key , path string , timeout time . Duration ) error {
2019-05-10 17:37:03 +02:00
key , err := OpenKeyWait ( k , path , registry . NOTIFY , timeout )
2019-05-09 10:11:15 +02:00
if err != nil {
return err
}
key . Close ( )
return nil
}
//
2019-05-11 17:25:48 +02:00
// getValue is more or less the same as windows/registry's getValue.
2019-05-11 06:21:02 +02:00
//
2019-05-11 17:25:48 +02:00
func getValue ( k registry . Key , name string , buf [ ] byte ) ( value [ ] byte , valueType uint32 , err error ) {
var name16 * uint16
name16 , err = windows . UTF16PtrFromString ( name )
2019-05-11 06:21:02 +02:00
if err != nil {
2019-05-11 17:25:48 +02:00
return
2019-05-11 06:21:02 +02:00
}
n := uint32 ( len ( buf ) )
for {
2019-05-11 17:25:48 +02:00
err = windows . RegQueryValueEx ( windows . Handle ( k ) , name16 , nil , & valueType , ( * byte ) ( unsafe . Pointer ( & buf [ 0 ] ) ) , & n )
2019-05-11 06:21:02 +02:00
if err == nil {
2019-05-11 17:25:48 +02:00
value = buf [ : n ]
return
2019-05-11 06:21:02 +02:00
}
2019-05-11 17:25:48 +02:00
if err != windows . ERROR_MORE_DATA {
return
2019-05-11 06:21:02 +02:00
}
if n <= uint32 ( len ( buf ) ) {
2019-05-11 17:25:48 +02:00
return
2019-05-11 06:21:02 +02:00
}
buf = make ( [ ] byte , n )
}
}
//
// getValueRetry function reads any value from registry. It waits for
2019-05-09 10:11:15 +02:00
// the registry value to become available or returns error on timeout.
//
2019-05-10 17:37:03 +02:00
// Key must be opened with at least QUERY_VALUE|NOTIFY access.
2019-05-09 10:11:15 +02:00
//
2019-05-11 06:21:02 +02:00
func getValueRetry ( key registry . Key , name string , buf [ ] byte , timeout time . Duration ) ( [ ] byte , uint32 , error ) {
2019-05-09 10:11:15 +02:00
runtime . LockOSThread ( )
defer runtime . UnlockOSThread ( )
event , err := windows . CreateEvent ( nil , 0 , 0 , nil )
if err != nil {
2019-05-11 06:21:02 +02:00
return nil , 0 , fmt . Errorf ( "Error creating event: %v" , err )
2019-05-09 10:11:15 +02:00
}
defer windows . CloseHandle ( event )
deadline := time . Now ( ) . Add ( timeout )
for {
err := regNotifyChangeKeyValue ( windows . Handle ( key ) , false , REG_NOTIFY_CHANGE_LAST_SET , windows . Handle ( event ) , true )
if err != nil {
2019-05-11 06:21:02 +02:00
return nil , 0 , fmt . Errorf ( "Setting up change notification on registry value failed: %v" , err )
2019-05-09 10:11:15 +02:00
}
2019-05-11 06:21:02 +02:00
buf , valueType , err := getValue ( key , name , buf )
if err == windows . ERROR_FILE_NOT_FOUND || err == windows . ERROR_PATH_NOT_FOUND {
2019-05-09 10:11:15 +02:00
timeout := time . Until ( deadline ) / time . Millisecond
if timeout < 0 {
timeout = 0
}
s , err := windows . WaitForSingleObject ( event , uint32 ( timeout ) )
if err != nil {
2019-05-11 06:21:02 +02:00
return nil , 0 , fmt . Errorf ( "Unable to wait on registry value: %v" , err )
2019-05-09 10:11:15 +02:00
}
if s == uint32 ( windows . WAIT_TIMEOUT ) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows
2019-05-11 06:21:02 +02:00
return nil , 0 , errors . New ( "Timeout waiting for registry value" )
2019-05-09 10:11:15 +02:00
}
} else if err != nil {
2019-05-11 06:21:02 +02:00
return nil , 0 , fmt . Errorf ( "Error reading registry value %v: %v" , name , err )
2019-05-09 10:11:15 +02:00
} else {
2019-05-11 06:21:02 +02:00
return buf , valueType , nil
2019-05-09 10:11:15 +02:00
}
}
}
2019-05-11 06:21:02 +02:00
func toString ( buf [ ] byte , valueType uint32 , err error ) ( string , error ) {
2019-05-09 10:11:15 +02:00
if err != nil {
return "" , err
}
2019-05-11 06:21:02 +02:00
var value string
switch valueType {
case registry . SZ , registry . EXPAND_SZ , registry . MULTI_SZ :
if len ( buf ) == 0 {
return "" , nil
}
2019-05-11 17:25:48 +02:00
value = windows . UTF16ToString ( ( * [ ( 1 << 30 ) - 1 ] uint16 ) ( unsafe . Pointer ( & buf [ 0 ] ) ) [ : len ( buf ) / 2 ] )
2019-05-11 06:21:02 +02:00
default :
return "" , registry . ErrUnexpectedType
}
2019-05-09 10:11:15 +02:00
if valueType != registry . EXPAND_SZ {
// Value does not require expansion.
return value , nil
}
valueExp , err := registry . ExpandString ( value )
if err != nil {
// Expanding failed: return original sting value.
return value , nil
}
// Return expanded value.
return valueExp , nil
}
2019-05-11 06:21:02 +02:00
func toInteger ( buf [ ] byte , valueType uint32 , err error ) ( uint64 , error ) {
if err != nil {
return 0 , err
}
switch valueType {
case registry . DWORD :
if len ( buf ) != 4 {
return 0 , errors . New ( "DWORD value is not 4 bytes long" )
}
2019-05-11 17:25:48 +02:00
var val uint32
copy ( ( * [ 4 ] byte ) ( unsafe . Pointer ( & val ) ) [ : ] , buf )
return uint64 ( val ) , nil
2019-05-11 06:21:02 +02:00
case registry . QWORD :
if len ( buf ) != 8 {
return 0 , errors . New ( "QWORD value is not 8 bytes long" )
}
2019-05-11 17:25:48 +02:00
var val uint64
copy ( ( * [ 8 ] byte ) ( unsafe . Pointer ( & val ) ) [ : ] , buf )
return val , nil
2019-05-11 06:21:02 +02:00
default :
return 0 , registry . ErrUnexpectedType
}
}
2019-05-09 10:11:15 +02:00
//
// GetStringValueWait function reads a string value from registry. It waits
// for the registry value to become available or returns error on timeout.
//
2019-05-10 17:37:03 +02:00
// Key must be opened with at least QUERY_VALUE|NOTIFY access.
2019-05-09 10:11:15 +02:00
//
// If the value type is REG_EXPAND_SZ the environment variables are expanded.
// Should expanding fail, original string value and nil error are returned.
//
2019-05-11 06:21:02 +02:00
// If the value type is REG_MULTI_SZ only the first string is returned.
2019-05-10 18:01:47 +02:00
//
2019-05-11 06:21:02 +02:00
func GetStringValueWait ( key registry . Key , name string , timeout time . Duration ) ( string , error ) {
2019-05-11 17:25:48 +02:00
return toString ( getValueRetry ( key , name , make ( [ ] byte , 256 ) , timeout ) )
2019-05-09 10:11:15 +02:00
}
//
// GetStringValue function reads a string value from registry.
//
// Key must be opened with at least QUERY_VALUE access.
//
// If the value type is REG_EXPAND_SZ the environment variables are expanded.
// Should expanding fail, original string value and nil error are returned.
//
2019-05-11 06:21:02 +02:00
// If the value type is REG_MULTI_SZ only the first string is returned.
//
2019-05-09 10:11:15 +02:00
func GetStringValue ( key registry . Key , name string ) ( string , error ) {
2019-05-11 17:25:48 +02:00
return toString ( getValue ( key , name , make ( [ ] byte , 256 ) ) )
2019-05-09 10:11:15 +02:00
}
//
// GetIntegerValueWait function reads a DWORD32 or QWORD value from registry.
// It waits for the registry value to become available or returns error on
// timeout.
//
2019-05-10 17:37:03 +02:00
// Key must be opened with at least QUERY_VALUE|NOTIFY access.
2019-05-09 10:11:15 +02:00
//
func GetIntegerValueWait ( key registry . Key , name string , timeout time . Duration ) ( uint64 , error ) {
2019-05-11 06:21:02 +02:00
return toInteger ( getValueRetry ( key , name , make ( [ ] byte , 8 ) , timeout ) )
2019-05-09 10:11:15 +02:00
}