blob: 762d94f82f58f5f2d41ab13aca0c4918c92b1e35 [file] [log] [blame]
package modules
import (
"flag"
"fmt"
"io"
"os"
"strings"
"sync"
"time"
"v.io/core/veyron2/vlog"
vexec "v.io/core/veyron/lib/exec"
)
type commandDesc struct {
factory func() command
main Main
help string
}
type cmdRegistry struct {
sync.Mutex
cmds map[string]*commandDesc
}
var registry = &cmdRegistry{cmds: make(map[string]*commandDesc)}
func (r *cmdRegistry) addCommand(name, help string, factory func() command, main Main) {
r.Lock()
defer r.Unlock()
r.cmds[name] = &commandDesc{factory, main, help}
}
func (r *cmdRegistry) getCommand(name string) *commandDesc {
r.Lock()
defer r.Unlock()
return r.cmds[name]
}
// RegisterChild adds a new command to the registery that will be run
// as a subprocess. It must be called before Dispatch or DispatchInTest is
// called, typically from an init function.
func RegisterChild(name, help string, main Main) {
factory := func() command { return newExecHandle(name) }
registry.addCommand(name, help, factory, main)
}
// RegisterFunction adds a new command to the registry that will be run
// within the current process. It can be called at any time prior to an
// attempt to use it.
func RegisterFunction(name, help string, main Main) {
factory := func() command { return newFunctionHandle(name, main) }
registry.addCommand(name, help, factory, main)
}
// Help returns the help message for the specified command, or a list
// of all commands if the command parameter is an empty string.
func Help(command string) string {
return registry.help(command)
}
func (r *cmdRegistry) help(command string) string {
r.Lock()
defer r.Unlock()
if len(command) == 0 {
h := ""
for c, _ := range r.cmds {
h += c + ", "
}
return strings.TrimRight(h, ", ")
}
if c := r.cmds[command]; c != nil {
return command + ": " + c.help
}
return ""
}
const shellEntryPoint = "VEYRON_SHELL_HELPER_PROCESS_ENTRY_POINT"
// IsModulesProcess returns true if this process is run using
// the modules package.
func IsModulesProcess() bool {
return os.Getenv(shellEntryPoint) != ""
}
// SetEntryPoint returns a slice of strings that is guaranteed to contain
// one and only instance of the entry point environment variable set to the
// supplied name value.
func SetEntryPoint(env map[string]string, name string) []string {
newEnv := make([]string, 0, len(env)+1)
for k, v := range env {
if k == shellEntryPoint {
continue
}
newEnv = append(newEnv, k+"="+v)
}
return append(newEnv, shellEntryPoint+"="+name)
}
// DispatchInTest will execute the requested subproccess command from within
// a unit test run as a subprocess.
func DispatchInTest() {
if !IsTestHelperProcess() {
return
}
if err := registry.dispatch(); err != nil {
vlog.Fatalf("Failed: %s", err)
}
os.Exit(0)
}
// Dispatch will execute the requested subprocess command from a within a
// a subprocess that is not a unit test. It will return without an error
// if it is executed by a process that does not specify an entry point
// in its environment.
//
// func main() {
// if modules.IsModulesProcess() {
// if err := modules.Dispatch(); err != nil {
// panic("error")
// }
// return
// }
// parent code...
//
func Dispatch() error {
if !IsModulesProcess() {
return nil
}
if IsTestHelperProcess() {
return fmt.Errorf("use DispatchInTest in unittests")
}
return registry.dispatch()
}
// DispatchAndExit is like Dispatch except that it will call os.Exit(0)
// when executed within a child process and the command succeeds, or panic
// on encountering an error.
//
// func main() {
// modules.DispatchAndExit()
// parent code...
//
func DispatchAndExit() {
if !IsModulesProcess() {
return
}
if IsTestHelperProcess() {
panic("use DispatchInTest in unittests")
}
if err := registry.dispatch(); err != nil {
panic(fmt.Sprintf("unexpected error: %s", err))
}
os.Exit(0)
}
func (r *cmdRegistry) dispatch() error {
ch, err := vexec.GetChildHandle()
if err != nil {
// This is for debugging only. It's perfectly reasonable for this
// error to occur if the process is started by a means other
// than the exec library.
vlog.VI(1).Infof("failed to get child handle: %s", err)
}
// Only signal that the child is ready or failed if we successfully get
// a child handle. We most likely failed to get a child handle
// because the subprocess was run directly from the command line.
command := os.Getenv(shellEntryPoint)
if len(command) == 0 {
err := fmt.Errorf("Failed to find entrypoint %q", command)
if ch != nil {
ch.SetFailed(err)
}
return err
}
m := registry.getCommand(command)
if m == nil {
err := fmt.Errorf("%s: not registered", command)
if ch != nil {
ch.SetFailed(err)
}
return err
}
if ch != nil {
ch.SetReady()
}
go func(pid int) {
for {
_, err := os.FindProcess(pid)
if err != nil {
vlog.Fatalf("Looks like our parent exited: %v", err)
}
time.Sleep(time.Second)
}
}(os.Getppid())
args := append([]string{command}, flag.Args()...)
return m.main(os.Stdin, os.Stdout, os.Stderr, envSliceToMap(os.Environ()), args...)
}
// WaitForEof returns when a read on its io.Reader parameter returns io.EOF
func WaitForEOF(stdin io.Reader) {
buf := [1024]byte{}
for {
if _, err := stdin.Read(buf[:]); err == io.EOF {
return
}
}
}