1 // Copyright 2012 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.
9 "internal/syscall/windows"
15 // normVolumeName is like VolumeName, but makes drive letter upper case.
16 // result of EvalSymlinks must be unique, so we have
17 // EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
18 func normVolumeName(path
string) string {
19 volume
:= VolumeName(path
)
21 if len(volume
) > 2 { // isUNC
25 return strings
.ToUpper(volume
)
28 // normBase returns the last element of path with correct case.
29 func normBase(path
string) (string, error
) {
30 p
, err
:= syscall
.UTF16PtrFromString(path
)
35 var data syscall
.Win32finddata
37 h
, err
:= syscall
.FindFirstFile(p
, &data
)
43 return syscall
.UTF16ToString(data
.FileName
[:]), nil
46 // baseIsDotDot returns whether the last element of path is "..".
47 // The given path should be 'Clean'-ed in advance.
48 func baseIsDotDot(path
string) bool {
49 i
:= strings
.LastIndexByte(path
, Separator
)
50 return path
[i
+1:] == ".."
53 // toNorm returns the normalized path that is guaranteed to be unique.
54 // It should accept the following formats:
55 // * UNC paths (e.g \\server\share\foo\bar)
56 // * absolute paths (e.g C:\foo\bar)
57 // * relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.)
58 // * relative paths begin with '\' (e.g \foo\bar)
59 // * relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .)
60 // The returned normalized path will be in the same form (of 5 listed above) as the input path.
61 // If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B).
62 // The normBase parameter should be equal to the normBase func, except for in tests. See docs on the normBase func.
63 func toNorm(path
string, normBase
func(string) (string, error
)) (string, error
) {
70 volume
:= normVolumeName(path
)
71 path
= path
[len(volume
):]
74 if path
== "." || path
== `\` {
75 return volume
+ path
, nil
81 if baseIsDotDot(path
) {
82 normPath
= path
+ `\` + normPath
87 name
, err
:= normBase(volume
+ path
)
92 normPath
= name
+ `\` + normPath
94 i
:= strings
.LastIndexByte(path
, Separator
)
98 if i
== 0 { // `\Go` or `C:\Go`
99 normPath
= `\` + normPath
107 normPath
= normPath
[:len(normPath
)-1] // remove trailing '\'
109 return volume
+ normPath
, nil
112 // evalSymlinksUsingGetFinalPathNameByHandle uses Windows
113 // GetFinalPathNameByHandle API to retrieve the final
114 // path for the specified file.
115 func evalSymlinksUsingGetFinalPathNameByHandle(path
string) (string, error
) {
116 err
:= windows
.LoadGetFinalPathNameByHandle()
118 // we must be using old version of Windows
126 // Use Windows I/O manager to dereference the symbolic link, as per
127 // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
128 p
, err
:= syscall
.UTF16PtrFromString(path
)
132 h
, err
:= syscall
.CreateFile(p
, 0, 0, nil,
133 syscall
.OPEN_EXISTING
, syscall
.FILE_FLAG_BACKUP_SEMANTICS
, 0)
137 defer syscall
.CloseHandle(h
)
139 buf
:= make([]uint16, 100)
141 n
, err
:= windows
.GetFinalPathNameByHandle(h
, &buf
[0], uint32(len(buf
)), windows
.VOLUME_NAME_DOS
)
145 if n
< uint32(len(buf
)) {
148 buf
= make([]uint16, n
)
150 s
:= syscall
.UTF16ToString(buf
)
151 if len(s
) > 4 && s
[:4] == `\\?\` {
153 if len(s
) > 3 && s
[:3] == `UNC` {
154 // return path like \\server\share\...
155 return `\` + s
[3:], nil
159 return "", errors
.New("GetFinalPathNameByHandle returned unexpected path=" + s
)
162 func samefile(path1
, path2
string) bool {
163 fi1
, err
:= os
.Lstat(path1
)
167 fi2
, err
:= os
.Lstat(path2
)
171 return os
.SameFile(fi1
, fi2
)
174 func evalSymlinks(path
string) (string, error
) {
175 newpath
, err
:= walkSymlinks(path
)
177 newpath2
, err2
:= evalSymlinksUsingGetFinalPathNameByHandle(path
)
179 return toNorm(newpath2
, normBase
)
183 newpath
, err
= toNorm(newpath
, normBase
)
185 newpath2
, err2
:= evalSymlinksUsingGetFinalPathNameByHandle(path
)
187 return toNorm(newpath2
, normBase
)
191 if strings
.ToUpper(newpath
) == strings
.ToUpper(path
) {
192 // walkSymlinks did not actually walk any symlinks,
193 // so we don't need to try GetFinalPathNameByHandle.
196 newpath2
, err2
:= evalSymlinksUsingGetFinalPathNameByHandle(path
)
200 newpath2
, err2
= toNorm(newpath2
, normBase
)
204 if samefile(newpath
, newpath2
) {