|  | // 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). | 
|  | if path == "." && !IsAbs(newpath) { | 
|  | return ".", nil | 
|  | } | 
|  | } | 
|  | if i == linksWalked { | 
|  | return Clean(newpath), nil | 
|  | } | 
|  | path = newpath | 
|  | } | 
|  | } |