github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/stub/stub.go (about)

     1  // Package stub is used to start and manage an elvish-stub process.
     2  package stub
     3  
     4  import (
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path"
     9  	"strings"
    10  	"syscall"
    11  
    12  	"github.com/elves/elvish/util"
    13  )
    14  
    15  var Logger = util.GetLogger("[stub] ")
    16  var stubname = "elvish-stub"
    17  
    18  type Stub struct {
    19  	process *os.Process
    20  	// write is the other end of stdin of the stub.
    21  	write *os.File
    22  	// read is the other end of stdout of the stub.
    23  	read    *os.File
    24  	sigch   chan os.Signal
    25  	statech chan struct{}
    26  }
    27  
    28  var stubEnv = []string{"A=BCDEFGHIJKLMNOPQRSTUVWXYZ"}
    29  
    30  // NewStub spawns a new stub. The specified stderr is used for the subprocess.
    31  func NewStub(stderr *os.File) (*Stub, error) {
    32  	// Find stub.
    33  	stubpath, err := searchStub()
    34  	if err != nil {
    35  		return nil, fmt.Errorf("search: %v", err)
    36  	}
    37  
    38  	// Make pipes.
    39  	stdin, write, err := os.Pipe()
    40  	if err != nil {
    41  		return nil, fmt.Errorf("pipe: %v", err)
    42  	}
    43  	read, stdout, err := os.Pipe()
    44  	if err != nil {
    45  		return nil, fmt.Errorf("pipe: %v", err)
    46  	}
    47  
    48  	// Spawn stub.
    49  	attr := os.ProcAttr{
    50  		Env:   stubEnv,
    51  		Files: []*os.File{stdin, stdout, stderr},
    52  		Sys: &syscall.SysProcAttr{
    53  			Setpgid: true,
    54  		},
    55  	}
    56  	process, err := os.StartProcess(stubpath, []string{stubpath}, &attr)
    57  
    58  	if err != nil {
    59  		return nil, fmt.Errorf("spawn: %v", err)
    60  	}
    61  
    62  	// Wait for startup message.
    63  	_, err = fmt.Fscanf(read, "ok\n")
    64  	if err != nil {
    65  		return nil, fmt.Errorf("read startup msg: %v", err)
    66  	}
    67  
    68  	// Spawn signal relayer and waiter.
    69  	sigch := make(chan os.Signal)
    70  	statech := make(chan struct{})
    71  	go relaySignals(read, sigch)
    72  	go wait(process, statech)
    73  
    74  	return &Stub{process, write, read, sigch, statech}, nil
    75  }
    76  
    77  func searchStub() (string, error) {
    78  	// os.Args[0] contains an absolute path. Find elvish-stub in the same
    79  	// directory where elvish was started.
    80  	if len(os.Args) > 0 && path.IsAbs(os.Args[0]) {
    81  		stubpath := path.Join(path.Dir(os.Args[0]), stubname)
    82  		if util.IsExecutable(stubpath) {
    83  			return stubpath, nil
    84  		}
    85  	}
    86  	return util.Search(strings.Split(os.Getenv("PATH"), ":"), stubname)
    87  }
    88  
    89  func (stub *Stub) Process() *os.Process {
    90  	return stub.process
    91  }
    92  
    93  // Terminate terminates the stub.
    94  func (stub *Stub) Terminate() {
    95  	stub.write.Close()
    96  }
    97  
    98  // SetTitle sets the title of the stub.
    99  func (stub *Stub) SetTitle(s string) {
   100  	s = strings.TrimSpace(s)
   101  	fmt.Fprintf(stub.write, "t%04d%s", len(s), s)
   102  }
   103  
   104  func (stub *Stub) Chdir(dir string) {
   105  	fmt.Fprintf(stub.write, "d%04d%s", len(dir), dir)
   106  }
   107  
   108  // Signals returns a channel into which signals sent to the stub are relayed.
   109  func (stub *Stub) Signals() <-chan os.Signal {
   110  	return stub.sigch
   111  }
   112  
   113  // State returns a channel that is closed when the stub exits.
   114  func (stub *Stub) State() <-chan struct{} {
   115  	return stub.statech
   116  }
   117  
   118  // Alive reports whether the stub is alive.
   119  func (stub *Stub) Alive() bool {
   120  	select {
   121  	case <-stub.statech:
   122  		return false
   123  	default:
   124  		return true
   125  	}
   126  }
   127  
   128  // relaySignals relays output of the stub to sigch, assuming that outputs
   129  // represent signal numbers.
   130  func relaySignals(reader io.Reader, sigch chan<- os.Signal) {
   131  	for {
   132  		var signum int
   133  		_, err := fmt.Fscanf(reader, "%d", &signum)
   134  		Logger.Println("signal:", signum, err)
   135  		if err != nil {
   136  			sigch <- BadSignal{err}
   137  			if err == io.EOF {
   138  				break
   139  			}
   140  		} else {
   141  			sigch <- syscall.Signal(signum)
   142  		}
   143  	}
   144  }
   145  
   146  func wait(proc *os.Process, ch chan<- struct{}) {
   147  	for {
   148  		state, err := proc.Wait()
   149  		Logger.Println("wait:", state, err)
   150  		if err != nil || state.Exited() {
   151  			break
   152  		}
   153  	}
   154  	close(ch)
   155  }