summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-03-23 20:44:52 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-03-23 20:44:52 +0000
commit5235d82d87c09fa18de8837b458d64927a9db65c (patch)
tree4e421c8e194800aa68ed81f344f6bc91371ab383
parentInitial commit. (diff)
downloadgolang-github-creack-pty-upstream.tar.xz
golang-github-creack-pty-upstream.zip
Adding upstream version 1.1.11.upstream/1.1.11upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.gitignore4
-rw-r--r--Dockerfile.riscv14
-rw-r--r--LICENSE23
-rw-r--r--README.md100
-rw-r--r--doc.go16
-rw-r--r--go.mod4
-rw-r--r--ioctl.go13
-rw-r--r--ioctl_bsd.go39
-rw-r--r--ioctl_solaris.go30
-rwxr-xr-xmktypes.bash19
-rw-r--r--pty_darwin.go65
-rw-r--r--pty_dragonfly.go80
-rw-r--r--pty_freebsd.go78
-rw-r--r--pty_linux.go51
-rw-r--r--pty_openbsd.go33
-rw-r--r--pty_solaris.go139
-rw-r--r--pty_unsupported.go11
-rw-r--r--run.go74
-rwxr-xr-xtest_crosscompile.sh50
-rw-r--r--types.go10
-rw-r--r--types_dragonfly.go17
-rw-r--r--types_freebsd.go15
-rw-r--r--types_openbsd.go14
-rw-r--r--util.go64
-rw-r--r--util_solaris.go51
-rw-r--r--ztypes_386.go9
-rw-r--r--ztypes_amd64.go9
-rw-r--r--ztypes_arm.go9
-rw-r--r--ztypes_arm64.go11
-rw-r--r--ztypes_dragonfly_amd64.go14
-rw-r--r--ztypes_freebsd_386.go13
-rw-r--r--ztypes_freebsd_amd64.go14
-rw-r--r--ztypes_freebsd_arm.go13
-rw-r--r--ztypes_freebsd_arm64.go13
-rw-r--r--ztypes_mipsx.go12
-rw-r--r--ztypes_openbsd_32bit_int.go13
-rw-r--r--ztypes_ppc64.go11
-rw-r--r--ztypes_ppc64le.go11
-rw-r--r--ztypes_riscvx.go11
-rw-r--r--ztypes_s390x.go11
40 files changed, 1188 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1f0a99f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+[568].out
+_go*
+_test*
+_obj
diff --git a/Dockerfile.riscv b/Dockerfile.riscv
new file mode 100644
index 0000000..adfdf82
--- /dev/null
+++ b/Dockerfile.riscv
@@ -0,0 +1,14 @@
+FROM golang:1.13
+
+# Clone and complie a riscv compatible version of the go compiler.
+RUN git clone https://review.gerrithub.io/riscv/riscv-go /riscv-go
+# riscvdev branch HEAD as of 2019-06-29.
+RUN cd /riscv-go && git checkout 04885fddd096d09d4450726064d06dd107e374bf
+ENV PATH=/riscv-go/misc/riscv:/riscv-go/bin:$PATH
+RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash
+ENV GOROOT=/riscv-go
+
+# Make sure we compile.
+WORKDIR pty
+ADD . .
+RUN GOOS=linux GOARCH=riscv go build
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6b7558b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2011 Keith Rarick
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute,
+sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall
+be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5275014
--- /dev/null
+++ b/README.md
@@ -0,0 +1,100 @@
+# pty
+
+Pty is a Go package for using unix pseudo-terminals.
+
+## Install
+
+ go get github.com/creack/pty
+
+## Example
+
+### Command
+
+```go
+package main
+
+import (
+ "github.com/creack/pty"
+ "io"
+ "os"
+ "os/exec"
+)
+
+func main() {
+ c := exec.Command("grep", "--color=auto", "bar")
+ f, err := pty.Start(c)
+ if err != nil {
+ panic(err)
+ }
+
+ go func() {
+ f.Write([]byte("foo\n"))
+ f.Write([]byte("bar\n"))
+ f.Write([]byte("baz\n"))
+ f.Write([]byte{4}) // EOT
+ }()
+ io.Copy(os.Stdout, f)
+}
+```
+
+### Shell
+
+```go
+package main
+
+import (
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "os/signal"
+ "syscall"
+
+ "github.com/creack/pty"
+ "golang.org/x/crypto/ssh/terminal"
+)
+
+func test() error {
+ // Create arbitrary command.
+ c := exec.Command("bash")
+
+ // Start the command with a pty.
+ ptmx, err := pty.Start(c)
+ if err != nil {
+ return err
+ }
+ // Make sure to close the pty at the end.
+ defer func() { _ = ptmx.Close() }() // Best effort.
+
+ // Handle pty size.
+ ch := make(chan os.Signal, 1)
+ signal.Notify(ch, syscall.SIGWINCH)
+ go func() {
+ for range ch {
+ if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
+ log.Printf("error resizing pty: %s", err)
+ }
+ }
+ }()
+ ch <- syscall.SIGWINCH // Initial resize.
+
+ // Set stdin in raw mode.
+ oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
+ if err != nil {
+ panic(err)
+ }
+ defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
+
+ // Copy stdin to the pty and the pty to stdout.
+ go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
+ _, _ = io.Copy(os.Stdout, ptmx)
+
+ return nil
+}
+
+func main() {
+ if err := test(); err != nil {
+ log.Fatal(err)
+ }
+}
+```
diff --git a/doc.go b/doc.go
new file mode 100644
index 0000000..190cfbe
--- /dev/null
+++ b/doc.go
@@ -0,0 +1,16 @@
+// Package pty provides functions for working with Unix terminals.
+package pty
+
+import (
+ "errors"
+ "os"
+)
+
+// ErrUnsupported is returned if a function is not
+// available on the current platform.
+var ErrUnsupported = errors.New("unsupported")
+
+// Opens a pty and its corresponding tty.
+func Open() (pty, tty *os.File, err error) {
+ return open()
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..e48deca
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,4 @@
+module github.com/creack/pty
+
+go 1.13
+
diff --git a/ioctl.go b/ioctl.go
new file mode 100644
index 0000000..c85cdcd
--- /dev/null
+++ b/ioctl.go
@@ -0,0 +1,13 @@
+// +build !windows,!solaris
+
+package pty
+
+import "syscall"
+
+func ioctl(fd, cmd, ptr uintptr) error {
+ _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
+ if e != 0 {
+ return e
+ }
+ return nil
+}
diff --git a/ioctl_bsd.go b/ioctl_bsd.go
new file mode 100644
index 0000000..73b12c5
--- /dev/null
+++ b/ioctl_bsd.go
@@ -0,0 +1,39 @@
+// +build darwin dragonfly freebsd netbsd openbsd
+
+package pty
+
+// from <sys/ioccom.h>
+const (
+ _IOC_VOID uintptr = 0x20000000
+ _IOC_OUT uintptr = 0x40000000
+ _IOC_IN uintptr = 0x80000000
+ _IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN
+ _IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN
+
+ _IOC_PARAM_SHIFT = 13
+ _IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
+)
+
+func _IOC_PARM_LEN(ioctl uintptr) uintptr {
+ return (ioctl >> 16) & _IOC_PARAM_MASK
+}
+
+func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr {
+ return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num
+}
+
+func _IO(group byte, ioctl_num uintptr) uintptr {
+ return _IOC(_IOC_VOID, group, ioctl_num, 0)
+}
+
+func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
+ return _IOC(_IOC_OUT, group, ioctl_num, param_len)
+}
+
+func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
+ return _IOC(_IOC_IN, group, ioctl_num, param_len)
+}
+
+func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
+ return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len)
+}
diff --git a/ioctl_solaris.go b/ioctl_solaris.go
new file mode 100644
index 0000000..f63985f
--- /dev/null
+++ b/ioctl_solaris.go
@@ -0,0 +1,30 @@
+package pty
+
+import (
+ "golang.org/x/sys/unix"
+ "unsafe"
+)
+
+const (
+ // see /usr/include/sys/stropts.h
+ I_PUSH = uintptr((int32('S')<<8 | 002))
+ I_STR = uintptr((int32('S')<<8 | 010))
+ I_FIND = uintptr((int32('S')<<8 | 013))
+ // see /usr/include/sys/ptms.h
+ ISPTM = (int32('P') << 8) | 1
+ UNLKPT = (int32('P') << 8) | 2
+ PTSSTTY = (int32('P') << 8) | 3
+ ZONEPT = (int32('P') << 8) | 4
+ OWNERPT = (int32('P') << 8) | 5
+)
+
+type strioctl struct {
+ ic_cmd int32
+ ic_timout int32
+ ic_len int32
+ ic_dp unsafe.Pointer
+}
+
+func ioctl(fd, cmd, ptr uintptr) error {
+ return unix.IoctlSetInt(int(fd), uint(cmd), int(ptr))
+}
diff --git a/mktypes.bash b/mktypes.bash
new file mode 100755
index 0000000..82ee167
--- /dev/null
+++ b/mktypes.bash
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+GOOSARCH="${GOOS}_${GOARCH}"
+case "$GOOSARCH" in
+_* | *_ | _)
+ echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
+ exit 1
+ ;;
+esac
+
+GODEFS="go tool cgo -godefs"
+
+$GODEFS types.go |gofmt > ztypes_$GOARCH.go
+
+case $GOOS in
+freebsd|dragonfly|openbsd)
+ $GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
+ ;;
+esac
diff --git a/pty_darwin.go b/pty_darwin.go
new file mode 100644
index 0000000..6344b6b
--- /dev/null
+++ b/pty_darwin.go
@@ -0,0 +1,65 @@
+package pty
+
+import (
+ "errors"
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+func open() (pty, tty *os.File, err error) {
+ pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ p := os.NewFile(uintptr(pFD), "/dev/ptmx")
+ // In case of error after this point, make sure we close the ptmx fd.
+ defer func() {
+ if err != nil {
+ _ = p.Close() // Best effort.
+ }
+ }()
+
+ sname, err := ptsname(p)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if err := grantpt(p); err != nil {
+ return nil, nil, err
+ }
+
+ if err := unlockpt(p); err != nil {
+ return nil, nil, err
+ }
+
+ t, err := os.OpenFile(sname, os.O_RDWR, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ return p, t, nil
+}
+
+func ptsname(f *os.File) (string, error) {
+ n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
+
+ err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
+ if err != nil {
+ return "", err
+ }
+
+ for i, c := range n {
+ if c == 0 {
+ return string(n[:i]), nil
+ }
+ }
+ return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
+}
+
+func grantpt(f *os.File) error {
+ return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
+}
+
+func unlockpt(f *os.File) error {
+ return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
+}
diff --git a/pty_dragonfly.go b/pty_dragonfly.go
new file mode 100644
index 0000000..b7d1f20
--- /dev/null
+++ b/pty_dragonfly.go
@@ -0,0 +1,80 @@
+package pty
+
+import (
+ "errors"
+ "os"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+// same code as pty_darwin.go
+func open() (pty, tty *os.File, err error) {
+ p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ // In case of error after this point, make sure we close the ptmx fd.
+ defer func() {
+ if err != nil {
+ _ = p.Close() // Best effort.
+ }
+ }()
+
+ sname, err := ptsname(p)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if err := grantpt(p); err != nil {
+ return nil, nil, err
+ }
+
+ if err := unlockpt(p); err != nil {
+ return nil, nil, err
+ }
+
+ t, err := os.OpenFile(sname, os.O_RDWR, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ return p, t, nil
+}
+
+func grantpt(f *os.File) error {
+ _, err := isptmaster(f.Fd())
+ return err
+}
+
+func unlockpt(f *os.File) error {
+ _, err := isptmaster(f.Fd())
+ return err
+}
+
+func isptmaster(fd uintptr) (bool, error) {
+ err := ioctl(fd, syscall.TIOCISPTMASTER, 0)
+ return err == nil, err
+}
+
+var (
+ emptyFiodgnameArg fiodgnameArg
+ ioctl_FIODNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
+)
+
+func ptsname(f *os.File) (string, error) {
+ name := make([]byte, _C_SPECNAMELEN)
+ fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}}
+
+ err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa)))
+ if err != nil {
+ return "", err
+ }
+
+ for i, c := range name {
+ if c == 0 {
+ s := "/dev/" + string(name[:i])
+ return strings.Replace(s, "ptm", "pts", -1), nil
+ }
+ }
+ return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
+}
diff --git a/pty_freebsd.go b/pty_freebsd.go
new file mode 100644
index 0000000..63b6d91
--- /dev/null
+++ b/pty_freebsd.go
@@ -0,0 +1,78 @@
+package pty
+
+import (
+ "errors"
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+func posixOpenpt(oflag int) (fd int, err error) {
+ r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
+ fd = int(r0)
+ if e1 != 0 {
+ err = e1
+ }
+ return fd, err
+}
+
+func open() (pty, tty *os.File, err error) {
+ fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC)
+ if err != nil {
+ return nil, nil, err
+ }
+ p := os.NewFile(uintptr(fd), "/dev/pts")
+ // In case of error after this point, make sure we close the pts fd.
+ defer func() {
+ if err != nil {
+ _ = p.Close() // Best effort.
+ }
+ }()
+
+ sname, err := ptsname(p)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ return p, t, nil
+}
+
+func isptmaster(fd uintptr) (bool, error) {
+ err := ioctl(fd, syscall.TIOCPTMASTER, 0)
+ return err == nil, err
+}
+
+var (
+ emptyFiodgnameArg fiodgnameArg
+ ioctlFIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
+)
+
+func ptsname(f *os.File) (string, error) {
+ master, err := isptmaster(f.Fd())
+ if err != nil {
+ return "", err
+ }
+ if !master {
+ return "", syscall.EINVAL
+ }
+
+ const n = _C_SPECNAMELEN + 1
+ var (
+ buf = make([]byte, n)
+ arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
+ )
+ if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil {
+ return "", err
+ }
+
+ for i, c := range buf {
+ if c == 0 {
+ return string(buf[:i]), nil
+ }
+ }
+ return "", errors.New("FIODGNAME string not NUL-terminated")
+}
diff --git a/pty_linux.go b/pty_linux.go
new file mode 100644
index 0000000..4a833de
--- /dev/null
+++ b/pty_linux.go
@@ -0,0 +1,51 @@
+package pty
+
+import (
+ "os"
+ "strconv"
+ "syscall"
+ "unsafe"
+)
+
+func open() (pty, tty *os.File, err error) {
+ p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ // In case of error after this point, make sure we close the ptmx fd.
+ defer func() {
+ if err != nil {
+ _ = p.Close() // Best effort.
+ }
+ }()
+
+ sname, err := ptsname(p)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if err := unlockpt(p); err != nil {
+ return nil, nil, err
+ }
+
+ t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ return p, t, nil
+}
+
+func ptsname(f *os.File) (string, error) {
+ var n _C_uint
+ err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
+ if err != nil {
+ return "", err
+ }
+ return "/dev/pts/" + strconv.Itoa(int(n)), nil
+}
+
+func unlockpt(f *os.File) error {
+ var u _C_int
+ // use TIOCSPTLCK with a pointer to zero to clear the lock
+ return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
+}
diff --git a/pty_openbsd.go b/pty_openbsd.go
new file mode 100644
index 0000000..a6a35d1
--- /dev/null
+++ b/pty_openbsd.go
@@ -0,0 +1,33 @@
+package pty
+
+import (
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+func open() (pty, tty *os.File, err error) {
+ /*
+ * from ptm(4):
+ * The PTMGET command allocates a free pseudo terminal, changes its
+ * ownership to the caller, revokes the access privileges for all previous
+ * users, opens the file descriptors for the pty and tty devices and
+ * returns them to the caller in struct ptmget.
+ */
+
+ p, err := os.OpenFile("/dev/ptm", os.O_RDWR|syscall.O_CLOEXEC, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer p.Close()
+
+ var ptm ptmget
+ if err := ioctl(p.Fd(), uintptr(ioctl_PTMGET), uintptr(unsafe.Pointer(&ptm))); err != nil {
+ return nil, nil, err
+ }
+
+ pty = os.NewFile(uintptr(ptm.Cfd), "/dev/ptm")
+ tty = os.NewFile(uintptr(ptm.Sfd), "/dev/ptm")
+
+ return pty, tty, nil
+}
diff --git a/pty_solaris.go b/pty_solaris.go
new file mode 100644
index 0000000..09ec1b7
--- /dev/null
+++ b/pty_solaris.go
@@ -0,0 +1,139 @@
+package pty
+
+/* based on:
+http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/pt.c
+*/
+
+import (
+ "errors"
+ "golang.org/x/sys/unix"
+ "os"
+ "strconv"
+ "syscall"
+ "unsafe"
+)
+
+const NODEV = ^uint64(0)
+
+func open() (pty, tty *os.File, err error) {
+ masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|unix.O_NOCTTY, 0)
+ //masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC|unix.O_NOCTTY, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ p := os.NewFile(uintptr(masterfd), "/dev/ptmx")
+
+ sname, err := ptsname(p)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ err = grantpt(p)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ err = unlockpt(p)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ slavefd, err := syscall.Open(sname, os.O_RDWR|unix.O_NOCTTY, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ t := os.NewFile(uintptr(slavefd), sname)
+
+ // pushing terminal driver STREAMS modules as per pts(7)
+ for _, mod := range([]string{"ptem", "ldterm", "ttcompat"}) {
+ err = streams_push(t, mod)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ return p, t, nil
+}
+
+func minor(x uint64) uint64 {
+ return x & 0377
+}
+
+func ptsdev(fd uintptr) uint64 {
+ istr := strioctl{ISPTM, 0, 0, nil}
+ err := ioctl(fd, I_STR, uintptr(unsafe.Pointer(&istr)))
+ if err != nil {
+ return NODEV
+ }
+ var status unix.Stat_t
+ err = unix.Fstat(int(fd), &status)
+ if err != nil {
+ return NODEV
+ }
+ return uint64(minor(status.Rdev))
+}
+
+func ptsname(f *os.File) (string, error) {
+ dev := ptsdev(f.Fd())
+ if dev == NODEV {
+ return "", errors.New("not a master pty")
+ }
+ fn := "/dev/pts/" + strconv.FormatInt(int64(dev), 10)
+ // access(2) creates the slave device (if the pty exists)
+ // F_OK == 0 (unistd.h)
+ err := unix.Access(fn, 0)
+ if err != nil {
+ return "", err
+ }
+ return fn, nil
+}
+
+type pt_own struct {
+ pto_ruid int32
+ pto_rgid int32
+}
+
+func grantpt(f *os.File) error {
+ if ptsdev(f.Fd()) == NODEV {
+ return errors.New("not a master pty")
+ }
+ var pto pt_own
+ pto.pto_ruid = int32(os.Getuid())
+ // XXX should first attempt to get gid of DEFAULT_TTY_GROUP="tty"
+ pto.pto_rgid = int32(os.Getgid())
+ var istr strioctl
+ istr.ic_cmd = OWNERPT
+ istr.ic_timout = 0
+ istr.ic_len = int32(unsafe.Sizeof(istr))
+ istr.ic_dp = unsafe.Pointer(&pto)
+ err := ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
+ if err != nil {
+ return errors.New("access denied")
+ }
+ return nil
+}
+
+func unlockpt(f *os.File) error {
+ istr := strioctl{UNLKPT, 0, 0, nil}
+ return ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
+}
+
+// push STREAMS modules if not already done so
+func streams_push(f *os.File, mod string) error {
+ var err error
+ buf := []byte(mod)
+ // XXX I_FIND is not returning an error when the module
+ // is already pushed even though truss reports a return
+ // value of 1. A bug in the Go Solaris syscall interface?
+ // XXX without this we are at risk of the issue
+ // https://www.illumos.org/issues/9042
+ // but since we are not using libc or XPG4.2, we should not be
+ // double-pushing modules
+
+ err = ioctl(f.Fd(), I_FIND, uintptr(unsafe.Pointer(&buf[0])))
+ if err != nil {
+ return nil
+ }
+ err = ioctl(f.Fd(), I_PUSH, uintptr(unsafe.Pointer(&buf[0])))
+ return err
+}
diff --git a/pty_unsupported.go b/pty_unsupported.go
new file mode 100644
index 0000000..ceb425b
--- /dev/null
+++ b/pty_unsupported.go
@@ -0,0 +1,11 @@
+// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd,!solaris
+
+package pty
+
+import (
+ "os"
+)
+
+func open() (pty, tty *os.File, err error) {
+ return nil, nil, ErrUnsupported
+}
diff --git a/run.go b/run.go
new file mode 100644
index 0000000..b079425
--- /dev/null
+++ b/run.go
@@ -0,0 +1,74 @@
+// +build !windows
+
+package pty
+
+import (
+ "os"
+ "os/exec"
+ "syscall"
+)
+
+// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
+// and c.Stderr, calls c.Start, and returns the File of the tty's
+// corresponding pty.
+//
+// Starts the process in a new session and sets the controlling terminal.
+func Start(c *exec.Cmd) (pty *os.File, err error) {
+ return StartWithSize(c, nil)
+}
+
+// StartWithSize assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
+// and c.Stderr, calls c.Start, and returns the File of the tty's
+// corresponding pty.
+//
+// This will resize the pty to the specified size before starting the command.
+// Starts the process in a new session and sets the controlling terminal.
+func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) {
+ if c.SysProcAttr == nil {
+ c.SysProcAttr = &syscall.SysProcAttr{}
+ }
+ c.SysProcAttr.Setsid = true
+ c.SysProcAttr.Setctty = true
+ return StartWithAttrs(c, sz, c.SysProcAttr)
+}
+
+// StartWithAttrs assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
+// and c.Stderr, calls c.Start, and returns the File of the tty's
+// corresponding pty.
+//
+// This will resize the pty to the specified size before starting the command if a size is provided.
+// The `attrs` parameter overrides the one set in c.SysProcAttr.
+//
+// This should generally not be needed. Used in some edge cases where it is needed to create a pty
+// without a controlling terminal.
+func StartWithAttrs(c *exec.Cmd, sz *Winsize, attrs *syscall.SysProcAttr) (pty *os.File, err error) {
+ pty, tty, err := Open()
+ if err != nil {
+ return nil, err
+ }
+ defer tty.Close()
+
+ if sz != nil {
+ if err := Setsize(pty, sz); err != nil {
+ pty.Close()
+ return nil, err
+ }
+ }
+ if c.Stdout == nil {
+ c.Stdout = tty
+ }
+ if c.Stderr == nil {
+ c.Stderr = tty
+ }
+ if c.Stdin == nil {
+ c.Stdin = tty
+ }
+
+ c.SysProcAttr = attrs
+
+ if err := c.Start(); err != nil {
+ _ = pty.Close()
+ return nil, err
+ }
+ return pty, err
+}
diff --git a/test_crosscompile.sh b/test_crosscompile.sh
new file mode 100755
index 0000000..c4b9e37
--- /dev/null
+++ b/test_crosscompile.sh
@@ -0,0 +1,50 @@
+#!/usr/bin/env sh
+
+# Test script checking that all expected os/arch compile properly.
+# Does not actually test the logic, just the compilation so we make sure we don't break code depending on the lib.
+
+echo2() {
+ echo $@ >&2
+}
+
+trap end 0
+end() {
+ [ "$?" = 0 ] && echo2 "Pass." || (echo2 "Fail."; exit 1)
+}
+
+cross() {
+ os=$1
+ shift
+ echo2 "Build for $os."
+ for arch in $@; do
+ echo2 " - $os/$arch"
+ GOOS=$os GOARCH=$arch go build
+ done
+ echo2
+}
+
+set -e
+
+cross linux amd64 386 arm arm64 ppc64 ppc64le s390x mips mipsle mips64 mips64le
+cross darwin amd64 386 arm arm64
+cross freebsd amd64 386 arm
+cross netbsd amd64 386 arm
+cross openbsd amd64 386 arm arm64
+cross dragonfly amd64
+cross solaris amd64
+
+# Not expected to work but should still compile.
+cross windows amd64 386 arm
+
+# TODO: Fix compilation error on openbsd/arm.
+# TODO: Merge the solaris PR.
+
+# Some os/arch require a different compiler. Run in docker.
+if ! hash docker; then
+ # If docker is not present, stop here.
+ return
+fi
+
+echo2 "Build for linux."
+echo2 " - linux/riscv"
+docker build -t test -f Dockerfile.riscv .
diff --git a/types.go b/types.go
new file mode 100644
index 0000000..5aecb6b
--- /dev/null
+++ b/types.go
@@ -0,0 +1,10 @@
+// +build ignore
+
+package pty
+
+import "C"
+
+type (
+ _C_int C.int
+ _C_uint C.uint
+)
diff --git a/types_dragonfly.go b/types_dragonfly.go
new file mode 100644
index 0000000..5c0493b
--- /dev/null
+++ b/types_dragonfly.go
@@ -0,0 +1,17 @@
+// +build ignore
+
+package pty
+
+/*
+#define _KERNEL
+#include <sys/conf.h>
+#include <sys/param.h>
+#include <sys/filio.h>
+*/
+import "C"
+
+const (
+ _C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
+)
+
+type fiodgnameArg C.struct_fiodname_args
diff --git a/types_freebsd.go b/types_freebsd.go
new file mode 100644
index 0000000..ce3eb95
--- /dev/null
+++ b/types_freebsd.go
@@ -0,0 +1,15 @@
+// +build ignore
+
+package pty
+
+/*
+#include <sys/param.h>
+#include <sys/filio.h>
+*/
+import "C"
+
+const (
+ _C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
+)
+
+type fiodgnameArg C.struct_fiodgname_arg
diff --git a/types_openbsd.go b/types_openbsd.go
new file mode 100644
index 0000000..47701b5
--- /dev/null
+++ b/types_openbsd.go
@@ -0,0 +1,14 @@
+// +build ignore
+
+package pty
+
+/*
+#include <sys/time.h>
+#include <stdlib.h>
+#include <sys/tty.h>
+*/
+import "C"
+
+type ptmget C.struct_ptmget
+
+var ioctl_PTMGET = C.PTMGET
diff --git a/util.go b/util.go
new file mode 100644
index 0000000..8fdde0b
--- /dev/null
+++ b/util.go
@@ -0,0 +1,64 @@
+// +build !windows,!solaris
+
+package pty
+
+import (
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+// InheritSize applies the terminal size of pty to tty. This should be run
+// in a signal handler for syscall.SIGWINCH to automatically resize the tty when
+// the pty receives a window size change notification.
+func InheritSize(pty, tty *os.File) error {
+ size, err := GetsizeFull(pty)
+ if err != nil {
+ return err
+ }
+ err = Setsize(tty, size)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// Setsize resizes t to s.
+func Setsize(t *os.File, ws *Winsize) error {
+ return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ)
+}
+
+// GetsizeFull returns the full terminal size description.
+func GetsizeFull(t *os.File) (size *Winsize, err error) {
+ var ws Winsize
+ err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ)
+ return &ws, err
+}
+
+// Getsize returns the number of rows (lines) and cols (positions
+// in each line) in terminal t.
+func Getsize(t *os.File) (rows, cols int, err error) {
+ ws, err := GetsizeFull(t)
+ return int(ws.Rows), int(ws.Cols), err
+}
+
+// Winsize describes the terminal size.
+type Winsize struct {
+ Rows uint16 // ws_row: Number of rows (in cells)
+ Cols uint16 // ws_col: Number of columns (in cells)
+ X uint16 // ws_xpixel: Width in pixels
+ Y uint16 // ws_ypixel: Height in pixels
+}
+
+func windowRectCall(ws *Winsize, fd, a2 uintptr) error {
+ _, _, errno := syscall.Syscall(
+ syscall.SYS_IOCTL,
+ fd,
+ a2,
+ uintptr(unsafe.Pointer(ws)),
+ )
+ if errno != 0 {
+ return syscall.Errno(errno)
+ }
+ return nil
+}
diff --git a/util_solaris.go b/util_solaris.go
new file mode 100644
index 0000000..e889692
--- /dev/null
+++ b/util_solaris.go
@@ -0,0 +1,51 @@
+//
+
+package pty
+
+import (
+ "os"
+ "golang.org/x/sys/unix"
+)
+
+const (
+ TIOCGWINSZ = 21608 // 'T' << 8 | 104
+ TIOCSWINSZ = 21607 // 'T' << 8 | 103
+)
+
+// Winsize describes the terminal size.
+type Winsize struct {
+ Rows uint16 // ws_row: Number of rows (in cells)
+ Cols uint16 // ws_col: Number of columns (in cells)
+ X uint16 // ws_xpixel: Width in pixels
+ Y uint16 // ws_ypixel: Height in pixels
+}
+
+// GetsizeFull returns the full terminal size description.
+func GetsizeFull(t *os.File) (size *Winsize, err error) {
+ var wsz *unix.Winsize
+ wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
+
+ if err != nil {
+ return nil, err
+ } else {
+ return &Winsize{wsz.Row, wsz.Col, wsz.Xpixel, wsz.Ypixel}, nil
+ }
+}
+
+// Get Windows Size
+func Getsize(t *os.File) (rows, cols int, err error) {
+ var wsz *unix.Winsize
+ wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
+
+ if err != nil {
+ return 80, 25, err
+ } else {
+ return int(wsz.Row), int(wsz.Col), nil
+ }
+}
+
+// Setsize resizes t to s.
+func Setsize(t *os.File, ws *Winsize) error {
+ wsz := unix.Winsize{ws.Rows, ws.Cols, ws.X, ws.Y}
+ return unix.IoctlSetWinsize(int(t.Fd()), TIOCSWINSZ, &wsz)
+}
diff --git a/ztypes_386.go b/ztypes_386.go
new file mode 100644
index 0000000..ff0b8fd
--- /dev/null
+++ b/ztypes_386.go
@@ -0,0 +1,9 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types.go
+
+package pty
+
+type (
+ _C_int int32
+ _C_uint uint32
+)
diff --git a/ztypes_amd64.go b/ztypes_amd64.go
new file mode 100644
index 0000000..ff0b8fd
--- /dev/null
+++ b/ztypes_amd64.go
@@ -0,0 +1,9 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types.go
+
+package pty
+
+type (
+ _C_int int32
+ _C_uint uint32
+)
diff --git a/ztypes_arm.go b/ztypes_arm.go
new file mode 100644
index 0000000..ff0b8fd
--- /dev/null
+++ b/ztypes_arm.go
@@ -0,0 +1,9 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types.go
+
+package pty
+
+type (
+ _C_int int32
+ _C_uint uint32
+)
diff --git a/ztypes_arm64.go b/ztypes_arm64.go
new file mode 100644
index 0000000..6c29a4b
--- /dev/null
+++ b/ztypes_arm64.go
@@ -0,0 +1,11 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types.go
+
+// +build arm64
+
+package pty
+
+type (
+ _C_int int32
+ _C_uint uint32
+)
diff --git a/ztypes_dragonfly_amd64.go b/ztypes_dragonfly_amd64.go
new file mode 100644
index 0000000..6b0ba03
--- /dev/null
+++ b/ztypes_dragonfly_amd64.go
@@ -0,0 +1,14 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types_dragonfly.go
+
+package pty
+
+const (
+ _C_SPECNAMELEN = 0x3f
+)
+
+type fiodgnameArg struct {
+ Name *byte
+ Len uint32
+ Pad_cgo_0 [4]byte
+}
diff --git a/ztypes_freebsd_386.go b/ztypes_freebsd_386.go
new file mode 100644
index 0000000..d997537
--- /dev/null
+++ b/ztypes_freebsd_386.go
@@ -0,0 +1,13 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types_freebsd.go
+
+package pty
+
+const (
+ _C_SPECNAMELEN = 0x3f
+)
+
+type fiodgnameArg struct {
+ Len int32
+ Buf *byte
+}
diff --git a/ztypes_freebsd_amd64.go b/ztypes_freebsd_amd64.go
new file mode 100644
index 0000000..5fa102f
--- /dev/null
+++ b/ztypes_freebsd_amd64.go
@@ -0,0 +1,14 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types_freebsd.go
+
+package pty
+
+const (
+ _C_SPECNAMELEN = 0x3f
+)
+
+type fiodgnameArg struct {
+ Len int32
+ Pad_cgo_0 [4]byte
+ Buf *byte
+}
diff --git a/ztypes_freebsd_arm.go b/ztypes_freebsd_arm.go
new file mode 100644
index 0000000..d997537
--- /dev/null
+++ b/ztypes_freebsd_arm.go
@@ -0,0 +1,13 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types_freebsd.go
+
+package pty
+
+const (
+ _C_SPECNAMELEN = 0x3f
+)
+
+type fiodgnameArg struct {
+ Len int32
+ Buf *byte
+}
diff --git a/ztypes_freebsd_arm64.go b/ztypes_freebsd_arm64.go
new file mode 100644
index 0000000..4418139
--- /dev/null
+++ b/ztypes_freebsd_arm64.go
@@ -0,0 +1,13 @@
+// Code generated by cmd/cgo -godefs; DO NOT EDIT.
+// cgo -godefs types_freebsd.go
+
+package pty
+
+const (
+ _C_SPECNAMELEN = 0xff
+)
+
+type fiodgnameArg struct {
+ Len int32
+ Buf *byte
+}
diff --git a/ztypes_mipsx.go b/ztypes_mipsx.go
new file mode 100644
index 0000000..f0ce740
--- /dev/null
+++ b/ztypes_mipsx.go
@@ -0,0 +1,12 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types.go
+
+// +build linux
+// +build mips mipsle mips64 mips64le
+
+package pty
+
+type (
+ _C_int int32
+ _C_uint uint32
+)
diff --git a/ztypes_openbsd_32bit_int.go b/ztypes_openbsd_32bit_int.go
new file mode 100644
index 0000000..d7cab4a
--- /dev/null
+++ b/ztypes_openbsd_32bit_int.go
@@ -0,0 +1,13 @@
+// +build openbsd
+// +build 386 amd64 arm arm64
+
+package pty
+
+type ptmget struct {
+ Cfd int32
+ Sfd int32
+ Cn [16]int8
+ Sn [16]int8
+}
+
+var ioctl_PTMGET = 0x40287401
diff --git a/ztypes_ppc64.go b/ztypes_ppc64.go
new file mode 100644
index 0000000..4e1af84
--- /dev/null
+++ b/ztypes_ppc64.go
@@ -0,0 +1,11 @@
+// +build ppc64
+
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types.go
+
+package pty
+
+type (
+ _C_int int32
+ _C_uint uint32
+)
diff --git a/ztypes_ppc64le.go b/ztypes_ppc64le.go
new file mode 100644
index 0000000..e6780f4
--- /dev/null
+++ b/ztypes_ppc64le.go
@@ -0,0 +1,11 @@
+// +build ppc64le
+
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types.go
+
+package pty
+
+type (
+ _C_int int32
+ _C_uint uint32
+)
diff --git a/ztypes_riscvx.go b/ztypes_riscvx.go
new file mode 100644
index 0000000..99eec8e
--- /dev/null
+++ b/ztypes_riscvx.go
@@ -0,0 +1,11 @@
+// Code generated by cmd/cgo -godefs; DO NOT EDIT.
+// cgo -godefs types.go
+
+// +build riscv riscv64
+
+package pty
+
+type (
+ _C_int int32
+ _C_uint uint32
+)
diff --git a/ztypes_s390x.go b/ztypes_s390x.go
new file mode 100644
index 0000000..a7452b6
--- /dev/null
+++ b/ztypes_s390x.go
@@ -0,0 +1,11 @@
+// +build s390x
+
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs types.go
+
+package pty
+
+type (
+ _C_int int32
+ _C_uint uint32
+)