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 }