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 }