diff --git a/fd_helper_other_test.go b/fd_helper_other_test.go new file mode 100644 index 0000000..9cb5152 --- /dev/null +++ b/fd_helper_other_test.go @@ -0,0 +1,14 @@ +//go:build !zos +// +build !zos + +package pty + +import ( + "os" + "testing" +) + +func getNonBlockingFile(t *testing.T, file *os.File, path string) *os.File { + t.Helper() + return file +} diff --git a/fd_helper_zos_test.go b/fd_helper_zos_test.go new file mode 100644 index 0000000..ffb0373 --- /dev/null +++ b/fd_helper_zos_test.go @@ -0,0 +1,20 @@ +//go:build zos +// +build zos + +package pty + +import ( + "os" + "testing" +) + +func getNonBlockingFile(t *testing.T, file *os.File, path string) *os.File { + t.Helper() + // z/OS doesn't open a pollable FD - fix that here + if _, err := fcntl(uintptr(file.Fd()), F_SETFL, O_NONBLOCK); err != nil { + t.Fatalf("Error: zos-nonblock: %s.\n", err) + } + nf := os.NewFile(file.Fd(), path) + t.Cleanup(func() { _ = nf.Close() }) + return nf +} diff --git a/io_test.go b/io_test.go index 2d88a50..922f9c1 100644 --- a/io_test.go +++ b/io_test.go @@ -106,9 +106,12 @@ func prepare(t *testing.T) (ptmx *os.File, done func()) { if err != nil { t.Fatalf("Error: open: %s.\n", err) } - t.Cleanup(func() { _ = ptmx.Close() }) + _ptmx := ptmx + t.Cleanup(func() { _ = _ptmx.Close() }) t.Cleanup(func() { _ = pts.Close() }) + ptmx = getNonBlockingFile(t, ptmx, "/dev/ptmx") + ctx, done := context.WithCancel(context.Background()) t.Cleanup(done) go func() { diff --git a/pty_unsupported.go b/pty_unsupported.go index c771020..0971dc7 100644 --- a/pty_unsupported.go +++ b/pty_unsupported.go @@ -1,5 +1,5 @@ -//go:build !linux && !darwin && !freebsd && !dragonfly && !netbsd && !openbsd && !solaris -// +build !linux,!darwin,!freebsd,!dragonfly,!netbsd,!openbsd,!solaris +//go:build !linux && !darwin && !freebsd && !dragonfly && !netbsd && !openbsd && !solaris && !zos +// +build !linux,!darwin,!freebsd,!dragonfly,!netbsd,!openbsd,!solaris,!zos package pty diff --git a/pty_zos.go b/pty_zos.go new file mode 100644 index 0000000..18e61e1 --- /dev/null +++ b/pty_zos.go @@ -0,0 +1,141 @@ +//go:build zos +// +build zos + +package pty + +import ( + "os" + "runtime" + "syscall" + "unsafe" +) + +const ( + SYS_UNLOCKPT = 0x37B + SYS_GRANTPT = 0x37A + SYS_POSIX_OPENPT = 0xC66 + SYS_FCNTL = 0x18C + SYS___PTSNAME_A = 0x718 + + SETCVTON = 1 + + O_NONBLOCK = 0x04 + + F_SETFL = 4 + F_CONTROL_CVT = 13 +) + +type f_cnvrt struct { + Cvtcmd int32 + Pccsid int16 + Fccsid int16 +} + +func open() (pty, tty *os.File, err error) { + ptmxfd, err := openpt(os.O_RDWR | syscall.O_NOCTTY) + if err != nil { + return nil, nil, err + } + + // Needed for z/OS so that the characters are not garbled if ptyp* is untagged + cvtreq := f_cnvrt{Cvtcmd: SETCVTON, Pccsid: 0, Fccsid: 1047} + if _, err = fcntl(uintptr(ptmxfd), F_CONTROL_CVT, uintptr(unsafe.Pointer(&cvtreq))); err != nil { + return nil, nil, err + } + + p := os.NewFile(uintptr(ptmxfd), "/dev/ptmx") + if p == 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(ptmxfd) + if err != nil { + return nil, nil, err + } + + _, err = grantpt(ptmxfd) + if err != nil { + return nil, nil, err + } + + if _, err = unlockpt(ptmxfd); err != nil { + return nil, nil, err + } + + ptsfd, err := syscall.Open(sname, os.O_RDWR|syscall.O_NOCTTY, 0) + if err != nil { + return nil, nil, err + } + + if _, err = fcntl(uintptr(ptsfd), F_CONTROL_CVT, uintptr(unsafe.Pointer(&cvtreq))); err != nil { + return nil, nil, err + } + + t := os.NewFile(uintptr(ptsfd), sname) + if err != nil { + return nil, nil, err + } + + return p, t, nil +} + +func openpt(oflag int) (fd int, err error) { + r0, _, e1 := runtime.CallLeFuncWithErr(runtime.GetZosLibVec()+SYS_POSIX_OPENPT<<4, uintptr(oflag)) + fd = int(r0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +func fcntl(fd uintptr, cmd int, arg uintptr) (val int, err error) { + r0, _, e1 := runtime.CallLeFuncWithErr(runtime.GetZosLibVec()+SYS_FCNTL<<4, uintptr(fd), uintptr(cmd), arg) + val = int(r0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +func ptsname(fd int) (name string, err error) { + r0, _, e1 := runtime.CallLeFuncWithPtrReturn(runtime.GetZosLibVec()+SYS___PTSNAME_A<<4, uintptr(fd)) + name = u2s(unsafe.Pointer(r0)) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +func grantpt(fildes int) (rc int, err error) { + r0, _, e1 := runtime.CallLeFuncWithErr(runtime.GetZosLibVec()+SYS_GRANTPT<<4, uintptr(fildes)) + rc = int(r0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +func unlockpt(fildes int) (rc int, err error) { + r0, _, e1 := runtime.CallLeFuncWithErr(runtime.GetZosLibVec()+SYS_UNLOCKPT<<4, uintptr(fildes)) + rc = int(r0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +func u2s(cstr unsafe.Pointer) string { + str := (*[1024]uint8)(cstr) + i := 0 + for str[i] != 0 { + i++ + } + return string(str[:i]) +}