1// Copyright 2015 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
5// The working directory in Plan 9 is effectively per P, so different
6// goroutines and even the same goroutine as it's rescheduled on
7// different Ps can see different working directories.
8//
9// Instead, track a Go process-wide intent of the current working directory,
10// and switch to it at important points.
11
12package syscall
13
14import (
15	"runtime"
16	"sync"
17)
18
19var (
20	wdmu  sync.Mutex // guards following
21	wdSet bool
22	wdStr string
23)
24
25// Ensure current working directory seen by this goroutine matches
26// the most recent [Chdir] called in any goroutine. It's called internally
27// before executing any syscall which uses a relative pathname. Must
28// be called with the goroutine locked to the OS thread, to prevent
29// rescheduling on a different thread (potentially with a different
30// working directory) before the syscall is executed.
31func Fixwd() {
32	wdmu.Lock()
33	defer wdmu.Unlock()
34	fixwdLocked()
35}
36
37func fixwdLocked() {
38	if !wdSet {
39		return
40	}
41	// always call chdir when getwd returns an error
42	wd, _ := getwd()
43	if wd == wdStr {
44		return
45	}
46	if err := chdir(wdStr); err != nil {
47		return
48	}
49}
50
51// If any of the paths is relative, call Fixwd and return true
52// (locked to OS thread). Otherwise return false.
53func fixwd(paths ...string) bool {
54	for _, path := range paths {
55		if path != "" && path[0] != '/' && path[0] != '#' {
56			runtime.LockOSThread()
57			Fixwd()
58			return true
59		}
60	}
61	return false
62}
63
64// goroutine-specific getwd
65func getwd() (wd string, err error) {
66	fd, err := open(".", O_RDONLY)
67	if err != nil {
68		return "", err
69	}
70	defer Close(fd)
71	return Fd2path(fd)
72}
73
74func Getwd() (wd string, err error) {
75	wdmu.Lock()
76	defer wdmu.Unlock()
77
78	if wdSet {
79		return wdStr, nil
80	}
81	wd, err = getwd()
82	if err != nil {
83		return
84	}
85	wdSet = true
86	wdStr = wd
87	return wd, nil
88}
89
90func Chdir(path string) error {
91	// If Chdir is to a relative path, sync working dir first
92	if fixwd(path) {
93		defer runtime.UnlockOSThread()
94	}
95	wdmu.Lock()
96	defer wdmu.Unlock()
97
98	runtime.LockOSThread()
99	defer runtime.UnlockOSThread()
100	if err := chdir(path); err != nil {
101		return err
102	}
103
104	wd, err := getwd()
105	if err != nil {
106		return err
107	}
108	wdSet = true
109	wdStr = wd
110	return nil
111}
112