| // Copyright 2012 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package filepath | |
| import ( | |
| "errors" | |
| "io/fs" | |
| "os" | |
| "runtime" | |
| "syscall" | |
| ) | |
| func walkSymlinks(path string) (string, error) { | |
| volLen := volumeNameLen(path) | |
| pathSeparator := string(os.PathSeparator) | |
| if volLen < len(path) && os.IsPathSeparator(path[volLen]) { | |
| volLen++ | |
| } | |
| vol := path[:volLen] | |
| dest := vol | |
| linksWalked := 0 | |
| for start, end := volLen, volLen; start < len(path); start = end { | |
| for start < len(path) && os.IsPathSeparator(path[start]) { | |
| start++ | |
| } | |
| end = start | |
| for end < len(path) && !os.IsPathSeparator(path[end]) { | |
| end++ | |
| } | |
| // On Windows, "." can be a symlink. | |
| // We look it up, and use the value if it is absolute. | |
| // If not, we just return ".". | |
| isWindowsDot := runtime.GOOS == "windows" && path[volumeNameLen(path):] == "." | |
| // The next path component is in path[start:end]. | |
| if end == start { | |
| // No more path components. | |
| break | |
| } else if path[start:end] == "." && !isWindowsDot { | |
| // Ignore path component ".". | |
| continue | |
| } else if path[start:end] == ".." { | |
| // Back up to previous component if possible. | |
| // Note that volLen includes any leading slash. | |
| // Set r to the index of the last slash in dest, | |
| // after the volume. | |
| var r int | |
| for r = len(dest) - 1; r >= volLen; r-- { | |
| if os.IsPathSeparator(dest[r]) { | |
| break | |
| } | |
| } | |
| if r < volLen || dest[r+1:] == ".." { | |
| // Either path has no slashes | |
| // (it's empty or just "C:") | |
| // or it ends in a ".." we had to keep. | |
| // Either way, keep this "..". | |
| if len(dest) > volLen { | |
| dest += pathSeparator | |
| } | |
| dest += ".." | |
| } else { | |
| // Discard everything since the last slash. | |
| dest = dest[:r] | |
| } | |
| continue | |
| } | |
| // Ordinary path component. Add it to result. | |
| if len(dest) > volumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) { | |
| dest += pathSeparator | |
| } | |
| dest += path[start:end] | |
| // Resolve symlink. | |
| fi, err := os.Lstat(dest) | |
| if err != nil { | |
| return "", err | |
| } | |
| if fi.Mode()&fs.ModeSymlink == 0 { | |
| if !fi.Mode().IsDir() && end < len(path) { | |
| return "", syscall.ENOTDIR | |
| } | |
| continue | |
| } | |
| // Found symlink. | |
| linksWalked++ | |
| if linksWalked > 255 { | |
| return "", errors.New("EvalSymlinks: too many links") | |
| } | |
| link, err := os.Readlink(dest) | |
| if err != nil { | |
| return "", err | |
| } | |
| if isWindowsDot && !IsAbs(link) { | |
| // On Windows, if "." is a relative symlink, | |
| // just return ".". | |
| break | |
| } | |
| path = link + path[end:] | |
| v := volumeNameLen(link) | |
| if v > 0 { | |
| // Symlink to drive name is an absolute path. | |
| if v < len(link) && os.IsPathSeparator(link[v]) { | |
| v++ | |
| } | |
| vol = link[:v] | |
| dest = vol | |
| end = len(vol) | |
| } else if len(link) > 0 && os.IsPathSeparator(link[0]) { | |
| // Symlink to absolute path. | |
| dest = link[:1] | |
| end = 1 | |
| } else { | |
| // Symlink to relative path; replace last | |
| // path component in dest. | |
| var r int | |
| for r = len(dest) - 1; r >= volLen; r-- { | |
| if os.IsPathSeparator(dest[r]) { | |
| break | |
| } | |
| } | |
| if r < volLen { | |
| dest = vol | |
| } else { | |
| dest = dest[:r] | |
| } | |
| end = 0 | |
| } | |
| } | |
| return Clean(dest), nil | |
| } |