167 lines
2.8 KiB
Go
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
|
|
}
|