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