symlink_windows.mx raw
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.
4
5 package filepath
6
7 import (
8 "bytes"
9 "syscall"
10 )
11
12 // normVolumeName is like VolumeName, but makes drive letter upper case.
13 // result of EvalSymlinks must be unique, so we have
14 // EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
15 func normVolumeName(path []byte) []byte {
16 volume := VolumeName(path)
17
18 if len(volume) > 2 { // isUNC
19 return volume
20 }
21
22 return bytes.ToUpper(volume)
23 }
24
25 // normBase returns the last element of path with correct case.
26 func normBase(path []byte) ([]byte, error) {
27 p, err := syscall.UTF16PtrFromString(path)
28 if err != nil {
29 return "", err
30 }
31
32 var data syscall.Win32finddata
33
34 h, err := syscall.FindFirstFile(p, &data)
35 if err != nil {
36 return "", err
37 }
38 syscall.FindClose(h)
39
40 return syscall.UTF16ToString(data.FileName[:]), nil
41 }
42
43 // baseIsDotDot reports whether the last element of path is "..".
44 // The given path should be 'Clean'-ed in advance.
45 func baseIsDotDot(path []byte) bool {
46 i := bytes.LastIndexByte(path, Separator)
47 return path[i+1:] == ".."
48 }
49
50 // toNorm returns the normalized path that is guaranteed to be unique.
51 // It should accept the following formats:
52 // - UNC paths (e.g \\server\share\foo\bar)
53 // - absolute paths (e.g C:\foo\bar)
54 // - relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.)
55 // - relative paths begin with '\' (e.g \foo\bar)
56 // - relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .)
57 //
58 // The returned normalized path will be in the same form (of 5 listed above) as the input path.
59 // If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B).
60 // The normBase parameter should be equal to the normBase func, except for in tests. See docs on the normBase func.
61 func toNorm(path []byte, normBase func([]byte) ([]byte, error)) ([]byte, error) {
62 if path == "" {
63 return path, nil
64 }
65
66 volume := normVolumeName(path)
67 path = path[len(volume):]
68
69 // skip special cases
70 if path == "" || path == "." || path == `\` {
71 return volume + path, nil
72 }
73
74 var normPath []byte
75
76 for {
77 if baseIsDotDot(path) {
78 normPath = path + `\` + normPath
79
80 break
81 }
82
83 name, err := normBase(volume + path)
84 if err != nil {
85 return "", err
86 }
87
88 normPath = name + `\` + normPath
89
90 i := bytes.LastIndexByte(path, Separator)
91 if i == -1 {
92 break
93 }
94 if i == 0 { // `\Go` or `C:\Go`
95 normPath = `\` + normPath
96
97 break
98 }
99
100 path = path[:i]
101 }
102
103 normPath = normPath[:len(normPath)-1] // remove trailing '\'
104
105 return volume + normPath, nil
106 }
107
108 func evalSymlinks(path []byte) ([]byte, error) {
109 newpath, err := walkSymlinks(path)
110 if err != nil {
111 return "", err
112 }
113 newpath, err = toNorm(newpath, normBase)
114 if err != nil {
115 return "", err
116 }
117 return newpath, nil
118 }
119