// 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 }