unix: augment support for zos/s390x

This augments sys/unix support for zos/s390x by
adding a small number of syscalls:
Errno2
Err2ad
W_Getmntent_A (pure ascii version of W_Getmntent)
Select

It also makes Mount and Unmount more Linux-like.
A few necessary constants and types are added,
and some tests.

These changes do not affect other platforms in any way.

Fixes golang/go#45838

Change-Id: I5783784a79b6c80a47cca74f3352bc07ea4ca682
Reviewed-on: https://go-review.googlesource.com/c/sys/+/314950
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Trust: Emmanuel Odeke <emmanuel@orijtech.com>
diff --git a/unix/fdset.go b/unix/fdset.go
index b1e07b2..a8068f9 100644
--- a/unix/fdset.go
+++ b/unix/fdset.go
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
-// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
+//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
+// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
 
 package unix
 
diff --git a/unix/mmap_zos_test.go b/unix/mmap_zos_test.go
index 4c17182..8d9303b 100644
--- a/unix/mmap_zos_test.go
+++ b/unix/mmap_zos_test.go
@@ -33,7 +33,7 @@
 	fmt.Fprintf(destination, "%s\n", "0 <- Flipped between 0 and 1 when test runs successfully")
 	fmt.Fprintf(destination, "%s\n", "//Do not change contents - mmap test relies on this")
 	destination.Close()
-	fd, err := unix.Open(filename, unix.O_RDWR, 0o777)
+	fd, err := unix.Open(filename, unix.O_RDWR, 0777)
 	if err != nil {
 		t.Fatalf("Open: %v", err)
 	}
diff --git a/unix/syscall_zos_s390x.go b/unix/syscall_zos_s390x.go
index 13f58d2..1ffd8bf 100644
--- a/unix/syscall_zos_s390x.go
+++ b/unix/syscall_zos_s390x.go
@@ -222,6 +222,8 @@
 //sys   Creat(path string, mode uint32) (fd int, err error) = SYS___CREAT_A
 //sys	Dup(oldfd int) (fd int, err error)
 //sys	Dup2(oldfd int, newfd int) (err error)
+//sys	Errno2() (er2 int) = SYS___ERRNO2
+//sys	Err2ad() (eadd *int) = SYS___ERR2AD
 //sys	Exit(code int)
 //sys	Fchdir(fd int) (err error)
 //sys	Fchmod(fd int, mode uint32) (err error)
@@ -245,10 +247,12 @@
 //sys   Poll(fds []PollFd, timeout int) (n int, err error) = SYS_POLL
 //sys   Times(tms *Tms) (ticks uintptr, err error) = SYS_TIMES
 //sys   W_Getmntent(buff *byte, size int) (lastsys int, err error) = SYS_W_GETMNTENT
+//sys   W_Getmntent_A(buff *byte, size int) (lastsys int, err error) = SYS___W_GETMNTENT_A
 
-//sys   Mount(path string, filesystem string, fstype string, mtm uint32, parmlen int32, parm string) (err error) = SYS___MOUNT_A
-//sys   Unmount(filesystem string, mtm int) (err error) = SYS___UMOUNT_A
+//sys   mount_LE(path string, filesystem string, fstype string, mtm uint32, parmlen int32, parm string) (err error) = SYS___MOUNT_A
+//sys   unmount(filesystem string, mtm int) (err error) = SYS___UMOUNT_A
 //sys   Chroot(path string) (err error) = SYS___CHROOT_A
+//sys   Select(nmsgsfds int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (ret int, err error) = SYS_SELECT
 //sysnb Uname(buf *Utsname) (err error) = SYS___UNAME_A
 
 func Ptsname(fd int) (name string, err error) {
@@ -1779,3 +1783,47 @@
 func Exec(argv0 string, argv []string, envv []string) error {
 	return syscall.Exec(argv0, argv, envv)
 }
+
+func Mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
+	if needspace := 8 - len(fstype); needspace <= 0 {
+		fstype = fstype[:8]
+	} else {
+		fstype += "        "[:needspace]
+	}
+	return mount_LE(target, source, fstype, uint32(flags), int32(len(data)), data)
+}
+
+func Unmount(name string, mtm int) (err error) {
+	// mountpoint is always a full path and starts with a '/'
+	// check if input string is not a mountpoint but a filesystem name
+	if name[0] != '/' {
+		return unmount(name, mtm)
+	}
+	// treat name as mountpoint
+	b2s := func(arr []byte) string {
+		nulli := bytes.IndexByte(arr, 0)
+		if nulli == -1 {
+			return string(arr)
+		} else {
+			return string(arr[:nulli])
+		}
+	}
+	var buffer struct {
+		header W_Mnth
+		fsinfo [64]W_Mntent
+	}
+	fsCount, err := W_Getmntent_A((*byte)(unsafe.Pointer(&buffer)), int(unsafe.Sizeof(buffer)))
+	if err != nil {
+		return err
+	}
+	if fsCount == 0 {
+		return EINVAL
+	}
+	for i := 0; i < fsCount; i++ {
+		if b2s(buffer.fsinfo[i].Mountpoint[:]) == name {
+			err = unmount(b2s(buffer.fsinfo[i].Fsname[:]), mtm)
+			break
+		}
+	}
+	return err
+}
diff --git a/unix/syscall_zos_test.go b/unix/syscall_zos_test.go
index ef5e391..640d264 100644
--- a/unix/syscall_zos_test.go
+++ b/unix/syscall_zos_test.go
@@ -8,6 +8,7 @@
 package unix_test
 
 import (
+	"bytes"
 	"flag"
 	"fmt"
 	"io/ioutil"
@@ -20,6 +21,7 @@
 	"syscall"
 	"testing"
 	"time"
+	"unsafe"
 
 	"golang.org/x/sys/unix"
 )
@@ -604,3 +606,266 @@
 		os.RemoveAll(d)
 	}
 }
+
+func TestMountUnmount(t *testing.T) {
+	b2s := func(arr []byte) string {
+		nulli := bytes.IndexByte(arr, 0)
+		if nulli == -1 {
+			return string(arr)
+		} else {
+			return string(arr[:nulli])
+		}
+	}
+	// use an available fs
+	var buffer struct {
+		header unix.W_Mnth
+		fsinfo [64]unix.W_Mntent
+	}
+	fsCount, err := unix.W_Getmntent_A((*byte)(unsafe.Pointer(&buffer)), int(unsafe.Sizeof(buffer)))
+	if err != nil {
+		t.Fatalf("W_Getmntent_A returns with error: %s", err.Error())
+	} else if fsCount == 0 {
+		t.Fatalf("W_Getmntent_A returns no entries")
+	}
+	var fs string
+	var fstype string
+	var mountpoint string
+	var available bool = false
+	for i := 0; i < fsCount; i++ {
+		err = unix.Unmount(b2s(buffer.fsinfo[i].Mountpoint[:]), unix.MTM_RDWR)
+		if err != nil {
+			// Unmount and Mount require elevated privilege
+			// If test is run without such permission, skip test
+			if err == unix.EPERM {
+				t.Logf("Permission denied for Unmount. Skipping test (Errno2:  %X)", unix.Errno2())
+				return
+			} else if err == unix.EBUSY {
+				continue
+			} else {
+				t.Fatalf("Unmount returns with error: %s", err.Error())
+			}
+		} else {
+			available = true
+			fs = b2s(buffer.fsinfo[i].Fsname[:])
+			fstype = b2s(buffer.fsinfo[i].Fstname[:])
+			mountpoint = b2s(buffer.fsinfo[i].Mountpoint[:])
+			t.Logf("using file system = %s; fstype = %s and mountpoint = %s\n", fs, fstype, mountpoint)
+			break
+		}
+	}
+	if !available {
+		t.Fatalf("No filesystem available")
+	}
+	// test unmount
+	buffer.header = unix.W_Mnth{}
+	fsCount, err = unix.W_Getmntent_A((*byte)(unsafe.Pointer(&buffer)), int(unsafe.Sizeof(buffer)))
+	if err != nil {
+		t.Fatalf("W_Getmntent_A returns with error: %s", err.Error())
+	}
+	for i := 0; i < fsCount; i++ {
+		if b2s(buffer.fsinfo[i].Fsname[:]) == fs {
+			t.Fatalf("File system found after unmount")
+		}
+	}
+	// test mount
+	err = unix.Mount(fs, mountpoint, fstype, unix.MTM_RDWR, "")
+	if err != nil {
+		t.Fatalf("Mount returns with error: %s", err.Error())
+	}
+	buffer.header = unix.W_Mnth{}
+	fsCount, err = unix.W_Getmntent_A((*byte)(unsafe.Pointer(&buffer)), int(unsafe.Sizeof(buffer)))
+	if err != nil {
+		t.Fatalf("W_Getmntent_A returns with error: %s", err.Error())
+	}
+	fsMounted := false
+	for i := 0; i < fsCount; i++ {
+		if b2s(buffer.fsinfo[i].Fsname[:]) == fs && b2s(buffer.fsinfo[i].Mountpoint[:]) == mountpoint {
+			fsMounted = true
+		}
+	}
+	if !fsMounted {
+		t.Fatalf("%s not mounted after Mount()", fs)
+	}
+}
+
+func TestChroot(t *testing.T) {
+	// create temp dir and tempfile 1
+	tempDir, err := ioutil.TempDir("", "TestChroot")
+	if err != nil {
+		t.Fatalf("TempDir: %s", err.Error())
+	}
+	defer os.RemoveAll(tempDir)
+	f, err := ioutil.TempFile(tempDir, "chroot_test_file")
+	if err != nil {
+		t.Fatalf("TempFile: %s", err.Error())
+	}
+	// chroot temp dir
+	err = unix.Chroot(tempDir)
+	// Chroot requires elevated privilege
+	// If test is run without such permission, skip test
+	if err == unix.EPERM {
+		t.Logf("Denied permission for Chroot. Skipping test (Errno2:  %X)", unix.Errno2())
+		return
+	} else if err != nil {
+		t.Fatalf("Chroot: %s", err.Error())
+	}
+	// check if tempDir contains test file
+	files, err := ioutil.ReadDir("/")
+	if err != nil {
+		t.Fatalf("ReadDir: %s", err.Error())
+	}
+	found := false
+	for _, file := range files {
+		if file.Name() == filepath.Base(f.Name()) {
+			found = true
+			break
+		}
+	}
+	if !found {
+		t.Fatalf("Temp file not found in temp dir")
+	}
+}
+
+func TestFlock(t *testing.T) {
+	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
+		defer os.Exit(0)
+		if len(os.Args) != 3 {
+			fmt.Printf("bad argument")
+			return
+		}
+		fn := os.Args[2]
+		f, err := os.OpenFile(fn, os.O_RDWR, 0755)
+		if err != nil {
+			fmt.Printf("%s", err.Error())
+			return
+		}
+		err = unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)
+		// if the lock we are trying should be locked, ignore EAGAIN error
+		// otherwise, report all errors
+		if err != nil && err != unix.EAGAIN {
+			fmt.Printf("%s", err.Error())
+		}
+	} else {
+		// create temp dir and tempfile 1
+		tempDir, err := ioutil.TempDir("", "TestFlock")
+		if err != nil {
+			t.Fatalf("TempDir: %s", err.Error())
+		}
+		defer os.RemoveAll(tempDir)
+		f, err := ioutil.TempFile(tempDir, "flock_test_file")
+		if err != nil {
+			t.Fatalf("TempFile: %s", err.Error())
+		}
+		fd := int(f.Fd())
+
+		/* Test Case 1
+		 * Try acquiring an occupied lock from another process
+		 */
+		err = unix.Flock(fd, unix.LOCK_EX)
+		if err != nil {
+			t.Fatalf("Flock: %s", err.Error())
+		}
+		cmd := exec.Command(os.Args[0], "-test.run=TestFlock", f.Name())
+		cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
+		out, err := cmd.CombinedOutput()
+		if len(out) > 0 || err != nil {
+			t.Fatalf("child process: %q, %v", out, err)
+		}
+		err = unix.Flock(fd, unix.LOCK_UN)
+		if err != nil {
+			t.Fatalf("Flock: %s", err.Error())
+		}
+
+		/* Test Case 2
+		 * Try locking with Flock and FcntlFlock for same file
+		 */
+		err = unix.Flock(fd, unix.LOCK_EX)
+		if err != nil {
+			t.Fatalf("Flock: %s", err.Error())
+		}
+		flock := unix.Flock_t{
+			Type:   int16(unix.F_WRLCK),
+			Whence: int16(0),
+			Start:  int64(0),
+			Len:    int64(0),
+			Pid:    int32(unix.Getppid()),
+		}
+		err = unix.FcntlFlock(f.Fd(), unix.F_SETLK, &flock)
+		if err != nil {
+			t.Fatalf("FcntlFlock: %s", err.Error())
+		}
+	}
+}
+
+func TestSelect(t *testing.T) {
+	for {
+		n, err := unix.Select(0, nil, nil, nil, &unix.Timeval{Sec: 0, Usec: 0})
+		if err == unix.EINTR {
+			t.Logf("Select interrupted")
+			continue
+		} else if err != nil {
+			t.Fatalf("Select: %v", err)
+		}
+		if n != 0 {
+			t.Fatalf("Select: got %v ready file descriptors, expected 0", n)
+		}
+		break
+	}
+
+	dur := 250 * time.Millisecond
+	var took time.Duration
+	for {
+		// On some platforms (e.g. Linux), the passed-in timeval is
+		// updated by select(2). Make sure to reset to the full duration
+		// in case of an EINTR.
+		tv := unix.NsecToTimeval(int64(dur))
+		start := time.Now()
+		n, err := unix.Select(0, nil, nil, nil, &tv)
+		took = time.Since(start)
+		if err == unix.EINTR {
+			t.Logf("Select interrupted after %v", took)
+			continue
+		} else if err != nil {
+			t.Fatalf("Select: %v", err)
+		}
+		if n != 0 {
+			t.Fatalf("Select: got %v ready file descriptors, expected 0", n)
+		}
+		break
+	}
+
+	// On some BSDs the actual timeout might also be slightly less than the requested.
+	// Add an acceptable margin to avoid flaky tests.
+	if took < dur*2/3 {
+		t.Errorf("Select: got %v timeout, expected at least %v", took, dur)
+	}
+
+	rr, ww, err := os.Pipe()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer rr.Close()
+	defer ww.Close()
+
+	if _, err := ww.Write([]byte("HELLO GOPHER")); err != nil {
+		t.Fatal(err)
+	}
+
+	rFdSet := &unix.FdSet{}
+	fd := int(rr.Fd())
+	rFdSet.Set(fd)
+
+	for {
+		n, err := unix.Select(fd+1, rFdSet, nil, nil, nil)
+		if err == unix.EINTR {
+			t.Log("Select interrupted")
+			continue
+		} else if err != nil {
+			t.Fatalf("Select: %v", err)
+		}
+		if n != 1 {
+			t.Fatalf("Select: got %v ready file descriptors, expected 1", n)
+		}
+		break
+	}
+}
diff --git a/unix/zerrors_zos_s390x.go b/unix/zerrors_zos_s390x.go
index 4e87b4b..fc7d050 100644
--- a/unix/zerrors_zos_s390x.go
+++ b/unix/zerrors_zos_s390x.go
@@ -67,24 +67,43 @@
 	IPPORT_RESERVED                 = 1024
 	IPPORT_USERRESERVED             = 5000
 	IPPROTO_AH                      = 51
+	SOL_AH                          = 51
 	IPPROTO_DSTOPTS                 = 60
+	SOL_DSTOPTS                     = 60
 	IPPROTO_EGP                     = 8
+	SOL_EGP                         = 8
 	IPPROTO_ESP                     = 50
+	SOL_ESP                         = 50
 	IPPROTO_FRAGMENT                = 44
+	SOL_FRAGMENT                    = 44
 	IPPROTO_GGP                     = 2
+	SOL_GGP                         = 2
 	IPPROTO_HOPOPTS                 = 0
+	SOL_HOPOPTS                     = 0
 	IPPROTO_ICMP                    = 1
+	SOL_ICMP                        = 1
 	IPPROTO_ICMPV6                  = 58
+	SOL_ICMPV6                      = 58
 	IPPROTO_IDP                     = 22
+	SOL_IDP                         = 22
 	IPPROTO_IP                      = 0
+	SOL_IP                          = 0
 	IPPROTO_IPV6                    = 41
+	SOL_IPV6                        = 41
 	IPPROTO_MAX                     = 256
+	SOL_MAX                         = 256
 	IPPROTO_NONE                    = 59
+	SOL_NONE                        = 59
 	IPPROTO_PUP                     = 12
+	SOL_PUP                         = 12
 	IPPROTO_RAW                     = 255
+	SOL_RAW                         = 255
 	IPPROTO_ROUTING                 = 43
+	SOL_ROUTING                     = 43
 	IPPROTO_TCP                     = 6
+	SOL_TCP                         = 6
 	IPPROTO_UDP                     = 17
+	SOL_UDP                         = 17
 	IPV6_ADDR_PREFERENCES           = 32
 	IPV6_CHECKSUM                   = 19
 	IPV6_DONTFRAG                   = 29
@@ -186,6 +205,7 @@
 	MTM_SYNCHONLY                   = 0x00000200
 	MTM_REMOUNT                     = 0x00000100
 	MTM_NOSECURITY                  = 0x00000080
+	NFDBITS                         = 0x20
 	O_ACCMODE                       = 0x03
 	O_APPEND                        = 0x08
 	O_ASYNCSIG                      = 0x0200
@@ -359,6 +379,8 @@
 	S_IFMST                         = 0x00FF0000
 	TCP_KEEPALIVE                   = 0x8
 	TCP_NODELAY                     = 0x1
+	TCP_INFO                        = 0xb
+	TCP_USER_TIMEOUT                = 0x1
 	TIOCGWINSZ                      = 0x4008a368
 	TIOCSWINSZ                      = 0x8008a367
 	TIOCSBRK                        = 0x2000a77b
diff --git a/unix/zsyscall_zos_s390x.go b/unix/zsyscall_zos_s390x.go
index 8285ab8..f207945 100644
--- a/unix/zsyscall_zos_s390x.go
+++ b/unix/zsyscall_zos_s390x.go
@@ -364,6 +364,22 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func Errno2() (er2 int) {
+	uer2, _, _ := syscall_syscall(SYS___ERRNO2, 0, 0, 0)
+	er2 = int(uer2)
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Err2ad() (eadd *int) {
+	ueadd, _, _ := syscall_syscall(SYS___ERR2AD, 0, 0, 0)
+	eadd = (*int)(unsafe.Pointer(ueadd))
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func Exit(code int) {
 	syscall_syscall(SYS_EXIT, uintptr(code), 0, 0)
 	return
@@ -531,7 +547,18 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
-func Mount(path string, filesystem string, fstype string, mtm uint32, parmlen int32, parm string) (err error) {
+func W_Getmntent_A(buff *byte, size int) (lastsys int, err error) {
+	r0, _, e1 := syscall_syscall(SYS___W_GETMNTENT_A, uintptr(unsafe.Pointer(buff)), uintptr(size), 0)
+	lastsys = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func mount_LE(path string, filesystem string, fstype string, mtm uint32, parmlen int32, parm string) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
 	if err != nil {
@@ -561,7 +588,7 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
-func Unmount(filesystem string, mtm int) (err error) {
+func unmount(filesystem string, mtm int) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(filesystem)
 	if err != nil {
@@ -1215,3 +1242,14 @@
 	}
 	return
 }
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Select(nmsgsfds int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (ret int, err error) {
+	r0, _, e1 := syscall_syscall6(SYS_SELECT, uintptr(nmsgsfds), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), 0)
+	ret = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
diff --git a/unix/ztypes_zos_s390x.go b/unix/ztypes_zos_s390x.go
index 8bffde7..4ab638c 100644
--- a/unix/ztypes_zos_s390x.go
+++ b/unix/ztypes_zos_s390x.go
@@ -347,6 +347,10 @@
 	Name   [256]byte
 }
 
+type FdSet struct {
+	Bits [64]int32
+}
+
 // This struct is packed on z/OS so it can't be used directly.
 type Flock_t struct {
 	Type   int16