package main import ( "fmt" "os" "os/exec" "syscall" "unsafe" "golang.org/x/sys/unix" ) func runRootPTY(target string) error { master, err := os.OpenFile("/dev/ptmx", os.O_RDWR|syscall.O_NOCTTY, 0) if err != nil { return fmt.Errorf("open /dev/ptmx: %w", err) } var unlock int32 if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, master.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&unlock))); errno != 0 { master.Close() return fmt.Errorf("unlockpt: %w", errno) } var ptsNum uint32 if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, master.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&ptsNum))); errno != 0 { master.Close() return fmt.Errorf("ptsname: %w", errno) } slaveName := fmt.Sprintf("/dev/pts/%d", ptsNum) var ws unix.Winsize if ws2, err2 := unix.IoctlGetWinsize(syscall.Stdin, unix.TIOCGWINSZ); err2 == nil { ws = *ws2 unix.IoctlSetWinsize(int(master.Fd()), unix.TIOCSWINSZ, &ws) } cmd := exec.Command(target) slave, err := os.OpenFile(slaveName, os.O_RDWR, 0) if err != nil { master.Close() return fmt.Errorf("open slave: %w", err) } cmd.Stdin = slave cmd.Stdout = slave cmd.Stderr = slave cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true, Setctty: true, Ctty: 0} if err := cmd.Start(); err != nil { slave.Close() master.Close() return fmt.Errorf("exec %s: %w", target, err) } slave.Close() var savedTermios unix.Termios restoreTermios := false if err := unix.IoctlSetTermios(syscall.Stdin, unix.TCGETS, &savedTermios); err == nil { raw := savedTermios unix.IoctlSetTermios(syscall.Stdin, unix.TCSETS, makeRaw(&raw)) restoreTermios = true } defer func() { if restoreTermios { unix.IoctlSetTermios(syscall.Stdin, unix.TCSETS, &savedTermios) } master.Close() }() masterFd := int(master.Fd()) stdinEOF := false buf := make([]byte, 4096) for { pfds := []unix.PollFd{ {Fd: int32(syscall.Stdin), Events: unix.POLLIN}, {Fd: int32(masterFd), Events: unix.POLLIN}, } if stdinEOF { pfds[0].Fd = -1 } unix.Poll(pfds, 200) if pfds[1].Revents&unix.POLLIN != 0 { n, err := syscall.Read(masterFd, buf) if n > 0 { os.Stdout.Write(buf[:n]) } if err != nil || n <= 0 { break } } if !stdinEOF && pfds[0].Revents&unix.POLLIN != 0 { n, err := syscall.Read(syscall.Stdin, buf) if err != nil || n <= 0 { stdinEOF = true } else { syscall.Write(masterFd, buf[:n]) } } if pfds[1].Revents&(unix.POLLHUP|unix.POLLERR) != 0 { break } var wst syscall.WaitStatus if pid, _ := syscall.Wait4(cmd.Process.Pid, &wst, syscall.WNOHANG, nil); pid == cmd.Process.Pid { for i := 0; i < 5; i++ { pf := []unix.PollFd{{Fd: int32(masterFd), Events: unix.POLLIN}} if n2, _ := unix.Poll(pf, 50); n2 <= 0 { break } n2, _ := syscall.Read(masterFd, buf) if n2 <= 0 { break } os.Stdout.Write(buf[:n2]) } break } } return nil } func makeRaw(t *unix.Termios) *unix.Termios { raw := *t raw.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON raw.Oflag &^= unix.OPOST raw.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN raw.Cflag &^= unix.CSIZE | unix.PARENB raw.Cflag |= unix.CS8 raw.Cc[unix.VMIN] = 1 raw.Cc[unix.VTIME] = 0 return &raw }