Skip to content

Commit 5e1abfb

Browse files
committed
os: improved code based on review feedback
- moved syscall changes to internal/syscall/windows - moved unit test to os package - unit test is now using direct windows regisrtry access
1 parent 5b1ae4a commit 5e1abfb

File tree

4 files changed

+112
-108
lines changed

4 files changed

+112
-108
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package windows
6+
7+
import "syscall"
8+
9+
const (
10+
ERROR_INVALID_PARAMETER syscall.Errno = 87
11+
12+
// symlink support for CreateSymbolicLink() starting with Windows 10 (1607, v10.0.14393)
13+
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2
14+
)

src/os/file_windows.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -371,22 +371,20 @@ func Symlink(oldname, newname string) error {
371371
return &LinkError{"symlink", oldname, newname, err}
372372
}
373373

374-
var flags uint32 = syscall.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
374+
var flags uint32 = windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
375375
if isdir {
376376
flags |= syscall.SYMBOLIC_LINK_FLAG_DIRECTORY
377377
}
378378
err = syscall.CreateSymbolicLink(n, o, flags)
379379

380-
// creating symlinks unelevated is unsupported
381-
// below Windows 10 (1607, v10.0.14393).
382-
// retry without the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
383-
if err == syscall.ERROR_INVALID_PARAMETER {
384-
flags &^= syscall.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
380+
if err == windows.ERROR_INVALID_PARAMETER {
381+
// the unprivileged create flag is unsupported
382+
// below Windows 10 (1607, v10.0.14393). retry without it.
383+
flags &^= windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
385384

386385
err = syscall.CreateSymbolicLink(n, o, flags)
387386
}
388387

389-
// handle error
390388
if err != nil {
391389
return &LinkError{"symlink", oldname, newname, err}
392390
}
Lines changed: 87 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,87 @@
1-
package main
2-
3-
import (
4-
"fmt"
5-
"io/ioutil"
6-
"os"
7-
"os/exec"
8-
"path/filepath"
9-
"runtime"
10-
"strings"
11-
"syscall"
12-
)
13-
14-
// https://github.com/golang/go/issues/22874
15-
// os: Symlink creation should work on Windows without elevation
16-
17-
func main() {
18-
// run on windows, only
19-
if runtime.GOOS != "windows" {
20-
fmt.Println("Skipping test on non-Windows system")
21-
return
22-
}
23-
24-
// run test
25-
err := test()
26-
27-
if err != nil {
28-
panic(err)
29-
}
30-
}
31-
32-
func test() error {
33-
// is developer mode active?
34-
// the expected result depends on it
35-
devMode, _ := isDeveloperModeActive()
36-
37-
fmt.Printf("Windows developer mode active: %v\n", devMode)
38-
39-
// create dummy file to symlink
40-
dummyFile := filepath.Join(os.TempDir(), "issue22874.test")
41-
42-
err := ioutil.WriteFile(dummyFile, []byte(""), 0644)
43-
44-
if err != nil {
45-
return fmt.Errorf("Failed to create dummy file: %v", err)
46-
}
47-
48-
defer os.Remove(dummyFile)
49-
50-
// create the symlink
51-
linkFile := fmt.Sprintf("%v.link", dummyFile)
52-
53-
err = os.Symlink(dummyFile, linkFile)
54-
55-
if err != nil {
56-
// only the ERROR_PRIVILEGE_NOT_HELD error is allowed
57-
if x, ok := err.(*os.LinkError); ok {
58-
if xx, ok := x.Err.(syscall.Errno); ok {
59-
60-
if xx == syscall.ERROR_PRIVILEGE_NOT_HELD {
61-
// is developer mode active?
62-
if devMode {
63-
return fmt.Errorf("Windows developer mode is active, but creating symlink failed with ERROR_PRIVILEGE_NOT_HELD anyway: %v", err)
64-
}
65-
66-
// developer mode is disabled, and the error is expected
67-
fmt.Printf("Success: Creating symlink failed with expected ERROR_PRIVILEGE_NOT_HELD error\n")
68-
69-
return nil
70-
}
71-
}
72-
}
73-
74-
return fmt.Errorf("Failed to create symlink: %v", err)
75-
}
76-
77-
// remove the link. don't care for any errors
78-
os.Remove(linkFile)
79-
80-
fmt.Printf("Success: Creating symlink succeeded\n")
81-
82-
return nil
83-
}
84-
85-
func isDeveloperModeActive() (bool, error) {
86-
result, err := exec.Command("reg.exe", "query", "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", "/v", "AllowDevelopmentWithoutDevLicense").Output()
87-
88-
if err != nil {
89-
return false, err
90-
}
91-
92-
return strings.Contains(string(result), "0x1"), nil
93-
}
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package os
6+
7+
import (
8+
"fmt"
9+
"internal/syscall/windows/registry"
10+
"io/ioutil"
11+
"os"
12+
"path/filepath"
13+
"syscall"
14+
"testing"
15+
)
16+
17+
// see https://github.com/golang/go/issues/22874
18+
// os: Symlink creation should work on Windows without elevation
19+
20+
func TestSymlink(t *testing.T) {
21+
// is developer mode active?
22+
// the expected result depends on it
23+
devMode, _ := isDeveloperModeActive()
24+
25+
t.Logf("Windows developer mode active: %v\n", devMode)
26+
27+
// create dummy file to symlink
28+
dummyFile := filepath.Join(os.TempDir(), "issue22874.test")
29+
30+
err := ioutil.WriteFile(dummyFile, []byte(""), 0644)
31+
32+
if err != nil {
33+
t.Fatalf("Failed to create dummy file: %v", err)
34+
}
35+
36+
defer os.Remove(dummyFile)
37+
38+
// create the symlink
39+
linkFile := fmt.Sprintf("%v.link", dummyFile)
40+
41+
err = os.Symlink(dummyFile, linkFile)
42+
43+
if err != nil {
44+
// only the ERROR_PRIVILEGE_NOT_HELD error is allowed
45+
if x, ok := err.(*os.LinkError); ok {
46+
if xx, ok := x.Err.(syscall.Errno); ok {
47+
48+
if xx == syscall.ERROR_PRIVILEGE_NOT_HELD {
49+
// is developer mode active?
50+
if devMode {
51+
t.Fatalf("Windows developer mode is active, but creating symlink failed with ERROR_PRIVILEGE_NOT_HELD anyway: %v", err)
52+
}
53+
54+
// developer mode is disabled, and the error is expected
55+
fmt.Printf("Success: Creating symlink failed with expected ERROR_PRIVILEGE_NOT_HELD error\n")
56+
57+
return nil
58+
}
59+
}
60+
}
61+
62+
t.Fatalf("Failed to create symlink: %v", err)
63+
}
64+
65+
// remove the link. don't care for any errors
66+
os.Remove(linkFile)
67+
68+
t.Logf("Success: Creating symlink succeeded\n")
69+
70+
return nil
71+
}
72+
73+
func isDeveloperModeActive() (bool, error) {
74+
key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", registry.READ)
75+
76+
if err != nil {
77+
return false, err
78+
}
79+
80+
val, _, err := key.GetIntegerValue("AllowDevelopmentWithoutDevLicense")
81+
82+
if err != nil {
83+
return false, err
84+
}
85+
86+
return val != 0, nil
87+
}

src/syscall/types_windows.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const (
1313
ERROR_HANDLE_EOF Errno = 38
1414
ERROR_NETNAME_DELETED Errno = 64
1515
ERROR_FILE_EXISTS Errno = 80
16-
ERROR_INVALID_PARAMETER Errno = 87
1716
ERROR_BROKEN_PIPE Errno = 109
1817
ERROR_BUFFER_OVERFLOW Errno = 111
1918
ERROR_INSUFFICIENT_BUFFER Errno = 122
@@ -1114,11 +1113,10 @@ type reparseDataBuffer struct {
11141113
}
11151114

11161115
const (
1117-
FSCTL_GET_REPARSE_POINT = 0x900A8
1118-
MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024
1119-
_IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
1120-
IO_REPARSE_TAG_SYMLINK = 0xA000000C
1121-
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
1122-
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2
1123-
_SYMLINK_FLAG_RELATIVE = 1
1116+
FSCTL_GET_REPARSE_POINT = 0x900A8
1117+
MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024
1118+
_IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
1119+
IO_REPARSE_TAG_SYMLINK = 0xA000000C
1120+
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
1121+
_SYMLINK_FLAG_RELATIVE = 1
11241122
)

0 commit comments

Comments
 (0)