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