func ttyStart(tree *processtree.ProcessTree, done, quit chan bool) { go func() { scw := &StringChannelWriter{make(chan string, 10)} slog.SetDefaultLogger(slog.NewShinyLogger(scw, scw)) termios, err := ttyutils.NoEcho(uintptr(os.Stdout.Fd())) if err != nil { theChart.terminalSupported = false } for { select { case <-quit: ttyutils.RestoreTerminalState(uintptr(os.Stdout.Fd()), termios) done <- true return case output := <-scw.Notif: theChart.L.Lock() if theChart.drawnInitial { print(output) } theChart.extraOutput += output theChart.L.Unlock() theChart.draw() case <-theChart.update: theChart.draw() } } }() }
func Start(tree *processtree.ProcessTree, done chan bool) chan bool { quit := make(chan bool) go func() { theChart = &StatusChart{} theChart.RootSlave = tree.Root theChart.numberOfSlaves = len(tree.SlavesByName) theChart.Commands = tree.Commands theChart.update = make(chan bool, 10) theChart.directLogger = slog.NewShinyLogger(os.Stdout, os.Stderr) theChart.terminalSupported = true scw := &StringChannelWriter{make(chan string, 10)} slog.DefaultLogger = slog.NewShinyLogger(scw, scw) termios, err := ttyutils.NoEcho(uintptr(os.Stdout.Fd())) if err != nil { theChart.terminalSupported = false } ticker := time.Tick(1000 * time.Millisecond) for { select { case <-quit: ttyutils.RestoreTerminalState(uintptr(os.Stdout.Fd()), termios) done <- true return case <-ticker: if theChart.terminalSupported { theChart.draw() } case output := <-scw.Notif: theChart.L.Lock() if theChart.drawnInitial { print(output) } theChart.extraOutput += output theChart.L.Unlock() theChart.draw() case <-tree.StateChanged: theChart.draw() case <-theChart.update: theChart.draw() } } }() return quit }
// SetupRawTerminal will set terminal to RAW mode and return function to // restore normal terminal. func SetupRawTerminal(setSize func(cols, row uint16) error) func() { // Switch terminal to raw mode state, err := ttyutils.MakeTerminalRaw(os.Stdout.Fd()) if err != nil { // If we can't do it just continue as we were return func() {} } // Get TTY size size, err := ttyutils.Winsize(os.Stdout) if err == nil && setSize != nil { setSize(size.Columns, size.Lines) } // Handle SIGWINCH signals (window resize signals from graphical terminals) sigWinch := make(chan os.Signal, 1) signal.Notify(sigWinch, syscall.SIGWINCH) go func() { for range sigWinch { size, err := ttyutils.Winsize(os.Stdout) if err == nil && setSize != nil { setSize(size.Columns, size.Lines) } } }() // Return a function to cleanup return func() { // Stop handling SIGWINCH, and close channel to clean up go routine signal.Stop(sigWinch) close(sigWinch) // Restore terminal state ttyutils.RestoreTerminalState(os.Stdout.Fd(), state) fmt.Println("") } }
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 }
func Run(args []string, input io.Reader, output *os.File) int { if os.Getenv("RAILS_ENV") != "" { println("Warning: Specifying a Rails environment via RAILS_ENV has no effect for commands run with zeus.") println("As a safety precaution to protect you from nuking your development database,") println("Zeus will now cowardly refuse to proceed. Please unset RAILS_ENV and try again.") return 1 } isTerminal := ttyutils.IsTerminal(output.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(output.Fd()) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } defer ttyutils.RestoreTerminalState(output.Fd(), oldState) } // should this happen if we're running over a pipe? I think maybe not? ttyutils.MirrorWinsize(output, master) addr, err := net.ResolveUnixAddr("unixgram", unixsocket.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.New(conn) msg := messages.CreateCommandAndArgumentsMessage(args, os.Getpid()) usock.WriteMessage(msg) err = sendCommandLineArguments(usock, args) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } 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(output, master) syscall.Kill(commandPid, syscall.SIGWINCH) } else { // member of terminatingSignals ttyutils.RestoreTerminalState(output.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 || (err == io.EOF && n > 0) { output.Write(buf[:n]) } else { eof <- true break } } }() go func() { buf := make([]byte, 8192) for { n, err := input.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 }