github.com/DerekStrickland/consul@v1.4.5/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 }