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