github.com/c12o16h1/go/src@v0.0.0-20200114212001-5a151c0f00ed/path/filepath/symlink.go (about)

     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  	"syscall"
    12  )
    13  
    14  func walkSymlinks(path string) (string, error) {
    15  	volLen := volumeNameLen(path)
    16  	pathSeparator := string(os.PathSeparator)
    17  
    18  	if volLen < len(path) && os.IsPathSeparator(path[volLen]) {
    19  		volLen++
    20  	}
    21  	vol := path[:volLen]
    22  	dest := vol
    23  	linksWalked := 0
    24  	for start, end := volLen, volLen; start < len(path); start = end {
    25  		for start < len(path) && os.IsPathSeparator(path[start]) {
    26  			start++
    27  		}
    28  		end = start
    29  		for end < len(path) && !os.IsPathSeparator(path[end]) {
    30  			end++
    31  		}
    32  
    33  		// On Windows, "." can be a symlink.
    34  		// We look it up, and use the value if it is absolute.
    35  		// If not, we just return ".".
    36  		isWindowsDot := runtime.GOOS == "windows" && path[volumeNameLen(path):] == "."
    37  
    38  		// The next path component is in path[start:end].
    39  		if end == start {
    40  			// No more path components.
    41  			break
    42  		} else if path[start:end] == "." && !isWindowsDot {
    43  			// Ignore path component ".".
    44  			continue
    45  		} else if path[start:end] == ".." {
    46  			// Back up to previous component if possible.
    47  			// Note that volLen includes any leading slash.
    48  
    49  			// Set r to the index of the last slash in dest,
    50  			// after the volume.
    51  			var r int
    52  			for r = len(dest) - 1; r >= volLen; r-- {
    53  				if os.IsPathSeparator(dest[r]) {
    54  					break
    55  				}
    56  			}
    57  			if r < volLen || dest[r+1:] == ".." {
    58  				// Either path has no slashes
    59  				// (it's empty or just "C:")
    60  				// or it ends in a ".." we had to keep.
    61  				// Either way, keep this "..".
    62  				if len(dest) > volLen {
    63  					dest += pathSeparator
    64  				}
    65  				dest += ".."
    66  			} else {
    67  				// Discard everything since the last slash.
    68  				dest = dest[:r]
    69  			}
    70  			continue
    71  		}
    72  
    73  		// Ordinary path component. Add it to result.
    74  
    75  		if len(dest) > volumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) {
    76  			dest += pathSeparator
    77  		}
    78  
    79  		dest += path[start:end]
    80  
    81  		// Resolve symlink.
    82  
    83  		fi, err := os.Lstat(dest)
    84  		if err != nil {
    85  			return "", err
    86  		}
    87  
    88  		if fi.Mode()&os.ModeSymlink == 0 {
    89  			if !fi.Mode().IsDir() && end < len(path) {
    90  				return "", syscall.ENOTDIR
    91  			}
    92  			continue
    93  		}
    94  
    95  		// Found symlink.
    96  
    97  		linksWalked++
    98  		if linksWalked > 255 {
    99  			return "", errors.New("EvalSymlinks: too many links")
   100  		}
   101  
   102  		link, err := os.Readlink(dest)
   103  		if err != nil {
   104  			return "", err
   105  		}
   106  
   107  		if isWindowsDot && !IsAbs(link) {
   108  			// On Windows, if "." is a relative symlink,
   109  			// just return ".".
   110  			break
   111  		}
   112  
   113  		path = link + path[end:]
   114  
   115  		v := volumeNameLen(link)
   116  		if v > 0 {
   117  			// Symlink to drive name is an absolute path.
   118  			if v < len(link) && os.IsPathSeparator(link[v]) {
   119  				v++
   120  			}
   121  			vol = link[:v]
   122  			dest = vol
   123  			end = len(vol)
   124  		} else if len(link) > 0 && os.IsPathSeparator(link[0]) {
   125  			// Symlink to absolute path.
   126  			dest = link[:1]
   127  			end = 1
   128  		} else {
   129  			// Symlink to relative path; replace last
   130  			// path component in dest.
   131  			var r int
   132  			for r = len(dest) - 1; r >= volLen; r-- {
   133  				if os.IsPathSeparator(dest[r]) {
   134  					break
   135  				}
   136  			}
   137  			if r < volLen {
   138  				dest = vol
   139  			} else {
   140  				dest = dest[:r]
   141  			}
   142  			end = 0
   143  		}
   144  	}
   145  	return Clean(dest), nil
   146  }