From 71e4cd670f794fc08493cd25995a8ce85e81b914 Mon Sep 17 00:00:00 2001 From: billofarrell Date: Wed, 28 Apr 2021 22:35:29 -0400 Subject: [PATCH] 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 TryBot-Result: Go Bot Reviewed-by: Ian Lance Taylor Trust: Emmanuel Odeke --- unix/fdset.go | 4 +- unix/mmap_zos_test.go | 2 +- unix/syscall_zos_s390x.go | 52 +++++++- unix/syscall_zos_test.go | 265 +++++++++++++++++++++++++++++++++++++ unix/zerrors_zos_s390x.go | 22 +++ unix/zsyscall_zos_s390x.go | 42 +++++- unix/ztypes_zos_s390x.go | 4 + 7 files changed, 384 insertions(+), 7 deletions(-) diff --git a/unix/fdset.go b/unix/fdset.go index b1e07b22..a8068f94 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 4c171824..8d9303b6 100644 --- a/unix/mmap_zos_test.go +++ b/unix/mmap_zos_test.go @@ -33,7 +33,7 @@ func TestMmap(t *testing.T) { 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 13f58d2b..1ffd8bfc 100644 --- a/unix/syscall_zos_s390x.go +++ b/unix/syscall_zos_s390x.go @@ -222,6 +222,8 @@ func (cmsg *Cmsghdr) SetLen(length int) { //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 @@ func Fstat(fd int, stat *Stat_t) (err error) { //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 SetNonblock(fd int, nonblocking bool) (err error) { 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 ef5e3913..640d2649 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 @@ import ( "syscall" "testing" "time" + "unsafe" "golang.org/x/sys/unix" ) @@ -604,3 +606,266 @@ func chtmpdir(t *testing.T) func() { 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 4e87b4be..fc7d0506 100644 --- a/unix/zerrors_zos_s390x.go +++ b/unix/zerrors_zos_s390x.go @@ -67,24 +67,43 @@ const ( 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 @@ const ( MTM_SYNCHONLY = 0x00000200 MTM_REMOUNT = 0x00000100 MTM_NOSECURITY = 0x00000080 + NFDBITS = 0x20 O_ACCMODE = 0x03 O_APPEND = 0x08 O_ASYNCSIG = 0x0200 @@ -359,6 +379,8 @@ const ( 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 8285ab84..f2079457 100644 --- a/unix/zsyscall_zos_s390x.go +++ b/unix/zsyscall_zos_s390x.go @@ -364,6 +364,22 @@ func Dup2(oldfd int, newfd int) (err error) { // 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 @@ func W_Getmntent(buff *byte, size int) (lastsys int, err error) { // 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 @@ func Mount(path string, filesystem string, fstype string, mtm uint32, parmlen in // 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 @@ func utimes(path string, timeval *[2]Timeval) (err error) { } 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 8bffde78..4ab638cb 100644 --- a/unix/ztypes_zos_s390x.go +++ b/unix/ztypes_zos_s390x.go @@ -347,6 +347,10 @@ type Dirent struct { 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