pwd_plan9.mx raw

   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