-
Notifications
You must be signed in to change notification settings - Fork 0
/
realpath.go
124 lines (98 loc) · 2.35 KB
/
realpath.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package lib
import (
"bytes"
"os"
"path/filepath"
)
// REALPATH lib copy to speed things update
// RealQuickPath just quickly gets path, without caring about errors
func RealQuickPath(fpath string) string {
rpath, _ := Realpath(fpath)
return rpath
}
// Realpath returns the real path of a given file in the os
func Realpath(fpath string) (string, error) {
if len(fpath) == 0 {
return "", os.ErrInvalid
}
if !filepath.IsAbs(fpath) {
pwd, err := os.Getwd()
if err != nil {
return "", err
}
fpath = filepath.Join(pwd, fpath)
}
path := []byte(fpath)
nlinks := 0
start := 1
prev := 1
for start < len(path) {
c := nextComponent(path, start)
cur := c[start:]
switch {
case len(cur) == 0:
copy(path[start:], path[start+1:])
path = path[0 : len(path)-1]
case len(cur) == 1 && cur[0] == '.':
if start+2 < len(path) {
copy(path[start:], path[start+2:])
}
path = path[0 : len(path)-2]
case len(cur) == 2 && cur[0] == '.' && cur[1] == '.':
copy(path[prev:], path[start+2:])
path = path[0 : len(path)+prev-(start+2)]
prev = 1
start = 1
default:
fi, err := os.Lstat(string(c))
if err != nil {
return "", err
}
if isSymlink(fi) {
nlinks++
if nlinks > 16 {
return "", os.ErrInvalid
}
var link string
link, err = os.Readlink(string(c))
if err != nil {
return "", err
}
after := string(path[len(c):])
// switch symlink component with its real path
path = switchSymlinkCom(path, start, link, after)
prev = 1
start = 1
} else {
// Directories
prev = start
start = len(c) + 1
}
}
}
for len(path) > 1 && path[len(path)-1] == os.PathSeparator {
path = path[0 : len(path)-1]
}
return string(path), nil
}
// test if a link is symbolic link
func isSymlink(fi os.FileInfo) bool {
return fi.Mode()&os.ModeSymlink == os.ModeSymlink
}
// switch a symbolic link component to its real path
func switchSymlinkCom(path []byte, start int, link, after string) []byte {
if link[0] == os.PathSeparator {
// Absolute links
return []byte(filepath.Join(link, after))
}
// Relative links
return []byte(filepath.Join(string(path[0:start]), link, after))
}
// get the next component
func nextComponent(path []byte, start int) []byte {
v := bytes.IndexByte(path[start:], os.PathSeparator)
if v < 0 {
return path
}
return path[0 : start+v]
}