lib: replace most uses of modules pkg
(mostly device mgr)
MultiPart: 1/2
Change-Id: Ib3f19287dc721d06f9dafbf92179153067987713
diff --git a/gosh/.api b/gosh/.api
index e99f9da..687f358 100644
--- a/gosh/.api
+++ b/gosh/.api
@@ -1,6 +1,6 @@
pkg gosh, func Call(string, ...interface{}) error
-pkg gosh, func MaybeRunFnAndExit()
-pkg gosh, func MaybeWatchParent()
+pkg gosh, func InitChildMain()
+pkg gosh, func InitMain()
pkg gosh, func NewBufferedPipe() io.ReadWriteCloser
pkg gosh, func NewShell(Opts) *Shell
pkg gosh, func NopWriteCloser(io.Writer) io.WriteCloser
@@ -8,7 +8,6 @@
pkg gosh, func Run(func() int) int
pkg gosh, func SendReady()
pkg gosh, func SendVars(map[string]string)
-pkg gosh, func WatchParent()
pkg gosh, method (*Cmd) AddStderrWriter(io.WriteCloser)
pkg gosh, method (*Cmd) AddStdoutWriter(io.WriteCloser)
pkg gosh, method (*Cmd) AwaitReady()
@@ -45,7 +44,9 @@
pkg gosh, type Cmd struct
pkg gosh, type Cmd struct, Args []string
pkg gosh, type Cmd struct, Err error
+pkg gosh, type Cmd struct, ExitAfter time.Duration
pkg gosh, type Cmd struct, ExitErrorIsOk bool
+pkg gosh, type Cmd struct, IgnoreParentExit bool
pkg gosh, type Cmd struct, OutputDir string
pkg gosh, type Cmd struct, Path string
pkg gosh, type Cmd struct, PropagateOutput bool
diff --git a/gosh/child.go b/gosh/child.go
index 50725ee..95c47ef 100644
--- a/gosh/child.go
+++ b/gosh/child.go
@@ -44,26 +44,38 @@
send(msg{Type: typeVars, Vars: vars})
}
-// WatchParent starts a goroutine that periodically checks whether the parent
-// process has exited and, if so, kills the current process.
-func WatchParent() {
- go func() {
- for {
- if os.Getppid() == 1 {
- log.Fatal("parent process has exited")
- }
- time.Sleep(time.Second)
+// watchParent periodically checks whether the parent process has exited and, if
+// so, kills the current process. Meant to be run in a goroutine.
+func watchParent() {
+ for {
+ if os.Getppid() == 1 {
+ log.Fatal("gosh: parent process has exited")
}
- }()
+ time.Sleep(time.Second)
+ }
}
-// MaybeWatchParent calls WatchParent iff this process was spawned by a
-// gosh.Shell in the parent process.
-func MaybeWatchParent() {
- if os.Getenv(envSpawnedByShell) != "" {
- // Our child processes should see envSpawnedByShell iff they were spawned by
- // a gosh.Shell in this process.
- os.Unsetenv(envSpawnedByShell)
- WatchParent()
+// exitAfter kills the current process once the given duration has elapsed.
+// Meant to be run in a goroutine.
+func exitAfter(d time.Duration) {
+ time.Sleep(d)
+ log.Fatalf("gosh: timed out after %v", d)
+}
+
+// InitChildMain must be called early on in main() of child processes. It spawns
+// goroutines to kill the current process when certain conditions are met, per
+// Cmd.IgnoreParentExit and Cmd.ExitAfter.
+func InitChildMain() {
+ if os.Getenv(envWatchParent) != "" {
+ os.Unsetenv(envWatchParent)
+ go watchParent()
+ }
+ if os.Getenv(envExitAfter) != "" {
+ d, err := time.ParseDuration(envExitAfter)
+ if err != nil {
+ panic(err)
+ }
+ os.Unsetenv(envExitAfter)
+ go exitAfter(d)
}
}
diff --git a/gosh/cmd.go b/gosh/cmd.go
index e80eff0..6ed9682 100644
--- a/gosh/cmd.go
+++ b/gosh/cmd.go
@@ -36,8 +36,19 @@
Path string
// Vars is the map of env vars for this Cmd.
Vars map[string]string
- // Args is the list of args for this Cmd, not including the path.
+ // Args is the list of args for this Cmd, starting with the resolved path.
+ // Note, we set Args[0] to the resolved path (rather than the user-specified
+ // name) so that a command started by Shell can reliably determine the path to
+ // its executable.
Args []string
+ // IgnoreParentExit, if true, makes it so the child process does not exit when
+ // its parent exits. Only takes effect if the child process was spawned via
+ // Shell.Fn or Shell.Main, or explicitly calls InitChildMain.
+ IgnoreParentExit bool
+ // ExitAfter, if non-zero, specifies that the child process should exit after
+ // the given duration has elapsed. Only takes effect if the child process was
+ // spawned via Shell.Fn or Shell.Main, or explicitly calls InitChildMain.
+ ExitAfter time.Duration
// PropagateOutput is inherited from Shell.Opts.PropagateChildOutput.
PropagateOutput bool
// OutputDir is inherited from Shell.Opts.ChildOutputDir.
@@ -228,7 +239,7 @@
c := &Cmd{
Path: path,
Vars: vars,
- Args: args,
+ Args: append([]string{path}, args...),
sh: sh,
c: &exec.Cmd{},
cond: sync.NewCond(&sync.Mutex{}),
@@ -397,16 +408,15 @@
}
func (c *Cmd) clone() (*Cmd, error) {
- vars := make(map[string]string, len(c.Vars))
- for k, v := range c.Vars {
- vars[k] = v
- }
+ vars := copyMap(c.Vars)
args := make([]string, len(c.Args))
copy(args, c.Args)
- res, err := newCmdInternal(c.sh, vars, c.Path, args)
+ res, err := newCmdInternal(c.sh, vars, c.Path, args[1:])
if err != nil {
return nil, err
}
+ res.IgnoreParentExit = c.IgnoreParentExit
+ res.ExitAfter = c.ExitAfter
res.PropagateOutput = c.PropagateOutput
res.OutputDir = c.OutputDir
res.ExitErrorIsOk = c.ExitErrorIsOk
@@ -475,7 +485,7 @@
}
// TODO(sadovsky): Maybe wrap every child process with a "supervisor" process
-// that calls WatchParent().
+// that calls InitChildMain.
func (c *Cmd) start() error {
if c.calledStart {
@@ -491,8 +501,19 @@
}
// Configure the command.
c.c.Path = c.Path
- c.c.Env = mapToSlice(c.Vars)
- c.c.Args = append([]string{c.Path}, c.Args...)
+ vars := copyMap(c.Vars)
+ if c.IgnoreParentExit {
+ delete(vars, envWatchParent)
+ } else {
+ vars[envWatchParent] = "1"
+ }
+ if c.ExitAfter == 0 {
+ delete(vars, envExitAfter)
+ } else {
+ vars[envExitAfter] = c.ExitAfter.String()
+ }
+ c.c.Env = mapToSlice(vars)
+ c.c.Args = c.Args
if c.Stdin != "" {
if c.stdinWriteCloser != nil {
return errors.New("gosh: cannot both set Stdin and call StdinPipe")
diff --git a/gosh/env_util.go b/gosh/env_util.go
index 3e7b815..a9966d7 100644
--- a/gosh/env_util.go
+++ b/gosh/env_util.go
@@ -67,3 +67,8 @@
}
return res
}
+
+// copyMap returns a copy of the given map.
+func copyMap(m map[string]string) map[string]string {
+ return mergeMaps(m)
+}
diff --git a/gosh/internal/gosh_example/main.go b/gosh/internal/gosh_example/main.go
index bde8f86..d79d680 100644
--- a/gosh/internal/gosh_example/main.go
+++ b/gosh/internal/gosh_example/main.go
@@ -59,7 +59,7 @@
}
func main() {
- gosh.MaybeRunFnAndExit()
+ gosh.InitMain()
ExampleCmds()
ExampleFns()
ExampleShellMain()
diff --git a/gosh/internal/gosh_example_client/main.go b/gosh/internal/gosh_example_client/main.go
index 6962d22..1568da5 100644
--- a/gosh/internal/gosh_example_client/main.go
+++ b/gosh/internal/gosh_example_client/main.go
@@ -14,7 +14,7 @@
var addr = flag.String("addr", "localhost:8080", "server addr")
func main() {
- gosh.MaybeWatchParent()
+ gosh.InitChildMain()
flag.Parse()
lib.Get(*addr)
}
diff --git a/gosh/internal/gosh_example_server/main.go b/gosh/internal/gosh_example_server/main.go
index f7d96c2..00968a0 100644
--- a/gosh/internal/gosh_example_server/main.go
+++ b/gosh/internal/gosh_example_server/main.go
@@ -10,6 +10,6 @@
)
func main() {
- gosh.MaybeWatchParent()
+ gosh.InitChildMain()
lib.Serve()
}
diff --git a/gosh/shell.go b/gosh/shell.go
index 02ee145..37d3174 100644
--- a/gosh/shell.go
+++ b/gosh/shell.go
@@ -20,6 +20,7 @@
"log"
"math/rand"
"os"
+ "os/exec"
"os/signal"
"path"
"path/filepath"
@@ -31,16 +32,25 @@
const (
envBinDir = "GOSH_BIN_DIR"
envChildOutputDir = "GOSH_CHILD_OUTPUT_DIR"
+ envExitAfter = "GOSH_EXIT_AFTER"
envInvocation = "GOSH_INVOCATION"
- envSpawnedByShell = "GOSH_SPAWNED_BY_SHELL"
+ envWatchParent = "GOSH_WATCH_PARENT"
)
var (
- errAlreadyCalledCleanup = errors.New("gosh: already called Shell.Cleanup")
- errDidNotCallMaybeRunFnAndExit = errors.New("gosh: did not call Shell.MaybeRunFnAndExit")
- errDidNotCallNewShell = errors.New("gosh: did not call gosh.NewShell")
+ errAlreadyCalledCleanup = errors.New("gosh: already called Shell.Cleanup")
+ errDidNotCallInitMain = errors.New("gosh: did not call gosh.InitMain")
+ errDidNotCallNewShell = errors.New("gosh: did not call gosh.NewShell")
)
+// TODO(sadovsky):
+// - Eliminate Shell.Main, since it's easy enough to use Shell.Fn and set the
+// returned Cmd's Args.
+// - Rename Shell.Fn to Shell.FuncCmd, Fn to Func, Register to RegisterFunc, and
+// Call to CallFunc.
+// - Eliminate gosh.Run.
+// - Revisit whether to have Cmd.Shutdown.
+
// Shell represents a shell. Not thread-safe.
type Shell struct {
// Err is the most recent error from this Shell or any of its Cmds (may be
@@ -288,7 +298,8 @@
if vars == nil {
vars = make(map[string]string)
}
- vars[envSpawnedByShell] = "1"
+ // TODO(sadovsky): Copy os.Environ() into sh.Vars in NewShell, and clear
+ // envWatchParent and envExitAfter if they're set.
c, err := newCmd(sh, mergeMaps(sliceToMap(os.Environ()), sh.Vars, vars), name, append(args, sh.Args...)...)
if err != nil {
return nil, err
@@ -298,25 +309,34 @@
return c, nil
}
+var executablePath = os.Args[0]
+
+func init() {
+ // If exec.LookPath fails, hope for the best.
+ if lp, err := exec.LookPath(executablePath); err != nil {
+ executablePath = lp
+ }
+}
+
func (sh *Shell) fn(fn *Fn, args ...interface{}) (*Cmd, error) {
- // Safeguard against the developer forgetting to call MaybeRunFnAndExit, which
- // could lead to infinite recursion.
- if !calledMaybeRunFnAndExit {
- return nil, errDidNotCallMaybeRunFnAndExit
+ // Safeguard against the developer forgetting to call InitMain, which could
+ // lead to infinite recursion.
+ if !calledInitMain {
+ return nil, errDidNotCallInitMain
}
b, err := encInvocation(fn.name, args...)
if err != nil {
return nil, err
}
vars := map[string]string{envInvocation: string(b)}
- return sh.cmd(vars, os.Args[0])
+ return sh.cmd(vars, executablePath)
}
func (sh *Shell) main(fn *Fn, args ...string) (*Cmd, error) {
- // Safeguard against the developer forgetting to call MaybeRunFnAndExit, which
- // could lead to infinite recursion.
- if !calledMaybeRunFnAndExit {
- return nil, errDidNotCallMaybeRunFnAndExit
+ // Safeguard against the developer forgetting to call InitMain, which could
+ // lead to infinite recursion.
+ if !calledInitMain {
+ return nil, errDidNotCallInitMain
}
// Check that fn has the required signature.
t := fn.value.Type()
@@ -328,7 +348,7 @@
return nil, err
}
vars := map[string]string{envInvocation: string(b)}
- return sh.cmd(vars, os.Args[0], args...)
+ return sh.cmd(vars, executablePath, args...)
}
func (sh *Shell) wait() error {
@@ -534,22 +554,19 @@
////////////////////////////////////////
// Public utilities
-var calledMaybeRunFnAndExit = false
+var calledInitMain = false
-// MaybeRunFnAndExit must be called first thing in main() or TestMain(), before
-// flags are parsed. In the parent process, it returns immediately with no
-// effect. In a child process for a Shell.Fn() or Shell.Main() command, it runs
-// the specified function, then exits.
-func MaybeRunFnAndExit() {
- calledMaybeRunFnAndExit = true
+// InitMain must be called early on in main(), before flags are parsed. In the
+// parent process, it returns immediately with no effect. In a child process for
+// a Shell.Fn or Shell.Main command, it runs the specified function, then exits.
+func InitMain() {
+ calledInitMain = true
s := os.Getenv(envInvocation)
if s == "" {
return
}
os.Unsetenv(envInvocation)
- // Call MaybeWatchParent rather than WatchParent so that envSpawnedByShell
- // gets cleared.
- MaybeWatchParent()
+ InitChildMain()
name, args, err := decInvocation(s)
if err != nil {
log.Fatal(err)
@@ -560,10 +577,10 @@
os.Exit(0)
}
-// Run calls MaybeRunFnAndExit(), then returns run(). Exported so that TestMain
-// functions can simply call os.Exit(gosh.Run(m.Run)).
+// Run calls InitMain, then returns run(). Exported so that TestMain functions
+// can simply call os.Exit(gosh.Run(m.Run)).
func Run(run func() int) int {
- MaybeRunFnAndExit()
+ InitMain()
return run()
}