Source file src/path/filepath/symlink.go

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

View as plain text