From 13c571dda34bd85e6f7aa2e642fc13b643576a62 Mon Sep 17 00:00:00 2001 From: Mac Malainey Date: Mon, 28 Oct 2024 13:11:43 -0400 Subject: [PATCH 1/2] add z/OS support --- io_test.go | 14 +++++++++- pty_unsupported.go | 4 +-- pty_zos.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 pty_zos.go diff --git a/io_test.go b/io_test.go index 2d88a50..e699ec1 100644 --- a/io_test.go +++ b/io_test.go @@ -12,6 +12,8 @@ import ( "syscall" "testing" "time" + + "golang.org/x/sys/unix" ) const ( @@ -106,9 +108,19 @@ 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() }) + // z/OS doesn't open a pollable FD - fix that here + if runtime.GOOS == "zos" { + if _, err = unix.Fcntl(uintptr(ptmx.Fd()), unix.F_SETFL, unix.O_NONBLOCK); err != nil { + t.Fatalf("Error: zos-nonblock: %s.\n", err) + } + ptmx = os.NewFile(ptmx.Fd(), "/dev/ptmx") + t.Cleanup(func() { _ = ptmx.Close() }) + } + 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..7cf494a --- /dev/null +++ b/pty_zos.go @@ -0,0 +1,68 @@ +//go:build zos +// +build zos + +package pty + +import ( + "os" + "syscall" + + "golang.org/x/sys/unix" +) + +func open() (pty, tty *os.File, err error) { + ptmxfd, err := unix.Posix_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 := unix.F_cnvrt{Cvtcmd: unix.SETCVTON, Pccsid: 0, Fccsid: 1047} + if _, err = unix.Fcntl(uintptr(ptmxfd), unix.F_CONTROL_CVT, &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 := unix.Ptsname(ptmxfd) + if err != nil { + return nil, nil, err + } + + _, err = unix.Grantpt(ptmxfd) + if err != nil { + return nil, nil, err + } + + if _, err = unix.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 = unix.Fcntl(uintptr(ptsfd), unix.F_CONTROL_CVT, &cvtreq); err != nil { + return nil, nil, err + } + + t := os.NewFile(uintptr(ptsfd), sname) + if err != nil { + return nil, nil, err + } + + return p, t, nil +} + From 7c00df359733d401c488a76705b51ecd4cb95af1 Mon Sep 17 00:00:00 2001 From: Mac Malainey Date: Thu, 31 Oct 2024 10:42:45 -0400 Subject: [PATCH 2/2] remove x/sys dependency for zos --- fd_helper_other_test.go | 14 +++++++ fd_helper_zos_test.go | 20 +++++++++ io_test.go | 11 +---- pty_zos.go | 91 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 fd_helper_other_test.go create mode 100644 fd_helper_zos_test.go 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 e699ec1..922f9c1 100644 --- a/io_test.go +++ b/io_test.go @@ -12,8 +12,6 @@ import ( "syscall" "testing" "time" - - "golang.org/x/sys/unix" ) const ( @@ -112,14 +110,7 @@ func prepare(t *testing.T) (ptmx *os.File, done func()) { t.Cleanup(func() { _ = _ptmx.Close() }) t.Cleanup(func() { _ = pts.Close() }) - // z/OS doesn't open a pollable FD - fix that here - if runtime.GOOS == "zos" { - if _, err = unix.Fcntl(uintptr(ptmx.Fd()), unix.F_SETFL, unix.O_NONBLOCK); err != nil { - t.Fatalf("Error: zos-nonblock: %s.\n", err) - } - ptmx = os.NewFile(ptmx.Fd(), "/dev/ptmx") - t.Cleanup(func() { _ = ptmx.Close() }) - } + ptmx = getNonBlockingFile(t, ptmx, "/dev/ptmx") ctx, done := context.WithCancel(context.Background()) t.Cleanup(done) diff --git a/pty_zos.go b/pty_zos.go index 7cf494a..18e61e1 100644 --- a/pty_zos.go +++ b/pty_zos.go @@ -5,24 +5,44 @@ 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 - "golang.org/x/sys/unix" + 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 := unix.Posix_openpt(os.O_RDWR|syscall.O_NOCTTY) + 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 := unix.F_cnvrt{Cvtcmd: unix.SETCVTON, Pccsid: 0, Fccsid: 1047} - if _, err = unix.Fcntl(uintptr(ptmxfd), unix.F_CONTROL_CVT, &cvtreq); err != nil { + 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 @@ -35,17 +55,17 @@ func open() (pty, tty *os.File, err error) { } }() - sname, err := unix.Ptsname(ptmxfd) + sname, err := ptsname(ptmxfd) if err != nil { return nil, nil, err } - _, err = unix.Grantpt(ptmxfd) + _, err = grantpt(ptmxfd) if err != nil { return nil, nil, err } - if _, err = unix.Unlockpt(ptmxfd); err != nil { + if _, err = unlockpt(ptmxfd); err != nil { return nil, nil, err } @@ -54,7 +74,7 @@ func open() (pty, tty *os.File, err error) { return nil, nil, err } - if _, err = unix.Fcntl(uintptr(ptsfd), unix.F_CONTROL_CVT, &cvtreq); err != nil { + if _, err = fcntl(uintptr(ptsfd), F_CONTROL_CVT, uintptr(unsafe.Pointer(&cvtreq))); err != nil { return nil, nil, err } @@ -66,3 +86,56 @@ func open() (pty, tty *os.File, err error) { 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]) +}