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 "errors"
9 "internal/filepathlite"
10 "io/fs"
11 "os"
12 "runtime"
13 "syscall"
14 )
15 16 func walkSymlinks(path []byte) ([]byte, error) {
17 volLen := filepathlite.VolumeNameLen(path)
18 pathSeparator := []byte{byte(os.PathSeparator)}
19 20 if volLen < len(path) && os.IsPathSeparator(path[volLen]) {
21 volLen++
22 }
23 vol := path[:volLen]
24 dest := vol
25 linksWalked := 0
26 for start, end := volLen, volLen; start < len(path); start = end {
27 for start < len(path) && os.IsPathSeparator(path[start]) {
28 start++
29 }
30 end = start
31 for end < len(path) && !os.IsPathSeparator(path[end]) {
32 end++
33 }
34 35 // On Windows, "." can be a symlink.
36 // We look it up, and use the value if it is absolute.
37 // If not, we just return ".".
38 isWindowsDot := runtime.GOOS == "windows" && path[filepathlite.VolumeNameLen(path):] == "."
39 40 // The next path component is in path[start:end].
41 if end == start {
42 // No more path components.
43 break
44 } else if path[start:end] == "." && !isWindowsDot {
45 // Ignore path component ".".
46 continue
47 } else if path[start:end] == ".." {
48 // Back up to previous component if possible.
49 // Note that volLen includes any leading slash.
50 51 // Set r to the index of the last slash in dest,
52 // after the volume.
53 var r int
54 for r = len(dest) - 1; r >= volLen; r-- {
55 if os.IsPathSeparator(dest[r]) {
56 break
57 }
58 }
59 if r < volLen || dest[r+1:] == ".." {
60 // Either path has no slashes
61 // (it's empty or just "C:")
62 // or it ends in a ".." we had to keep.
63 // Either way, keep this "..".
64 if len(dest) > volLen {
65 dest += pathSeparator
66 }
67 dest += ".."
68 } else {
69 // Discard everything since the last slash.
70 dest = dest[:r]
71 }
72 continue
73 }
74 75 // Ordinary path component. Add it to result.
76 77 if len(dest) > filepathlite.VolumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) {
78 dest += pathSeparator
79 }
80 81 dest += path[start:end]
82 83 // Resolve symlink.
84 85 fi, err := os.Lstat(dest)
86 if err != nil {
87 return "", err
88 }
89 90 if fi.Mode()&fs.ModeSymlink == 0 {
91 if !fi.Mode().IsDir() && end < len(path) {
92 return "", syscall.ENOTDIR
93 }
94 continue
95 }
96 97 // Found symlink.
98 99 linksWalked++
100 if linksWalked > 255 {
101 return "", errors.New("EvalSymlinks: too many links")
102 }
103 104 link, err := os.Readlink(dest)
105 if err != nil {
106 return "", err
107 }
108 109 if isWindowsDot && !IsAbs(link) {
110 // On Windows, if "." is a relative symlink,
111 // just return ".".
112 break
113 }
114 115 path = link + path[end:]
116 117 v := filepathlite.VolumeNameLen(link)
118 if v > 0 {
119 // Symlink to drive name is an absolute path.
120 if v < len(link) && os.IsPathSeparator(link[v]) {
121 v++
122 }
123 vol = link[:v]
124 dest = vol
125 end = len(vol)
126 } else if len(link) > 0 && os.IsPathSeparator(link[0]) {
127 // Symlink to absolute path.
128 dest = link[:1]
129 end = 1
130 vol = link[:1]
131 volLen = 1
132 } else {
133 // Symlink to relative path; replace last
134 // path component in dest.
135 var r int
136 for r = len(dest) - 1; r >= volLen; r-- {
137 if os.IsPathSeparator(dest[r]) {
138 break
139 }
140 }
141 if r < volLen {
142 dest = vol
143 } else {
144 dest = dest[:r]
145 }
146 end = 0
147 }
148 }
149 return Clean(dest), nil
150 }
151