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