1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
|
package linux
import (
"errors"
"fmt"
"os"
"unsafe"
"golang.org/x/sys/unix"
"github.com/lxc/incus/v6/shared/revert"
)
// OpenPtyInDevpts creates a new PTS pair, configures them and returns them.
func OpenPtyInDevpts(devpts_fd int, uid, gid int64) (*os.File, *os.File, error) {
reverter := revert.New()
defer reverter.Fail()
var fd int
var ptx *os.File
var err error
// Create a PTS pair.
if devpts_fd >= 0 {
fd, err = unix.Openat(devpts_fd, "ptmx", unix.O_RDWR|unix.O_CLOEXEC|unix.O_NOCTTY, 0)
} else {
fd, err = unix.Openat(-1, "/dev/ptmx", unix.O_RDWR|unix.O_CLOEXEC|unix.O_NOCTTY, 0)
}
if err != nil {
return nil, nil, err
}
ptx = os.NewFile(uintptr(fd), "/dev/pts/ptmx")
reverter.Add(func() { _ = ptx.Close() })
// Unlock the ptx and pty.
val := 0
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(ptx.Fd()), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&val)))
if errno != 0 {
return nil, nil, unix.Errno(errno)
}
var pty *os.File
ptyFd, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(ptx.Fd()), unix.TIOCGPTPEER, uintptr(unix.O_NOCTTY|unix.O_CLOEXEC|os.O_RDWR))
// We can only fallback to looking up the fd in /dev/pts when we aren't dealing with the container's devpts instance.
if errno == 0 {
// Get the pty side.
id := 0
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(ptx.Fd()), unix.TIOCGPTN, uintptr(unsafe.Pointer(&id)))
if errno != 0 {
return nil, nil, unix.Errno(errno)
}
pty = os.NewFile(ptyFd, fmt.Sprintf("/dev/pts/%d", id))
} else {
if devpts_fd >= 0 {
return nil, nil, errors.New("TIOCGPTPEER required but not available")
}
// Get the pty side.
id := 0
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(ptx.Fd()), unix.TIOCGPTN, uintptr(unsafe.Pointer(&id)))
if errno != 0 {
return nil, nil, unix.Errno(errno)
}
// Open the pty.
pty, err = os.OpenFile(fmt.Sprintf("/dev/pts/%d", id), unix.O_NOCTTY|unix.O_CLOEXEC|os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
}
reverter.Add(func() { _ = pty.Close() })
// Configure both sides
for _, entry := range []*os.File{ptx, pty} {
// Get termios.
t, err := unix.IoctlGetTermios(int(entry.Fd()), unix.TCGETS)
if err != nil {
return nil, nil, err
}
// Set flags.
t.Cflag |= unix.IMAXBEL
t.Cflag |= unix.IUTF8
t.Cflag |= unix.BRKINT
t.Cflag |= unix.IXANY
t.Cflag |= unix.HUPCL
// Set termios.
err = unix.IoctlSetTermios(int(entry.Fd()), unix.TCSETS, t)
if err != nil {
return nil, nil, err
}
// Set the default window size.
sz := &unix.Winsize{
Col: 80,
Row: 25,
}
err = unix.IoctlSetWinsize(int(entry.Fd()), unix.TIOCSWINSZ, sz)
if err != nil {
return nil, nil, err
}
// Set CLOEXEC.
_, _, errno = unix.Syscall(unix.SYS_FCNTL, uintptr(entry.Fd()), unix.F_SETFD, unix.FD_CLOEXEC)
if errno != 0 {
return nil, nil, unix.Errno(errno)
}
}
// Fix the ownership of the pty side.
err = unix.Fchown(int(pty.Fd()), int(uid), int(gid))
if err != nil {
return nil, nil, err
}
reverter.Success()
return ptx, pty, nil
}
// OpenPty creates a new PTS pair, configures them and returns them.
func OpenPty(uid, gid int64) (*os.File, *os.File, error) {
return OpenPtyInDevpts(-1, uid, gid)
}
// SetPtySize issues the correct ioctl to resize a pty.
func SetPtySize(fd int, width int, height int) (err error) {
var dimensions [4]uint16
dimensions[0] = uint16(height)
dimensions[1] = uint16(width)
_, _, errno := unix.Syscall6(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.TIOCSWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0)
if errno != 0 {
return errno
}
return nil
}
|