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

View as plain text