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
|
//go:build darwin
package terminal
import (
"bytes"
"fmt"
"os"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
// openPTY allocates a pseudo-terminal and returns both of its ends.
//
// Darwin hands out the slave by name rather than by number: grant it, unlock
// it, then ask for the name. TIOCPTYGNAME writes into a buffer, and x/sys/unix
// exposes no helper for an ioctl of that shape — hence the one raw call below.
func openPTY() (master, slave *os.File, err error) {
master, err = os.OpenFile("/dev/ptmx", os.O_RDWR|unix.O_NOCTTY, 0)
if err != nil {
return nil, nil, fmt.Errorf("terminal: opening /dev/ptmx: %w", err)
}
for _, step := range []struct {
request uint
what string
}{
{unix.TIOCPTYGRANT, "granting"},
{unix.TIOCPTYUNLK, "unlocking"},
} {
if err := unix.IoctlSetInt(int(master.Fd()), step.request, 0); err != nil {
master.Close()
return nil, nil, fmt.Errorf("terminal: %s the pseudo-terminal: %w", step.what, err)
}
}
name, err := slaveName(master)
if err != nil {
master.Close()
return nil, nil, err
}
slave, err = os.OpenFile(name, os.O_RDWR|unix.O_NOCTTY, 0)
if err != nil {
master.Close()
return nil, nil, fmt.Errorf("terminal: opening the slave side: %w", err)
}
return master, slave, nil
}
// ptyNameLength is the size of the buffer TIOCPTYGNAME fills, as the request
// itself encodes.
const ptyNameLength = 128
// slaveName asks the kernel for the path of the slave device.
func slaveName(master *os.File) (string, error) {
var buffer [ptyNameLength]byte
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
master.Fd(),
uintptr(unix.TIOCPTYGNAME),
uintptr(unsafe.Pointer(&buffer[0])),
)
if errno != 0 {
return "", fmt.Errorf("terminal: naming the pseudo-terminal: %w", errno)
}
// The kernel writes a C string, so the name ends at the first zero byte.
if end := bytes.IndexByte(buffer[:], 0); end >= 0 {
return string(buffer[:end]), nil
}
return string(buffer[:]), nil
}
// setWinsize tells the pseudo-terminal how big it is, which the kernel passes
// on to the shell as SIGWINCH.
func setWinsize(master *os.File, width, height int) error {
return unix.IoctlSetWinsize(int(master.Fd()), unix.TIOCSWINSZ, &unix.Winsize{
Row: uint16(height),
Col: uint16(width),
})
}
// childAttributes put the shell in a session of its own with the slave as its
// controlling terminal.
//
// Without this the shell has no controlling terminal, and job control — Ctrl-C,
// Ctrl-Z, running something in the background — does not work at all.
func childAttributes() *syscall.SysProcAttr {
return &syscall.SysProcAttr{Setsid: true, Setctty: true}
}
|