Home | History | Annotate | Download | only in filepath
      1 // Copyright 2012 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package filepath
      6 
      7 import (
      8 	"errors"
      9 	"os"
     10 	"runtime"
     11 )
     12 
     13 // isRoot returns true if path is root of file system
     14 // (`/` on unix and `/`, `\`, `c:\` or `c:/` on windows).
     15 func isRoot(path string) bool {
     16 	if runtime.GOOS != "windows" {
     17 		return path == "/"
     18 	}
     19 	switch len(path) {
     20 	case 1:
     21 		return os.IsPathSeparator(path[0])
     22 	case 3:
     23 		return path[1] == ':' && os.IsPathSeparator(path[2])
     24 	}
     25 	return false
     26 }
     27 
     28 // isDriveLetter returns true if path is Windows drive letter (like "c:").
     29 func isDriveLetter(path string) bool {
     30 	if runtime.GOOS != "windows" {
     31 		return false
     32 	}
     33 	return len(path) == 2 && path[1] == ':'
     34 }
     35 
     36 func walkLink(path string, linksWalked *int) (newpath string, islink bool, err error) {
     37 	if *linksWalked > 255 {
     38 		return "", false, errors.New("EvalSymlinks: too many links")
     39 	}
     40 	fi, err := os.Lstat(path)
     41 	if err != nil {
     42 		return "", false, err
     43 	}
     44 	if fi.Mode()&os.ModeSymlink == 0 {
     45 		return path, false, nil
     46 	}
     47 	newpath, err = os.Readlink(path)
     48 	if err != nil {
     49 		return "", false, err
     50 	}
     51 	*linksWalked++
     52 	return newpath, true, nil
     53 }
     54 
     55 func walkLinks(path string, linksWalked *int) (string, error) {
     56 	switch dir, file := Split(path); {
     57 	case dir == "":
     58 		newpath, _, err := walkLink(file, linksWalked)
     59 		return newpath, err
     60 	case file == "":
     61 		if isDriveLetter(dir) {
     62 			return dir, nil
     63 		}
     64 		if os.IsPathSeparator(dir[len(dir)-1]) {
     65 			if isRoot(dir) {
     66 				return dir, nil
     67 			}
     68 			return walkLinks(dir[:len(dir)-1], linksWalked)
     69 		}
     70 		newpath, _, err := walkLink(dir, linksWalked)
     71 		return newpath, err
     72 	default:
     73 		newdir, err := walkLinks(dir, linksWalked)
     74 		if err != nil {
     75 			return "", err
     76 		}
     77 		newpath, islink, err := walkLink(Join(newdir, file), linksWalked)
     78 		if err != nil {
     79 			return "", err
     80 		}
     81 		if !islink {
     82 			return newpath, nil
     83 		}
     84 		if IsAbs(newpath) || os.IsPathSeparator(newpath[0]) {
     85 			return newpath, nil
     86 		}
     87 		return Join(newdir, newpath), nil
     88 	}
     89 }
     90 
     91 func walkSymlinks(path string) (string, error) {
     92 	if path == "" {
     93 		return path, nil
     94 	}
     95 	var linksWalked int // to protect against cycles
     96 	for {
     97 		i := linksWalked
     98 		newpath, err := walkLinks(path, &linksWalked)
     99 		if err != nil {
    100 			return "", err
    101 		}
    102 		if runtime.GOOS == "windows" {
    103 			// walkLinks(".", ...) always returns "." on unix.
    104 			// But on windows it returns symlink target, if current
    105 			// directory is a symlink. Stop the walk, if symlink
    106 			// target is not absolute path, and return "."
    107 			// to the caller (just like unix does).
    108 			// Same for "C:.".
    109 			if path[volumeNameLen(path):] == "." && !IsAbs(newpath) {
    110 				return path, nil
    111 			}
    112 		}
    113 		if i == linksWalked {
    114 			return Clean(newpath), nil
    115 		}
    116 		path = newpath
    117 	}
    118 }
    119