// 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"
"os"
"runtime"
)
// isRoot returns true if path is root of file system
// (`/` on unix and `/`, `\`, `c:\` or `c:/` on windows).
func isRoot(path string) bool {
if runtime.GOOS != "windows" {
return path == "/"
}
switch len(path) {
case 1:
return os.IsPathSeparator(path[0])
case 3:
return path[1] == ':' && os.IsPathSeparator(path[2])
}
return false
}
// isDriveLetter returns true if path is Windows drive letter (like "c:").
func isDriveLetter(path string) bool {
if runtime.GOOS != "windows" {
return false
}
return len(path) == 2 && path[1] == ':'
}
func walkLink(path string, linksWalked *int) (newpath string, islink bool, err error) {
if *linksWalked > 255 {
return "", false, errors.New("EvalSymlinks: too many links")
}
fi, err := os.Lstat(path)
if err != nil {
return "", false, err
}
if fi.Mode()&os.ModeSymlink == 0 {
return path, false, nil
}
newpath, err = os.Readlink(path)
if err != nil {
return "", false, err
}
*linksWalked++
return newpath, true, nil
}
func walkLinks(path string, linksWalked *int) (string, error) {
switch dir, file := Split(path); {
case dir == "":
newpath, _, err := walkLink(file, linksWalked)
return newpath, err
case file == "":
if isDriveLetter(dir) {
return dir, nil
}
if os.IsPathSeparator(dir[len(dir)-1]) {
if isRoot(dir) {
return dir, nil
}
return walkLinks(dir[:len(dir)-1], linksWalked)
}
newpath, _, err := walkLink(dir, linksWalked)
return newpath, err
default:
newdir, err := walkLinks(dir, linksWalked)
if err != nil {
return "", err
}
newpath, islink, err := walkLink(Join(newdir, file), linksWalked)
if err != nil {
return "", err
}
if !islink {
return newpath, nil
}
if IsAbs(newpath) || os.IsPathSeparator(newpath[0]) {
return newpath, nil
}
return Join(newdir, newpath), nil
}
}
func walkSymlinks(path string) (string, error) {
if path == "" {
return path, nil
}
var linksWalked int // to protect against cycles
for {
i := linksWalked
newpath, err := walkLinks(path, &linksWalked)
if err != nil {
return "", err
}
if runtime.GOOS == "windows" {
// walkLinks(".", ...) always returns "." on unix.
// But on windows it returns symlink target, if current
// directory is a symlink. Stop the walk, if symlink
// target is not absolute path, and return "."
// to the caller (just like unix does).
// Same for "C:.".
if path[volumeNameLen(path):] == "." && !IsAbs(newpath) {
return path, nil
}
}
if i == linksWalked {
return Clean(newpath), nil
}
path = newpath
}
}