Download
48 lines 1.4 KiBGoModified 2026-10-31 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
package pty

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"syscall"
	"unsafe"
)

// getTermios and setTermios are the ioctls that get and set a
// terminal's settings (see KeyMode).
const getTermios, setTermios = syscall.TIOCGETA, syscall.TIOCSETA

// Open opens a new pseudo-terminal: the master, which the opener reads and
// writes, and the slave, the terminal a command gets.
func Open() (master, slave *os.File, err error) {
	m, err := os.OpenFile("/dev/ptmx", os.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
	if err != nil {
		return nil, nil, err
	}
	defer func() {
		if err != nil {
			m.Close()
		}
	}()
	if err := ioctl(m, syscall.TIOCPTYGRANT, nil); err != nil {
		return nil, nil, fmt.Errorf("granting the pty: %w", err)
	}
	if err := ioctl(m, syscall.TIOCPTYUNLK, nil); err != nil {
		return nil, nil, fmt.Errorf("unlocking the pty: %w", err)
	}
	var buf [128]byte
	if err := ioctl(m, syscall.TIOCPTYGNAME, unsafe.Pointer(&buf[0])); err != nil {
		return nil, nil, fmt.Errorf("naming the pty: %w", err)
	}
	name, _, _ := bytes.Cut(buf[:], []byte{0})
	// The sandbox profile names the device, so it must be one.
	if !strings.HasPrefix(string(name), "/dev/tty") || strings.ContainsAny(string(name), "\"\\\n") {
		return nil, nil, fmt.Errorf("naming the pty: unexpected name %q", name)
	}
	s, err := os.OpenFile(string(name), os.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
	if err != nil {
		return nil, nil, err
	}
	return m, s, nil
}