github.com/judwhite/consul@v1.4.4-0.20190315202039-6ef970a191d3/command/connect/envoy/exec_unix.go (about)

     1  // +build linux darwin
     2  
     3  package envoy
     4  
     5  import (
     6  	"errors"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"golang.org/x/sys/unix"
    15  )
    16  
    17  func isHotRestartOption(s string) bool {
    18  	restartOpts := []string{
    19  		"--restart-epoch",
    20  		"--hot-restart-version",
    21  		"--drain-time-s",
    22  		"--parent-shutdown-time-s",
    23  	}
    24  	for _, opt := range restartOpts {
    25  		if s == opt {
    26  			return true
    27  		}
    28  		if strings.HasPrefix(s, opt+"=") {
    29  			return true
    30  		}
    31  	}
    32  	return false
    33  }
    34  
    35  func hasHotRestartOption(argSets ...[]string) bool {
    36  	for _, args := range argSets {
    37  		for _, opt := range args {
    38  			if isHotRestartOption(opt) {
    39  				return true
    40  			}
    41  		}
    42  	}
    43  	return false
    44  }
    45  
    46  func execEnvoy(binary string, prefixArgs, suffixArgs []string, bootstrapJson []byte) error {
    47  	// Write the Envoy bootstrap config file out to disk in a pocket universe
    48  	// visible only to the current process (and exec'd future selves).
    49  	fd, err := writeEphemeralEnvoyTempFile(bootstrapJson)
    50  	if err != nil {
    51  		return errors.New("Could not write envoy bootstrap config to a temp file: " + err.Error())
    52  	}
    53  
    54  	// On unix systems after exec the file descriptors that we should see:
    55  	//
    56  	//   0: stdin
    57  	//   1: stdout
    58  	//   2: stderr
    59  	//   ... any open file descriptors from the parent without CLOEXEC set
    60  	//
    61  	// Above we explicitly disabled CLOEXEC for our temp file, so assuming
    62  	// FD numbers survive across execs, it should just be the value of
    63  	// `fd`. This is accessible as a file itself (trippy!) under
    64  	// /dev/fd/$FDNUMBER.
    65  	magicPath := filepath.Join("/dev/fd", strconv.Itoa(int(fd)))
    66  
    67  	// We default to disabling hot restart because it makes it easier to run
    68  	// multiple envoys locally for testing without them trying to share memory and
    69  	// unix sockets and complain about being different IDs. But if user is
    70  	// actually configuring hot-restart explicitly with the --restart-epoch option
    71  	// then don't disable it!
    72  	disableHotRestart := !hasHotRestartOption(prefixArgs, suffixArgs)
    73  
    74  	// First argument needs to be the executable name.
    75  	envoyArgs := []string{binary}
    76  	envoyArgs = append(envoyArgs, prefixArgs...)
    77  	envoyArgs = append(envoyArgs, "--v2-config-only",
    78  		"--config-path",
    79  		magicPath,
    80  	)
    81  	if disableHotRestart {
    82  		envoyArgs = append(envoyArgs, "--disable-hot-restart")
    83  	}
    84  	envoyArgs = append(envoyArgs, suffixArgs...)
    85  
    86  	// Exec
    87  	if err = unix.Exec(binary, envoyArgs, os.Environ()); err != nil {
    88  		return errors.New("Failed to exec envoy: " + err.Error())
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  func writeEphemeralEnvoyTempFile(b []byte) (uintptr, error) {
    95  	f, err := ioutil.TempFile("", "envoy-ephemeral-config")
    96  	if err != nil {
    97  		return 0, err
    98  	}
    99  
   100  	errFn := func(err error) (uintptr, error) {
   101  		_ = f.Close()
   102  		return 0, err
   103  	}
   104  
   105  	// TempFile already does this, but it's cheap to reinforce that we
   106  	// WANT the default behavior.
   107  	if err := f.Chmod(0600); err != nil {
   108  		return errFn(err)
   109  	}
   110  
   111  	// Immediately unlink the file as we are going to just pass the
   112  	// file descriptor, not the path.
   113  	if err = os.Remove(f.Name()); err != nil {
   114  		return errFn(err)
   115  	}
   116  	if _, err = f.Write(b); err != nil {
   117  		return errFn(err)
   118  	}
   119  	// Rewind the file descriptor so Envoy can read it.
   120  	if _, err = f.Seek(0, io.SeekStart); err != nil {
   121  		return errFn(err)
   122  	}
   123  
   124  	// Disable CLOEXEC so that this file descriptor is available
   125  	// to the exec'd Envoy.
   126  	if err := setCloseOnExec(f.Fd(), false); err != nil {
   127  		return errFn(err)
   128  	}
   129  
   130  	return f.Fd(), nil
   131  }
   132  
   133  // isCloseOnExec checks the provided file descriptor to see if the CLOEXEC flag
   134  // is set.
   135  func isCloseOnExec(fd uintptr) (bool, error) {
   136  	flags, err := getFdFlags(fd)
   137  	if err != nil {
   138  		return false, err
   139  	}
   140  	return flags&unix.FD_CLOEXEC != 0, nil
   141  }
   142  
   143  // setCloseOnExec sets or unsets the CLOEXEC flag on the provided file descriptor
   144  // depending upon the value of the enabled arg.
   145  func setCloseOnExec(fd uintptr, enabled bool) error {
   146  	flags, err := getFdFlags(fd)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	newFlags := flags
   152  	if enabled {
   153  		newFlags |= unix.FD_CLOEXEC
   154  	} else {
   155  		newFlags &= ^unix.FD_CLOEXEC
   156  	}
   157  
   158  	if newFlags == flags {
   159  		return nil // noop
   160  	}
   161  
   162  	_, err = unix.FcntlInt(fd, unix.F_SETFD, newFlags)
   163  	return err
   164  }
   165  
   166  func getFdFlags(fd uintptr) (int, error) {
   167  	return unix.FcntlInt(fd, unix.F_GETFD, 0)
   168  }