| // 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 { |
| for { |
| dir, err = syscall.Getwd() |
| if err != syscall.EINTR { |
| break |
| } |
| } |
| // 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 |
| } |