mirror of
https://github.com/status-im/consul.git
synced 2025-01-24 20:51:10 +00:00
c310451b2b
Play a trick with CLOEXEC to pass the envoy bootstrap configuration as an open file descriptor to the exec'd envoy process. The file only briefly touches disk before being unlinked. We convince envoy to read from this open file descriptor by using the /dev/fd/$FDNUMBER mechanism to read the open file descriptor as a file. Because the filename no longer has an extension envoy's sniffing logic falls back on JSON instead of YAML, so the bootstrap configuration must be generated as JSON instead.
130 lines
3.2 KiB
Go
130 lines
3.2 KiB
Go
// +build linux darwin
|
|
|
|
package envoy
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func execEnvoy(binary string, prefixArgs, suffixArgs []string, bootstrapJson []byte) error {
|
|
// Write the Envoy bootstrap config file out to disk in a pocket universe
|
|
// visible only to the current process (and exec'd future selves).
|
|
fd, err := writeEphemeralEnvoyTempFile(bootstrapJson)
|
|
if err != nil {
|
|
return errors.New("Could not write envoy bootstrap config to a temp file: " + err.Error())
|
|
}
|
|
|
|
// On unix systems after exec the file descriptors that we should see:
|
|
//
|
|
// 0: stdin
|
|
// 1: stdout
|
|
// 2: stderr
|
|
// ... any open file descriptors from the parent without CLOEXEC set
|
|
//
|
|
// Above we explicitly disabled CLOEXEC for our temp file, so assuming
|
|
// FD numbers survive across execs, it should just be the value of
|
|
// `fd`. This is accessible as a file itself (trippy!) under
|
|
// /dev/fd/$FDNUMBER.
|
|
magicPath := filepath.Join("/dev/fd", strconv.Itoa(int(fd)))
|
|
|
|
// First argument needs to be the executable name.
|
|
envoyArgs := []string{binary}
|
|
envoyArgs = append(envoyArgs, prefixArgs...)
|
|
envoyArgs = append(envoyArgs, "--v2-config-only",
|
|
"--disable-hot-restart",
|
|
"--config-path",
|
|
magicPath,
|
|
)
|
|
envoyArgs = append(envoyArgs, suffixArgs...)
|
|
|
|
// Exec
|
|
if err = unix.Exec(binary, envoyArgs, os.Environ()); err != nil {
|
|
return errors.New("Failed to exec envoy: " + err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeEphemeralEnvoyTempFile(b []byte) (uintptr, error) {
|
|
f, err := ioutil.TempFile("", "envoy-ephemeral-config")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
errFn := func(err error) (uintptr, error) {
|
|
_ = f.Close()
|
|
return 0, err
|
|
}
|
|
|
|
// TempFile already does this, but it's cheap to reinforce that we
|
|
// WANT the default behavior.
|
|
if err := f.Chmod(0600); err != nil {
|
|
return errFn(err)
|
|
}
|
|
|
|
// Immediately unlink the file as we are going to just pass the
|
|
// file descriptor, not the path.
|
|
if err = os.Remove(f.Name()); err != nil {
|
|
return errFn(err)
|
|
}
|
|
if _, err = f.Write(b); err != nil {
|
|
return errFn(err)
|
|
}
|
|
// Rewind the file descriptor so Envoy can read it.
|
|
if _, err = f.Seek(0, io.SeekStart); err != nil {
|
|
return errFn(err)
|
|
}
|
|
|
|
// Disable CLOEXEC so that this file descriptor is available
|
|
// to the exec'd Envoy.
|
|
if err := setCloseOnExec(f.Fd(), false); err != nil {
|
|
return errFn(err)
|
|
}
|
|
|
|
return f.Fd(), nil
|
|
}
|
|
|
|
// isCloseOnExec checks the provided file descriptor to see if the CLOEXEC flag
|
|
// is set.
|
|
func isCloseOnExec(fd uintptr) (bool, error) {
|
|
flags, err := getFdFlags(fd)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return flags&unix.FD_CLOEXEC != 0, nil
|
|
}
|
|
|
|
// setCloseOnExec sets or unsets the CLOEXEC flag on the provided file descriptor
|
|
// depending upon the value of the enabled arg.
|
|
func setCloseOnExec(fd uintptr, enabled bool) error {
|
|
flags, err := getFdFlags(fd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newFlags := flags
|
|
if enabled {
|
|
newFlags |= unix.FD_CLOEXEC
|
|
} else {
|
|
newFlags &= ^unix.FD_CLOEXEC
|
|
}
|
|
|
|
if newFlags == flags {
|
|
return nil // noop
|
|
}
|
|
|
|
_, err = unix.FcntlInt(fd, unix.F_SETFD, newFlags)
|
|
return err
|
|
}
|
|
|
|
func getFdFlags(fd uintptr) (int, error) {
|
|
return unix.FcntlInt(fd, unix.F_GETFD, 0)
|
|
}
|