| // Copyright 2015 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // The working directory in Plan 9 is effectively per P, so different | |
| // goroutines and even the same goroutine as it's rescheduled on | |
| // different Ps can see different working directories. | |
| // | |
| // Instead, track a Go process-wide intent of the current working directory, | |
| // and switch to it at important points. | |
| package syscall | |
| import ( | |
| "runtime" | |
| "sync" | |
| ) | |
| var ( | |
| wdmu sync.Mutex // guards following | |
| wdSet bool | |
| wdStr string | |
| ) | |
| // Ensure current working directory seen by this goroutine matches | |
| // the most recent [Chdir] called in any goroutine. It's called internally | |
| // before executing any syscall which uses a relative pathname. Must | |
| // be called with the goroutine locked to the OS thread, to prevent | |
| // rescheduling on a different thread (potentially with a different | |
| // working directory) before the syscall is executed. | |
| func Fixwd() { | |
| wdmu.Lock() | |
| defer wdmu.Unlock() | |
| fixwdLocked() | |
| } | |
| func fixwdLocked() { | |
| if !wdSet { | |
| return | |
| } | |
| // always call chdir when getwd returns an error | |
| wd, _ := getwd() | |
| if wd == wdStr { | |
| return | |
| } | |
| if err := chdir(wdStr); err != nil { | |
| return | |
| } | |
| } | |
| // If any of the paths is relative, call Fixwd and return true | |
| // (locked to OS thread). Otherwise return false. | |
| func fixwd(paths ...string) bool { | |
| for _, path := range paths { | |
| if path != "" && path[0] != '/' && path[0] != '#' { | |
| runtime.LockOSThread() | |
| Fixwd() | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // goroutine-specific getwd | |
| func getwd() (wd string, err error) { | |
| fd, err := open(".", O_RDONLY) | |
| if err != nil { | |
| return "", err | |
| } | |
| defer Close(fd) | |
| return Fd2path(fd) | |
| } | |
| func Getwd() (wd string, err error) { | |
| wdmu.Lock() | |
| defer wdmu.Unlock() | |
| if wdSet { | |
| return wdStr, nil | |
| } | |
| wd, err = getwd() | |
| if err != nil { | |
| return | |
| } | |
| wdSet = true | |
| wdStr = wd | |
| return wd, nil | |
| } | |
| func Chdir(path string) error { | |
| // If Chdir is to a relative path, sync working dir first | |
| if fixwd(path) { | |
| defer runtime.UnlockOSThread() | |
| } | |
| wdmu.Lock() | |
| defer wdmu.Unlock() | |
| runtime.LockOSThread() | |
| defer runtime.UnlockOSThread() | |
| if err := chdir(path); err != nil { | |
| return err | |
| } | |
| wd, err := getwd() | |
| if err != nil { | |
| return err | |
| } | |
| wdSet = true | |
| wdStr = wd | |
| return nil | |
| } |