README.md
Rendering markdown...
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
}