diff --git a/internal/cli/run/run.go b/internal/cli/run/run.go index 7af40f4..bfeafd5 100644 --- a/internal/cli/run/run.go +++ b/internal/cli/run/run.go @@ -2,9 +2,6 @@ package run import ( "errors" - "os" - "os/signal" - "syscall" "github.com/alecthomas/kingpin" "github.com/apex/log" @@ -16,22 +13,6 @@ import ( "github.com/ooni/probe-cli/nettests" ) -// listenForSignals will listen for SIGINT and SIGTERM. When it receives those -// signals it will set isTerminatedAtomicInt to non-zero, which will cleanly -// shutdown the test logic. -// TODO refactor this to use a cancellable context.Context instead of a bool -// flag, probably as part of: https://github.com/ooni/probe-cli/issues/45 -func listenForSignals(ctx *ooni.Context) { - s := make(chan os.Signal, 1) - signal.Notify(s, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - go func() { - <-s - log.Info("caught a stop signal, shutting down cleanly") - ctx.Terminate() - }() -} - func runNettestGroup(tg string, ctx *ooni.Context, network *database.Network) error { group, ok := nettests.NettestGroups[tg] if !ok { @@ -46,7 +27,8 @@ func runNettestGroup(tg string, ctx *ooni.Context, network *database.Network) er return err } - listenForSignals(ctx) + ctx.ListenForSignals() + ctx.MaybeListenForStdinClosed() for i, nt := range group.Nettests { if ctx.IsTerminated() == true { log.Debugf("context is terminated, breaking") diff --git a/ooni.go b/ooni.go index 674f51d..0ea6205 100644 --- a/ooni.go +++ b/ooni.go @@ -3,7 +3,9 @@ package ooni import ( "io/ioutil" "os" + "os/signal" "sync/atomic" + "syscall" "github.com/apex/log" "github.com/ooni/probe-cli/config" @@ -51,6 +53,51 @@ func (c *Context) Terminate() { atomic.AddInt64(&c.isTerminatedAtomicInt, 1) } +// ListenForSignals will listen for SIGINT and SIGTERM. When it receives those +// signals it will set isTerminatedAtomicInt to non-zero, which will cleanly +// shutdown the test logic. +// +// TODO refactor this to use a cancellable context.Context instead of a bool +// flag, probably as part of: https://github.com/ooni/probe-cli/issues/45 +func (c *Context) ListenForSignals() { + s := make(chan os.Signal, 1) + signal.Notify(s, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-s + log.Info("caught a stop signal, shutting down cleanly") + c.Terminate() + }() +} + +// MaybeListenForStdinClosed will treat any error on stdin just +// like SIGTERM if and only if +// +// os.Getenv("OONI_STDIN_EOF_IMPLIES_SIGTERM") == "true" +// +// When this feature is enabled, a collateral effect is that we swallow +// whatever is passed to us on the standard input. +// +// See https://github.com/ooni/probe-cli/pull/111 for more info +// regarding the design of this functionality. +// +// TODO refactor this to use a cancellable context.Context instead of a bool +// flag, probably as part of: https://github.com/ooni/probe-cli/issues/45 +func (c *Context) MaybeListenForStdinClosed() { + if os.Getenv("OONI_STDIN_EOF_IMPLIES_SIGTERM") != "true" { + return + } + go func() { + defer c.Terminate() + defer log.Info("stdin closed, shutting down cleanly") + b := make([]byte, 1<<10) + for { + if _, err := os.Stdin.Read(b); err != nil { + return + } + } + }() +} + // Init the OONI manager func (c *Context) Init(softwareName, softwareVersion string) error { var err error