148 lines
3.2 KiB
Go
148 lines
3.2 KiB
Go
|
// 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
|
||
|
}
|