github.com/thediveo/gons@v0.9.9/reexec/reexec.go (about)

     1  // Reexec support; because the Golang runtime sucks at fork() and switching
     2  // Linux kernel namespaces.
     3  
     4  // Copyright 2020 Harald Albrecht.
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //    http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package reexec
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"os/exec"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/thediveo/gons"
    31  	"github.com/thediveo/gons/reexec/internal/testsupport"
    32  )
    33  
    34  // Breaks the vicious cycle of recursive imports which would otherwise raise
    35  // its ugly head: this way, gons/reexec/testing can call RunAction while under
    36  // test, without having to import us. Instead, we and gons/reexec/testing both
    37  // import gons/reexec/internal/testsupport, which in turn doesn't import
    38  // anything which would cause import cycles.
    39  func init() {
    40  	testsupport.RunAction = RunAction
    41  }
    42  
    43  // magicEnvVar defines the name of the environment variable which triggers a
    44  // specific registered action to be run when an application using the reexec
    45  // package forks and restarts itself, typically to switch into different
    46  // namespaces.
    47  const magicEnvVar = "gons_reexec_action"
    48  
    49  // reexecEnabled enables fork/restarts only for applications which are
    50  // reexec-aware by calling CheckAction() as early as possible in their
    51  // main()s. Applications (indirectly) using reexec and triggering some
    52  // function that needs fork/re-execution, but which have not called
    53  // CheckAction() will panic instead of forking and re-executing themselves.
    54  // This is a safeguard measure to cause havoc by unexpected clone restarts.
    55  var reexecEnabled = false
    56  
    57  // CheckAction checks if an application using reexec has been forked and
    58  // re-executed in order to switch namespaces in the clone. If we're in a
    59  // re-execution, then this function won't return, but instead run the
    60  // scheduled reexec functionality. Please do not confuse re-execution with
    61  // royalists and round-heads.
    62  func CheckAction() {
    63  	if RunAction() {
    64  		osExit(0)
    65  	}
    66  }
    67  
    68  // For the sake of code coverage ;)
    69  var osExit = os.Exit
    70  
    71  // RunAction checks if an application using the gons/reexec package has been
    72  // forked and re-executed as a copy of itself. If this is the case, then the
    73  // action specified for re-execution is run, and true returned. If this isn't
    74  // the case, because this is the parent process and not a re-executed child,
    75  // then no action is run, and false returned instead.
    76  func RunAction() (action bool) {
    77  	// Did we had a problem during reentry...?
    78  	if err := gons.Status(); err != nil {
    79  		panic(err)
    80  	}
    81  	if actionname := os.Getenv(magicEnvVar); actionname != "" {
    82  		// Only run the requested action, and then exit. The caller will never
    83  		// gain back control in this case.
    84  		action, ok := actions[actionname]
    85  		if !ok {
    86  			panic(fmt.Sprintf(
    87  				"unregistered gons/reexec re-execution action %q", actionname))
    88  		}
    89  		action()
    90  		return true
    91  	}
    92  	// Enable fork/re-execution only for the parent process of the application
    93  	// using reexec, but not in the re-executed child.
    94  	reexecEnabled = true
    95  	return
    96  }
    97  
    98  // Namespace describes a Linux kernel namespace into which a forked and
    99  // re-executed child process should switch: its type and a path to reference
   100  // it. The type can optionally preceded by a bang "!" which indicates that the
   101  // corresponding path should be opened before any namespace switching occurs;
   102  // without a bang, the path will be opened only right when this namespace
   103  // should be switched. Thus, the path will depend on the current set of
   104  // namespaces, not the initial set when calling ForkReexec().
   105  type Namespace struct {
   106  	Type string // namespace type, such as "net", "mnt", ...
   107  	Path string // path reference to namespace in filesystem.
   108  }
   109  
   110  // ReexecAction describes a named action to be re-executed in a forked child
   111  // copy of this process, together with its mandatory parameters and options.
   112  type ReexecAction struct {
   113  	ActionName  string      // name of action to run in re-executed child.
   114  	Namespaces  []Namespace // namespaces to switch into before executing action.
   115  	Param       interface{} // optional parameter to be sent to the action.
   116  	Result      interface{} // where to put the action result to.
   117  	Environment []string    // optional environment variables to pass to re-executed child.
   118  }
   119  
   120  // ReexecActionOption is an option function configuring some aspect of a
   121  // ReexecAction object. It can be passed to NewReexecAction when creating a
   122  // named action to be re-executed in a forked child copy of our process.
   123  type ReexecActionOption func(*ReexecAction)
   124  
   125  // NewReexecAction returns a new ReexecAction object, tailored according to the
   126  // additionally specified options.
   127  func NewReexecAction(actionname string, options ...ReexecActionOption) *ReexecAction {
   128  	a := &ReexecAction{
   129  		ActionName: actionname,
   130  	}
   131  	for _, opt := range options {
   132  		opt(a)
   133  	}
   134  	return a
   135  }
   136  
   137  // RunExecAction runs the named action in a forked and re-executed child copy of
   138  // this process with the specified options and returns only after the action in
   139  // the child has finished.
   140  func RunReexecAction(actionname string, options ...ReexecActionOption) error {
   141  	return NewReexecAction(actionname, options...).Run()
   142  }
   143  
   144  // Namespaces specifies the namespaces an (re-executed) named action is to be
   145  // run in.
   146  func Namespaces(namespaces []Namespace) ReexecActionOption {
   147  	return func(a *ReexecAction) {
   148  		a.Namespaces = namespaces
   149  	}
   150  }
   151  
   152  // Param specifies an (optional) parameter to be sent to the (re-executed) named
   153  // action.
   154  func Param(param interface{}) ReexecActionOption {
   155  	return func(a *ReexecAction) {
   156  		a.Param = param
   157  	}
   158  }
   159  
   160  // Result specifies where to place the result received from the (re-executed)
   161  // named action.
   162  func Result(result interface{}) ReexecActionOption {
   163  	return func(a *ReexecAction) {
   164  		a.Result = result
   165  	}
   166  }
   167  
   168  // Environment specifies (optional) environment variables passed to the
   169  // (re-executed) named action.
   170  func Environment(environment []string) ReexecActionOption {
   171  	return func(a *ReexecAction) {
   172  		a.Environment = environment
   173  	}
   174  }
   175  
   176  // Run restarts the application using reexec and thus as a new child process,
   177  // then immediately executes only the this named action. It optionally passes a
   178  // parameter (as JSON) and/or additional environment variables to the child. The
   179  // output of the child gets deserialized as JSON into the passed result element.
   180  // The call only returns after the child process has terminated.
   181  func (a *ReexecAction) Run() (err error) {
   182  	// Safeguard against applications trying to run more elaborate discoveries
   183  	// and are forgetting to enable the required re-execution of themselves by
   184  	// calling CheckAction() very early in their runtime live.
   185  	if !reexecEnabled {
   186  		if actionname := os.Getenv(magicEnvVar); actionname == "" {
   187  			panic("gons/reexec: ReexecAction.Run: application does not support " +
   188  				"forking and restarting, needs to call reexec.CheckAction() " +
   189  				"first before running discovery")
   190  		}
   191  		panic("gons/reexec: ReexecAction.Run: tried to re-execute in " +
   192  			"already re-executing child process")
   193  	}
   194  	if _, ok := actions[a.ActionName]; !ok {
   195  		panic("gons/reexec: ReexecAction.Run: attempting to re-execute into " +
   196  			"unregistered action \"" + a.ActionName + "\"")
   197  	}
   198  	// If testing has been enabled, then make sure to pass the necessary
   199  	// parameters on to our child processes, as it will (have to) use a
   200  	// TestMain and our "enhanced" gons.reexec.testing.M.
   201  	//
   202  	// When under test, we need to run tests, as otherwise no coverage profile
   203  	// data would be written (if requested by passing an non-empty
   204  	// "-test.coverprofile"), so we make sure to run an empty set of tests;
   205  	// this avoids the same tests getting run multiple times ... and
   206  	// eventually panicking when trying to re-execute again.
   207  	//
   208  	// If coverage propfiling is enabled, then for each child we allocate a
   209  	// separate child coverage profile data file, which we will have to merge
   210  	// later with our main coverage profile of this process.
   211  	testargs := testsupport.TestingArgs()
   212  	// Prepare a fork/re-execution of ourselves, which then switches itself
   213  	// into the required namespace(s) before its Go runtime spins up.
   214  	forkchild := exec.Command("/proc/self/exe", testargs...)
   215  	forkchild.Env = append(os.Environ(), a.Environment...)
   216  	// Pass the namespaces the fork/child should switch into via the
   217  	// soon-to-be child's environment. The sequence of the namespaces slice is
   218  	// kept, so that the caller has control of the exact sequence of namespace
   219  	// switches.
   220  	ooorder := []string{} // cSpell:ignore ooorder
   221  	for _, ns := range a.Namespaces {
   222  		ooorder = append(ooorder, ns.Type)
   223  		forkchild.Env = append(forkchild.Env,
   224  			fmt.Sprintf("gons_%s=%s", strings.TrimPrefix(ns.Type, "!"), ns.Path))
   225  	}
   226  	forkchild.Env = append(forkchild.Env, "gons_order="+strings.Join(ooorder, ","))
   227  	// Finally set the action to run on restarting our fork, and then try to
   228  	// start our re-executed fork child...
   229  	forkchild.Env = append(forkchild.Env, magicEnvVar+"="+a.ActionName)
   230  	// If necessary, prepare a JSON encode to send input data to the child
   231  	// process via the child's stdin.
   232  	var encoder *json.Encoder
   233  	if a.Param != nil {
   234  		childin, err := forkchild.StdinPipe()
   235  		if err != nil {
   236  			panic(fmt.Sprintf(
   237  				"gons/reexec: ReexecAction.Run: cannot prepare for restarting my fork, reason: %s",
   238  				err.Error()))
   239  		}
   240  		defer childin.Close()
   241  		encoder = json.NewEncoder(childin)
   242  	}
   243  	// Get the stdout pipe from the child.
   244  	childout, err := forkchild.StdoutPipe()
   245  	if err != nil {
   246  		panic(fmt.Sprintf(
   247  			"gons/reexec: ReexecAction.Run: cannot prepare for restarting my fork, reason: %s",
   248  			err.Error()))
   249  	}
   250  	defer childout.Close()
   251  	// Get the stderr pipe from the child and collect any data we might receive.
   252  	// Unfortunately, we can't use the buffer writer directly without further
   253  	// measures as this creates a race condition in those situations where we
   254  	// need to kill the child process: we need to know when the stderr pipe has
   255  	// been closed.
   256  	var childerr bytes.Buffer
   257  	errpipe, err := forkchild.StderrPipe()
   258  	if err != nil {
   259  		panic(fmt.Sprintf(
   260  			"gons/reexec: ReexecAction.Run: cannot prepare for restarting my fork, reason: %s",
   261  			err.Error()))
   262  	}
   263  	errdone := make(chan struct{}, 1)
   264  	go func() {
   265  		defer close(errdone)
   266  		io.Copy(&childerr, errpipe)
   267  	}()
   268  	decoder := json.NewDecoder(childout)
   269  	if err := forkchild.Start(); err != nil {
   270  		panic("gons/reexec: ReexecAction.Run: cannot restart a fork of myself")
   271  	}
   272  	// Sent the optional parameter, if any...
   273  	var encodererr error
   274  	if encoder != nil {
   275  		encodererr = encoder.Encode(a.Param)
   276  	}
   277  	// Decode the result as it flows in. Keep any error for later. Skip this
   278  	// step if we had an encoder error already, as the action won't have got its
   279  	// paremeters correctly.
   280  	var decodererr error
   281  	if encodererr == nil {
   282  		decodererr = decoder.Decode(a.Result)
   283  	}
   284  	// Either wait for the child to automatically terminate within a short
   285  	// grace period after we deserialized its result output, or kill it the
   286  	// hard way if it can't terminate in time.
   287  	done := make(chan error, 1)
   288  	go func() {
   289  		done <- forkchild.Wait()
   290  	}()
   291  	select {
   292  	case err = <-done:
   293  	case <-time.After(1 * time.Second):
   294  		_ = forkchild.Process.Kill()
   295  	}
   296  	// Wait for the stderr pipe to properly wind down, so we got all that there
   297  	// is to get.
   298  	<-errdone
   299  	// Any child stderr output takes precedence over decoder errors, as when the
   300  	// child panics, then that is of more importance than any hiccup the result
   301  	// decoder encounters due to the child's problems. However, any encoder
   302  	// error takes it all...
   303  	if encodererr != nil {
   304  		return fmt.Errorf(
   305  			"gons/reexec: ReexecAction.Run: cannot send parameter to child, reason: %w",
   306  			decodererr)
   307  	}
   308  	childhiccup := childerr.String()
   309  	if childhiccup != "" {
   310  		return fmt.Errorf(
   311  			"gons/reexec: ReexecAction.Run: child failed with stderr message %q",
   312  			childhiccup)
   313  	}
   314  	if decodererr != nil {
   315  		return fmt.Errorf(
   316  			"gons/reexec: ReexecAction.Run: cannot decode child result, reason: %w",
   317  			decodererr)
   318  	}
   319  	return err
   320  }
   321  
   322  // ForkReexec restarts the application using reexec as a new child process and
   323  // then immediately executes only the specified action (actionname). The output
   324  // of the child gets deserialized as JSON into the passed result element. The
   325  // call returns after the child process has terminated.
   326  //
   327  // Deprecated: use RunReexecAction("foo", Namespaces(n), Result(r)) instead.
   328  func ForkReexec(actionname string, namespaces []Namespace, result interface{}) (err error) {
   329  	return RunReexecAction(
   330  		actionname,
   331  		Namespaces(namespaces),
   332  		Result(result))
   333  }
   334  
   335  // ForkReexecEnv restarts the application using reexec as a new child process
   336  // and then immediately executes only the specified action (actionname), passing
   337  // additional environment variables to the child. The output of the child gets
   338  // deserialized as JSON into the passed result element. The call returns after
   339  // the child process has terminated.
   340  //
   341  // Deprecated: use RunReexecAction("foo", Namespaces(n), Environment(env),
   342  // Result(r)) instead.
   343  func ForkReexecEnv(actionname string, namespaces []Namespace, envvars []string, result interface{}) (err error) {
   344  	return RunReexecAction(
   345  		actionname,
   346  		Namespaces(namespaces),
   347  		Environment(envvars),
   348  		Result(result))
   349  }
   350  
   351  // Action is a function that is run on demand during re-execution of a forked
   352  // child.
   353  type Action func()
   354  
   355  // actions maps re-execution topics (names) to action functions to execute on
   356  // a scheduled re-execution.
   357  var actions = map[string]Action{}
   358  
   359  // Register registers a Action function with a name so it can be
   360  // triggered during ForkReexec(name, ...). The registration panics if the same
   361  // Action name is registered more than once, regardless of whether with the
   362  // same Action or different ones.
   363  func Register(name string, action Action) {
   364  	if _, ok := actions[name]; ok {
   365  		panic(fmt.Sprintf(
   366  			"gons/reexec: registerAction: re-execution action %q already registered",
   367  			name))
   368  	}
   369  	actions[name] = action
   370  }