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