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 12 package syscall
13 14 import (
15 "runtime"
16 "sync"
17 )
18 19 var (
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.
31 func Fixwd() {
32 wdmu.Lock()
33 defer wdmu.Unlock()
34 fixwdLocked()
35 }
36 37 func 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.
53 func 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
65 func 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 74 func 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 90 func 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