blob: 8331f9cd508d3578047c114d199cf15efd205733 [file] [log] [blame]
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -08001package modules
2
3import (
4 "flag"
5 "fmt"
6 "io"
7 "os"
8 "strings"
9 "sync"
10 "time"
11
Jiri Simsa764efb72014-12-25 20:57:03 -080012 "v.io/core/veyron2/vlog"
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080013
Jiri Simsa764efb72014-12-25 20:57:03 -080014 vexec "v.io/core/veyron/lib/exec"
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080015)
16
17type commandDesc struct {
18 factory func() command
19 main Main
20 help string
21}
22
23type cmdRegistry struct {
24 sync.Mutex
25 cmds map[string]*commandDesc
26}
27
28var registry = &cmdRegistry{cmds: make(map[string]*commandDesc)}
29
30func (r *cmdRegistry) addCommand(name, help string, factory func() command, main Main) {
31 r.Lock()
32 defer r.Unlock()
33 r.cmds[name] = &commandDesc{factory, main, help}
34}
35
36func (r *cmdRegistry) getCommand(name string) *commandDesc {
37 r.Lock()
38 defer r.Unlock()
39 return r.cmds[name]
40}
41
James Ring9d9489d2015-01-27 15:48:07 -080042// RegisterChild adds a new command to the registry that will be run
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080043// as a subprocess. It must be called before Dispatch or DispatchInTest is
44// called, typically from an init function.
45func RegisterChild(name, help string, main Main) {
46 factory := func() command { return newExecHandle(name) }
47 registry.addCommand(name, help, factory, main)
48}
49
50// RegisterFunction adds a new command to the registry that will be run
51// within the current process. It can be called at any time prior to an
52// attempt to use it.
53func RegisterFunction(name, help string, main Main) {
54 factory := func() command { return newFunctionHandle(name, main) }
55 registry.addCommand(name, help, factory, main)
56}
57
58// Help returns the help message for the specified command, or a list
59// of all commands if the command parameter is an empty string.
60func Help(command string) string {
61 return registry.help(command)
62}
63
64func (r *cmdRegistry) help(command string) string {
65 r.Lock()
66 defer r.Unlock()
67 if len(command) == 0 {
68 h := ""
69 for c, _ := range r.cmds {
70 h += c + ", "
71 }
72 return strings.TrimRight(h, ", ")
73 }
74 if c := r.cmds[command]; c != nil {
75 return command + ": " + c.help
76 }
77 return ""
78}
79
80const shellEntryPoint = "VEYRON_SHELL_HELPER_PROCESS_ENTRY_POINT"
81
82// IsModulesProcess returns true if this process is run using
83// the modules package.
84func IsModulesProcess() bool {
85 return os.Getenv(shellEntryPoint) != ""
86}
87
88// SetEntryPoint returns a slice of strings that is guaranteed to contain
89// one and only instance of the entry point environment variable set to the
90// supplied name value.
91func SetEntryPoint(env map[string]string, name string) []string {
92 newEnv := make([]string, 0, len(env)+1)
93 for k, v := range env {
94 if k == shellEntryPoint {
95 continue
96 }
97 newEnv = append(newEnv, k+"="+v)
98 }
99 return append(newEnv, shellEntryPoint+"="+name)
100}
101
102// DispatchInTest will execute the requested subproccess command from within
103// a unit test run as a subprocess.
104func DispatchInTest() {
105 if !IsTestHelperProcess() {
106 return
107 }
108 if err := registry.dispatch(); err != nil {
109 vlog.Fatalf("Failed: %s", err)
110 }
111 os.Exit(0)
112}
113
114// Dispatch will execute the requested subprocess command from a within a
115// a subprocess that is not a unit test. It will return without an error
116// if it is executed by a process that does not specify an entry point
117// in its environment.
Cosmos Nicolaou90610bd2014-12-02 22:31:04 -0800118//
119// func main() {
120// if modules.IsModulesProcess() {
121// if err := modules.Dispatch(); err != nil {
122// panic("error")
123// }
124// return
125// }
126// parent code...
127//
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800128func Dispatch() error {
129 if !IsModulesProcess() {
130 return nil
131 }
132 if IsTestHelperProcess() {
133 return fmt.Errorf("use DispatchInTest in unittests")
134 }
135 return registry.dispatch()
136}
137
Cosmos Nicolaou90610bd2014-12-02 22:31:04 -0800138// DispatchAndExit is like Dispatch except that it will call os.Exit(0)
139// when executed within a child process and the command succeeds, or panic
140// on encountering an error.
141//
142// func main() {
143// modules.DispatchAndExit()
144// parent code...
145//
146func DispatchAndExit() {
147 if !IsModulesProcess() {
148 return
149 }
150 if IsTestHelperProcess() {
151 panic("use DispatchInTest in unittests")
152 }
153 if err := registry.dispatch(); err != nil {
154 panic(fmt.Sprintf("unexpected error: %s", err))
155 }
156 os.Exit(0)
157}
158
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800159func (r *cmdRegistry) dispatch() error {
160 ch, err := vexec.GetChildHandle()
161 if err != nil {
162 // This is for debugging only. It's perfectly reasonable for this
163 // error to occur if the process is started by a means other
164 // than the exec library.
165 vlog.VI(1).Infof("failed to get child handle: %s", err)
166 }
167
168 // Only signal that the child is ready or failed if we successfully get
169 // a child handle. We most likely failed to get a child handle
170 // because the subprocess was run directly from the command line.
171 command := os.Getenv(shellEntryPoint)
172 if len(command) == 0 {
173 err := fmt.Errorf("Failed to find entrypoint %q", command)
174 if ch != nil {
175 ch.SetFailed(err)
176 }
177 return err
178 }
179
180 m := registry.getCommand(command)
181 if m == nil {
182 err := fmt.Errorf("%s: not registered", command)
183 if ch != nil {
184 ch.SetFailed(err)
185 }
186 return err
187 }
188
189 if ch != nil {
190 ch.SetReady()
191 }
192
193 go func(pid int) {
194 for {
195 _, err := os.FindProcess(pid)
196 if err != nil {
197 vlog.Fatalf("Looks like our parent exited: %v", err)
198 }
199 time.Sleep(time.Second)
200 }
201 }(os.Getppid())
202
203 args := append([]string{command}, flag.Args()...)
204 return m.main(os.Stdin, os.Stdout, os.Stderr, envSliceToMap(os.Environ()), args...)
205}
206
Bogdan Capritac7e72b62015-01-07 19:22:23 -0800207// WaitForEOF returns when a read on its io.Reader parameter returns io.EOF
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800208func WaitForEOF(stdin io.Reader) {
209 buf := [1024]byte{}
210 for {
211 if _, err := stdin.Read(buf[:]); err == io.EOF {
212 return
213 }
214 }
215}