Browse code

Windows CI: TP4 reliability hack

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2016/02/02 03:13:54
Showing 9 changed files
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"path/filepath"
10 10
 	"strconv"
11 11
 	"strings"
12
+	"time"
12 13
 
13 14
 	"github.com/Sirupsen/logrus"
14 15
 	"github.com/docker/docker/daemon/execdriver"
... ...
@@ -223,10 +224,32 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd
223 223
 
224 224
 	configuration := string(configurationb)
225 225
 
226
-	err = hcsshim.CreateComputeSystem(c.ID, configuration)
227
-	if err != nil {
228
-		logrus.Debugln("Failed to create temporary container ", err)
229
-		return execdriver.ExitStatus{ExitCode: -1}, err
226
+	// TODO Windows TP5 timeframe. Remove when TP4 is no longer supported.
227
+	// The following a workaround for Windows TP4 which has a networking
228
+	// bug which fairly frequently returns an error. Back off and retry.
229
+	maxAttempts := 1
230
+	if TP4RetryHack {
231
+		maxAttempts = 5
232
+	}
233
+	i := 0
234
+	for i < maxAttempts {
235
+		i++
236
+		err = hcsshim.CreateComputeSystem(c.ID, configuration)
237
+		if err != nil {
238
+			if TP4RetryHack {
239
+				if !strings.Contains(err.Error(), `Win32 API call returned error r1=2147746291`) && // Invalid class string
240
+					!strings.Contains(err.Error(), `Win32 API call returned error r1=2147943568`) && // Element not found
241
+					!strings.Contains(err.Error(), `Win32 API call returned error r1=2147942402`) && // The system cannot find the file specified
242
+					!strings.Contains(err.Error(), `Win32 API call returned error r1=2147943622`) { // The network is not present or not started
243
+					logrus.Debugln("Failed to create temporary container ", err)
244
+					return execdriver.ExitStatus{ExitCode: -1}, err
245
+				}
246
+				logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1)
247
+				time.Sleep(50 * time.Millisecond)
248
+			}
249
+		} else {
250
+			break
251
+		}
230 252
 	}
231 253
 
232 254
 	// Start the container
... ...
@@ -4,6 +4,7 @@ package windows
4 4
 
5 5
 import (
6 6
 	"fmt"
7
+	"strconv"
7 8
 	"strings"
8 9
 	"sync"
9 10
 
... ...
@@ -12,8 +13,13 @@ import (
12 12
 	"github.com/docker/docker/dockerversion"
13 13
 	"github.com/docker/docker/pkg/parsers"
14 14
 	"github.com/docker/engine-api/types/container"
15
+	"golang.org/x/sys/windows/registry"
15 16
 )
16 17
 
18
+// TP4RetryHack is a hack to retry CreateComputeSystem if it fails with
19
+// known return codes from Windows due to bugs in TP4.
20
+var TP4RetryHack bool
21
+
17 22
 // This is a daemon development variable only and should not be
18 23
 // used for running production containers on Windows.
19 24
 var dummyMode bool
... ...
@@ -89,6 +95,37 @@ func NewDriver(root string, options []string) (*Driver, error) {
89 89
 		}
90 90
 	}
91 91
 
92
+	// TODO Windows TP5 timeframe. Remove this next block of code once TP4
93
+	// is no longer supported. Also remove the workaround in run.go.
94
+	//
95
+	// Hack for TP4 - determine the version of Windows from the registry.
96
+	// This overcomes an issue on TP4 which causes CreateComputeSystem to
97
+	// intermittently fail. It's predominantly here to make Windows to Windows
98
+	// CI more reliable.
99
+	TP4RetryHack = false
100
+	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
101
+	if err != nil {
102
+		return &Driver{}, err
103
+	}
104
+	defer k.Close()
105
+
106
+	s, _, err := k.GetStringValue("BuildLab")
107
+	if err != nil {
108
+		return &Driver{}, err
109
+	}
110
+	parts := strings.Split(s, ".")
111
+	if len(parts) < 1 {
112
+		return &Driver{}, err
113
+	}
114
+	var val int
115
+	if val, err = strconv.Atoi(parts[0]); err != nil {
116
+		return &Driver{}, err
117
+	}
118
+	if val < 14250 {
119
+		TP4RetryHack = true
120
+	}
121
+	// End of Windows TP4 hack
122
+
92 123
 	return &Driver{
93 124
 		root:             root,
94 125
 		activeContainers: make(map[string]*activeContainer),
... ...
@@ -20,6 +20,7 @@ clone git github.com/mistifyio/go-zfs v2.1.1
20 20
 clone git github.com/tchap/go-patricia v2.1.0
21 21
 clone git github.com/vdemeester/shakers 3c10293ce22b900c27acad7b28656196fcc2f73b
22 22
 clone git golang.org/x/net 47990a1ba55743e6ef1affd3a14e5bac8553615d https://github.com/golang/net.git
23
+clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git
23 24
 clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
24 25
 clone git github.com/docker/go-connections v0.1.2
25 26
 clone git github.com/docker/engine-api bdbab71ec21209ef56dffdbe42c9d21843c30862
... ...
@@ -17,21 +17,17 @@ import (
17 17
 )
18 18
 
19 19
 func (s *DockerSuite) TestPsListContainersBase(c *check.C) {
20
-	// TODO Windows CI (TP5 timeframe). This test has a very high likelyhood
21
-	// of hitting a known bug in TP4. Hence disabling for now until TP5
22
-	// is available.
23
-	testRequires(c, DaemonIsLinux)
24
-	out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
20
+	out, _ := runSleepingContainer(c, "-d")
25 21
 	firstID := strings.TrimSpace(out)
26 22
 
27
-	out, _ = dockerCmd(c, "run", "-d", "busybox", "top")
23
+	out, _ = runSleepingContainer(c, "-d")
28 24
 	secondID := strings.TrimSpace(out)
29 25
 
30 26
 	// not long running
31 27
 	out, _ = dockerCmd(c, "run", "-d", "busybox", "true")
32 28
 	thirdID := strings.TrimSpace(out)
33 29
 
34
-	out, _ = dockerCmd(c, "run", "-d", "busybox", "top")
30
+	out, _ = runSleepingContainer(c, "-d")
35 31
 	fourthID := strings.TrimSpace(out)
36 32
 
37 33
 	// make sure the second is running
... ...
@@ -172,10 +168,6 @@ func (s *DockerSuite) TestPsListContainersSize(c *check.C) {
172 172
 }
173 173
 
174 174
 func (s *DockerSuite) TestPsListContainersFilterStatus(c *check.C) {
175
-	// TODO Windows CI (TP5 timeframe). This test has a very high likelyhood
176
-	// of hitting a known bug in TP4. Hence disabling for now until TP5
177
-	// is available.
178
-	testRequires(c, DaemonIsLinux)
179 175
 	// start exited container
180 176
 	out, _ := dockerCmd(c, "run", "-d", "busybox")
181 177
 	firstID := strings.TrimSpace(out)
... ...
@@ -215,10 +207,6 @@ func (s *DockerSuite) TestPsListContainersFilterStatus(c *check.C) {
215 215
 }
216 216
 
217 217
 func (s *DockerSuite) TestPsListContainersFilterID(c *check.C) {
218
-	// TODO Windows CI (TP5 timeframe). This test has a very high likelyhood
219
-	// of hitting a known bug in TP4. Hence disabling for now until TP5
220
-	// is available.
221
-	testRequires(c, DaemonIsLinux)
222 218
 	// start container
223 219
 	out, _ := dockerCmd(c, "run", "-d", "busybox")
224 220
 	firstID := strings.TrimSpace(out)
... ...
@@ -257,10 +245,6 @@ func (s *DockerSuite) TestPsListContainersFilterName(c *check.C) {
257 257
 // - Run containers for each of those image (busybox, images_ps_filter_test1, images_ps_filter_test2)
258 258
 // - Filter them out :P
259 259
 func (s *DockerSuite) TestPsListContainersFilterAncestorImage(c *check.C) {
260
-	// TODO Windows CI (TP5 timeframe). This test has a very high likelyhood
261
-	// of hitting a known bug in TP4. Hence disabling for now until TP5
262
-	// is available.
263
-	testRequires(c, DaemonIsLinux)
264 260
 	// Build images
265 261
 	imageName1 := "images_ps_filter_test1"
266 262
 	imageID1, err := buildImage(imageName1,
... ...
@@ -356,10 +340,6 @@ func checkPsAncestorFilterOutput(c *check.C, out string, filterName string, expe
356 356
 }
357 357
 
358 358
 func (s *DockerSuite) TestPsListContainersFilterLabel(c *check.C) {
359
-	// TODO Windows CI (TP5 timeframe). This test has a very high likelyhood
360
-	// of hitting a known bug in TP4. Hence disabling for now until TP5
361
-	// is available.
362
-	testRequires(c, DaemonIsLinux)
363 359
 	// start container
364 360
 	out, _ := dockerCmd(c, "run", "-d", "-l", "match=me", "-l", "second=tag", "busybox")
365 361
 	firstID := strings.TrimSpace(out)
... ...
@@ -396,11 +376,6 @@ func (s *DockerSuite) TestPsListContainersFilterLabel(c *check.C) {
396 396
 }
397 397
 
398 398
 func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) {
399
-	// TODO Windows CI (TP5 timeframe). This test has a very high likelyhood
400
-	// of hitting a known bug in TP4. Hence disabling for now until TP5
401
-	// is available.
402
-	testRequires(c, DaemonIsLinux)
403
-	testRequires(c, DaemonIsLinux)
404 399
 	runSleepingContainer(c, "--name=sleep")
405 400
 
406 401
 	dockerCmd(c, "run", "--name", "zero1", "busybox", "true")
407 402
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+Copyright (c) 2009 The Go Authors. All rights reserved.
1
+
2
+Redistribution and use in source and binary forms, with or without
3
+modification, are permitted provided that the following conditions are
4
+met:
5
+
6
+   * Redistributions of source code must retain the above copyright
7
+notice, this list of conditions and the following disclaimer.
8
+   * Redistributions in binary form must reproduce the above
9
+copyright notice, this list of conditions and the following disclaimer
10
+in the documentation and/or other materials provided with the
11
+distribution.
12
+   * Neither the name of Google Inc. nor the names of its
13
+contributors may be used to endorse or promote products derived from
14
+this software without specific prior written permission.
15
+
16
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 27
new file mode 100644
... ...
@@ -0,0 +1,178 @@
0
+// Copyright 2015 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build windows
5
+
6
+// Package registry provides access to the Windows registry.
7
+//
8
+// Here is a simple example, opening a registry key and reading a string value from it.
9
+//
10
+//	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
11
+//	if err != nil {
12
+//		log.Fatal(err)
13
+//	}
14
+//	defer k.Close()
15
+//
16
+//	s, _, err := k.GetStringValue("SystemRoot")
17
+//	if err != nil {
18
+//		log.Fatal(err)
19
+//	}
20
+//	fmt.Printf("Windows system root is %q\n", s)
21
+//
22
+package registry
23
+
24
+import (
25
+	"io"
26
+	"syscall"
27
+	"time"
28
+)
29
+
30
+const (
31
+	// Registry key security and access rights.
32
+	// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878.aspx
33
+	// for details.
34
+	ALL_ACCESS         = 0xf003f
35
+	CREATE_LINK        = 0x00020
36
+	CREATE_SUB_KEY     = 0x00004
37
+	ENUMERATE_SUB_KEYS = 0x00008
38
+	EXECUTE            = 0x20019
39
+	NOTIFY             = 0x00010
40
+	QUERY_VALUE        = 0x00001
41
+	READ               = 0x20019
42
+	SET_VALUE          = 0x00002
43
+	WOW64_32KEY        = 0x00200
44
+	WOW64_64KEY        = 0x00100
45
+	WRITE              = 0x20006
46
+)
47
+
48
+// Key is a handle to an open Windows registry key.
49
+// Keys can be obtained by calling OpenKey; there are
50
+// also some predefined root keys such as CURRENT_USER.
51
+// Keys can be used directly in the Windows API.
52
+type Key syscall.Handle
53
+
54
+const (
55
+	// Windows defines some predefined root keys that are always open.
56
+	// An application can use these keys as entry points to the registry.
57
+	// Normally these keys are used in OpenKey to open new keys,
58
+	// but they can also be used anywhere a Key is required.
59
+	CLASSES_ROOT   = Key(syscall.HKEY_CLASSES_ROOT)
60
+	CURRENT_USER   = Key(syscall.HKEY_CURRENT_USER)
61
+	LOCAL_MACHINE  = Key(syscall.HKEY_LOCAL_MACHINE)
62
+	USERS          = Key(syscall.HKEY_USERS)
63
+	CURRENT_CONFIG = Key(syscall.HKEY_CURRENT_CONFIG)
64
+)
65
+
66
+// Close closes open key k.
67
+func (k Key) Close() error {
68
+	return syscall.RegCloseKey(syscall.Handle(k))
69
+}
70
+
71
+// OpenKey opens a new key with path name relative to key k.
72
+// It accepts any open key, including CURRENT_USER and others,
73
+// and returns the new key and an error.
74
+// The access parameter specifies desired access rights to the
75
+// key to be opened.
76
+func OpenKey(k Key, path string, access uint32) (Key, error) {
77
+	p, err := syscall.UTF16PtrFromString(path)
78
+	if err != nil {
79
+		return 0, err
80
+	}
81
+	var subkey syscall.Handle
82
+	err = syscall.RegOpenKeyEx(syscall.Handle(k), p, 0, access, &subkey)
83
+	if err != nil {
84
+		return 0, err
85
+	}
86
+	return Key(subkey), nil
87
+}
88
+
89
+// ReadSubKeyNames returns the names of subkeys of key k.
90
+// The parameter n controls the number of returned names,
91
+// analogous to the way os.File.Readdirnames works.
92
+func (k Key) ReadSubKeyNames(n int) ([]string, error) {
93
+	ki, err := k.Stat()
94
+	if err != nil {
95
+		return nil, err
96
+	}
97
+	names := make([]string, 0, ki.SubKeyCount)
98
+	buf := make([]uint16, ki.MaxSubKeyLen+1) // extra room for terminating zero byte
99
+loopItems:
100
+	for i := uint32(0); ; i++ {
101
+		if n > 0 {
102
+			if len(names) == n {
103
+				return names, nil
104
+			}
105
+		}
106
+		l := uint32(len(buf))
107
+		for {
108
+			err := syscall.RegEnumKeyEx(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil)
109
+			if err == nil {
110
+				break
111
+			}
112
+			if err == syscall.ERROR_MORE_DATA {
113
+				// Double buffer size and try again.
114
+				l = uint32(2 * len(buf))
115
+				buf = make([]uint16, l)
116
+				continue
117
+			}
118
+			if err == _ERROR_NO_MORE_ITEMS {
119
+				break loopItems
120
+			}
121
+			return names, err
122
+		}
123
+		names = append(names, syscall.UTF16ToString(buf[:l]))
124
+	}
125
+	if n > len(names) {
126
+		return names, io.EOF
127
+	}
128
+	return names, nil
129
+}
130
+
131
+// CreateKey creates a key named path under open key k.
132
+// CreateKey returns the new key and a boolean flag that reports
133
+// whether the key already existed.
134
+// The access parameter specifies the access rights for the key
135
+// to be created.
136
+func CreateKey(k Key, path string, access uint32) (newk Key, openedExisting bool, err error) {
137
+	var h syscall.Handle
138
+	var d uint32
139
+	err = regCreateKeyEx(syscall.Handle(k), syscall.StringToUTF16Ptr(path),
140
+		0, nil, _REG_OPTION_NON_VOLATILE, access, nil, &h, &d)
141
+	if err != nil {
142
+		return 0, false, err
143
+	}
144
+	return Key(h), d == _REG_OPENED_EXISTING_KEY, nil
145
+}
146
+
147
+// DeleteKey deletes the subkey path of key k and its values.
148
+func DeleteKey(k Key, path string) error {
149
+	return regDeleteKey(syscall.Handle(k), syscall.StringToUTF16Ptr(path))
150
+}
151
+
152
+// A KeyInfo describes the statistics of a key. It is returned by Stat.
153
+type KeyInfo struct {
154
+	SubKeyCount     uint32
155
+	MaxSubKeyLen    uint32 // size of the key's subkey with the longest name, in Unicode characters, not including the terminating zero byte
156
+	ValueCount      uint32
157
+	MaxValueNameLen uint32 // size of the key's longest value name, in Unicode characters, not including the terminating zero byte
158
+	MaxValueLen     uint32 // longest data component among the key's values, in bytes
159
+	lastWriteTime   syscall.Filetime
160
+}
161
+
162
+// ModTime returns the key's last write time.
163
+func (ki *KeyInfo) ModTime() time.Time {
164
+	return time.Unix(0, ki.lastWriteTime.Nanoseconds())
165
+}
166
+
167
+// Stat retrieves information about the open key k.
168
+func (k Key) Stat() (*KeyInfo, error) {
169
+	var ki KeyInfo
170
+	err := syscall.RegQueryInfoKey(syscall.Handle(k), nil, nil, nil,
171
+		&ki.SubKeyCount, &ki.MaxSubKeyLen, nil, &ki.ValueCount,
172
+		&ki.MaxValueNameLen, &ki.MaxValueLen, nil, &ki.lastWriteTime)
173
+	if err != nil {
174
+		return nil, err
175
+	}
176
+	return &ki, nil
177
+}
0 178
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+// Copyright 2015 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build windows
5
+
6
+package registry
7
+
8
+import "syscall"
9
+
10
+//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall.go
11
+
12
+const (
13
+	_REG_OPTION_NON_VOLATILE = 0
14
+
15
+	_REG_CREATED_NEW_KEY     = 1
16
+	_REG_OPENED_EXISTING_KEY = 2
17
+
18
+	_ERROR_NO_MORE_ITEMS syscall.Errno = 259
19
+)
20
+
21
+func LoadRegLoadMUIString() error {
22
+	return procRegLoadMUIStringW.Find()
23
+}
24
+
25
+//sys	regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW
26
+//sys	regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW
27
+//sys	regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW
28
+//sys	regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW
29
+//sys	regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW
30
+//sys   regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) = advapi32.RegLoadMUIStringW
31
+
32
+//sys	expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW
0 33
new file mode 100644
... ...
@@ -0,0 +1,384 @@
0
+// Copyright 2015 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build windows
5
+
6
+package registry
7
+
8
+import (
9
+	"errors"
10
+	"io"
11
+	"syscall"
12
+	"unicode/utf16"
13
+	"unsafe"
14
+)
15
+
16
+const (
17
+	// Registry value types.
18
+	NONE                       = 0
19
+	SZ                         = 1
20
+	EXPAND_SZ                  = 2
21
+	BINARY                     = 3
22
+	DWORD                      = 4
23
+	DWORD_BIG_ENDIAN           = 5
24
+	LINK                       = 6
25
+	MULTI_SZ                   = 7
26
+	RESOURCE_LIST              = 8
27
+	FULL_RESOURCE_DESCRIPTOR   = 9
28
+	RESOURCE_REQUIREMENTS_LIST = 10
29
+	QWORD                      = 11
30
+)
31
+
32
+var (
33
+	// ErrShortBuffer is returned when the buffer was too short for the operation.
34
+	ErrShortBuffer = syscall.ERROR_MORE_DATA
35
+
36
+	// ErrNotExist is returned when a registry key or value does not exist.
37
+	ErrNotExist = syscall.ERROR_FILE_NOT_FOUND
38
+
39
+	// ErrUnexpectedType is returned by Get*Value when the value's type was unexpected.
40
+	ErrUnexpectedType = errors.New("unexpected key value type")
41
+)
42
+
43
+// GetValue retrieves the type and data for the specified value associated
44
+// with an open key k. It fills up buffer buf and returns the retrieved
45
+// byte count n. If buf is too small to fit the stored value it returns
46
+// ErrShortBuffer error along with the required buffer size n.
47
+// If no buffer is provided, it returns true and actual buffer size n.
48
+// If no buffer is provided, GetValue returns the value's type only.
49
+// If the value does not exist, the error returned is ErrNotExist.
50
+//
51
+// GetValue is a low level function. If value's type is known, use the appropriate
52
+// Get*Value function instead.
53
+func (k Key) GetValue(name string, buf []byte) (n int, valtype uint32, err error) {
54
+	pname, err := syscall.UTF16PtrFromString(name)
55
+	if err != nil {
56
+		return 0, 0, err
57
+	}
58
+	var pbuf *byte
59
+	if len(buf) > 0 {
60
+		pbuf = (*byte)(unsafe.Pointer(&buf[0]))
61
+	}
62
+	l := uint32(len(buf))
63
+	err = syscall.RegQueryValueEx(syscall.Handle(k), pname, nil, &valtype, pbuf, &l)
64
+	if err != nil {
65
+		return int(l), valtype, err
66
+	}
67
+	return int(l), valtype, nil
68
+}
69
+
70
+func (k Key) getValue(name string, buf []byte) (date []byte, valtype uint32, err error) {
71
+	p, err := syscall.UTF16PtrFromString(name)
72
+	if err != nil {
73
+		return nil, 0, err
74
+	}
75
+	var t uint32
76
+	n := uint32(len(buf))
77
+	for {
78
+		err = syscall.RegQueryValueEx(syscall.Handle(k), p, nil, &t, (*byte)(unsafe.Pointer(&buf[0])), &n)
79
+		if err == nil {
80
+			return buf[:n], t, nil
81
+		}
82
+		if err != syscall.ERROR_MORE_DATA {
83
+			return nil, 0, err
84
+		}
85
+		if n <= uint32(len(buf)) {
86
+			return nil, 0, err
87
+		}
88
+		buf = make([]byte, n)
89
+	}
90
+}
91
+
92
+// GetStringValue retrieves the string value for the specified
93
+// value name associated with an open key k. It also returns the value's type.
94
+// If value does not exist, GetStringValue returns ErrNotExist.
95
+// If value is not SZ or EXPAND_SZ, it will return the correct value
96
+// type and ErrUnexpectedType.
97
+func (k Key) GetStringValue(name string) (val string, valtype uint32, err error) {
98
+	data, typ, err2 := k.getValue(name, make([]byte, 64))
99
+	if err2 != nil {
100
+		return "", typ, err2
101
+	}
102
+	switch typ {
103
+	case SZ, EXPAND_SZ:
104
+	default:
105
+		return "", typ, ErrUnexpectedType
106
+	}
107
+	if len(data) == 0 {
108
+		return "", typ, nil
109
+	}
110
+	u := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:]
111
+	return syscall.UTF16ToString(u), typ, nil
112
+}
113
+
114
+// GetMUIStringValue retrieves the localized string value for
115
+// the specified value name associated with an open key k.
116
+// If the value name doesn't exist or the localized string value
117
+// can't be resolved, GetMUIStringValue returns ErrNotExist.
118
+// GetMUIStringValue panics if the system doesn't support
119
+// regLoadMUIString; use LoadRegLoadMUIString to check if
120
+// regLoadMUIString is supported before calling this function.
121
+func (k Key) GetMUIStringValue(name string) (string, error) {
122
+	pname, err := syscall.UTF16PtrFromString(name)
123
+	if err != nil {
124
+		return "", err
125
+	}
126
+
127
+	buf := make([]uint16, 1024)
128
+	var buflen uint32
129
+	var pdir *uint16
130
+
131
+	err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
132
+	if err == syscall.ERROR_FILE_NOT_FOUND { // Try fallback path
133
+
134
+		// Try to resolve the string value using the system directory as
135
+		// a DLL search path; this assumes the string value is of the form
136
+		// @[path]\dllname,-strID but with no path given, e.g. @tzres.dll,-320.
137
+
138
+		// This approach works with tzres.dll but may have to be revised
139
+		// in the future to allow callers to provide custom search paths.
140
+
141
+		var s string
142
+		s, err = ExpandString("%SystemRoot%\\system32\\")
143
+		if err != nil {
144
+			return "", err
145
+		}
146
+		pdir, err = syscall.UTF16PtrFromString(s)
147
+		if err != nil {
148
+			return "", err
149
+		}
150
+
151
+		err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
152
+	}
153
+
154
+	for err == syscall.ERROR_MORE_DATA { // Grow buffer if needed
155
+		if buflen <= uint32(len(buf)) {
156
+			break // Buffer not growing, assume race; break
157
+		}
158
+		buf = make([]uint16, buflen)
159
+		err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
160
+	}
161
+
162
+	if err != nil {
163
+		return "", err
164
+	}
165
+
166
+	return syscall.UTF16ToString(buf), nil
167
+}
168
+
169
+// ExpandString expands environment-variable strings and replaces
170
+// them with the values defined for the current user.
171
+// Use ExpandString to expand EXPAND_SZ strings.
172
+func ExpandString(value string) (string, error) {
173
+	if value == "" {
174
+		return "", nil
175
+	}
176
+	p, err := syscall.UTF16PtrFromString(value)
177
+	if err != nil {
178
+		return "", err
179
+	}
180
+	r := make([]uint16, 100)
181
+	for {
182
+		n, err := expandEnvironmentStrings(p, &r[0], uint32(len(r)))
183
+		if err != nil {
184
+			return "", err
185
+		}
186
+		if n <= uint32(len(r)) {
187
+			u := (*[1 << 29]uint16)(unsafe.Pointer(&r[0]))[:]
188
+			return syscall.UTF16ToString(u), nil
189
+		}
190
+		r = make([]uint16, n)
191
+	}
192
+}
193
+
194
+// GetStringsValue retrieves the []string value for the specified
195
+// value name associated with an open key k. It also returns the value's type.
196
+// If value does not exist, GetStringsValue returns ErrNotExist.
197
+// If value is not MULTI_SZ, it will return the correct value
198
+// type and ErrUnexpectedType.
199
+func (k Key) GetStringsValue(name string) (val []string, valtype uint32, err error) {
200
+	data, typ, err2 := k.getValue(name, make([]byte, 64))
201
+	if err2 != nil {
202
+		return nil, typ, err2
203
+	}
204
+	if typ != MULTI_SZ {
205
+		return nil, typ, ErrUnexpectedType
206
+	}
207
+	if len(data) == 0 {
208
+		return nil, typ, nil
209
+	}
210
+	p := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:len(data)/2]
211
+	if len(p) == 0 {
212
+		return nil, typ, nil
213
+	}
214
+	if p[len(p)-1] == 0 {
215
+		p = p[:len(p)-1] // remove terminating null
216
+	}
217
+	val = make([]string, 0, 5)
218
+	from := 0
219
+	for i, c := range p {
220
+		if c == 0 {
221
+			val = append(val, string(utf16.Decode(p[from:i])))
222
+			from = i + 1
223
+		}
224
+	}
225
+	return val, typ, nil
226
+}
227
+
228
+// GetIntegerValue retrieves the integer value for the specified
229
+// value name associated with an open key k. It also returns the value's type.
230
+// If value does not exist, GetIntegerValue returns ErrNotExist.
231
+// If value is not DWORD or QWORD, it will return the correct value
232
+// type and ErrUnexpectedType.
233
+func (k Key) GetIntegerValue(name string) (val uint64, valtype uint32, err error) {
234
+	data, typ, err2 := k.getValue(name, make([]byte, 8))
235
+	if err2 != nil {
236
+		return 0, typ, err2
237
+	}
238
+	switch typ {
239
+	case DWORD:
240
+		if len(data) != 4 {
241
+			return 0, typ, errors.New("DWORD value is not 4 bytes long")
242
+		}
243
+		return uint64(*(*uint32)(unsafe.Pointer(&data[0]))), DWORD, nil
244
+	case QWORD:
245
+		if len(data) != 8 {
246
+			return 0, typ, errors.New("QWORD value is not 8 bytes long")
247
+		}
248
+		return uint64(*(*uint64)(unsafe.Pointer(&data[0]))), QWORD, nil
249
+	default:
250
+		return 0, typ, ErrUnexpectedType
251
+	}
252
+}
253
+
254
+// GetBinaryValue retrieves the binary value for the specified
255
+// value name associated with an open key k. It also returns the value's type.
256
+// If value does not exist, GetBinaryValue returns ErrNotExist.
257
+// If value is not BINARY, it will return the correct value
258
+// type and ErrUnexpectedType.
259
+func (k Key) GetBinaryValue(name string) (val []byte, valtype uint32, err error) {
260
+	data, typ, err2 := k.getValue(name, make([]byte, 64))
261
+	if err2 != nil {
262
+		return nil, typ, err2
263
+	}
264
+	if typ != BINARY {
265
+		return nil, typ, ErrUnexpectedType
266
+	}
267
+	return data, typ, nil
268
+}
269
+
270
+func (k Key) setValue(name string, valtype uint32, data []byte) error {
271
+	p, err := syscall.UTF16PtrFromString(name)
272
+	if err != nil {
273
+		return err
274
+	}
275
+	if len(data) == 0 {
276
+		return regSetValueEx(syscall.Handle(k), p, 0, valtype, nil, 0)
277
+	}
278
+	return regSetValueEx(syscall.Handle(k), p, 0, valtype, &data[0], uint32(len(data)))
279
+}
280
+
281
+// SetDWordValue sets the data and type of a name value
282
+// under key k to value and DWORD.
283
+func (k Key) SetDWordValue(name string, value uint32) error {
284
+	return k.setValue(name, DWORD, (*[4]byte)(unsafe.Pointer(&value))[:])
285
+}
286
+
287
+// SetQWordValue sets the data and type of a name value
288
+// under key k to value and QWORD.
289
+func (k Key) SetQWordValue(name string, value uint64) error {
290
+	return k.setValue(name, QWORD, (*[8]byte)(unsafe.Pointer(&value))[:])
291
+}
292
+
293
+func (k Key) setStringValue(name string, valtype uint32, value string) error {
294
+	v, err := syscall.UTF16FromString(value)
295
+	if err != nil {
296
+		return err
297
+	}
298
+	buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2]
299
+	return k.setValue(name, valtype, buf)
300
+}
301
+
302
+// SetStringValue sets the data and type of a name value
303
+// under key k to value and SZ. The value must not contain a zero byte.
304
+func (k Key) SetStringValue(name, value string) error {
305
+	return k.setStringValue(name, SZ, value)
306
+}
307
+
308
+// SetExpandStringValue sets the data and type of a name value
309
+// under key k to value and EXPAND_SZ. The value must not contain a zero byte.
310
+func (k Key) SetExpandStringValue(name, value string) error {
311
+	return k.setStringValue(name, EXPAND_SZ, value)
312
+}
313
+
314
+// SetStringsValue sets the data and type of a name value
315
+// under key k to value and MULTI_SZ. The value strings
316
+// must not contain a zero byte.
317
+func (k Key) SetStringsValue(name string, value []string) error {
318
+	ss := ""
319
+	for _, s := range value {
320
+		for i := 0; i < len(s); i++ {
321
+			if s[i] == 0 {
322
+				return errors.New("string cannot have 0 inside")
323
+			}
324
+		}
325
+		ss += s + "\x00"
326
+	}
327
+	v := utf16.Encode([]rune(ss + "\x00"))
328
+	buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2]
329
+	return k.setValue(name, MULTI_SZ, buf)
330
+}
331
+
332
+// SetBinaryValue sets the data and type of a name value
333
+// under key k to value and BINARY.
334
+func (k Key) SetBinaryValue(name string, value []byte) error {
335
+	return k.setValue(name, BINARY, value)
336
+}
337
+
338
+// DeleteValue removes a named value from the key k.
339
+func (k Key) DeleteValue(name string) error {
340
+	return regDeleteValue(syscall.Handle(k), syscall.StringToUTF16Ptr(name))
341
+}
342
+
343
+// ReadValueNames returns the value names of key k.
344
+// The parameter n controls the number of returned names,
345
+// analogous to the way os.File.Readdirnames works.
346
+func (k Key) ReadValueNames(n int) ([]string, error) {
347
+	ki, err := k.Stat()
348
+	if err != nil {
349
+		return nil, err
350
+	}
351
+	names := make([]string, 0, ki.ValueCount)
352
+	buf := make([]uint16, ki.MaxValueNameLen+1) // extra room for terminating null character
353
+loopItems:
354
+	for i := uint32(0); ; i++ {
355
+		if n > 0 {
356
+			if len(names) == n {
357
+				return names, nil
358
+			}
359
+		}
360
+		l := uint32(len(buf))
361
+		for {
362
+			err := regEnumValue(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil)
363
+			if err == nil {
364
+				break
365
+			}
366
+			if err == syscall.ERROR_MORE_DATA {
367
+				// Double buffer size and try again.
368
+				l = uint32(2 * len(buf))
369
+				buf = make([]uint16, l)
370
+				continue
371
+			}
372
+			if err == _ERROR_NO_MORE_ITEMS {
373
+				break loopItems
374
+			}
375
+			return names, err
376
+		}
377
+		names = append(names, syscall.UTF16ToString(buf[:l]))
378
+	}
379
+	if n > len(names) {
380
+		return names, io.EOF
381
+	}
382
+	return names, nil
383
+}
0 384
new file mode 100644
... ...
@@ -0,0 +1,82 @@
0
+// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
1
+
2
+package registry
3
+
4
+import "unsafe"
5
+import "syscall"
6
+
7
+var _ unsafe.Pointer
8
+
9
+var (
10
+	modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
11
+	modkernel32 = syscall.NewLazyDLL("kernel32.dll")
12
+
13
+	procRegCreateKeyExW           = modadvapi32.NewProc("RegCreateKeyExW")
14
+	procRegDeleteKeyW             = modadvapi32.NewProc("RegDeleteKeyW")
15
+	procRegSetValueExW            = modadvapi32.NewProc("RegSetValueExW")
16
+	procRegEnumValueW             = modadvapi32.NewProc("RegEnumValueW")
17
+	procRegDeleteValueW           = modadvapi32.NewProc("RegDeleteValueW")
18
+	procRegLoadMUIStringW         = modadvapi32.NewProc("RegLoadMUIStringW")
19
+	procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
20
+)
21
+
22
+func regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) {
23
+	r0, _, _ := syscall.Syscall9(procRegCreateKeyExW.Addr(), 9, uintptr(key), uintptr(unsafe.Pointer(subkey)), uintptr(reserved), uintptr(unsafe.Pointer(class)), uintptr(options), uintptr(desired), uintptr(unsafe.Pointer(sa)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(disposition)))
24
+	if r0 != 0 {
25
+		regerrno = syscall.Errno(r0)
26
+	}
27
+	return
28
+}
29
+
30
+func regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) {
31
+	r0, _, _ := syscall.Syscall(procRegDeleteKeyW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(subkey)), 0)
32
+	if r0 != 0 {
33
+		regerrno = syscall.Errno(r0)
34
+	}
35
+	return
36
+}
37
+
38
+func regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) {
39
+	r0, _, _ := syscall.Syscall6(procRegSetValueExW.Addr(), 6, uintptr(key), uintptr(unsafe.Pointer(valueName)), uintptr(reserved), uintptr(vtype), uintptr(unsafe.Pointer(buf)), uintptr(bufsize))
40
+	if r0 != 0 {
41
+		regerrno = syscall.Errno(r0)
42
+	}
43
+	return
44
+}
45
+
46
+func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) {
47
+	r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valtype)), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(buflen)), 0)
48
+	if r0 != 0 {
49
+		regerrno = syscall.Errno(r0)
50
+	}
51
+	return
52
+}
53
+
54
+func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) {
55
+	r0, _, _ := syscall.Syscall(procRegDeleteValueW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(name)), 0)
56
+	if r0 != 0 {
57
+		regerrno = syscall.Errno(r0)
58
+	}
59
+	return
60
+}
61
+
62
+func regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) {
63
+	r0, _, _ := syscall.Syscall9(procRegLoadMUIStringW.Addr(), 7, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(unsafe.Pointer(buflenCopied)), uintptr(flags), uintptr(unsafe.Pointer(dir)), 0, 0)
64
+	if r0 != 0 {
65
+		regerrno = syscall.Errno(r0)
66
+	}
67
+	return
68
+}
69
+
70
+func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) {
71
+	r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size))
72
+	n = uint32(r0)
73
+	if n == 0 {
74
+		if e1 != 0 {
75
+			err = error(e1)
76
+		} else {
77
+			err = syscall.EINVAL
78
+		}
79
+	}
80
+	return
81
+}