Skip to content

Commit 1c95d97

Browse files
committed
os: use FILE_FLAG_OPEN_REPARSE_POINT in SameFile
SameFile opens file to discover identifier and volume serial number that uniquely identify the file. SameFile uses Windows CreateFile API to open the file, and that works well for files and directories. But CreateFile always follows symlinks, so SameFile always opens symlink target instead of symlink itself. This CL uses FILE_FLAG_OPEN_REPARSE_POINT flag to adjust CreateFile behavior when handling symlinks. As per https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted "... If FILE_FLAG_OPEN_REPARSE_POINT is specified and: If an existing file is opened and it is a symbolic link, the handle returned is a handle to the symbolic link. ...". I also added new tests for both issue #21854 and #27225. Issue #27225 is still to be fixed, so skipping the test on windows for the moment. Fixes #21854 Updates #27225 Change-Id: I8aaa13ad66ce3b4074991bb50994d2aeeeaa7c95 Reviewed-on: https://go-review.googlesource.com/134195 Run-TryBot: Alex Brainman <alex.brainman@gmail.com> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
1 parent 7dda512 commit 1c95d97

File tree

3 files changed

+284
-62
lines changed

3 files changed

+284
-62
lines changed

src/os/os_windows_test.go

+1-61
Original file line numberDiff line numberDiff line change
@@ -895,16 +895,6 @@ func main() {
895895
}
896896
}
897897

898-
func testIsDir(t *testing.T, path string, fi os.FileInfo) {
899-
t.Helper()
900-
if !fi.IsDir() {
901-
t.Errorf("%q should be a directory", path)
902-
}
903-
if fi.Mode()&os.ModeSymlink != 0 {
904-
t.Errorf("%q should not be a symlink", path)
905-
}
906-
}
907-
908898
func findOneDriveDir() (string, error) {
909899
// as per https://stackoverflow.com/questions/42519624/how-to-determine-location-of-onedrive-on-windows-7-and-8-in-c
910900
const onedrivekey = `SOFTWARE\Microsoft\OneDrive`
@@ -927,57 +917,7 @@ func TestOneDrive(t *testing.T) {
927917
if err != nil {
928918
t.Skipf("Skipping, because we did not find OneDrive directory: %v", err)
929919
}
930-
931-
// test os.Stat
932-
fi, err := os.Stat(dir)
933-
if err != nil {
934-
t.Fatal(err)
935-
}
936-
testIsDir(t, dir, fi)
937-
938-
// test os.Lstat
939-
fi, err = os.Lstat(dir)
940-
if err != nil {
941-
t.Fatal(err)
942-
}
943-
testIsDir(t, dir, fi)
944-
945-
// test os.File.Stat
946-
f, err := os.Open(dir)
947-
if err != nil {
948-
t.Fatal(err)
949-
}
950-
defer f.Close()
951-
952-
fi, err = f.Stat()
953-
if err != nil {
954-
t.Fatal(err)
955-
}
956-
testIsDir(t, dir, fi)
957-
958-
// test os.FileInfo returned by os.Readdir
959-
parent, err := os.Open(filepath.Dir(dir))
960-
if err != nil {
961-
t.Fatal(err)
962-
}
963-
defer parent.Close()
964-
965-
fis, err := parent.Readdir(-1)
966-
if err != nil {
967-
t.Fatal(err)
968-
}
969-
fi = nil
970-
base := filepath.Base(dir)
971-
for _, fi2 := range fis {
972-
if fi2.Name() == base {
973-
fi = fi2
974-
break
975-
}
976-
}
977-
if fi == nil {
978-
t.Errorf("failed to find %q in its parent", dir)
979-
}
980-
testIsDir(t, dir, fi)
920+
testDirStats(t, dir)
981921
}
982922

983923
func TestWindowsDevNullFile(t *testing.T) {

src/os/stat_test.go

+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// Copyright 2018 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_test
6+
7+
import (
8+
"internal/testenv"
9+
"io/ioutil"
10+
"os"
11+
"path/filepath"
12+
"runtime"
13+
"testing"
14+
)
15+
16+
// testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work.
17+
func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCheck func(*testing.T, string, os.FileInfo)) {
18+
// test os.Stat
19+
sfi, err := os.Stat(path)
20+
if err != nil {
21+
t.Error(err)
22+
return
23+
}
24+
statCheck(t, path, sfi)
25+
26+
// test os.Lstat
27+
lsfi, err := os.Lstat(path)
28+
if err != nil {
29+
t.Error(err)
30+
return
31+
}
32+
lstatCheck(t, path, lsfi)
33+
34+
if isLink {
35+
if os.SameFile(sfi, lsfi) {
36+
t.Errorf("stat and lstat of %q should not be the same", path)
37+
}
38+
} else {
39+
if !os.SameFile(sfi, lsfi) {
40+
t.Errorf("stat and lstat of %q should be the same", path)
41+
}
42+
}
43+
44+
// test os.File.Stat
45+
f, err := os.Open(path)
46+
if err != nil {
47+
t.Error(err)
48+
return
49+
}
50+
defer f.Close()
51+
52+
sfi2, err := f.Stat()
53+
if err != nil {
54+
t.Error(err)
55+
return
56+
}
57+
statCheck(t, path, sfi2)
58+
59+
if !os.SameFile(sfi, sfi2) {
60+
t.Errorf("stat of open %q file and stat of %q should be the same", path, path)
61+
}
62+
63+
if isLink {
64+
if os.SameFile(sfi2, lsfi) {
65+
t.Errorf("stat of opened %q file and lstat of %q should not be the same", path, path)
66+
}
67+
} else {
68+
if !os.SameFile(sfi2, lsfi) {
69+
t.Errorf("stat of opened %q file and lstat of %q should be the same", path, path)
70+
}
71+
}
72+
73+
// test os.FileInfo returned by os.Readdir
74+
if len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
75+
// skip os.Readdir test of directories with slash at the end
76+
return
77+
}
78+
parentdir := filepath.Dir(path)
79+
parent, err := os.Open(parentdir)
80+
if err != nil {
81+
t.Error(err)
82+
return
83+
}
84+
defer parent.Close()
85+
86+
fis, err := parent.Readdir(-1)
87+
if err != nil {
88+
t.Error(err)
89+
return
90+
}
91+
var lsfi2 os.FileInfo
92+
base := filepath.Base(path)
93+
for _, fi2 := range fis {
94+
if fi2.Name() == base {
95+
lsfi2 = fi2
96+
break
97+
}
98+
}
99+
if lsfi2 == nil {
100+
t.Errorf("failed to find %q in its parent", path)
101+
return
102+
}
103+
lstatCheck(t, path, lsfi2)
104+
105+
if !os.SameFile(lsfi, lsfi2) {
106+
t.Errorf("lstat of %q file in %q directory and %q should be the same", lsfi2.Name(), parentdir, path)
107+
}
108+
}
109+
110+
// testIsDir verifies that fi refers to directory.
111+
func testIsDir(t *testing.T, path string, fi os.FileInfo) {
112+
t.Helper()
113+
if !fi.IsDir() {
114+
t.Errorf("%q should be a directory", path)
115+
}
116+
if fi.Mode()&os.ModeSymlink != 0 {
117+
t.Errorf("%q should not be a symlink", path)
118+
}
119+
}
120+
121+
// testIsSymlink verifies that fi refers to symlink.
122+
func testIsSymlink(t *testing.T, path string, fi os.FileInfo) {
123+
t.Helper()
124+
if fi.IsDir() {
125+
t.Errorf("%q should not be a directory", path)
126+
}
127+
if fi.Mode()&os.ModeSymlink == 0 {
128+
t.Errorf("%q should be a symlink", path)
129+
}
130+
}
131+
132+
// testIsFile verifies that fi refers to file.
133+
func testIsFile(t *testing.T, path string, fi os.FileInfo) {
134+
t.Helper()
135+
if fi.IsDir() {
136+
t.Errorf("%q should not be a directory", path)
137+
}
138+
if fi.Mode()&os.ModeSymlink != 0 {
139+
t.Errorf("%q should not be a symlink", path)
140+
}
141+
}
142+
143+
func testDirStats(t *testing.T, path string) {
144+
testStatAndLstat(t, path, false, testIsDir, testIsDir)
145+
}
146+
147+
func testFileStats(t *testing.T, path string) {
148+
testStatAndLstat(t, path, false, testIsFile, testIsFile)
149+
}
150+
151+
func testSymlinkStats(t *testing.T, path string, isdir bool) {
152+
if isdir {
153+
testStatAndLstat(t, path, true, testIsDir, testIsSymlink)
154+
} else {
155+
testStatAndLstat(t, path, true, testIsFile, testIsSymlink)
156+
}
157+
}
158+
159+
func testSymlinkSameFile(t *testing.T, path, link string) {
160+
pathfi, err := os.Stat(path)
161+
if err != nil {
162+
t.Error(err)
163+
return
164+
}
165+
166+
linkfi, err := os.Stat(link)
167+
if err != nil {
168+
t.Error(err)
169+
return
170+
}
171+
if !os.SameFile(pathfi, linkfi) {
172+
t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", path, link)
173+
}
174+
175+
linkfi, err = os.Lstat(link)
176+
if err != nil {
177+
t.Error(err)
178+
return
179+
}
180+
if os.SameFile(pathfi, linkfi) {
181+
t.Errorf("os.Stat(%q) and os.Lstat(%q) are the same file", path, link)
182+
}
183+
}
184+
185+
func TestDirAndSymlinkStats(t *testing.T) {
186+
testenv.MustHaveSymlink(t)
187+
188+
tmpdir, err := ioutil.TempDir("", "TestDirAndSymlinkStats")
189+
if err != nil {
190+
t.Fatal(err)
191+
}
192+
defer os.RemoveAll(tmpdir)
193+
194+
dir := filepath.Join(tmpdir, "dir")
195+
err = os.Mkdir(dir, 0777)
196+
if err != nil {
197+
t.Fatal(err)
198+
}
199+
testDirStats(t, dir)
200+
201+
dirlink := filepath.Join(tmpdir, "link")
202+
err = os.Symlink(dir, dirlink)
203+
if err != nil {
204+
t.Fatal(err)
205+
}
206+
testSymlinkStats(t, dirlink, true)
207+
testSymlinkSameFile(t, dir, dirlink)
208+
}
209+
210+
func TestFileAndSymlinkStats(t *testing.T) {
211+
testenv.MustHaveSymlink(t)
212+
213+
tmpdir, err := ioutil.TempDir("", "TestFileAndSymlinkStats")
214+
if err != nil {
215+
t.Fatal(err)
216+
}
217+
defer os.RemoveAll(tmpdir)
218+
219+
file := filepath.Join(tmpdir, "file")
220+
err = ioutil.WriteFile(file, []byte(""), 0644)
221+
if err != nil {
222+
t.Fatal(err)
223+
}
224+
testFileStats(t, file)
225+
226+
filelink := filepath.Join(tmpdir, "link")
227+
err = os.Symlink(file, filelink)
228+
if err != nil {
229+
t.Fatal(err)
230+
}
231+
testSymlinkStats(t, filelink, false)
232+
testSymlinkSameFile(t, file, filelink)
233+
}
234+
235+
// see issue 27225 for details
236+
func TestSymlinkWithTrailingSlash(t *testing.T) {
237+
if runtime.GOOS == "windows" {
238+
t.Skip("skipping on windows; issue 27225")
239+
}
240+
241+
testenv.MustHaveSymlink(t)
242+
243+
tmpdir, err := ioutil.TempDir("", "TestSymlinkWithTrailingSlash")
244+
if err != nil {
245+
t.Fatal(err)
246+
}
247+
defer os.RemoveAll(tmpdir)
248+
249+
dir := filepath.Join(tmpdir, "dir")
250+
err = os.Mkdir(dir, 0777)
251+
if err != nil {
252+
t.Fatal(err)
253+
}
254+
dirlink := filepath.Join(tmpdir, "link")
255+
err = os.Symlink(dir, dirlink)
256+
if err != nil {
257+
t.Fatal(err)
258+
}
259+
dirlinkWithSlash := dirlink + string(os.PathSeparator)
260+
261+
testDirStats(t, dirlinkWithSlash)
262+
263+
fi1, err := os.Stat(dir)
264+
if err != nil {
265+
t.Error(err)
266+
return
267+
}
268+
fi2, err := os.Stat(dirlinkWithSlash)
269+
if err != nil {
270+
t.Error(err)
271+
return
272+
}
273+
if !os.SameFile(fi1, fi2) {
274+
t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", dir, dirlinkWithSlash)
275+
}
276+
}

src/os/types_windows.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,13 @@ func (fs *fileStat) loadFileId() error {
211211
if err != nil {
212212
return err
213213
}
214-
h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
214+
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
215+
if fs.isSymlink() {
216+
// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
217+
// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
218+
attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
219+
}
220+
h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
215221
if err != nil {
216222
return err
217223
}

0 commit comments

Comments
 (0)