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 5package filepath 6 7import ( 8 "errors" 9 "internal/filepathlite" 10 "io/fs" 11 "os" 12 "runtime" 13 "syscall" 14) 15 16func walkSymlinks(path string) (string, error) { 17 volLen := filepathlite.VolumeNameLen(path) 18 pathSeparator := string(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