func StartClientHandler(tree *ProcessTree, quit chan bool) { path, _ := filepath.Abs(zeusSockName) addr, err := net.ResolveUnixAddr("unix", path) if err != nil { panic("Can't open socket.") } listener, err := net.ListenUnix("unix", addr) if err != nil { ErrorCantCreateListener() } defer listener.Close() connections := make(chan *unixsocket.Usock) go func() { for { if conn, err := listener.AcceptUnix(); err != nil { errorUnableToAcceptSocketConnection() time.Sleep(500 * time.Millisecond) } else { connections <- unixsocket.NewUsock(conn) } } }() for { select { case <-quit: quit <- true return case conn := <-connections: go handleClientConnection(tree, conn) } } }
// We want to make this the single interface point with the socket. // we want to republish unneeded messages to channels so other modules //can pick them up. (notably, clienthandler.) func (node *SlaveNode) handleMessages() { socket := node.Socket usock := unixsocket.NewUsock(socket) for { if msg, fd, err := usock.ReadMessageOrFD(); err != nil { node.crashed() return } else if fd > 0 { // File descriptors are sent during client negotiation node.ClientCommandPTYFileDescriptor <- fd } else { // Every other message indicates a feature loaded, and should be sent to filemonitor. msg = strings.TrimRight(msg, "\000") node.handleFeatureMessage(msg) } } }
func (mon *SlaveMonitor) bootSlave(slave *SlaveNode) { for { slave.Parent.WaitUntilBooted() msg := CreateSpawnSlaveMessage(slave.Name) unixsocket.NewUsock(slave.Parent.Socket).WriteMessage(msg) restartNow := make(chan bool) go func() { slave.Parent.WaitUntilUnbooted() restartNow <- true }() go func() { slave.WaitUntilRestartRequested() restartNow <- true }() <-restartNow slave.Kill() } }
func doRun(color bool) int { if !color { slog.DisableColor() DisableErrorColor() } if os.Getenv("RAILS_ENV") != "" { println("Warning: Specifying a Rails environment via RAILS_ENV has no effect for commands run with zeus.") } master, slave, err := pty.Open() if err != nil { panic(err) } defer master.Close() if ttyutils.IsTerminal(os.Stdout.Fd()) { oldState, err := ttyutils.MakeTerminalRaw(os.Stdout.Fd()) if err != nil { panic(err) } defer ttyutils.RestoreTerminalState(os.Stdout.Fd(), oldState) } ttyutils.MirrorWinsize(os.Stdout, master) addr, err := net.ResolveUnixAddr("unixgram", zeusSockName) if err != nil { panic("Can't resolve server address") } conn, err := net.DialUnix("unix", nil, addr) if err != nil { ErrorCantConnectToMaster() } usock := unixsocket.NewUsock(conn) msg := CreateCommandAndArgumentsMessage(os.Args[1], os.Args[2:]) usock.WriteMessage(msg) usock.WriteFD(int(slave.Fd())) slave.Close() msg, err = usock.ReadMessage() if err != nil { panic(err) } parts := strings.Split(msg, "\000") commandPid, err := strconv.Atoi(parts[0]) defer func() { if commandPid > 0 { // Just in case. syscall.Kill(commandPid, 9) } }() if err != nil { panic(err) } c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGWINCH, syscall.SIGCONT) go func() { for sig := range c { if sig == syscall.SIGCONT { syscall.Kill(commandPid, syscall.SIGCONT) } else if sig == syscall.SIGWINCH { ttyutils.MirrorWinsize(os.Stdout, master) syscall.Kill(commandPid, syscall.SIGWINCH) } } }() var exitStatus int = -1 if len(parts) > 2 { exitStatus, err = strconv.Atoi(parts[0]) if err != nil { panic(err) } } eof := make(chan bool) go func() { for { buf := make([]byte, 1024) n, err := master.Read(buf) if err != nil { eof <- true break } os.Stdout.Write(buf[:n]) } }() go func() { buf := make([]byte, 8192) for { n, err := os.Stdin.Read(buf) if err != nil { eof <- true break } for i := 0; i < n; i++ { switch buf[i] { case sigInt: syscall.Kill(commandPid, syscall.SIGINT) case sigQuit: syscall.Kill(commandPid, syscall.SIGQUIT) case sigTstp: syscall.Kill(commandPid, syscall.SIGTSTP) syscall.Kill(os.Getpid(), syscall.SIGTSTP) } } master.Write(buf[:n]) } }() <-eof if exitStatus == -1 { msg, err = usock.ReadMessage() if err != nil { panic(err) } parts := strings.Split(msg, "\000") exitStatus, err = strconv.Atoi(parts[0]) if err != nil { panic(err) } } return exitStatus }
// see docs/client_master_handshake.md func handleClientConnection(tree *ProcessTree, usock *unixsocket.Usock) { defer usock.Close() // we have established first contact to the client. // we first read the command and arguments specified from the connection. (step 1) msg, err := usock.ReadMessage() if err != nil { fmt.Println(err) return } command, arguments, err := ParseClientCommandRequestMessage(msg) if err != nil { fmt.Println(err) return } commandNode := tree.FindCommand(command) if commandNode == nil { fmt.Println("ERROR: Node not found!: ", command) return } command = commandNode.Name // resolve aliases slaveNode := commandNode.Parent // Now we read the terminal IO socket to use for raw IO (step 2) clientFd, err := usock.ReadFD() if err != nil { fmt.Println("Expected FD, none received!") return } fileName := strconv.Itoa(rand.Int()) clientFile := unixsocket.FdToFile(clientFd, fileName) defer clientFile.Close() // We now need to fork a new command process. // For now, we naively assume it's running... if slaveNode.Error != "" { // we can skip steps 3-5 as they deal with the command process we're not spawning. // Write a fake pid (step 6) usock.WriteMessage("0") // Write the error message to the terminal clientFile.Write([]byte(slaveNode.Error)) // Skip step 7, and write an exit code to the client (step 8) usock.WriteMessage("1") return } // boot a command process and establish a socket connection to it. slaveNode.WaitUntilBooted() msg = CreateSpawnCommandMessage(command) slaveNode.mu.Lock() slaveNode.Socket.Write([]byte(msg)) slaveNode.mu.Unlock() // TODO: deadline? how to respond if this is never sent? commandFd := <-slaveNode.ClientCommandPTYFileDescriptor if err != nil { fmt.Println("Couldn't start command process!", err) } fileName = strconv.Itoa(rand.Int()) commandFile := unixsocket.FdToFile(commandFd, fileName) defer commandFile.Close() // Send the arguments to the command process (step 3) commandFile.Write([]byte(arguments)) // TODO: What if they're too long? commandSocket, err := unixsocket.MakeUnixSocket(commandFile) if err != nil { fmt.Println("MakeUnixSocket", err) } defer commandSocket.Close() // Send the client terminal connection to the command process (step 4) commandUsock := unixsocket.NewUsock(commandSocket) commandUsock.WriteFD(clientFd) // Receive the pid from the command process (step 5) msg, err = commandUsock.ReadMessage() if err != nil { println(err) } intPid, _, _ := ParsePidMessage(msg) // Send the pid to the client process (step 6) strPid := strconv.Itoa(intPid) usock.WriteMessage(strPid) // Receive the exit status from the command (step 7) msg, err = commandUsock.ReadMessage() if err != nil { println(err) } // Forward the exit status to the Client (step 8) usock.WriteMessage(msg) // Done! Hooray! }
func doRun() int { if os.Getenv("RAILS_ENV") != "" { println("Warning: Specifying a Rails environment via RAILS_ENV has no effect for commands run with zeus.") } isTerminal := ttyutils.IsTerminal(os.Stdout.Fd()) var master, slave *os.File var err error if isTerminal { master, slave, err = pty.Open() } else { master, slave, err = unixsocket.Socketpair(syscall.SOCK_STREAM) } if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } defer master.Close() var oldState *ttyutils.Termios if isTerminal { oldState, err = ttyutils.MakeTerminalRaw(os.Stdout.Fd()) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } defer ttyutils.RestoreTerminalState(os.Stdout.Fd(), oldState) } // should this happen if we're running over a pipe? I think maybe not? ttyutils.MirrorWinsize(os.Stdout, master) addr, err := net.ResolveUnixAddr("unixgram", zeusSockName) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } conn, err := net.DialUnix("unix", nil, addr) if err != nil { zerror.ErrorCantConnectToMaster() return 1 } usock := unixsocket.NewUsock(conn) msg := messages.CreateCommandAndArgumentsMessage(os.Args[1], os.Getpid(), os.Args[2:]) usock.WriteMessage(msg) usock.WriteFD(int(slave.Fd())) slave.Close() msg, err = usock.ReadMessage() if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } parts := strings.Split(msg, "\000") commandPid, err := strconv.Atoi(parts[0]) defer func() { if commandPid > 0 { // Just in case. syscall.Kill(commandPid, 9) } }() if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } if isTerminal { c := make(chan os.Signal, 1) handledSignals := append(append(terminatingSignals, syscall.SIGWINCH), syscall.SIGCONT) signal.Notify(c, handledSignals...) go func() { for sig := range c { if sig == syscall.SIGCONT { syscall.Kill(commandPid, syscall.SIGCONT) } else if sig == syscall.SIGWINCH { ttyutils.MirrorWinsize(os.Stdout, master) syscall.Kill(commandPid, syscall.SIGWINCH) } else { // member of terminatingSignals ttyutils.RestoreTerminalState(os.Stdout.Fd(), oldState) print("\r") syscall.Kill(commandPid, sig.(syscall.Signal)) os.Exit(1) } } }() } var exitStatus int = -1 if len(parts) > 2 { exitStatus, err = strconv.Atoi(parts[0]) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } } eof := make(chan bool) go func() { for { buf := make([]byte, 1024) n, err := master.Read(buf) if err != nil { eof <- true break } os.Stdout.Write(buf[:n]) } }() go func() { buf := make([]byte, 8192) for { n, err := os.Stdin.Read(buf) if err != nil { eof <- true break } if isTerminal { for i := 0; i < n; i++ { switch buf[i] { case sigInt: syscall.Kill(commandPid, syscall.SIGINT) case sigQuit: syscall.Kill(commandPid, syscall.SIGQUIT) case sigTstp: syscall.Kill(commandPid, syscall.SIGTSTP) syscall.Kill(os.Getpid(), syscall.SIGTSTP) } } } master.Write(buf[:n]) } }() <-eof if exitStatus == -1 { msg, err = usock.ReadMessage() if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } parts := strings.Split(msg, "\000") exitStatus, err = strconv.Atoi(parts[0]) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } } return exitStatus }