blob: 22970eee5b5060a8fe6d6a9afaa21b8b74599c51 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modules
import (
"flag"
"fmt"
"io"
"os"
"strings"
"sync"
"time"
"v.io/x/lib/vlog"
vexec "v.io/x/ref/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]
}
func (r *cmdRegistry) getExternalCommand(name string) *commandDesc {
h := newExecHandleForExternalCommand(name)
return &commandDesc{
factory: func() command { return h },
}
}
// RegisterChild adds a new command to the registry 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 = "V23_SHELL_HELPER_PROCESS_ENTRY_POINT"
// IsModulesChildProcess returns true if this process was started by
// the modules package.
func IsModulesChildProcess() 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)
}
// Dispatch will execute the requested subprocess command from a within a
// a subprocess. 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.IsModulesChildProcess() {
// if err := modules.Dispatch(); err != nil {
// panic("error")
// }
// eturn
// }
// parent code...
//
func Dispatch() error {
if !IsModulesChildProcess() {
return nil
}
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 !IsModulesChildProcess() {
return
}
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())
flag.Parse()
return m.main(os.Stdin, os.Stdout, os.Stderr, envSliceToMap(os.Environ()), flag.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
}
}
}