1// Copyright 2009 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 os
6
7import (
8	"runtime"
9	"sync"
10	"syscall"
11)
12
13var getwdCache struct {
14	sync.Mutex
15	dir string
16}
17
18// useSyscallwd determines whether to use the return value of
19// syscall.Getwd based on its error.
20var useSyscallwd = func(error) bool { return true }
21
22// Getwd returns a rooted path name corresponding to the
23// current directory.  If the current directory can be
24// reached via multiple paths (due to symbolic links),
25// Getwd may return any one of them.
26func Getwd() (dir string, err error) {
27	if runtime.GOOS == "windows" {
28		return syscall.Getwd()
29	}
30
31	// Clumsy but widespread kludge:
32	// if $PWD is set and matches ".", use it.
33	dot, err := Stat(".")
34	if err != nil {
35		return "", err
36	}
37	dir = Getenv("PWD")
38	if len(dir) > 0 && dir[0] == '/' {
39		d, err := Stat(dir)
40		if err == nil && SameFile(dot, d) {
41			return dir, nil
42		}
43	}
44
45	// If the operating system provides a Getwd call, use it.
46	// Otherwise, we're trying to find our way back to ".".
47	if syscall.ImplementsGetwd {
48		s, e := syscall.Getwd()
49		if useSyscallwd(e) {
50			return s, NewSyscallError("getwd", e)
51		}
52	}
53
54	// Apply same kludge but to cached dir instead of $PWD.
55	getwdCache.Lock()
56	dir = getwdCache.dir
57	getwdCache.Unlock()
58	if len(dir) > 0 {
59		d, err := Stat(dir)
60		if err == nil && SameFile(dot, d) {
61			return dir, nil
62		}
63	}
64
65	// Root is a special case because it has no parent
66	// and ends in a slash.
67	root, err := Stat("/")
68	if err != nil {
69		// Can't stat root - no hope.
70		return "", err
71	}
72	if SameFile(root, dot) {
73		return "/", nil
74	}
75
76	// General algorithm: find name in parent
77	// and then find name of parent.  Each iteration
78	// adds /name to the beginning of dir.
79	dir = ""
80	for parent := ".."; ; parent = "../" + parent {
81		if len(parent) >= 1024 { // Sanity check
82			return "", syscall.ENAMETOOLONG
83		}
84		fd, err := Open(parent)
85		if err != nil {
86			return "", err
87		}
88
89		for {
90			names, err := fd.Readdirnames(100)
91			if err != nil {
92				fd.Close()
93				return "", err
94			}
95			for _, name := range names {
96				d, _ := Lstat(parent + "/" + name)
97				if SameFile(d, dot) {
98					dir = "/" + name + dir
99					goto Found
100				}
101			}
102		}
103
104	Found:
105		pd, err := fd.Stat()
106		if err != nil {
107			return "", err
108		}
109		fd.Close()
110		if SameFile(pd, root) {
111			break
112		}
113		// Set up for next round.
114		dot = pd
115	}
116
117	// Save answer as hint to avoid the expensive path next time.
118	getwdCache.Lock()
119	getwdCache.dir = dir
120	getwdCache.Unlock()
121
122	return dir, nil
123}
124