1package pty
2
3/* based on:
4http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/pt.c
5*/
6
7import (
8	"errors"
9	"golang.org/x/sys/unix"
10	"os"
11	"strconv"
12	"syscall"
13	"unsafe"
14)
15
16const NODEV = ^uint64(0)
17
18func open() (pty, tty *os.File, err error) {
19	masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|unix.O_NOCTTY, 0)
20	//masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC|unix.O_NOCTTY, 0)
21	if err != nil {
22		return nil, nil, err
23	}
24	p := os.NewFile(uintptr(masterfd), "/dev/ptmx")
25
26	sname, err := ptsname(p)
27	if err != nil {
28		return nil, nil, err
29	}
30
31	err = grantpt(p)
32	if err != nil {
33		return nil, nil, err
34	}
35
36	err = unlockpt(p)
37	if err != nil {
38		return nil, nil, err
39	}
40
41	slavefd, err := syscall.Open(sname, os.O_RDWR|unix.O_NOCTTY, 0)
42	if err != nil {
43		return nil, nil, err
44	}
45	t := os.NewFile(uintptr(slavefd), sname)
46
47	// pushing terminal driver STREAMS modules as per pts(7)
48	for _, mod := range([]string{"ptem", "ldterm", "ttcompat"}) {
49		err = streams_push(t, mod)
50		if err != nil {
51			return nil, nil, err
52		}
53	}
54
55	return p, t, nil
56}
57
58func minor(x uint64) uint64 {
59	return x & 0377
60}
61
62func ptsdev(fd uintptr) uint64 {
63	istr := strioctl{ISPTM, 0, 0, nil}
64	err := ioctl(fd, I_STR, uintptr(unsafe.Pointer(&istr)))
65	if err != nil {
66		return NODEV
67	}
68	var status unix.Stat_t
69	err = unix.Fstat(int(fd), &status)
70	if err != nil {
71		return NODEV
72	}
73	return uint64(minor(status.Rdev))
74}
75
76func ptsname(f *os.File) (string, error) {
77	dev := ptsdev(f.Fd())
78	if dev == NODEV {
79		return "", errors.New("not a master pty")
80	}
81	fn := "/dev/pts/" + strconv.FormatInt(int64(dev), 10)
82	// access(2) creates the slave device (if the pty exists)
83	// F_OK == 0 (unistd.h)
84	err := unix.Access(fn, 0)
85	if err != nil {
86		return "", err
87	}
88	return fn, nil
89}
90
91type pt_own struct {
92	pto_ruid int32
93	pto_rgid int32
94}
95
96func grantpt(f *os.File) error {
97	if ptsdev(f.Fd()) == NODEV {
98		return errors.New("not a master pty")
99	}
100	var pto pt_own
101	pto.pto_ruid = int32(os.Getuid())
102	// XXX should first attempt to get gid of DEFAULT_TTY_GROUP="tty"
103	pto.pto_rgid = int32(os.Getgid())
104	var istr strioctl
105	istr.ic_cmd = OWNERPT
106	istr.ic_timout = 0
107	istr.ic_len = int32(unsafe.Sizeof(istr))
108	istr.ic_dp = unsafe.Pointer(&pto)
109	err := ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
110	if err != nil {
111		return errors.New("access denied")
112	}
113	return nil
114}
115
116func unlockpt(f *os.File) error {
117	istr := strioctl{UNLKPT, 0, 0, nil}
118	return ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
119}
120
121// push STREAMS modules if not already done so
122func streams_push(f *os.File, mod string) error {
123	var err error
124	buf := []byte(mod)
125	// XXX I_FIND is not returning an error when the module
126	// is already pushed even though truss reports a return
127	// value of 1. A bug in the Go Solaris syscall interface?
128	// XXX without this we are at risk of the issue
129	// https://www.illumos.org/issues/9042
130	// but since we are not using libc or XPG4.2, we should not be
131	// double-pushing modules
132
133	err = ioctl(f.Fd(), I_FIND, uintptr(unsafe.Pointer(&buf[0])))
134	if err != nil {
135		return nil
136	}
137	err = ioctl(f.Fd(), I_PUSH, uintptr(unsafe.Pointer(&buf[0])))
138	return err
139}
140