rs2322tcp/internal/client/pty_linux.go
2026-08-10 12:42:22 +02:00

167 lines
2.8 KiB
Go

//go:build linux
package client
import (
"fmt"
"golang.org/x/sys/unix"
)
type ptySerial struct {
masterFD int
slaveFD int
path string
}
func newVirtualSerial() (VirtualSerial, error) {
masterFD, err := unix.Open(
"/dev/ptmx",
unix.O_RDWR|unix.O_NOCTTY,
0,
)
if err != nil {
return nil, fmt.Errorf("open PTY master: %w", err)
}
// Wenn die weitere Initialisierung fehlschlägt,
// darf der Master nicht offen bleiben.
closeMaster := true
defer func() {
if closeMaster {
_ = unix.Close(masterFD)
}
}()
// PTY-Slave entsperren.
if err := unix.IoctlSetPointerInt(
masterFD,
unix.TIOCSPTLCK,
0,
); err != nil {
return nil, fmt.Errorf("unlock PTY slave: %w", err)
}
// PTY-Nummer ermitteln.
ptsNumber, err := unix.IoctlGetInt(
masterFD,
unix.TIOCGPTN,
)
if err != nil {
return nil, fmt.Errorf("get PTY number: %w", err)
}
path := fmt.Sprintf("/dev/pts/%d", ptsNumber)
// Slave öffnen, um die Terminalparameter konfigurieren zu können.
slaveFD, err := unix.Open(
path,
unix.O_RDWR|unix.O_NOCTTY,
0,
)
if err != nil {
return nil, fmt.Errorf("open PTY slave %s: %w", path, err)
}
closeSlave := true
defer func() {
if closeSlave {
_ = unix.Close(slaveFD)
}
}()
if err := configurePTY(slaveFD); err != nil {
return nil, err
}
closeMaster = false
closeSlave = false
return &ptySerial{
masterFD: masterFD,
slaveFD: slaveFD,
path: path,
}, nil
}
func configurePTY(fd int) error {
termios, err := unix.IoctlGetTermios(
fd,
unix.TCGETS,
)
if err != nil {
return fmt.Errorf("get PTY termios: %w", err)
}
// Raw Mode:
// keine Canonical-Verarbeitung,
// kein Echo,
// keine Signalinterpretation,
// keine CR/LF-Konvertierung,
// keine XON/XOFF-Verarbeitung.
termios.Iflag &^= unix.IGNBRK |
unix.BRKINT |
unix.PARMRK |
unix.ISTRIP |
unix.INLCR |
unix.IGNCR |
unix.ICRNL |
unix.IXON
termios.Oflag &^= unix.OPOST
termios.Lflag &^= unix.ECHO |
unix.ECHONL |
unix.ICANON |
unix.ISIG |
unix.IEXTEN
termios.Cflag &^= unix.CSIZE | unix.PARENB
termios.Cflag |= unix.CS8
// Byte-orientiertes, blockierendes Lesen.
termios.Cc[unix.VMIN] = 1
termios.Cc[unix.VTIME] = 0
if err := unix.IoctlSetTermios(
fd,
unix.TCSETS,
termios,
); err != nil {
return fmt.Errorf("set PTY raw mode: %w", err)
}
return nil
}
func (p *ptySerial) Path() string {
return p.path
}
func (p *ptySerial) Read(b []byte) (int, error) {
return unix.Read(p.masterFD, b)
}
func (p *ptySerial) Write(b []byte) (int, error) {
return unix.Write(p.masterFD, b)
}
func (p *ptySerial) Close() error {
var firstErr error
if p.masterFD >= 0 {
if err := unix.Close(p.masterFD); err != nil {
firstErr = err
}
p.masterFD = -1
}
if p.slaveFD >= 0 {
if err := unix.Close(p.slaveFD); err != nil && firstErr == nil {
firstErr = err
}
p.slaveFD = -1
}
return firstErr
}