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