-
Notifications
You must be signed in to change notification settings - Fork 18k
/
Copy pathgetwd.go
149 lines (136 loc) · 3.71 KB
/
getwd.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package os
import (
"runtime"
"sync"
"syscall"
)
var getwdCache struct {
sync.Mutex
dir string
}
// Getwd returns an absolute path name corresponding to the
// current directory. If the current directory can be
// reached via multiple paths (due to symbolic links),
// Getwd may return any one of them.
//
// On Unix platforms, if the environment variable PWD
// provides an absolute name, and it is a name of the
// current directory, it is returned.
func Getwd() (dir string, err error) {
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
// Use syscall.Getwd directly for
// - plan9: see reasons in CL 89575;
// - windows: syscall implementation is sufficient,
// and we should not rely on $PWD.
dir, err = syscall.Getwd()
return dir, NewSyscallError("getwd", err)
}
// Clumsy but widespread kludge:
// if $PWD is set and matches ".", use it.
var dot FileInfo
dir = Getenv("PWD")
if len(dir) > 0 && dir[0] == '/' {
dot, err = statNolog(".")
if err != nil {
return "", err
}
d, err := statNolog(dir)
if err == nil && SameFile(dot, d) {
return dir, nil
}
// If err is ENAMETOOLONG here, the syscall.Getwd below will
// fail with the same error, too, but let's give it a try
// anyway as the fallback code is much slower.
}
// If the operating system provides a Getwd call, use it.
if syscall.ImplementsGetwd {
dir, err = ignoringEINTR2(syscall.Getwd)
// Linux returns ENAMETOOLONG if the result is too long.
// Some BSD systems appear to return EINVAL.
// FreeBSD systems appear to use ENOMEM
// Solaris appears to use ERANGE.
if err != syscall.ENAMETOOLONG && err != syscall.EINVAL && err != errERANGE && err != errENOMEM {
return dir, NewSyscallError("getwd", err)
}
}
// We're trying to find our way back to ".".
if dot == nil {
dot, err = statNolog(".")
if err != nil {
return "", err
}
}
// Apply same kludge but to cached dir instead of $PWD.
getwdCache.Lock()
dir = getwdCache.dir
getwdCache.Unlock()
if len(dir) > 0 {
d, err := statNolog(dir)
if err == nil && SameFile(dot, d) {
return dir, nil
}
}
// Root is a special case because it has no parent
// and ends in a slash.
root, err := statNolog("/")
if err != nil {
// Can't stat root - no hope.
return "", err
}
if SameFile(root, dot) {
return "/", nil
}
// General algorithm: find name in parent
// and then find name of parent. Each iteration
// adds /name to the beginning of dir.
dir = ""
for parent := ".."; ; parent = "../" + parent {
if len(parent) >= 1024 { // Sanity check
return "", NewSyscallError("getwd", syscall.ENAMETOOLONG)
}
fd, err := openDirNolog(parent)
if err != nil {
return "", err
}
for {
names, err := fd.Readdirnames(100)
if err != nil {
fd.Close()
// Readdirnames can return io.EOF or other error.
// In any case, we're here because syscall.Getwd
// is not implemented or failed with ENAMETOOLONG,
// so return the most sensible error.
if syscall.ImplementsGetwd {
return "", NewSyscallError("getwd", syscall.ENAMETOOLONG)
}
return "", NewSyscallError("getwd", errENOSYS)
}
for _, name := range names {
d, _ := lstatNolog(parent + "/" + name)
if SameFile(d, dot) {
dir = "/" + name + dir
goto Found
}
}
}
Found:
pd, err := fd.Stat()
fd.Close()
if err != nil {
return "", err
}
if SameFile(pd, root) {
break
}
// Set up for next round.
dot = pd
}
// Save answer as hint to avoid the expensive path next time.
getwdCache.Lock()
getwdCache.dir = dir
getwdCache.Unlock()
return dir, nil
}