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