diff options
Diffstat (limited to 'src/os/getwd.go')
-rw-r--r-- | src/os/getwd.go | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/src/os/getwd.go b/src/os/getwd.go new file mode 100644 index 0000000..90604cf --- /dev/null +++ b/src/os/getwd.go @@ -0,0 +1,126 @@ +// 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 a rooted 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. +func Getwd() (dir string, err error) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + return syscall.Getwd() + } + + // Clumsy but widespread kludge: + // if $PWD is set and matches ".", use it. + dot, err := statNolog(".") + if err != nil { + return "", err + } + dir = Getenv("PWD") + if len(dir) > 0 && dir[0] == '/' { + d, err := statNolog(dir) + if err == nil && SameFile(dot, d) { + return dir, nil + } + } + + // If the operating system provides a Getwd call, use it. + // Otherwise, we're trying to find our way back to ".". + if syscall.ImplementsGetwd { + var ( + s string + e error + ) + for { + s, e = syscall.Getwd() + if e != syscall.EINTR { + break + } + } + return s, NewSyscallError("getwd", e) + } + + // 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 "", syscall.ENAMETOOLONG + } + fd, err := openFileNolog(parent, O_RDONLY, 0) + if err != nil { + return "", err + } + + for { + names, err := fd.Readdirnames(100) + if err != nil { + fd.Close() + return "", err + } + 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 +} |