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	"internal/filepathlite"
10	"io/fs"
11	"os"
12	"runtime"
13	"syscall"
14)
15
16func 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