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  }