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
13// isRoot returns true if path is root of file system
14// (`/` on unix and `/`, `\`, `c:\` or `c:/` on windows).
15func isRoot(path string) bool {
16	if runtime.GOOS != "windows" {
17		return path == "/"
18	}
19	switch len(path) {
20	case 1:
21		return os.IsPathSeparator(path[0])
22	case 3:
23		return path[1] == ':' && os.IsPathSeparator(path[2])
24	}
25	return false
26}
27
28// isDriveLetter returns true if path is Windows drive letter (like "c:").
29func isDriveLetter(path string) bool {
30	if runtime.GOOS != "windows" {
31		return false
32	}
33	return len(path) == 2 && path[1] == ':'
34}
35
36func walkLink(path string, linksWalked *int) (newpath string, islink bool, err error) {
37	if *linksWalked > 255 {
38		return "", false, errors.New("EvalSymlinks: too many links")
39	}
40	fi, err := os.Lstat(path)
41	if err != nil {
42		return "", false, err
43	}
44	if fi.Mode()&os.ModeSymlink == 0 {
45		return path, false, nil
46	}
47	newpath, err = os.Readlink(path)
48	if err != nil {
49		return "", false, err
50	}
51	*linksWalked++
52	return newpath, true, nil
53}
54
55func walkLinks(path string, linksWalked *int) (string, error) {
56	switch dir, file := Split(path); {
57	case dir == "":
58		newpath, _, err := walkLink(file, linksWalked)
59		return newpath, err
60	case file == "":
61		if isDriveLetter(dir) {
62			return dir, nil
63		}
64		if os.IsPathSeparator(dir[len(dir)-1]) {
65			if isRoot(dir) {
66				return dir, nil
67			}
68			return walkLinks(dir[:len(dir)-1], linksWalked)
69		}
70		newpath, _, err := walkLink(dir, linksWalked)
71		return newpath, err
72	default:
73		newdir, err := walkLinks(dir, linksWalked)
74		if err != nil {
75			return "", err
76		}
77		newpath, islink, err := walkLink(Join(newdir, file), linksWalked)
78		if err != nil {
79			return "", err
80		}
81		if !islink {
82			return newpath, nil
83		}
84		if IsAbs(newpath) || os.IsPathSeparator(newpath[0]) {
85			return newpath, nil
86		}
87		return Join(newdir, newpath), nil
88	}
89}
90
91func walkSymlinks(path string) (string, error) {
92	if path == "" {
93		return path, nil
94	}
95	var linksWalked int // to protect against cycles
96	for {
97		i := linksWalked
98		newpath, err := walkLinks(path, &linksWalked)
99		if err != nil {
100			return "", err
101		}
102		if runtime.GOOS == "windows" {
103			// walkLinks(".", ...) always returns "." on unix.
104			// But on windows it returns symlink target, if current
105			// directory is a symlink. Stop the walk, if symlink
106			// target is not absolute path, and return "."
107			// to the caller (just like unix does).
108			// Same for "C:.".
109			if path[volumeNameLen(path):] == "." && !IsAbs(newpath) {
110				return path, nil
111			}
112		}
113		if i == linksWalked {
114			return Clean(newpath), nil
115		}
116		path = newpath
117	}
118}
119