github.com/nycdavid/zeus@v0.0.0-20201208104106-9ba439429e03/go/clienthandler/clienthandler.go (about) 1 package clienthandler 2 3 import ( 4 "errors" 5 "math/rand" 6 "net" 7 "os" 8 "path/filepath" 9 "strconv" 10 "time" 11 12 "github.com/burke/zeus/go/messages" 13 "github.com/burke/zeus/go/processtree" 14 slog "github.com/burke/zeus/go/shinylog" 15 "github.com/burke/zeus/go/unixsocket" 16 "github.com/burke/zeus/go/zerror" 17 ) 18 19 func Start(tree *processtree.ProcessTree, done chan bool) chan bool { 20 quit := make(chan bool) 21 go func() { 22 path, _ := filepath.Abs(unixsocket.ZeusSockName()) 23 addr, err := net.ResolveUnixAddr("unix", path) 24 if err != nil { 25 zerror.Error("Can't open socket.") 26 } 27 listener, err := net.ListenUnix("unix", addr) 28 if err != nil { 29 zerror.ErrorCantCreateListener() 30 } 31 32 connections := make(chan *unixsocket.Usock) 33 go func() { 34 for { 35 if conn, err := listener.AcceptUnix(); err != nil { 36 zerror.ErrorUnableToAcceptSocketConnection() 37 time.Sleep(500 * time.Millisecond) 38 } else { 39 connections <- unixsocket.New(conn) 40 } 41 } 42 }() 43 44 for { 45 select { 46 case <-quit: 47 listener.Close() 48 done <- true 49 return 50 case conn := <-connections: 51 go handleClientConnection(tree, conn) 52 } 53 } 54 }() 55 return quit 56 } 57 58 // see docs/client_master_handshake.md 59 func handleClientConnection(tree *processtree.ProcessTree, usock *unixsocket.Usock) { 60 defer usock.Close() 61 // we have established first contact to the client. 62 63 command, clientPid, argCount, argFD, err := receiveCommandArgumentsAndPid(usock, nil) 64 commandNode, slaveNode, err := findCommandAndSlaveNodes(tree, command, err) 65 if err != nil { 66 // connection was established, no data was sent. Ignore. 67 return 68 } 69 command = commandNode.Name // resolve aliases 70 71 clientFile, err := receiveTTY(usock, err) 72 defer clientFile.Close() 73 74 stderrFile, err := receiveTTY(usock, err) 75 defer stderrFile.Close() 76 77 if err == nil && slaveNode.Error != "" { 78 writeStacktrace(usock, slaveNode, clientFile) 79 return 80 } 81 82 commandUsock, err := bootNewCommand(slaveNode, command, err) 83 if err != nil { 84 // If a client connects while the command is just 85 // booting up, it actually makes it here - still 86 // expects a backtrace, of course. 87 writeStacktrace(usock, slaveNode, clientFile) 88 return 89 } 90 defer commandUsock.Close() 91 92 err = sendClientPidAndArgumentsToCommand(commandUsock, clientPid, argCount, argFD, err) 93 94 // send stdout to use 95 err = sendTTYToCommand(commandUsock, clientFile, err) 96 // send stderr to use 97 err = sendTTYToCommand(commandUsock, stderrFile, err) 98 99 cmdPid, err := receivePidFromCommand(commandUsock, err) 100 101 err = sendCommandPidToClient(usock, cmdPid, err) 102 103 exitStatus, err := receiveExitStatus(commandUsock, err) 104 105 err = sendExitStatus(usock, exitStatus, err) 106 107 if err != nil { 108 slog.Error(err) 109 } 110 // Done! Hooray! 111 } 112 113 func writeStacktrace(usock *unixsocket.Usock, slaveNode *processtree.SlaveNode, clientFile *os.File) { 114 // Fake process ID / output / error codes: 115 // Write a fake pid (step 6) 116 usock.WriteMessage("0") 117 // Write the error message to the terminal 118 clientFile.Write([]byte(slaveNode.Error)) 119 // Write a non-positive exit code to the client 120 usock.WriteMessage("1") 121 } 122 123 func receiveFileFromFD(usock *unixsocket.Usock) (*os.File, error) { 124 clientFd, err := usock.ReadFD() 125 if err != nil { 126 return nil, errors.New("Expected FD, none received!") 127 } 128 fileName := strconv.Itoa(rand.Int()) 129 return os.NewFile(uintptr(clientFd), fileName), nil 130 } 131 132 func receiveCommandArgumentsAndPid(usock *unixsocket.Usock, err error) (string, int, int, int, error) { 133 if err != nil { 134 return "", -1, -1, -1, err 135 } 136 137 msg, err := usock.ReadMessage() 138 if err != nil { 139 return "", -1, -1, -1, err 140 } 141 142 argCount, clientPid, command, err := messages.ParseClientCommandRequestMessage(msg) 143 if err != nil { 144 return "", -1, -1, -1, err 145 } 146 147 argFD, err := usock.ReadFD() 148 return command, clientPid, argCount, argFD, err 149 } 150 151 func findCommandAndSlaveNodes(tree *processtree.ProcessTree, command string, err error) (*processtree.CommandNode, *processtree.SlaveNode, error) { 152 if err != nil { 153 return nil, nil, err 154 } 155 156 commandNode := tree.FindCommand(command) 157 if commandNode == nil { 158 return nil, nil, errors.New("ERROR: Node not found!: " + command) 159 } 160 command = commandNode.Name 161 slaveNode := commandNode.Parent 162 163 return commandNode, slaveNode, nil 164 } 165 166 func receiveTTY(usock *unixsocket.Usock, err error) (*os.File, error) { 167 if err != nil { 168 return nil, err 169 } 170 return receiveFileFromFD(usock) 171 } 172 173 func sendClientPidAndArgumentsToCommand(commandUsock *unixsocket.Usock, clientPid int, argCount int, argFD int, err error) error { 174 if err != nil { 175 return err 176 } 177 178 msg := messages.CreatePidAndArgumentsMessage(clientPid, argCount) 179 _, err = commandUsock.WriteMessage(msg) 180 if err != nil { 181 return err 182 } 183 184 return commandUsock.WriteFD(argFD) 185 } 186 187 func receiveExitStatus(commandUsock *unixsocket.Usock, err error) (string, error) { 188 if err != nil { 189 return "", err 190 } 191 192 return commandUsock.ReadMessage() 193 } 194 195 func sendExitStatus(usock *unixsocket.Usock, exitStatus string, err error) error { 196 if err != nil { 197 return err 198 } 199 200 _, err = usock.WriteMessage(exitStatus) 201 return err 202 } 203 204 func receivePidFromCommand(commandUsock *unixsocket.Usock, err error) (int, error) { 205 if err != nil { 206 return -1, err 207 } 208 209 msg, err := commandUsock.ReadMessage() 210 if err != nil { 211 return -1, err 212 } 213 intPid, _, _, _ := messages.ParsePidMessage(msg) 214 215 return intPid, err 216 } 217 218 func sendCommandPidToClient(usock *unixsocket.Usock, pid int, err error) error { 219 if err != nil { 220 return err 221 } 222 223 strPid := strconv.Itoa(pid) 224 _, err = usock.WriteMessage(strPid) 225 226 return err 227 } 228 229 func bootNewCommand(slaveNode *processtree.SlaveNode, command string, err error) (*unixsocket.Usock, error) { 230 if err != nil { 231 return nil, err 232 } 233 234 request := &processtree.CommandRequest{Name: command, Retchan: make(chan *processtree.CommandReply)} 235 slaveNode.RequestCommandBoot(request) 236 reply := <-request.Retchan // TODO: don't really want to wait indefinitely. 237 // defer commandFile.Close() // TODO: can't do this here anymore. 238 239 if reply.State == processtree.SCrashed { 240 return nil, errors.New("Process has crashed") 241 } 242 243 return unixsocket.NewFromFile(reply.File) 244 } 245 246 func sendTTYToCommand(commandUsock *unixsocket.Usock, clientFile *os.File, err error) error { 247 if err != nil { 248 return err 249 } 250 251 return commandUsock.WriteFD(int(clientFile.Fd())) 252 }