// 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 }