symlink.mx raw

   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 []byte) ([]byte, error) {
  17  	volLen := filepathlite.VolumeNameLen(path)
  18  	pathSeparator := []byte{byte(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