Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium 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 | |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 5 | package modules |
| 6 | |
| 7 | import ( |
| 8 | "flag" |
| 9 | "fmt" |
| 10 | "io" |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 11 | "io/ioutil" |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 12 | "os" |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 13 | "path/filepath" |
| 14 | "runtime" |
| 15 | "strconv" |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 16 | "time" |
| 17 | |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 18 | "v.io/x/ref/internal/logger" |
Jiri Simsa | ffceefa | 2015-02-28 11:03:34 -0800 | [diff] [blame] | 19 | vexec "v.io/x/ref/lib/exec" |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 20 | ) |
| 21 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 22 | // Program is a symbolic representation of a registered Main function. |
| 23 | type Program string |
| 24 | |
| 25 | type programInfo struct { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 26 | main Main |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 27 | factory func() *execHandle |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 28 | } |
| 29 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 30 | type programRegistry struct { |
| 31 | programs []*programInfo |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 32 | } |
| 33 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 34 | var registry = new(programRegistry) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 35 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 36 | func (r *programRegistry) addProgram(main Main, description string) Program { |
| 37 | prog := strconv.Itoa(len(r.programs)) |
| 38 | factory := func() *execHandle { return newExecHandle(prog, description) } |
| 39 | r.programs = append(r.programs, &programInfo{main, factory}) |
| 40 | return Program(prog) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 41 | } |
| 42 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 43 | func (r *programRegistry) getProgram(prog Program) *programInfo { |
| 44 | index, err := strconv.Atoi(string(prog)) |
| 45 | if err != nil || index < 0 || index >= len(r.programs) { |
| 46 | return nil |
| 47 | } |
| 48 | return r.programs[index] |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 49 | } |
| 50 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 51 | func (r *programRegistry) getExternalProgram(prog Program) *programInfo { |
| 52 | h := newExecHandleExternal(string(prog)) |
| 53 | return &programInfo{ |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 54 | factory: func() *execHandle { return h }, |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 55 | } |
| 56 | } |
| 57 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 58 | func (r *programRegistry) String() string { |
| 59 | var s string |
| 60 | for _, info := range r.programs { |
| 61 | h := info.factory() |
| 62 | s += fmt.Sprintf("%s: %s\n", h.entryPoint, h.desc) |
| 63 | } |
| 64 | return s |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 65 | } |
| 66 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 67 | // Register adds a new program to the registry that will be run as a subprocess. |
| 68 | // It must be called before Dispatch is called, typically from an init function. |
| 69 | func Register(main Main, description string) Program { |
| 70 | if _, file, line, ok := runtime.Caller(1); ok { |
| 71 | description = fmt.Sprintf("%s:%d %s", shortFile(file), line, description) |
| 72 | } |
| 73 | return registry.addProgram(main, description) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 74 | } |
| 75 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 76 | // shortFile returns the last 3 components of the given file name. |
| 77 | func shortFile(file string) string { |
| 78 | var short string |
| 79 | for i := 0; i < 3; i++ { |
| 80 | short = filepath.Join(filepath.Base(file), short) |
| 81 | file = filepath.Dir(file) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 82 | } |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 83 | return short |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 84 | } |
| 85 | |
Asim Shankar | 59b8b69 | 2015-03-30 01:23:36 -0700 | [diff] [blame] | 86 | const shellEntryPoint = "V23_SHELL_HELPER_PROCESS_ENTRY_POINT" |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 87 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 88 | // IsChildProcess returns true if this process was started by the modules |
| 89 | // package. |
| 90 | func IsChildProcess() bool { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 91 | return os.Getenv(shellEntryPoint) != "" |
| 92 | } |
| 93 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 94 | // Dispatch executes the requested subprocess program from within a subprocess. |
| 95 | // Returns an error if it is executed by a process that does not specify an |
| 96 | // entry point in its environment. |
Cosmos Nicolaou | ec6ed65 | 2015-02-13 14:31:06 -0800 | [diff] [blame] | 97 | func Dispatch() error { |
Cosmos Nicolaou | ec6ed65 | 2015-02-13 14:31:06 -0800 | [diff] [blame] | 98 | return registry.dispatch() |
| 99 | } |
| 100 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 101 | // DispatchAndExitIfChild is a convenience function with three possible results: |
| 102 | // * os.Exit(0) if called within a child process, and the dispatch succeeds. |
| 103 | // * os.Exit(1) if called within a child process, and the dispatch fails. |
| 104 | // * return with no side-effects, if not called within a child process. |
| 105 | func DispatchAndExitIfChild() { |
| 106 | if IsChildProcess() { |
| 107 | if err := Dispatch(); err != nil { |
| 108 | fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err) |
| 109 | os.Exit(1) |
| 110 | } |
| 111 | os.Exit(0) |
Cosmos Nicolaou | 90610bd | 2014-12-02 22:31:04 -0800 | [diff] [blame] | 112 | } |
Cosmos Nicolaou | 90610bd | 2014-12-02 22:31:04 -0800 | [diff] [blame] | 113 | } |
| 114 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 115 | func (r *programRegistry) dispatch() error { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 116 | ch, err := vexec.GetChildHandle() |
| 117 | if err != nil { |
| 118 | // This is for debugging only. It's perfectly reasonable for this |
| 119 | // error to occur if the process is started by a means other |
| 120 | // than the exec library. |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 121 | logger.Global().VI(1).Infof("failed to get child handle: %s", err) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 122 | } |
| 123 | |
| 124 | // Only signal that the child is ready or failed if we successfully get |
| 125 | // a child handle. We most likely failed to get a child handle |
| 126 | // because the subprocess was run directly from the command line. |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 127 | prog := os.Getenv(shellEntryPoint) |
| 128 | if prog == "" { |
| 129 | err := fmt.Errorf("Failed to find entrypoint %q", prog) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 130 | if ch != nil { |
| 131 | ch.SetFailed(err) |
| 132 | } |
| 133 | return err |
| 134 | } |
| 135 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 136 | m := registry.getProgram(Program(prog)) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 137 | if m == nil { |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 138 | err := fmt.Errorf("%s: not registered\n%s", prog, registry.String()) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 139 | if ch != nil { |
| 140 | ch.SetFailed(err) |
| 141 | } |
| 142 | return err |
| 143 | } |
| 144 | |
| 145 | if ch != nil { |
| 146 | ch.SetReady() |
| 147 | } |
| 148 | |
| 149 | go func(pid int) { |
| 150 | for { |
| 151 | _, err := os.FindProcess(pid) |
| 152 | if err != nil { |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 153 | logger.Global().Fatalf("Looks like our parent exited: %v", err) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 154 | } |
| 155 | time.Sleep(time.Second) |
| 156 | } |
| 157 | }(os.Getppid()) |
| 158 | |
Suharsh Sivakumar | 9d17e4a | 2015-02-02 22:42:16 -0800 | [diff] [blame] | 159 | flag.Parse() |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 160 | return m.main(EnvFromOS(), flag.Args()...) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 161 | } |
| 162 | |
Bogdan Caprita | c7e72b6 | 2015-01-07 19:22:23 -0800 | [diff] [blame] | 163 | // WaitForEOF returns when a read on its io.Reader parameter returns io.EOF |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 164 | func WaitForEOF(r io.Reader) { |
| 165 | io.Copy(ioutil.Discard, r) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 166 | } |